(options: {
url: string;
WebSocketImpl?: typeof WebSocket;
})
| 134 | // shim from createSyncClient doesn't compose cleanly. Add when a |
| 135 | // caller actually needs it. |
| 136 | export function createWorkspaceClient(options: { |
| 137 | url: string; |
| 138 | WebSocketImpl?: typeof WebSocket; |
| 139 | }): WorkspaceClient { |
| 140 | const WS = options.WebSocketImpl ?? WebSocket; |
| 141 | const ws = new WS(options.url); |
| 142 | // Same WebSocket / RpcStub cast pattern as createSyncClient — |
| 143 | // see comment there. WorkspaceRPC adds the composite shape so |
| 144 | // callers can pipeline `.sync.push(...)` and `.shell.exec(...)`. |
| 145 | const stub = newWebSocketRpcSession( |
| 146 | ws as unknown as globalThis.WebSocket, |
| 147 | ) as RpcStub<WorkspaceRPC>; |
| 148 | return new Proxy(stub, { |
| 149 | get(target, prop, receiver) { |
| 150 | if (prop === "close") { |
| 151 | return async () => { |
| 152 | // Same dispose-before-close pattern as createSyncClient. |
| 153 | try { |
| 154 | (target as unknown as Disposable)[Symbol.dispose]?.(); |
| 155 | } catch { |
| 156 | // already disposed; idempotent |
| 157 | } |
| 158 | await new Promise<void>((resolve) => { |
| 159 | const w = ws as unknown as { readyState: number; close: () => void }; |
| 160 | if (w.readyState >= 2) { |
| 161 | resolve(); |
| 162 | return; |
| 163 | } |
| 164 | (ws as unknown as EventTarget).addEventListener("close", () => resolve(), { |
| 165 | once: true, |
| 166 | }); |
| 167 | w.close(); |
| 168 | setTimeout(resolve, 200); |
| 169 | }); |
| 170 | }; |
| 171 | } |
| 172 | return Reflect.get(target, prop, receiver); |
| 173 | }, |
| 174 | // As in createSyncClient, the Proxy is structurally a |
| 175 | // WorkspaceRPC stub + close(); route through unknown so TS |
| 176 | // accepts the WorkspaceClient landing type. |
| 177 | }) as unknown as WorkspaceClient; |
| 178 | } |
no outgoing calls
no test coverage detected