@ricsam/isolate-fetch 0.0.1 → 0.1.1

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,627 @@
1
+ import { test, describe, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert";
3
+ import ivm from "isolated-vm";
4
+ import { setupFetch, clearAllInstanceState, type FetchHandle, type WebSocketCommand } from "./index.ts";
5
+
6
+ describe("WebSocket", () => {
7
+ let isolate: ivm.Isolate;
8
+ let context: ivm.Context;
9
+ let fetchHandle: FetchHandle;
10
+
11
+ beforeEach(async () => {
12
+ isolate = new ivm.Isolate();
13
+ context = await isolate.createContext();
14
+ clearAllInstanceState();
15
+ fetchHandle = await setupFetch(context);
16
+ });
17
+
18
+ afterEach(() => {
19
+ fetchHandle.dispose();
20
+ context.release();
21
+ isolate.dispose();
22
+ });
23
+
24
+ test("server.upgrade() returns true and sets pendingUpgrade with connectionId", async () => {
25
+ context.evalSync(`
26
+ serve({
27
+ fetch(request, server) {
28
+ const upgraded = server.upgrade(request, { data: { userId: "123" } });
29
+ return new Response(upgraded ? "upgrading" : "failed", { status: upgraded ? 101 : 400 });
30
+ }
31
+ });
32
+ `);
33
+
34
+ const response = await fetchHandle.dispatchRequest(
35
+ new Request("http://localhost/ws")
36
+ );
37
+ // Note: Status 101 is not valid for native Response, so we expose it via _originalStatus
38
+ // @ts-expect-error - accessing custom property
39
+ assert.strictEqual(response._originalStatus, 101);
40
+
41
+ const upgrade = fetchHandle.getUpgradeRequest();
42
+ assert.strictEqual(upgrade?.requested, true);
43
+ assert.strictEqual(typeof upgrade?.connectionId, "string");
44
+ assert.ok(upgrade?.connectionId);
45
+ });
46
+
47
+ test("server.upgrade() without data option", async () => {
48
+ context.evalSync(`
49
+ serve({
50
+ fetch(request, server) {
51
+ const upgraded = server.upgrade(request);
52
+ return new Response(null, { status: upgraded ? 101 : 400 });
53
+ }
54
+ });
55
+ `);
56
+
57
+ const response = await fetchHandle.dispatchRequest(
58
+ new Request("http://localhost/ws")
59
+ );
60
+ // @ts-expect-error - accessing custom property
61
+ assert.strictEqual(response._originalStatus, 101);
62
+
63
+ const upgrade = fetchHandle.getUpgradeRequest();
64
+ assert.strictEqual(upgrade?.requested, true);
65
+ assert.strictEqual(typeof upgrade?.connectionId, "string");
66
+ });
67
+
68
+ test("dispatchWebSocketOpen calls websocket.open handler", async () => {
69
+ context.evalSync(`
70
+ globalThis.openedConnections = [];
71
+ serve({
72
+ fetch(request, server) {
73
+ server.upgrade(request, { data: { test: true } });
74
+ return new Response(null, { status: 101 });
75
+ },
76
+ websocket: {
77
+ open(ws) {
78
+ globalThis.openedConnections.push(ws.data);
79
+ }
80
+ }
81
+ });
82
+ `);
83
+
84
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
85
+ const upgrade = fetchHandle.getUpgradeRequest();
86
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
87
+
88
+ const result = context.evalSync(`JSON.stringify(globalThis.openedConnections)`);
89
+ const connections = JSON.parse(result as string);
90
+ assert.strictEqual(connections.length, 1);
91
+ assert.deepStrictEqual(connections[0], { test: true });
92
+ });
93
+
94
+ test("dispatchWebSocketMessage delivers messages to handler", async () => {
95
+ context.evalSync(`
96
+ globalThis.receivedMessages = [];
97
+ serve({
98
+ fetch(request, server) {
99
+ server.upgrade(request);
100
+ return new Response(null, { status: 101 });
101
+ },
102
+ websocket: {
103
+ open(ws) {
104
+ // Need open handler for connection to be established
105
+ },
106
+ message(ws, message) {
107
+ globalThis.receivedMessages.push(message);
108
+ }
109
+ }
110
+ });
111
+ `);
112
+
113
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
114
+ const upgrade = fetchHandle.getUpgradeRequest();
115
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
116
+ fetchHandle.dispatchWebSocketMessage(upgrade!.connectionId, "Hello WebSocket!");
117
+
118
+ const result = context.evalSync(`JSON.stringify(globalThis.receivedMessages)`);
119
+ const messages = JSON.parse(result as string);
120
+ assert.deepStrictEqual(messages, ["Hello WebSocket!"]);
121
+ });
122
+
123
+ test("dispatchWebSocketClose notifies handler with code and reason", async () => {
124
+ context.evalSync(`
125
+ globalThis.closeInfo = null;
126
+ serve({
127
+ fetch(request, server) {
128
+ server.upgrade(request);
129
+ return new Response(null, { status: 101 });
130
+ },
131
+ websocket: {
132
+ open(ws) {
133
+ // Need open handler for connection to be established
134
+ },
135
+ close(ws, code, reason) {
136
+ globalThis.closeInfo = { code, reason };
137
+ }
138
+ }
139
+ });
140
+ `);
141
+
142
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
143
+ const upgrade = fetchHandle.getUpgradeRequest();
144
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
145
+ fetchHandle.dispatchWebSocketClose(upgrade!.connectionId, 1000, "Normal closure");
146
+
147
+ const result = context.evalSync(`JSON.stringify(globalThis.closeInfo)`);
148
+ const closeInfo = JSON.parse(result as string);
149
+ assert.deepStrictEqual(closeInfo, { code: 1000, reason: "Normal closure" });
150
+ });
151
+
152
+ test("dispatchWebSocketError delivers error to handler", async () => {
153
+ context.evalSync(`
154
+ globalThis.errorInfo = null;
155
+ serve({
156
+ fetch(request, server) {
157
+ server.upgrade(request);
158
+ return new Response(null, { status: 101 });
159
+ },
160
+ websocket: {
161
+ open(ws) {
162
+ // Need open handler for connection to be established
163
+ },
164
+ error(ws, error) {
165
+ globalThis.errorInfo = { name: error.name, message: error.message };
166
+ }
167
+ }
168
+ });
169
+ `);
170
+
171
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
172
+ const upgrade = fetchHandle.getUpgradeRequest();
173
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
174
+ fetchHandle.dispatchWebSocketError(upgrade!.connectionId, new Error("Connection lost"));
175
+
176
+ const result = context.evalSync(`JSON.stringify(globalThis.errorInfo)`);
177
+ const errorInfo = JSON.parse(result as string);
178
+ assert.deepStrictEqual(errorInfo, { name: "Error", message: "Connection lost" });
179
+ });
180
+
181
+ test("ws.send() triggers onWebSocketCommand callback", async () => {
182
+ context.evalSync(`
183
+ serve({
184
+ fetch(request, server) {
185
+ server.upgrade(request);
186
+ return new Response(null, { status: 101 });
187
+ },
188
+ websocket: {
189
+ open(ws) {
190
+ ws.send("Welcome!");
191
+ }
192
+ }
193
+ });
194
+ `);
195
+
196
+ const commands: WebSocketCommand[] = [];
197
+ fetchHandle.onWebSocketCommand((cmd) => {
198
+ commands.push(cmd);
199
+ });
200
+
201
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
202
+ const upgrade = fetchHandle.getUpgradeRequest();
203
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
204
+
205
+ assert.strictEqual(commands.length, 1);
206
+ assert.strictEqual(commands[0].type, "message");
207
+ assert.strictEqual(commands[0].connectionId, upgrade!.connectionId);
208
+ assert.strictEqual(commands[0].data, "Welcome!");
209
+ });
210
+
211
+ test("ws.close() triggers onWebSocketCommand with close message", async () => {
212
+ context.evalSync(`
213
+ serve({
214
+ fetch(request, server) {
215
+ server.upgrade(request);
216
+ return new Response(null, { status: 101 });
217
+ },
218
+ websocket: {
219
+ open(ws) {
220
+ ws.close(1000, "Goodbye");
221
+ }
222
+ }
223
+ });
224
+ `);
225
+
226
+ const commands: WebSocketCommand[] = [];
227
+ fetchHandle.onWebSocketCommand((cmd) => {
228
+ commands.push(cmd);
229
+ });
230
+
231
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
232
+ const upgrade = fetchHandle.getUpgradeRequest();
233
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
234
+
235
+ assert.strictEqual(commands.length, 1);
236
+ assert.strictEqual(commands[0].type, "close");
237
+ assert.strictEqual(commands[0].connectionId, upgrade!.connectionId);
238
+ assert.strictEqual(commands[0].code, 1000);
239
+ assert.strictEqual(commands[0].reason, "Goodbye");
240
+ });
241
+
242
+ test("WebSocket echo server roundtrip", async () => {
243
+ context.evalSync(`
244
+ serve({
245
+ fetch(request, server) {
246
+ server.upgrade(request);
247
+ return new Response(null, { status: 101 });
248
+ },
249
+ websocket: {
250
+ open(ws) {
251
+ // Need open handler for connection to be established
252
+ },
253
+ message(ws, message) {
254
+ ws.send("Echo: " + message);
255
+ }
256
+ }
257
+ });
258
+ `);
259
+
260
+ const messages: string[] = [];
261
+ fetchHandle.onWebSocketCommand((cmd) => {
262
+ if (cmd.type === "message") {
263
+ messages.push(cmd.data as string);
264
+ }
265
+ });
266
+
267
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
268
+ const upgrade = fetchHandle.getUpgradeRequest();
269
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
270
+ fetchHandle.dispatchWebSocketMessage(upgrade!.connectionId, "Hello");
271
+
272
+ assert.deepStrictEqual(messages, ["Echo: Hello"]);
273
+ });
274
+
275
+ test("message to unknown connection is ignored", async () => {
276
+ context.evalSync(`
277
+ globalThis.receivedMessages = [];
278
+ serve({
279
+ fetch(request, server) {
280
+ server.upgrade(request);
281
+ return new Response(null, { status: 101 });
282
+ },
283
+ websocket: {
284
+ open(ws) {},
285
+ message(ws, message) {
286
+ globalThis.receivedMessages.push(message);
287
+ }
288
+ }
289
+ });
290
+ `);
291
+
292
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
293
+ const upgrade = fetchHandle.getUpgradeRequest();
294
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
295
+
296
+ // Send to unknown connection - should not throw
297
+ fetchHandle.dispatchWebSocketMessage("unknown-conn", "Hello");
298
+
299
+ const result = context.evalSync(`JSON.stringify(globalThis.receivedMessages)`);
300
+ const messages = JSON.parse(result as string);
301
+ assert.deepStrictEqual(messages, []);
302
+ });
303
+
304
+ test("ws.readyState is 1 (OPEN) when connected", async () => {
305
+ context.evalSync(`
306
+ globalThis.readyState = null;
307
+ serve({
308
+ fetch(request, server) {
309
+ server.upgrade(request);
310
+ return new Response(null, { status: 101 });
311
+ },
312
+ websocket: {
313
+ open(ws) {
314
+ globalThis.readyState = ws.readyState;
315
+ }
316
+ }
317
+ });
318
+ `);
319
+
320
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
321
+ const upgrade = fetchHandle.getUpgradeRequest();
322
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
323
+
324
+ const result = context.evalSync(`globalThis.readyState`);
325
+ assert.strictEqual(result, 1);
326
+ });
327
+
328
+ test("multiple connections are tracked independently", async () => {
329
+ context.evalSync(`
330
+ globalThis.messages = {};
331
+ serve({
332
+ fetch(request, server) {
333
+ const match = request.url.match(/id=([^&]+)/);
334
+ const id = match ? match[1] : "unknown";
335
+ server.upgrade(request, { data: { id } });
336
+ return new Response(null, { status: 101 });
337
+ },
338
+ websocket: {
339
+ open(ws) {
340
+ // Need open handler for connection to be established
341
+ },
342
+ message(ws, message) {
343
+ if (!globalThis.messages[ws.data.id]) {
344
+ globalThis.messages[ws.data.id] = [];
345
+ }
346
+ globalThis.messages[ws.data.id].push(message);
347
+ }
348
+ }
349
+ });
350
+ `);
351
+
352
+ // Open two connections
353
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?id=conn1"));
354
+ const upgrade1 = fetchHandle.getUpgradeRequest();
355
+ fetchHandle.dispatchWebSocketOpen(upgrade1!.connectionId);
356
+
357
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?id=conn2"));
358
+ const upgrade2 = fetchHandle.getUpgradeRequest();
359
+ fetchHandle.dispatchWebSocketOpen(upgrade2!.connectionId);
360
+
361
+ // Send messages to each
362
+ fetchHandle.dispatchWebSocketMessage(upgrade1!.connectionId, "Message to conn1");
363
+ fetchHandle.dispatchWebSocketMessage(upgrade2!.connectionId, "Message to conn2");
364
+
365
+ const result = context.evalSync(`JSON.stringify(globalThis.messages)`);
366
+ const messages = JSON.parse(result as string);
367
+ assert.deepStrictEqual(messages, {
368
+ conn1: ["Message to conn1"],
369
+ conn2: ["Message to conn2"],
370
+ });
371
+ });
372
+
373
+ test("connection is removed after close", async () => {
374
+ context.evalSync(`
375
+ globalThis.messageCount = 0;
376
+ serve({
377
+ fetch(request, server) {
378
+ server.upgrade(request);
379
+ return new Response(null, { status: 101 });
380
+ },
381
+ websocket: {
382
+ open(ws) {
383
+ // Need open handler for connection to be established
384
+ },
385
+ message(ws, message) {
386
+ globalThis.messageCount++;
387
+ }
388
+ }
389
+ });
390
+ `);
391
+
392
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
393
+ const upgrade = fetchHandle.getUpgradeRequest();
394
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
395
+ fetchHandle.dispatchWebSocketMessage(upgrade!.connectionId, "Before close");
396
+ fetchHandle.dispatchWebSocketClose(upgrade!.connectionId, 1000, "Normal");
397
+
398
+ // Message after close should be ignored
399
+ fetchHandle.dispatchWebSocketMessage(upgrade!.connectionId, "After close");
400
+
401
+ const result = context.evalSync(`globalThis.messageCount`);
402
+ assert.strictEqual(result, 1);
403
+ });
404
+
405
+ test("hasActiveConnections returns correct state", async () => {
406
+ assert.strictEqual(fetchHandle.hasActiveConnections(), false);
407
+
408
+ context.evalSync(`
409
+ serve({
410
+ fetch(request, server) {
411
+ server.upgrade(request);
412
+ return new Response(null, { status: 101 });
413
+ },
414
+ websocket: {
415
+ open(ws) {}
416
+ }
417
+ });
418
+ `);
419
+
420
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
421
+ const upgrade = fetchHandle.getUpgradeRequest();
422
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
423
+
424
+ assert.strictEqual(fetchHandle.hasActiveConnections(), true);
425
+
426
+ fetchHandle.dispatchWebSocketClose(upgrade!.connectionId, 1000, "Normal");
427
+
428
+ assert.strictEqual(fetchHandle.hasActiveConnections(), false);
429
+ });
430
+
431
+ describe("close handler data access", () => {
432
+ test("close handler can access ws.data set during upgrade", async () => {
433
+ context.evalSync(`
434
+ globalThis.closeHandlerData = null;
435
+ serve({
436
+ fetch(request, server) {
437
+ server.upgrade(request, { data: { userId: "user123", sessionId: "sess456" } });
438
+ return new Response(null, { status: 101 });
439
+ },
440
+ websocket: {
441
+ open(ws) {
442
+ // Connection established
443
+ },
444
+ close(ws, code, reason) {
445
+ globalThis.closeHandlerData = ws.data;
446
+ }
447
+ }
448
+ });
449
+ `);
450
+
451
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
452
+ const upgrade = fetchHandle.getUpgradeRequest();
453
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
454
+ fetchHandle.dispatchWebSocketClose(upgrade!.connectionId, 1000, "Normal");
455
+
456
+ const result = context.evalSync(`JSON.stringify(globalThis.closeHandlerData)`);
457
+ const data = JSON.parse(result as string);
458
+ assert.deepStrictEqual(data, { userId: "user123", sessionId: "sess456" });
459
+ });
460
+
461
+ test("close handler can access ws.data modified during message handling", async () => {
462
+ context.evalSync(`
463
+ globalThis.closeHandlerData = null;
464
+ serve({
465
+ fetch(request, server) {
466
+ server.upgrade(request, { data: {} });
467
+ return new Response(null, { status: 101 });
468
+ },
469
+ websocket: {
470
+ open(ws) {
471
+ // Initial data is empty
472
+ },
473
+ message(ws, message) {
474
+ if (message === "join:Alice") {
475
+ ws.data.username = "Alice";
476
+ ws.data.joinedAt = Date.now();
477
+ }
478
+ },
479
+ close(ws, code, reason) {
480
+ globalThis.closeHandlerData = ws.data;
481
+ }
482
+ }
483
+ });
484
+ `);
485
+
486
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
487
+ const upgrade = fetchHandle.getUpgradeRequest();
488
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
489
+ fetchHandle.dispatchWebSocketMessage(upgrade!.connectionId, "join:Alice");
490
+ fetchHandle.dispatchWebSocketClose(upgrade!.connectionId, 1000, "Normal");
491
+
492
+ const result = context.evalSync(`JSON.stringify(globalThis.closeHandlerData)`);
493
+ const data = JSON.parse(result as string);
494
+ assert.strictEqual(data.username, "Alice");
495
+ assert.strictEqual(typeof data.joinedAt, "number");
496
+ });
497
+
498
+ test("close handler requires open handler for connection tracking", async () => {
499
+ context.evalSync(`
500
+ globalThis.closeCalled = false;
501
+ serve({
502
+ fetch(request, server) {
503
+ server.upgrade(request, { data: { test: true } });
504
+ return new Response(null, { status: 101 });
505
+ },
506
+ websocket: {
507
+ open(ws) {
508
+ // Must define open handler for connection tracking
509
+ },
510
+ close(ws, code, reason) {
511
+ globalThis.closeCalled = true;
512
+ }
513
+ }
514
+ });
515
+ `);
516
+
517
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws"));
518
+ const upgrade = fetchHandle.getUpgradeRequest();
519
+ fetchHandle.dispatchWebSocketOpen(upgrade!.connectionId);
520
+ fetchHandle.dispatchWebSocketClose(upgrade!.connectionId, 1000, "Normal");
521
+
522
+ const result = context.evalSync(`globalThis.closeCalled`);
523
+ assert.strictEqual(result, true);
524
+ });
525
+
526
+ test("multiple connections close handlers receive correct data", async () => {
527
+ context.evalSync(`
528
+ globalThis.closedUsers = [];
529
+ serve({
530
+ fetch(request, server) {
531
+ const match = request.url.match(/user=([^&]+)/);
532
+ const username = match ? match[1] : "unknown";
533
+ server.upgrade(request, { data: { username } });
534
+ return new Response(null, { status: 101 });
535
+ },
536
+ websocket: {
537
+ open(ws) {
538
+ // Connection established
539
+ },
540
+ close(ws, code, reason) {
541
+ globalThis.closedUsers.push(ws.data.username);
542
+ }
543
+ }
544
+ });
545
+ `);
546
+
547
+ // Open three connections
548
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?user=Alice"));
549
+ const upgrade1 = fetchHandle.getUpgradeRequest();
550
+ fetchHandle.dispatchWebSocketOpen(upgrade1!.connectionId);
551
+
552
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?user=Bob"));
553
+ const upgrade2 = fetchHandle.getUpgradeRequest();
554
+ fetchHandle.dispatchWebSocketOpen(upgrade2!.connectionId);
555
+
556
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?user=Charlie"));
557
+ const upgrade3 = fetchHandle.getUpgradeRequest();
558
+ fetchHandle.dispatchWebSocketOpen(upgrade3!.connectionId);
559
+
560
+ // Close them in different order
561
+ fetchHandle.dispatchWebSocketClose(upgrade2!.connectionId, 1000, "Normal");
562
+ fetchHandle.dispatchWebSocketClose(upgrade1!.connectionId, 1000, "Normal");
563
+ fetchHandle.dispatchWebSocketClose(upgrade3!.connectionId, 1000, "Normal");
564
+
565
+ const result = context.evalSync(`JSON.stringify(globalThis.closedUsers)`);
566
+ const closedUsers = JSON.parse(result as string);
567
+ assert.deepStrictEqual(closedUsers, ["Bob", "Alice", "Charlie"]);
568
+ });
569
+
570
+ test("close handler can broadcast to other connections", async () => {
571
+ context.evalSync(`
572
+ globalThis.connections = new Map();
573
+ globalThis.broadcastMessages = [];
574
+ serve({
575
+ fetch(request, server) {
576
+ const match = request.url.match(/user=([^&]+)/);
577
+ const username = match ? match[1] : "unknown";
578
+ server.upgrade(request, { data: { username } });
579
+ return new Response(null, { status: 101 });
580
+ },
581
+ websocket: {
582
+ open(ws) {
583
+ globalThis.connections.set(ws.data.username, ws);
584
+ },
585
+ close(ws, code, reason) {
586
+ const username = ws.data.username;
587
+ globalThis.connections.delete(username);
588
+ for (const [name, otherWs] of globalThis.connections) {
589
+ otherWs.send("userLeft:" + username);
590
+ globalThis.broadcastMessages.push({ to: name, about: username });
591
+ }
592
+ }
593
+ }
594
+ });
595
+ `);
596
+
597
+ const commands: WebSocketCommand[] = [];
598
+ fetchHandle.onWebSocketCommand((cmd) => {
599
+ commands.push(cmd);
600
+ });
601
+
602
+ // Open three connections
603
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?user=Alice"));
604
+ const upgrade1 = fetchHandle.getUpgradeRequest();
605
+ fetchHandle.dispatchWebSocketOpen(upgrade1!.connectionId);
606
+
607
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?user=Bob"));
608
+ const upgrade2 = fetchHandle.getUpgradeRequest();
609
+ fetchHandle.dispatchWebSocketOpen(upgrade2!.connectionId);
610
+
611
+ await fetchHandle.dispatchRequest(new Request("http://localhost/ws?user=Charlie"));
612
+ const upgrade3 = fetchHandle.getUpgradeRequest();
613
+ fetchHandle.dispatchWebSocketOpen(upgrade3!.connectionId);
614
+
615
+ // Bob disconnects - should broadcast to Alice and Charlie
616
+ fetchHandle.dispatchWebSocketClose(upgrade2!.connectionId, 1000, "Normal");
617
+
618
+ const result = context.evalSync(`JSON.stringify(globalThis.broadcastMessages)`);
619
+ const broadcasts = JSON.parse(result as string);
620
+
621
+ // Should have 2 broadcasts (to Alice and Charlie)
622
+ assert.strictEqual(broadcasts.length, 2);
623
+ assert.deepStrictEqual(broadcasts.map((b: { about: string }) => b.about), ["Bob", "Bob"]);
624
+ assert.deepStrictEqual(broadcasts.map((b: { to: string }) => b.to).sort(), ["Alice", "Charlie"]);
625
+ });
626
+ });
627
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src"
5
+ },
6
+ "include": ["src/**/*"],
7
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
8
+ }
package/README.md DELETED
@@ -1,45 +0,0 @@
1
- # @ricsam/isolate-fetch
2
-
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@ricsam/isolate-fetch`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**