@ricsam/isolate-daemon 0.1.14 → 0.1.16

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.
@@ -7,12 +7,39 @@ import {
7
7
  ErrorCode,
8
8
  STREAM_CHUNK_SIZE,
9
9
  STREAM_DEFAULT_CREDIT,
10
- marshalValue
10
+ marshalValue,
11
+ isPromiseRef,
12
+ isAsyncIteratorRef,
13
+ deserializeResponse,
14
+ IsolateEvents,
15
+ ClientEvents
11
16
  } from "@ricsam/isolate-protocol";
12
17
  import { createCallbackFileSystemHandler } from "./callback-fs-handler.mjs";
13
18
  import {
14
19
  createRuntime
15
20
  } from "@ricsam/isolate-runtime";
21
+ var LINKER_CONFLICT_ERROR = "Module is currently being linked by another linker";
22
+ function getErrorText(error) {
23
+ if (error instanceof Error) {
24
+ const cause = error.cause;
25
+ const causeText = cause instanceof Error ? `${cause.name}: ${cause.message}
26
+ ${cause.stack ?? ""}` : cause != null ? String(cause) : "";
27
+ return [error.name, error.message, error.stack, causeText].filter((part) => part != null && part !== "").join(`
28
+ `);
29
+ }
30
+ if (typeof error === "string") {
31
+ return error;
32
+ }
33
+ try {
34
+ return JSON.stringify(error);
35
+ } catch {
36
+ return String(error ?? "");
37
+ }
38
+ }
39
+ function isLinkerConflictError(error) {
40
+ const text = getErrorText(error).toLowerCase();
41
+ return text.includes(LINKER_CONFLICT_ERROR.toLowerCase());
42
+ }
16
43
  function handleConnection(socket, state) {
17
44
  const connection = {
18
45
  socket,
@@ -45,17 +72,17 @@ function handleConnection(socket, state) {
45
72
  const instance = state.isolates.get(isolateId);
46
73
  if (instance) {
47
74
  if (instance.namespaceId != null && !instance.isDisposed) {
48
- softDeleteRuntime(instance, state);
75
+ if (instance.isPoisoned) {
76
+ hardDeleteRuntime(instance, state).catch(() => {});
77
+ } else {
78
+ softDeleteRuntime(instance, state);
79
+ }
49
80
  } else if (!instance.isDisposed) {
50
- instance.runtime.dispose().catch(() => {});
51
- state.isolates.delete(isolateId);
81
+ hardDeleteRuntime(instance, state).catch(() => {});
52
82
  }
53
83
  }
54
84
  }
55
85
  for (const [, pending] of connection.pendingCallbacks) {
56
- if (pending.timeoutId) {
57
- clearTimeout(pending.timeoutId);
58
- }
59
86
  pending.reject(new Error("Connection closed"));
60
87
  }
61
88
  connection.pendingCallbacks.clear();
@@ -183,28 +210,64 @@ async function handleMessage(message, connection, state) {
183
210
  case MessageType.CALLBACK_STREAM_END:
184
211
  handleCallbackStreamEnd(message, connection);
185
212
  break;
186
- case MessageType.CLIENT_WS_OPENED:
187
- handleClientWsOpened(message, connection, state);
188
- break;
189
- case MessageType.CLIENT_WS_MESSAGE:
190
- handleClientWsMessage(message, connection, state);
191
- break;
192
- case MessageType.CLIENT_WS_CLOSED:
193
- handleClientWsClosed(message, connection, state);
194
- break;
195
- case MessageType.CLIENT_WS_ERROR:
196
- handleClientWsError(message, connection, state);
213
+ case MessageType.CLIENT_EVENT:
214
+ handleClientEvent(message, connection, state);
197
215
  break;
198
216
  default:
199
217
  sendError(connection.socket, message.requestId ?? 0, ErrorCode.UNKNOWN_MESSAGE_TYPE, `Unknown message type: ${message.type}`);
200
218
  }
201
219
  }
220
+ async function hardDeleteRuntime(instance, state) {
221
+ try {
222
+ await instance.runtime.dispose();
223
+ } finally {
224
+ state.isolates.delete(instance.isolateId);
225
+ if (instance.namespaceId != null) {
226
+ const indexed = state.namespacedRuntimes.get(instance.namespaceId);
227
+ if (indexed?.isolateId === instance.isolateId) {
228
+ state.namespacedRuntimes.delete(instance.namespaceId);
229
+ }
230
+ }
231
+ instance.isDisposed = true;
232
+ instance.disposedAt = undefined;
233
+ instance.ownerConnection = null;
234
+ if (instance.callbackContext) {
235
+ if (instance.callbackContext.reconnectionPromise) {
236
+ clearTimeout(instance.callbackContext.reconnectionPromise.timeoutId);
237
+ instance.callbackContext.reconnectionPromise.promise.catch(() => {});
238
+ instance.callbackContext.reconnectionPromise.reject(new Error("Runtime was permanently disposed"));
239
+ instance.callbackContext.reconnectionPromise = undefined;
240
+ }
241
+ instance.callbackContext.connection = null;
242
+ }
243
+ }
244
+ }
245
+ var RECONNECTION_TIMEOUT_MS = 30000;
202
246
  function softDeleteRuntime(instance, state) {
203
247
  instance.isDisposed = true;
204
248
  instance.disposedAt = Date.now();
205
249
  instance.ownerConnection = null;
206
250
  if (instance.callbackContext) {
207
251
  instance.callbackContext.connection = null;
252
+ let resolve;
253
+ let reject;
254
+ const promise = new Promise((res, rej) => {
255
+ resolve = res;
256
+ reject = rej;
257
+ });
258
+ promise.catch(() => {});
259
+ const timeoutId = setTimeout(() => {
260
+ if (instance.callbackContext?.reconnectionPromise) {
261
+ instance.callbackContext.reconnectionPromise = undefined;
262
+ reject(new Error("Reconnection timeout: no client reconnected within timeout"));
263
+ }
264
+ }, RECONNECTION_TIMEOUT_MS);
265
+ instance.callbackContext.reconnectionPromise = {
266
+ promise,
267
+ resolve,
268
+ reject,
269
+ timeoutId
270
+ };
208
271
  }
209
272
  instance.callbacks.clear();
210
273
  instance.runtime.timers.clearAll();
@@ -218,6 +281,7 @@ function softDeleteRuntime(instance, state) {
218
281
  function reuseNamespacedRuntime(instance, connection, message, state) {
219
282
  instance.ownerConnection = connection.socket;
220
283
  instance.isDisposed = false;
284
+ instance.isPoisoned = false;
221
285
  instance.disposedAt = undefined;
222
286
  instance.lastActivity = Date.now();
223
287
  connection.isolates.add(instance.isolateId);
@@ -225,6 +289,11 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
225
289
  const testEnvOptions = message.options.testEnvironment != null && typeof message.options.testEnvironment === "object" ? message.options.testEnvironment : undefined;
226
290
  if (instance.callbackContext) {
227
291
  instance.callbackContext.connection = connection;
292
+ if (instance.callbackContext.reconnectionPromise) {
293
+ clearTimeout(instance.callbackContext.reconnectionPromise.timeoutId);
294
+ instance.callbackContext.reconnectionPromise.resolve(connection);
295
+ instance.callbackContext.reconnectionPromise = undefined;
296
+ }
228
297
  instance.callbackContext.consoleOnEntry = callbacks?.console?.onEntry?.callbackId;
229
298
  instance.callbackContext.fetch = callbacks?.fetch?.callbackId;
230
299
  instance.callbackContext.moduleLoader = callbacks?.moduleLoader?.callbackId;
@@ -319,6 +388,35 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
319
388
  instance.returnedIterators = new Map;
320
389
  instance.nextLocalCallbackId = 1e6;
321
390
  }
391
+ async function waitForConnection(callbackContext) {
392
+ if (callbackContext.connection) {
393
+ return callbackContext.connection;
394
+ }
395
+ if (callbackContext.reconnectionPromise) {
396
+ return callbackContext.reconnectionPromise.promise;
397
+ }
398
+ throw new Error("No connection available and no reconnection pending");
399
+ }
400
+ async function invokeCallbackWithReconnect(callbackContext, getCallbackId, args, label, invokeClientCallback) {
401
+ const conn = await waitForConnection(callbackContext);
402
+ const cbId = getCallbackId();
403
+ if (cbId === undefined) {
404
+ throw new Error(`${label} callback not available`);
405
+ }
406
+ try {
407
+ return await invokeClientCallback(conn, cbId, args);
408
+ } catch (err) {
409
+ if (callbackContext.reconnectionPromise && !callbackContext.connection) {
410
+ const newConn = await callbackContext.reconnectionPromise.promise;
411
+ const newCbId = getCallbackId();
412
+ if (newCbId === undefined) {
413
+ throw new Error(`${label} callback not available after reconnection`);
414
+ }
415
+ return invokeClientCallback(newConn, newCbId, args);
416
+ }
417
+ throw err;
418
+ }
419
+ }
322
420
  async function evictOldestDisposedRuntime(state) {
323
421
  let oldest = null;
324
422
  let oldestTime = Infinity;
@@ -332,18 +430,15 @@ async function evictOldestDisposedRuntime(state) {
332
430
  }
333
431
  if (oldest) {
334
432
  try {
335
- await oldest.runtime.dispose();
433
+ await hardDeleteRuntime(oldest, state);
336
434
  } catch {}
337
- state.isolates.delete(oldest.isolateId);
338
- if (oldest.namespaceId != null) {
339
- state.namespacedRuntimes.delete(oldest.namespaceId);
340
- }
341
435
  return true;
342
436
  }
343
437
  return false;
344
438
  }
345
439
  async function handleCreateRuntime(message, connection, state) {
346
440
  const namespaceId = message.options.namespaceId;
441
+ let namespaceCreationLocked = false;
347
442
  if (namespaceId != null) {
348
443
  const existing = state.namespacedRuntimes.get(namespaceId);
349
444
  if (existing) {
@@ -365,14 +460,20 @@ async function handleCreateRuntime(message, connection, state) {
365
460
  });
366
461
  return;
367
462
  }
368
- }
369
- if (state.isolates.size >= state.options.maxIsolates) {
370
- if (!await evictOldestDisposedRuntime(state)) {
371
- sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
463
+ if (state.namespacedCreatesInFlight.has(namespaceId)) {
464
+ sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, `Namespace "${namespaceId}" creation already in progress`);
372
465
  return;
373
466
  }
467
+ state.namespacedCreatesInFlight.add(namespaceId);
468
+ namespaceCreationLocked = true;
374
469
  }
375
470
  try {
471
+ if (state.isolates.size >= state.options.maxIsolates) {
472
+ if (!await evictOldestDisposedRuntime(state)) {
473
+ sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
474
+ return;
475
+ }
476
+ }
376
477
  const isolateId = randomUUID();
377
478
  const consoleCallbacks = message.options.callbacks?.console;
378
479
  const fetchCallback = message.options.callbacks?.fetch;
@@ -415,6 +516,7 @@ async function handleCreateRuntime(message, connection, state) {
415
516
  nextLocalCallbackId: 1e6,
416
517
  namespaceId,
417
518
  isDisposed: false,
519
+ isPoisoned: false,
418
520
  callbackContext
419
521
  };
420
522
  let bridgedCustomFunctions;
@@ -437,8 +539,6 @@ async function handleCreateRuntime(message, connection, state) {
437
539
  return iteratorId;
438
540
  }
439
541
  });
440
- const isPromiseRef = (value) => typeof value === "object" && value !== null && value.__type === "PromiseRef";
441
- const isAsyncIteratorRef = (value) => typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
442
542
  const addCallbackIdsToRefs = (value) => {
443
543
  if (value === null || typeof value !== "object")
444
544
  return value;
@@ -503,11 +603,16 @@ async function handleCreateRuntime(message, connection, state) {
503
603
  }
504
604
  return await callback(...args);
505
605
  } else {
506
- const conn = callbackContext.connection;
507
- if (!conn) {
508
- throw new Error(`No connection available for callback ${callbackId}`);
606
+ const conn = await waitForConnection(callbackContext);
607
+ try {
608
+ return await invokeClientCallback(conn, callbackId, args);
609
+ } catch (err) {
610
+ if (callbackContext.reconnectionPromise && !callbackContext.connection) {
611
+ const newConn = await callbackContext.reconnectionPromise.promise;
612
+ return invokeClientCallback(newConn, callbackId, args);
613
+ }
614
+ throw err;
509
615
  }
510
- return invokeClientCallback(conn, callbackId, args);
511
616
  }
512
617
  };
513
618
  customFnMarshalOptions = { createMarshalContext, addCallbackIdsToRefs, invokeCallback };
@@ -524,27 +629,20 @@ async function handleCreateRuntime(message, connection, state) {
524
629
  const nextCallbackId = callbackContext_.custom.get(`${name}:next`);
525
630
  const returnCallbackId = callbackContext_.custom.get(`${name}:return`);
526
631
  async function* bridgedIterator() {
527
- const conn = callbackContext_.connection;
528
- if (!conn || startCallbackId === undefined) {
529
- throw new Error(`AsyncIterator callback '${name}' not available`);
530
- }
531
- const startResult = await invokeClientCallback(conn, startCallbackId, args);
632
+ const startResult = await invokeCallbackWithReconnect(callbackContext_, () => callbackContext_.custom.get(`${name}:start`), args, `AsyncIterator '${name}' start`, invokeClientCallback);
532
633
  const iteratorId = startResult.iteratorId;
533
634
  try {
534
635
  while (true) {
535
- const nextConn = callbackContext_.connection;
536
- if (!nextConn || nextCallbackId === undefined) {
537
- throw new Error(`AsyncIterator callback '${name}' not available`);
538
- }
539
- const nextResult = await invokeClientCallback(nextConn, nextCallbackId, [iteratorId]);
636
+ const nextResult = await invokeCallbackWithReconnect(callbackContext_, () => callbackContext_.custom.get(`${name}:next`), [iteratorId], `AsyncIterator '${name}' next`, invokeClientCallback);
540
637
  if (nextResult.done)
541
638
  return nextResult.value;
542
639
  yield nextResult.value;
543
640
  }
544
641
  } finally {
545
642
  const retConn = callbackContext_.connection;
546
- if (retConn && returnCallbackId !== undefined) {
547
- await invokeClientCallback(retConn, returnCallbackId, [iteratorId]).catch(() => {});
643
+ const retCbId = callbackContext_.custom.get(`${name}:return`);
644
+ if (retConn && retCbId !== undefined) {
645
+ await invokeClientCallback(retConn, retCbId, [iteratorId]).catch(() => {});
548
646
  }
549
647
  }
550
648
  }
@@ -555,12 +653,7 @@ async function handleCreateRuntime(message, connection, state) {
555
653
  bridgedCustomFunctions[name] = {
556
654
  type: registration.type,
557
655
  fn: async (...args) => {
558
- const conn = callbackContext_.connection;
559
- const cbId = callbackContext_.custom.get(name);
560
- if (!conn || cbId === undefined) {
561
- throw new Error(`Custom function callback '${name}' not available`);
562
- }
563
- return invokeClientCallback(conn, cbId, args);
656
+ return invokeCallbackWithReconnect(callbackContext_, () => callbackContext_.custom.get(name), args, `Custom function '${name}'`, invokeClientCallback);
564
657
  }
565
658
  };
566
659
  }
@@ -569,12 +662,7 @@ async function handleCreateRuntime(message, connection, state) {
569
662
  let moduleLoader;
570
663
  if (moduleLoaderCallback) {
571
664
  moduleLoader = async (specifier, importer) => {
572
- const conn = callbackContext.connection;
573
- const cbId = callbackContext.moduleLoader;
574
- if (!conn || cbId === undefined) {
575
- throw new Error("Module loader callback not available");
576
- }
577
- return invokeClientCallback(conn, cbId, [specifier, importer]);
665
+ return invokeCallbackWithReconnect(callbackContext, () => callbackContext.moduleLoader, [specifier, importer], "Module loader", invokeClientCallback);
578
666
  };
579
667
  }
580
668
  let testEnvironment;
@@ -605,19 +693,8 @@ async function handleCreateRuntime(message, connection, state) {
605
693
  if (playwrightCallbacks) {
606
694
  playwrightOptions = {
607
695
  handler: async (op) => {
608
- const conn = callbackContext.connection;
609
- const callbackId = callbackContext.playwright.handlerCallbackId;
610
- if (!conn || callbackId === undefined) {
611
- return {
612
- ok: false,
613
- error: {
614
- name: "Error",
615
- message: "Playwright handler callback not available"
616
- }
617
- };
618
- }
619
696
  try {
620
- const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)], 60000);
697
+ const resultJson = await invokeCallbackWithReconnect(callbackContext, () => callbackContext.playwright.handlerCallbackId, [JSON.stringify(op)], "Playwright handler", invokeClientCallback);
621
698
  return JSON.parse(resultJson);
622
699
  } catch (err) {
623
700
  const error = err;
@@ -657,18 +734,13 @@ async function handleCreateRuntime(message, connection, state) {
657
734
  }
658
735
  },
659
736
  fetch: async (url, init) => {
660
- const conn = callbackContext.connection;
661
- const callbackId = callbackContext.fetch;
662
- if (!conn || callbackId === undefined) {
663
- throw new Error("Fetch callback not available");
664
- }
665
737
  const serialized = {
666
738
  url,
667
739
  method: init.method,
668
740
  headers: init.headers,
669
741
  body: init.rawBody
670
742
  };
671
- const result = await invokeClientCallback(conn, callbackId, [serialized], 60000);
743
+ const result = await invokeCallbackWithReconnect(callbackContext, () => callbackContext.fetch, [serialized], "Fetch", invokeClientCallback);
672
744
  if (result && typeof result === "object" && result.__streamingResponse) {
673
745
  const response = result.response;
674
746
  response.__isCallbackStream = true;
@@ -678,10 +750,7 @@ async function handleCreateRuntime(message, connection, state) {
678
750
  },
679
751
  fs: {
680
752
  getDirectory: async (dirPath) => {
681
- const conn = callbackContext.connection;
682
- if (!conn) {
683
- throw new Error("FS callbacks not available");
684
- }
753
+ const conn = await waitForConnection(callbackContext);
685
754
  return createCallbackFileSystemHandler({
686
755
  connection: conn,
687
756
  callbackContext,
@@ -768,18 +837,19 @@ async function handleCreateRuntime(message, connection, state) {
768
837
  } else {
769
838
  data = cmd.data;
770
839
  }
771
- const wsCommandMsg = {
772
- type: MessageType.WS_COMMAND,
773
- isolateId,
774
- command: {
775
- type: cmd.type,
776
- connectionId: cmd.connectionId,
777
- data,
778
- code: cmd.code,
779
- reason: cmd.reason
780
- }
840
+ const payload = {
841
+ type: cmd.type,
842
+ connectionId: cmd.connectionId,
843
+ data,
844
+ code: cmd.code,
845
+ reason: cmd.reason
781
846
  };
782
- sendMessage(targetConnection.socket, wsCommandMsg);
847
+ sendMessage(targetConnection.socket, {
848
+ type: MessageType.ISOLATE_EVENT,
849
+ isolateId,
850
+ event: IsolateEvents.WS_COMMAND,
851
+ payload
852
+ });
783
853
  });
784
854
  runtime.fetch.onClientWebSocketCommand((cmd) => {
785
855
  const targetConnection = callbackContext.connection;
@@ -793,40 +863,62 @@ async function handleCreateRuntime(message, connection, state) {
793
863
  data = cmd.data;
794
864
  }
795
865
  if (cmd.type === "connect") {
796
- const msg = {
797
- type: MessageType.CLIENT_WS_CONNECT,
798
- requestId: 0,
799
- isolateId,
866
+ const payload = {
800
867
  socketId: cmd.socketId,
801
868
  url: cmd.url,
802
869
  protocols: cmd.protocols
803
870
  };
804
- sendMessage(targetConnection.socket, msg);
805
- } else if (cmd.type === "send") {
806
- const msg = {
807
- type: MessageType.CLIENT_WS_SEND,
808
- requestId: 0,
871
+ sendMessage(targetConnection.socket, {
872
+ type: MessageType.ISOLATE_EVENT,
809
873
  isolateId,
874
+ event: IsolateEvents.WS_CLIENT_CONNECT,
875
+ payload
876
+ });
877
+ } else if (cmd.type === "send") {
878
+ const payload = {
810
879
  socketId: cmd.socketId,
811
880
  data
812
881
  };
813
- sendMessage(targetConnection.socket, msg);
814
- } else if (cmd.type === "close") {
815
- const msg = {
816
- type: MessageType.CLIENT_WS_CLOSE,
817
- requestId: 0,
882
+ sendMessage(targetConnection.socket, {
883
+ type: MessageType.ISOLATE_EVENT,
818
884
  isolateId,
885
+ event: IsolateEvents.WS_CLIENT_SEND,
886
+ payload
887
+ });
888
+ } else if (cmd.type === "close") {
889
+ const payload = {
819
890
  socketId: cmd.socketId,
820
891
  code: cmd.code,
821
892
  reason: cmd.reason
822
893
  };
823
- sendMessage(targetConnection.socket, msg);
894
+ sendMessage(targetConnection.socket, {
895
+ type: MessageType.ISOLATE_EVENT,
896
+ isolateId,
897
+ event: IsolateEvents.WS_CLIENT_CLOSE,
898
+ payload
899
+ });
824
900
  }
825
901
  });
902
+ runtime.fetch.onEvent((eventName, payload) => {
903
+ const targetConnection = callbackContext.connection;
904
+ if (!targetConnection) {
905
+ return;
906
+ }
907
+ sendMessage(targetConnection.socket, {
908
+ type: MessageType.ISOLATE_EVENT,
909
+ isolateId,
910
+ event: eventName,
911
+ payload
912
+ });
913
+ });
826
914
  sendOk(connection.socket, message.requestId, { isolateId, reused: false });
827
915
  } catch (err) {
828
916
  const error = err;
829
917
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
918
+ } finally {
919
+ if (namespaceCreationLocked && namespaceId != null) {
920
+ state.namespacedCreatesInFlight.delete(namespaceId);
921
+ }
830
922
  }
831
923
  }
832
924
  async function handleDisposeRuntime(message, connection, state) {
@@ -842,14 +934,20 @@ async function handleDisposeRuntime(message, connection, state) {
842
934
  try {
843
935
  connection.isolates.delete(message.isolateId);
844
936
  if (instance.namespaceId != null) {
845
- softDeleteRuntime(instance, state);
937
+ if (instance.isPoisoned) {
938
+ await hardDeleteRuntime(instance, state);
939
+ } else {
940
+ softDeleteRuntime(instance, state);
941
+ }
846
942
  } else {
847
- await instance.runtime.dispose();
848
- state.isolates.delete(message.isolateId);
943
+ await hardDeleteRuntime(instance, state);
849
944
  }
850
945
  sendOk(connection.socket, message.requestId);
851
946
  } catch (err) {
852
947
  const error = err;
948
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
949
+ instance.isPoisoned = true;
950
+ }
853
951
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
854
952
  }
855
953
  }
@@ -862,14 +960,15 @@ async function handleEval(message, connection, state) {
862
960
  instance.lastActivity = Date.now();
863
961
  try {
864
962
  await instance.runtime.eval(message.code, {
865
- filename: message.filename,
866
- maxExecutionMs: message.maxExecutionMs
963
+ filename: message.filename
867
964
  });
868
965
  sendOk(connection.socket, message.requestId, { value: undefined });
869
966
  } catch (err) {
870
967
  const error = err;
871
- const isTimeoutError = error.message?.includes("Script execution timed out");
872
- sendError(connection.socket, message.requestId, isTimeoutError ? ErrorCode.ISOLATE_TIMEOUT : ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
968
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
969
+ instance.isPoisoned = true;
970
+ }
971
+ sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
873
972
  }
874
973
  }
875
974
  async function handleDispatchRequest(message, connection, state) {
@@ -977,34 +1076,38 @@ async function handleWsClose(message, connection, state) {
977
1076
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
978
1077
  }
979
1078
  }
980
- function handleClientWsOpened(message, connection, state) {
981
- const instance = state.isolates.get(message.isolateId);
982
- if (!instance)
983
- return;
984
- instance.lastActivity = Date.now();
985
- instance.runtime.fetch.dispatchClientWebSocketOpen(message.socketId, message.protocol, message.extensions);
986
- }
987
- function handleClientWsMessage(message, connection, state) {
988
- const instance = state.isolates.get(message.isolateId);
989
- if (!instance)
990
- return;
991
- instance.lastActivity = Date.now();
992
- const data = message.data instanceof Uint8Array ? message.data.buffer.slice(message.data.byteOffset, message.data.byteOffset + message.data.byteLength) : message.data;
993
- instance.runtime.fetch.dispatchClientWebSocketMessage(message.socketId, data);
994
- }
995
- function handleClientWsClosed(message, connection, state) {
1079
+ function handleClientEvent(message, connection, state) {
996
1080
  const instance = state.isolates.get(message.isolateId);
997
1081
  if (!instance)
998
1082
  return;
999
1083
  instance.lastActivity = Date.now();
1000
- instance.runtime.fetch.dispatchClientWebSocketClose(message.socketId, message.code, message.reason, message.wasClean);
1001
- }
1002
- function handleClientWsError(message, connection, state) {
1003
- const instance = state.isolates.get(message.isolateId);
1004
- if (!instance)
1005
- return;
1006
- instance.lastActivity = Date.now();
1007
- instance.runtime.fetch.dispatchClientWebSocketError(message.socketId);
1084
+ switch (message.event) {
1085
+ case ClientEvents.WS_CLIENT_OPENED: {
1086
+ const payload = message.payload;
1087
+ instance.runtime.fetch.dispatchClientWebSocketOpen(payload.socketId, payload.protocol, payload.extensions);
1088
+ break;
1089
+ }
1090
+ case ClientEvents.WS_CLIENT_MESSAGE: {
1091
+ const payload = message.payload;
1092
+ const data = payload.data instanceof Uint8Array ? payload.data.buffer.slice(payload.data.byteOffset, payload.data.byteOffset + payload.data.byteLength) : payload.data;
1093
+ instance.runtime.fetch.dispatchClientWebSocketMessage(payload.socketId, data);
1094
+ break;
1095
+ }
1096
+ case ClientEvents.WS_CLIENT_CLOSED: {
1097
+ const payload = message.payload;
1098
+ instance.runtime.fetch.dispatchClientWebSocketClose(payload.socketId, payload.code, payload.reason, payload.wasClean);
1099
+ break;
1100
+ }
1101
+ case ClientEvents.WS_CLIENT_ERROR: {
1102
+ const payload = message.payload;
1103
+ instance.runtime.fetch.dispatchClientWebSocketError(payload.socketId);
1104
+ break;
1105
+ }
1106
+ default: {
1107
+ instance.runtime.fetch.dispatchEvent(message.event, message.payload);
1108
+ break;
1109
+ }
1110
+ }
1008
1111
  }
1009
1112
  async function handleFetchGetUpgradeRequest(message, connection, state) {
1010
1113
  const instance = state.isolates.get(message.isolateId);
@@ -1148,9 +1251,6 @@ function handleCallbackResponse(message, connection) {
1148
1251
  return;
1149
1252
  }
1150
1253
  connection.pendingCallbacks.delete(message.requestId);
1151
- if (pending.timeoutId) {
1152
- clearTimeout(pending.timeoutId);
1153
- }
1154
1254
  if (message.error) {
1155
1255
  const error = new Error(message.error.message);
1156
1256
  error.name = message.error.name;
@@ -1162,17 +1262,12 @@ function handleCallbackResponse(message, connection) {
1162
1262
  pending.resolve(message.result);
1163
1263
  }
1164
1264
  }
1165
- async function invokeClientCallback(connection, callbackId, args, timeout = 1e4) {
1265
+ async function invokeClientCallback(connection, callbackId, args) {
1166
1266
  const requestId = connection.nextCallbackId++;
1167
1267
  return new Promise((resolve, reject) => {
1168
- const timeoutId = setTimeout(() => {
1169
- connection.pendingCallbacks.delete(requestId);
1170
- reject(new Error("Callback timeout"));
1171
- }, timeout);
1172
1268
  const pending = {
1173
1269
  resolve,
1174
- reject,
1175
- timeoutId
1270
+ reject
1176
1271
  };
1177
1272
  connection.pendingCallbacks.set(requestId, pending);
1178
1273
  const invoke = {
@@ -1184,13 +1279,6 @@ async function invokeClientCallback(connection, callbackId, args, timeout = 1e4)
1184
1279
  sendMessage(connection.socket, invoke);
1185
1280
  });
1186
1281
  }
1187
- function deserializeResponse(data) {
1188
- return new Response(data.body, {
1189
- status: data.status,
1190
- statusText: data.statusText,
1191
- headers: data.headers
1192
- });
1193
- }
1194
1282
  function handleStreamPush(message, connection) {
1195
1283
  const receiver = connection.streamReceivers.get(message.streamId);
1196
1284
  if (!receiver) {
@@ -1318,9 +1406,6 @@ function handleCallbackStreamStart(message, connection) {
1318
1406
  const pending = connection.pendingCallbacks.get(message.requestId);
1319
1407
  if (pending) {
1320
1408
  connection.pendingCallbacks.delete(message.requestId);
1321
- if (pending.timeoutId) {
1322
- clearTimeout(pending.timeoutId);
1323
- }
1324
1409
  const response = new Response(readableStream, {
1325
1410
  status: message.metadata.status,
1326
1411
  statusText: message.metadata.statusText,
@@ -1451,13 +1536,35 @@ async function handleRunTests(message, connection, state) {
1451
1536
  return;
1452
1537
  }
1453
1538
  instance.lastActivity = Date.now();
1539
+ if (instance.pendingTestRun) {
1540
+ try {
1541
+ const results = await instance.pendingTestRun.promise;
1542
+ const currentConn = instance.callbackContext?.connection;
1543
+ if (currentConn) {
1544
+ sendOk(currentConn.socket, message.requestId, results);
1545
+ }
1546
+ } catch (err) {
1547
+ const error = err;
1548
+ const currentConn = instance.callbackContext?.connection;
1549
+ if (currentConn) {
1550
+ sendError(currentConn.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
1551
+ }
1552
+ }
1553
+ return;
1554
+ }
1454
1555
  try {
1455
1556
  const timeout = message.timeout ?? 30000;
1456
- const results = await instance.runtime.testEnvironment.runTests(timeout);
1457
- sendOk(connection.socket, message.requestId, results);
1557
+ const runPromise = instance.runtime.testEnvironment.runTests(timeout);
1558
+ instance.pendingTestRun = { promise: runPromise };
1559
+ const results = await runPromise;
1560
+ instance.pendingTestRun = undefined;
1561
+ const currentConn = instance.callbackContext?.connection ?? connection;
1562
+ sendOk(currentConn.socket, message.requestId, results);
1458
1563
  } catch (err) {
1564
+ instance.pendingTestRun = undefined;
1459
1565
  const error = err;
1460
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
1566
+ const currentConn = instance.callbackContext?.connection ?? connection;
1567
+ sendError(currentConn.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
1461
1568
  }
1462
1569
  }
1463
1570
  async function handleResetTestEnv(message, connection, state) {
@@ -1539,4 +1646,4 @@ export {
1539
1646
  handleConnection
1540
1647
  };
1541
1648
 
1542
- //# debugId=C809F161C354C84D64756E2164756E21
1649
+ //# debugId=DCB2345D02FBF03C64756E2164756E21