Files
orca/src/shared/remote-runtime-request-socket.ts
T
OrcaWinandm4air 34e2e5f469 refactor(remote): clients connect through a paired-connection handle, not a pairing offer (#27533)
Desktop, CLI, web and runtime transports now take a PairedRuntimeConnection
(endpoint, pinned host key, credential) instead of a PairingOffer. Offers stay on
the pairing input path (codes, QR, links, environment add) and are converted with
pairedRuntimeConnectionFromOffer. The credential is token-only today; request
envelopes still carry the same deviceToken, so the wire is unchanged.

This lets device-key and ticket credentials replace the token later without
touching every connect call site again.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 14:02:03 -07:00

247 lines
7.6 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import WebSocket from 'ws'
import { abortSignalReason, throwIfSignalAborted } from './abort-signal-reason'
import type { PairedRuntimeConnection } from './paired-runtime-connection'
import type { RuntimeCapability } from './protocol-version'
import { remoteRuntimeClientCapabilities } from './remote-runtime-client-capabilities'
import {
createRemoteRuntimeClientHandshake,
formatRemoteRuntimeCloseMessage,
ignoreSettledRemoteRuntimeSocketError
} from './remote-runtime-client-handshake'
import { RemoteRuntimeClientError } from './remote-runtime-client-error'
import {
remoteRuntimeConnectFailureMessage,
remoteRuntimeConnectOptions
} from './remote-runtime-connect-bound'
import {
REMOTE_RUNTIME_MAX_WEBSOCKET_FRAME_BYTES,
serializeRemoteRuntimePayload,
serializeRemoteRuntimeRpcRequest
} from './remote-runtime-memory-limits'
import {
prepareRemoteRuntimeRequest,
releaseRemoteRuntimePreparedRequest,
takeRemoteRuntimePreparedRequest
} from './remote-runtime-prepared-request-admission'
import { RemoteRuntimeRequestResponseRouter } from './remote-runtime-request-response-router'
import type { RuntimeOrchestrationEnvelope, RuntimeRpcResponse } from './runtime-rpc-envelope'
import type { RuntimeStatus } from './runtime-types'
import { isSafeTimerDelayMs, MAX_TIMER_DELAY_MS } from './timer-delay'
export async function sendRemoteRuntimeRequestOnSocket<TResult>(
connection: PairedRuntimeConnection,
method: string,
params: unknown,
timeoutMs: number,
envelope?: RuntimeOrchestrationEnvelope,
validateStatus?: (response: RuntimeRpcResponse<RuntimeStatus>) => void,
signal?: AbortSignal,
clientCapabilities: readonly RuntimeCapability[] = []
): Promise<RuntimeRpcResponse<TResult>> {
throwIfSignalAborted(signal)
if (!isSafeTimerDelayMs(timeoutMs)) {
throw new RemoteRuntimeClientError(
'invalid_argument',
`Runtime request timeout must be an integer between 0 and ${MAX_TIMER_DELAY_MS}ms.`
)
}
const requestId = randomUUID()
const statusRequestId = validateStatus ? randomUUID() : null
const serializedStatusRequest = statusRequestId
? serializeRemoteRuntimePayload({
id: statusRequestId,
deviceToken: connection.credential.deviceToken,
method: 'status.get'
})
: null
const pendingRequest = {
preparedRequest: prepareRemoteRuntimeRequest(new Map(), () =>
serializeRemoteRuntimeRpcRequest({
requestId,
deviceToken: connection.credential.deviceToken,
method,
params,
envelope
})
)
}
let serializedRequest = takeRemoteRuntimePreparedRequest(pendingRequest)
return await new Promise<RuntimeRpcResponse<TResult>>((resolve, reject) => {
const handshake = createRemoteRuntimeClientHandshake(connection.hostPublicKeyB64)
let settled = false
let ws: WebSocket | null = null
let router: RemoteRuntimeRequestResponseRouter<TResult>
const cleanupSocketListeners = (): void => {
signal?.removeEventListener('abort', onAbort)
const socket = ws
if (!socket) {
return
}
socket.off('open', onOpen)
socket.off('error', onError)
socket.off('close', onClose)
socket.off('message', onMessage)
if (socket.readyState !== WebSocket.CLOSED) {
socket.on('error', ignoreSettledRemoteRuntimeSocketError)
}
}
let timeout = setTimeout(onTimeout, timeoutMs)
function onTimeout(): void {
finishError(
new RemoteRuntimeClientError(
'runtime_timeout',
'Timed out waiting for the remote Orca runtime to respond.',
{ pairingStage: router.pairingStage }
)
)
}
function onAbort(): void {
finishError(abortSignalReason(signal!))
}
function refreshTimeout(): void {
const refreshableTimeout = timeout as { refresh?: () => void }
if (typeof refreshableTimeout.refresh === 'function') {
refreshableTimeout.refresh()
return
}
clearTimeout(timeout)
timeout = setTimeout(onTimeout, timeoutMs)
}
const finish = (
result: { ok: true; response: RuntimeRpcResponse<TResult> } | { ok: false; error: Error }
): void => {
if (settled) {
return
}
settled = true
clearTimeout(timeout)
try {
cleanupSocketListeners()
ws?.close()
} catch {
// ignore best-effort close
}
if (result.ok === false) {
reject(result.error)
} else {
resolve(result.response)
}
}
const finishError = (error: Error): void => finish({ ok: false, error })
const finishResponse = (response: RuntimeRpcResponse<TResult>): void =>
finish({ ok: true, response })
function sendRequestedRpc(): void {
const request = serializedRequest
serializedRequest = null
if (request === null) {
finishError(
new RemoteRuntimeClientError(
'remote_runtime_unavailable',
'Remote Orca runtime request was released before it could be sent.'
)
)
return
}
ws?.send(handshake.channel.sealText(request))
}
router = new RemoteRuntimeRequestResponseRouter({
handshake,
sealAuth: () =>
handshake.sealAuth(
{ kind: 'token', deviceToken: connection.credential.deviceToken },
{ clientCapabilities: remoteRuntimeClientCapabilities(clientCapabilities) }
),
isSettled: () => settled,
serializedStatusRequest,
requestId,
statusRequestId,
validateStatus,
send: (frame) => ws?.send(frame),
sendRequestedRpc,
refreshTimeout,
finishError,
finishResponse
})
signal?.addEventListener('abort', onAbort, { once: true })
if (signal?.aborted) {
onAbort()
return
}
try {
const connectOptions = remoteRuntimeConnectOptions({
maxPayload: REMOTE_RUNTIME_MAX_WEBSOCKET_FRAME_BYTES
})
ws = new WebSocket(connection.endpoint, connectOptions)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
finishError(
new RemoteRuntimeClientError('invalid_argument', `Invalid remote endpoint: ${message}`)
)
return
}
function onOpen(): void {
ws?.send(handshake.helloText())
}
function onError(error: Error): void {
finishError(
new RemoteRuntimeClientError(
'remote_runtime_unavailable',
remoteRuntimeConnectFailureMessage(error, connection.endpoint),
{ pairingStage: router.pairingStage }
)
)
}
function onClose(code: number, reason: Buffer): void {
if (!settled) {
finishError(
new RemoteRuntimeClientError(
'remote_runtime_unavailable',
formatRemoteRuntimeCloseMessage(code, reason),
{ pairingStage: router.pairingStage, closeCode: code }
)
)
}
}
function onMessage(data: WebSocket.RawData, isBinary: boolean): void {
if (settled) {
return
}
if (isBinary) {
finishError(
new RemoteRuntimeClientError(
'invalid_runtime_response',
'Remote Orca runtime returned an unexpected binary frame.',
{
pairingStage:
router.state === 'awaiting_ready' ? 'host-identity' : router.pairingStage
}
)
)
return
}
router.handleTextFrame(data.toString())
}
ws.once('open', onOpen)
ws.once('error', onError)
ws.on('close', onClose)
ws.on('message', onMessage)
}).finally(() => releaseRemoteRuntimePreparedRequest(pendingRequest))
}