@kuralle-syrinx/ws 4.4.1 → 4.5.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/dist/node.js ADDED
@@ -0,0 +1,95 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Node/Bun socket adapter for WebSocketConnection, backed by the `ws` library.
4
+ // Kept in its own entry point so a Cloudflare Workers build (which uses
5
+ // createWebSocketAdapter) never bundles `ws`.
6
+ import WebSocket from "ws";
7
+ export const createNodeWsSocket = (url, headers) => {
8
+ const ws = new WebSocket(url, Object.keys(headers).length > 0 ? { headers } : undefined);
9
+ let verifyTimer = null;
10
+ let verifyOnPong = null;
11
+ let verifyResolve = null;
12
+ const clearVerify = (result) => {
13
+ if (verifyTimer)
14
+ clearTimeout(verifyTimer);
15
+ if (verifyOnPong)
16
+ ws.off("pong", verifyOnPong);
17
+ verifyTimer = null;
18
+ verifyOnPong = null;
19
+ if (verifyResolve) {
20
+ verifyResolve(result ?? false);
21
+ verifyResolve = null;
22
+ }
23
+ };
24
+ return {
25
+ supportsFramePing: true,
26
+ get isOpen() {
27
+ return ws.readyState === ws.OPEN;
28
+ },
29
+ send: (data) => ws.send(data),
30
+ keepAlivePing: () => {
31
+ try {
32
+ ws.ping();
33
+ }
34
+ catch {
35
+ // socket not open — keepalive is best-effort
36
+ }
37
+ },
38
+ verify: (timeoutMs) => new Promise((resolve) => {
39
+ clearVerify(false);
40
+ if (ws.readyState !== ws.OPEN) {
41
+ resolve(false);
42
+ return;
43
+ }
44
+ verifyResolve = resolve;
45
+ const onPong = () => {
46
+ clearVerify(true);
47
+ };
48
+ verifyOnPong = onPong;
49
+ verifyTimer = setTimeout(() => {
50
+ clearVerify(false);
51
+ }, timeoutMs);
52
+ ws.once("pong", onPong);
53
+ try {
54
+ ws.ping();
55
+ }
56
+ catch {
57
+ clearVerify(false);
58
+ }
59
+ }),
60
+ dispose: () => {
61
+ clearVerify(false);
62
+ ws.removeAllListeners();
63
+ // Closing a socket that is still CONNECTING makes `ws` emit an asynchronous
64
+ // 'error' event ("WebSocket was closed before the connection was established").
65
+ // removeAllListeners() just stripped the error handler, so without re-attaching
66
+ // a sink that event becomes an uncaught exception and crashes the process — the
67
+ // exact thing that happens when a caller hangs up during a slow provider connect.
68
+ // Swallow stray close-time errors, and abort a pending handshake with terminate().
69
+ ws.on("error", () => { });
70
+ try {
71
+ if (ws.readyState === ws.CONNECTING) {
72
+ ws.terminate();
73
+ }
74
+ else {
75
+ ws.close();
76
+ }
77
+ }
78
+ catch {
79
+ // best effort
80
+ }
81
+ },
82
+ onOpen: (handler) => ws.on("open", handler),
83
+ onMessage: (handler) => ws.on("message", (data, isBinary) => handler(isBinary ? toUint8(data) : data.toString(), isBinary)),
84
+ onClose: (handler) => ws.on("close", (code, reason) => handler(code, reason.toString("utf8"))),
85
+ onError: (handler) => ws.on("error", handler),
86
+ };
87
+ };
88
+ function toUint8(data) {
89
+ if (data instanceof ArrayBuffer)
90
+ return new Uint8Array(data);
91
+ if (Array.isArray(data))
92
+ return new Uint8Array(Buffer.concat(data));
93
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
94
+ }
95
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type RetryConfig } from "@kuralle-syrinx/core";
2
+ import { type SocketFactory } from "./index.js";
3
+ export interface RealtimeSocketOptions {
4
+ readonly url: () => string;
5
+ readonly headers?: Record<string, string>;
6
+ readonly socketFactory: SocketFactory;
7
+ readonly retry?: RetryConfig;
8
+ readonly keepAliveIntervalMs?: number;
9
+ readonly replayBufferSize?: number;
10
+ readonly onMessage: (json: string) => void;
11
+ readonly onReady?: () => void;
12
+ readonly onConnectionLost?: (err: Error) => void;
13
+ readonly onUnrecoverable?: (err: Error) => void;
14
+ }
15
+ export declare class RealtimeSocket {
16
+ private readonly opts;
17
+ private conn;
18
+ constructor(opts: RealtimeSocketOptions);
19
+ connect(): Promise<void>;
20
+ send(event: Record<string, unknown>): void;
21
+ get isReady(): boolean;
22
+ close(): Promise<void>;
23
+ }
24
+ //# sourceMappingURL=realtime-socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-socket.d.ts","sourceRoot":"","sources":["../src/realtime-socket.ts"],"names":[],"mappings":"AAMA,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAErF,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CACjD;AAED,qBAAa,cAAc;IAGb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,IAAI,CAAoC;gBAEnB,IAAI,EAAE,qBAAqB;IAElD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAK1C,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
@@ -0,0 +1,45 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Full-duplex provider socket for OpenAI Realtime (and siblings). Wraps
4
+ // WebSocketConnection with JSON send/receive — the cascade STT/TTS sockets
5
+ // stay separate; this is the bi-model live loop transport.
6
+ import { VOICE_PROVIDER_RETRY_CONFIG } from "@kuralle-syrinx/core";
7
+ import { WebSocketConnection } from "./index.js";
8
+ export class RealtimeSocket {
9
+ opts;
10
+ conn = null;
11
+ constructor(opts) {
12
+ this.opts = opts;
13
+ }
14
+ async connect() {
15
+ this.conn = new WebSocketConnection({
16
+ url: this.opts.url,
17
+ headers: this.opts.headers,
18
+ socketFactory: this.opts.socketFactory,
19
+ retry: this.opts.retry ?? VOICE_PROVIDER_RETRY_CONFIG,
20
+ keepAliveIntervalMs: this.opts.keepAliveIntervalMs ?? 15_000,
21
+ replayBufferSize: this.opts.replayBufferSize ?? 0,
22
+ onMessage: (data) => {
23
+ if (typeof data === "string")
24
+ this.opts.onMessage(data);
25
+ },
26
+ onConnectionLost: this.opts.onConnectionLost,
27
+ onUnrecoverable: this.opts.onUnrecoverable,
28
+ onReadyBeforeReplay: this.opts.onReady,
29
+ });
30
+ await this.conn.connect();
31
+ }
32
+ send(event) {
33
+ if (!this.conn)
34
+ throw new Error("Realtime socket is not connected");
35
+ this.conn.send(JSON.stringify(event));
36
+ }
37
+ get isReady() {
38
+ return this.conn?.isReady ?? false;
39
+ }
40
+ async close() {
41
+ await this.conn?.close();
42
+ this.conn = null;
43
+ }
44
+ }
45
+ //# sourceMappingURL=realtime-socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-socket.js","sourceRoot":"","sources":["../src/realtime-socket.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,2DAA2D;AAE3D,OAAO,EAAE,2BAA2B,EAAoB,MAAM,sBAAsB,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAsB,MAAM,YAAY,CAAC;AAerE,MAAM,OAAO,cAAc;IAGI;IAFrB,IAAI,GAA+B,IAAI,CAAC;IAEhD,YAA6B,IAA2B;QAA3B,SAAI,GAAJ,IAAI,CAAuB;IAAG,CAAC;IAE5D,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,GAAG,IAAI,mBAAmB,CAAC;YAClC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG;YAClB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO;YAC1B,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa;YACtC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,2BAA2B;YACrD,mBAAmB,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,IAAI,MAAM;YAC5D,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC;YACjD,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;gBAClB,IAAI,OAAO,IAAI,KAAK,QAAQ;oBAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1D,CAAC;YACD,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAC5C,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe;YAC1C,mBAAmB,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO;SACvC,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,KAA8B;QACjC,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACpE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,KAAK,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import type { ManagedSocket, SocketFactory } from "./index.js";
2
+ /** The subset of the standard WebSocket this adapter relies on (no DOM lib needed). */
3
+ export interface WebSocketLike {
4
+ readyState: number;
5
+ binaryType: string;
6
+ send(data: string | ArrayBufferView | ArrayBuffer): void;
7
+ close(code?: number, reason?: string): void;
8
+ addEventListener(type: string, listener: (event: WebSocketEventLike) => void): void;
9
+ }
10
+ /** The event object a standard WebSocket hands its listeners (message: data; close: code/reason). */
11
+ export interface WebSocketEventLike {
12
+ readonly data?: string | ArrayBuffer;
13
+ readonly code?: number;
14
+ readonly reason?: string;
15
+ }
16
+ /** Factory using the runtime's global WebSocket (no auth headers — same-origin or token-in-URL). */
17
+ export declare const createWebSocketAdapter: SocketFactory;
18
+ /** Wrap an already-constructed WebSocket (e.g. a Workers fetch-upgrade `response.webSocket`). */
19
+ export declare function wrapWebSocket(ws: WebSocketLike): ManagedSocket;
20
+ //# sourceMappingURL=web-socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-socket.d.ts","sourceRoot":"","sources":["../src/web-socket.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAc,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3E,uFAAuF;AACvF,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,WAAW,GAAG,IAAI,CAAC;IACzD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAED,qGAAqG;AACrG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID,oGAAoG;AACpG,eAAO,MAAM,sBAAsB,EAAE,aAIpC,CAAC;AAEF,iGAAiG;AACjG,wBAAgB,aAAa,CAAC,EAAE,EAAE,aAAa,GAAG,aAAa,CAyC9D"}
@@ -0,0 +1,62 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Adapter for WebSocketConnection backed by the standard built-in WebSocket —
4
+ // the one available in the browser, Cloudflare Workers, and Bun (and Node 21+).
5
+ // It has no ping frame, so verify() falls back to readyState and keepalive must
6
+ // use an app-level message (keepAliveMessage). Headers cannot be set on the
7
+ // WebSocket constructor in the browser; on Workers, auth-header connections use
8
+ // the fetch-upgrade route — construct the socket yourself and pass it to
9
+ // wrapWebSocket.
10
+ const WEBSOCKET_OPEN = 1;
11
+ /** Factory using the runtime's global WebSocket (no auth headers — same-origin or token-in-URL). */
12
+ export const createWebSocketAdapter = (url) => {
13
+ const ctor = globalThis.WebSocket;
14
+ if (!ctor)
15
+ throw new Error("global WebSocket is not available in this runtime");
16
+ return wrapWebSocket(new ctor(url));
17
+ };
18
+ /** Wrap an already-constructed WebSocket (e.g. a Workers fetch-upgrade `response.webSocket`). */
19
+ export function wrapWebSocket(ws) {
20
+ ws.binaryType = "arraybuffer";
21
+ return {
22
+ get isOpen() {
23
+ return ws.readyState === WEBSOCKET_OPEN;
24
+ },
25
+ send: (data) => ws.send(data),
26
+ keepAlivePing: () => {
27
+ // The standard WebSocket has no ping frame — rely on keepAliveMessage.
28
+ },
29
+ verify: async (_timeoutMs) => ws.readyState === WEBSOCKET_OPEN,
30
+ dispose: () => {
31
+ try {
32
+ ws.close();
33
+ }
34
+ catch {
35
+ // best effort
36
+ }
37
+ },
38
+ onOpen: (handler) => {
39
+ // workerd: after fetch-upgrade + WebSocket.accept(), readyState is already OPEN
40
+ // and the runtime never emits an "open" event — only sockets that transition
41
+ // from CONNECTING fire it. queueMicrotask preserves the async onOpen contract
42
+ // WebSocketConnection expects without waiting for an event that won't come.
43
+ if (ws.readyState === WEBSOCKET_OPEN) {
44
+ queueMicrotask(handler);
45
+ return;
46
+ }
47
+ ws.addEventListener("open", () => handler());
48
+ },
49
+ onMessage: (handler) => ws.addEventListener("message", (event) => {
50
+ const payload = event.data;
51
+ if (typeof payload === "string") {
52
+ handler(payload, false);
53
+ }
54
+ else if (payload) {
55
+ handler(new Uint8Array(payload), true);
56
+ }
57
+ }),
58
+ onClose: (handler) => ws.addEventListener("close", (event) => handler(event.code ?? 1006, event.reason ?? "")),
59
+ onError: (handler) => ws.addEventListener("error", () => handler(new Error("WebSocket error"))),
60
+ };
61
+ }
62
+ //# sourceMappingURL=web-socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-socket.js","sourceRoot":"","sources":["../src/web-socket.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,gFAAgF;AAChF,4EAA4E;AAC5E,gFAAgF;AAChF,yEAAyE;AACzE,iBAAiB;AAoBjB,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,oGAAoG;AACpG,MAAM,CAAC,MAAM,sBAAsB,GAAkB,CAAC,GAAG,EAAiB,EAAE;IAC1E,MAAM,IAAI,GAAI,UAAiE,CAAC,SAAS,CAAC;IAC1F,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAChF,OAAO,aAAa,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,iGAAiG;AACjG,MAAM,UAAU,aAAa,CAAC,EAAiB;IAC7C,EAAE,CAAC,UAAU,GAAG,aAAa,CAAC;IAC9B,OAAO;QACL,IAAI,MAAM;YACR,OAAO,EAAE,CAAC,UAAU,KAAK,cAAc,CAAC;QAC1C,CAAC;QACD,IAAI,EAAE,CAAC,IAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,aAAa,EAAE,GAAG,EAAE;YAClB,uEAAuE;QACzE,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,UAAkB,EAAoB,EAAE,CAAC,EAAE,CAAC,UAAU,KAAK,cAAc;QACxF,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC;gBACH,EAAE,CAAC,KAAK,EAAE,CAAC;YACb,CAAC;YAAC,MAAM,CAAC;gBACP,cAAc;YAChB,CAAC;QACH,CAAC;QACD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;YAClB,gFAAgF;YAChF,6EAA6E;YAC7E,8EAA8E;YAC9E,4EAA4E;YAC5E,IAAI,EAAE,CAAC,UAAU,KAAK,cAAc,EAAE,CAAC;gBACrC,cAAc,CAAC,OAAO,CAAC,CAAC;gBACxB,OAAO;YACT,CAAC;YACD,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CACrB,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACvC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;YAC3B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC1B,CAAC;iBAAM,IAAI,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC;QACJ,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QAC9G,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;KAChG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ManagedSocket } from "./index.js";
2
+ import { type WebSocketLike } from "./web-socket.js";
3
+ /** The Workers WebSocket adds accept(); the upgrade response carries it. */
4
+ type WorkersWebSocket = WebSocketLike & {
5
+ accept(): void;
6
+ };
7
+ export interface WorkersDurableObjectWebSocketContext {
8
+ acceptWebSocket(socket: WorkersWebSocket): void;
9
+ }
10
+ export interface WorkersInboundSocket {
11
+ readonly socket: ManagedSocket;
12
+ readonly response: Response;
13
+ readonly clientWebSocket: WorkersWebSocket;
14
+ readonly serverWebSocket: WorkersWebSocket;
15
+ readonly controller?: WorkersInboundSocketController;
16
+ }
17
+ export interface WorkersInboundSocketController {
18
+ message(data: string | ArrayBuffer): void;
19
+ close(code: number, reason: string): void;
20
+ error(err?: Error): void;
21
+ }
22
+ /** Open an auth-header WebSocket from a Worker via fetch upgrade. Async — await before use. */
23
+ export declare function createWorkersSocket(url: string, headers: Record<string, string>): Promise<ManagedSocket>;
24
+ export declare function createWorkersInboundSocket(ctx?: WorkersDurableObjectWebSocketContext): WorkersInboundSocket;
25
+ export {};
26
+ //# sourceMappingURL=workers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../src/workers.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEpE,4EAA4E;AAC5E,KAAK,gBAAgB,GAAG,aAAa,GAAG;IAAE,MAAM,IAAI,IAAI,CAAA;CAAE,CAAC;AAO3D,MAAM,WAAW,oCAAoC;IACnD,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,8BAA8B,CAAC;CACtD;AAED,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAC1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CAC1B;AAKD,+FAA+F;AAC/F,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAU9G;AASD,wBAAgB,0BAA0B,CAAC,GAAG,CAAC,EAAE,oCAAoC,GAAG,oBAAoB,CA4B3G"}
@@ -0,0 +1,122 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Cloudflare Workers socket adapter. The built-in WebSocket constructor can't
4
+ // set request headers, so auth-header provider connections (Deepgram's
5
+ // `Authorization: Token`, Cartesia's `X-API-Key`) are opened with a `fetch`
6
+ // upgrade and the returned `response.webSocket`, then `accept()`-ed before use.
7
+ //
8
+ // Pattern from Cloudflare's own agents/voice package (workers-ai-providers.ts):
9
+ // const resp = await fetch(url, { headers: { Upgrade: "websocket", ...auth } });
10
+ // const ws = resp.webSocket; ws.accept();
11
+ import { wrapWebSocket } from "./web-socket.js";
12
+ /** Open an auth-header WebSocket from a Worker via fetch upgrade. Async — await before use. */
13
+ export async function createWorkersSocket(url, headers) {
14
+ const doFetch = globalThis.fetch;
15
+ if (!doFetch)
16
+ throw new Error("fetch is not available in this runtime");
17
+ // workerd's fetch() only accepts http(s) schemes; the upgrade is requested on
18
+ // the http(s) URL with `Upgrade: websocket`, not on a ws(s):// URL.
19
+ const resp = await doFetch(toHttpUrl(url), { headers: { ...headers, Upgrade: "websocket" } });
20
+ const ws = resp.webSocket;
21
+ if (!ws)
22
+ throw new Error(`WebSocket upgrade failed (status ${String(resp.status ?? "unknown")})`);
23
+ ws.accept();
24
+ return wrapWebSocket(ws);
25
+ }
26
+ /** Normalize a ws(s):// provider URL to the http(s):// scheme workerd's fetch requires. */
27
+ function toHttpUrl(url) {
28
+ if (url.startsWith("wss://"))
29
+ return `https://${url.slice("wss://".length)}`;
30
+ if (url.startsWith("ws://"))
31
+ return `http://${url.slice("ws://".length)}`;
32
+ return url;
33
+ }
34
+ export function createWorkersInboundSocket(ctx) {
35
+ const Pair = globalThis.WebSocketPair;
36
+ if (!Pair)
37
+ throw new Error("WebSocketPair is not available in this runtime");
38
+ const pair = new Pair();
39
+ const client = pair[0];
40
+ const server = pair[1];
41
+ let controller;
42
+ let socket;
43
+ if (ctx) {
44
+ ctx.acceptWebSocket(server);
45
+ const controlled = createControlledSocket(server);
46
+ socket = controlled.socket;
47
+ controller = controlled.controller;
48
+ }
49
+ else {
50
+ server.accept();
51
+ socket = wrapWebSocket(server);
52
+ }
53
+ const response = new Response(null, {
54
+ status: 101,
55
+ webSocket: client,
56
+ });
57
+ return {
58
+ socket,
59
+ response,
60
+ clientWebSocket: client,
61
+ serverWebSocket: server,
62
+ controller,
63
+ };
64
+ }
65
+ function createControlledSocket(ws) {
66
+ const messageHandlers = new Set();
67
+ const closeHandlers = new Set();
68
+ const errorHandlers = new Set();
69
+ return {
70
+ socket: {
71
+ get isOpen() {
72
+ return ws.readyState === 1;
73
+ },
74
+ send: (data) => ws.send(data),
75
+ keepAlivePing: () => undefined,
76
+ verify: async () => ws.readyState === 1,
77
+ dispose: () => {
78
+ try {
79
+ ws.close();
80
+ }
81
+ catch {
82
+ // best effort
83
+ }
84
+ },
85
+ onOpen: (handler) => {
86
+ if (ws.readyState === 1)
87
+ queueMicrotask(handler);
88
+ },
89
+ onMessage: (handler) => {
90
+ messageHandlers.add(handler);
91
+ },
92
+ onClose: (handler) => {
93
+ closeHandlers.add(handler);
94
+ },
95
+ onError: (handler) => {
96
+ errorHandlers.add(handler);
97
+ },
98
+ },
99
+ controller: {
100
+ message(data) {
101
+ if (typeof data === "string") {
102
+ for (const handler of messageHandlers)
103
+ handler(data, false);
104
+ return;
105
+ }
106
+ const bytes = new Uint8Array(data);
107
+ for (const handler of messageHandlers)
108
+ handler(bytes, true);
109
+ },
110
+ close(code, reason) {
111
+ for (const handler of closeHandlers)
112
+ handler(code, reason);
113
+ },
114
+ error(err) {
115
+ const error = err ?? new Error("WebSocket error");
116
+ for (const handler of errorHandlers)
117
+ handler(error);
118
+ },
119
+ },
120
+ };
121
+ }
122
+ //# sourceMappingURL=workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.js","sourceRoot":"","sources":["../src/workers.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuralle-syrinx/ws",
3
- "version": "4.4.1",
3
+ "version": "4.5.0",
4
4
  "private": false,
5
5
  "description": "Reconnecting WebSocket connection manager for Syrinx provider sockets — verify-before-trust reconnects, ordered replay, keepalive; Node and Workers",
6
6
  "keywords": [
@@ -22,26 +22,48 @@
22
22
  "url": "https://github.com/kuralle/syrinx/issues"
23
23
  },
24
24
  "type": "module",
25
- "main": "./src/index.ts",
26
- "types": "./src/index.ts",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
27
  "exports": {
28
- ".": "./src/index.ts",
29
- "./node": "./src/node.ts",
30
- "./realtime": "./src/realtime-socket.ts",
31
- "./web": "./src/web-socket.ts",
32
- "./workers": "./src/workers.ts"
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./node": {
33
+ "types": "./dist/node.d.ts",
34
+ "default": "./dist/node.js"
35
+ },
36
+ "./realtime": {
37
+ "types": "./dist/realtime-socket.d.ts",
38
+ "default": "./dist/realtime-socket.js"
39
+ },
40
+ "./web": {
41
+ "types": "./dist/web-socket.d.ts",
42
+ "default": "./dist/web-socket.js"
43
+ },
44
+ "./workers": {
45
+ "types": "./dist/workers.d.ts",
46
+ "default": "./dist/workers.js"
47
+ }
33
48
  },
34
49
  "dependencies": {
35
50
  "ws": "^8.21.0",
36
- "@kuralle-syrinx/core": "4.4.1"
51
+ "@kuralle-syrinx/core": "4.5.0"
37
52
  },
38
53
  "devDependencies": {
54
+ "@types/node": "^22.0.0",
39
55
  "@types/ws": "^8.5.0",
40
56
  "typescript": "^5.7.0",
41
57
  "vitest": "^3.2.6"
42
58
  },
59
+ "files": [
60
+ "dist",
61
+ "src",
62
+ "README.md"
63
+ ],
43
64
  "scripts": {
44
65
  "typecheck": "tsc --noEmit",
45
- "test": "vitest run"
66
+ "test": "vitest run",
67
+ "build": "tsc -p tsconfig.build.json"
46
68
  }
47
69
  }