@gjsify/ws 0.4.30 → 0.4.31

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/lib/esm/stream.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{Duplex as e}from"node:stream";function emitClose(e){e.emit(`close`)}function duplexOnEnd(){!this.destroyed&&this._writableState?.finished&&this.destroy()}function duplexOnError(e){this.removeListener(`error`,duplexOnError),this.destroy(),this.listenerCount(`error`)===0&&this.emit(`error`,e)}function createWebSocketStream(t,n={}){let r=!0,i=new e({...n,autoDestroy:!1,emitClose:!1,objectMode:!1,writableObjectMode:!1});t.on(`message`,(e,n)=>{let r;r=n||i.readableObjectMode?e:typeof e==`string`?Buffer.from(e):e,!i.push(r)&&typeof t.pause==`function`&&t.pause()}),t.once(`error`,e=>{i.destroyed||(r=!1,i.destroy(e))}),t.once(`close`,()=>{i.destroyed||i.push(null)});let a=i;return a._destroy=function(e,n){if(t.readyState===t.CLOSED){n(e),process.nextTick(emitClose,i);return}let a=!1;t.once(`error`,e=>{a=!0,n(e)}),t.once(`close`,()=>{a||n(e),process.nextTick(emitClose,i)}),r&&t.terminate()},a._final=function(e){if(t.readyState===t.CONNECTING){t.once(`open`,()=>a._final(e));return}if(t.readyState===t.CLOSED||t.readyState===t.CLOSING){e();return}t.once(`close`,e),t.close()},i._read=function(){typeof t.resume==`function`&&t.resume()},i._write=function(e,n,r){if(t.readyState===t.CONNECTING){t.once(`open`,()=>i._write(e,n,r));return}t.send(e,r)},i.on(`end`,duplexOnEnd),i.on(`error`,duplexOnError),i}export{createWebSocketStream};
1
+ import"./_virtual/_rolldown/runtime.js";import{Duplex as e}from"node:stream";function emitClose(e){e.emit(`close`)}function duplexOnEnd(){!this.destroyed&&this._writableState?.finished&&this.destroy()}function duplexOnError(e){this.removeListener(`error`,duplexOnError),this.destroy(),this.listenerCount(`error`)===0&&this.emit(`error`,e)}function createWebSocketStream(t,n={}){let r=!0,i=t,a=new e({...n,autoDestroy:!1,emitClose:!1,objectMode:!1,writableObjectMode:!1});i.on(`message`,(...e)=>{let t=e[0],n=e[1],r;r=n||a.readableObjectMode?t:typeof t==`string`?Buffer.from(t):t,!a.push(r)&&typeof i.pause==`function`&&i.pause()}),i.once(`error`,(...e)=>{let t=e[0];a.destroyed||(r=!1,a.destroy(t))}),i.once(`close`,()=>{a.destroyed||a.push(null)});let o=a;return o._destroy=function(e,t){if(i.readyState===i.CLOSED){t(e),process.nextTick(emitClose,a);return}let n=!1;i.once(`error`,(...e)=>{let r=e[0];n=!0,t(r)}),i.once(`close`,()=>{n||t(e),process.nextTick(emitClose,a)}),r&&i.terminate()},o._final=function(e){if(i.readyState===i.CONNECTING){i.once(`open`,()=>o._final(e));return}if(i.readyState===i.CLOSED||i.readyState===i.CLOSING){e();return}i.once(`close`,()=>e()),i.close()},a._read=function(){typeof i.resume==`function`&&i.resume()},a._write=function(e,t,n){if(i.readyState===i.CONNECTING){i.once(`open`,()=>a._write(e,t,n));return}i.send(e,n)},a.on(`end`,duplexOnEnd),a.on(`error`,duplexOnError),a}export{createWebSocketStream};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{BINARY_TYPES as e}from"./constants.js";import{EventEmitter as t}from"@gjsify/events";import{Buffer as n}from"@gjsify/buffer";import{WebSocket as r}from"@gjsify/websocket";var WebSocket=class extends t{static CONNECTING=0;static OPEN=1;static CLOSING=2;static CLOSED=3;CONNECTING=0;OPEN=1;CLOSING=2;CLOSED=3;readyState=0;url=``;protocol=``;extensions=``;bufferedAmount=0;binaryType=`nodebuffer`;_native=null;constructor(e,t,n={}){if(super(),e===null){queueMicrotask(()=>this._fail(`Constructing ws.WebSocket with null address is not supported on Gjs`));return}this.url=typeof e==`string`?e:String(e);let r=this._resolveProtocols(t,n);this._openNative(this.url,r,n)}_resolveProtocols(e,t){if(e!==void 0)return Array.isArray(e)?e:[e];if(t.protocols!==void 0)return Array.isArray(t.protocols)?t.protocols:[t.protocols];if(t.protocol!==void 0)return[t.protocol]}_openNative(e,t,n){if(typeof r!=`function`){queueMicrotask(()=>this._fail("@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."));return}let i={perMessageDeflate:n.perMessageDeflate!==!1,headers:n.headers,origin:n.origin,handshakeTimeout:n.handshakeTimeout};try{this._native=new r(e,t,i)}catch(e){queueMicrotask(()=>this._fail(e instanceof Error?e:Error(String(e))));return}this._native.binaryType=`arraybuffer`,this._native.addEventListener(`open`,()=>this._onOpen()),this._native.addEventListener(`message`,e=>this._onMessage(e)),this._native.addEventListener(`close`,e=>this._onClose(e)),this._native.addEventListener(`error`,e=>this._onError(e))}_fail(e){let t=typeof e==`string`?Error(e):e;this.readyState=3,this.emit(`error`,t),this._dispatchEvent(`error`,{error:t,message:t.message}),this.emit(`close`,1006,n.from(t.message)),this._dispatchEvent(`close`,{code:1006,reason:t.message,wasClean:!1})}_onOpen(){this.readyState=1,typeof this._native.protocol==`string`&&(this.protocol=this._native.protocol),typeof this._native.extensions==`string`&&(this.extensions=this._native.extensions),this.emit(`open`),this._dispatchEvent(`open`,{})}_onMessage(e){let t=e?.data,n,r=!1;typeof t==`string`?(n=t,r=!1):t instanceof ArrayBuffer?(r=!0,n=this._decodeBinary(t)):ArrayBuffer.isView(t)?(r=!0,n=this._decodeBinary(t.buffer)):(n=t,r=!1),this.emit(`message`,n,r),this._dispatchEvent(`message`,{data:n,type:r?`binary`:`text`})}_decodeBinary(e){switch(this.binaryType){case`arraybuffer`:return e;case`fragments`:return[n.from(e)];case`blob`:{let t=globalThis.Blob;return t?new t([new Uint8Array(e)]):n.from(e)}default:return n.from(e)}}_onClose(e){let t=typeof e?.code==`number`?e.code:1006,r=typeof e?.reason==`string`?e.reason:``;this.readyState=3,this.emit(`close`,t,n.from(r)),this._dispatchEvent(`close`,{code:t,reason:r,wasClean:!!e?.wasClean})}_onError(e){let t=e?.message||`WebSocket error`,n=e?.error instanceof Error?e.error:Error(t);this.emit(`error`,n),this._dispatchEvent(`error`,{error:n,message:t})}send(e,t,n){let r=typeof t==`function`?t:n;if(this.readyState===0)throw Error(`WebSocket is not open: readyState 0 (CONNECTING)`);if(this.readyState!==1){let e=Error(`WebSocket is not open: readyState `+this.readyState);queueMicrotask(r?()=>r(e):()=>this.emit(`error`,e));return}this._nativeSend(e,r)}_nativeSend(e,t){try{let r=e;typeof e==`number`||typeof e==`boolean`?r=String(e):n.isBuffer(e)&&(r=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)),this._native.send(r),t&&queueMicrotask(()=>t())}catch(e){let n=e instanceof Error?e:Error(String(e));queueMicrotask(t?()=>t(n):()=>this.emit(`error`,n))}}_sizeOf(e){return typeof e==`string`?e.length:e instanceof ArrayBuffer||ArrayBuffer.isView(e)?e.byteLength:0}close(e,t){if(this.readyState!==3&&this.readyState!==2){this.readyState=2;try{let r=t===void 0?void 0:n.isBuffer(t)?t.toString(`utf8`):String(t);e===void 0?this._native?.close():r===void 0?this._native?.close(e):this._native?.close(e,r)}catch(e){this.emit(`error`,e instanceof Error?e:Error(String(e)))}}}terminate(){if(this.readyState!==3){this.readyState=2;try{this._native?.close(1006,`terminated`)}catch{}}}get isPaused(){return this.readyState===2||this.readyState===3}_eventTargetListeners=new Map;addEventListener(e,t){let n=this._eventTargetListeners.get(e);n||(n=new Set,this._eventTargetListeners.set(e,n)),n.add(t)}removeEventListener(e,t){this._eventTargetListeners.get(e)?.delete(t)}_dispatchEvent(e,t){let n=this._eventTargetListeners.get(e);if(!n||n.size===0)return;let r=Object.assign({type:e,target:this},t);for(let e of n)try{e(r)}catch(e){queueMicrotask(()=>this.emit(`error`,e))}}static get BINARY_TYPES(){return e}};WebSocket.WebSocket=WebSocket;export{WebSocket};
1
+ import"./_virtual/_rolldown/runtime.js";import{BINARY_TYPES as e}from"./constants.js";import{EventEmitter as t}from"@gjsify/events";import{Buffer as n}from"@gjsify/buffer";import{WebSocket as r}from"@gjsify/websocket";var WebSocket=class extends t{static CONNECTING=0;static OPEN=1;static CLOSING=2;static CLOSED=3;CONNECTING=0;OPEN=1;CLOSING=2;CLOSED=3;readyState=0;url=``;protocol=``;extensions=``;bufferedAmount=0;binaryType=`nodebuffer`;_native=null;constructor(e,t,n={}){if(super(),e===null){queueMicrotask(()=>this._fail(`Constructing ws.WebSocket with null address is not supported on Gjs`));return}this.url=typeof e==`string`?e:String(e);let r=this._resolveProtocols(t,n);this._openNative(this.url,r,n)}_resolveProtocols(e,t){if(e!==void 0)return Array.isArray(e)?e:[e];if(t.protocols!==void 0)return Array.isArray(t.protocols)?t.protocols:[t.protocols];if(t.protocol!==void 0)return[t.protocol]}_openNative(e,t,n){if(typeof r!=`function`){queueMicrotask(()=>this._fail("@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."));return}let i={perMessageDeflate:n.perMessageDeflate!==!1,headers:n.headers,origin:n.origin,handshakeTimeout:n.handshakeTimeout};try{this._native=new r(e,t,i)}catch(e){queueMicrotask(()=>this._fail(e instanceof Error?e:Error(String(e))));return}this._native.binaryType=`arraybuffer`,this._native.addEventListener(`open`,()=>this._onOpen()),this._native.addEventListener(`message`,e=>this._onMessage(e)),this._native.addEventListener(`close`,e=>this._onClose(e)),this._native.addEventListener(`error`,e=>this._onError(e))}_fail(e){let t=typeof e==`string`?Error(e):e;this.readyState=3,this.emit(`error`,t),this._dispatchEvent(`error`,{error:t,message:t.message}),this.emit(`close`,1006,n.from(t.message)),this._dispatchEvent(`close`,{code:1006,reason:t.message,wasClean:!1})}_onOpen(){this.readyState=1,typeof this._native?.protocol==`string`&&(this.protocol=this._native.protocol),typeof this._native?.extensions==`string`&&(this.extensions=this._native.extensions),this.emit(`open`),this._dispatchEvent(`open`,{})}_onMessage(e){let t=e?.data,n,r=!1;typeof t==`string`?(n=t,r=!1):t instanceof ArrayBuffer?(r=!0,n=this._decodeBinary(t)):ArrayBuffer.isView(t)?(r=!0,n=this._decodeBinary(t.buffer)):(n=t,r=!1),this.emit(`message`,n,r),this._dispatchEvent(`message`,{data:n,type:r?`binary`:`text`})}_decodeBinary(e){switch(this.binaryType){case`arraybuffer`:return e;case`fragments`:return[n.from(e)];case`blob`:{let t=globalThis.Blob;return t?new t([new Uint8Array(e)]):n.from(e)}default:return n.from(e)}}_onClose(e){let t=typeof e?.code==`number`?e.code:1006,r=typeof e?.reason==`string`?e.reason:``;this.readyState=3,this.emit(`close`,t,n.from(r)),this._dispatchEvent(`close`,{code:t,reason:r,wasClean:!!e?.wasClean})}_onError(e){let t=typeof e?.message==`string`&&e.message||`WebSocket error`,n=e?.error instanceof Error?e.error:Error(t);this.emit(`error`,n),this._dispatchEvent(`error`,{error:n,message:t})}send(e,t,n){let r=typeof t==`function`?t:n;if(this.readyState===0)throw Error(`WebSocket is not open: readyState 0 (CONNECTING)`);if(this.readyState!==1){let e=Error(`WebSocket is not open: readyState `+this.readyState);queueMicrotask(r?()=>r(e):()=>this.emit(`error`,e));return}this._nativeSend(e,r)}_nativeSend(e,t){try{let r;r=typeof e==`string`?e:typeof e==`number`||typeof e==`boolean`?String(e):n.isBuffer(e)?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e instanceof ArrayBuffer||ArrayBuffer.isView(e)?e:String(e),this._native?.send(r),t&&queueMicrotask(()=>t())}catch(e){let n=e instanceof Error?e:Error(String(e));queueMicrotask(t?()=>t(n):()=>this.emit(`error`,n))}}_sizeOf(e){return typeof e==`string`?e.length:e instanceof ArrayBuffer||ArrayBuffer.isView(e)?e.byteLength:0}close(e,t){if(this.readyState!==3&&this.readyState!==2){this.readyState=2;try{let r=t===void 0?void 0:n.isBuffer(t)?t.toString(`utf8`):String(t);e===void 0?this._native?.close():r===void 0?this._native?.close(e):this._native?.close(e,r)}catch(e){this.emit(`error`,e instanceof Error?e:Error(String(e)))}}}terminate(){if(this.readyState!==3){this.readyState=2;try{this._native?.close(1006,`terminated`)}catch{}}}get isPaused(){return this.readyState===2||this.readyState===3}_eventTargetListeners=new Map;addEventListener(e,t){let n=this._eventTargetListeners.get(e);n||(n=new Set,this._eventTargetListeners.set(e,n)),n.add(t)}removeEventListener(e,t){this._eventTargetListeners.get(e)?.delete(t)}_dispatchEvent(e,t){let n=this._eventTargetListeners.get(e);if(!n||n.size===0)return;let r=Object.assign({type:e,target:this},t);for(let e of n)try{e(r)}catch(e){queueMicrotask(()=>this.emit(`error`,e))}}static get BINARY_TYPES(){return e}};WebSocket.WebSocket=WebSocket;export{WebSocket};
@@ -1,2 +1,24 @@
1
1
  import { Duplex } from 'node:stream';
2
- export declare function createWebSocketStream(ws: any, options?: Record<string, unknown>): Duplex;
2
+ import type { WebSocket } from './websocket.js';
3
+ /** Structural subset of a `ws.WebSocket` we read in the Duplex bridge. Includes
4
+ * the two server-side wrappers in this package (the public `WebSocket` client
5
+ * and the private `ServerSideWebSocket` from `websocket-server.ts`) plus any
6
+ * other ws-compatible class — typed structurally so the bridge stays drop-in
7
+ * for the npm `ws` semantics (`ws.on(event, …)`/`ws.send(…)`/`ws.close()`/`ws.terminate()`
8
+ * + the readyState constants both as instance and static fields). */
9
+ interface WSLike {
10
+ readyState: number;
11
+ readonly CONNECTING: number;
12
+ readonly OPEN: number;
13
+ readonly CLOSING: number;
14
+ readonly CLOSED: number;
15
+ on(event: string, listener: (...args: unknown[]) => void): unknown;
16
+ once(event: string, listener: (...args: unknown[]) => void): unknown;
17
+ send(data: string | Buffer, cb?: (err?: Error) => void): void;
18
+ close(): void;
19
+ terminate(): void;
20
+ pause?(): void;
21
+ resume?(): void;
22
+ }
23
+ export declare function createWebSocketStream(ws: WebSocket | WSLike, options?: Record<string, unknown>): Duplex;
24
+ export {};
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from '@gjsify/events';
2
2
  import { Buffer } from '@gjsify/buffer';
3
3
  import Soup from '@girs/soup-3.0';
4
+ import Gio from '@girs/gio-2.0';
4
5
  /** Structural duck-type for @gjsify/http Server — avoids a hard dep on @gjsify/http. */
5
6
  interface HttpServer {
6
7
  soupServer: Soup.Server | null;
@@ -10,6 +11,29 @@ interface HttpServer {
10
11
  port: number;
11
12
  } | null;
12
13
  }
14
+ /** Structural duck-type for an HTTP upgrade request (Node `IncomingMessage`-shaped).
15
+ * ws's `handleUpgrade(req, socket, head, cb)` accepts the IncomingMessage produced
16
+ * by `http.Server`'s 'upgrade' event — modelled structurally so this file does not
17
+ * hard-depend on `@gjsify/http`'s exact request class. Members read defensively. */
18
+ interface UpgradeRequest {
19
+ method?: string;
20
+ url?: string;
21
+ headers?: Record<string, string | string[] | undefined>;
22
+ socket?: {
23
+ remoteAddress?: string;
24
+ remotePort?: number;
25
+ };
26
+ }
27
+ /** Structural duck-type for the upgrade socket (Node `Duplex`/`net.Socket`-shaped).
28
+ * We only need `write`/`destroy` to send the 101/4xx response, plus the optional
29
+ * `_releaseIOStream` hook that `@gjsify/net`'s Socket exposes so we can hand its
30
+ * underlying `Gio.IOStream` to `Soup.WebsocketConnection.new`. The hook stays
31
+ * optional so a plain net.Socket can still pass through `_abortHandshake`. */
32
+ interface UpgradeSocket {
33
+ write(chunk: string | Uint8Array, cb?: (err?: Error) => void): boolean;
34
+ destroy?(err?: Error): void;
35
+ _releaseIOStream?(): Gio.IOStream | null;
36
+ }
13
37
  export interface VerifyClientInfo {
14
38
  /** Value of the HTTP Origin request header (empty string if absent). */
15
39
  origin: string;
@@ -107,7 +131,7 @@ export declare class WebSocketServer extends EventEmitter {
107
131
  * Sec-WebSocket-Accept, emits 'headers' (mutable array), writes the 101
108
132
  * response via socket.write(), then creates Soup.WebsocketConnection from
109
133
  * the underlying IOStream and calls cb(ws, req). */
110
- handleUpgrade(req: any, socket: any, _head: Buffer, cb: (ws: ServerSideWebSocket, req: any) => void): void;
134
+ handleUpgrade(req: UpgradeRequest, socket: UpgradeSocket, _head: Buffer, cb: (ws: ServerSideWebSocket, req: UpgradeRequest) => void): void;
111
135
  shouldHandle(req: {
112
136
  url?: string;
113
137
  }): boolean;
@@ -1,6 +1,21 @@
1
1
  import { EventEmitter } from '@gjsify/events';
2
2
  import { Buffer } from '@gjsify/buffer';
3
3
  export type BinaryType = 'nodebuffer' | 'arraybuffer' | 'fragments' | 'blob';
4
+ /** Event shape passed to W3C-style `addEventListener` consumers. ws's public
5
+ * API uses `(event: ws.Event | ws.MessageEvent | ws.CloseEvent | ws.ErrorEvent) => void`;
6
+ * consumers read `event.type`/`event.data`/`event.code`/`event.reason`/`event.error`,
7
+ * all of which are surfaced here. `target` is the wrapper, matching ws. */
8
+ interface WsListenerEvent {
9
+ type: string;
10
+ target: WebSocket;
11
+ data?: unknown;
12
+ code?: number;
13
+ reason?: string;
14
+ wasClean?: boolean;
15
+ error?: unknown;
16
+ message?: string;
17
+ }
18
+ type WsListener = (ev: WsListenerEvent) => void;
4
19
  /** Options accepted by the ws constructor. Only a subset is honored on Gjs —
5
20
  * see README for the support matrix. Unknown options are silently ignored
6
21
  * to preserve drop-in compatibility with `ws`-calling code. */
@@ -58,9 +73,9 @@ export declare class WebSocket extends EventEmitter {
58
73
  extensions: string;
59
74
  bufferedAmount: number;
60
75
  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). */
76
+ /** The real WebSocket we delegate to. Typed structurally (`NativeWebSocketLike`)
77
+ * because the W3C ambient type comes from multiple realms depending on where
78
+ * this bundle ends up (GJS browser-like globals vs. Node's undici). */
64
79
  private _native;
65
80
  constructor(address: string | URL | null, protocols?: string | string[], options?: ClientOptions);
66
81
  /** Merge `protocols` arg and `options.protocols` / `options.protocol`. */
@@ -92,8 +107,9 @@ export declare class WebSocket extends EventEmitter {
92
107
  * ws.isPaused() / internal state checks that some consumers rely on. */
93
108
  get isPaused(): boolean;
94
109
  private _eventTargetListeners;
95
- addEventListener(type: string, listener: (ev: any) => void): void;
96
- removeEventListener(type: string, listener: (ev: any) => void): void;
110
+ addEventListener(type: string, listener: WsListener): void;
111
+ removeEventListener(type: string, listener: WsListener): void;
97
112
  private _dispatchEvent;
98
113
  static get BINARY_TYPES(): readonly ["nodebuffer", "arraybuffer", "fragments"];
99
114
  }
115
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/ws",
3
- "version": "0.4.30",
3
+ "version": "0.4.31",
4
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
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -37,15 +37,15 @@
37
37
  "@girs/gio-2.0": "2.88.0-4.0.1",
38
38
  "@girs/glib-2.0": "2.88.0-4.0.1",
39
39
  "@girs/soup-3.0": "3.6.6-4.0.1",
40
- "@gjsify/buffer": "^0.4.30",
41
- "@gjsify/crypto": "^0.4.30",
42
- "@gjsify/events": "^0.4.30",
43
- "@gjsify/utils": "^0.4.30",
44
- "@gjsify/websocket": "^0.4.30"
40
+ "@gjsify/buffer": "^0.4.31",
41
+ "@gjsify/crypto": "^0.4.31",
42
+ "@gjsify/events": "^0.4.31",
43
+ "@gjsify/utils": "^0.4.31",
44
+ "@gjsify/websocket": "^0.4.31"
45
45
  },
46
46
  "devDependencies": {
47
- "@gjsify/cli": "^0.4.30",
48
- "@gjsify/unit": "^0.4.30",
47
+ "@gjsify/cli": "^0.4.31",
48
+ "@gjsify/unit": "^0.4.31",
49
49
  "@types/node": "^25.9.1",
50
50
  "typescript": "^6.0.3"
51
51
  }