@gjsify/ws 0.3.21 → 0.4.3
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/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/stream.js +1 -1
- package/lib/esm/websocket-server.js +1 -1
- package/lib/esm/websocket.js +1 -1
- package/package.json +50 -47
- package/src/constants.ts +0 -15
- package/src/index.spec.ts +0 -144
- package/src/index.ts +0 -25
- package/src/stream.spec.ts +0 -121
- package/src/stream.ts +0 -107
- package/src/test.mts +0 -5
- package/src/websocket-server.spec.ts +0 -354
- package/src/websocket-server.ts +0 -561
- package/src/websocket.ts +0 -391
- package/tsconfig.json +0 -29
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Ported from refs/ws/test/websocket-server.test.js
|
|
3
|
-
// Original: Copyright (c) 2011+ Einar Otto Stangvik. MIT.
|
|
4
|
-
// Rewritten for @gjsify/unit — behavior preserved, scope narrowed to
|
|
5
|
-
// verifyClient, handleProtocols, and { server } mode supported by Phase 2.
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, on } from '@gjsify/unit';
|
|
8
|
-
import { WebSocket, WebSocketServer } from 'ws';
|
|
9
|
-
|
|
10
|
-
export default async () => {
|
|
11
|
-
await on('Gjs', async () => {
|
|
12
|
-
// ── verifyClient ──────────────────────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
await describe('WebSocketServer verifyClient sync', async () => {
|
|
15
|
-
await it('sync reject: client receives error, server never emits connection', async () => {
|
|
16
|
-
const wss = new WebSocketServer({
|
|
17
|
-
port: 0,
|
|
18
|
-
verifyClient: () => false,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
await new Promise<void>((resolve) => {
|
|
22
|
-
wss.on('listening', () => {
|
|
23
|
-
const addr = wss.address() as { address: string; family: string; port: number };
|
|
24
|
-
let connectionEmitted = false;
|
|
25
|
-
wss.on('connection', () => { connectionEmitted = true; });
|
|
26
|
-
|
|
27
|
-
const ws = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
28
|
-
ws.on('error', () => {
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
expect(connectionEmitted).toBe(false);
|
|
31
|
-
wss.close();
|
|
32
|
-
resolve();
|
|
33
|
-
}, 50);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
await it('sync accept: server emits connection', async () => {
|
|
40
|
-
const wss = new WebSocketServer({
|
|
41
|
-
port: 0,
|
|
42
|
-
verifyClient: () => true,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await new Promise<void>((resolve) => {
|
|
46
|
-
wss.on('listening', () => {
|
|
47
|
-
const addr = wss.address() as { address: string; family: string; port: number };
|
|
48
|
-
wss.on('connection', () => {
|
|
49
|
-
wss.close();
|
|
50
|
-
resolve();
|
|
51
|
-
});
|
|
52
|
-
const ws = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
53
|
-
ws.on('error', () => {});
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
await describe('WebSocketServer verifyClient async', async () => {
|
|
60
|
-
await it('async reject with 403: client receives error, no connection emitted', async () => {
|
|
61
|
-
const wss = new WebSocketServer({
|
|
62
|
-
port: 0,
|
|
63
|
-
verifyClient: (_info: any, cb: any) => { cb(false, 403); },
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
await new Promise<void>((resolve) => {
|
|
67
|
-
wss.on('listening', () => {
|
|
68
|
-
const addr = wss.address() as { address: string; family: string; port: number };
|
|
69
|
-
let connectionEmitted = false;
|
|
70
|
-
wss.on('connection', () => { connectionEmitted = true; });
|
|
71
|
-
|
|
72
|
-
const ws = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
73
|
-
ws.on('error', () => {
|
|
74
|
-
setTimeout(() => {
|
|
75
|
-
expect(connectionEmitted).toBe(false);
|
|
76
|
-
wss.close();
|
|
77
|
-
resolve();
|
|
78
|
-
}, 50);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
await it('async accept: server emits connection', async () => {
|
|
85
|
-
const wss = new WebSocketServer({
|
|
86
|
-
port: 0,
|
|
87
|
-
verifyClient: (_info: any, cb: any) => { cb(true); },
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await new Promise<void>((resolve) => {
|
|
91
|
-
wss.on('listening', () => {
|
|
92
|
-
const addr = wss.address() as { address: string; family: string; port: number };
|
|
93
|
-
wss.on('connection', () => {
|
|
94
|
-
wss.close();
|
|
95
|
-
resolve();
|
|
96
|
-
});
|
|
97
|
-
const ws = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
98
|
-
ws.on('error', () => {});
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
await it('populates verifyClient info fields (origin, req.url, req.headers)', async () => {
|
|
104
|
-
let capturedInfo: any = null;
|
|
105
|
-
|
|
106
|
-
const wss = new WebSocketServer({
|
|
107
|
-
port: 0,
|
|
108
|
-
verifyClient: (info: any) => {
|
|
109
|
-
capturedInfo = info;
|
|
110
|
-
return true;
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await new Promise<void>((resolve) => {
|
|
115
|
-
wss.on('listening', () => {
|
|
116
|
-
const addr = wss.address() as { address: string; family: string; port: number };
|
|
117
|
-
wss.on('connection', () => {
|
|
118
|
-
expect(typeof capturedInfo).toBe('object');
|
|
119
|
-
expect(capturedInfo).not.toBeNull();
|
|
120
|
-
expect(typeof capturedInfo.origin).toBe('string');
|
|
121
|
-
expect(typeof capturedInfo.secure).toBe('boolean');
|
|
122
|
-
expect(capturedInfo.secure).toBe(false);
|
|
123
|
-
expect(typeof capturedInfo.req).toBe('object');
|
|
124
|
-
expect(typeof capturedInfo.req.url).toBe('string');
|
|
125
|
-
expect(typeof capturedInfo.req.method).toBe('string');
|
|
126
|
-
expect(capturedInfo.req.method).toBe('GET');
|
|
127
|
-
expect(typeof capturedInfo.req.headers).toBe('object');
|
|
128
|
-
wss.close();
|
|
129
|
-
resolve();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const ws = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
133
|
-
ws.on('error', () => {});
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// ── handleProtocols ───────────────────────────────────────────────────
|
|
140
|
-
|
|
141
|
-
await describe('WebSocketServer handleProtocols', async () => {
|
|
142
|
-
await it('server-side ws.protocol reflects selected subprotocol', async () => {
|
|
143
|
-
let serverProtocol = '';
|
|
144
|
-
|
|
145
|
-
const wss = new WebSocketServer({
|
|
146
|
-
port: 0,
|
|
147
|
-
handleProtocols: (protocols: Set<string>) => {
|
|
148
|
-
// Prefer 'bar' over 'foo'
|
|
149
|
-
if (protocols.has('bar')) return 'bar';
|
|
150
|
-
return false;
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
await new Promise<void>((resolve) => {
|
|
155
|
-
wss.on('listening', () => {
|
|
156
|
-
const addr = wss.address() as { address: string; family: string; port: number };
|
|
157
|
-
wss.on('connection', (ws: any) => {
|
|
158
|
-
serverProtocol = ws.protocol;
|
|
159
|
-
ws.close();
|
|
160
|
-
wss.close();
|
|
161
|
-
resolve();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const ws = new WebSocket(`ws://127.0.0.1:${addr.port}/`, ['foo', 'bar']);
|
|
165
|
-
ws.on('error', () => {});
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
expect(serverProtocol).toBe('bar');
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ── noServer + handleUpgrade ──────────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
await describe('WebSocketServer noServer + handleUpgrade', async () => {
|
|
176
|
-
await it('echo works end-to-end via handleUpgrade', async () => {
|
|
177
|
-
const { createServer } = await import('node:http');
|
|
178
|
-
const server = createServer();
|
|
179
|
-
const wss = new WebSocketServer({ noServer: true });
|
|
180
|
-
server.on('upgrade', (req: any, socket: any, head: any) => {
|
|
181
|
-
wss.handleUpgrade(req, socket, head, (ws: any) => wss.emit('connection', ws, req));
|
|
182
|
-
});
|
|
183
|
-
await new Promise<void>((resolve, reject) => {
|
|
184
|
-
wss.on('connection', (ws: any) => {
|
|
185
|
-
ws.on('message', (msg: any) => { ws.send(String(msg)); });
|
|
186
|
-
});
|
|
187
|
-
server.listen(0, () => {
|
|
188
|
-
const addr = server.address() as { address: string; family: string; port: number };
|
|
189
|
-
const client = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
190
|
-
client.on('open', () => client.send('hello'));
|
|
191
|
-
client.on('message', (data: any) => {
|
|
192
|
-
expect(String(data)).toBe('hello');
|
|
193
|
-
client.close();
|
|
194
|
-
server.close();
|
|
195
|
-
wss.close();
|
|
196
|
-
resolve();
|
|
197
|
-
});
|
|
198
|
-
client.on('error', reject);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
await it('verifyClient reject via handleUpgrade closes client', async () => {
|
|
204
|
-
const { createServer } = await import('node:http');
|
|
205
|
-
const server = createServer();
|
|
206
|
-
const wss = new WebSocketServer({ noServer: true, verifyClient: () => false });
|
|
207
|
-
server.on('upgrade', (req: any, socket: any, head: any) => {
|
|
208
|
-
wss.handleUpgrade(req, socket, head, () => {});
|
|
209
|
-
});
|
|
210
|
-
await new Promise<void>((resolve) => {
|
|
211
|
-
server.listen(0, () => {
|
|
212
|
-
const addr = server.address() as { address: string; family: string; port: number };
|
|
213
|
-
const client = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
214
|
-
const done = () => { server.close(); wss.close(); resolve(); };
|
|
215
|
-
client.on('error', done);
|
|
216
|
-
client.on('unexpected-response', done);
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
await it('handleProtocols selection appears in client ws.protocol via handleUpgrade', async () => {
|
|
222
|
-
const { createServer } = await import('node:http');
|
|
223
|
-
const server = createServer();
|
|
224
|
-
const wss = new WebSocketServer({
|
|
225
|
-
noServer: true,
|
|
226
|
-
handleProtocols: (protocols: Set<string>) => protocols.has('bar') ? 'bar' : false,
|
|
227
|
-
});
|
|
228
|
-
server.on('upgrade', (req: any, socket: any, head: any) => {
|
|
229
|
-
wss.handleUpgrade(req, socket, head, (ws: any) => wss.emit('connection', ws, req));
|
|
230
|
-
});
|
|
231
|
-
let clientProtocol = '';
|
|
232
|
-
await new Promise<void>((resolve, reject) => {
|
|
233
|
-
wss.on('connection', (ws: any) => { ws.close(); });
|
|
234
|
-
server.listen(0, () => {
|
|
235
|
-
const addr = server.address() as { address: string; family: string; port: number };
|
|
236
|
-
const client = new WebSocket(`ws://127.0.0.1:${addr.port}/`, ['foo', 'bar']);
|
|
237
|
-
client.on('open', () => { clientProtocol = (client as any).protocol; });
|
|
238
|
-
client.on('close', () => { server.close(); wss.close(); resolve(); });
|
|
239
|
-
client.on('error', reject);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
expect(clientProtocol).toBe('bar');
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
await it('headers event allows injecting custom response headers', async () => {
|
|
246
|
-
const { createServer } = await import('node:http');
|
|
247
|
-
const server = createServer();
|
|
248
|
-
const wss = new WebSocketServer({ noServer: true });
|
|
249
|
-
let headersEventFired = false;
|
|
250
|
-
wss.on('headers', (headers: string[]) => {
|
|
251
|
-
headersEventFired = true;
|
|
252
|
-
headers.push('X-Custom: gjsify');
|
|
253
|
-
});
|
|
254
|
-
server.on('upgrade', (req: any, socket: any, head: any) => {
|
|
255
|
-
wss.handleUpgrade(req, socket, head, (ws: any) => wss.emit('connection', ws, req));
|
|
256
|
-
});
|
|
257
|
-
await new Promise<void>((resolve, reject) => {
|
|
258
|
-
wss.on('connection', (ws: any) => { ws.close(); });
|
|
259
|
-
server.listen(0, () => {
|
|
260
|
-
const addr = server.address() as { address: string; family: string; port: number };
|
|
261
|
-
const client = new WebSocket(`ws://127.0.0.1:${addr.port}/`);
|
|
262
|
-
client.on('open', () => {
|
|
263
|
-
expect(headersEventFired).toBe(true);
|
|
264
|
-
client.close();
|
|
265
|
-
});
|
|
266
|
-
client.on('close', () => { server.close(); wss.close(); resolve(); });
|
|
267
|
-
client.on('error', reject);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// ── { server } mode ───────────────────────────────────────────────────
|
|
274
|
-
|
|
275
|
-
await describe('WebSocketServer { server } mode', async () => {
|
|
276
|
-
await it('attaches to a shared Soup.Server; WebSocket echo works', async () => {
|
|
277
|
-
// We test the { server } mode by creating a WebSocketServer and attaching it
|
|
278
|
-
// to an existing Soup.Server via the soupServer getter.
|
|
279
|
-
// The WebSocketServer emits 'connection' with a ServerSideWebSocket for each
|
|
280
|
-
// new connection. We verify that message echo works end-to-end.
|
|
281
|
-
const Soup3 = (await import('@girs/soup-3.0')).default;
|
|
282
|
-
const GLib2 = (await import('@girs/glib-2.0')).default;
|
|
283
|
-
|
|
284
|
-
// Create the raw server, register our handler, THEN listen — matching
|
|
285
|
-
// the standalone WebSocketServer initialization order.
|
|
286
|
-
const rawSoup = new Soup3.Server({});
|
|
287
|
-
|
|
288
|
-
let actualPort = 0;
|
|
289
|
-
const fakeHttpServer: any = {
|
|
290
|
-
soupServer: rawSoup,
|
|
291
|
-
address: () => actualPort ? { address: '127.0.0.1', family: 'IPv4', port: actualPort } : null,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const wss = new WebSocketServer({ server: fakeHttpServer, path: '/ws' });
|
|
295
|
-
|
|
296
|
-
rawSoup.listen_local(0, Soup3.ServerListenOptions.IPV4_ONLY);
|
|
297
|
-
actualPort = (rawSoup.get_listeners()[0].get_local_address() as any).get_port();
|
|
298
|
-
|
|
299
|
-
// Use a raw Soup.Session for the client to avoid any @gjsify/ws wrapper effects
|
|
300
|
-
await new Promise<void>((resolve, reject) => {
|
|
301
|
-
wss.on('connection', (ws: any) => {
|
|
302
|
-
ws.on('error', (err: Error) => reject(err));
|
|
303
|
-
ws.on('message', (msg: any) => {
|
|
304
|
-
ws.send(String(msg));
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
wss.on('listening', () => {
|
|
309
|
-
// Use a raw Soup.Session WebSocket client to sidestep any wrapper
|
|
310
|
-
const session = new Soup3.Session();
|
|
311
|
-
const uri = GLib2.Uri.parse(`ws://127.0.0.1:${actualPort}/ws`, GLib2.UriFlags.NONE);
|
|
312
|
-
const soupMsg = new Soup3.Message({ method: 'GET', uri });
|
|
313
|
-
session.websocket_connect_async(
|
|
314
|
-
soupMsg, null, null,
|
|
315
|
-
GLib2.PRIORITY_DEFAULT, null,
|
|
316
|
-
(_self: unknown, asyncRes: any) => {
|
|
317
|
-
try {
|
|
318
|
-
const conn = session.websocket_connect_finish(asyncRes);
|
|
319
|
-
// Send 'hello-shared' after connection
|
|
320
|
-
const bytes = new TextEncoder().encode('hello-shared');
|
|
321
|
-
conn.send_message(Soup3.WebsocketDataType.TEXT, new GLib2.Bytes(bytes));
|
|
322
|
-
|
|
323
|
-
conn.connect('message', (_c: any, type: number, data: any) => {
|
|
324
|
-
const text = new TextDecoder().decode(data.toArray());
|
|
325
|
-
expect(text).toBe('hello-shared');
|
|
326
|
-
conn.close(1000, null);
|
|
327
|
-
wss.close();
|
|
328
|
-
rawSoup.disconnect();
|
|
329
|
-
resolve();
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
conn.connect('error', (_c: any, err: any) => {
|
|
333
|
-
wss.close();
|
|
334
|
-
rawSoup.disconnect();
|
|
335
|
-
reject(new Error(err.message));
|
|
336
|
-
});
|
|
337
|
-
} catch (err: any) {
|
|
338
|
-
wss.close();
|
|
339
|
-
rawSoup.disconnect();
|
|
340
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
wss.on('error', (err: Error) => {
|
|
347
|
-
rawSoup.disconnect();
|
|
348
|
-
reject(err);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
};
|