@gjsify/ws 0.2.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.
@@ -0,0 +1,249 @@
1
+ import { EventEmitter } from "@gjsify/events";
2
+ import { Buffer } from "@gjsify/buffer";
3
+ import { WebSocket as NativeWebSocket } from "@gjsify/websocket";
4
+ import {
5
+ BINARY_TYPES,
6
+ CLOSED,
7
+ CLOSING,
8
+ CONNECTING,
9
+ OPEN
10
+ } from "./constants.js";
11
+ class WebSocket extends EventEmitter {
12
+ /** Static readyState constants — match the W3C and `ws` values. */
13
+ static CONNECTING = CONNECTING;
14
+ static OPEN = OPEN;
15
+ static CLOSING = CLOSING;
16
+ static CLOSED = CLOSED;
17
+ /** Instance-side copies for `ws.readyState === ws.OPEN` style code. */
18
+ CONNECTING = CONNECTING;
19
+ OPEN = OPEN;
20
+ CLOSING = CLOSING;
21
+ CLOSED = CLOSED;
22
+ /** Per-instance state. Populated from the underlying native WebSocket as
23
+ * events arrive; exposed as properties so that ws-calling code like
24
+ * `if (ws.readyState === ws.OPEN)` works identically to Node's `ws`. */
25
+ readyState = CONNECTING;
26
+ url = "";
27
+ protocol = "";
28
+ extensions = "";
29
+ bufferedAmount = 0;
30
+ binaryType = "nodebuffer";
31
+ /** The real WebSocket we delegate to. Typed as `any` because the W3C
32
+ * ambient type comes from multiple realms depending on where this bundle
33
+ * ends up (GJS browser-like globals vs. Node's undici). */
34
+ _native = null;
35
+ constructor(address, protocols, options = {}) {
36
+ super();
37
+ if (address === null) {
38
+ queueMicrotask(() => this._fail("Constructing ws.WebSocket with null address is not supported on Gjs"));
39
+ return;
40
+ }
41
+ this.url = typeof address === "string" ? address : String(address);
42
+ const protos = this._resolveProtocols(protocols, options);
43
+ this._openNative(this.url, protos, options);
44
+ }
45
+ /** Merge `protocols` arg and `options.protocols` / `options.protocol`. */
46
+ _resolveProtocols(protocols, options) {
47
+ if (protocols !== void 0) {
48
+ return Array.isArray(protocols) ? protocols : [protocols];
49
+ }
50
+ if (options.protocols !== void 0) {
51
+ return Array.isArray(options.protocols) ? options.protocols : [options.protocols];
52
+ }
53
+ if (options.protocol !== void 0) return [options.protocol];
54
+ return void 0;
55
+ }
56
+ /** Lazy-open the underlying native WebSocket. Separated from the constructor
57
+ * so subclasses or future socket-adoption paths can bypass it. */
58
+ _openNative(url, protocols, options) {
59
+ if (typeof NativeWebSocket !== "function") {
60
+ queueMicrotask(() => this._fail(
61
+ "@gjsify/websocket provided no WebSocket constructor. On Node.js 22+ globalThis.WebSocket is native; on older Node install `ws` directly, or ensure globalThis.WebSocket is set before @gjsify/ws is imported."
62
+ ));
63
+ return;
64
+ }
65
+ const nativeOpts = {
66
+ perMessageDeflate: options.perMessageDeflate !== false,
67
+ headers: options.headers,
68
+ origin: options.origin,
69
+ handshakeTimeout: options.handshakeTimeout
70
+ };
71
+ try {
72
+ this._native = new NativeWebSocket(url, protocols, nativeOpts);
73
+ } catch (err) {
74
+ queueMicrotask(() => this._fail(err instanceof Error ? err : new Error(String(err))));
75
+ return;
76
+ }
77
+ this._native.binaryType = "arraybuffer";
78
+ this._native.addEventListener("open", () => this._onOpen());
79
+ this._native.addEventListener("message", (ev) => this._onMessage(ev));
80
+ this._native.addEventListener("close", (ev) => this._onClose(ev));
81
+ this._native.addEventListener("error", (ev) => this._onError(ev));
82
+ }
83
+ _fail(err) {
84
+ const error = typeof err === "string" ? new Error(err) : err;
85
+ this.readyState = CLOSED;
86
+ this.emit("error", error);
87
+ this._dispatchEvent("error", { error, message: error.message });
88
+ this.emit("close", 1006, Buffer.from(error.message));
89
+ this._dispatchEvent("close", { code: 1006, reason: error.message, wasClean: false });
90
+ }
91
+ _onOpen() {
92
+ this.readyState = OPEN;
93
+ if (typeof this._native.protocol === "string") this.protocol = this._native.protocol;
94
+ if (typeof this._native.extensions === "string") this.extensions = this._native.extensions;
95
+ this.emit("open");
96
+ this._dispatchEvent("open", {});
97
+ }
98
+ _onMessage(ev) {
99
+ const raw = ev?.data;
100
+ let data;
101
+ let isBinary = false;
102
+ if (typeof raw === "string") {
103
+ data = raw;
104
+ isBinary = false;
105
+ } else if (raw instanceof ArrayBuffer) {
106
+ isBinary = true;
107
+ data = this._decodeBinary(raw);
108
+ } else if (ArrayBuffer.isView(raw)) {
109
+ isBinary = true;
110
+ data = this._decodeBinary(raw.buffer);
111
+ } else {
112
+ data = raw;
113
+ isBinary = false;
114
+ }
115
+ this.emit("message", data, isBinary);
116
+ this._dispatchEvent("message", { data, type: isBinary ? "binary" : "text" });
117
+ }
118
+ /** Convert an ArrayBuffer to the Buffer flavor requested by `binaryType`. */
119
+ _decodeBinary(buf) {
120
+ switch (this.binaryType) {
121
+ case "arraybuffer":
122
+ return buf;
123
+ case "fragments":
124
+ return [Buffer.from(buf)];
125
+ // one-fragment approximation
126
+ case "blob": {
127
+ const BlobCtor = globalThis.Blob;
128
+ return BlobCtor ? new BlobCtor([new Uint8Array(buf)]) : Buffer.from(buf);
129
+ }
130
+ case "nodebuffer":
131
+ default:
132
+ return Buffer.from(buf);
133
+ }
134
+ }
135
+ _onClose(ev) {
136
+ const code = typeof ev?.code === "number" ? ev.code : 1006;
137
+ const reason = typeof ev?.reason === "string" ? ev.reason : "";
138
+ this.readyState = CLOSED;
139
+ this.emit("close", code, Buffer.from(reason));
140
+ this._dispatchEvent("close", { code, reason, wasClean: !!ev?.wasClean });
141
+ }
142
+ _onError(ev) {
143
+ const msg = ev?.message || "WebSocket error";
144
+ const err = ev?.error instanceof Error ? ev.error : new Error(msg);
145
+ this.emit("error", err);
146
+ this._dispatchEvent("error", { error: err, message: msg });
147
+ }
148
+ send(data, optionsOrCb, cb) {
149
+ const callback = typeof optionsOrCb === "function" ? optionsOrCb : cb;
150
+ if (this.readyState === CONNECTING) {
151
+ throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
152
+ }
153
+ if (this.readyState !== OPEN) {
154
+ const err = new Error("WebSocket is not open: readyState " + this.readyState);
155
+ if (callback) queueMicrotask(() => callback(err));
156
+ else queueMicrotask(() => this.emit("error", err));
157
+ return;
158
+ }
159
+ this._nativeSend(data, callback);
160
+ }
161
+ _nativeSend(data, cb) {
162
+ try {
163
+ let payload = data;
164
+ if (typeof data === "number" || typeof data === "boolean") {
165
+ payload = String(data);
166
+ } else if (Buffer.isBuffer(data)) {
167
+ const b = data;
168
+ payload = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
169
+ }
170
+ this._native.send(payload);
171
+ if (cb) queueMicrotask(() => cb());
172
+ } catch (err) {
173
+ const e = err instanceof Error ? err : new Error(String(err));
174
+ if (cb) queueMicrotask(() => cb(e));
175
+ else queueMicrotask(() => this.emit("error", e));
176
+ }
177
+ }
178
+ _sizeOf(data) {
179
+ if (typeof data === "string") return data.length;
180
+ if (data instanceof ArrayBuffer) return data.byteLength;
181
+ if (ArrayBuffer.isView(data)) return data.byteLength;
182
+ return 0;
183
+ }
184
+ close(code, reason) {
185
+ if (this.readyState === CLOSED) return;
186
+ if (this.readyState === CLOSING) return;
187
+ this.readyState = CLOSING;
188
+ try {
189
+ const reasonStr = reason === void 0 ? void 0 : Buffer.isBuffer(reason) ? reason.toString("utf8") : String(reason);
190
+ if (code === void 0) this._native?.close();
191
+ else if (reasonStr === void 0) this._native?.close(code);
192
+ else this._native?.close(code, reasonStr);
193
+ } catch (err) {
194
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
195
+ }
196
+ }
197
+ /** ws-only: force-close without sending a Close frame. On Gjs we can't bypass
198
+ * Soup's close handshake, so terminate is approximated as close(1006).
199
+ * Known gap vs. ws semantics — documented. */
200
+ terminate() {
201
+ if (this.readyState === CLOSED) return;
202
+ this.readyState = CLOSING;
203
+ try {
204
+ this._native?.close(1006, "terminated");
205
+ } catch {
206
+ }
207
+ }
208
+ /** Convenience: returns true if the socket is closed or closing. Matches
209
+ * ws.isPaused() / internal state checks that some consumers rely on. */
210
+ get isPaused() {
211
+ return this.readyState === CLOSING || this.readyState === CLOSED;
212
+ }
213
+ // --- W3C EventTarget compat surface -------------------------------------
214
+ // ws uses EventEmitter by default, but exposes addEventListener-style
215
+ // registration for consumers that prefer the W3C API. We mirror it to
216
+ // stay compat.
217
+ _eventTargetListeners = /* @__PURE__ */ new Map();
218
+ addEventListener(type, listener) {
219
+ let set = this._eventTargetListeners.get(type);
220
+ if (!set) {
221
+ set = /* @__PURE__ */ new Set();
222
+ this._eventTargetListeners.set(type, set);
223
+ }
224
+ set.add(listener);
225
+ }
226
+ removeEventListener(type, listener) {
227
+ this._eventTargetListeners.get(type)?.delete(listener);
228
+ }
229
+ _dispatchEvent(type, detail) {
230
+ const set = this._eventTargetListeners.get(type);
231
+ if (!set || set.size === 0) return;
232
+ const ev = Object.assign({ type, target: this }, detail);
233
+ for (const listener of set) {
234
+ try {
235
+ listener(ev);
236
+ } catch (err) {
237
+ queueMicrotask(() => this.emit("error", err));
238
+ }
239
+ }
240
+ }
241
+ // Static list of accepted binary types — exposed for parity with ws.
242
+ static get BINARY_TYPES() {
243
+ return BINARY_TYPES;
244
+ }
245
+ }
246
+ WebSocket.WebSocket = WebSocket;
247
+ export {
248
+ WebSocket
249
+ };
@@ -0,0 +1,9 @@
1
+ export declare const BINARY_TYPES: readonly ["nodebuffer", "arraybuffer", "fragments"];
2
+ export declare const EMPTY_BUFFER: Uint8Array<ArrayBuffer>;
3
+ export declare const CONNECTING = 0;
4
+ export declare const OPEN = 1;
5
+ export declare const CLOSING = 2;
6
+ export declare const CLOSED = 3;
7
+ /** Internal marker for native `globalThis.WebSocket` instances handed in via
8
+ * `{ socket }` option (not currently supported but reserved). */
9
+ export declare const kWebSocket: unique symbol;
@@ -0,0 +1,6 @@
1
+ import { WebSocket } from './websocket.js';
2
+ import { WebSocketServer } from './websocket-server.js';
3
+ import { createWebSocketStream } from './stream.js';
4
+ export { WebSocket, WebSocketServer, createWebSocketStream };
5
+ export { WebSocketServer as Server };
6
+ export default WebSocket;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ import { Duplex } from 'node:stream';
2
+ export declare function createWebSocketStream(ws: any, options?: Record<string, unknown>): Duplex;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,119 @@
1
+ import { EventEmitter } from '@gjsify/events';
2
+ import { Buffer } from '@gjsify/buffer';
3
+ import Soup from '@girs/soup-3.0';
4
+ /** Structural duck-type for @gjsify/http Server — avoids a hard dep on @gjsify/http. */
5
+ interface HttpServer {
6
+ soupServer: Soup.Server | null;
7
+ address(): {
8
+ address: string;
9
+ family: string;
10
+ port: number;
11
+ } | null;
12
+ }
13
+ export interface VerifyClientInfo {
14
+ /** Value of the HTTP Origin request header (empty string if absent). */
15
+ origin: string;
16
+ /** Whether the connection uses TLS. Always false on Gjs (Soup plain text). */
17
+ secure: boolean;
18
+ /** Minimal HTTP request object populated from Soup.ServerMessage. */
19
+ req: {
20
+ method: string;
21
+ url: string;
22
+ headers: Record<string, string | string[]>;
23
+ socket: {
24
+ remoteAddress: string;
25
+ remotePort: number;
26
+ };
27
+ };
28
+ }
29
+ export type VerifyClientSync = (info: VerifyClientInfo) => boolean;
30
+ export type VerifyClientAsync = (info: VerifyClientInfo, cb: (result: boolean, code?: number, message?: string, headers?: Record<string, string>) => void) => void;
31
+ export interface ServerOptions {
32
+ host?: string;
33
+ port?: number;
34
+ backlog?: number;
35
+ /** Attach to an existing @gjsify/http Server instead of creating a new one. */
36
+ server?: HttpServer;
37
+ /** Pre-upgrade access control hook. Sync: return boolean. Async: call cb(result, code?). */
38
+ verifyClient?: VerifyClientSync | VerifyClientAsync;
39
+ /** Subprotocol selection hook. Receives the Set of client-offered protocols and
40
+ * a minimal request object; return the selected protocol string or false to
41
+ * use none. Server-side ws.protocol is set correctly; client-visible protocol
42
+ * negotiation requires Phase 3 (manual handshake). */
43
+ handleProtocols?: (protocols: Set<string>, req: VerifyClientInfo['req']) => string | false;
44
+ path?: string;
45
+ noServer?: boolean;
46
+ clientTracking?: boolean;
47
+ perMessageDeflate?: boolean | object;
48
+ maxPayload?: number;
49
+ skipUTF8Validation?: boolean;
50
+ allowSynchronousEvents?: boolean;
51
+ }
52
+ /** Wraps an accepted Soup.WebsocketConnection in a `ws.WebSocket`-shaped
53
+ * EventEmitter. Kept private to this file: the WebSocket class in
54
+ * ./websocket.ts targets the CLIENT constructor path. Server-accepted
55
+ * connections have different semantics (no URL reconnect, different
56
+ * lifecycle) so we expose a narrower surface. */
57
+ declare class ServerSideWebSocket extends EventEmitter {
58
+ static readonly CONNECTING = 0;
59
+ static readonly OPEN = 1;
60
+ static readonly CLOSING = 2;
61
+ static readonly CLOSED = 3;
62
+ readonly CONNECTING = 0;
63
+ readonly OPEN = 1;
64
+ readonly CLOSING = 2;
65
+ readonly CLOSED = 3;
66
+ readyState: number;
67
+ protocol: string;
68
+ extensions: string;
69
+ url: string;
70
+ private _conn;
71
+ constructor(conn: Soup.WebsocketConnection, url: string);
72
+ send(data: string | Buffer | ArrayBuffer | ArrayBufferView, optionsOrCb?: ((err?: Error) => void) | object, cb?: (err?: Error) => void): void;
73
+ close(code?: number, reason?: string | Buffer): void;
74
+ terminate(): void;
75
+ }
76
+ /** `ws.WebSocketServer` — listens on a TCP port (or attaches to an existing
77
+ * @gjsify/http Server) and emits 'connection' events wrapping
78
+ * Soup.WebsocketConnection as ws.WebSocket-shaped objects. */
79
+ export declare class WebSocketServer extends EventEmitter {
80
+ readonly options: ServerOptions;
81
+ readonly clients: Set<ServerSideWebSocket>;
82
+ readonly path: string;
83
+ private _server;
84
+ private _address;
85
+ constructor(options?: ServerOptions, callback?: () => void);
86
+ private _buildVerifyClientInfo;
87
+ /** Register add_handler (verifyClient) + add_websocket_handler on soupServer.
88
+ * The verifyClient add_handler MUST be registered before add_websocket_handler —
89
+ * Soup processes normal handlers before websocket handlers; setting a status code
90
+ * in add_handler prevents the websocket handler from firing (HTTP-level rejection).
91
+ * Only register add_handler when verifyClient is provided — a no-op handler on the
92
+ * same path as an existing http.Server catch-all can interfere with Soup's routing. */
93
+ private _setupHandlers;
94
+ private _start;
95
+ address(): {
96
+ address: string;
97
+ family: string;
98
+ port: number;
99
+ } | null;
100
+ close(callback?: (err?: Error) => void): void;
101
+ /** Manual WebSocket upgrade — matches npm ws semantics exactly.
102
+ * The caller intercepts 'upgrade' on an http.Server (typically with
103
+ * { noServer: true } on this WebSocketServer) and passes the raw
104
+ * IncomingMessage + net.Socket + head buffer here.
105
+ *
106
+ * Internally: validates headers, runs verifyClient, computes
107
+ * Sec-WebSocket-Accept, emits 'headers' (mutable array), writes the 101
108
+ * response via socket.write(), then creates Soup.WebsocketConnection from
109
+ * the underlying IOStream and calls cb(ws, req). */
110
+ handleUpgrade(req: any, socket: any, _head: Buffer, cb: (ws: ServerSideWebSocket, req: any) => void): void;
111
+ shouldHandle(req: {
112
+ url?: string;
113
+ }): boolean;
114
+ private _validateUpgradeHeaders;
115
+ private _completeUpgrade;
116
+ private _abortHandshake;
117
+ private _buildVerifyClientInfoFromReq;
118
+ }
119
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,99 @@
1
+ import { EventEmitter } from '@gjsify/events';
2
+ import { Buffer } from '@gjsify/buffer';
3
+ export type BinaryType = 'nodebuffer' | 'arraybuffer' | 'fragments' | 'blob';
4
+ /** Options accepted by the ws constructor. Only a subset is honored on Gjs —
5
+ * see README for the support matrix. Unknown options are silently ignored
6
+ * to preserve drop-in compatibility with `ws`-calling code. */
7
+ export interface ClientOptions {
8
+ protocol?: string;
9
+ protocols?: string | string[];
10
+ origin?: string;
11
+ headers?: Record<string, string | string[]>;
12
+ handshakeTimeout?: number;
13
+ /** Enable permessage-deflate (RFC 7692). Defaults to true, matching the real
14
+ * ws npm package. Set to false to disable deflate negotiation (useful when
15
+ * the remote server has buggy deflate handling). */
16
+ perMessageDeflate?: boolean;
17
+ }
18
+ /** `ws.WebSocket` — EventEmitter-based WebSocket client.
19
+ *
20
+ * Events (ws-compatible):
21
+ * - 'open' → ()
22
+ * - 'message' → (data: Buffer | ArrayBuffer | string, isBinary: boolean)
23
+ * - 'close' → (code: number, reason: Buffer)
24
+ * - 'error' → (error: Error)
25
+ * - 'ping' / 'pong' → NOT EMITTED on Gjs (Soup handles control
26
+ * frames internally; no JS hook exposed).
27
+ * Consumers that rely on 'ping'/'pong' for
28
+ * keep-alive application logic need to use
29
+ * data messages instead. Tracked as a
30
+ * known limitation in STATUS.md.
31
+ * - 'upgrade' / 'unexpected-response' / 'redirect'
32
+ * → NOT EMITTED on Gjs (Soup does not expose
33
+ * the raw HTTP upgrade response). Code
34
+ * that only branches on `open` vs `error`
35
+ * still works.
36
+ *
37
+ * Also implements W3C DOM `EventTarget` methods (addEventListener /
38
+ * removeEventListener) so `ws` users who follow W3C-style handlers via the
39
+ * `ws.EventTarget` flag still work without special-casing the Gjs path.
40
+ */
41
+ export declare class WebSocket extends EventEmitter {
42
+ /** Static readyState constants — match the W3C and `ws` values. */
43
+ static readonly CONNECTING = 0;
44
+ static readonly OPEN = 1;
45
+ static readonly CLOSING = 2;
46
+ static readonly CLOSED = 3;
47
+ /** Instance-side copies for `ws.readyState === ws.OPEN` style code. */
48
+ readonly CONNECTING = 0;
49
+ readonly OPEN = 1;
50
+ readonly CLOSING = 2;
51
+ readonly CLOSED = 3;
52
+ /** Per-instance state. Populated from the underlying native WebSocket as
53
+ * events arrive; exposed as properties so that ws-calling code like
54
+ * `if (ws.readyState === ws.OPEN)` works identically to Node's `ws`. */
55
+ readyState: number;
56
+ url: string;
57
+ protocol: string;
58
+ extensions: string;
59
+ bufferedAmount: number;
60
+ binaryType: BinaryType;
61
+ /** The real WebSocket we delegate to. Typed as `any` because the W3C
62
+ * ambient type comes from multiple realms depending on where this bundle
63
+ * ends up (GJS browser-like globals vs. Node's undici). */
64
+ private _native;
65
+ constructor(address: string | URL | null, protocols?: string | string[], options?: ClientOptions);
66
+ /** Merge `protocols` arg and `options.protocols` / `options.protocol`. */
67
+ private _resolveProtocols;
68
+ /** Lazy-open the underlying native WebSocket. Separated from the constructor
69
+ * so subclasses or future socket-adoption paths can bypass it. */
70
+ private _openNative;
71
+ private _fail;
72
+ private _onOpen;
73
+ private _onMessage;
74
+ /** Convert an ArrayBuffer to the Buffer flavor requested by `binaryType`. */
75
+ private _decodeBinary;
76
+ private _onClose;
77
+ private _onError;
78
+ send(data: string | Buffer | ArrayBuffer | ArrayBufferView | Blob | number | boolean, optionsOrCb?: ((err?: Error) => void) | {
79
+ mask?: boolean;
80
+ binary?: boolean;
81
+ compress?: boolean;
82
+ fin?: boolean;
83
+ }, cb?: (err?: Error) => void): void;
84
+ private _nativeSend;
85
+ private _sizeOf;
86
+ close(code?: number, reason?: string | Buffer): void;
87
+ /** ws-only: force-close without sending a Close frame. On Gjs we can't bypass
88
+ * Soup's close handshake, so terminate is approximated as close(1006).
89
+ * Known gap vs. ws semantics — documented. */
90
+ terminate(): void;
91
+ /** Convenience: returns true if the socket is closed or closing. Matches
92
+ * ws.isPaused() / internal state checks that some consumers rely on. */
93
+ get isPaused(): boolean;
94
+ private _eventTargetListeners;
95
+ addEventListener(type: string, listener: (ev: any) => void): void;
96
+ removeEventListener(type: string, listener: (ev: any) => void): void;
97
+ private _dispatchEvent;
98
+ static get BINARY_TYPES(): readonly ["nodebuffer", "arraybuffer", "fragments"];
99
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@gjsify/ws",
3
+ "version": "0.2.0",
4
+ "description": "Drop-in replacement for the `ws` npm package on Gjs — wraps globalThis.WebSocket (Soup.WebsocketConnection) and Soup.Server for the WebSocketServer side",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
16
+ "check": "tsc --noEmit",
17
+ "build": "yarn build:gjsify && yarn build:types",
18
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
19
+ "build:types": "tsc",
20
+ "build:test": "yarn build:test:gjs && yarn build:test:node",
21
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
22
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
23
+ "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
24
+ "test:gjs": "gjsify run test.gjs.mjs",
25
+ "test:node": "node test.node.mjs"
26
+ },
27
+ "keywords": [
28
+ "gjs",
29
+ "node",
30
+ "ws",
31
+ "websocket"
32
+ ],
33
+ "dependencies": {
34
+ "@girs/gio-2.0": "^2.88.0-4.0.0-rc.9",
35
+ "@girs/glib-2.0": "^2.88.0-4.0.0-rc.9",
36
+ "@girs/soup-3.0": "^3.6.6-4.0.0-rc.9",
37
+ "@gjsify/buffer": "^0.2.0",
38
+ "@gjsify/crypto": "^0.2.0",
39
+ "@gjsify/events": "^0.2.0",
40
+ "@gjsify/utils": "^0.2.0",
41
+ "@gjsify/websocket": "^0.2.0"
42
+ },
43
+ "devDependencies": {
44
+ "@gjsify/cli": "^0.2.0",
45
+ "@gjsify/unit": "^0.2.0",
46
+ "@types/node": "^25.6.0",
47
+ "typescript": "^6.0.3"
48
+ }
49
+ }
@@ -0,0 +1,15 @@
1
+ // Constants shared between WebSocket and WebSocketServer.
2
+ // Values chosen to match the `ws` npm package where observable.
3
+
4
+ export const BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'] as const;
5
+ export const EMPTY_BUFFER = new Uint8Array(0);
6
+
7
+ // WebSocket readyState values — identical to the W3C spec and `ws` npm pkg.
8
+ export const CONNECTING = 0;
9
+ export const OPEN = 1;
10
+ export const CLOSING = 2;
11
+ export const CLOSED = 3;
12
+
13
+ /** Internal marker for native `globalThis.WebSocket` instances handed in via
14
+ * `{ socket }` option (not currently supported but reserved). */
15
+ export const kWebSocket = Symbol('ws:kWebSocket');