@ricsam/isolate-fetch 0.1.1 → 0.1.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/README.md +93 -0
- package/dist/cjs/index.cjs +1800 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/stream-state.cjs +230 -0
- package/dist/cjs/stream-state.cjs.map +10 -0
- package/{src/index.ts → dist/mjs/index.mjs} +357 -923
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/mjs/stream-state.mjs +199 -0
- package/dist/mjs/stream-state.mjs.map +10 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/isolate.d.ts +267 -0
- package/dist/types/stream-state.d.ts +61 -0
- package/package.json +41 -13
- package/CHANGELOG.md +0 -9
- package/src/debug-delayed.test.ts +0 -89
- package/src/debug-streaming.test.ts +0 -81
- package/src/download-streaming-simple.test.ts +0 -167
- package/src/download-streaming.test.ts +0 -286
- package/src/form-data.test.ts +0 -824
- package/src/formdata.test.ts +0 -212
- package/src/headers.test.ts +0 -582
- package/src/host-backed-stream.test.ts +0 -363
- package/src/index.test.ts +0 -274
- package/src/integration.test.ts +0 -665
- package/src/request.test.ts +0 -482
- package/src/response.test.ts +0 -520
- package/src/serve.test.ts +0 -425
- package/src/stream-state.test.ts +0 -338
- package/src/stream-state.ts +0 -337
- package/src/upload-streaming.test.ts +0 -373
- package/src/websocket.test.ts +0 -627
- package/tsconfig.json +0 -8
|
@@ -1,121 +1,22 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/fetch/src/index.ts
|
|
1
3
|
import ivm from "isolated-vm";
|
|
2
4
|
import { setupCore, clearAllInstanceState } from "@ricsam/isolate-core";
|
|
3
5
|
import {
|
|
4
6
|
getStreamRegistryForContext,
|
|
5
|
-
startNativeStreamReader
|
|
6
|
-
} from "./stream-state.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export interface FetchOptions {
|
|
12
|
-
/** Handler for fetch requests from the isolate */
|
|
13
|
-
onFetch?: (request: Request) => Promise<Response>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Serve Types
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
export interface UpgradeRequest {
|
|
21
|
-
requested: true;
|
|
22
|
-
connectionId: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface WebSocketCommand {
|
|
26
|
-
type: "message" | "close";
|
|
27
|
-
connectionId: string;
|
|
28
|
-
data?: string | ArrayBuffer;
|
|
29
|
-
code?: number;
|
|
30
|
-
reason?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface ServeState {
|
|
34
|
-
pendingUpgrade: UpgradeRequest | null;
|
|
35
|
-
activeConnections: Map<string, { connectionId: string }>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface DispatchRequestOptions {
|
|
39
|
-
/** Tick function to pump isolate timers - required for streaming responses */
|
|
40
|
-
tick?: () => Promise<void>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface FetchHandle {
|
|
44
|
-
dispose(): void;
|
|
45
|
-
/** Dispatch an HTTP request to the isolate's serve() handler */
|
|
46
|
-
dispatchRequest(request: Request, options?: DispatchRequestOptions): Promise<Response>;
|
|
47
|
-
/** Check if isolate requested WebSocket upgrade */
|
|
48
|
-
getUpgradeRequest(): UpgradeRequest | null;
|
|
49
|
-
/** Dispatch WebSocket open event to isolate */
|
|
50
|
-
dispatchWebSocketOpen(connectionId: string): void;
|
|
51
|
-
/** Dispatch WebSocket message event to isolate */
|
|
52
|
-
dispatchWebSocketMessage(connectionId: string, message: string | ArrayBuffer): void;
|
|
53
|
-
/** Dispatch WebSocket close event to isolate */
|
|
54
|
-
dispatchWebSocketClose(connectionId: string, code: number, reason: string): void;
|
|
55
|
-
/** Dispatch WebSocket error event to isolate */
|
|
56
|
-
dispatchWebSocketError(connectionId: string, error: Error): void;
|
|
57
|
-
/** Register callback for WebSocket commands from isolate */
|
|
58
|
-
onWebSocketCommand(callback: (cmd: WebSocketCommand) => void): () => void;
|
|
59
|
-
/** Check if serve() has been called */
|
|
60
|
-
hasServeHandler(): boolean;
|
|
61
|
-
/** Check if there are active WebSocket connections */
|
|
62
|
-
hasActiveConnections(): boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ============================================================================
|
|
66
|
-
// Instance State Management
|
|
67
|
-
// ============================================================================
|
|
68
|
-
|
|
69
|
-
const instanceStateMap = new WeakMap<ivm.Context, Map<number, unknown>>();
|
|
70
|
-
let nextInstanceId = 1;
|
|
71
|
-
|
|
72
|
-
function getInstanceStateMapForContext(
|
|
73
|
-
context: ivm.Context
|
|
74
|
-
): Map<number, unknown> {
|
|
7
|
+
startNativeStreamReader
|
|
8
|
+
} from "./stream-state.mjs";
|
|
9
|
+
var instanceStateMap = new WeakMap;
|
|
10
|
+
var nextInstanceId = 1;
|
|
11
|
+
function getInstanceStateMapForContext(context) {
|
|
75
12
|
let map = instanceStateMap.get(context);
|
|
76
13
|
if (!map) {
|
|
77
|
-
map = new Map
|
|
14
|
+
map = new Map;
|
|
78
15
|
instanceStateMap.set(context, map);
|
|
79
16
|
}
|
|
80
17
|
return map;
|
|
81
18
|
}
|
|
82
|
-
|
|
83
|
-
// ============================================================================
|
|
84
|
-
// State Types
|
|
85
|
-
// ============================================================================
|
|
86
|
-
|
|
87
|
-
interface ResponseState {
|
|
88
|
-
status: number;
|
|
89
|
-
statusText: string;
|
|
90
|
-
headers: [string, string][];
|
|
91
|
-
body: Uint8Array | null;
|
|
92
|
-
bodyUsed: boolean;
|
|
93
|
-
type: string;
|
|
94
|
-
url: string;
|
|
95
|
-
redirected: boolean;
|
|
96
|
-
streamId: number | null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
interface RequestState {
|
|
100
|
-
method: string;
|
|
101
|
-
url: string;
|
|
102
|
-
headers: [string, string][];
|
|
103
|
-
body: Uint8Array | null;
|
|
104
|
-
bodyUsed: boolean;
|
|
105
|
-
streamId: number | null;
|
|
106
|
-
mode: string;
|
|
107
|
-
credentials: string;
|
|
108
|
-
cache: string;
|
|
109
|
-
redirect: string;
|
|
110
|
-
referrer: string;
|
|
111
|
-
integrity: string;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ============================================================================
|
|
115
|
-
// Headers Implementation (Pure JS)
|
|
116
|
-
// ============================================================================
|
|
117
|
-
|
|
118
|
-
const headersCode = `
|
|
19
|
+
var headersCode = `
|
|
119
20
|
(function() {
|
|
120
21
|
class Headers {
|
|
121
22
|
#headers = new Map(); // lowercase key -> [originalCase, values[]]
|
|
@@ -202,12 +103,7 @@ const headersCode = `
|
|
|
202
103
|
globalThis.Headers = Headers;
|
|
203
104
|
})();
|
|
204
105
|
`;
|
|
205
|
-
|
|
206
|
-
// ============================================================================
|
|
207
|
-
// FormData Implementation (Pure JS)
|
|
208
|
-
// ============================================================================
|
|
209
|
-
|
|
210
|
-
const formDataCode = `
|
|
106
|
+
var formDataCode = `
|
|
211
107
|
(function() {
|
|
212
108
|
class FormData {
|
|
213
109
|
#entries = []; // Array of [name, value]
|
|
@@ -285,12 +181,7 @@ const formDataCode = `
|
|
|
285
181
|
globalThis.FormData = FormData;
|
|
286
182
|
})();
|
|
287
183
|
`;
|
|
288
|
-
|
|
289
|
-
// ============================================================================
|
|
290
|
-
// Multipart FormData Parsing/Serialization (Pure JS)
|
|
291
|
-
// ============================================================================
|
|
292
|
-
|
|
293
|
-
const multipartCode = `
|
|
184
|
+
var multipartCode = `
|
|
294
185
|
(function() {
|
|
295
186
|
// Find byte sequence in Uint8Array
|
|
296
187
|
function findSequence(haystack, needle, start = 0) {
|
|
@@ -449,60 +340,25 @@ const multipartCode = `
|
|
|
449
340
|
};
|
|
450
341
|
})();
|
|
451
342
|
`;
|
|
452
|
-
|
|
453
|
-
// ============================================================================
|
|
454
|
-
// Stream Callbacks (Host State)
|
|
455
|
-
// ============================================================================
|
|
456
|
-
|
|
457
|
-
function setupStreamCallbacks(
|
|
458
|
-
context: ivm.Context,
|
|
459
|
-
streamRegistry: StreamStateRegistry
|
|
460
|
-
): void {
|
|
343
|
+
function setupStreamCallbacks(context, streamRegistry) {
|
|
461
344
|
const global = context.global;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
new
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
global.setSync(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
// Close stream (sync)
|
|
481
|
-
global.setSync(
|
|
482
|
-
"__Stream_close",
|
|
483
|
-
new ivm.Callback((streamId: number) => {
|
|
484
|
-
streamRegistry.close(streamId);
|
|
485
|
-
})
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
// Error stream (sync)
|
|
489
|
-
global.setSync(
|
|
490
|
-
"__Stream_error",
|
|
491
|
-
new ivm.Callback((streamId: number, message: string) => {
|
|
492
|
-
streamRegistry.error(streamId, new Error(message));
|
|
493
|
-
})
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
// Check backpressure (sync)
|
|
497
|
-
global.setSync(
|
|
498
|
-
"__Stream_isQueueFull",
|
|
499
|
-
new ivm.Callback((streamId: number) => {
|
|
500
|
-
return streamRegistry.isQueueFull(streamId);
|
|
501
|
-
})
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
// Pull chunk (async with applySyncPromise)
|
|
505
|
-
const pullRef = new ivm.Reference(async (streamId: number) => {
|
|
345
|
+
global.setSync("__Stream_create", new ivm.Callback(() => {
|
|
346
|
+
return streamRegistry.create();
|
|
347
|
+
}));
|
|
348
|
+
global.setSync("__Stream_push", new ivm.Callback((streamId, chunkArray) => {
|
|
349
|
+
const chunk = new Uint8Array(chunkArray);
|
|
350
|
+
return streamRegistry.push(streamId, chunk);
|
|
351
|
+
}));
|
|
352
|
+
global.setSync("__Stream_close", new ivm.Callback((streamId) => {
|
|
353
|
+
streamRegistry.close(streamId);
|
|
354
|
+
}));
|
|
355
|
+
global.setSync("__Stream_error", new ivm.Callback((streamId, message) => {
|
|
356
|
+
streamRegistry.error(streamId, new Error(message));
|
|
357
|
+
}));
|
|
358
|
+
global.setSync("__Stream_isQueueFull", new ivm.Callback((streamId) => {
|
|
359
|
+
return streamRegistry.isQueueFull(streamId);
|
|
360
|
+
}));
|
|
361
|
+
const pullRef = new ivm.Reference(async (streamId) => {
|
|
506
362
|
const result = await streamRegistry.pull(streamId);
|
|
507
363
|
if (result.done) {
|
|
508
364
|
return JSON.stringify({ done: true });
|
|
@@ -511,12 +367,7 @@ function setupStreamCallbacks(
|
|
|
511
367
|
});
|
|
512
368
|
global.setSync("__Stream_pull_ref", pullRef);
|
|
513
369
|
}
|
|
514
|
-
|
|
515
|
-
// ============================================================================
|
|
516
|
-
// Host-Backed ReadableStream (Isolate Code)
|
|
517
|
-
// ============================================================================
|
|
518
|
-
|
|
519
|
-
const hostBackedStreamCode = `
|
|
370
|
+
var hostBackedStreamCode = `
|
|
520
371
|
(function() {
|
|
521
372
|
const _streamIds = new WeakMap();
|
|
522
373
|
|
|
@@ -581,233 +432,132 @@ const hostBackedStreamCode = `
|
|
|
581
432
|
globalThis.HostBackedReadableStream = HostBackedReadableStream;
|
|
582
433
|
})();
|
|
583
434
|
`;
|
|
584
|
-
|
|
585
|
-
// ============================================================================
|
|
586
|
-
// Response Implementation (Host State + Isolate Class)
|
|
587
|
-
// ============================================================================
|
|
588
|
-
|
|
589
|
-
function setupResponse(
|
|
590
|
-
context: ivm.Context,
|
|
591
|
-
stateMap: Map<number, unknown>
|
|
592
|
-
): void {
|
|
435
|
+
function setupResponse(context, stateMap) {
|
|
593
436
|
const global = context.global;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
)
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
global.setSync(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
)
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
global.setSync(
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
return state?.url ?? "";
|
|
719
|
-
})
|
|
720
|
-
);
|
|
721
|
-
|
|
722
|
-
global.setSync(
|
|
723
|
-
"__Response_get_redirected",
|
|
724
|
-
new ivm.Callback((instanceId: number) => {
|
|
725
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
726
|
-
return state?.redirected ?? false;
|
|
727
|
-
})
|
|
728
|
-
);
|
|
729
|
-
|
|
730
|
-
global.setSync(
|
|
731
|
-
"__Response_get_type",
|
|
732
|
-
new ivm.Callback((instanceId: number) => {
|
|
733
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
734
|
-
return state?.type ?? "default";
|
|
735
|
-
})
|
|
736
|
-
);
|
|
737
|
-
|
|
738
|
-
global.setSync(
|
|
739
|
-
"__Response_setType",
|
|
740
|
-
new ivm.Callback((instanceId: number, type: string) => {
|
|
741
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
742
|
-
if (state) {
|
|
743
|
-
state.type = type;
|
|
744
|
-
}
|
|
745
|
-
})
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
global.setSync(
|
|
749
|
-
"__Response_markBodyUsed",
|
|
750
|
-
new ivm.Callback((instanceId: number) => {
|
|
751
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
752
|
-
if (state) {
|
|
753
|
-
if (state.bodyUsed) {
|
|
754
|
-
throw new Error("[TypeError]Body has already been consumed");
|
|
755
|
-
}
|
|
756
|
-
state.bodyUsed = true;
|
|
757
|
-
}
|
|
758
|
-
})
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
global.setSync(
|
|
762
|
-
"__Response_text",
|
|
763
|
-
new ivm.Callback((instanceId: number) => {
|
|
764
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
765
|
-
if (!state || !state.body) return "";
|
|
766
|
-
return new TextDecoder().decode(state.body);
|
|
767
|
-
})
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
global.setSync(
|
|
771
|
-
"__Response_arrayBuffer",
|
|
772
|
-
new ivm.Callback((instanceId: number) => {
|
|
773
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
774
|
-
if (!state || !state.body) {
|
|
775
|
-
return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
|
|
776
|
-
}
|
|
777
|
-
return new ivm.ExternalCopy(state.body.buffer.slice(
|
|
778
|
-
state.body.byteOffset,
|
|
779
|
-
state.body.byteOffset + state.body.byteLength
|
|
780
|
-
)).copyInto();
|
|
781
|
-
})
|
|
782
|
-
);
|
|
783
|
-
|
|
784
|
-
global.setSync(
|
|
785
|
-
"__Response_clone",
|
|
786
|
-
new ivm.Callback((instanceId: number) => {
|
|
787
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
788
|
-
if (!state) {
|
|
789
|
-
throw new Error("[TypeError]Cannot clone invalid Response");
|
|
790
|
-
}
|
|
791
|
-
const newId = nextInstanceId++;
|
|
792
|
-
const newState: ResponseState = {
|
|
793
|
-
...state,
|
|
794
|
-
body: state.body ? new Uint8Array(state.body) : null,
|
|
795
|
-
bodyUsed: false,
|
|
796
|
-
};
|
|
797
|
-
stateMap.set(newId, newState);
|
|
798
|
-
return newId;
|
|
799
|
-
})
|
|
800
|
-
);
|
|
801
|
-
|
|
802
|
-
global.setSync(
|
|
803
|
-
"__Response_getStreamId",
|
|
804
|
-
new ivm.Callback((instanceId: number) => {
|
|
805
|
-
const state = stateMap.get(instanceId) as ResponseState | undefined;
|
|
806
|
-
return state?.streamId ?? null;
|
|
807
|
-
})
|
|
808
|
-
);
|
|
809
|
-
|
|
810
|
-
// Inject Response class
|
|
437
|
+
global.setSync("__Response_construct", new ivm.Callback((bodyBytes, status, statusText, headers) => {
|
|
438
|
+
const instanceId = nextInstanceId++;
|
|
439
|
+
const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
|
|
440
|
+
const state = {
|
|
441
|
+
status,
|
|
442
|
+
statusText,
|
|
443
|
+
headers,
|
|
444
|
+
body,
|
|
445
|
+
bodyUsed: false,
|
|
446
|
+
type: "default",
|
|
447
|
+
url: "",
|
|
448
|
+
redirected: false,
|
|
449
|
+
streamId: null
|
|
450
|
+
};
|
|
451
|
+
stateMap.set(instanceId, state);
|
|
452
|
+
return instanceId;
|
|
453
|
+
}));
|
|
454
|
+
global.setSync("__Response_constructStreaming", new ivm.Callback((streamId, status, statusText, headers) => {
|
|
455
|
+
const instanceId = nextInstanceId++;
|
|
456
|
+
const state = {
|
|
457
|
+
status,
|
|
458
|
+
statusText,
|
|
459
|
+
headers,
|
|
460
|
+
body: null,
|
|
461
|
+
bodyUsed: false,
|
|
462
|
+
type: "default",
|
|
463
|
+
url: "",
|
|
464
|
+
redirected: false,
|
|
465
|
+
streamId
|
|
466
|
+
};
|
|
467
|
+
stateMap.set(instanceId, state);
|
|
468
|
+
return instanceId;
|
|
469
|
+
}));
|
|
470
|
+
global.setSync("__Response_constructFromFetch", new ivm.Callback((bodyBytes, status, statusText, headers, url, redirected) => {
|
|
471
|
+
const instanceId = nextInstanceId++;
|
|
472
|
+
const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
|
|
473
|
+
const state = {
|
|
474
|
+
status,
|
|
475
|
+
statusText,
|
|
476
|
+
headers,
|
|
477
|
+
body,
|
|
478
|
+
bodyUsed: false,
|
|
479
|
+
type: "default",
|
|
480
|
+
url,
|
|
481
|
+
redirected,
|
|
482
|
+
streamId: null
|
|
483
|
+
};
|
|
484
|
+
stateMap.set(instanceId, state);
|
|
485
|
+
return instanceId;
|
|
486
|
+
}));
|
|
487
|
+
global.setSync("__Response_get_status", new ivm.Callback((instanceId) => {
|
|
488
|
+
const state = stateMap.get(instanceId);
|
|
489
|
+
return state?.status ?? 200;
|
|
490
|
+
}));
|
|
491
|
+
global.setSync("__Response_get_statusText", new ivm.Callback((instanceId) => {
|
|
492
|
+
const state = stateMap.get(instanceId);
|
|
493
|
+
return state?.statusText ?? "";
|
|
494
|
+
}));
|
|
495
|
+
global.setSync("__Response_get_headers", new ivm.Callback((instanceId) => {
|
|
496
|
+
const state = stateMap.get(instanceId);
|
|
497
|
+
return state?.headers ?? [];
|
|
498
|
+
}));
|
|
499
|
+
global.setSync("__Response_get_bodyUsed", new ivm.Callback((instanceId) => {
|
|
500
|
+
const state = stateMap.get(instanceId);
|
|
501
|
+
return state?.bodyUsed ?? false;
|
|
502
|
+
}));
|
|
503
|
+
global.setSync("__Response_get_url", new ivm.Callback((instanceId) => {
|
|
504
|
+
const state = stateMap.get(instanceId);
|
|
505
|
+
return state?.url ?? "";
|
|
506
|
+
}));
|
|
507
|
+
global.setSync("__Response_get_redirected", new ivm.Callback((instanceId) => {
|
|
508
|
+
const state = stateMap.get(instanceId);
|
|
509
|
+
return state?.redirected ?? false;
|
|
510
|
+
}));
|
|
511
|
+
global.setSync("__Response_get_type", new ivm.Callback((instanceId) => {
|
|
512
|
+
const state = stateMap.get(instanceId);
|
|
513
|
+
return state?.type ?? "default";
|
|
514
|
+
}));
|
|
515
|
+
global.setSync("__Response_setType", new ivm.Callback((instanceId, type) => {
|
|
516
|
+
const state = stateMap.get(instanceId);
|
|
517
|
+
if (state) {
|
|
518
|
+
state.type = type;
|
|
519
|
+
}
|
|
520
|
+
}));
|
|
521
|
+
global.setSync("__Response_markBodyUsed", new ivm.Callback((instanceId) => {
|
|
522
|
+
const state = stateMap.get(instanceId);
|
|
523
|
+
if (state) {
|
|
524
|
+
if (state.bodyUsed) {
|
|
525
|
+
throw new Error("[TypeError]Body has already been consumed");
|
|
526
|
+
}
|
|
527
|
+
state.bodyUsed = true;
|
|
528
|
+
}
|
|
529
|
+
}));
|
|
530
|
+
global.setSync("__Response_text", new ivm.Callback((instanceId) => {
|
|
531
|
+
const state = stateMap.get(instanceId);
|
|
532
|
+
if (!state || !state.body)
|
|
533
|
+
return "";
|
|
534
|
+
return new TextDecoder().decode(state.body);
|
|
535
|
+
}));
|
|
536
|
+
global.setSync("__Response_arrayBuffer", new ivm.Callback((instanceId) => {
|
|
537
|
+
const state = stateMap.get(instanceId);
|
|
538
|
+
if (!state || !state.body) {
|
|
539
|
+
return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
|
|
540
|
+
}
|
|
541
|
+
return new ivm.ExternalCopy(state.body.buffer.slice(state.body.byteOffset, state.body.byteOffset + state.body.byteLength)).copyInto();
|
|
542
|
+
}));
|
|
543
|
+
global.setSync("__Response_clone", new ivm.Callback((instanceId) => {
|
|
544
|
+
const state = stateMap.get(instanceId);
|
|
545
|
+
if (!state) {
|
|
546
|
+
throw new Error("[TypeError]Cannot clone invalid Response");
|
|
547
|
+
}
|
|
548
|
+
const newId = nextInstanceId++;
|
|
549
|
+
const newState = {
|
|
550
|
+
...state,
|
|
551
|
+
body: state.body ? new Uint8Array(state.body) : null,
|
|
552
|
+
bodyUsed: false
|
|
553
|
+
};
|
|
554
|
+
stateMap.set(newId, newState);
|
|
555
|
+
return newId;
|
|
556
|
+
}));
|
|
557
|
+
global.setSync("__Response_getStreamId", new ivm.Callback((instanceId) => {
|
|
558
|
+
const state = stateMap.get(instanceId);
|
|
559
|
+
return state?.streamId ?? null;
|
|
560
|
+
}));
|
|
811
561
|
const responseCode = `
|
|
812
562
|
(function() {
|
|
813
563
|
const _responseInstanceIds = new WeakMap();
|
|
@@ -1129,210 +879,116 @@ function setupResponse(
|
|
|
1129
879
|
globalThis.Response = Response;
|
|
1130
880
|
})();
|
|
1131
881
|
`;
|
|
1132
|
-
|
|
1133
882
|
context.evalSync(responseCode);
|
|
1134
883
|
}
|
|
1135
|
-
|
|
1136
|
-
// ============================================================================
|
|
1137
|
-
// Request Implementation (Host State + Isolate Class)
|
|
1138
|
-
// ============================================================================
|
|
1139
|
-
|
|
1140
|
-
function setupRequest(
|
|
1141
|
-
context: ivm.Context,
|
|
1142
|
-
stateMap: Map<number, unknown>
|
|
1143
|
-
): void {
|
|
884
|
+
function setupRequest(context, stateMap) {
|
|
1144
885
|
const global = context.global;
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
)
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
new
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
);
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1252
|
-
return state?.referrer ?? "about:client";
|
|
1253
|
-
})
|
|
1254
|
-
);
|
|
1255
|
-
|
|
1256
|
-
global.setSync(
|
|
1257
|
-
"__Request_get_integrity",
|
|
1258
|
-
new ivm.Callback((instanceId: number) => {
|
|
1259
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1260
|
-
return state?.integrity ?? "";
|
|
1261
|
-
})
|
|
1262
|
-
);
|
|
1263
|
-
|
|
1264
|
-
global.setSync(
|
|
1265
|
-
"__Request_markBodyUsed",
|
|
1266
|
-
new ivm.Callback((instanceId: number) => {
|
|
1267
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1268
|
-
if (state) {
|
|
1269
|
-
if (state.bodyUsed) {
|
|
1270
|
-
throw new Error("[TypeError]Body has already been consumed");
|
|
1271
|
-
}
|
|
1272
|
-
state.bodyUsed = true;
|
|
1273
|
-
}
|
|
1274
|
-
})
|
|
1275
|
-
);
|
|
1276
|
-
|
|
1277
|
-
global.setSync(
|
|
1278
|
-
"__Request_text",
|
|
1279
|
-
new ivm.Callback((instanceId: number) => {
|
|
1280
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1281
|
-
if (!state || !state.body) return "";
|
|
1282
|
-
return new TextDecoder().decode(state.body);
|
|
1283
|
-
})
|
|
1284
|
-
);
|
|
1285
|
-
|
|
1286
|
-
global.setSync(
|
|
1287
|
-
"__Request_arrayBuffer",
|
|
1288
|
-
new ivm.Callback((instanceId: number) => {
|
|
1289
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1290
|
-
if (!state || !state.body) {
|
|
1291
|
-
return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
|
|
1292
|
-
}
|
|
1293
|
-
return new ivm.ExternalCopy(state.body.buffer.slice(
|
|
1294
|
-
state.body.byteOffset,
|
|
1295
|
-
state.body.byteOffset + state.body.byteLength
|
|
1296
|
-
)).copyInto();
|
|
1297
|
-
})
|
|
1298
|
-
);
|
|
1299
|
-
|
|
1300
|
-
global.setSync(
|
|
1301
|
-
"__Request_getBodyBytes",
|
|
1302
|
-
new ivm.Callback((instanceId: number) => {
|
|
1303
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1304
|
-
if (!state || !state.body) return null;
|
|
1305
|
-
return Array.from(state.body);
|
|
1306
|
-
})
|
|
1307
|
-
);
|
|
1308
|
-
|
|
1309
|
-
global.setSync(
|
|
1310
|
-
"__Request_clone",
|
|
1311
|
-
new ivm.Callback((instanceId: number) => {
|
|
1312
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1313
|
-
if (!state) {
|
|
1314
|
-
throw new Error("[TypeError]Cannot clone invalid Request");
|
|
1315
|
-
}
|
|
1316
|
-
const newId = nextInstanceId++;
|
|
1317
|
-
const newState: RequestState = {
|
|
1318
|
-
...state,
|
|
1319
|
-
body: state.body ? new Uint8Array(state.body) : null,
|
|
1320
|
-
bodyUsed: false,
|
|
1321
|
-
};
|
|
1322
|
-
stateMap.set(newId, newState);
|
|
1323
|
-
return newId;
|
|
1324
|
-
})
|
|
1325
|
-
);
|
|
1326
|
-
|
|
1327
|
-
global.setSync(
|
|
1328
|
-
"__Request_getStreamId",
|
|
1329
|
-
new ivm.Callback((instanceId: number) => {
|
|
1330
|
-
const state = stateMap.get(instanceId) as RequestState | undefined;
|
|
1331
|
-
return state?.streamId ?? null;
|
|
1332
|
-
})
|
|
1333
|
-
);
|
|
1334
|
-
|
|
1335
|
-
// Inject Request class
|
|
886
|
+
global.setSync("__Request_construct", new ivm.Callback((url, method, headers, bodyBytes, mode, credentials, cache, redirect, referrer, integrity) => {
|
|
887
|
+
const instanceId = nextInstanceId++;
|
|
888
|
+
const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
|
|
889
|
+
const state = {
|
|
890
|
+
url,
|
|
891
|
+
method,
|
|
892
|
+
headers,
|
|
893
|
+
body,
|
|
894
|
+
bodyUsed: false,
|
|
895
|
+
streamId: null,
|
|
896
|
+
mode,
|
|
897
|
+
credentials,
|
|
898
|
+
cache,
|
|
899
|
+
redirect,
|
|
900
|
+
referrer,
|
|
901
|
+
integrity
|
|
902
|
+
};
|
|
903
|
+
stateMap.set(instanceId, state);
|
|
904
|
+
return instanceId;
|
|
905
|
+
}));
|
|
906
|
+
global.setSync("__Request_get_method", new ivm.Callback((instanceId) => {
|
|
907
|
+
const state = stateMap.get(instanceId);
|
|
908
|
+
return state?.method ?? "GET";
|
|
909
|
+
}));
|
|
910
|
+
global.setSync("__Request_get_url", new ivm.Callback((instanceId) => {
|
|
911
|
+
const state = stateMap.get(instanceId);
|
|
912
|
+
return state?.url ?? "";
|
|
913
|
+
}));
|
|
914
|
+
global.setSync("__Request_get_headers", new ivm.Callback((instanceId) => {
|
|
915
|
+
const state = stateMap.get(instanceId);
|
|
916
|
+
return state?.headers ?? [];
|
|
917
|
+
}));
|
|
918
|
+
global.setSync("__Request_get_bodyUsed", new ivm.Callback((instanceId) => {
|
|
919
|
+
const state = stateMap.get(instanceId);
|
|
920
|
+
return state?.bodyUsed ?? false;
|
|
921
|
+
}));
|
|
922
|
+
global.setSync("__Request_get_mode", new ivm.Callback((instanceId) => {
|
|
923
|
+
const state = stateMap.get(instanceId);
|
|
924
|
+
return state?.mode ?? "cors";
|
|
925
|
+
}));
|
|
926
|
+
global.setSync("__Request_get_credentials", new ivm.Callback((instanceId) => {
|
|
927
|
+
const state = stateMap.get(instanceId);
|
|
928
|
+
return state?.credentials ?? "same-origin";
|
|
929
|
+
}));
|
|
930
|
+
global.setSync("__Request_get_cache", new ivm.Callback((instanceId) => {
|
|
931
|
+
const state = stateMap.get(instanceId);
|
|
932
|
+
return state?.cache ?? "default";
|
|
933
|
+
}));
|
|
934
|
+
global.setSync("__Request_get_redirect", new ivm.Callback((instanceId) => {
|
|
935
|
+
const state = stateMap.get(instanceId);
|
|
936
|
+
return state?.redirect ?? "follow";
|
|
937
|
+
}));
|
|
938
|
+
global.setSync("__Request_get_referrer", new ivm.Callback((instanceId) => {
|
|
939
|
+
const state = stateMap.get(instanceId);
|
|
940
|
+
return state?.referrer ?? "about:client";
|
|
941
|
+
}));
|
|
942
|
+
global.setSync("__Request_get_integrity", new ivm.Callback((instanceId) => {
|
|
943
|
+
const state = stateMap.get(instanceId);
|
|
944
|
+
return state?.integrity ?? "";
|
|
945
|
+
}));
|
|
946
|
+
global.setSync("__Request_markBodyUsed", new ivm.Callback((instanceId) => {
|
|
947
|
+
const state = stateMap.get(instanceId);
|
|
948
|
+
if (state) {
|
|
949
|
+
if (state.bodyUsed) {
|
|
950
|
+
throw new Error("[TypeError]Body has already been consumed");
|
|
951
|
+
}
|
|
952
|
+
state.bodyUsed = true;
|
|
953
|
+
}
|
|
954
|
+
}));
|
|
955
|
+
global.setSync("__Request_text", new ivm.Callback((instanceId) => {
|
|
956
|
+
const state = stateMap.get(instanceId);
|
|
957
|
+
if (!state || !state.body)
|
|
958
|
+
return "";
|
|
959
|
+
return new TextDecoder().decode(state.body);
|
|
960
|
+
}));
|
|
961
|
+
global.setSync("__Request_arrayBuffer", new ivm.Callback((instanceId) => {
|
|
962
|
+
const state = stateMap.get(instanceId);
|
|
963
|
+
if (!state || !state.body) {
|
|
964
|
+
return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
|
|
965
|
+
}
|
|
966
|
+
return new ivm.ExternalCopy(state.body.buffer.slice(state.body.byteOffset, state.body.byteOffset + state.body.byteLength)).copyInto();
|
|
967
|
+
}));
|
|
968
|
+
global.setSync("__Request_getBodyBytes", new ivm.Callback((instanceId) => {
|
|
969
|
+
const state = stateMap.get(instanceId);
|
|
970
|
+
if (!state || !state.body)
|
|
971
|
+
return null;
|
|
972
|
+
return Array.from(state.body);
|
|
973
|
+
}));
|
|
974
|
+
global.setSync("__Request_clone", new ivm.Callback((instanceId) => {
|
|
975
|
+
const state = stateMap.get(instanceId);
|
|
976
|
+
if (!state) {
|
|
977
|
+
throw new Error("[TypeError]Cannot clone invalid Request");
|
|
978
|
+
}
|
|
979
|
+
const newId = nextInstanceId++;
|
|
980
|
+
const newState = {
|
|
981
|
+
...state,
|
|
982
|
+
body: state.body ? new Uint8Array(state.body) : null,
|
|
983
|
+
bodyUsed: false
|
|
984
|
+
};
|
|
985
|
+
stateMap.set(newId, newState);
|
|
986
|
+
return newId;
|
|
987
|
+
}));
|
|
988
|
+
global.setSync("__Request_getStreamId", new ivm.Callback((instanceId) => {
|
|
989
|
+
const state = stateMap.get(instanceId);
|
|
990
|
+
return state?.streamId ?? null;
|
|
991
|
+
}));
|
|
1336
992
|
const requestCode = `
|
|
1337
993
|
(function() {
|
|
1338
994
|
function __decodeError(err) {
|
|
@@ -1661,79 +1317,42 @@ function setupRequest(
|
|
|
1661
1317
|
globalThis.Request = Request;
|
|
1662
1318
|
})();
|
|
1663
1319
|
`;
|
|
1664
|
-
|
|
1665
1320
|
context.evalSync(requestCode);
|
|
1666
1321
|
}
|
|
1667
|
-
|
|
1668
|
-
// ============================================================================
|
|
1669
|
-
// fetch Implementation
|
|
1670
|
-
// ============================================================================
|
|
1671
|
-
|
|
1672
|
-
function setupFetchFunction(
|
|
1673
|
-
context: ivm.Context,
|
|
1674
|
-
stateMap: Map<number, unknown>,
|
|
1675
|
-
options?: FetchOptions
|
|
1676
|
-
): void {
|
|
1322
|
+
function setupFetchFunction(context, stateMap, options) {
|
|
1677
1323
|
const global = context.global;
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
// Read response body
|
|
1711
|
-
const responseBody = await nativeResponse.arrayBuffer();
|
|
1712
|
-
const responseBodyArray = Array.from(new Uint8Array(responseBody));
|
|
1713
|
-
|
|
1714
|
-
// Store the response in the state map and return just the ID + metadata
|
|
1715
|
-
const instanceId = nextInstanceId++;
|
|
1716
|
-
const state: ResponseState = {
|
|
1717
|
-
status: nativeResponse.status,
|
|
1718
|
-
statusText: nativeResponse.statusText,
|
|
1719
|
-
headers: Array.from(nativeResponse.headers.entries()),
|
|
1720
|
-
body: new Uint8Array(responseBodyArray),
|
|
1721
|
-
bodyUsed: false,
|
|
1722
|
-
type: "default",
|
|
1723
|
-
url: nativeResponse.url,
|
|
1724
|
-
redirected: nativeResponse.redirected,
|
|
1725
|
-
streamId: null,
|
|
1726
|
-
};
|
|
1727
|
-
stateMap.set(instanceId, state);
|
|
1728
|
-
|
|
1729
|
-
// Return only the instance ID - avoid complex object transfer
|
|
1730
|
-
return instanceId;
|
|
1731
|
-
}
|
|
1732
|
-
);
|
|
1733
|
-
|
|
1324
|
+
const fetchRef = new ivm.Reference(async (url, method, headersJson, bodyJson, signalAborted) => {
|
|
1325
|
+
if (signalAborted) {
|
|
1326
|
+
throw new Error("[AbortError]The operation was aborted.");
|
|
1327
|
+
}
|
|
1328
|
+
const headers = JSON.parse(headersJson);
|
|
1329
|
+
const bodyBytes = bodyJson ? JSON.parse(bodyJson) : null;
|
|
1330
|
+
const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
|
|
1331
|
+
const nativeRequest = new Request(url, {
|
|
1332
|
+
method,
|
|
1333
|
+
headers,
|
|
1334
|
+
body
|
|
1335
|
+
});
|
|
1336
|
+
const onFetch = options?.onFetch ?? fetch;
|
|
1337
|
+
const nativeResponse = await onFetch(nativeRequest);
|
|
1338
|
+
const responseBody = await nativeResponse.arrayBuffer();
|
|
1339
|
+
const responseBodyArray = Array.from(new Uint8Array(responseBody));
|
|
1340
|
+
const instanceId = nextInstanceId++;
|
|
1341
|
+
const state = {
|
|
1342
|
+
status: nativeResponse.status,
|
|
1343
|
+
statusText: nativeResponse.statusText,
|
|
1344
|
+
headers: Array.from(nativeResponse.headers.entries()),
|
|
1345
|
+
body: new Uint8Array(responseBodyArray),
|
|
1346
|
+
bodyUsed: false,
|
|
1347
|
+
type: "default",
|
|
1348
|
+
url: nativeResponse.url,
|
|
1349
|
+
redirected: nativeResponse.redirected,
|
|
1350
|
+
streamId: null
|
|
1351
|
+
};
|
|
1352
|
+
stateMap.set(instanceId, state);
|
|
1353
|
+
return instanceId;
|
|
1354
|
+
});
|
|
1734
1355
|
global.setSync("__fetch_ref", fetchRef);
|
|
1735
|
-
|
|
1736
|
-
// Inject fetch function
|
|
1737
1356
|
const fetchCode = `
|
|
1738
1357
|
(function() {
|
|
1739
1358
|
function __decodeError(err) {
|
|
@@ -1780,35 +1399,17 @@ function setupFetchFunction(
|
|
|
1780
1399
|
};
|
|
1781
1400
|
})();
|
|
1782
1401
|
`;
|
|
1783
|
-
|
|
1784
1402
|
context.evalSync(fetchCode);
|
|
1785
1403
|
}
|
|
1786
|
-
|
|
1787
|
-
// ============================================================================
|
|
1788
|
-
// Server Implementation (for serve())
|
|
1789
|
-
// ============================================================================
|
|
1790
|
-
|
|
1791
|
-
function setupServer(
|
|
1792
|
-
context: ivm.Context,
|
|
1793
|
-
serveState: ServeState
|
|
1794
|
-
): void {
|
|
1404
|
+
function setupServer(context, serveState) {
|
|
1795
1405
|
const global = context.global;
|
|
1796
|
-
|
|
1797
|
-
// Setup upgrade registry in isolate (data stays in isolate, never marshalled to host)
|
|
1798
1406
|
context.evalSync(`
|
|
1799
1407
|
globalThis.__upgradeRegistry__ = new Map();
|
|
1800
1408
|
globalThis.__upgradeIdCounter__ = 0;
|
|
1801
1409
|
`);
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
"__setPendingUpgrade__",
|
|
1806
|
-
new ivm.Callback((connectionId: string) => {
|
|
1807
|
-
serveState.pendingUpgrade = { requested: true, connectionId };
|
|
1808
|
-
})
|
|
1809
|
-
);
|
|
1810
|
-
|
|
1811
|
-
// Pure JS Server class with upgrade method
|
|
1410
|
+
global.setSync("__setPendingUpgrade__", new ivm.Callback((connectionId) => {
|
|
1411
|
+
serveState.pendingUpgrade = { requested: true, connectionId };
|
|
1412
|
+
}));
|
|
1812
1413
|
context.evalSync(`
|
|
1813
1414
|
(function() {
|
|
1814
1415
|
class Server {
|
|
@@ -1824,36 +1425,18 @@ function setupServer(
|
|
|
1824
1425
|
})();
|
|
1825
1426
|
`);
|
|
1826
1427
|
}
|
|
1827
|
-
|
|
1828
|
-
// ============================================================================
|
|
1829
|
-
// ServerWebSocket Implementation (for serve())
|
|
1830
|
-
// ============================================================================
|
|
1831
|
-
|
|
1832
|
-
function setupServerWebSocket(
|
|
1833
|
-
context: ivm.Context,
|
|
1834
|
-
wsCommandCallbacks: Set<(cmd: WebSocketCommand) => void>
|
|
1835
|
-
): void {
|
|
1428
|
+
function setupServerWebSocket(context, wsCommandCallbacks) {
|
|
1836
1429
|
const global = context.global;
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
// Host callback for ws.close()
|
|
1848
|
-
global.setSync(
|
|
1849
|
-
"__ServerWebSocket_close",
|
|
1850
|
-
new ivm.Callback((connectionId: string, code?: number, reason?: string) => {
|
|
1851
|
-
const cmd: WebSocketCommand = { type: "close", connectionId, code, reason };
|
|
1852
|
-
for (const cb of wsCommandCallbacks) cb(cmd);
|
|
1853
|
-
})
|
|
1854
|
-
);
|
|
1855
|
-
|
|
1856
|
-
// Pure JS ServerWebSocket class
|
|
1430
|
+
global.setSync("__ServerWebSocket_send", new ivm.Callback((connectionId, data) => {
|
|
1431
|
+
const cmd = { type: "message", connectionId, data };
|
|
1432
|
+
for (const cb of wsCommandCallbacks)
|
|
1433
|
+
cb(cmd);
|
|
1434
|
+
}));
|
|
1435
|
+
global.setSync("__ServerWebSocket_close", new ivm.Callback((connectionId, code, reason) => {
|
|
1436
|
+
const cmd = { type: "close", connectionId, code, reason };
|
|
1437
|
+
for (const cb of wsCommandCallbacks)
|
|
1438
|
+
cb(cmd);
|
|
1439
|
+
}));
|
|
1857
1440
|
context.evalSync(`
|
|
1858
1441
|
(function() {
|
|
1859
1442
|
const _wsInstanceData = new WeakMap();
|
|
@@ -1901,13 +1484,7 @@ function setupServerWebSocket(
|
|
|
1901
1484
|
})();
|
|
1902
1485
|
`);
|
|
1903
1486
|
}
|
|
1904
|
-
|
|
1905
|
-
// ============================================================================
|
|
1906
|
-
// serve() Function Implementation
|
|
1907
|
-
// ============================================================================
|
|
1908
|
-
|
|
1909
|
-
function setupServe(context: ivm.Context): void {
|
|
1910
|
-
// Pure JS serve() that stores options on __serveOptions__ global
|
|
1487
|
+
function setupServe(context) {
|
|
1911
1488
|
context.evalSync(`
|
|
1912
1489
|
(function() {
|
|
1913
1490
|
globalThis.__serveOptions__ = null;
|
|
@@ -1920,136 +1497,58 @@ function setupServe(context: ivm.Context): void {
|
|
|
1920
1497
|
})();
|
|
1921
1498
|
`);
|
|
1922
1499
|
}
|
|
1923
|
-
|
|
1924
|
-
// ============================================================================
|
|
1925
|
-
// Main Setup Function
|
|
1926
|
-
// ============================================================================
|
|
1927
|
-
|
|
1928
|
-
/**
|
|
1929
|
-
* Setup Fetch API in an isolated-vm context
|
|
1930
|
-
*
|
|
1931
|
-
* Injects fetch, Request, Response, Headers, FormData
|
|
1932
|
-
* Also sets up core APIs (Blob, File, AbortController, etc.) if not already present
|
|
1933
|
-
*
|
|
1934
|
-
* @example
|
|
1935
|
-
* const handle = await setupFetch(context, {
|
|
1936
|
-
* onFetch: async (request) => {
|
|
1937
|
-
* // Proxy fetch requests to the host
|
|
1938
|
-
* return fetch(request);
|
|
1939
|
-
* }
|
|
1940
|
-
* });
|
|
1941
|
-
*
|
|
1942
|
-
* await context.eval(`
|
|
1943
|
-
* const response = await fetch("https://example.com");
|
|
1944
|
-
* const text = await response.text();
|
|
1945
|
-
* `);
|
|
1946
|
-
*/
|
|
1947
|
-
export async function setupFetch(
|
|
1948
|
-
context: ivm.Context,
|
|
1949
|
-
options?: FetchOptions
|
|
1950
|
-
): Promise<FetchHandle> {
|
|
1951
|
-
// Setup core APIs first (Blob, File, AbortController, Streams, etc.)
|
|
1500
|
+
async function setupFetch(context, options) {
|
|
1952
1501
|
await setupCore(context);
|
|
1953
|
-
|
|
1954
1502
|
const stateMap = getInstanceStateMapForContext(context);
|
|
1955
1503
|
const streamRegistry = getStreamRegistryForContext(context);
|
|
1956
|
-
|
|
1957
|
-
// Inject Headers (pure JS)
|
|
1958
1504
|
context.evalSync(headersCode);
|
|
1959
|
-
|
|
1960
|
-
// Inject FormData (pure JS)
|
|
1961
1505
|
context.evalSync(formDataCode);
|
|
1962
|
-
|
|
1963
|
-
// Inject multipart parsing/serialization (pure JS)
|
|
1964
1506
|
context.evalSync(multipartCode);
|
|
1965
|
-
|
|
1966
|
-
// Setup stream callbacks and inject HostBackedReadableStream
|
|
1967
1507
|
setupStreamCallbacks(context, streamRegistry);
|
|
1968
1508
|
context.evalSync(hostBackedStreamCode);
|
|
1969
|
-
|
|
1970
|
-
// Setup Response (host state + isolate class)
|
|
1971
1509
|
setupResponse(context, stateMap);
|
|
1972
|
-
|
|
1973
|
-
// Setup Request (host state + isolate class)
|
|
1974
1510
|
setupRequest(context, stateMap);
|
|
1975
|
-
|
|
1976
|
-
// Setup fetch function
|
|
1977
1511
|
setupFetchFunction(context, stateMap, options);
|
|
1978
|
-
|
|
1979
|
-
// Setup serve state
|
|
1980
|
-
const serveState: ServeState = {
|
|
1512
|
+
const serveState = {
|
|
1981
1513
|
pendingUpgrade: null,
|
|
1982
|
-
activeConnections: new Map
|
|
1514
|
+
activeConnections: new Map
|
|
1983
1515
|
};
|
|
1984
|
-
|
|
1985
|
-
// Setup WebSocket command callbacks
|
|
1986
|
-
const wsCommandCallbacks = new Set<(cmd: WebSocketCommand) => void>();
|
|
1987
|
-
|
|
1988
|
-
// Setup Server class
|
|
1516
|
+
const wsCommandCallbacks = new Set;
|
|
1989
1517
|
setupServer(context, serveState);
|
|
1990
|
-
|
|
1991
|
-
// Setup ServerWebSocket class
|
|
1992
1518
|
setupServerWebSocket(context, wsCommandCallbacks);
|
|
1993
|
-
|
|
1994
|
-
// Setup serve function
|
|
1995
1519
|
setupServe(context);
|
|
1996
|
-
|
|
1997
1520
|
return {
|
|
1998
1521
|
dispose() {
|
|
1999
|
-
// Clear state for this context
|
|
2000
1522
|
stateMap.clear();
|
|
2001
|
-
// Clear upgrade registry
|
|
2002
1523
|
context.evalSync(`globalThis.__upgradeRegistry__.clear()`);
|
|
2003
|
-
// Clear serve state
|
|
2004
1524
|
serveState.activeConnections.clear();
|
|
2005
1525
|
serveState.pendingUpgrade = null;
|
|
2006
1526
|
},
|
|
2007
|
-
|
|
2008
|
-
async dispatchRequest(
|
|
2009
|
-
request: Request,
|
|
2010
|
-
dispatchOptions?: DispatchRequestOptions
|
|
2011
|
-
): Promise<Response> {
|
|
1527
|
+
async dispatchRequest(request, dispatchOptions) {
|
|
2012
1528
|
const tick = dispatchOptions?.tick;
|
|
2013
|
-
|
|
2014
|
-
// Clean up previous pending upgrade if not consumed
|
|
2015
1529
|
if (serveState.pendingUpgrade) {
|
|
2016
1530
|
const oldConnectionId = serveState.pendingUpgrade.connectionId;
|
|
2017
1531
|
context.evalSync(`globalThis.__upgradeRegistry__.delete("${oldConnectionId}")`);
|
|
2018
1532
|
serveState.pendingUpgrade = null;
|
|
2019
1533
|
}
|
|
2020
|
-
|
|
2021
|
-
// Check if serve handler exists
|
|
2022
1534
|
const hasHandler = context.evalSync(`!!globalThis.__serveOptions__?.fetch`);
|
|
2023
1535
|
if (!hasHandler) {
|
|
2024
1536
|
throw new Error("No serve() handler registered");
|
|
2025
1537
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
let requestStreamId: number | null = null;
|
|
2029
|
-
let streamCleanup: (() => Promise<void>) | null = null;
|
|
2030
|
-
|
|
1538
|
+
let requestStreamId = null;
|
|
1539
|
+
let streamCleanup = null;
|
|
2031
1540
|
if (request.body) {
|
|
2032
|
-
// Create a stream in the registry for the request body
|
|
2033
1541
|
requestStreamId = streamRegistry.create();
|
|
2034
|
-
|
|
2035
|
-
// Start background reader that pushes from native stream to host queue
|
|
2036
|
-
streamCleanup = startNativeStreamReader(
|
|
2037
|
-
request.body,
|
|
2038
|
-
requestStreamId,
|
|
2039
|
-
streamRegistry
|
|
2040
|
-
);
|
|
1542
|
+
streamCleanup = startNativeStreamReader(request.body, requestStreamId, streamRegistry);
|
|
2041
1543
|
}
|
|
2042
|
-
|
|
2043
1544
|
try {
|
|
2044
1545
|
const headersArray = Array.from(request.headers.entries());
|
|
2045
|
-
|
|
2046
|
-
// Create Request instance in isolate
|
|
2047
1546
|
const requestInstanceId = nextInstanceId++;
|
|
2048
|
-
const requestState
|
|
1547
|
+
const requestState = {
|
|
2049
1548
|
url: request.url,
|
|
2050
1549
|
method: request.method,
|
|
2051
1550
|
headers: headersArray,
|
|
2052
|
-
body: null,
|
|
1551
|
+
body: null,
|
|
2053
1552
|
bodyUsed: false,
|
|
2054
1553
|
streamId: requestStreamId,
|
|
2055
1554
|
mode: request.mode,
|
|
@@ -2057,12 +1556,9 @@ export async function setupFetch(
|
|
|
2057
1556
|
cache: request.cache,
|
|
2058
1557
|
redirect: request.redirect,
|
|
2059
1558
|
referrer: request.referrer,
|
|
2060
|
-
integrity: request.integrity
|
|
1559
|
+
integrity: request.integrity
|
|
2061
1560
|
};
|
|
2062
1561
|
stateMap.set(requestInstanceId, requestState);
|
|
2063
|
-
|
|
2064
|
-
// Call the fetch handler and get response
|
|
2065
|
-
// We use eval with promise: true to handle async handlers
|
|
2066
1562
|
const responseInstanceId = await context.eval(`
|
|
2067
1563
|
(async function() {
|
|
2068
1564
|
const request = Request._fromInstanceId(${requestInstanceId});
|
|
@@ -2071,46 +1567,32 @@ export async function setupFetch(
|
|
|
2071
1567
|
return response._getInstanceId();
|
|
2072
1568
|
})()
|
|
2073
1569
|
`, { promise: true });
|
|
2074
|
-
|
|
2075
|
-
// Get ResponseState from the instance
|
|
2076
|
-
const responseState = stateMap.get(responseInstanceId) as ResponseState | undefined;
|
|
1570
|
+
const responseState = stateMap.get(responseInstanceId);
|
|
2077
1571
|
if (!responseState) {
|
|
2078
1572
|
throw new Error("Response state not found");
|
|
2079
1573
|
}
|
|
2080
|
-
|
|
2081
|
-
// Check if response has streaming body
|
|
2082
1574
|
if (responseState.streamId !== null) {
|
|
2083
1575
|
const responseStreamId = responseState.streamId;
|
|
2084
1576
|
let streamDone = false;
|
|
2085
|
-
|
|
2086
|
-
// Create native stream that pumps isolate timers while waiting
|
|
2087
|
-
const pumpedStream = new ReadableStream<Uint8Array>({
|
|
1577
|
+
const pumpedStream = new ReadableStream({
|
|
2088
1578
|
async pull(controller) {
|
|
2089
|
-
if (streamDone)
|
|
2090
|
-
|
|
2091
|
-
// Pump isolate timers to allow stream pump to progress
|
|
1579
|
+
if (streamDone)
|
|
1580
|
+
return;
|
|
2092
1581
|
while (!streamDone) {
|
|
2093
1582
|
if (tick) {
|
|
2094
1583
|
await tick();
|
|
2095
1584
|
}
|
|
2096
|
-
|
|
2097
|
-
// Check if data is available
|
|
2098
1585
|
const state = streamRegistry.get(responseStreamId);
|
|
2099
1586
|
if (!state) {
|
|
2100
1587
|
controller.close();
|
|
2101
1588
|
streamDone = true;
|
|
2102
1589
|
return;
|
|
2103
1590
|
}
|
|
2104
|
-
|
|
2105
|
-
// If queue has data or stream is done, break and pull
|
|
2106
1591
|
if (state.queue.length > 0 || state.closed || state.errored) {
|
|
2107
1592
|
break;
|
|
2108
1593
|
}
|
|
2109
|
-
|
|
2110
|
-
// Small delay to avoid busy-waiting
|
|
2111
1594
|
await new Promise((r) => setTimeout(r, 1));
|
|
2112
1595
|
}
|
|
2113
|
-
|
|
2114
1596
|
try {
|
|
2115
1597
|
const result = await streamRegistry.pull(responseStreamId);
|
|
2116
1598
|
if (result.done) {
|
|
@@ -2128,79 +1610,50 @@ export async function setupFetch(
|
|
|
2128
1610
|
},
|
|
2129
1611
|
cancel() {
|
|
2130
1612
|
streamDone = true;
|
|
2131
|
-
streamRegistry.error(
|
|
2132
|
-
responseStreamId,
|
|
2133
|
-
new Error("Stream cancelled")
|
|
2134
|
-
);
|
|
1613
|
+
streamRegistry.error(responseStreamId, new Error("Stream cancelled"));
|
|
2135
1614
|
streamRegistry.delete(responseStreamId);
|
|
2136
|
-
}
|
|
1615
|
+
}
|
|
2137
1616
|
});
|
|
2138
|
-
|
|
2139
|
-
const
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2142
|
-
const response = new Response(pumpedStream, {
|
|
2143
|
-
status,
|
|
1617
|
+
const responseHeaders2 = new Headers(responseState.headers);
|
|
1618
|
+
const status2 = responseState.status === 101 ? 200 : responseState.status;
|
|
1619
|
+
const response2 = new Response(pumpedStream, {
|
|
1620
|
+
status: status2,
|
|
2144
1621
|
statusText: responseState.statusText,
|
|
2145
|
-
headers:
|
|
1622
|
+
headers: responseHeaders2
|
|
2146
1623
|
});
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
response._originalStatus = responseState.status;
|
|
2150
|
-
|
|
2151
|
-
return response;
|
|
1624
|
+
response2._originalStatus = responseState.status;
|
|
1625
|
+
return response2;
|
|
2152
1626
|
}
|
|
2153
|
-
|
|
2154
|
-
// Convert to native Response (non-streaming)
|
|
2155
1627
|
const responseHeaders = new Headers(responseState.headers);
|
|
2156
1628
|
const responseBody = responseState.body;
|
|
2157
|
-
|
|
2158
|
-
// Note: Status 101 (Switching Protocols) is not valid for Response constructor
|
|
2159
|
-
// We use 200 as the status but preserve the actual status in a custom header
|
|
2160
|
-
// The caller should check getUpgradeRequest() for WebSocket upgrades
|
|
2161
1629
|
const status = responseState.status === 101 ? 200 : responseState.status;
|
|
2162
|
-
const response = new Response(responseBody
|
|
1630
|
+
const response = new Response(responseBody, {
|
|
2163
1631
|
status,
|
|
2164
1632
|
statusText: responseState.statusText,
|
|
2165
|
-
headers: responseHeaders
|
|
1633
|
+
headers: responseHeaders
|
|
2166
1634
|
});
|
|
2167
|
-
|
|
2168
|
-
// Expose the original status via a property for callers to check
|
|
2169
|
-
// @ts-expect-error - adding custom property
|
|
2170
1635
|
response._originalStatus = responseState.status;
|
|
2171
|
-
|
|
2172
1636
|
return response;
|
|
2173
1637
|
} finally {
|
|
2174
|
-
// Cleanup: cancel stream reader if still running
|
|
2175
1638
|
if (streamCleanup) {
|
|
2176
1639
|
await streamCleanup();
|
|
2177
1640
|
}
|
|
2178
|
-
// Delete stream from registry
|
|
2179
1641
|
if (requestStreamId !== null) {
|
|
2180
1642
|
streamRegistry.delete(requestStreamId);
|
|
2181
1643
|
}
|
|
2182
1644
|
}
|
|
2183
1645
|
},
|
|
2184
|
-
|
|
2185
|
-
getUpgradeRequest(): UpgradeRequest | null {
|
|
1646
|
+
getUpgradeRequest() {
|
|
2186
1647
|
const result = serveState.pendingUpgrade;
|
|
2187
|
-
// Don't clear yet - it will be cleared on next dispatchRequest or consumed by dispatchWebSocketOpen
|
|
2188
1648
|
return result;
|
|
2189
1649
|
},
|
|
2190
|
-
|
|
2191
|
-
dispatchWebSocketOpen(connectionId: string): void {
|
|
2192
|
-
// Check if websocket.open handler exists - required for connection tracking
|
|
1650
|
+
dispatchWebSocketOpen(connectionId) {
|
|
2193
1651
|
const hasOpenHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.open`);
|
|
2194
1652
|
if (!hasOpenHandler) {
|
|
2195
|
-
// Delete from registry and return - connection NOT tracked
|
|
2196
1653
|
context.evalSync(`globalThis.__upgradeRegistry__.delete("${connectionId}")`);
|
|
2197
1654
|
return;
|
|
2198
1655
|
}
|
|
2199
|
-
|
|
2200
|
-
// Store connection (data stays in isolate registry)
|
|
2201
1656
|
serveState.activeConnections.set(connectionId, { connectionId });
|
|
2202
|
-
|
|
2203
|
-
// Create ServerWebSocket and call open handler
|
|
2204
1657
|
context.evalSync(`
|
|
2205
1658
|
(function() {
|
|
2206
1659
|
const ws = new __ServerWebSocket__("${connectionId}");
|
|
@@ -2208,35 +1661,26 @@ export async function setupFetch(
|
|
|
2208
1661
|
__serveOptions__.websocket.open(ws);
|
|
2209
1662
|
})()
|
|
2210
1663
|
`);
|
|
2211
|
-
|
|
2212
|
-
// Clear pending upgrade after successful open
|
|
2213
1664
|
if (serveState.pendingUpgrade?.connectionId === connectionId) {
|
|
2214
1665
|
serveState.pendingUpgrade = null;
|
|
2215
1666
|
}
|
|
2216
1667
|
},
|
|
2217
|
-
|
|
2218
|
-
dispatchWebSocketMessage(connectionId: string, message: string | ArrayBuffer): void {
|
|
2219
|
-
// Check if connection is tracked
|
|
1668
|
+
dispatchWebSocketMessage(connectionId, message) {
|
|
2220
1669
|
if (!serveState.activeConnections.has(connectionId)) {
|
|
2221
|
-
return;
|
|
1670
|
+
return;
|
|
2222
1671
|
}
|
|
2223
|
-
|
|
2224
|
-
// Check if message handler exists
|
|
2225
1672
|
const hasMessageHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.message`);
|
|
2226
1673
|
if (!hasMessageHandler) {
|
|
2227
1674
|
return;
|
|
2228
1675
|
}
|
|
2229
|
-
|
|
2230
|
-
// Marshal message and call handler
|
|
2231
1676
|
if (typeof message === "string") {
|
|
2232
1677
|
context.evalSync(`
|
|
2233
1678
|
(function() {
|
|
2234
1679
|
const ws = globalThis.__activeWs_${connectionId}__;
|
|
2235
|
-
if (ws) __serveOptions__.websocket.message(ws, "${message.replace(/\\/g, "\\\\").replace(/"/g,
|
|
1680
|
+
if (ws) __serveOptions__.websocket.message(ws, "${message.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n")}");
|
|
2236
1681
|
})()
|
|
2237
1682
|
`);
|
|
2238
1683
|
} else {
|
|
2239
|
-
// ArrayBuffer - convert to base64 or pass as array
|
|
2240
1684
|
const bytes = Array.from(new Uint8Array(message));
|
|
2241
1685
|
context.evalSync(`
|
|
2242
1686
|
(function() {
|
|
@@ -2249,25 +1693,19 @@ export async function setupFetch(
|
|
|
2249
1693
|
`);
|
|
2250
1694
|
}
|
|
2251
1695
|
},
|
|
2252
|
-
|
|
2253
|
-
dispatchWebSocketClose(connectionId: string, code: number, reason: string): void {
|
|
2254
|
-
// Check if connection is tracked
|
|
1696
|
+
dispatchWebSocketClose(connectionId, code, reason) {
|
|
2255
1697
|
if (!serveState.activeConnections.has(connectionId)) {
|
|
2256
1698
|
return;
|
|
2257
1699
|
}
|
|
2258
|
-
|
|
2259
|
-
// Update readyState to CLOSED
|
|
2260
1700
|
context.evalSync(`
|
|
2261
1701
|
(function() {
|
|
2262
1702
|
const ws = globalThis.__activeWs_${connectionId}__;
|
|
2263
1703
|
if (ws) ws._setReadyState(3);
|
|
2264
1704
|
})()
|
|
2265
1705
|
`);
|
|
2266
|
-
|
|
2267
|
-
// Check if close handler exists
|
|
2268
1706
|
const hasCloseHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.close`);
|
|
2269
1707
|
if (hasCloseHandler) {
|
|
2270
|
-
const safeReason = reason.replace(/\\/g, "\\\\").replace(/"/g,
|
|
1708
|
+
const safeReason = reason.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
2271
1709
|
context.evalSync(`
|
|
2272
1710
|
(function() {
|
|
2273
1711
|
const ws = globalThis.__activeWs_${connectionId}__;
|
|
@@ -2275,29 +1713,22 @@ export async function setupFetch(
|
|
|
2275
1713
|
})()
|
|
2276
1714
|
`);
|
|
2277
1715
|
}
|
|
2278
|
-
|
|
2279
|
-
// Cleanup
|
|
2280
1716
|
context.evalSync(`
|
|
2281
1717
|
delete globalThis.__activeWs_${connectionId}__;
|
|
2282
1718
|
globalThis.__upgradeRegistry__.delete("${connectionId}");
|
|
2283
1719
|
`);
|
|
2284
1720
|
serveState.activeConnections.delete(connectionId);
|
|
2285
1721
|
},
|
|
2286
|
-
|
|
2287
|
-
dispatchWebSocketError(connectionId: string, error: Error): void {
|
|
2288
|
-
// Check if connection is tracked
|
|
1722
|
+
dispatchWebSocketError(connectionId, error) {
|
|
2289
1723
|
if (!serveState.activeConnections.has(connectionId)) {
|
|
2290
1724
|
return;
|
|
2291
1725
|
}
|
|
2292
|
-
|
|
2293
|
-
// Check if error handler exists
|
|
2294
1726
|
const hasErrorHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.error`);
|
|
2295
1727
|
if (!hasErrorHandler) {
|
|
2296
1728
|
return;
|
|
2297
1729
|
}
|
|
2298
|
-
|
|
2299
|
-
const
|
|
2300
|
-
const safeMessage = error.message.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
1730
|
+
const safeName = error.name.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
1731
|
+
const safeMessage = error.message.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
2301
1732
|
context.evalSync(`
|
|
2302
1733
|
(function() {
|
|
2303
1734
|
const ws = globalThis.__activeWs_${connectionId}__;
|
|
@@ -2308,18 +1739,21 @@ export async function setupFetch(
|
|
|
2308
1739
|
})()
|
|
2309
1740
|
`);
|
|
2310
1741
|
},
|
|
2311
|
-
|
|
2312
|
-
onWebSocketCommand(callback: (cmd: WebSocketCommand) => void): () => void {
|
|
1742
|
+
onWebSocketCommand(callback) {
|
|
2313
1743
|
wsCommandCallbacks.add(callback);
|
|
2314
1744
|
return () => wsCommandCallbacks.delete(callback);
|
|
2315
1745
|
},
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
return context.evalSync(`!!globalThis.__serveOptions__?.fetch`) as boolean;
|
|
1746
|
+
hasServeHandler() {
|
|
1747
|
+
return context.evalSync(`!!globalThis.__serveOptions__?.fetch`);
|
|
2319
1748
|
},
|
|
2320
|
-
|
|
2321
|
-
hasActiveConnections(): boolean {
|
|
1749
|
+
hasActiveConnections() {
|
|
2322
1750
|
return serveState.activeConnections.size > 0;
|
|
2323
|
-
}
|
|
1751
|
+
}
|
|
2324
1752
|
};
|
|
2325
1753
|
}
|
|
1754
|
+
export {
|
|
1755
|
+
setupFetch,
|
|
1756
|
+
clearAllInstanceState
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
//# debugId=30AA5852218E953D64756E2164756E21
|