@firtoz/socka 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/assets/banner.png +0 -0
- package/dist/SockaWebSocketSession-Bru8yFcK.d.ts +107 -0
- package/dist/bun/index.d.ts +38 -0
- package/dist/bun/index.js +121 -0
- package/dist/bun/index.js.map +1 -0
- package/dist/chunk-45D4T232.js +236 -0
- package/dist/chunk-45D4T232.js.map +1 -0
- package/dist/chunk-5WQTYLIC.js +46 -0
- package/dist/chunk-5WQTYLIC.js.map +1 -0
- package/dist/chunk-AM7PB26G.js +421 -0
- package/dist/chunk-AM7PB26G.js.map +1 -0
- package/dist/chunk-MZCQHJXY.js +158 -0
- package/dist/chunk-MZCQHJXY.js.map +1 -0
- package/dist/chunk-YMT4HAH7.js +20 -0
- package/dist/chunk-YMT4HAH7.js.map +1 -0
- package/dist/client/index.d.ts +119 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/do/index.d.ts +80 -0
- package/dist/do/index.js +110 -0
- package/dist/do/index.js.map +1 -0
- package/dist/hono/cloudflare-workers.d.ts +21 -0
- package/dist/hono/cloudflare-workers.js +68 -0
- package/dist/hono/cloudflare-workers.js.map +1 -0
- package/dist/hono/index.d.ts +30 -0
- package/dist/hono/index.js +74 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/react/index.d.ts +72 -0
- package/dist/react/index.js +126 -0
- package/dist/react/index.js.map +1 -0
- package/dist/server/index.d.ts +27 -0
- package/dist/server/index.js +63 -0
- package/dist/server/index.js.map +1 -0
- package/dist/socka-report-error-DzFI2Tr7.d.ts +206 -0
- package/docs/README.md +18 -0
- package/docs/client.md +85 -0
- package/docs/comparison.md +36 -0
- package/docs/durable-objects.md +74 -0
- package/docs/events.md +48 -0
- package/docs/getting-started.md +138 -0
- package/docs/lifecycle.md +31 -0
- package/docs/multi-room.md +31 -0
- package/docs/peers.md +85 -0
- package/docs/reference.md +123 -0
- package/docs/server.md +124 -0
- package/examples/minimal-socka.ts +31 -0
- package/package.json +148 -0
- package/roadmap.md +8 -0
- package/skills/socka/core-rpc/SKILL.md +36 -0
- package/skills/socka/do-session/SKILL.md +33 -0
- package/skills/socka/standard-schema/SKILL.md +26 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/SockaWebSocketSession.ts"],"names":[],"mappings":"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","file":"chunk-45D4T232.js","sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { exhaustiveGuard } from \"@firtoz/maybe-error\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContract,\n\tSockaContractConfig,\n} from \"../core/contract\";\nimport {\n\tSockaWireError,\n\tdecodeSockaWire,\n\tencodeServerResponse,\n\tencodeServerError,\n\tencodeServerEvent,\n\ttype SockaClientRequestFrame,\n\ttype SockaWireFrame,\n} from \"../core/envelope\";\nimport {\n\tencodeSockaWire,\n\tparseWirePayload,\n\ttype SockaWireFormat,\n} from \"../core/wire-codec\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport { parseStandardSchema } from \"../core/validate\";\nimport { SockaError } from \"../core/socka-error\";\nimport type {\n\tSockaWebSocketInit,\n\tSockaWebSocketSessionConfig,\n} from \"./SockaWebSocketSessionConfig\";\n\n/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */\ntype EmptySockaSessionData = Record<string, never>;\n\nexport type { SockaWebSocketInit, SockaWebSocketSessionConfig };\n\n/** Session that can send a wire-level server event (already validated). */\nexport type SockaEmitCapable = {\n\temitWireEvent(event: string, body: unknown): void;\n};\n\n/**\n * Contract-typed session surface for handlers that push to clients.\n */\nexport interface SockaPushSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n> {\n\temitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void>;\n\tbroadcastPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t\texcludeSelf?: boolean,\n\t): Promise<void>;\n}\n\n/**\n * Broadcast a socka server event to every session in the map (optionally\n * excluding the caller). Payload must already be contract-validated.\n *\n * Exclusion uses the **WebSocket** identity (`self.websocket`), not the session\n * object reference, so the same `sessions` map can hold `SockaDoSession` while\n * `broadcastPush` runs on `this.socka` (inner {@link SockaWebSocketSession}).\n */\nexport function broadcastSockaEventToPeers(\n\tsessions: Map<WebSocket, SockaEmitCapable>,\n\tself: SockaEmitCapable & { readonly websocket: WebSocket },\n\tevent: string,\n\tbody: unknown,\n\texcludeSelf = false,\n): void {\n\tfor (const [ws, session] of sessions) {\n\t\tif (excludeSelf && ws === self.websocket) continue;\n\t\tsession.emitWireEvent(event, body);\n\t}\n}\n\n/**\n * Runtime-agnostic socka server session: standard {@link WebSocket} wire\n * dispatch without Cloudflare Durable Object APIs.\n */\nexport class SockaWebSocketSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData = EmptySockaSessionData,\n> implements SockaPushSession<TContract>\n{\n\tprivate readonly config: SockaWebSocketSessionConfig<TContract, TData>;\n\tprivate readonly wireFormat: SockaWireFormat;\n\tprivate _data!: TData;\n\n\tpublic constructor(\n\t\tpublic readonly websocket: WebSocket,\n\t\tprotected readonly sessions: Map<\n\t\t\tWebSocket,\n\t\t\tSockaWebSocketSession<TContract, TData>\n\t\t>,\n\t\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\t\tinit?: SockaWebSocketInit,\n\t) {\n\t\tthis.config = config;\n\t\tthis.wireFormat = config.wireFormat ?? \"json\";\n\t\tconst create =\n\t\t\tconfig.createData ?? ((_i: SockaWebSocketInit) => ({}) as TData);\n\t\tthis._data = create(init ?? {});\n\t}\n\n\tpublic get data(): TData {\n\t\treturn this._data;\n\t}\n\n\t/**\n\t * Invokes the user {@link typeof SockaWebSocketSessionConfig.handleClose} callback.\n\t * Server adapters should call this when the WebSocket closes, **before** deleting\n\t * this session from the shared `sessions` map.\n\t */\n\tpublic async invokeHandleClose(): Promise<void> {\n\t\tawait this.config.handleClose(this);\n\t}\n\n\tpublic async handleRawMessage(rawMessage: string): Promise<void> {\n\t\tif (this.wireFormat !== \"json\") {\n\t\t\tawait this.reportValidationError(\n\t\t\t\tnew Error(\"socka: unexpected JSON frame in msgpack mode\"),\n\t\t\t\trawMessage,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst deserialize = this.config.deserializeJson ?? JSON.parse;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = deserialize(rawMessage);\n\t\t} catch {\n\t\t\tawait this.reportValidationError(\n\t\t\t\tnew Error(\"socka: invalid JSON\"),\n\t\t\t\trawMessage,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tawait this.dispatchAfterParsed(parsed, rawMessage);\n\t}\n\n\tpublic async handleBinaryMessage(buffer: ArrayBuffer): Promise<void> {\n\t\tif (this.wireFormat !== \"msgpack\") {\n\t\t\tawait this.reportValidationError(\n\t\t\t\tnew Error(\"socka: unexpected binary frame in JSON mode\"),\n\t\t\t\tbuffer,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = parseWirePayload(buffer, \"msgpack\");\n\t\t} catch (err) {\n\t\t\tawait this.reportValidationError(\n\t\t\t\terr instanceof Error ? err : new Error(\"socka: msgpack decode failed\"),\n\t\t\t\tbuffer,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tawait this.dispatchAfterParsed(parsed, buffer);\n\t}\n\n\tprivate async dispatchAfterParsed(\n\t\tparsed: unknown,\n\t\toriginalWire: unknown,\n\t): Promise<void> {\n\t\tlet decoded: ReturnType<typeof decodeSockaWire>;\n\t\ttry {\n\t\t\tdecoded = decodeSockaWire(parsed);\n\t\t} catch (err) {\n\t\t\tif (err instanceof SockaWireError) {\n\t\t\t\tawait this.reportValidationError(err, originalWire);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\n\t\tswitch (decoded.kind) {\n\t\t\tcase \"clientRequest\":\n\t\t\t\tawait this.dispatchClientRequest(decoded.frame, originalWire);\n\t\t\t\treturn;\n\t\t\tcase \"serverResponse\":\n\t\t\tcase \"serverError\":\n\t\t\tcase \"serverEvent\":\n\t\t\t\tawait this.reportValidationError(\n\t\t\t\t\tnew Error(\"socka: unexpected server-originated frame from client\"),\n\t\t\t\t\tparsed,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\tdefault:\n\t\t\t\texhaustiveGuard(decoded);\n\t\t}\n\t}\n\n\tprivate async dispatchClientRequest(\n\t\tframe: SockaClientRequestFrame,\n\t\t_originalWire: unknown,\n\t): Promise<void> {\n\t\tconst rpcName = frame.rpc;\n\t\tconst procedure = this.config.contract.calls[rpcName];\n\n\t\tif (!procedure) {\n\t\t\tconst errorFrame = encodeServerError(\n\t\t\t\tframe.id,\n\t\t\t\t`Unknown call: ${rpcName}`,\n\t\t\t);\n\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\treturn;\n\t\t}\n\n\t\tlet validatedInput: unknown;\n\t\tif (procedure.input) {\n\t\t\ttry {\n\t\t\t\tvalidatedInput = await parseStandardSchema(procedure.input, frame.body);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg =\n\t\t\t\t\terr instanceof Error ? err.message : \"Input validation failed\";\n\t\t\t\tconst errorFrame = encodeServerError(frame.id, msg);\n\t\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tif (procedure.input) {\n\t\t\t\tconst handler = this.config.handlers[rpcName] as (\n\t\t\t\t\tinput: unknown,\n\t\t\t\t\ts: SockaWebSocketSession<TContract, TData>,\n\t\t\t\t) => unknown | Promise<unknown>;\n\t\t\t\tresult = await handler(validatedInput, this);\n\t\t\t} else {\n\t\t\t\tconst handler = this.config.handlers[rpcName] as (\n\t\t\t\t\ts: SockaWebSocketSession<TContract, TData>,\n\t\t\t\t) => unknown | Promise<unknown>;\n\t\t\t\tresult = await handler(this);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.config.onHandlerError?.(err, rpcName, validatedInput, this);\n\t\t\tconst sockaErr =\n\t\t\t\terr instanceof SockaError\n\t\t\t\t\t? err\n\t\t\t\t\t: new SockaError(\n\t\t\t\t\t\t\terr instanceof Error ? err.message : \"Handler failed\",\n\t\t\t\t\t\t);\n\t\t\tconst errorFrame = encodeServerError(frame.id, sockaErr.message);\n\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\treturn;\n\t\t}\n\n\t\tlet validatedOutput: unknown;\n\t\ttry {\n\t\t\tvalidatedOutput = await parseStandardSchema(procedure.output, result);\n\t\t} catch (err) {\n\t\t\tconst msg =\n\t\t\t\terr instanceof Error ? err.message : \"Output validation failed\";\n\t\t\tconst errorFrame = encodeServerError(frame.id, msg);\n\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\treturn;\n\t\t}\n\n\t\tconst responseFrame = encodeServerResponse(\n\t\t\tframe.id,\n\t\t\trpcName,\n\t\t\tvalidatedOutput,\n\t\t);\n\t\tthis.sendWireFrame(responseFrame);\n\t}\n\n\tprivate encodeOutgoing(frame: SockaWireFrame): string | Uint8Array {\n\t\treturn encodeSockaWire(\n\t\t\tframe,\n\t\t\tthis.wireFormat,\n\t\t\tthis.config.serializeJson ?? JSON.stringify,\n\t\t);\n\t}\n\n\tprivate sendWireFrame(frame: SockaWireFrame): void {\n\t\tif (this.websocket.readyState !== WebSocket.OPEN) {\n\t\t\treturn;\n\t\t}\n\t\tconst encoded = this.encodeOutgoing(frame);\n\t\tif (typeof encoded === \"string\") {\n\t\t\tthis.websocket.send(encoded);\n\t\t\treturn;\n\t\t}\n\t\tconst copy = new Uint8Array(encoded.byteLength);\n\t\tcopy.set(encoded);\n\t\tthis.websocket.send(copy.buffer);\n\t}\n\n\t/**\n\t * Send a server event frame (wire). Prefer {@link emitPush} so\n\t * payloads are validated against the contract.\n\t */\n\tpublic emitWireEvent(event: string, body: unknown): void {\n\t\tconst frame = encodeServerEvent(event, body);\n\t\tthis.sendWireFrame(frame);\n\t}\n\n\tpublic async emitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void> {\n\t\tconst schema = this.config.contract.pushes[name];\n\t\tif (!schema) {\n\t\t\tthrow new Error(`socka: unknown push ${String(name)}`);\n\t\t}\n\t\tconst validated = await parseStandardSchema(\n\t\t\tschema as StandardSchemaV1<unknown, InferSockaPushPayload<TContract, K>>,\n\t\t\tbody,\n\t\t);\n\t\tthis.emitWireEvent(name, validated);\n\t}\n\n\tpublic async broadcastPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t\texcludeSelf = false,\n\t): Promise<void> {\n\t\tconst schema = this.config.contract.pushes[name];\n\t\tif (!schema) {\n\t\t\tthrow new Error(`socka: unknown push ${String(name)}`);\n\t\t}\n\t\tconst validated = await parseStandardSchema(\n\t\t\tschema as StandardSchemaV1<unknown, InferSockaPushPayload<TContract, K>>,\n\t\t\tbody,\n\t\t);\n\t\tbroadcastSockaEventToPeers(\n\t\t\tthis.sessions,\n\t\t\tthis,\n\t\t\tname,\n\t\t\tvalidated,\n\t\t\texcludeSelf,\n\t\t);\n\t}\n\n\tprivate async reportValidationError(\n\t\terror: unknown,\n\t\toriginalMessage: unknown,\n\t): Promise<void> {\n\t\tif (this.config.onValidationError) {\n\t\t\tawait this.config.onValidationError(error, originalMessage);\n\t\t} else {\n\t\t\tconsole.error(\"socka: validation error:\", error, originalMessage);\n\t\t}\n\t}\n}\n\n/**\n * Invoke {@link SockaWebSocketSessionConfig.onAttached} after the session is\n * registered in the shared map.\n */\nexport function runSockaSessionOnAttached<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\tsession: SockaWebSocketSession<TContract, TData>,\n): void {\n\tconst cb = config.onAttached;\n\tif (!cb) return;\n\ttry {\n\t\tconst result = cb(session);\n\t\tvoid Promise.resolve(result).catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverOnAttached\",\n\t\t\t\terror,\n\t\t\t});\n\t\t});\n\t} catch (error) {\n\t\treportSockaError(config.reportError, { kind: \"serverOnAttached\", error });\n\t}\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/server/dispatchSockaInboundMessage.ts
|
|
2
|
+
async function dispatchSockaInboundMessage(session, wireFormat, data) {
|
|
3
|
+
if (typeof data === "string") {
|
|
4
|
+
await session.handleRawMessage(data);
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
8
|
+
if (wireFormat === "json") {
|
|
9
|
+
await session.handleRawMessage(data.toString("utf8"));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
await session.handleBinaryMessage(new Uint8Array(data).buffer);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (data instanceof ArrayBuffer) {
|
|
16
|
+
if (wireFormat === "json") {
|
|
17
|
+
await session.handleRawMessage(new TextDecoder().decode(data));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await session.handleBinaryMessage(data);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (data instanceof Blob) {
|
|
24
|
+
if (wireFormat === "json") {
|
|
25
|
+
await session.handleRawMessage(await data.text());
|
|
26
|
+
} else {
|
|
27
|
+
await session.handleBinaryMessage(await data.arrayBuffer());
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (ArrayBuffer.isView(data)) {
|
|
32
|
+
const v = data;
|
|
33
|
+
const view = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
|
|
34
|
+
if (wireFormat === "json") {
|
|
35
|
+
await session.handleRawMessage(new TextDecoder().decode(view));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const copy = new Uint8Array(view.length);
|
|
39
|
+
copy.set(view);
|
|
40
|
+
await session.handleBinaryMessage(copy.buffer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { dispatchSockaInboundMessage };
|
|
45
|
+
//# sourceMappingURL=chunk-5WQTYLIC.js.map
|
|
46
|
+
//# sourceMappingURL=chunk-5WQTYLIC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/dispatchSockaInboundMessage.ts"],"names":[],"mappings":";AAUA,eAAsB,2BAAA,CAIrB,OAAA,EACA,UAAA,EACA,IAAA,EACgB;AAChB,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC7B,IAAA,MAAM,OAAA,CAAQ,iBAAiB,IAAI,CAAA;AACnC,IAAA;AAAA,EACD;AACA,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AAC3D,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,OAAA,CAAQ,gBAAA,CAAiB,IAAA,CAAK,QAAA,CAAS,MAAM,CAAC,CAAA;AACpD,MAAA;AAAA,IACD;AACA,IAAA,MAAM,QAAQ,mBAAA,CAAoB,IAAI,UAAA,CAAW,IAAI,EAAE,MAAM,CAAA;AAC7D,IAAA;AAAA,EACD;AACA,EAAA,IAAI,gBAAgB,WAAA,EAAa;AAChC,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAI,aAAY,CAAE,MAAA,CAAO,IAAI,CAAC,CAAA;AAC7D,MAAA;AAAA,IACD;AACA,IAAA,MAAM,OAAA,CAAQ,oBAAoB,IAAI,CAAA;AACtC,IAAA;AAAA,EACD;AACA,EAAA,IAAI,gBAAgB,IAAA,EAAM;AACzB,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,OAAA,CAAQ,gBAAA,CAAiB,MAAM,IAAA,CAAK,MAAM,CAAA;AAAA,IACjD,CAAA,MAAO;AACN,MAAA,MAAM,OAAA,CAAQ,mBAAA,CAAoB,MAAM,IAAA,CAAK,aAAa,CAAA;AAAA,IAC3D;AACA,IAAA;AAAA,EACD;AACA,EAAA,IAAI,WAAA,CAAY,MAAA,CAAO,IAAI,CAAA,EAAG;AAC7B,IAAA,MAAM,CAAA,GAAI,IAAA;AACV,IAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,CAAA,CAAE,QAAQ,CAAA,CAAE,UAAA,EAAY,EAAE,UAAU,CAAA;AAChE,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAI,aAAY,CAAE,MAAA,CAAO,IAAI,CAAC,CAAA;AAC7D,MAAA;AAAA,IACD;AACA,IAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA;AACvC,IAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AACb,IAAA,MAAM,OAAA,CAAQ,mBAAA,CAAoB,IAAA,CAAK,MAAM,CAAA;AAAA,EAC9C;AACD","file":"chunk-5WQTYLIC.js","sourcesContent":["import type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport type { SockaWebSocketSession } from \"./SockaWebSocketSession\";\n\n/**\n * Decode a WebSocket `message` payload and dispatch it to the session (same\n * behavior as the `message` handler installed by {@link attachSockaWebSocket}).\n * Use this when the runtime does not support `addEventListener` on the socket\n * (e.g. Bun {@link ServerWebSocket}) or when handling messages manually.\n */\nexport async function dispatchSockaInboundMessage<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tsession: SockaWebSocketSession<TContract, TData>,\n\twireFormat: SockaWireFormat,\n\tdata: MessageEvent[\"data\"],\n): Promise<void> {\n\tif (typeof data === \"string\") {\n\t\tawait session.handleRawMessage(data);\n\t\treturn;\n\t}\n\tif (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data)) {\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(data.toString(\"utf8\"));\n\t\t\treturn;\n\t\t}\n\t\tawait session.handleBinaryMessage(new Uint8Array(data).buffer);\n\t\treturn;\n\t}\n\tif (data instanceof ArrayBuffer) {\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(new TextDecoder().decode(data));\n\t\t\treturn;\n\t\t}\n\t\tawait session.handleBinaryMessage(data);\n\t\treturn;\n\t}\n\tif (data instanceof Blob) {\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(await data.text());\n\t\t} else {\n\t\t\tawait session.handleBinaryMessage(await data.arrayBuffer());\n\t\t}\n\t\treturn;\n\t}\n\tif (ArrayBuffer.isView(data)) {\n\t\tconst v = data;\n\t\tconst view = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(new TextDecoder().decode(view));\n\t\t\treturn;\n\t\t}\n\t\tconst copy = new Uint8Array(view.length);\n\t\tcopy.set(view);\n\t\tawait session.handleBinaryMessage(copy.buffer);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { RESERVED_SOCKA_PROCEDURE_NAMES } from './chunk-YMT4HAH7.js';
|
|
2
|
+
import { parseWirePayload, decodeSockaWire, SockaWireError, encodeClientRequest, encodeSockaWire, SockaError, parseStandardSchema, reportSockaError } from './chunk-MZCQHJXY.js';
|
|
3
|
+
|
|
4
|
+
// src/client/SockaWebSocketClient.ts
|
|
5
|
+
var SockaWebSocketClient = class {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.opts = options;
|
|
8
|
+
this.contract = options.contract;
|
|
9
|
+
this.wireFormat = options.wireFormat ?? "json";
|
|
10
|
+
this.serializeJson = options.serializeJson ?? JSON.stringify;
|
|
11
|
+
this.deserializeJson = options.deserializeJson ?? JSON.parse;
|
|
12
|
+
this.onResponseCb = options.onResponse;
|
|
13
|
+
this.onServerErrorCb = options.onServerError;
|
|
14
|
+
this.onEventCb = options.onEvent;
|
|
15
|
+
this.onValidationError = options.onValidationError;
|
|
16
|
+
if (options.autoConnect !== false) {
|
|
17
|
+
this.attachSocket(this.createSocket());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
createSocket() {
|
|
21
|
+
if (this.opts.webSocket) {
|
|
22
|
+
return this.opts.webSocket;
|
|
23
|
+
}
|
|
24
|
+
if (this.opts.url) {
|
|
25
|
+
return new WebSocket(this.opts.url);
|
|
26
|
+
}
|
|
27
|
+
throw new Error("Either 'url' or 'webSocket' must be provided");
|
|
28
|
+
}
|
|
29
|
+
attachSocket(ws) {
|
|
30
|
+
this.ws = ws;
|
|
31
|
+
if (this.wireFormat === "msgpack") {
|
|
32
|
+
ws.binaryType = "arraybuffer";
|
|
33
|
+
}
|
|
34
|
+
ws.addEventListener("open", (event) => {
|
|
35
|
+
this.opts.onOpen?.(event);
|
|
36
|
+
});
|
|
37
|
+
ws.addEventListener("message", (event) => {
|
|
38
|
+
this.handleMessageEvent(event);
|
|
39
|
+
});
|
|
40
|
+
ws.addEventListener("close", (event) => {
|
|
41
|
+
this.opts.onClose?.(event);
|
|
42
|
+
});
|
|
43
|
+
ws.addEventListener("error", (event) => {
|
|
44
|
+
this.opts.onError?.(event);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
handleMessageEvent(event) {
|
|
48
|
+
try {
|
|
49
|
+
const fmt = this.wireFormat;
|
|
50
|
+
let payload;
|
|
51
|
+
if (fmt === "json") {
|
|
52
|
+
if (typeof event.data !== "string") {
|
|
53
|
+
this.onValidationError?.(
|
|
54
|
+
new Error("socka: expected JSON text frame"),
|
|
55
|
+
event.data
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
payload = event.data;
|
|
60
|
+
} else {
|
|
61
|
+
if (!(event.data instanceof ArrayBuffer)) {
|
|
62
|
+
this.onValidationError?.(
|
|
63
|
+
new Error("socka: expected ArrayBuffer msgpack frame"),
|
|
64
|
+
event.data
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
payload = event.data;
|
|
69
|
+
}
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = parseWirePayload(payload, fmt, this.deserializeJson);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
this.onValidationError?.(
|
|
75
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
76
|
+
event.data
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let decoded;
|
|
81
|
+
try {
|
|
82
|
+
decoded = decodeSockaWire(parsed);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (err instanceof SockaWireError) {
|
|
85
|
+
this.onValidationError?.(err, parsed);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
switch (decoded.kind) {
|
|
91
|
+
case "serverResponse":
|
|
92
|
+
this.onResponseCb?.(decoded.frame);
|
|
93
|
+
break;
|
|
94
|
+
case "serverError":
|
|
95
|
+
this.onServerErrorCb?.(decoded.frame);
|
|
96
|
+
break;
|
|
97
|
+
case "serverEvent":
|
|
98
|
+
this.onEventCb?.(decoded.frame);
|
|
99
|
+
break;
|
|
100
|
+
case "clientRequest":
|
|
101
|
+
this.onValidationError?.(
|
|
102
|
+
new Error("socka: unexpected clientRequest frame from server"),
|
|
103
|
+
parsed
|
|
104
|
+
);
|
|
105
|
+
break;
|
|
106
|
+
default: {
|
|
107
|
+
const _exhaustive = decoded;
|
|
108
|
+
throw new Error(
|
|
109
|
+
`socka: unexpected wire decode branch ${JSON.stringify(_exhaustive)}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
115
|
+
this.onValidationError?.(err, event.data);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Creates the WebSocket (when {@link SockaWebSocketClientOptions.autoConnect}
|
|
120
|
+
* was `false`) and waits until the connection is open.
|
|
121
|
+
*/
|
|
122
|
+
async connect() {
|
|
123
|
+
if (!this.ws) {
|
|
124
|
+
this.attachSocket(this.createSocket());
|
|
125
|
+
}
|
|
126
|
+
await this.waitForOpen();
|
|
127
|
+
}
|
|
128
|
+
sendRequest(id, rpc, body) {
|
|
129
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
130
|
+
throw new Error("WebSocket is not open");
|
|
131
|
+
}
|
|
132
|
+
const frame = encodeClientRequest(id, rpc, body);
|
|
133
|
+
const encoded = encodeSockaWire(frame, this.wireFormat, this.serializeJson);
|
|
134
|
+
if (typeof encoded === "string") {
|
|
135
|
+
this.ws.send(encoded);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const copy = new Uint8Array(encoded.byteLength);
|
|
139
|
+
copy.set(encoded);
|
|
140
|
+
this.ws.send(copy.buffer);
|
|
141
|
+
}
|
|
142
|
+
close(code, reason) {
|
|
143
|
+
this.ws?.close(code, reason);
|
|
144
|
+
}
|
|
145
|
+
get readyState() {
|
|
146
|
+
return this.ws?.readyState ?? WebSocket.CONNECTING;
|
|
147
|
+
}
|
|
148
|
+
get socket() {
|
|
149
|
+
if (!this.ws) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"socka: WebSocket not created yet; call connect() first or use autoConnect: true"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return this.ws;
|
|
155
|
+
}
|
|
156
|
+
async waitForOpen() {
|
|
157
|
+
const ws = this.ws;
|
|
158
|
+
if (!ws) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
"socka: WebSocket not created yet; call connect() first or use autoConnect: true"
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const abortController = new AbortController();
|
|
168
|
+
const { signal } = abortController;
|
|
169
|
+
const cleanup = () => {
|
|
170
|
+
abortController.abort();
|
|
171
|
+
};
|
|
172
|
+
ws.addEventListener(
|
|
173
|
+
"open",
|
|
174
|
+
() => {
|
|
175
|
+
cleanup();
|
|
176
|
+
resolve();
|
|
177
|
+
},
|
|
178
|
+
{ signal }
|
|
179
|
+
);
|
|
180
|
+
ws.addEventListener(
|
|
181
|
+
"error",
|
|
182
|
+
() => {
|
|
183
|
+
cleanup();
|
|
184
|
+
reject(new Error("WebSocket connection failed"));
|
|
185
|
+
},
|
|
186
|
+
{ signal }
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/client/SockaSession.ts
|
|
193
|
+
var RESERVED_CALL_NAMES = new Set(RESERVED_SOCKA_PROCEDURE_NAMES);
|
|
194
|
+
function waitForPushAbortError() {
|
|
195
|
+
if (typeof DOMException !== "undefined") {
|
|
196
|
+
return new DOMException("waitForPush aborted", "AbortError");
|
|
197
|
+
}
|
|
198
|
+
return new Error("socka: waitForPush aborted");
|
|
199
|
+
}
|
|
200
|
+
var SockaSessionBase = class {
|
|
201
|
+
constructor(options) {
|
|
202
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
203
|
+
this.idSeq = 0;
|
|
204
|
+
this.pushListeners = /* @__PURE__ */ new Map();
|
|
205
|
+
const { pushHandlers, reportError, ...clientOpts } = options;
|
|
206
|
+
this.reportError = reportError;
|
|
207
|
+
this.client = new SockaWebSocketClient({
|
|
208
|
+
...clientOpts,
|
|
209
|
+
onResponse: (frame) => this.handleResponse(frame),
|
|
210
|
+
onServerError: (frame) => this.handleServerError(frame),
|
|
211
|
+
onEvent: (frame) => this.handleEvent(frame)
|
|
212
|
+
});
|
|
213
|
+
this.subscribe = this.createSubscribeApi();
|
|
214
|
+
const sendBag = this.buildSendMethods();
|
|
215
|
+
for (const name of Object.keys(sendBag)) {
|
|
216
|
+
if (RESERVED_CALL_NAMES.has(name)) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`socka: call name "${name}" is reserved on SockaSession.send; rename it in defineSocka`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.send = sendBag;
|
|
223
|
+
if (pushHandlers) {
|
|
224
|
+
for (const key of Object.keys(pushHandlers)) {
|
|
225
|
+
const fn = pushHandlers[key];
|
|
226
|
+
if (fn) {
|
|
227
|
+
this.subscribe.on(key, fn);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
createSubscribeApi() {
|
|
233
|
+
return {
|
|
234
|
+
on: (name, handler) => {
|
|
235
|
+
this.addPushListener(name, handler);
|
|
236
|
+
},
|
|
237
|
+
off: (name, handler) => {
|
|
238
|
+
this.removePushListener(name, handler);
|
|
239
|
+
},
|
|
240
|
+
once: (name, handler) => {
|
|
241
|
+
const wrapped = (payload) => {
|
|
242
|
+
this.removePushListener(name, wrapped);
|
|
243
|
+
try {
|
|
244
|
+
const result = handler(
|
|
245
|
+
payload
|
|
246
|
+
);
|
|
247
|
+
void Promise.resolve(result).catch((error) => {
|
|
248
|
+
reportSockaError(this.reportError, {
|
|
249
|
+
kind: "clientEventListener",
|
|
250
|
+
eventName: String(name),
|
|
251
|
+
error
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
reportSockaError(this.reportError, {
|
|
256
|
+
kind: "clientEventListener",
|
|
257
|
+
eventName: String(name),
|
|
258
|
+
error
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
this.addPushListener(name, wrapped);
|
|
263
|
+
},
|
|
264
|
+
waitForPush: (name, options) => this.waitForPushImpl(name, options)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
addPushListener(name, handler) {
|
|
268
|
+
let set = this.pushListeners.get(name);
|
|
269
|
+
if (!set) {
|
|
270
|
+
set = /* @__PURE__ */ new Set();
|
|
271
|
+
this.pushListeners.set(name, set);
|
|
272
|
+
}
|
|
273
|
+
set.add(handler);
|
|
274
|
+
}
|
|
275
|
+
removePushListener(name, handler) {
|
|
276
|
+
this.pushListeners.get(name)?.delete(handler);
|
|
277
|
+
}
|
|
278
|
+
waitForPushImpl(name, options) {
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
const signal = options?.signal;
|
|
281
|
+
if (signal?.aborted) {
|
|
282
|
+
reject(waitForPushAbortError());
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const onAbort = () => {
|
|
286
|
+
cleanup();
|
|
287
|
+
reject(waitForPushAbortError());
|
|
288
|
+
};
|
|
289
|
+
signal?.addEventListener("abort", onAbort);
|
|
290
|
+
let timeoutId;
|
|
291
|
+
if (options?.timeoutMs != null) {
|
|
292
|
+
timeoutId = setTimeout(() => {
|
|
293
|
+
cleanup();
|
|
294
|
+
reject(new Error("socka: waitForPush timed out"));
|
|
295
|
+
}, options.timeoutMs);
|
|
296
|
+
}
|
|
297
|
+
const listener = (payload) => {
|
|
298
|
+
if (options?.predicate && !options.predicate(payload)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
cleanup();
|
|
302
|
+
resolve(payload);
|
|
303
|
+
};
|
|
304
|
+
const cleanup = () => {
|
|
305
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
306
|
+
signal?.removeEventListener("abort", onAbort);
|
|
307
|
+
this.removePushListener(name, listener);
|
|
308
|
+
};
|
|
309
|
+
this.addPushListener(name, listener);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
buildSendMethods() {
|
|
313
|
+
const methods = {};
|
|
314
|
+
for (const name of Object.keys(this.client.contract.calls)) {
|
|
315
|
+
const proc = this.client.contract.calls[name];
|
|
316
|
+
if (proc.input) {
|
|
317
|
+
methods[name] = (input) => this.call(name, input);
|
|
318
|
+
} else {
|
|
319
|
+
methods[name] = () => this.call(name, void 0);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return methods;
|
|
323
|
+
}
|
|
324
|
+
call(callName, input) {
|
|
325
|
+
return (async () => {
|
|
326
|
+
await this.client.connect();
|
|
327
|
+
if (this.client.readyState !== WebSocket.OPEN) {
|
|
328
|
+
throw new Error("WebSocket not connected");
|
|
329
|
+
}
|
|
330
|
+
const id = this.nextId(callName);
|
|
331
|
+
const body = input !== void 0 && input !== null ? input : {};
|
|
332
|
+
return new Promise((resolve, reject) => {
|
|
333
|
+
this.pending.set(id, { rpc: callName, resolve, reject });
|
|
334
|
+
try {
|
|
335
|
+
this.client.sendRequest(id, callName, body);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
this.pending.delete(id);
|
|
338
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
})();
|
|
342
|
+
}
|
|
343
|
+
handleResponse(frame) {
|
|
344
|
+
const entry = this.pending.get(frame.id);
|
|
345
|
+
if (!entry) return;
|
|
346
|
+
this.pending.delete(frame.id);
|
|
347
|
+
const proc = this.client.contract.calls[frame.rpc];
|
|
348
|
+
if (!proc) {
|
|
349
|
+
entry.reject(new SockaError(`Unknown call: ${frame.rpc}`));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
void parseStandardSchema(proc.output, frame.body).then(
|
|
353
|
+
(validated) => entry.resolve(validated),
|
|
354
|
+
(err) => entry.reject(err instanceof Error ? err : new Error(String(err)))
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
handleServerError(frame) {
|
|
358
|
+
const entry = this.pending.get(frame.id);
|
|
359
|
+
if (!entry) return;
|
|
360
|
+
this.pending.delete(frame.id);
|
|
361
|
+
entry.reject(SockaError.fromWire(frame));
|
|
362
|
+
}
|
|
363
|
+
handleEvent(frame) {
|
|
364
|
+
const schema = this.client.contract.pushes[frame.event];
|
|
365
|
+
if (schema) {
|
|
366
|
+
void parseStandardSchema(schema, frame.body).then(
|
|
367
|
+
(validated) => this.dispatchValidatedPush(frame.event, validated),
|
|
368
|
+
(error) => reportSockaError(this.reportError, {
|
|
369
|
+
kind: "clientEventValidation",
|
|
370
|
+
eventName: frame.event,
|
|
371
|
+
error
|
|
372
|
+
})
|
|
373
|
+
);
|
|
374
|
+
} else {
|
|
375
|
+
this.dispatchValidatedPush(frame.event, frame.body);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
dispatchValidatedPush(pushName, payload) {
|
|
379
|
+
const set = this.pushListeners.get(pushName);
|
|
380
|
+
if (!set || set.size === 0) return;
|
|
381
|
+
for (const fn of [...set]) {
|
|
382
|
+
try {
|
|
383
|
+
const result = fn(payload);
|
|
384
|
+
void Promise.resolve(result).catch((error) => {
|
|
385
|
+
reportSockaError(this.reportError, {
|
|
386
|
+
kind: "clientEventListener",
|
|
387
|
+
eventName: pushName,
|
|
388
|
+
error
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
} catch (error) {
|
|
392
|
+
reportSockaError(this.reportError, {
|
|
393
|
+
kind: "clientEventListener",
|
|
394
|
+
eventName: pushName,
|
|
395
|
+
error
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
nextId(prefix) {
|
|
401
|
+
return `${prefix}-${++this.idSeq}`;
|
|
402
|
+
}
|
|
403
|
+
rejectAllPending(reason) {
|
|
404
|
+
for (const [, entry] of this.pending) {
|
|
405
|
+
entry.reject(reason);
|
|
406
|
+
}
|
|
407
|
+
this.pending.clear();
|
|
408
|
+
}
|
|
409
|
+
close(code, reason) {
|
|
410
|
+
this.client.close(code, reason);
|
|
411
|
+
}
|
|
412
|
+
/** Opens the WebSocket when using {@link SockaWebSocketClientOptions.autoConnect} `false`. */
|
|
413
|
+
connect() {
|
|
414
|
+
return this.client.connect();
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
var SockaSession = SockaSessionBase;
|
|
418
|
+
|
|
419
|
+
export { SockaSession, SockaWebSocketClient };
|
|
420
|
+
//# sourceMappingURL=chunk-AM7PB26G.js.map
|
|
421
|
+
//# sourceMappingURL=chunk-AM7PB26G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/SockaWebSocketClient.ts","../src/client/SockaSession.ts"],"names":[],"mappings":"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","file":"chunk-AM7PB26G.js","sourcesContent":["import type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport {\n\tSockaWireError,\n\tdecodeSockaWire,\n\tencodeClientRequest,\n} from \"../core/envelope\";\nimport type {\n\tSockaServerResponseFrame,\n\tSockaServerErrorFrame,\n\tSockaServerEventFrame,\n} from \"../core/envelope\";\nimport {\n\tencodeSockaWire,\n\tparseWirePayload,\n\ttype SockaWireFormat,\n} from \"../core/wire-codec\";\n\nexport interface SockaWebSocketClientOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n> {\n\tcontract: TContract;\n\t/** Default `\"json\"` (text frames). Use `\"msgpack\"` for binary `ArrayBuffer` frames. */\n\twireFormat?: SockaWireFormat;\n\turl?: string;\n\twebSocket?: WebSocket;\n\t/**\n\t * When `false`, the socket is not created until {@link SockaWebSocketClient.connect}\n\t * (or the first operation that implicitly opens, e.g. {@link SockaSession} `send`).\n\t * Default `true`.\n\t */\n\tautoConnect?: boolean;\n\tserializeJson?: (value: unknown) => string;\n\tdeserializeJson?: (raw: string) => unknown;\n\tonOpen?: (event: Event) => void;\n\tonClose?: (event: CloseEvent) => void;\n\tonError?: (event: Event) => void;\n\tonResponse?: (frame: SockaServerResponseFrame) => void;\n\tonServerError?: (frame: SockaServerErrorFrame) => void;\n\tonEvent?: (frame: SockaServerEventFrame) => void;\n\tonValidationError?: (error: Error, rawMessage: unknown) => void;\n}\n\n/**\n * Browser WebSocket client driven by a socka contract. Sends client request\n * frames and dispatches decoded server frames to callbacks.\n */\nexport class SockaWebSocketClient<\n\tTContract extends SockaContract<SockaContractConfig>,\n> {\n\tprivate ws: WebSocket | undefined;\n\tprivate readonly opts: SockaWebSocketClientOptions<TContract>;\n\tprivate readonly wireFormat: SockaWireFormat;\n\tprivate readonly serializeJson: (value: unknown) => string;\n\tprivate readonly deserializeJson: (raw: string) => unknown;\n\tprivate readonly onResponseCb?: (frame: SockaServerResponseFrame) => void;\n\tprivate readonly onServerErrorCb?: (frame: SockaServerErrorFrame) => void;\n\tprivate readonly onEventCb?: (frame: SockaServerEventFrame) => void;\n\tprivate readonly onValidationError?: (\n\t\terror: Error,\n\t\trawMessage: unknown,\n\t) => void;\n\n\treadonly contract: TContract;\n\n\tconstructor(options: SockaWebSocketClientOptions<TContract>) {\n\t\tthis.opts = options;\n\t\tthis.contract = options.contract;\n\t\tthis.wireFormat = options.wireFormat ?? \"json\";\n\t\tthis.serializeJson = options.serializeJson ?? JSON.stringify;\n\t\tthis.deserializeJson = options.deserializeJson ?? JSON.parse;\n\t\tthis.onResponseCb = options.onResponse;\n\t\tthis.onServerErrorCb = options.onServerError;\n\t\tthis.onEventCb = options.onEvent;\n\t\tthis.onValidationError = options.onValidationError;\n\n\t\tif (options.autoConnect !== false) {\n\t\t\tthis.attachSocket(this.createSocket());\n\t\t}\n\t}\n\n\tprivate createSocket(): WebSocket {\n\t\tif (this.opts.webSocket) {\n\t\t\treturn this.opts.webSocket;\n\t\t}\n\t\tif (this.opts.url) {\n\t\t\treturn new WebSocket(this.opts.url);\n\t\t}\n\t\tthrow new Error(\"Either 'url' or 'webSocket' must be provided\");\n\t}\n\n\tprivate attachSocket(ws: WebSocket): void {\n\t\tthis.ws = ws;\n\t\tif (this.wireFormat === \"msgpack\") {\n\t\t\tws.binaryType = \"arraybuffer\";\n\t\t}\n\n\t\tws.addEventListener(\"open\", (event) => {\n\t\t\tthis.opts.onOpen?.(event);\n\t\t});\n\n\t\tws.addEventListener(\"message\", (event) => {\n\t\t\tthis.handleMessageEvent(event);\n\t\t});\n\n\t\tws.addEventListener(\"close\", (event) => {\n\t\t\tthis.opts.onClose?.(event);\n\t\t});\n\n\t\tws.addEventListener(\"error\", (event) => {\n\t\t\tthis.opts.onError?.(event);\n\t\t});\n\t}\n\n\tprivate handleMessageEvent(event: MessageEvent): void {\n\t\ttry {\n\t\t\tconst fmt = this.wireFormat;\n\t\t\tlet payload: string | ArrayBuffer;\n\t\t\tif (fmt === \"json\") {\n\t\t\t\tif (typeof event.data !== \"string\") {\n\t\t\t\t\tthis.onValidationError?.(\n\t\t\t\t\t\tnew Error(\"socka: expected JSON text frame\"),\n\t\t\t\t\t\tevent.data,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tpayload = event.data;\n\t\t\t} else {\n\t\t\t\tif (!(event.data instanceof ArrayBuffer)) {\n\t\t\t\t\tthis.onValidationError?.(\n\t\t\t\t\t\tnew Error(\"socka: expected ArrayBuffer msgpack frame\"),\n\t\t\t\t\t\tevent.data,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tpayload = event.data;\n\t\t\t}\n\n\t\t\tlet parsed: unknown;\n\t\t\ttry {\n\t\t\t\tparsed = parseWirePayload(payload, fmt, this.deserializeJson);\n\t\t\t} catch (err) {\n\t\t\t\tthis.onValidationError?.(\n\t\t\t\t\terr instanceof Error ? err : new Error(String(err)),\n\t\t\t\t\tevent.data,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet decoded: ReturnType<typeof decodeSockaWire>;\n\t\t\ttry {\n\t\t\t\tdecoded = decodeSockaWire(parsed);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof SockaWireError) {\n\t\t\t\t\tthis.onValidationError?.(err, parsed);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tswitch (decoded.kind) {\n\t\t\t\tcase \"serverResponse\":\n\t\t\t\t\tthis.onResponseCb?.(decoded.frame);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"serverError\":\n\t\t\t\t\tthis.onServerErrorCb?.(decoded.frame);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"serverEvent\":\n\t\t\t\t\tthis.onEventCb?.(decoded.frame);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"clientRequest\":\n\t\t\t\t\tthis.onValidationError?.(\n\t\t\t\t\t\tnew Error(\"socka: unexpected clientRequest frame from server\"),\n\t\t\t\t\t\tparsed,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: {\n\t\t\t\t\tconst _exhaustive: never = decoded;\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`socka: unexpected wire decode branch ${JSON.stringify(_exhaustive)}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\tthis.onValidationError?.(err, event.data);\n\t\t}\n\t}\n\n\t/**\n\t * Creates the WebSocket (when {@link SockaWebSocketClientOptions.autoConnect}\n\t * was `false`) and waits until the connection is open.\n\t */\n\tasync connect(): Promise<void> {\n\t\tif (!this.ws) {\n\t\t\tthis.attachSocket(this.createSocket());\n\t\t}\n\t\tawait this.waitForOpen();\n\t}\n\n\tsendRequest(id: string, rpc: string, body: Record<string, unknown>): void {\n\t\tif (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n\t\t\tthrow new Error(\"WebSocket is not open\");\n\t\t}\n\t\tconst frame = encodeClientRequest(id, rpc, body);\n\t\tconst encoded = encodeSockaWire(frame, this.wireFormat, this.serializeJson);\n\t\tif (typeof encoded === \"string\") {\n\t\t\tthis.ws.send(encoded);\n\t\t\treturn;\n\t\t}\n\t\tconst copy = new Uint8Array(encoded.byteLength);\n\t\tcopy.set(encoded);\n\t\tthis.ws.send(copy.buffer);\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.ws?.close(code, reason);\n\t}\n\n\tget readyState(): number {\n\t\treturn this.ws?.readyState ?? WebSocket.CONNECTING;\n\t}\n\n\tget socket(): WebSocket {\n\t\tif (!this.ws) {\n\t\t\tthrow new Error(\n\t\t\t\t\"socka: WebSocket not created yet; call connect() first or use autoConnect: true\",\n\t\t\t);\n\t\t}\n\t\treturn this.ws;\n\t}\n\n\tasync waitForOpen(): Promise<void> {\n\t\tconst ws = this.ws;\n\t\tif (!ws) {\n\t\t\tthrow new Error(\n\t\t\t\t\"socka: WebSocket not created yet; call connect() first or use autoConnect: true\",\n\t\t\t);\n\t\t}\n\t\tif (ws.readyState === WebSocket.OPEN) {\n\t\t\treturn;\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst abortController = new AbortController();\n\t\t\tconst { signal } = abortController;\n\t\t\tconst cleanup = () => {\n\t\t\t\tabortController.abort();\n\t\t\t};\n\t\t\tws.addEventListener(\n\t\t\t\t\"open\",\n\t\t\t\t() => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve();\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t\tws.addEventListener(\n\t\t\t\t\"error\",\n\t\t\t\t() => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new Error(\"WebSocket connection failed\"));\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t});\n\t}\n}\n","import type {\n\tSockaContract,\n\tSockaContractConfig,\n\tInferSockaSend,\n\tInferSockaPushHandlers,\n\tInferSockaPushPayload,\n} from \"../core/contract\";\nimport {\n\treportSockaError,\n\ttype SockaReportError,\n} from \"../core/socka-report-error\";\nimport { parseStandardSchema } from \"../core/validate\";\nimport { SockaError } from \"../core/socka-error\";\nimport type {\n\tSockaServerResponseFrame,\n\tSockaServerErrorFrame,\n\tSockaServerEventFrame,\n} from \"../core/envelope\";\nimport {\n\tSockaWebSocketClient,\n\ttype SockaWebSocketClientOptions,\n} from \"./SockaWebSocketClient\";\nimport { RESERVED_SOCKA_PROCEDURE_NAMES } from \"../core/reserved-procedure-names\";\n\ntype PendingEntry = {\n\trpc: string;\n\tresolve: (value: unknown) => void;\n\treject: (error: Error) => void;\n};\n\ntype PushListenerFn = (payload: unknown) => void | Promise<void>;\n\n/** Same strings as `RESERVED_SOCKA_PROCEDURE_NAMES` (core) for O(1) lookup on `send`. */\nconst RESERVED_CALL_NAMES = new Set<string>(RESERVED_SOCKA_PROCEDURE_NAMES);\n\nexport type SockaSessionPushWaitOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tK extends keyof TContract[\"pushes\"] & string,\n> = {\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n\tpredicate?: (payload: InferSockaPushPayload<TContract, K>) => boolean;\n};\n\nexport type SockaSessionSubscribeApi<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = {\n\ton<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\thandler: (\n\t\t\tpayload: InferSockaPushPayload<TContract, K>,\n\t\t) => void | Promise<void>,\n\t): void;\n\toff<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\thandler: (\n\t\t\tpayload: InferSockaPushPayload<TContract, K>,\n\t\t) => void | Promise<void>,\n\t): void;\n\tonce<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\thandler: (\n\t\t\tpayload: InferSockaPushPayload<TContract, K>,\n\t\t) => void | Promise<void>,\n\t): void;\n\twaitForPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\toptions?: SockaSessionPushWaitOptions<TContract, K>,\n\t): Promise<InferSockaPushPayload<TContract, K>>;\n};\n\nfunction waitForPushAbortError(): Error {\n\tif (typeof DOMException !== \"undefined\") {\n\t\treturn new DOMException(\"waitForPush aborted\", \"AbortError\");\n\t}\n\treturn new Error(\"socka: waitForPush aborted\");\n}\n\nexport type SockaSessionOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = Omit<\n\tSockaWebSocketClientOptions<TContract>,\n\t\"onResponse\" | \"onServerError\" | \"onEvent\"\n> & {\n\tonOpen?: (event: Event) => void;\n\tonClose?: (event: CloseEvent) => void;\n\tonError?: (event: Event) => void;\n\tpushHandlers?: Partial<InferSockaPushHandlers<TContract>>;\n\t/**\n\t * Optional sink for client-side push pipeline failures (listener throws,\n\t * push payload validation). Defaults to `console.error`; see\n\t * `SockaReportError` in `@firtoz/socka/core`.\n\t */\n\treportError?: (event: SockaReportError) => void;\n};\n\n/**\n * Browser WebSocket session: **`session.send`** for contract calls, **`session.subscribe`**\n * for server pushes, **`session.client`** for low-level wire access.\n */\nclass SockaSessionBase<TContract extends SockaContract<SockaContractConfig>> {\n\treadonly client: SockaWebSocketClient<TContract>;\n\treadonly send: InferSockaSend<TContract>;\n\treadonly subscribe: SockaSessionSubscribeApi<TContract>;\n\n\tprivate readonly pending = new Map<string, PendingEntry>();\n\tprivate idSeq = 0;\n\tprivate readonly pushListeners = new Map<string, Set<PushListenerFn>>();\n\tprivate readonly reportError?: (event: SockaReportError) => void;\n\n\tconstructor(options: SockaSessionOptions<TContract>) {\n\t\tconst { pushHandlers, reportError, ...clientOpts } = options;\n\t\tthis.reportError = reportError;\n\n\t\tthis.client = new SockaWebSocketClient({\n\t\t\t...clientOpts,\n\t\t\tonResponse: (frame) => this.handleResponse(frame),\n\t\t\tonServerError: (frame) => this.handleServerError(frame),\n\t\t\tonEvent: (frame) => this.handleEvent(frame),\n\t\t});\n\n\t\tthis.subscribe = this.createSubscribeApi();\n\n\t\tconst sendBag = this.buildSendMethods();\n\t\tfor (const name of Object.keys(sendBag)) {\n\t\t\tif (RESERVED_CALL_NAMES.has(name)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`socka: call name \"${name}\" is reserved on SockaSession.send; rename it in defineSocka`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tthis.send = sendBag;\n\n\t\tif (pushHandlers) {\n\t\t\tfor (const key of Object.keys(pushHandlers) as Array<\n\t\t\t\tkeyof InferSockaPushHandlers<TContract> & string\n\t\t\t>) {\n\t\t\t\tconst fn = pushHandlers[key];\n\t\t\t\tif (fn) {\n\t\t\t\t\tthis.subscribe.on(key, fn);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate createSubscribeApi(): SockaSessionSubscribeApi<TContract> {\n\t\treturn {\n\t\t\ton: (name, handler) => {\n\t\t\t\tthis.addPushListener(name, handler as PushListenerFn);\n\t\t\t},\n\t\t\toff: (name, handler) => {\n\t\t\t\tthis.removePushListener(name, handler as PushListenerFn);\n\t\t\t},\n\t\t\tonce: (name, handler) => {\n\t\t\t\tconst wrapped: PushListenerFn = (payload: unknown) => {\n\t\t\t\t\tthis.removePushListener(name, wrapped);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = (handler as (p: unknown) => void | Promise<void>)(\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tvoid Promise.resolve(result).catch((error: unknown) => {\n\t\t\t\t\t\t\treportSockaError(this.reportError, {\n\t\t\t\t\t\t\t\tkind: \"clientEventListener\",\n\t\t\t\t\t\t\t\teventName: String(name),\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treportSockaError(this.reportError, {\n\t\t\t\t\t\t\tkind: \"clientEventListener\",\n\t\t\t\t\t\t\teventName: String(name),\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tthis.addPushListener(name, wrapped);\n\t\t\t},\n\t\t\twaitForPush: (name, options) => this.waitForPushImpl(name, options),\n\t\t};\n\t}\n\n\tprivate addPushListener(name: string, handler: PushListenerFn): void {\n\t\tlet set = this.pushListeners.get(name);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.pushListeners.set(name, set);\n\t\t}\n\t\tset.add(handler);\n\t}\n\n\tprivate removePushListener(name: string, handler: PushListenerFn): void {\n\t\tthis.pushListeners.get(name)?.delete(handler);\n\t}\n\n\tprivate waitForPushImpl<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\toptions?: SockaSessionPushWaitOptions<TContract, K>,\n\t): Promise<InferSockaPushPayload<TContract, K>> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst signal = options?.signal;\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(waitForPushAbortError());\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => {\n\t\t\t\tcleanup();\n\t\t\t\treject(waitForPushAbortError());\n\t\t\t};\n\t\t\tsignal?.addEventListener(\"abort\", onAbort);\n\n\t\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\n\t\t\tif (options?.timeoutMs != null) {\n\t\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new Error(\"socka: waitForPush timed out\"));\n\t\t\t\t}, options.timeoutMs);\n\t\t\t}\n\n\t\t\tconst listener: PushListenerFn = (payload: unknown) => {\n\t\t\t\tif (\n\t\t\t\t\toptions?.predicate &&\n\t\t\t\t\t!options.predicate(payload as InferSockaPushPayload<TContract, K>)\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcleanup();\n\t\t\t\tresolve(payload as InferSockaPushPayload<TContract, K>);\n\t\t\t};\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (timeoutId !== undefined) clearTimeout(timeoutId);\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tthis.removePushListener(name, listener);\n\t\t\t};\n\n\t\t\tthis.addPushListener(name, listener);\n\t\t});\n\t}\n\n\tprivate buildSendMethods(): InferSockaSend<TContract> {\n\t\tconst methods: Record<string, (input?: unknown) => Promise<unknown>> = {};\n\n\t\tfor (const name of Object.keys(this.client.contract.calls)) {\n\t\t\tconst proc = this.client.contract.calls[name];\n\t\t\tif (proc.input) {\n\t\t\t\tmethods[name] = (input: unknown) => this.call(name, input);\n\t\t\t} else {\n\t\t\t\tmethods[name] = () => this.call(name, undefined);\n\t\t\t}\n\t\t}\n\n\t\treturn methods as InferSockaSend<TContract>;\n\t}\n\n\tprivate call(callName: string, input: unknown): Promise<unknown> {\n\t\treturn (async () => {\n\t\t\tawait this.client.connect();\n\t\t\tif (this.client.readyState !== WebSocket.OPEN) {\n\t\t\t\tthrow new Error(\"WebSocket not connected\");\n\t\t\t}\n\t\t\tconst id = this.nextId(callName);\n\t\t\tconst body =\n\t\t\t\tinput !== undefined && input !== null\n\t\t\t\t\t? (input as Record<string, unknown>)\n\t\t\t\t\t: {};\n\t\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\t\tthis.pending.set(id, { rpc: callName, resolve, reject });\n\t\t\t\ttry {\n\t\t\t\t\tthis.client.sendRequest(id, callName, body);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.pending.delete(id);\n\t\t\t\t\treject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\t}\n\t\t\t});\n\t\t})();\n\t}\n\n\tprivate handleResponse(frame: SockaServerResponseFrame): void {\n\t\tconst entry = this.pending.get(frame.id);\n\t\tif (!entry) return;\n\t\tthis.pending.delete(frame.id);\n\n\t\tconst proc = this.client.contract.calls[frame.rpc];\n\t\tif (!proc) {\n\t\t\tentry.reject(new SockaError(`Unknown call: ${frame.rpc}`));\n\t\t\treturn;\n\t\t}\n\n\t\tvoid parseStandardSchema(proc.output, frame.body).then(\n\t\t\t(validated) => entry.resolve(validated),\n\t\t\t(err) =>\n\t\t\t\tentry.reject(err instanceof Error ? err : new Error(String(err))),\n\t\t);\n\t}\n\n\tprivate handleServerError(frame: SockaServerErrorFrame): void {\n\t\tconst entry = this.pending.get(frame.id);\n\t\tif (!entry) return;\n\t\tthis.pending.delete(frame.id);\n\t\tentry.reject(SockaError.fromWire(frame));\n\t}\n\n\tprivate handleEvent(frame: SockaServerEventFrame): void {\n\t\tconst schema = (\n\t\t\tthis.client.contract.pushes as Record<\n\t\t\t\tstring,\n\t\t\t\tParameters<typeof parseStandardSchema>[0] | undefined\n\t\t\t>\n\t\t)[frame.event];\n\t\tif (schema) {\n\t\t\tvoid parseStandardSchema(schema, frame.body).then(\n\t\t\t\t(validated) => this.dispatchValidatedPush(frame.event, validated),\n\t\t\t\t(error: unknown) =>\n\t\t\t\t\treportSockaError(this.reportError, {\n\t\t\t\t\t\tkind: \"clientEventValidation\",\n\t\t\t\t\t\teventName: frame.event,\n\t\t\t\t\t\terror,\n\t\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.dispatchValidatedPush(frame.event, frame.body);\n\t\t}\n\t}\n\n\tprivate dispatchValidatedPush(pushName: string, payload: unknown): void {\n\t\tconst set = this.pushListeners.get(pushName);\n\t\tif (!set || set.size === 0) return;\n\t\tfor (const fn of [...set]) {\n\t\t\ttry {\n\t\t\t\tconst result = fn(payload);\n\t\t\t\tvoid Promise.resolve(result).catch((error: unknown) => {\n\t\t\t\t\treportSockaError(this.reportError, {\n\t\t\t\t\t\tkind: \"clientEventListener\",\n\t\t\t\t\t\teventName: pushName,\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(this.reportError, {\n\t\t\t\t\tkind: \"clientEventListener\",\n\t\t\t\t\teventName: pushName,\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate nextId(prefix: string): string {\n\t\treturn `${prefix}-${++this.idSeq}`;\n\t}\n\n\trejectAllPending(reason: Error): void {\n\t\tfor (const [, entry] of this.pending) {\n\t\t\tentry.reject(reason);\n\t\t}\n\t\tthis.pending.clear();\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis.client.close(code, reason);\n\t}\n\n\t/** Opens the WebSocket when using {@link SockaWebSocketClientOptions.autoConnect} `false`. */\n\tconnect(): Promise<void> {\n\t\treturn this.client.connect();\n\t}\n}\n\nexport type SockaSession<TContract extends SockaContract<SockaContractConfig>> =\n\tSockaSessionBase<TContract>;\n\nexport interface SockaSessionConstructor {\n\tnew <TContract extends SockaContract<SockaContractConfig>>(\n\t\toptions: SockaSessionOptions<TContract>,\n\t): SockaSession<TContract>;\n}\n\n/**\n * WebSocket session: **`session.send`** for contract calls, **`session.subscribe`** for server pushes.\n */\nexport const SockaSession: SockaSessionConstructor =\n\tSockaSessionBase as SockaSessionConstructor;\n"]}
|