import type { BrowserRpcGenerationFence, BrowserRpcServerStreamPort, BrowserRpcUnaryPort, } from "../../application/ports/browser-rpc/index.ts"; import type { ClockPort } from "../../application/ports/clock-port.ts"; import type { Result } from "../../application/result.ts"; import { BROWSER_RPC_HARD_LIMITS, validateBrowserRpcContractBindings, type BrowserRpcOperationV3, type BrowserRpcProviderProfile, type BrowserRpcRequestEncoder, type BrowserRpcTransportFailureCode, } from "../../contracts/browser-rpc.ts"; import type { InstalledBoundaryMapper } from "../../contracts/boundary-mapper.ts"; import { createFailure, type AppFailure, type FailureKind, } from "../../contracts/errors.ts"; import type { RuntimeSchemaCodec } from "../../contracts/schema-registry.ts"; import { systemClock } from "../platform/system-clock.ts"; import type { BrowserRpcStreamFrame, BrowserRpcTransport, BrowserRpcTransportFailure, BrowserRpcUnaryTransportResult, } from "./transport.ts"; export type BrowserRpcObservationOutcome = | "SUCCESS" | "FAILED" | "ABORTED" | "TIMEOUT" | "CONTRACT_REJECTED"; export type BrowserRpcObservation = Readonly<{ operationId: string; protocol: "CONNECT_HTTP" | "GRPC_WEB"; runtimeProfileId: string; rpcKind: "UNARY" | "SERVER_STREAM"; outcome: BrowserRpcObservationOutcome; attemptCount: number; messageCount: number; }>; export type BrowserRpcObservationSink = Readonly<{ observe(observation: BrowserRpcObservation): void; }>; export type BrowserRpcRuntimeDependencies = Readonly<{ operations: Readonly>; profiles: Readonly>; schemaCodecs: Readonly>; mappers: Readonly>; requestEncoders: Readonly>; transports: Readonly>; generationFence?: BrowserRpcGenerationFence; clock?: ClockPort; observations?: BrowserRpcObservationSink; }>; export type BrowserRpcRuntime = Readonly<{ bindUnary( operationId: string, isOutput: (value: unknown) => value is Output, ): BrowserRpcUnaryPort; bindServerStream( operationId: string, isEvent: (value: unknown) => value is Event, ): BrowserRpcServerStreamPort; }>; type BoundOperation = Readonly<{ operation: BrowserRpcOperationV3; profile: BrowserRpcProviderProfile; encoder: BrowserRpcRequestEncoder; transport: BrowserRpcTransport; }>; type PreparedRequest = | Readonly<{ ok: true; value: unknown; encodedBytes: number; }> | Readonly<{ ok: false; error: AppFailure }>; type TimedResult = | Readonly<{ kind: "VALUE"; value: Value }> | Readonly<{ kind: "TIMEOUT" }> | Readonly<{ kind: "ABORTED" }> | Readonly<{ kind: "THREW" }>; const stableGenerationFence: BrowserRpcGenerationFence = Object.freeze({ capture: () => "stable", isCurrent: (token) => token === "stable", }); export function createBrowserRpcRuntime( dependencies: BrowserRpcRuntimeDependencies, ): BrowserRpcRuntime { const clock = dependencies.clock ?? systemClock; const generationFence = dependencies.generationFence ?? stableGenerationFence; validateRuntimeDependencies(dependencies); function bind( operationId: string, expectedKind: "UNARY" | "SERVER_STREAM", ): BoundOperation { const operation = dependencies.operations[operationId]; if (!operation || operation.rpcKind !== expectedKind) { throw new TypeError( `Browser RPC operation cannot be bound as ${expectedKind}: ${operationId}`, ); } const profile = dependencies.profiles[operation.runtimeProfileId]; const encoder = dependencies.requestEncoders[operation.requestEncoderId]; const transport = dependencies.transports[operation.runtimeProfileId]; if (!profile || !encoder || !transport) { throw new TypeError( `Browser RPC runtime binding is incomplete: ${operationId}`, ); } return Object.freeze({ operation, profile, encoder, transport }); } return Object.freeze({ bindUnary( operationId: string, isOutput: (value: unknown) => value is Output, ): BrowserRpcUnaryPort { if (typeof isOutput !== "function") { throw new TypeError("Browser RPC unary result guard is required."); } const bound = bind(operationId, "UNARY"); return Object.freeze({ execute: (input, context = {}) => executeUnary( dependencies, bound, input, context, isOutput, generationFence, clock, ), }); }, bindServerStream( operationId: string, isEvent: (value: unknown) => value is Event, ): BrowserRpcServerStreamPort { if (typeof isEvent !== "function") { throw new TypeError("Browser RPC stream result guard is required."); } const bound = bind(operationId, "SERVER_STREAM"); return Object.freeze({ open: (input, context = {}) => executeServerStream( dependencies, bound, input, context, isEvent, generationFence, clock, ), }); }, }); } async function executeUnary( dependencies: BrowserRpcRuntimeDependencies, bound: BoundOperation, input: unknown, context: Readonly<{ signal?: AbortSignal; idempotencyKey?: string }>, isOutput: (value: unknown) => value is Output, generationFence: BrowserRpcGenerationFence, clock: ClockPort, ): Promise> { const { operation, profile, encoder, transport } = bound; const generation = generationFence.capture(); const startedAt = clock.now(); const deadlineAt = startedAt + operation.totalDeadlineMs; const linked = linkedAbortController(context.signal); let attemptCount = 0; const finish = ( result: Result, outcome: BrowserRpcObservationOutcome, ): Result => { linked.cleanup(); observe(dependencies, operation, outcome, attemptCount, result.ok ? 1 : 0); return result; }; const contextFailure = validateCallContext(operation, context, 0); if (contextFailure) { return finish(failureResult(contextFailure), "CONTRACT_REJECTED"); } if (linked.controller.signal.aborted) { return finish( failureResult(callFailure(operation, 0, "REQUEST_ABORTED", "RPC_ABORTED")), "ABORTED", ); } const prepared = prepareRequest(dependencies, operation, encoder, input, 0); if (!prepared.ok) { return finish(failureResult(prepared.error), "CONTRACT_REJECTED"); } for (let attempt = 0; attempt < profile.maxAttempts; attempt += 1) { attemptCount = attempt + 1; const remainingMs = deadlineAt - clock.now(); if (remainingMs <= 0) { linked.controller.abort("deadline"); return finish( failureResult( callFailure( operation, attempt, "REQUEST_TIMEOUT", "RPC_TOTAL_DEADLINE_EXCEEDED", ), ), "TIMEOUT", ); } const call = Object.freeze({ operation, profile, request: prepared.value, encodedRequestBytes: prepared.encodedBytes, attempt: attempt + 1, timeoutMs: Math.max(1, Math.min(remainingMs, operation.totalDeadlineMs)), signal: linked.controller.signal, ...(context.idempotencyKey ? { idempotencyKey: context.idempotencyKey } : {}), }); const timed = await raceWithin( Promise.resolve().then(() => transport.invokeUnary!(call)), remainingMs, linked.controller.signal, clock, ); if (timed.kind === "TIMEOUT") { linked.controller.abort("deadline"); return finish( failureResult( callFailure( operation, attempt, "REQUEST_TIMEOUT", "RPC_TOTAL_DEADLINE_EXCEEDED", ), ), "TIMEOUT", ); } if (timed.kind === "ABORTED" || linked.controller.signal.aborted) { return finish( failureResult( callFailure(operation, attempt, "REQUEST_ABORTED", "RPC_ABORTED"), ), "ABORTED", ); } if (timed.kind === "THREW") { return finish( failureResult( callFailure( operation, attempt, "SERVER_FAILURE", "RPC_TRANSPORT_EXECUTION_FAILED", ), ), "FAILED", ); } const transportResult = validateUnaryTransportResult(timed.value); if (!transportResult) { return finish( failureResult(protocolFailure(operation, attempt)), "CONTRACT_REJECTED", ); } if (!transportResult.ok) { if ( shouldRetry( operation, profile, transportResult.failure, attempt, context.idempotencyKey, ) ) { const delay = retryDelay(profile, transportResult.failure, attempt); if (delay >= deadlineAt - clock.now()) { linked.controller.abort("deadline"); return finish( failureResult( callFailure( operation, attempt, "REQUEST_TIMEOUT", "RPC_RETRY_BUDGET_EXHAUSTED", ), ), "TIMEOUT", ); } try { await clock.sleep(delay, linked.controller.signal); } catch { return finish( failureResult( callFailure( operation, attempt, "REQUEST_ABORTED", "RPC_ABORTED", ), ), "ABORTED", ); } continue; } const mapped = mapTransportFailure( operation, attempt, transportResult.failure, ); return finish( failureResult(mapped), mapped.kind === "REQUEST_ABORTED" ? "ABORTED" : mapped.kind === "REQUEST_TIMEOUT" ? "TIMEOUT" : "FAILED", ); } const mapped = mapResponse( dependencies, operation, attempt, transportResult.message, transportResult.encodedBytes, generation, generationFence, isOutput, deadlineAt, clock, ); return finish( mapped, mapped.ok ? "SUCCESS" : mapped.error.kind === "REQUEST_TIMEOUT" ? "TIMEOUT" : "CONTRACT_REJECTED", ); } return finish( failureResult( callFailure( operation, Math.max(0, attemptCount - 1), "SERVER_FAILURE", "RPC_RETRY_EXHAUSTED", ), ), "FAILED", ); } async function* executeServerStream( dependencies: BrowserRpcRuntimeDependencies, bound: BoundOperation, input: unknown, context: Readonly<{ signal?: AbortSignal; idempotencyKey?: string }>, isEvent: (value: unknown) => value is Event, generationFence: BrowserRpcGenerationFence, clock: ClockPort, ): AsyncIterable> { const { operation, profile, encoder, transport } = bound; const generation = generationFence.capture(); const deadlineAt = clock.now() + operation.totalDeadlineMs; const linked = linkedAbortController(context.signal); let iterator: AsyncIterator | null = null; let observed = false; let messageCount = 0; const finish = (outcome: BrowserRpcObservationOutcome) => { if (observed) return; observed = true; observe(dependencies, operation, outcome, 1, messageCount); }; try { const contextFailure = validateCallContext(operation, context, 0); if (contextFailure) { finish("CONTRACT_REJECTED"); yield failureResult(contextFailure); return; } if (linked.controller.signal.aborted) { finish("ABORTED"); yield failureResult( callFailure(operation, 0, "REQUEST_ABORTED", "RPC_ABORTED"), ); return; } const prepared = prepareRequest( dependencies, operation, encoder, input, 0, ); if (!prepared.ok) { finish("CONTRACT_REJECTED"); yield failureResult(prepared.error); return; } const remainingMs = deadlineAt - clock.now(); if (remainingMs <= 0) { finish("TIMEOUT"); yield failureResult( callFailure( operation, 0, "REQUEST_TIMEOUT", "RPC_TOTAL_DEADLINE_EXCEEDED", ), ); return; } let stream: AsyncIterable; try { stream = transport.openServerStream!( Object.freeze({ operation, profile, request: prepared.value, encodedRequestBytes: prepared.encodedBytes, attempt: 1, timeoutMs: Math.max(1, remainingMs), signal: linked.controller.signal, ...(context.idempotencyKey ? { idempotencyKey: context.idempotencyKey } : {}), }), ); iterator = stream[Symbol.asyncIterator](); } catch { finish("FAILED"); yield failureResult( callFailure( operation, 0, "SERVER_FAILURE", "RPC_TRANSPORT_EXECUTION_FAILED", ), ); return; } let totalBytes = 0; let terminal: | Readonly<{ ok: true }> | Readonly<{ ok: false; failure: BrowserRpcTransportFailure }> | null = null; while (true) { const totalRemaining = deadlineAt - clock.now(); if (totalRemaining <= 0) { linked.controller.abort("deadline"); finish("TIMEOUT"); yield failureResult( callFailure( operation, 0, "REQUEST_TIMEOUT", "RPC_TOTAL_DEADLINE_EXCEEDED", ), ); return; } const waitMs = Math.min( totalRemaining, operation.idleDeadlineMs ?? totalRemaining, ); const next = await raceWithin( Promise.resolve().then(() => iterator!.next()), waitMs, linked.controller.signal, clock, ); if (next.kind === "TIMEOUT") { linked.controller.abort("idle-or-deadline"); const totalExpired = clock.now() >= deadlineAt; finish("TIMEOUT"); yield failureResult( callFailure( operation, 0, "REQUEST_TIMEOUT", totalExpired ? "RPC_TOTAL_DEADLINE_EXCEEDED" : "RPC_STREAM_IDLE_TIMEOUT", ), ); return; } if (next.kind === "ABORTED" || linked.controller.signal.aborted) { finish("ABORTED"); yield failureResult( callFailure(operation, 0, "REQUEST_ABORTED", "RPC_ABORTED"), ); return; } if (next.kind === "THREW") { finish("FAILED"); yield failureResult( callFailure( operation, 0, "SERVER_FAILURE", "RPC_STREAM_EXECUTION_FAILED", ), ); return; } if (next.value.done) { if (!terminal) { finish("CONTRACT_REJECTED"); yield failureResult(protocolFailure(operation, 0)); return; } if (!terminal.ok) { const mapped = mapTransportFailure( operation, 0, terminal.failure, ); finish( mapped.kind === "REQUEST_ABORTED" ? "ABORTED" : mapped.kind === "REQUEST_TIMEOUT" ? "TIMEOUT" : "FAILED", ); yield failureResult(mapped); return; } finish("SUCCESS"); return; } const frame = validateStreamFrame(next.value.value); if (!frame || terminal) { finish("CONTRACT_REJECTED"); yield failureResult(protocolFailure(operation, 0)); return; } if (frame.kind === "TERMINAL") { terminal = frame.ok ? Object.freeze({ ok: true }) : Object.freeze({ ok: false, failure: frame.failure }); continue; } messageCount += 1; totalBytes += frame.encodedBytes; if ( messageCount > operation.maxResponseMessages || frame.encodedBytes > operation.maxResponseMessageBytes || totalBytes > operation.maxTotalResponseBytes ) { linked.controller.abort("message-limit"); finish("CONTRACT_REJECTED"); yield failureResult( callFailure( operation, 0, "RESPONSE_BODY_LIMIT", "RPC_STREAM_MESSAGE_LIMIT", ), ); return; } const mapped = mapResponse( dependencies, operation, 0, frame.message, frame.encodedBytes, generation, generationFence, isEvent, deadlineAt, clock, ); if (!mapped.ok) { linked.controller.abort("mapping-failure"); finish( mapped.error.kind === "REQUEST_TIMEOUT" ? "TIMEOUT" : "CONTRACT_REJECTED", ); yield mapped; return; } yield mapped; } } finally { linked.controller.abort("stream-closed"); linked.cleanup(); if (iterator?.return) { try { await iterator.return(); } catch { // Cleanup cannot replace the already selected stream outcome. } } finish("ABORTED"); } } function validateRuntimeDependencies( dependencies: BrowserRpcRuntimeDependencies, ): void { const runtimeBindings: Record< string, Pick< BrowserRpcTransport, "runtimeProfileId" | "providerId" | "protocol" | "rpcKind" > > = Object.create(null); for (const [profileId, transport] of Object.entries( dependencies.transports, )) { if ( profileId !== transport.runtimeProfileId || Object.hasOwn(runtimeBindings, profileId) ) { throw new TypeError( `Browser RPC transport registry is invalid: ${profileId}`, ); } runtimeBindings[profileId] = Object.freeze({ runtimeProfileId: transport.runtimeProfileId, providerId: transport.providerId, protocol: transport.protocol, rpcKind: transport.rpcKind, }); } validateBrowserRpcContractBindings({ operations: dependencies.operations, profiles: dependencies.profiles, schemaCodecs: dependencies.schemaCodecs, mappers: dependencies.mappers, requestEncoders: dependencies.requestEncoders, runtimeBindings: Object.freeze(runtimeBindings), }); for (const operation of Object.values(dependencies.operations)) { if (!dependencies.transports[operation.runtimeProfileId]) { throw new TypeError( `Browser RPC transport is missing: ${operation.operationId}`, ); } } } function prepareRequest( dependencies: BrowserRpcRuntimeDependencies, operation: BrowserRpcOperationV3, encoder: BrowserRpcRequestEncoder, input: unknown, attempt: number, ): PreparedRequest { const schema = dependencies.schemaCodecs[operation.requestSchemaId]; let validated; try { validated = schema?.parse(input); } catch { validated = undefined; } if (!validated?.success) { return Object.freeze({ ok: false, error: callFailure( operation, attempt, "VALIDATION_REJECTED", "RPC_REQUEST_SCHEMA_INVALID", ), }); } try { const encoded = encoder.encode(validated.data); if ( !encoded.ok || !validEncodedByteCount( encoded.encodedBytes, operation.maxRequestMessageBytes, ) ) { return Object.freeze({ ok: false, error: callFailure( operation, attempt, "MAPPING_CONTRACT_VIOLATION", encoded.ok ? "RPC_REQUEST_MESSAGE_LIMIT" : safeCode(encoded.code), ), }); } return Object.freeze({ ok: true, value: encoded.value, encodedBytes: encoded.encodedBytes, }); } catch { return Object.freeze({ ok: false, error: callFailure( operation, attempt, "MAPPING_CONTRACT_VIOLATION", "RPC_REQUEST_ENCODING_FAILED", ), }); } } function mapResponse( dependencies: BrowserRpcRuntimeDependencies, operation: BrowserRpcOperationV3, attempt: number, message: unknown, encodedBytes: number, generation: unknown, generationFence: BrowserRpcGenerationFence, isOutput: (value: unknown) => value is Output, deadlineAt: number, clock: ClockPort, ): Result { if ( !validEncodedByteCount( encodedBytes, operation.maxResponseMessageBytes, ) ) { return failureResult( callFailure( operation, attempt, "RESPONSE_BODY_LIMIT", "RPC_RESPONSE_MESSAGE_LIMIT", ), ); } if (clock.now() >= deadlineAt) { return failureResult( callFailure( operation, attempt, "REQUEST_TIMEOUT", "RPC_TOTAL_DEADLINE_EXCEEDED", ), ); } const schema = dependencies.schemaCodecs[operation.responseSchemaId]; let validated; try { validated = schema?.parse(message); } catch { validated = undefined; } if (!validated?.success) { return failureResult( callFailure( operation, attempt, "SCHEMA_MISMATCH", "RPC_RESPONSE_SCHEMA_INVALID", ), ); } const mapper = dependencies.mappers[operation.mapperId]; let mapped; try { mapped = mapper?.map(validated.data); } catch { mapped = undefined; } if (!mapped?.ok) { return failureResult( callFailure( operation, attempt, "MAPPING_CONTRACT_VIOLATION", mapped?.code ?? "RPC_RESPONSE_MAPPING_FAILED", ), ); } const output = mapped.value; const outputMatches = safelyMatches(isOutput, output); if (!outputMatches) { return failureResult( callFailure( operation, attempt, "MAPPING_CONTRACT_VIOLATION", "RPC_BOUND_RESULT_TYPE_MISMATCH", ), ); } if (!generationFence.isCurrent(generation)) { return failureResult( callFailure( operation, attempt, "SCOPE_GENERATION_CHANGED", "RPC_SCOPE_GENERATION_CHANGED", ), ); } if (clock.now() >= deadlineAt) { return failureResult( callFailure( operation, attempt, "REQUEST_TIMEOUT", "RPC_TOTAL_DEADLINE_EXCEEDED", ), ); } return Object.freeze({ ok: true, value: output }); } function validateCallContext( operation: BrowserRpcOperationV3, context: Readonly<{ idempotencyKey?: string }>, attempt: number, ): AppFailure | null { const key = context.idempotencyKey; if ( (operation.idempotencyKeyPolicy === "REQUIRED" && !validIdempotencyKey(key)) || (operation.idempotencyKeyPolicy === "NONE" && key !== undefined) ) { return callFailure( operation, attempt, "VALIDATION_REJECTED", "RPC_IDEMPOTENCY_KEY_INVALID", ); } return null; } function validIdempotencyKey(value: unknown): value is string { return ( typeof value === "string" && value.length >= 8 && value.length <= 200 && ![...value].some((character) => { const codePoint = character.codePointAt(0) ?? 0; return codePoint <= 31 || codePoint === 127 || /\s/u.test(character); }) ); } function safelyMatches( guard: (value: unknown) => value is Output, value: unknown, ): value is Output { try { return guard(value); } catch { return false; } } function validateUnaryTransportResult( value: unknown, ): BrowserRpcUnaryTransportResult | null { if (!value || typeof value !== "object" || !("ok" in value)) return null; const candidate = value as BrowserRpcUnaryTransportResult; if (candidate.ok) { return validEncodedByteCount(candidate.encodedBytes, Number.MAX_SAFE_INTEGER) ? candidate : null; } return validTransportFailure(candidate.failure) ? candidate : null; } function validateStreamFrame(value: unknown): BrowserRpcStreamFrame | null { if (!value || typeof value !== "object" || !("kind" in value)) return null; const frame = value as BrowserRpcStreamFrame; if (frame.kind === "MESSAGE") { return validEncodedByteCount(frame.encodedBytes, Number.MAX_SAFE_INTEGER) ? frame : null; } if (frame.kind !== "TERMINAL" || typeof frame.ok !== "boolean") return null; return frame.ok || validTransportFailure(frame.failure) ? frame : null; } function validTransportFailure( failure: unknown, ): failure is BrowserRpcTransportFailure { if (!failure || typeof failure !== "object" || !("code" in failure)) { return false; } const candidate = failure as BrowserRpcTransportFailure; return ( TRANSPORT_FAILURE_CODES.has(candidate.code) && (candidate.retryAfterMs === undefined || (Number.isSafeInteger(candidate.retryAfterMs) && candidate.retryAfterMs >= 0 && candidate.retryAfterMs <= BROWSER_RPC_HARD_LIMITS.maxRetryAfterMs)) ); } const TRANSPORT_FAILURE_CODES = new Set([ "NETWORK_UNREACHABLE", "CANCELED", "DEADLINE_EXCEEDED", "UNAUTHENTICATED", "PERMISSION_DENIED", "NOT_FOUND", "ALREADY_EXISTS", "ABORTED", "FAILED_PRECONDITION", "INVALID_ARGUMENT", "RESOURCE_EXHAUSTED", "UNAVAILABLE", "UNIMPLEMENTED", "INTERNAL", "DATA_LOSS", "PROTOCOL_MISMATCH", "MESSAGE_LIMIT", ]); function shouldRetry( operation: BrowserRpcOperationV3, profile: BrowserRpcProviderProfile, failure: BrowserRpcTransportFailure, attempt: number, idempotencyKey: string | undefined, ): boolean { return ( profile.retryOwner === "FRONTEND_ADAPTER" && attempt + 1 < profile.maxAttempts && profile.retryableFailures.includes(failure.code) && (["SAFE", "IDEMPOTENT"].includes(operation.replayPolicy) || (operation.replayPolicy === "KEYED_COMMAND" && validIdempotencyKey(idempotencyKey))) ); } function retryDelay( profile: BrowserRpcProviderProfile, failure: BrowserRpcTransportFailure, attempt: number, ): number { const backoff = profile.backoffMs[attempt] ?? 0; const retryAfter = Math.min( failure.retryAfterMs ?? 0, profile.maxRetryAfterMs, ); return Math.max(backoff, retryAfter); } function mapTransportFailure( operation: BrowserRpcOperationV3, attempt: number, failure: BrowserRpcTransportFailure, ): AppFailure { const kind: FailureKind = failure.code === "NETWORK_UNREACHABLE" ? "NETWORK_UNREACHABLE" : failure.code === "CANCELED" ? "REQUEST_ABORTED" : failure.code === "DEADLINE_EXCEEDED" ? "REQUEST_TIMEOUT" : failure.code === "UNAUTHENTICATED" ? "AUTH_REQUIRED" : failure.code === "PERMISSION_DENIED" ? "FORBIDDEN" : failure.code === "NOT_FOUND" ? "NOT_FOUND" : ["ALREADY_EXISTS", "ABORTED"].includes(failure.code) ? "CONFLICT" : ["FAILED_PRECONDITION", "INVALID_ARGUMENT"].includes( failure.code, ) ? "VALIDATION_REJECTED" : failure.code === "RESOURCE_EXHAUSTED" ? "RATE_LIMITED" : failure.code === "MESSAGE_LIMIT" ? "RESPONSE_BODY_LIMIT" : ["PROTOCOL_MISMATCH", "UNIMPLEMENTED"].includes( failure.code, ) ? "API_CONTRACT_MISMATCH" : "SERVER_FAILURE"; return createFailure(kind, operation.operationId, attempt, { code: `RPC_${failure.code}`, ...(failure.retryAfterMs !== undefined ? { retryAfterMs: failure.retryAfterMs } : {}), }); } function protocolFailure( operation: BrowserRpcOperationV3, attempt: number, ): AppFailure { return callFailure( operation, attempt, "API_CONTRACT_MISMATCH", "RPC_PROTOCOL_MISMATCH", ); } function callFailure( operation: BrowserRpcOperationV3, attempt: number, kind: FailureKind, code: string, ): AppFailure { return createFailure(kind, operation.operationId, attempt, { code: safeCode(code), }); } function safeCode(value: string): string { return /^[A-Z][A-Z0-9_]{2,79}$/.test(value) ? value : "RPC_ADAPTER_REJECTED"; } function failureResult( error: AppFailure, ): Readonly<{ ok: false; error: AppFailure }> { return Object.freeze({ ok: false, error }); } function validEncodedByteCount(value: number, maximum: number): boolean { return ( Number.isSafeInteger(value) && value >= 0 && value <= maximum ); } function linkedAbortController(external?: AbortSignal): Readonly<{ controller: AbortController; cleanup(): void; }> { const controller = new AbortController(); const onAbort = () => controller.abort(external?.reason); external?.addEventListener("abort", onAbort, { once: true }); if (external?.aborted) onAbort(); return Object.freeze({ controller, cleanup() { external?.removeEventListener("abort", onAbort); }, }); } async function raceWithin( work: Promise, milliseconds: number, signal: AbortSignal, clock: ClockPort, ): Promise> { if (signal.aborted) return Object.freeze({ kind: "ABORTED" }); const timerController = new AbortController(); const onAbort = () => timerController.abort(signal.reason); signal.addEventListener("abort", onAbort, { once: true }); const workResult = work.then, TimedResult>( (value) => Object.freeze({ kind: "VALUE", value }), () => Object.freeze({ kind: "THREW" }), ); const timerResult = clock.sleep(milliseconds, timerController.signal).then< TimedResult, TimedResult >( () => Object.freeze({ kind: "TIMEOUT" }), () => Object.freeze({ kind: "ABORTED" }), ); const selected = await Promise.race([workResult, timerResult]); timerController.abort("race-complete"); signal.removeEventListener("abort", onAbort); return signal.aborted && selected.kind === "VALUE" ? Object.freeze({ kind: "ABORTED" }) : selected; } function observe( dependencies: BrowserRpcRuntimeDependencies, operation: BrowserRpcOperationV3, outcome: BrowserRpcObservationOutcome, attemptCount: number, messageCount: number, ): void { try { dependencies.observations?.observe( Object.freeze({ operationId: operation.operationId, protocol: operation.protocol, runtimeProfileId: operation.runtimeProfileId, rpcKind: operation.rpcKind, outcome, attemptCount: Math.max(1, attemptCount), messageCount: Math.max(0, messageCount), }), ); } catch { // Observation cannot change the selected application result. } }