import { randomUUID } from 'node:crypto' import { remoteRuntimeTimeoutError } from './remote-runtime-request-frames' import type { RuntimeRpcResponse } from './runtime-rpc-envelope' import { toRemoteRuntimeClientError } from './remote-runtime-shared-control-protocol' import { rejectSharedControlPendingRequest } from './remote-runtime-shared-control-state' import type { SharedControlPendingRequest } from './remote-runtime-shared-control-types' export function requestSharedControl(args: { pendingRequests: Map> method: string params: unknown timeoutMs: number ensureReady: () => Promise send: (requestId: string, method: string, params: unknown) => void // Why: default off — ordinary short RPCs keep an absolute deadline. Only // long-polls routed through this path opt in so keepalives extend them. refreshTimeoutOnKeepalive?: boolean }): Promise> { const requestId = randomUUID() return new Promise>((resolve, reject) => { const timeout = setTimeout(() => { const pending = args.pendingRequests.get(requestId) if (!pending) { return } args.pendingRequests.delete(requestId) // Why: one stalled method does not prove the shared socket is dead; // socket liveness owns connection-wide teardown so other RPCs survive. pending.reject(remoteRuntimeTimeoutError()) }, args.timeoutMs) args.pendingRequests.set(requestId, { method: args.method, resolve: resolve as (response: RuntimeRpcResponse) => void, reject, timeout, refreshTimeoutOnKeepalive: args.refreshTimeoutOnKeepalive ?? false }) void args.ensureReady().then( () => args.send(requestId, args.method, args.params), (error) => rejectSharedControlPendingRequest( args.pendingRequests, requestId, toRemoteRuntimeClientError(error) ) ) }) }