MCPcopy Create free account
hub / github.com/cloudflare/computer / createSyncClient

Function createSyncClient

packages/rpc/src/client.ts:39–121  ·  view source on GitHub ↗
(options: ClientOptions)

Source from the content-addressed store, hash-verified

37// method on the returned stub queues until the WebSocket reaches
38// readyState OPEN; capnweb's transport handles that.
39export function createSyncClient(options: ClientOptions): SyncClient {
40 const WS = options.WebSocketImpl ?? WebSocket;
41 const ws = new WS(options.url);
42 // The WebSocket cast crosses two type boundaries: the runtime
43 // ws (node `ws` package or global) is structurally compatible
44 // with capnweb's expected globalThis.WebSocket but TS can't
45 // bridge the nominal types. The RpcStub cast names the remote
46 // interface so the Proxy returned downstream is strongly typed.
47 const stub = newWebSocketRpcSession(ws as unknown as globalThis.WebSocket) as RpcStub<SyncRPC>;
48 // capnweb's RpcStub is a Proxy that exposes the remote interface
49 // as if it were local. We wrap it so callers see SyncClient
50 // (= SyncRPC + close).
51 const onEvent = options.onRPCEvent;
52 return new Proxy(stub, {
53 get(target, prop, receiver) {
54 if (prop === "close") {
55 return async () => {
56 // Dispose the root stub first — per capnweb's docs this
57 // is the documented way to close a session and lets the
58 // RPC layer send a clean abort frame before the transport
59 // dies. ws.close() below is belt-and-braces.
60 try {
61 (target as unknown as Disposable)[Symbol.dispose]?.();
62 } catch {
63 // already disposed; idempotent
64 }
65 await new Promise<void>((resolve) => {
66 const w = ws as unknown as { readyState: number; close: () => void };
67 if (w.readyState >= 2) {
68 resolve();
69 return;
70 }
71 (ws as unknown as EventTarget).addEventListener("close", () => resolve(), {
72 once: true,
73 });
74 w.close();
75 // Belt-and-braces: if `close` never fires (the socket
76 // was already torn down) the timeout breaks the await.
77 setTimeout(resolve, 200);
78 });
79 };
80 }
81 const value = Reflect.get(target, prop, receiver);
82 if (onEvent === undefined || typeof prop !== "string") return value;
83 // Capnweb's Proxy returns a callable RpcPromise/RpcStub for
84 // every string property. Wrap the call to time it and fire
85 // onRPCEvent. The wrapped value still behaves like an
86 // RpcPromise (thenable + property-access for pipelining) for
87 // calls that return synchronously-pipelined values; we only
88 // measure the awaited terminal call.
89 return (...args: unknown[]) => {
90 const start = Date.now();
91 const result = (value as (...a: unknown[]) => unknown)(...args);
92 // For non-thenable returns (streams), fire the event
93 // immediately with ok=true. The caller may still throw
94 // while reading the stream; observability for that path
95 // belongs to the caller.
96 if (result && typeof (result as { then?: unknown }).then === "function") {

Callers 2

wire.test.tsFile · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected