@gjsify/ws 0.24.2 → 0.26.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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import"./constants.js";import{EventEmitter as e}from"@gjsify/events";import{Buffer as t}from"@gjsify/buffer";import{createHash as n}from"@gjsify/crypto";import r from"@girs/soup-3.0";import i from"@girs/glib-2.0";import a from"@girs/gio-2.0";import{ensureMainLoop as o}from"@gjsify/utils/core";const s=/^[+/0-9A-Za-z]{22}==$/;var ServerSideWebSocket=class extends e{static CONNECTING=0;static OPEN=1;static CLOSING=2;static CLOSED=3;CONNECTING=0;OPEN=1;CLOSING=2;CLOSED=3;readyState=1;protocol=``;extensions=``;url=``;_conn;constructor(e,n){super(),this._conn=e,this.url=n,e.connect(`message`,(e,n,i)=>{let a=i.get_data();if(n===r.WebsocketDataType.TEXT){let e=typeof a==`string`?a:a?new TextDecoder(`utf-8`).decode(a):``;this.emit(`message`,e,!1)}else{let e=a?t.from(a):t.alloc(0);this.emit(`message`,e,!0)}}),e.connect(`closed`,()=>{this.readyState=3;let n=e.get_close_code()||1005,r=e.get_close_data()||``;this.emit(`close`,n,t.from(r))}),e.connect(`error`,(e,t)=>{this.emit(`error`,Error(t.message))})}send(e,n,a){let o=typeof n==`function`?n:a;try{if(typeof e==`string`){let t=new TextEncoder().encode(e);this._conn.send_message(r.WebsocketDataType.TEXT,new i.Bytes(t))}else{let n;if(t.isBuffer(e)){let t=e;n=new i.Bytes(new Uint8Array(t.buffer,t.byteOffset,t.byteLength))}else if(e instanceof ArrayBuffer)n=new i.Bytes(new Uint8Array(e));else if(ArrayBuffer.isView(e)){let t=e;n=new i.Bytes(new Uint8Array(t.buffer,t.byteOffset,t.byteLength))}else throw TypeError(`Unsupported send() payload type`);this._conn.send_message(r.WebsocketDataType.BINARY,n)}o&&queueMicrotask(()=>o())}catch(e){let t=e instanceof Error?e:Error(String(e));queueMicrotask(o?()=>o(t):()=>this.emit(`error`,t))}}close(e,n){if(!(this.readyState===3||this.readyState===2)){this.readyState=2;try{let r=n===void 0?null:t.isBuffer(n)?n.toString(`utf8`):String(n);this._conn.close(e??1e3,r)}catch(e){this.emit(`error`,e instanceof Error?e:Error(String(e)))}}}terminate(){
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import"./constants.js";import{EventEmitter as e}from"@gjsify/events";import{Buffer as t}from"@gjsify/buffer";import{createHash as n}from"@gjsify/crypto";import r from"@girs/soup-3.0";import i from"@girs/glib-2.0";import a from"@girs/gio-2.0";import{ensureMainLoop as o}from"@gjsify/utils/core";const s=/^[+/0-9A-Za-z]{22}==$/;var ServerSideWebSocket=class extends e{static CONNECTING=0;static OPEN=1;static CLOSING=2;static CLOSED=3;CONNECTING=0;OPEN=1;CLOSING=2;CLOSED=3;readyState=1;protocol=``;extensions=``;url=``;_conn;constructor(e,n){super(),this._conn=e,this.url=n,e.connect(`message`,(e,n,i)=>{let a=i.get_data();if(n===r.WebsocketDataType.TEXT){let e=typeof a==`string`?a:a?new TextDecoder(`utf-8`).decode(a):``;this.emit(`message`,e,!1)}else{let e=a?t.from(a):t.alloc(0);this.emit(`message`,e,!0)}}),e.connect(`closed`,()=>{this.readyState=3;let n=e.get_close_code()||1005,r=e.get_close_data()||``;this.emit(`close`,n,t.from(r))}),e.connect(`error`,(e,t)=>{this.emit(`error`,Error(t.message))})}send(e,n,a){let o=typeof n==`function`?n:a;try{if(typeof e==`string`){let t=new TextEncoder().encode(e);this._conn.send_message(r.WebsocketDataType.TEXT,new i.Bytes(t))}else{let n;if(t.isBuffer(e)){let t=e;n=new i.Bytes(new Uint8Array(t.buffer,t.byteOffset,t.byteLength))}else if(e instanceof ArrayBuffer)n=new i.Bytes(new Uint8Array(e));else if(ArrayBuffer.isView(e)){let t=e;n=new i.Bytes(new Uint8Array(t.buffer,t.byteOffset,t.byteLength))}else throw TypeError(`Unsupported send() payload type`);this._conn.send_message(r.WebsocketDataType.BINARY,n)}o&&queueMicrotask(()=>o())}catch(e){let t=e instanceof Error?e:Error(String(e));queueMicrotask(o?()=>o(t):()=>this.emit(`error`,t))}}close(e,n){if(!(this.readyState===3||this.readyState===2)){this.readyState=2;try{let r=n===void 0?null:t.isBuffer(n)?n.toString(`utf8`):String(n);this._conn.close(e??1e3,r)}catch(e){this.emit(`error`,e instanceof Error?e:Error(String(e)))}}}terminate(){this.readyState!==3&&(this.readyState=2,this._conn.close(1006,null))}},WebSocketServer=class extends e{options;clients=new Set;path;_server=null;_address=null;constructor(e={},t){if(super(),this.options=e,this.path=e.path??`/`,e.noServer){if(e.port!==void 0||e.server!==void 0)throw Error(`ws.WebSocketServer: { noServer: true } is mutually exclusive with port and server.`);t&&this.once(`listening`,t);return}if(e.port===void 0&&!e.server)throw Error(`ws.WebSocketServer requires either options.port or options.server on Gjs.`);t&&this.once(`listening`,t),this._start(e)}_buildVerifyClientInfo(e){let t=e.get_request_headers(),n={};t.foreach((e,t)=>{let r=e.toLowerCase(),i=n[r];i===void 0?n[r]=t:Array.isArray(i)?i.push(t):n[r]=[i,t]});let r=e.get_uri(),i=r.get_path()??`/`,o=r.get_query(),s=o?`${i}?${o}`:i,c=e.get_remote_host()??`127.0.0.1`,l=e.get_remote_address(),u=l instanceof a.InetSocketAddress?l.get_port():0;return{origin:n.origin??``,secure:!1,req:{method:e.get_method(),url:s,headers:n,socket:{remoteAddress:c,remotePort:u}}}}_setupHandlers(e,t){try{e.remove_websocket_extension(r.WebsocketExtensionDeflate.$gtype)}catch{}if(t.verifyClient){let n=t.verifyClient;e.add_handler(this.path,(e,t)=>{if((t.get_request_headers().get_one(`Upgrade`)??``).toLowerCase()!==`websocket`)return;let r=this._buildVerifyClientInfo(t);n.length>=2?(t.pause(),n(r,(e,n=401)=>{e||t.set_status(n,null),t.unpause()})):n(r)||t.set_status(401,null)})}e.add_websocket_handler(this.path,null,null,(e,n,r,i)=>{let a=new ServerSideWebSocket(i,n.get_uri()?.to_string()??this.path);if(t.handleProtocols){let e=n.get_request_headers().get_one(`Sec-WebSocket-Protocol`)??``,r=new Set(e.split(`,`).map(e=>e.trim()).filter(Boolean)),i=this._buildVerifyClientInfo(n).req,o=t.handleProtocols(r,i);o&&(a.protocol=o)}t.clientTracking!==!1&&(this.clients.add(a),a.on(`close`,()=>this.clients.delete(a))),this.emit(`connection`,a,n)})}_start(e){try{if(e.server){let t=e.server,n=t.soupServer;if(!n)throw Error(`options.server has no active Soup.Server. Ensure httpServer.listen() was called before creating WebSocketServer.`);this._server=n,this._setupHandlers(n,e),o();let r=t.address();r&&(this._address={address:r.address,family:r.family,port:r.port}),queueMicrotask(()=>this.emit(`listening`))}else{this._server=new r.Server({}),this._setupHandlers(this._server,e);let t=e.host??`0.0.0.0`,n=e.port;t===`127.0.0.1`||t===`localhost`?this._server.listen_local(n,r.ServerListenOptions.IPV4_ONLY):t===`::1`?this._server.listen_local(n,r.ServerListenOptions.IPV6_ONLY):this._server.listen_all(n,0);let i=this._server.get_listeners(),a=n;if(i&&i.length>0){let e=i[0].get_local_address();e&&typeof e.get_port==`function`&&(a=e.get_port())}o(),this._address={address:t,family:`IPv4`,port:a},queueMicrotask(()=>this.emit(`listening`))}}catch(e){queueMicrotask(()=>this.emit(`error`,e instanceof Error?e:Error(String(e))))}}address(){return this._address}close(e){try{for(let e of this.clients)e.close();this.clients.clear(),this.options.server||this._server?.disconnect(),this._server=null,this._address=null,this.emit(`close`),e&&queueMicrotask(()=>e())}catch(t){let n=t instanceof Error?t:Error(String(t));this.emit(`error`,n),e&&queueMicrotask(()=>e(n))}}handleUpgrade(e,t,n,r){if(!this._validateUpgradeHeaders(e,t))return;let i=e.headers?.[`sec-websocket-key`]??``,doUpgrade=()=>this._completeUpgrade(e,t,i,r);if(this.options.verifyClient){let n=this.options.verifyClient,r=this._buildVerifyClientInfoFromReq(e);if(n.length>=2){n(r,(e,n=401)=>{if(!e){this._abortHandshake(t,n);return}doUpgrade()});return}if(!n(r)){this._abortHandshake(t,401);return}}doUpgrade()}shouldHandle(e){if(this.path===`/`)return!0;let t=e?.url??`/`;return t===this.path||t.startsWith(this.path+`?`)||t.startsWith(this.path+`/`)}_validateUpgradeHeaders(e,t){let n=e.headers??{};if(e.method!==`GET`)return this._abortHandshake(t,405),!1;if((n.upgrade??``).toLowerCase()!==`websocket`||!s.test(n[`sec-websocket-key`]??``))return this._abortHandshake(t,400),!1;let r=Number(n[`sec-websocket-version`]??`0`);return r!==13&&r!==8?(this._abortHandshake(t,426),!1):this.shouldHandle(e)?!0:(this._abortHandshake(t,400),!1)}_completeUpgrade(e,t,a,o){let s=[`HTTP/1.1 101 Switching Protocols`,`Upgrade: websocket`,`Connection: Upgrade`,`Sec-WebSocket-Accept: ${n(`sha1`).update(a+`258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(`base64`)}`],c=null;if(this.options.handleProtocols){let t=e.headers?.[`sec-websocket-protocol`]??``,n=new Set(t.split(`,`).map(e=>e.trim()).filter(Boolean)),r=this.options.handleProtocols(n,this._buildVerifyClientInfoFromReq(e).req);r&&(c=r,s.push(`Sec-WebSocket-Protocol: ${r}`))}this.emit(`headers`,s,e);let l=s.join(`\r
|
|
2
2
|
`)+`\r
|
|
3
3
|
\r
|
|
4
4
|
`;t.write(l,()=>{let n=typeof t._releaseIOStream==`function`?t._releaseIOStream():null;if(!n){t.destroy?.();return}let a=e.url??`/`,s=i.Uri.parse(`ws://localhost${a}`,i.UriFlags.NONE),l=new ServerSideWebSocket(r.WebsocketConnection.new(n,s,r.WebsocketConnectionType.SERVER,null,c,[]),a);c&&(l.protocol=c),this.options.clientTracking!==!1&&(this.clients.add(l),l.on(`close`,()=>this.clients.delete(l))),o(l,e)})}_abortHandshake(e,t){let n={400:`Bad Request`,401:`Unauthorized`,403:`Forbidden`,405:`Method Not Allowed`,426:`Upgrade Required`}[t]??`Error`;e.write?.(`HTTP/1.1 ${t} ${n}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n`,()=>{e.destroy?.()})}_buildVerifyClientInfoFromReq(e){let t=e.headers??{};return{origin:t.origin??``,secure:!1,req:{method:e.method??`GET`,url:e.url??`/`,headers:t,socket:{remoteAddress:e.socket?.remoteAddress??`127.0.0.1`,remotePort:e.socket?.remotePort??0}}}}};export{WebSocketServer};
|
package/lib/types/websocket.d.ts
CHANGED
|
@@ -41,8 +41,8 @@ export interface ClientOptions {
|
|
|
41
41
|
* frames internally; no JS hook exposed).
|
|
42
42
|
* Consumers that rely on 'ping'/'pong' for
|
|
43
43
|
* keep-alive application logic need to use
|
|
44
|
-
* data messages instead.
|
|
45
|
-
*
|
|
44
|
+
* data messages instead. Tracked in
|
|
45
|
+
* status/upstream-patch-candidates.md.
|
|
46
46
|
* - 'upgrade' / 'unexpected-response' / 'redirect'
|
|
47
47
|
* → NOT EMITTED on Gjs (Soup does not expose
|
|
48
48
|
* the raw HTTP upgrade response). Code
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/ws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"@girs/gio-2.0": "^4.1.0",
|
|
42
42
|
"@girs/glib-2.0": "^4.1.0",
|
|
43
43
|
"@girs/soup-3.0": "^4.1.0",
|
|
44
|
-
"@gjsify/buffer": "^0.
|
|
45
|
-
"@gjsify/crypto": "^0.
|
|
46
|
-
"@gjsify/events": "^0.
|
|
47
|
-
"@gjsify/utils": "^0.
|
|
48
|
-
"@gjsify/websocket": "^0.
|
|
44
|
+
"@gjsify/buffer": "^0.26.0",
|
|
45
|
+
"@gjsify/crypto": "^0.26.0",
|
|
46
|
+
"@gjsify/events": "^0.26.0",
|
|
47
|
+
"@gjsify/utils": "^0.26.0",
|
|
48
|
+
"@gjsify/websocket": "^0.26.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@gjsify/cli": "^0.
|
|
52
|
-
"@gjsify/unit": "^0.
|
|
51
|
+
"@gjsify/cli": "^0.26.0",
|
|
52
|
+
"@gjsify/unit": "^0.26.0",
|
|
53
53
|
"@types/node": "^25.9.2",
|
|
54
54
|
"typescript": "^6.0.3"
|
|
55
55
|
},
|