@ricsam/isolate-daemon 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,5 @@
1
1
  // packages/isolate-daemon/src/connection.ts
2
2
  import { randomUUID } from "node:crypto";
3
- import path from "node:path";
4
- import ivm from "isolated-vm";
5
3
  import {
6
4
  createFrameParser,
7
5
  buildFrame,
@@ -9,29 +7,39 @@ import {
9
7
  ErrorCode,
10
8
  STREAM_CHUNK_SIZE,
11
9
  STREAM_DEFAULT_CREDIT,
12
- normalizeEntryFilename,
13
10
  marshalValue,
14
- unmarshalValue
11
+ isPromiseRef,
12
+ isAsyncIteratorRef,
13
+ deserializeResponse,
14
+ IsolateEvents,
15
+ ClientEvents
15
16
  } from "@ricsam/isolate-protocol";
16
17
  import { createCallbackFileSystemHandler } from "./callback-fs-handler.mjs";
17
18
  import {
18
- setupTestEnvironment,
19
- runTests as runTestsInContext,
20
- hasTests as hasTestsInContext,
21
- getTestCount as getTestCountInContext
22
- } from "@ricsam/isolate-test-environment";
23
- import {
24
- setupPlaywright
25
- } from "@ricsam/isolate-playwright";
26
- import {
27
- createInternalRuntime
19
+ createRuntime
28
20
  } from "@ricsam/isolate-runtime";
29
- import {
30
- transformEntryCode,
31
- transformModuleCode,
32
- contentHash,
33
- mapErrorStack
34
- } from "@ricsam/isolate-transform";
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
+ }
35
43
  function handleConnection(socket, state) {
36
44
  const connection = {
37
45
  socket,
@@ -64,22 +72,17 @@ function handleConnection(socket, state) {
64
72
  const instance = state.isolates.get(isolateId);
65
73
  if (instance) {
66
74
  if (instance.namespaceId != null && !instance.isDisposed) {
67
- softDeleteRuntime(instance, state);
75
+ if (instance.isPoisoned) {
76
+ hardDeleteRuntime(instance, state).catch(() => {});
77
+ } else {
78
+ softDeleteRuntime(instance, state);
79
+ }
68
80
  } else if (!instance.isDisposed) {
69
- try {
70
- if (instance.playwrightHandle) {
71
- instance.playwrightHandle.dispose();
72
- }
73
- instance.runtime.dispose();
74
- } catch {}
75
- state.isolates.delete(isolateId);
81
+ hardDeleteRuntime(instance, state).catch(() => {});
76
82
  }
77
83
  }
78
84
  }
79
85
  for (const [, pending] of connection.pendingCallbacks) {
80
- if (pending.timeoutId) {
81
- clearTimeout(pending.timeoutId);
82
- }
83
86
  pending.reject(new Error("Connection closed"));
84
87
  }
85
88
  connection.pendingCallbacks.clear();
@@ -177,12 +180,6 @@ async function handleMessage(message, connection, state) {
177
180
  case MessageType.GET_TEST_COUNT:
178
181
  await handleGetTestCount(message, connection, state);
179
182
  break;
180
- case MessageType.RUN_PLAYWRIGHT_TESTS:
181
- await handleRunPlaywrightTests(message, connection, state);
182
- break;
183
- case MessageType.RESET_PLAYWRIGHT_TESTS:
184
- await handleResetPlaywrightTests(message, connection, state);
185
- break;
186
183
  case MessageType.GET_COLLECTED_DATA:
187
184
  await handleGetCollectedData(message, connection, state);
188
185
  break;
@@ -213,34 +210,69 @@ async function handleMessage(message, connection, state) {
213
210
  case MessageType.CALLBACK_STREAM_END:
214
211
  handleCallbackStreamEnd(message, connection);
215
212
  break;
213
+ case MessageType.CLIENT_EVENT:
214
+ handleClientEvent(message, connection, state);
215
+ break;
216
216
  default:
217
217
  sendError(connection.socket, message.requestId ?? 0, ErrorCode.UNKNOWN_MESSAGE_TYPE, `Unknown message type: ${message.type}`);
218
218
  }
219
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
+ instance.callbackContext.connection = null;
236
+ }
237
+ }
238
+ }
220
239
  function softDeleteRuntime(instance, state) {
221
240
  instance.isDisposed = true;
222
241
  instance.disposedAt = Date.now();
223
242
  instance.ownerConnection = null;
243
+ if (instance.callbackContext) {
244
+ instance.callbackContext.connection = null;
245
+ }
224
246
  instance.callbacks.clear();
225
247
  instance.runtime.timers.clearAll();
226
248
  instance.runtime.console.reset();
227
- instance.pendingCallbacks.length = 0;
249
+ instance.runtime.pendingCallbacks.length = 0;
228
250
  instance.returnedCallbacks?.clear();
229
251
  instance.returnedPromises?.clear();
230
252
  instance.returnedIterators?.clear();
253
+ instance.runtime.clearModuleCache();
231
254
  }
232
255
  function reuseNamespacedRuntime(instance, connection, message, state) {
233
256
  instance.ownerConnection = connection.socket;
234
257
  instance.isDisposed = false;
258
+ instance.isPoisoned = false;
235
259
  instance.disposedAt = undefined;
236
260
  instance.lastActivity = Date.now();
237
261
  connection.isolates.add(instance.isolateId);
238
262
  const callbacks = message.options.callbacks;
263
+ const testEnvOptions = message.options.testEnvironment != null && typeof message.options.testEnvironment === "object" ? message.options.testEnvironment : undefined;
239
264
  if (instance.callbackContext) {
240
265
  instance.callbackContext.connection = connection;
241
266
  instance.callbackContext.consoleOnEntry = callbacks?.console?.onEntry?.callbackId;
242
267
  instance.callbackContext.fetch = callbacks?.fetch?.callbackId;
243
268
  instance.callbackContext.moduleLoader = callbacks?.moduleLoader?.callbackId;
269
+ instance.callbackContext.testEnvironmentOnEvent = testEnvOptions?.callbacks?.onEvent?.callbackId;
270
+ instance.callbackContext.playwright = {
271
+ handlerCallbackId: callbacks?.playwright?.handlerCallbackId,
272
+ onBrowserConsoleLogCallbackId: callbacks?.playwright?.onBrowserConsoleLogCallbackId,
273
+ onNetworkRequestCallbackId: callbacks?.playwright?.onNetworkRequestCallbackId,
274
+ onNetworkResponseCallbackId: callbacks?.playwright?.onNetworkResponseCallbackId
275
+ };
244
276
  instance.callbackContext.fs = {
245
277
  readFile: callbacks?.fs?.readFile?.callbackId,
246
278
  writeFile: callbacks?.fs?.writeFile?.callbackId,
@@ -277,7 +309,6 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
277
309
  }
278
310
  }
279
311
  if (callbacks?.moduleLoader) {
280
- instance.moduleLoaderCallbackId = callbacks.moduleLoader.callbackId;
281
312
  instance.callbacks.set(callbacks.moduleLoader.callbackId, callbacks.moduleLoader);
282
313
  }
283
314
  if (callbacks?.custom) {
@@ -287,23 +318,46 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
287
318
  }
288
319
  }
289
320
  }
321
+ if (testEnvOptions?.callbacks?.onEvent) {
322
+ instance.callbacks.set(testEnvOptions.callbacks.onEvent.callbackId, {
323
+ ...testEnvOptions.callbacks.onEvent,
324
+ name: "testEnvironment.onEvent"
325
+ });
326
+ }
327
+ if (callbacks?.playwright) {
328
+ instance.callbacks.set(callbacks.playwright.handlerCallbackId, {
329
+ callbackId: callbacks.playwright.handlerCallbackId,
330
+ name: "playwright.handler",
331
+ type: "async"
332
+ });
333
+ if (callbacks.playwright.onBrowserConsoleLogCallbackId !== undefined) {
334
+ instance.callbacks.set(callbacks.playwright.onBrowserConsoleLogCallbackId, {
335
+ callbackId: callbacks.playwright.onBrowserConsoleLogCallbackId,
336
+ name: "playwright.onBrowserConsoleLog",
337
+ type: "sync"
338
+ });
339
+ }
340
+ if (callbacks.playwright.onNetworkRequestCallbackId !== undefined) {
341
+ instance.callbacks.set(callbacks.playwright.onNetworkRequestCallbackId, {
342
+ callbackId: callbacks.playwright.onNetworkRequestCallbackId,
343
+ name: "playwright.onNetworkRequest",
344
+ type: "sync"
345
+ });
346
+ }
347
+ if (callbacks.playwright.onNetworkResponseCallbackId !== undefined) {
348
+ instance.callbacks.set(callbacks.playwright.onNetworkResponseCallbackId, {
349
+ callbackId: callbacks.playwright.onNetworkResponseCallbackId,
350
+ name: "playwright.onNetworkResponse",
351
+ type: "sync"
352
+ });
353
+ }
354
+ }
290
355
  instance.returnedCallbacks = new Map;
291
356
  instance.returnedPromises = new Map;
292
357
  instance.returnedIterators = new Map;
293
358
  instance.nextLocalCallbackId = 1e6;
294
- if (callbacks?.custom) {
295
- const newCallbackIdMap = {};
296
- for (const [name, reg] of Object.entries(callbacks.custom)) {
297
- if (reg) {
298
- newCallbackIdMap[name] = reg.callbackId;
299
- }
300
- }
301
- try {
302
- instance.runtime.context.global.setSync("__customFnCallbackIds", new ivm.ExternalCopy(newCallbackIdMap).copyInto());
303
- } catch {}
304
- }
305
359
  }
306
- function evictOldestDisposedRuntime(state) {
360
+ async function evictOldestDisposedRuntime(state) {
307
361
  let oldest = null;
308
362
  let oldestTime = Infinity;
309
363
  for (const [, instance] of state.isolates) {
@@ -316,21 +370,15 @@ function evictOldestDisposedRuntime(state) {
316
370
  }
317
371
  if (oldest) {
318
372
  try {
319
- if (oldest.playwrightHandle) {
320
- oldest.playwrightHandle.dispose();
321
- }
322
- oldest.runtime.dispose();
373
+ await hardDeleteRuntime(oldest, state);
323
374
  } catch {}
324
- state.isolates.delete(oldest.isolateId);
325
- if (oldest.namespaceId != null) {
326
- state.namespacedRuntimes.delete(oldest.namespaceId);
327
- }
328
375
  return true;
329
376
  }
330
377
  return false;
331
378
  }
332
379
  async function handleCreateRuntime(message, connection, state) {
333
380
  const namespaceId = message.options.namespaceId;
381
+ let namespaceCreationLocked = false;
334
382
  if (namespaceId != null) {
335
383
  const existing = state.namespacedRuntimes.get(namespaceId);
336
384
  if (existing) {
@@ -352,26 +400,38 @@ async function handleCreateRuntime(message, connection, state) {
352
400
  });
353
401
  return;
354
402
  }
355
- }
356
- if (state.isolates.size >= state.options.maxIsolates) {
357
- if (!evictOldestDisposedRuntime(state)) {
358
- sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
403
+ if (state.namespacedCreatesInFlight.has(namespaceId)) {
404
+ sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, `Namespace "${namespaceId}" creation already in progress`);
359
405
  return;
360
406
  }
407
+ state.namespacedCreatesInFlight.add(namespaceId);
408
+ namespaceCreationLocked = true;
361
409
  }
362
410
  try {
411
+ if (state.isolates.size >= state.options.maxIsolates) {
412
+ if (!await evictOldestDisposedRuntime(state)) {
413
+ sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
414
+ return;
415
+ }
416
+ }
363
417
  const isolateId = randomUUID();
364
418
  const consoleCallbacks = message.options.callbacks?.console;
365
419
  const fetchCallback = message.options.callbacks?.fetch;
366
420
  const fsCallbacks = message.options.callbacks?.fs;
367
421
  const moduleLoaderCallback = message.options.callbacks?.moduleLoader;
368
422
  const customCallbacks = message.options.callbacks?.custom;
369
- const pendingCallbacks = [];
370
423
  const callbackContext = {
371
424
  connection,
372
425
  consoleOnEntry: consoleCallbacks?.onEntry?.callbackId,
373
426
  fetch: fetchCallback?.callbackId,
374
427
  moduleLoader: moduleLoaderCallback?.callbackId,
428
+ testEnvironmentOnEvent: message.options.testEnvironment != null && typeof message.options.testEnvironment === "object" ? message.options.testEnvironment.callbacks?.onEvent?.callbackId : undefined,
429
+ playwright: {
430
+ handlerCallbackId: message.options.callbacks?.playwright?.handlerCallbackId,
431
+ onBrowserConsoleLogCallbackId: message.options.callbacks?.playwright?.onBrowserConsoleLogCallbackId,
432
+ onNetworkRequestCallbackId: message.options.callbacks?.playwright?.onNetworkRequestCallbackId,
433
+ onNetworkResponseCallbackId: message.options.callbacks?.playwright?.onNetworkResponseCallbackId
434
+ },
375
435
  fs: {
376
436
  readFile: fsCallbacks?.readFile?.callbackId,
377
437
  writeFile: fsCallbacks?.writeFile?.callbackId,
@@ -383,7 +443,247 @@ async function handleCreateRuntime(message, connection, state) {
383
443
  },
384
444
  custom: new Map(customCallbacks ? Object.entries(customCallbacks).map(([name, reg]) => [name, reg.callbackId]) : [])
385
445
  };
386
- const runtime = await createInternalRuntime({
446
+ const instance = {
447
+ isolateId,
448
+ runtime: null,
449
+ ownerConnection: connection.socket,
450
+ callbacks: new Map,
451
+ createdAt: Date.now(),
452
+ lastActivity: Date.now(),
453
+ returnedCallbacks: new Map,
454
+ returnedPromises: new Map,
455
+ returnedIterators: new Map,
456
+ nextLocalCallbackId: 1e6,
457
+ namespaceId,
458
+ isDisposed: false,
459
+ isPoisoned: false,
460
+ callbackContext
461
+ };
462
+ let bridgedCustomFunctions;
463
+ let customFnMarshalOptions;
464
+ if (customCallbacks) {
465
+ const createMarshalContext = () => ({
466
+ registerCallback: (fn) => {
467
+ const callbackId = instance.nextLocalCallbackId++;
468
+ instance.returnedCallbacks.set(callbackId, fn);
469
+ return callbackId;
470
+ },
471
+ registerPromise: (promise) => {
472
+ const promiseId = instance.nextLocalCallbackId++;
473
+ instance.returnedPromises.set(promiseId, promise);
474
+ return promiseId;
475
+ },
476
+ registerIterator: (iterator) => {
477
+ const iteratorId = instance.nextLocalCallbackId++;
478
+ instance.returnedIterators.set(iteratorId, iterator);
479
+ return iteratorId;
480
+ }
481
+ });
482
+ const addCallbackIdsToRefs = (value) => {
483
+ if (value === null || typeof value !== "object")
484
+ return value;
485
+ if (isPromiseRef(value)) {
486
+ if ("__resolveCallbackId" in value)
487
+ return value;
488
+ const resolveCallbackId = instance.nextLocalCallbackId++;
489
+ instance.returnedCallbacks.set(resolveCallbackId, async (promiseId) => {
490
+ const promise = instance.returnedPromises.get(promiseId);
491
+ if (!promise)
492
+ throw new Error(`Promise ${promiseId} not found`);
493
+ const result2 = await promise;
494
+ instance.returnedPromises.delete(promiseId);
495
+ const ctx = createMarshalContext();
496
+ const marshalled = await marshalValue(result2, ctx);
497
+ return addCallbackIdsToRefs(marshalled);
498
+ });
499
+ return { ...value, __resolveCallbackId: resolveCallbackId };
500
+ }
501
+ if (isAsyncIteratorRef(value)) {
502
+ if ("__nextCallbackId" in value)
503
+ return value;
504
+ const nextCallbackId = instance.nextLocalCallbackId++;
505
+ instance.returnedCallbacks.set(nextCallbackId, async (iteratorId) => {
506
+ const iterator = instance.returnedIterators.get(iteratorId);
507
+ if (!iterator)
508
+ throw new Error(`Iterator ${iteratorId} not found`);
509
+ const result2 = await iterator.next();
510
+ if (result2.done)
511
+ instance.returnedIterators.delete(iteratorId);
512
+ const ctx = createMarshalContext();
513
+ const marshalledValue = await marshalValue(result2.value, ctx);
514
+ return { done: result2.done, value: addCallbackIdsToRefs(marshalledValue) };
515
+ });
516
+ const returnCallbackId = instance.nextLocalCallbackId++;
517
+ instance.returnedCallbacks.set(returnCallbackId, async (iteratorId, returnValue) => {
518
+ const iterator = instance.returnedIterators.get(iteratorId);
519
+ instance.returnedIterators.delete(iteratorId);
520
+ if (!iterator || !iterator.return)
521
+ return { done: true, value: undefined };
522
+ const result2 = await iterator.return(returnValue);
523
+ const ctx = createMarshalContext();
524
+ const marshalledValue = await marshalValue(result2.value, ctx);
525
+ return { done: true, value: addCallbackIdsToRefs(marshalledValue) };
526
+ });
527
+ return { ...value, __nextCallbackId: nextCallbackId, __returnCallbackId: returnCallbackId };
528
+ }
529
+ if (Array.isArray(value))
530
+ return value.map((item) => addCallbackIdsToRefs(item));
531
+ const result = {};
532
+ for (const key of Object.keys(value)) {
533
+ result[key] = addCallbackIdsToRefs(value[key]);
534
+ }
535
+ return result;
536
+ };
537
+ const LOCAL_CALLBACK_THRESHOLD = 1e6;
538
+ const invokeCallback = async (callbackId, args) => {
539
+ if (callbackId >= LOCAL_CALLBACK_THRESHOLD) {
540
+ const callback = instance.returnedCallbacks.get(callbackId);
541
+ if (!callback) {
542
+ throw new Error(`Local callback ${callbackId} not found`);
543
+ }
544
+ return await callback(...args);
545
+ } else {
546
+ const conn = callbackContext.connection;
547
+ if (!conn) {
548
+ throw new Error(`No connection available for callback ${callbackId}`);
549
+ }
550
+ return invokeClientCallback(conn, callbackId, args);
551
+ }
552
+ };
553
+ customFnMarshalOptions = { createMarshalContext, addCallbackIdsToRefs, invokeCallback };
554
+ bridgedCustomFunctions = {};
555
+ for (const [name, registration] of Object.entries(customCallbacks)) {
556
+ if (name.includes(":"))
557
+ continue;
558
+ const callbackContext_ = callbackContext;
559
+ if (registration.type === "asyncIterator") {
560
+ bridgedCustomFunctions[name] = {
561
+ type: "asyncIterator",
562
+ fn: (...args) => {
563
+ const startCallbackId = callbackContext_.custom.get(`${name}:start`);
564
+ const nextCallbackId = callbackContext_.custom.get(`${name}:next`);
565
+ const returnCallbackId = callbackContext_.custom.get(`${name}:return`);
566
+ async function* bridgedIterator() {
567
+ const conn = callbackContext_.connection;
568
+ if (!conn || startCallbackId === undefined) {
569
+ throw new Error(`AsyncIterator callback '${name}' not available`);
570
+ }
571
+ const startResult = await invokeClientCallback(conn, startCallbackId, args);
572
+ const iteratorId = startResult.iteratorId;
573
+ try {
574
+ while (true) {
575
+ const nextConn = callbackContext_.connection;
576
+ if (!nextConn || nextCallbackId === undefined) {
577
+ throw new Error(`AsyncIterator callback '${name}' not available`);
578
+ }
579
+ const nextResult = await invokeClientCallback(nextConn, nextCallbackId, [iteratorId]);
580
+ if (nextResult.done)
581
+ return nextResult.value;
582
+ yield nextResult.value;
583
+ }
584
+ } finally {
585
+ const retConn = callbackContext_.connection;
586
+ if (retConn && returnCallbackId !== undefined) {
587
+ await invokeClientCallback(retConn, returnCallbackId, [iteratorId]).catch(() => {});
588
+ }
589
+ }
590
+ }
591
+ return bridgedIterator();
592
+ }
593
+ };
594
+ } else {
595
+ bridgedCustomFunctions[name] = {
596
+ type: registration.type,
597
+ fn: async (...args) => {
598
+ const conn = callbackContext_.connection;
599
+ const cbId = callbackContext_.custom.get(name);
600
+ if (!conn || cbId === undefined) {
601
+ throw new Error(`Custom function callback '${name}' not available`);
602
+ }
603
+ return invokeClientCallback(conn, cbId, args);
604
+ }
605
+ };
606
+ }
607
+ }
608
+ }
609
+ let moduleLoader;
610
+ if (moduleLoaderCallback) {
611
+ moduleLoader = async (specifier, importer) => {
612
+ const conn = callbackContext.connection;
613
+ const cbId = callbackContext.moduleLoader;
614
+ if (!conn || cbId === undefined) {
615
+ throw new Error("Module loader callback not available");
616
+ }
617
+ return invokeClientCallback(conn, cbId, [specifier, importer]);
618
+ };
619
+ }
620
+ let testEnvironment;
621
+ if (message.options.testEnvironment) {
622
+ const testEnvOption = message.options.testEnvironment;
623
+ const testEnvOptions = typeof testEnvOption === "object" ? testEnvOption : undefined;
624
+ testEnvironment = {
625
+ onEvent: testEnvOptions?.callbacks?.onEvent ? (event) => {
626
+ const conn = callbackContext.connection;
627
+ const callbackId = callbackContext.testEnvironmentOnEvent;
628
+ if (!conn || callbackId === undefined) {
629
+ return;
630
+ }
631
+ const promise = invokeClientCallback(conn, callbackId, [JSON.stringify(event)]).catch(() => {});
632
+ instance.runtime?.pendingCallbacks?.push(promise);
633
+ } : undefined,
634
+ testTimeout: testEnvOptions?.testTimeout
635
+ };
636
+ if (testEnvOptions?.callbacks?.onEvent) {
637
+ instance.callbacks.set(testEnvOptions.callbacks.onEvent.callbackId, {
638
+ ...testEnvOptions.callbacks.onEvent,
639
+ name: "testEnvironment.onEvent"
640
+ });
641
+ }
642
+ }
643
+ let playwrightOptions;
644
+ const playwrightCallbacks = message.options.callbacks?.playwright;
645
+ if (playwrightCallbacks) {
646
+ playwrightOptions = {
647
+ handler: async (op) => {
648
+ const conn = callbackContext.connection;
649
+ const callbackId = callbackContext.playwright.handlerCallbackId;
650
+ if (!conn || callbackId === undefined) {
651
+ return {
652
+ ok: false,
653
+ error: {
654
+ name: "Error",
655
+ message: "Playwright handler callback not available"
656
+ }
657
+ };
658
+ }
659
+ try {
660
+ const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)]);
661
+ return JSON.parse(resultJson);
662
+ } catch (err) {
663
+ const error = err;
664
+ return { ok: false, error: { name: error.name, message: error.message } };
665
+ }
666
+ },
667
+ console: playwrightCallbacks.console,
668
+ onEvent: (event) => {
669
+ const conn = callbackContext.connection;
670
+ if (!conn) {
671
+ return;
672
+ }
673
+ if (event.type === "browserConsoleLog" && callbackContext.playwright.onBrowserConsoleLogCallbackId !== undefined) {
674
+ const promise = invokeClientCallback(conn, callbackContext.playwright.onBrowserConsoleLogCallbackId, [{ level: event.level, stdout: event.stdout, timestamp: event.timestamp }]).catch(() => {});
675
+ instance.runtime?.pendingCallbacks?.push(promise);
676
+ } else if (event.type === "networkRequest" && callbackContext.playwright.onNetworkRequestCallbackId !== undefined) {
677
+ const promise = invokeClientCallback(conn, callbackContext.playwright.onNetworkRequestCallbackId, [event]).catch(() => {});
678
+ instance.runtime?.pendingCallbacks?.push(promise);
679
+ } else if (event.type === "networkResponse" && callbackContext.playwright.onNetworkResponseCallbackId !== undefined) {
680
+ const promise = invokeClientCallback(conn, callbackContext.playwright.onNetworkResponseCallbackId, [event]).catch(() => {});
681
+ instance.runtime?.pendingCallbacks?.push(promise);
682
+ }
683
+ }
684
+ };
685
+ }
686
+ const runtime = await createRuntime({
387
687
  memoryLimitMB: message.options.memoryLimitMB ?? state.options.defaultMemoryLimitMB,
388
688
  cwd: message.options.cwd,
389
689
  console: {
@@ -393,28 +693,31 @@ async function handleCreateRuntime(message, connection, state) {
393
693
  if (!conn || callbackId === undefined)
394
694
  return;
395
695
  const promise = invokeClientCallback(conn, callbackId, [entry]).catch(() => {});
396
- pendingCallbacks.push(promise);
696
+ runtime.pendingCallbacks.push(promise);
397
697
  }
398
698
  },
399
- fetch: {
400
- onFetch: async (request) => {
401
- const conn = callbackContext.connection;
402
- const callbackId = callbackContext.fetch;
403
- if (!conn || callbackId === undefined) {
404
- throw new Error("Fetch callback not available");
405
- }
406
- const serialized = await serializeRequest(request);
407
- const result = await invokeClientCallback(conn, callbackId, [serialized], 60000);
408
- if (result && typeof result === "object" && result.__streamingResponse) {
409
- const response = result.response;
410
- response.__isCallbackStream = true;
411
- return response;
412
- }
413
- return deserializeResponse(result);
699
+ fetch: async (url, init) => {
700
+ const conn = callbackContext.connection;
701
+ const callbackId = callbackContext.fetch;
702
+ if (!conn || callbackId === undefined) {
703
+ throw new Error("Fetch callback not available");
704
+ }
705
+ const serialized = {
706
+ url,
707
+ method: init.method,
708
+ headers: init.headers,
709
+ body: init.rawBody
710
+ };
711
+ const result = await invokeClientCallback(conn, callbackId, [serialized]);
712
+ if (result && typeof result === "object" && result.__streamingResponse) {
713
+ const response = result.response;
714
+ response.__isCallbackStream = true;
715
+ return response;
414
716
  }
717
+ return deserializeResponse(result);
415
718
  },
416
719
  fs: {
417
- getDirectory: async (path2) => {
720
+ getDirectory: async (dirPath) => {
418
721
  const conn = callbackContext.connection;
419
722
  if (!conn) {
420
723
  throw new Error("FS callbacks not available");
@@ -423,35 +726,17 @@ async function handleCreateRuntime(message, connection, state) {
423
726
  connection: conn,
424
727
  callbackContext,
425
728
  invokeClientCallback,
426
- basePath: path2
729
+ basePath: dirPath
427
730
  });
428
731
  }
429
- }
732
+ },
733
+ moduleLoader,
734
+ customFunctions: bridgedCustomFunctions,
735
+ customFunctionsMarshalOptions: customFnMarshalOptions,
736
+ testEnvironment,
737
+ playwright: playwrightOptions
430
738
  });
431
- const instance = {
432
- isolateId,
433
- runtime,
434
- ownerConnection: connection.socket,
435
- callbacks: new Map,
436
- createdAt: Date.now(),
437
- lastActivity: Date.now(),
438
- pendingCallbacks,
439
- returnedCallbacks: new Map,
440
- returnedPromises: new Map,
441
- returnedIterators: new Map,
442
- nextLocalCallbackId: 1e6,
443
- namespaceId,
444
- isDisposed: false,
445
- callbackContext
446
- };
447
- if (moduleLoaderCallback) {
448
- instance.moduleLoaderCallbackId = moduleLoaderCallback.callbackId;
449
- instance.moduleCache = new Map;
450
- instance.moduleToFilename = new Map;
451
- }
452
- if (customCallbacks) {
453
- await setupCustomFunctions(runtime.context, customCallbacks, connection, instance);
454
- }
739
+ instance.runtime = runtime;
455
740
  if (consoleCallbacks?.onEntry) {
456
741
  instance.callbacks.set(consoleCallbacks.onEntry.callbackId, {
457
742
  ...consoleCallbacks.onEntry,
@@ -478,52 +763,33 @@ async function handleCreateRuntime(message, connection, state) {
478
763
  }
479
764
  }
480
765
  }
481
- if (message.options.testEnvironment) {
482
- const testEnvOption = message.options.testEnvironment;
483
- const testEnvOptions = typeof testEnvOption === "object" ? testEnvOption : undefined;
484
- const onEventCallback = testEnvOptions?.callbacks?.onEvent;
485
- await setupTestEnvironment(runtime.context, {
486
- onEvent: onEventCallback ? (event) => {
487
- const promise = invokeClientCallback(connection, onEventCallback.callbackId, [JSON.stringify(event)]).catch(() => {});
488
- pendingCallbacks.push(promise);
489
- } : undefined,
490
- testTimeout: testEnvOptions?.testTimeout
766
+ if (playwrightCallbacks) {
767
+ instance.callbacks.set(playwrightCallbacks.handlerCallbackId, {
768
+ callbackId: playwrightCallbacks.handlerCallbackId,
769
+ name: "playwright.handler",
770
+ type: "async"
491
771
  });
492
- instance.testEnvironmentEnabled = true;
493
- if (onEventCallback) {
494
- instance.callbacks.set(onEventCallback.callbackId, {
495
- ...onEventCallback,
496
- name: "testEnvironment.onEvent"
772
+ if (playwrightCallbacks.onBrowserConsoleLogCallbackId !== undefined) {
773
+ instance.callbacks.set(playwrightCallbacks.onBrowserConsoleLogCallbackId, {
774
+ callbackId: playwrightCallbacks.onBrowserConsoleLogCallbackId,
775
+ name: "playwright.onBrowserConsoleLog",
776
+ type: "sync"
777
+ });
778
+ }
779
+ if (playwrightCallbacks.onNetworkRequestCallbackId !== undefined) {
780
+ instance.callbacks.set(playwrightCallbacks.onNetworkRequestCallbackId, {
781
+ callbackId: playwrightCallbacks.onNetworkRequestCallbackId,
782
+ name: "playwright.onNetworkRequest",
783
+ type: "sync"
784
+ });
785
+ }
786
+ if (playwrightCallbacks.onNetworkResponseCallbackId !== undefined) {
787
+ instance.callbacks.set(playwrightCallbacks.onNetworkResponseCallbackId, {
788
+ callbackId: playwrightCallbacks.onNetworkResponseCallbackId,
789
+ name: "playwright.onNetworkResponse",
790
+ type: "sync"
497
791
  });
498
792
  }
499
- }
500
- const playwrightCallbacks = message.options.callbacks?.playwright;
501
- if (playwrightCallbacks) {
502
- const handler = async (op) => {
503
- try {
504
- const resultJson = await invokeClientCallback(connection, playwrightCallbacks.handlerCallbackId, [JSON.stringify(op)]);
505
- return JSON.parse(resultJson);
506
- } catch (err) {
507
- const error = err;
508
- return { ok: false, error: { name: error.name, message: error.message } };
509
- }
510
- };
511
- instance.playwrightHandle = await setupPlaywright(runtime.context, {
512
- handler,
513
- console: playwrightCallbacks.console,
514
- onEvent: (event) => {
515
- if (event.type === "browserConsoleLog" && playwrightCallbacks.onBrowserConsoleLogCallbackId) {
516
- const promise = invokeClientCallback(connection, playwrightCallbacks.onBrowserConsoleLogCallbackId, [{ level: event.level, stdout: event.stdout, timestamp: event.timestamp }]).catch(() => {});
517
- pendingCallbacks.push(promise);
518
- } else if (event.type === "networkRequest" && playwrightCallbacks.onNetworkRequestCallbackId) {
519
- const promise = invokeClientCallback(connection, playwrightCallbacks.onNetworkRequestCallbackId, [event]).catch(() => {});
520
- pendingCallbacks.push(promise);
521
- } else if (event.type === "networkResponse" && playwrightCallbacks.onNetworkResponseCallbackId) {
522
- const promise = invokeClientCallback(connection, playwrightCallbacks.onNetworkResponseCallbackId, [event]).catch(() => {});
523
- pendingCallbacks.push(promise);
524
- }
525
- }
526
- });
527
793
  }
528
794
  state.isolates.set(isolateId, instance);
529
795
  connection.isolates.add(isolateId);
@@ -531,30 +797,99 @@ async function handleCreateRuntime(message, connection, state) {
531
797
  if (namespaceId != null) {
532
798
  state.namespacedRuntimes.set(namespaceId, instance);
533
799
  }
534
- instance.runtime.fetch.onWebSocketCommand((cmd) => {
800
+ runtime.fetch.onWebSocketCommand((cmd) => {
801
+ const targetConnection = callbackContext.connection;
802
+ if (!targetConnection) {
803
+ return;
804
+ }
535
805
  let data;
536
806
  if (cmd.data instanceof ArrayBuffer) {
537
807
  data = new Uint8Array(cmd.data);
538
808
  } else {
539
809
  data = cmd.data;
540
810
  }
541
- const wsCommandMsg = {
542
- type: MessageType.WS_COMMAND,
811
+ const payload = {
812
+ type: cmd.type,
813
+ connectionId: cmd.connectionId,
814
+ data,
815
+ code: cmd.code,
816
+ reason: cmd.reason
817
+ };
818
+ sendMessage(targetConnection.socket, {
819
+ type: MessageType.ISOLATE_EVENT,
543
820
  isolateId,
544
- command: {
545
- type: cmd.type,
546
- connectionId: cmd.connectionId,
547
- data,
821
+ event: IsolateEvents.WS_COMMAND,
822
+ payload
823
+ });
824
+ });
825
+ runtime.fetch.onClientWebSocketCommand((cmd) => {
826
+ const targetConnection = callbackContext.connection;
827
+ if (!targetConnection) {
828
+ return;
829
+ }
830
+ let data;
831
+ if (cmd.data instanceof ArrayBuffer) {
832
+ data = new Uint8Array(cmd.data);
833
+ } else {
834
+ data = cmd.data;
835
+ }
836
+ if (cmd.type === "connect") {
837
+ const payload = {
838
+ socketId: cmd.socketId,
839
+ url: cmd.url,
840
+ protocols: cmd.protocols
841
+ };
842
+ sendMessage(targetConnection.socket, {
843
+ type: MessageType.ISOLATE_EVENT,
844
+ isolateId,
845
+ event: IsolateEvents.WS_CLIENT_CONNECT,
846
+ payload
847
+ });
848
+ } else if (cmd.type === "send") {
849
+ const payload = {
850
+ socketId: cmd.socketId,
851
+ data
852
+ };
853
+ sendMessage(targetConnection.socket, {
854
+ type: MessageType.ISOLATE_EVENT,
855
+ isolateId,
856
+ event: IsolateEvents.WS_CLIENT_SEND,
857
+ payload
858
+ });
859
+ } else if (cmd.type === "close") {
860
+ const payload = {
861
+ socketId: cmd.socketId,
548
862
  code: cmd.code,
549
863
  reason: cmd.reason
550
- }
551
- };
552
- sendMessage(connection.socket, wsCommandMsg);
864
+ };
865
+ sendMessage(targetConnection.socket, {
866
+ type: MessageType.ISOLATE_EVENT,
867
+ isolateId,
868
+ event: IsolateEvents.WS_CLIENT_CLOSE,
869
+ payload
870
+ });
871
+ }
872
+ });
873
+ runtime.fetch.onEvent((eventName, payload) => {
874
+ const targetConnection = callbackContext.connection;
875
+ if (!targetConnection) {
876
+ return;
877
+ }
878
+ sendMessage(targetConnection.socket, {
879
+ type: MessageType.ISOLATE_EVENT,
880
+ isolateId,
881
+ event: eventName,
882
+ payload
883
+ });
553
884
  });
554
885
  sendOk(connection.socket, message.requestId, { isolateId, reused: false });
555
886
  } catch (err) {
556
887
  const error = err;
557
888
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
889
+ } finally {
890
+ if (namespaceCreationLocked && namespaceId != null) {
891
+ state.namespacedCreatesInFlight.delete(namespaceId);
892
+ }
558
893
  }
559
894
  }
560
895
  async function handleDisposeRuntime(message, connection, state) {
@@ -570,17 +905,20 @@ async function handleDisposeRuntime(message, connection, state) {
570
905
  try {
571
906
  connection.isolates.delete(message.isolateId);
572
907
  if (instance.namespaceId != null) {
573
- softDeleteRuntime(instance, state);
574
- } else {
575
- if (instance.playwrightHandle) {
576
- instance.playwrightHandle.dispose();
908
+ if (instance.isPoisoned) {
909
+ await hardDeleteRuntime(instance, state);
910
+ } else {
911
+ softDeleteRuntime(instance, state);
577
912
  }
578
- instance.runtime.dispose();
579
- state.isolates.delete(message.isolateId);
913
+ } else {
914
+ await hardDeleteRuntime(instance, state);
580
915
  }
581
916
  sendOk(connection.socket, message.requestId);
582
917
  } catch (err) {
583
918
  const error = err;
919
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
920
+ instance.isPoisoned = true;
921
+ }
584
922
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
585
923
  }
586
924
  }
@@ -592,47 +930,16 @@ async function handleEval(message, connection, state) {
592
930
  }
593
931
  instance.lastActivity = Date.now();
594
932
  try {
595
- const filename = normalizeEntryFilename(message.filename);
596
- const transformed = await transformEntryCode(message.code, filename);
597
- if (transformed.sourceMap) {
598
- if (!instance.sourceMaps) {
599
- instance.sourceMaps = new Map;
600
- }
601
- instance.sourceMaps.set(filename, transformed.sourceMap);
602
- }
603
- const mod = await instance.runtime.isolate.compileModule(transformed.code, {
604
- filename
933
+ await instance.runtime.eval(message.code, {
934
+ filename: message.filename
605
935
  });
606
- if (instance.moduleLoaderCallbackId) {
607
- instance.moduleToFilename?.set(mod, filename);
608
- const resolver = createModuleResolver(instance, connection);
609
- await mod.instantiate(instance.runtime.context, resolver);
610
- } else {
611
- await mod.instantiate(instance.runtime.context, (specifier) => {
612
- throw new Error(`No module loader registered. Cannot import: ${specifier}`);
613
- });
614
- }
615
- await mod.evaluate();
616
- const ns = mod.namespace;
617
- const runRef = await ns.get("default", { reference: true });
618
- try {
619
- await runRef.apply(undefined, [], {
620
- result: { promise: true },
621
- ...message.maxExecutionMs ? { timeout: message.maxExecutionMs } : {}
622
- });
623
- } finally {
624
- runRef.release();
625
- }
626
- await Promise.all(instance.pendingCallbacks);
627
- instance.pendingCallbacks.length = 0;
628
936
  sendOk(connection.socket, message.requestId, { value: undefined });
629
937
  } catch (err) {
630
938
  const error = err;
631
- if (error.stack && instance.sourceMaps?.size) {
632
- error.stack = mapErrorStack(error.stack, instance.sourceMaps);
939
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
940
+ instance.isPoisoned = true;
633
941
  }
634
- const isTimeoutError = error.message?.includes("Script execution timed out");
635
- sendError(connection.socket, message.requestId, isTimeoutError ? ErrorCode.ISOLATE_TIMEOUT : ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
942
+ sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
636
943
  }
637
944
  }
638
945
  async function handleDispatchRequest(message, connection, state) {
@@ -740,6 +1047,39 @@ async function handleWsClose(message, connection, state) {
740
1047
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
741
1048
  }
742
1049
  }
1050
+ function handleClientEvent(message, connection, state) {
1051
+ const instance = state.isolates.get(message.isolateId);
1052
+ if (!instance)
1053
+ return;
1054
+ instance.lastActivity = Date.now();
1055
+ switch (message.event) {
1056
+ case ClientEvents.WS_CLIENT_OPENED: {
1057
+ const payload = message.payload;
1058
+ instance.runtime.fetch.dispatchClientWebSocketOpen(payload.socketId, payload.protocol, payload.extensions);
1059
+ break;
1060
+ }
1061
+ case ClientEvents.WS_CLIENT_MESSAGE: {
1062
+ const payload = message.payload;
1063
+ const data = payload.data instanceof Uint8Array ? payload.data.buffer.slice(payload.data.byteOffset, payload.data.byteOffset + payload.data.byteLength) : payload.data;
1064
+ instance.runtime.fetch.dispatchClientWebSocketMessage(payload.socketId, data);
1065
+ break;
1066
+ }
1067
+ case ClientEvents.WS_CLIENT_CLOSED: {
1068
+ const payload = message.payload;
1069
+ instance.runtime.fetch.dispatchClientWebSocketClose(payload.socketId, payload.code, payload.reason, payload.wasClean);
1070
+ break;
1071
+ }
1072
+ case ClientEvents.WS_CLIENT_ERROR: {
1073
+ const payload = message.payload;
1074
+ instance.runtime.fetch.dispatchClientWebSocketError(payload.socketId);
1075
+ break;
1076
+ }
1077
+ default: {
1078
+ instance.runtime.fetch.dispatchEvent(message.event, message.payload);
1079
+ break;
1080
+ }
1081
+ }
1082
+ }
743
1083
  async function handleFetchGetUpgradeRequest(message, connection, state) {
744
1084
  const instance = state.isolates.get(message.isolateId);
745
1085
  if (!instance) {
@@ -882,9 +1222,6 @@ function handleCallbackResponse(message, connection) {
882
1222
  return;
883
1223
  }
884
1224
  connection.pendingCallbacks.delete(message.requestId);
885
- if (pending.timeoutId) {
886
- clearTimeout(pending.timeoutId);
887
- }
888
1225
  if (message.error) {
889
1226
  const error = new Error(message.error.message);
890
1227
  error.name = message.error.name;
@@ -896,17 +1233,12 @@ function handleCallbackResponse(message, connection) {
896
1233
  pending.resolve(message.result);
897
1234
  }
898
1235
  }
899
- async function invokeClientCallback(connection, callbackId, args, timeout = 1e4) {
1236
+ async function invokeClientCallback(connection, callbackId, args) {
900
1237
  const requestId = connection.nextCallbackId++;
901
1238
  return new Promise((resolve, reject) => {
902
- const timeoutId = setTimeout(() => {
903
- connection.pendingCallbacks.delete(requestId);
904
- reject(new Error("Callback timeout"));
905
- }, timeout);
906
1239
  const pending = {
907
1240
  resolve,
908
- reject,
909
- timeoutId
1241
+ reject
910
1242
  };
911
1243
  connection.pendingCallbacks.set(requestId, pending);
912
1244
  const invoke = {
@@ -918,530 +1250,6 @@ async function invokeClientCallback(connection, callbackId, args, timeout = 1e4)
918
1250
  sendMessage(connection.socket, invoke);
919
1251
  });
920
1252
  }
921
- var ISOLATE_MARSHAL_CODE = `
922
- (function() {
923
- // Marshal a value (JavaScript → Ref)
924
- function marshalForHost(value, depth = 0) {
925
- if (depth > 100) throw new Error('Maximum marshalling depth exceeded');
926
-
927
- if (value === null) return null;
928
- if (value === undefined) return { __type: 'UndefinedRef' };
929
-
930
- const type = typeof value;
931
- if (type === 'string' || type === 'number' || type === 'boolean') return value;
932
- if (type === 'bigint') return { __type: 'BigIntRef', value: value.toString() };
933
- if (type === 'function') throw new Error('Cannot marshal functions from isolate');
934
- if (type === 'symbol') throw new Error('Cannot marshal Symbol values');
935
-
936
- if (type === 'object') {
937
- if (value instanceof Date) {
938
- return { __type: 'DateRef', timestamp: value.getTime() };
939
- }
940
- if (value instanceof RegExp) {
941
- return { __type: 'RegExpRef', source: value.source, flags: value.flags };
942
- }
943
- if (value instanceof URL) {
944
- return { __type: 'URLRef', href: value.href };
945
- }
946
- if (typeof Headers !== 'undefined' && value instanceof Headers) {
947
- const pairs = [];
948
- value.forEach((v, k) => pairs.push([k, v]));
949
- return { __type: 'HeadersRef', pairs };
950
- }
951
- if (value instanceof Uint8Array) {
952
- return { __type: 'Uint8ArrayRef', data: Array.from(value) };
953
- }
954
- if (value instanceof ArrayBuffer) {
955
- return { __type: 'Uint8ArrayRef', data: Array.from(new Uint8Array(value)) };
956
- }
957
- if (typeof Request !== 'undefined' && value instanceof Request) {
958
- throw new Error('Cannot marshal Request from isolate. Use fetch callback instead.');
959
- }
960
- if (typeof Response !== 'undefined' && value instanceof Response) {
961
- throw new Error('Cannot marshal Response from isolate. Return plain objects instead.');
962
- }
963
- if (typeof File !== 'undefined' && value instanceof File) {
964
- throw new Error('Cannot marshal File from isolate.');
965
- }
966
- if (typeof Blob !== 'undefined' && value instanceof Blob) {
967
- throw new Error('Cannot marshal Blob from isolate.');
968
- }
969
- if (typeof FormData !== 'undefined' && value instanceof FormData) {
970
- throw new Error('Cannot marshal FormData from isolate.');
971
- }
972
- if (Array.isArray(value)) {
973
- return value.map(v => marshalForHost(v, depth + 1));
974
- }
975
- // Plain object
976
- const result = {};
977
- for (const key of Object.keys(value)) {
978
- result[key] = marshalForHost(value[key], depth + 1);
979
- }
980
- return result;
981
- }
982
- return value;
983
- }
984
-
985
- // Unmarshal a value (Ref → JavaScript)
986
- function unmarshalFromHost(value, depth = 0) {
987
- if (depth > 100) throw new Error('Maximum unmarshalling depth exceeded');
988
-
989
- if (value === null) return null;
990
- if (typeof value !== 'object') return value;
991
-
992
- if (value.__type) {
993
- switch (value.__type) {
994
- case 'UndefinedRef': return undefined;
995
- case 'DateRef': return new Date(value.timestamp);
996
- case 'RegExpRef': return new RegExp(value.source, value.flags);
997
- case 'BigIntRef': return BigInt(value.value);
998
- case 'URLRef': return new URL(value.href);
999
- case 'HeadersRef': return new Headers(value.pairs);
1000
- case 'Uint8ArrayRef': return new Uint8Array(value.data);
1001
- case 'RequestRef': {
1002
- const init = {
1003
- method: value.method,
1004
- headers: value.headers,
1005
- body: value.body ? new Uint8Array(value.body) : null,
1006
- };
1007
- if (value.mode) init.mode = value.mode;
1008
- if (value.credentials) init.credentials = value.credentials;
1009
- if (value.cache) init.cache = value.cache;
1010
- if (value.redirect) init.redirect = value.redirect;
1011
- if (value.referrer) init.referrer = value.referrer;
1012
- if (value.referrerPolicy) init.referrerPolicy = value.referrerPolicy;
1013
- if (value.integrity) init.integrity = value.integrity;
1014
- return new Request(value.url, init);
1015
- }
1016
- case 'ResponseRef': {
1017
- return new Response(value.body ? new Uint8Array(value.body) : null, {
1018
- status: value.status,
1019
- statusText: value.statusText,
1020
- headers: value.headers,
1021
- });
1022
- }
1023
- case 'FileRef': {
1024
- if (!value.name) {
1025
- return new Blob([new Uint8Array(value.data)], { type: value.type });
1026
- }
1027
- return new File([new Uint8Array(value.data)], value.name, {
1028
- type: value.type,
1029
- lastModified: value.lastModified,
1030
- });
1031
- }
1032
- case 'FormDataRef': {
1033
- const fd = new FormData();
1034
- for (const [key, entry] of value.entries) {
1035
- if (typeof entry === 'string') {
1036
- fd.append(key, entry);
1037
- } else {
1038
- const file = unmarshalFromHost(entry, depth + 1);
1039
- fd.append(key, file);
1040
- }
1041
- }
1042
- return fd;
1043
- }
1044
- case 'CallbackRef': {
1045
- // Create a proxy function that invokes the callback
1046
- const callbackId = value.callbackId;
1047
- return function(...args) {
1048
- const argsJson = JSON.stringify(marshalForHost(args));
1049
- const resultJson = __customFn_invoke.applySyncPromise(undefined, [callbackId, argsJson]);
1050
- const result = JSON.parse(resultJson);
1051
- if (result.ok) {
1052
- return unmarshalFromHost(result.value);
1053
- } else {
1054
- const error = new Error(result.error.message);
1055
- error.name = result.error.name;
1056
- throw error;
1057
- }
1058
- };
1059
- }
1060
- case 'PromiseRef': {
1061
- // Create a proxy Promise that resolves via callback
1062
- const promiseId = value.promiseId;
1063
- return new Promise((resolve, reject) => {
1064
- try {
1065
- const argsJson = JSON.stringify([promiseId]);
1066
- const resultJson = __customFn_invoke.applySyncPromise(undefined, [value.__resolveCallbackId, argsJson]);
1067
- const result = JSON.parse(resultJson);
1068
- if (result.ok) {
1069
- resolve(unmarshalFromHost(result.value));
1070
- } else {
1071
- reject(new Error(result.error.message));
1072
- }
1073
- } catch (e) {
1074
- reject(e);
1075
- }
1076
- });
1077
- }
1078
- case 'AsyncIteratorRef': {
1079
- const iteratorId = value.iteratorId;
1080
- const nextCallbackId = value.__nextCallbackId;
1081
- const returnCallbackId = value.__returnCallbackId;
1082
- return {
1083
- [Symbol.asyncIterator]() { return this; },
1084
- async next() {
1085
- const argsJson = JSON.stringify([iteratorId]);
1086
- const resultJson = __customFn_invoke.applySyncPromise(undefined, [nextCallbackId, argsJson]);
1087
- const result = JSON.parse(resultJson);
1088
- if (!result.ok) {
1089
- const error = new Error(result.error.message);
1090
- error.name = result.error.name;
1091
- throw error;
1092
- }
1093
- return {
1094
- done: result.value.done,
1095
- value: unmarshalFromHost(result.value.value)
1096
- };
1097
- },
1098
- async return(v) {
1099
- const argsJson = JSON.stringify([iteratorId, marshalForHost(v)]);
1100
- const resultJson = __customFn_invoke.applySyncPromise(undefined, [returnCallbackId, argsJson]);
1101
- const result = JSON.parse(resultJson);
1102
- return { done: true, value: result.ok ? unmarshalFromHost(result.value) : undefined };
1103
- }
1104
- };
1105
- }
1106
- default:
1107
- // Unknown ref type, return as-is
1108
- break;
1109
- }
1110
- }
1111
-
1112
- if (Array.isArray(value)) {
1113
- return value.map(v => unmarshalFromHost(v, depth + 1));
1114
- }
1115
-
1116
- // Plain object - recursively unmarshal
1117
- const result = {};
1118
- for (const key of Object.keys(value)) {
1119
- result[key] = unmarshalFromHost(value[key], depth + 1);
1120
- }
1121
- return result;
1122
- }
1123
-
1124
- globalThis.__marshalForHost = marshalForHost;
1125
- globalThis.__unmarshalFromHost = unmarshalFromHost;
1126
- })();
1127
- `;
1128
- var LOCAL_CALLBACK_THRESHOLD = 1e6;
1129
- function isPromiseRef(value) {
1130
- return typeof value === "object" && value !== null && value.__type === "PromiseRef";
1131
- }
1132
- function isAsyncIteratorRef(value) {
1133
- return typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
1134
- }
1135
- function isLocalCallbackId(callbackId) {
1136
- return callbackId >= LOCAL_CALLBACK_THRESHOLD;
1137
- }
1138
- async function setupCustomFunctions(context, customCallbacks, connection, instance) {
1139
- const global = context.global;
1140
- function createMarshalContext() {
1141
- return {
1142
- registerCallback: (fn) => {
1143
- const callbackId = instance.nextLocalCallbackId++;
1144
- instance.returnedCallbacks.set(callbackId, fn);
1145
- return callbackId;
1146
- },
1147
- registerPromise: (promise) => {
1148
- const promiseId = instance.nextLocalCallbackId++;
1149
- instance.returnedPromises.set(promiseId, promise);
1150
- return promiseId;
1151
- },
1152
- registerIterator: (iterator) => {
1153
- const iteratorId = instance.nextLocalCallbackId++;
1154
- instance.returnedIterators.set(iteratorId, iterator);
1155
- return iteratorId;
1156
- }
1157
- };
1158
- }
1159
- function addCallbackIdsToRefs(value) {
1160
- if (value === null || typeof value !== "object") {
1161
- return value;
1162
- }
1163
- if (isPromiseRef(value)) {
1164
- if ("__resolveCallbackId" in value) {
1165
- return value;
1166
- }
1167
- const resolveCallbackId = instance.nextLocalCallbackId++;
1168
- instance.returnedCallbacks.set(resolveCallbackId, async (promiseId) => {
1169
- const promise = instance.returnedPromises.get(promiseId);
1170
- if (!promise) {
1171
- throw new Error(`Promise ${promiseId} not found`);
1172
- }
1173
- const result2 = await promise;
1174
- instance.returnedPromises.delete(promiseId);
1175
- const ctx = createMarshalContext();
1176
- const marshalled = await marshalValue(result2, ctx);
1177
- return addCallbackIdsToRefs(marshalled);
1178
- });
1179
- return {
1180
- ...value,
1181
- __resolveCallbackId: resolveCallbackId
1182
- };
1183
- }
1184
- if (isAsyncIteratorRef(value)) {
1185
- if ("__nextCallbackId" in value) {
1186
- return value;
1187
- }
1188
- const nextCallbackId = instance.nextLocalCallbackId++;
1189
- instance.returnedCallbacks.set(nextCallbackId, async (iteratorId) => {
1190
- const iterator = instance.returnedIterators.get(iteratorId);
1191
- if (!iterator) {
1192
- throw new Error(`Iterator ${iteratorId} not found`);
1193
- }
1194
- const result2 = await iterator.next();
1195
- if (result2.done) {
1196
- instance.returnedIterators.delete(iteratorId);
1197
- }
1198
- const ctx = createMarshalContext();
1199
- const marshalledValue = await marshalValue(result2.value, ctx);
1200
- return {
1201
- done: result2.done,
1202
- value: addCallbackIdsToRefs(marshalledValue)
1203
- };
1204
- });
1205
- const returnCallbackId = instance.nextLocalCallbackId++;
1206
- instance.returnedCallbacks.set(returnCallbackId, async (iteratorId, returnValue) => {
1207
- const iterator = instance.returnedIterators.get(iteratorId);
1208
- instance.returnedIterators.delete(iteratorId);
1209
- if (!iterator || !iterator.return) {
1210
- return { done: true, value: undefined };
1211
- }
1212
- const result2 = await iterator.return(returnValue);
1213
- const ctx = createMarshalContext();
1214
- const marshalledValue = await marshalValue(result2.value, ctx);
1215
- return {
1216
- done: true,
1217
- value: addCallbackIdsToRefs(marshalledValue)
1218
- };
1219
- });
1220
- return {
1221
- ...value,
1222
- __nextCallbackId: nextCallbackId,
1223
- __returnCallbackId: returnCallbackId
1224
- };
1225
- }
1226
- if (Array.isArray(value)) {
1227
- return value.map((item) => addCallbackIdsToRefs(item));
1228
- }
1229
- const result = {};
1230
- for (const key of Object.keys(value)) {
1231
- result[key] = addCallbackIdsToRefs(value[key]);
1232
- }
1233
- return result;
1234
- }
1235
- const invokeCallbackRef = new ivm.Reference(async (callbackId, argsJson) => {
1236
- const marshalledArgs = JSON.parse(argsJson);
1237
- const args = unmarshalValue(marshalledArgs);
1238
- try {
1239
- let result;
1240
- if (isLocalCallbackId(callbackId)) {
1241
- const callback = instance.returnedCallbacks.get(callbackId);
1242
- if (!callback) {
1243
- throw new Error(`Local callback ${callbackId} not found`);
1244
- }
1245
- result = await callback(...args);
1246
- } else {
1247
- const conn = instance.callbackContext?.connection || connection;
1248
- result = await invokeClientCallback(conn, callbackId, args);
1249
- }
1250
- const ctx = createMarshalContext();
1251
- const marshalledResult = await marshalValue({ ok: true, value: result }, ctx);
1252
- const processedResult = addCallbackIdsToRefs(marshalledResult);
1253
- return JSON.stringify(processedResult);
1254
- } catch (error) {
1255
- const err = error;
1256
- return JSON.stringify({
1257
- ok: false,
1258
- error: { message: err.message, name: err.name }
1259
- });
1260
- }
1261
- });
1262
- global.setSync("__customFn_invoke", invokeCallbackRef);
1263
- context.evalSync(ISOLATE_MARSHAL_CODE);
1264
- const callbackIdMap = {};
1265
- for (const [name, registration] of Object.entries(customCallbacks)) {
1266
- callbackIdMap[name] = registration.callbackId;
1267
- }
1268
- global.setSync("__customFnCallbackIds", new ivm.ExternalCopy(callbackIdMap).copyInto());
1269
- for (const [name, registration] of Object.entries(customCallbacks)) {
1270
- if (name.includes(":")) {
1271
- continue;
1272
- }
1273
- if (registration.type === "sync") {
1274
- context.evalSync(`
1275
- globalThis.${name} = function(...args) {
1276
- const callbackId = globalThis.__customFnCallbackIds["${name}"];
1277
- const argsJson = JSON.stringify(__marshalForHost(args));
1278
- const resultJson = __customFn_invoke.applySyncPromise(
1279
- undefined,
1280
- [callbackId, argsJson]
1281
- );
1282
- const result = JSON.parse(resultJson);
1283
- if (result.ok) {
1284
- return __unmarshalFromHost(result.value);
1285
- } else {
1286
- const error = new Error(result.error.message);
1287
- error.name = result.error.name;
1288
- throw error;
1289
- }
1290
- };
1291
- `);
1292
- } else if (registration.type === "asyncIterator") {
1293
- const startReg = customCallbacks[`${name}:start`];
1294
- const nextReg = customCallbacks[`${name}:next`];
1295
- const returnReg = customCallbacks[`${name}:return`];
1296
- const throwReg = customCallbacks[`${name}:throw`];
1297
- if (!startReg || !nextReg || !returnReg || !throwReg) {
1298
- throw new Error(`Missing companion callbacks for asyncIterator function "${name}"`);
1299
- }
1300
- context.evalSync(`
1301
- globalThis.${name} = function(...args) {
1302
- // Start the iterator and get the iteratorId
1303
- const startCallbackId = globalThis.__customFnCallbackIds["${name}:start"];
1304
- const argsJson = JSON.stringify(__marshalForHost(args));
1305
- const startResultJson = __customFn_invoke.applySyncPromise(
1306
- undefined,
1307
- [startCallbackId, argsJson]
1308
- );
1309
- const startResult = JSON.parse(startResultJson);
1310
- if (!startResult.ok) {
1311
- const error = new Error(startResult.error.message);
1312
- error.name = startResult.error.name;
1313
- throw error;
1314
- }
1315
- const iteratorId = __unmarshalFromHost(startResult.value).iteratorId;
1316
-
1317
- return {
1318
- [Symbol.asyncIterator]() { return this; },
1319
- async next() {
1320
- const nextCallbackId = globalThis.__customFnCallbackIds["${name}:next"];
1321
- const argsJson = JSON.stringify(__marshalForHost([iteratorId]));
1322
- const resultJson = __customFn_invoke.applySyncPromise(
1323
- undefined,
1324
- [nextCallbackId, argsJson]
1325
- );
1326
- const result = JSON.parse(resultJson);
1327
- if (!result.ok) {
1328
- const error = new Error(result.error.message);
1329
- error.name = result.error.name;
1330
- throw error;
1331
- }
1332
- const val = __unmarshalFromHost(result.value);
1333
- return { done: val.done, value: val.value };
1334
- },
1335
- async return(v) {
1336
- const returnCallbackId = globalThis.__customFnCallbackIds["${name}:return"];
1337
- const argsJson = JSON.stringify(__marshalForHost([iteratorId, v]));
1338
- const resultJson = __customFn_invoke.applySyncPromise(
1339
- undefined,
1340
- [returnCallbackId, argsJson]
1341
- );
1342
- const result = JSON.parse(resultJson);
1343
- return { done: true, value: result.ok ? __unmarshalFromHost(result.value) : undefined };
1344
- },
1345
- async throw(e) {
1346
- const throwCallbackId = globalThis.__customFnCallbackIds["${name}:throw"];
1347
- const argsJson = JSON.stringify(__marshalForHost([iteratorId, { message: e?.message, name: e?.name }]));
1348
- const resultJson = __customFn_invoke.applySyncPromise(
1349
- undefined,
1350
- [throwCallbackId, argsJson]
1351
- );
1352
- const result = JSON.parse(resultJson);
1353
- if (!result.ok) {
1354
- const error = new Error(result.error.message);
1355
- error.name = result.error.name;
1356
- throw error;
1357
- }
1358
- const val = __unmarshalFromHost(result.value);
1359
- return { done: val.done, value: val.value };
1360
- }
1361
- };
1362
- };
1363
- `);
1364
- } else if (registration.type === "async") {
1365
- context.evalSync(`
1366
- globalThis.${name} = async function(...args) {
1367
- const callbackId = globalThis.__customFnCallbackIds["${name}"];
1368
- const argsJson = JSON.stringify(__marshalForHost(args));
1369
- const resultJson = __customFn_invoke.applySyncPromise(
1370
- undefined,
1371
- [callbackId, argsJson]
1372
- );
1373
- const result = JSON.parse(resultJson);
1374
- if (result.ok) {
1375
- return __unmarshalFromHost(result.value);
1376
- } else {
1377
- const error = new Error(result.error.message);
1378
- error.name = result.error.name;
1379
- throw error;
1380
- }
1381
- };
1382
- `);
1383
- }
1384
- }
1385
- }
1386
- function createModuleResolver(instance, connection) {
1387
- return async (specifier, referrer) => {
1388
- const cached = instance.moduleCache?.get(specifier);
1389
- if (cached)
1390
- return cached;
1391
- if (!instance.moduleLoaderCallbackId) {
1392
- throw new Error(`Module not found: ${specifier}`);
1393
- }
1394
- const importerPath = instance.moduleToFilename?.get(referrer) ?? "<unknown>";
1395
- const importerResolveDir = path.posix.dirname(importerPath);
1396
- const result = await invokeClientCallback(connection, instance.moduleLoaderCallbackId, [specifier, { path: importerPath, resolveDir: importerResolveDir }]);
1397
- const { code, resolveDir } = result;
1398
- const hash = contentHash(code);
1399
- const cacheKey = `${specifier}:${hash}`;
1400
- const hashCached = instance.moduleCache?.get(cacheKey);
1401
- if (hashCached)
1402
- return hashCached;
1403
- const transformed = await transformModuleCode(code, specifier);
1404
- if (transformed.sourceMap) {
1405
- if (!instance.sourceMaps) {
1406
- instance.sourceMaps = new Map;
1407
- }
1408
- instance.sourceMaps.set(specifier, transformed.sourceMap);
1409
- }
1410
- const mod = await instance.runtime.isolate.compileModule(transformed.code, {
1411
- filename: specifier
1412
- });
1413
- const resolvedPath = path.posix.join(resolveDir, path.posix.basename(specifier));
1414
- instance.moduleToFilename?.set(mod, resolvedPath);
1415
- const resolver = createModuleResolver(instance, connection);
1416
- await mod.instantiate(instance.runtime.context, resolver);
1417
- instance.moduleCache?.set(specifier, mod);
1418
- instance.moduleCache?.set(cacheKey, mod);
1419
- return mod;
1420
- };
1421
- }
1422
- async function serializeRequest(request) {
1423
- const headers = [];
1424
- request.headers.forEach((value, key) => {
1425
- headers.push([key, value]);
1426
- });
1427
- let body = null;
1428
- if (request.body) {
1429
- body = new Uint8Array(await request.arrayBuffer());
1430
- }
1431
- return {
1432
- method: request.method,
1433
- url: request.url,
1434
- headers,
1435
- body
1436
- };
1437
- }
1438
- function deserializeResponse(data) {
1439
- return new Response(data.body, {
1440
- status: data.status,
1441
- statusText: data.statusText,
1442
- headers: data.headers
1443
- });
1444
- }
1445
1253
  function handleStreamPush(message, connection) {
1446
1254
  const receiver = connection.streamReceivers.get(message.streamId);
1447
1255
  if (!receiver) {
@@ -1569,9 +1377,6 @@ function handleCallbackStreamStart(message, connection) {
1569
1377
  const pending = connection.pendingCallbacks.get(message.requestId);
1570
1378
  if (pending) {
1571
1379
  connection.pendingCallbacks.delete(message.requestId);
1572
- if (pending.timeoutId) {
1573
- clearTimeout(pending.timeoutId);
1574
- }
1575
1380
  const response = new Response(readableStream, {
1576
1381
  status: message.metadata.status,
1577
1382
  statusText: message.metadata.statusText,
@@ -1701,28 +1506,11 @@ async function handleRunTests(message, connection, state) {
1701
1506
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1702
1507
  return;
1703
1508
  }
1704
- if (!instance.testEnvironmentEnabled) {
1705
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
1706
- return;
1707
- }
1708
1509
  instance.lastActivity = Date.now();
1709
1510
  try {
1710
1511
  const timeout = message.timeout ?? 30000;
1711
- let timeoutId;
1712
- const timeoutPromise = new Promise((_, reject) => {
1713
- timeoutId = setTimeout(() => reject(new Error("Test timeout")), timeout);
1714
- });
1715
- try {
1716
- const results = await Promise.race([
1717
- runTestsInContext(instance.runtime.context),
1718
- timeoutPromise
1719
- ]);
1720
- sendOk(connection.socket, message.requestId, results);
1721
- } finally {
1722
- if (timeoutId) {
1723
- clearTimeout(timeoutId);
1724
- }
1725
- }
1512
+ const results = await instance.runtime.testEnvironment.runTests(timeout);
1513
+ sendOk(connection.socket, message.requestId, results);
1726
1514
  } catch (err) {
1727
1515
  const error = err;
1728
1516
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
@@ -1734,13 +1522,9 @@ async function handleResetTestEnv(message, connection, state) {
1734
1522
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1735
1523
  return;
1736
1524
  }
1737
- if (!instance.testEnvironmentEnabled) {
1738
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
1739
- return;
1740
- }
1741
1525
  instance.lastActivity = Date.now();
1742
1526
  try {
1743
- await instance.runtime.context.eval("__resetTestEnvironment()", { promise: true });
1527
+ instance.runtime.testEnvironment.reset();
1744
1528
  sendOk(connection.socket, message.requestId);
1745
1529
  } catch (err) {
1746
1530
  const error = err;
@@ -1753,13 +1537,9 @@ async function handleHasTests(message, connection, state) {
1753
1537
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1754
1538
  return;
1755
1539
  }
1756
- if (!instance.testEnvironmentEnabled) {
1757
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
1758
- return;
1759
- }
1760
1540
  instance.lastActivity = Date.now();
1761
1541
  try {
1762
- const result = hasTestsInContext(instance.runtime.context);
1542
+ const result = instance.runtime.testEnvironment.hasTests();
1763
1543
  sendOk(connection.socket, message.requestId, result);
1764
1544
  } catch (err) {
1765
1545
  const error = err;
@@ -1772,42 +1552,24 @@ async function handleGetTestCount(message, connection, state) {
1772
1552
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1773
1553
  return;
1774
1554
  }
1775
- if (!instance.testEnvironmentEnabled) {
1776
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
1777
- return;
1778
- }
1779
1555
  instance.lastActivity = Date.now();
1780
1556
  try {
1781
- const result = getTestCountInContext(instance.runtime.context);
1557
+ const result = instance.runtime.testEnvironment.getTestCount();
1782
1558
  sendOk(connection.socket, message.requestId, result);
1783
1559
  } catch (err) {
1784
1560
  const error = err;
1785
1561
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
1786
1562
  }
1787
1563
  }
1788
- async function handleRunPlaywrightTests(message, connection, _state) {
1789
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "playwright.runTests() has been removed. Use testEnvironment.runTests() instead.");
1790
- }
1791
- async function handleResetPlaywrightTests(message, connection, _state) {
1792
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "playwright.reset() has been removed. Use testEnvironment.reset() instead.");
1793
- }
1794
1564
  async function handleGetCollectedData(message, connection, state) {
1795
1565
  const instance = state.isolates.get(message.isolateId);
1796
1566
  if (!instance) {
1797
1567
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1798
1568
  return;
1799
1569
  }
1800
- if (!instance.playwrightHandle) {
1801
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Playwright not configured. Provide playwright.page in createRuntime options.");
1802
- return;
1803
- }
1804
1570
  instance.lastActivity = Date.now();
1805
1571
  try {
1806
- const data = {
1807
- browserConsoleLogs: instance.playwrightHandle.getBrowserConsoleLogs(),
1808
- networkRequests: instance.playwrightHandle.getNetworkRequests(),
1809
- networkResponses: instance.playwrightHandle.getNetworkResponses()
1810
- };
1572
+ const data = instance.runtime.playwright.getCollectedData();
1811
1573
  sendOk(connection.socket, message.requestId, data);
1812
1574
  } catch (err) {
1813
1575
  const error = err;
@@ -1820,13 +1582,9 @@ async function handleClearCollectedData(message, connection, state) {
1820
1582
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1821
1583
  return;
1822
1584
  }
1823
- if (!instance.playwrightHandle) {
1824
- sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Playwright not configured. Provide playwright.page in createRuntime options.");
1825
- return;
1826
- }
1827
1585
  instance.lastActivity = Date.now();
1828
1586
  try {
1829
- instance.playwrightHandle.clearCollected();
1587
+ instance.runtime.playwright.clearCollectedData();
1830
1588
  sendOk(connection.socket, message.requestId);
1831
1589
  } catch (err) {
1832
1590
  const error = err;
@@ -1837,4 +1595,4 @@ export {
1837
1595
  handleConnection
1838
1596
  };
1839
1597
 
1840
- //# debugId=514032B8427B94A564756E2164756E21
1598
+ //# debugId=4DCD38BA729DF7E764756E2164756E21