@ricsam/isolate-daemon 0.1.13 → 0.1.14

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,12 @@ import {
9
7
  ErrorCode,
10
8
  STREAM_CHUNK_SIZE,
11
9
  STREAM_DEFAULT_CREDIT,
12
- normalizeEntryFilename,
13
- marshalValue,
14
- unmarshalValue
10
+ marshalValue
15
11
  } from "@ricsam/isolate-protocol";
16
12
  import { createCallbackFileSystemHandler } from "./callback-fs-handler.mjs";
17
13
  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
14
+ createRuntime
28
15
  } from "@ricsam/isolate-runtime";
29
- import {
30
- transformEntryCode,
31
- transformModuleCode,
32
- contentHash,
33
- mapErrorStack
34
- } from "@ricsam/isolate-transform";
35
16
  function handleConnection(socket, state) {
36
17
  const connection = {
37
18
  socket,
@@ -66,12 +47,7 @@ function handleConnection(socket, state) {
66
47
  if (instance.namespaceId != null && !instance.isDisposed) {
67
48
  softDeleteRuntime(instance, state);
68
49
  } else if (!instance.isDisposed) {
69
- try {
70
- if (instance.playwrightHandle) {
71
- instance.playwrightHandle.dispose();
72
- }
73
- instance.runtime.dispose();
74
- } catch {}
50
+ instance.runtime.dispose().catch(() => {});
75
51
  state.isolates.delete(isolateId);
76
52
  }
77
53
  }
@@ -177,12 +153,6 @@ async function handleMessage(message, connection, state) {
177
153
  case MessageType.GET_TEST_COUNT:
178
154
  await handleGetTestCount(message, connection, state);
179
155
  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
156
  case MessageType.GET_COLLECTED_DATA:
187
157
  await handleGetCollectedData(message, connection, state);
188
158
  break;
@@ -213,6 +183,18 @@ async function handleMessage(message, connection, state) {
213
183
  case MessageType.CALLBACK_STREAM_END:
214
184
  handleCallbackStreamEnd(message, connection);
215
185
  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);
197
+ break;
216
198
  default:
217
199
  sendError(connection.socket, message.requestId ?? 0, ErrorCode.UNKNOWN_MESSAGE_TYPE, `Unknown message type: ${message.type}`);
218
200
  }
@@ -221,13 +203,17 @@ function softDeleteRuntime(instance, state) {
221
203
  instance.isDisposed = true;
222
204
  instance.disposedAt = Date.now();
223
205
  instance.ownerConnection = null;
206
+ if (instance.callbackContext) {
207
+ instance.callbackContext.connection = null;
208
+ }
224
209
  instance.callbacks.clear();
225
210
  instance.runtime.timers.clearAll();
226
211
  instance.runtime.console.reset();
227
- instance.pendingCallbacks.length = 0;
212
+ instance.runtime.pendingCallbacks.length = 0;
228
213
  instance.returnedCallbacks?.clear();
229
214
  instance.returnedPromises?.clear();
230
215
  instance.returnedIterators?.clear();
216
+ instance.runtime.clearModuleCache();
231
217
  }
232
218
  function reuseNamespacedRuntime(instance, connection, message, state) {
233
219
  instance.ownerConnection = connection.socket;
@@ -236,11 +222,19 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
236
222
  instance.lastActivity = Date.now();
237
223
  connection.isolates.add(instance.isolateId);
238
224
  const callbacks = message.options.callbacks;
225
+ const testEnvOptions = message.options.testEnvironment != null && typeof message.options.testEnvironment === "object" ? message.options.testEnvironment : undefined;
239
226
  if (instance.callbackContext) {
240
227
  instance.callbackContext.connection = connection;
241
228
  instance.callbackContext.consoleOnEntry = callbacks?.console?.onEntry?.callbackId;
242
229
  instance.callbackContext.fetch = callbacks?.fetch?.callbackId;
243
230
  instance.callbackContext.moduleLoader = callbacks?.moduleLoader?.callbackId;
231
+ instance.callbackContext.testEnvironmentOnEvent = testEnvOptions?.callbacks?.onEvent?.callbackId;
232
+ instance.callbackContext.playwright = {
233
+ handlerCallbackId: callbacks?.playwright?.handlerCallbackId,
234
+ onBrowserConsoleLogCallbackId: callbacks?.playwright?.onBrowserConsoleLogCallbackId,
235
+ onNetworkRequestCallbackId: callbacks?.playwright?.onNetworkRequestCallbackId,
236
+ onNetworkResponseCallbackId: callbacks?.playwright?.onNetworkResponseCallbackId
237
+ };
244
238
  instance.callbackContext.fs = {
245
239
  readFile: callbacks?.fs?.readFile?.callbackId,
246
240
  writeFile: callbacks?.fs?.writeFile?.callbackId,
@@ -277,7 +271,6 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
277
271
  }
278
272
  }
279
273
  if (callbacks?.moduleLoader) {
280
- instance.moduleLoaderCallbackId = callbacks.moduleLoader.callbackId;
281
274
  instance.callbacks.set(callbacks.moduleLoader.callbackId, callbacks.moduleLoader);
282
275
  }
283
276
  if (callbacks?.custom) {
@@ -287,23 +280,46 @@ function reuseNamespacedRuntime(instance, connection, message, state) {
287
280
  }
288
281
  }
289
282
  }
283
+ if (testEnvOptions?.callbacks?.onEvent) {
284
+ instance.callbacks.set(testEnvOptions.callbacks.onEvent.callbackId, {
285
+ ...testEnvOptions.callbacks.onEvent,
286
+ name: "testEnvironment.onEvent"
287
+ });
288
+ }
289
+ if (callbacks?.playwright) {
290
+ instance.callbacks.set(callbacks.playwright.handlerCallbackId, {
291
+ callbackId: callbacks.playwright.handlerCallbackId,
292
+ name: "playwright.handler",
293
+ type: "async"
294
+ });
295
+ if (callbacks.playwright.onBrowserConsoleLogCallbackId !== undefined) {
296
+ instance.callbacks.set(callbacks.playwright.onBrowserConsoleLogCallbackId, {
297
+ callbackId: callbacks.playwright.onBrowserConsoleLogCallbackId,
298
+ name: "playwright.onBrowserConsoleLog",
299
+ type: "sync"
300
+ });
301
+ }
302
+ if (callbacks.playwright.onNetworkRequestCallbackId !== undefined) {
303
+ instance.callbacks.set(callbacks.playwright.onNetworkRequestCallbackId, {
304
+ callbackId: callbacks.playwright.onNetworkRequestCallbackId,
305
+ name: "playwright.onNetworkRequest",
306
+ type: "sync"
307
+ });
308
+ }
309
+ if (callbacks.playwright.onNetworkResponseCallbackId !== undefined) {
310
+ instance.callbacks.set(callbacks.playwright.onNetworkResponseCallbackId, {
311
+ callbackId: callbacks.playwright.onNetworkResponseCallbackId,
312
+ name: "playwright.onNetworkResponse",
313
+ type: "sync"
314
+ });
315
+ }
316
+ }
290
317
  instance.returnedCallbacks = new Map;
291
318
  instance.returnedPromises = new Map;
292
319
  instance.returnedIterators = new Map;
293
320
  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
321
  }
306
- function evictOldestDisposedRuntime(state) {
322
+ async function evictOldestDisposedRuntime(state) {
307
323
  let oldest = null;
308
324
  let oldestTime = Infinity;
309
325
  for (const [, instance] of state.isolates) {
@@ -316,10 +332,7 @@ function evictOldestDisposedRuntime(state) {
316
332
  }
317
333
  if (oldest) {
318
334
  try {
319
- if (oldest.playwrightHandle) {
320
- oldest.playwrightHandle.dispose();
321
- }
322
- oldest.runtime.dispose();
335
+ await oldest.runtime.dispose();
323
336
  } catch {}
324
337
  state.isolates.delete(oldest.isolateId);
325
338
  if (oldest.namespaceId != null) {
@@ -354,7 +367,7 @@ async function handleCreateRuntime(message, connection, state) {
354
367
  }
355
368
  }
356
369
  if (state.isolates.size >= state.options.maxIsolates) {
357
- if (!evictOldestDisposedRuntime(state)) {
370
+ if (!await evictOldestDisposedRuntime(state)) {
358
371
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
359
372
  return;
360
373
  }
@@ -366,12 +379,18 @@ async function handleCreateRuntime(message, connection, state) {
366
379
  const fsCallbacks = message.options.callbacks?.fs;
367
380
  const moduleLoaderCallback = message.options.callbacks?.moduleLoader;
368
381
  const customCallbacks = message.options.callbacks?.custom;
369
- const pendingCallbacks = [];
370
382
  const callbackContext = {
371
383
  connection,
372
384
  consoleOnEntry: consoleCallbacks?.onEntry?.callbackId,
373
385
  fetch: fetchCallback?.callbackId,
374
386
  moduleLoader: moduleLoaderCallback?.callbackId,
387
+ testEnvironmentOnEvent: message.options.testEnvironment != null && typeof message.options.testEnvironment === "object" ? message.options.testEnvironment.callbacks?.onEvent?.callbackId : undefined,
388
+ playwright: {
389
+ handlerCallbackId: message.options.callbacks?.playwright?.handlerCallbackId,
390
+ onBrowserConsoleLogCallbackId: message.options.callbacks?.playwright?.onBrowserConsoleLogCallbackId,
391
+ onNetworkRequestCallbackId: message.options.callbacks?.playwright?.onNetworkRequestCallbackId,
392
+ onNetworkResponseCallbackId: message.options.callbacks?.playwright?.onNetworkResponseCallbackId
393
+ },
375
394
  fs: {
376
395
  readFile: fsCallbacks?.readFile?.callbackId,
377
396
  writeFile: fsCallbacks?.writeFile?.callbackId,
@@ -383,7 +402,248 @@ async function handleCreateRuntime(message, connection, state) {
383
402
  },
384
403
  custom: new Map(customCallbacks ? Object.entries(customCallbacks).map(([name, reg]) => [name, reg.callbackId]) : [])
385
404
  };
386
- const runtime = await createInternalRuntime({
405
+ const instance = {
406
+ isolateId,
407
+ runtime: null,
408
+ ownerConnection: connection.socket,
409
+ callbacks: new Map,
410
+ createdAt: Date.now(),
411
+ lastActivity: Date.now(),
412
+ returnedCallbacks: new Map,
413
+ returnedPromises: new Map,
414
+ returnedIterators: new Map,
415
+ nextLocalCallbackId: 1e6,
416
+ namespaceId,
417
+ isDisposed: false,
418
+ callbackContext
419
+ };
420
+ let bridgedCustomFunctions;
421
+ let customFnMarshalOptions;
422
+ if (customCallbacks) {
423
+ const createMarshalContext = () => ({
424
+ registerCallback: (fn) => {
425
+ const callbackId = instance.nextLocalCallbackId++;
426
+ instance.returnedCallbacks.set(callbackId, fn);
427
+ return callbackId;
428
+ },
429
+ registerPromise: (promise) => {
430
+ const promiseId = instance.nextLocalCallbackId++;
431
+ instance.returnedPromises.set(promiseId, promise);
432
+ return promiseId;
433
+ },
434
+ registerIterator: (iterator) => {
435
+ const iteratorId = instance.nextLocalCallbackId++;
436
+ instance.returnedIterators.set(iteratorId, iterator);
437
+ return iteratorId;
438
+ }
439
+ });
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
+ const addCallbackIdsToRefs = (value) => {
443
+ if (value === null || typeof value !== "object")
444
+ return value;
445
+ if (isPromiseRef(value)) {
446
+ if ("__resolveCallbackId" in value)
447
+ return value;
448
+ const resolveCallbackId = instance.nextLocalCallbackId++;
449
+ instance.returnedCallbacks.set(resolveCallbackId, async (promiseId) => {
450
+ const promise = instance.returnedPromises.get(promiseId);
451
+ if (!promise)
452
+ throw new Error(`Promise ${promiseId} not found`);
453
+ const result2 = await promise;
454
+ instance.returnedPromises.delete(promiseId);
455
+ const ctx = createMarshalContext();
456
+ const marshalled = await marshalValue(result2, ctx);
457
+ return addCallbackIdsToRefs(marshalled);
458
+ });
459
+ return { ...value, __resolveCallbackId: resolveCallbackId };
460
+ }
461
+ if (isAsyncIteratorRef(value)) {
462
+ if ("__nextCallbackId" in value)
463
+ return value;
464
+ const nextCallbackId = instance.nextLocalCallbackId++;
465
+ instance.returnedCallbacks.set(nextCallbackId, async (iteratorId) => {
466
+ const iterator = instance.returnedIterators.get(iteratorId);
467
+ if (!iterator)
468
+ throw new Error(`Iterator ${iteratorId} not found`);
469
+ const result2 = await iterator.next();
470
+ if (result2.done)
471
+ instance.returnedIterators.delete(iteratorId);
472
+ const ctx = createMarshalContext();
473
+ const marshalledValue = await marshalValue(result2.value, ctx);
474
+ return { done: result2.done, value: addCallbackIdsToRefs(marshalledValue) };
475
+ });
476
+ const returnCallbackId = instance.nextLocalCallbackId++;
477
+ instance.returnedCallbacks.set(returnCallbackId, async (iteratorId, returnValue) => {
478
+ const iterator = instance.returnedIterators.get(iteratorId);
479
+ instance.returnedIterators.delete(iteratorId);
480
+ if (!iterator || !iterator.return)
481
+ return { done: true, value: undefined };
482
+ const result2 = await iterator.return(returnValue);
483
+ const ctx = createMarshalContext();
484
+ const marshalledValue = await marshalValue(result2.value, ctx);
485
+ return { done: true, value: addCallbackIdsToRefs(marshalledValue) };
486
+ });
487
+ return { ...value, __nextCallbackId: nextCallbackId, __returnCallbackId: returnCallbackId };
488
+ }
489
+ if (Array.isArray(value))
490
+ return value.map((item) => addCallbackIdsToRefs(item));
491
+ const result = {};
492
+ for (const key of Object.keys(value)) {
493
+ result[key] = addCallbackIdsToRefs(value[key]);
494
+ }
495
+ return result;
496
+ };
497
+ const LOCAL_CALLBACK_THRESHOLD = 1e6;
498
+ const invokeCallback = async (callbackId, args) => {
499
+ if (callbackId >= LOCAL_CALLBACK_THRESHOLD) {
500
+ const callback = instance.returnedCallbacks.get(callbackId);
501
+ if (!callback) {
502
+ throw new Error(`Local callback ${callbackId} not found`);
503
+ }
504
+ return await callback(...args);
505
+ } else {
506
+ const conn = callbackContext.connection;
507
+ if (!conn) {
508
+ throw new Error(`No connection available for callback ${callbackId}`);
509
+ }
510
+ return invokeClientCallback(conn, callbackId, args);
511
+ }
512
+ };
513
+ customFnMarshalOptions = { createMarshalContext, addCallbackIdsToRefs, invokeCallback };
514
+ bridgedCustomFunctions = {};
515
+ for (const [name, registration] of Object.entries(customCallbacks)) {
516
+ if (name.includes(":"))
517
+ continue;
518
+ const callbackContext_ = callbackContext;
519
+ if (registration.type === "asyncIterator") {
520
+ bridgedCustomFunctions[name] = {
521
+ type: "asyncIterator",
522
+ fn: (...args) => {
523
+ const startCallbackId = callbackContext_.custom.get(`${name}:start`);
524
+ const nextCallbackId = callbackContext_.custom.get(`${name}:next`);
525
+ const returnCallbackId = callbackContext_.custom.get(`${name}:return`);
526
+ 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);
532
+ const iteratorId = startResult.iteratorId;
533
+ try {
534
+ 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]);
540
+ if (nextResult.done)
541
+ return nextResult.value;
542
+ yield nextResult.value;
543
+ }
544
+ } finally {
545
+ const retConn = callbackContext_.connection;
546
+ if (retConn && returnCallbackId !== undefined) {
547
+ await invokeClientCallback(retConn, returnCallbackId, [iteratorId]).catch(() => {});
548
+ }
549
+ }
550
+ }
551
+ return bridgedIterator();
552
+ }
553
+ };
554
+ } else {
555
+ bridgedCustomFunctions[name] = {
556
+ type: registration.type,
557
+ 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);
564
+ }
565
+ };
566
+ }
567
+ }
568
+ }
569
+ let moduleLoader;
570
+ if (moduleLoaderCallback) {
571
+ 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]);
578
+ };
579
+ }
580
+ let testEnvironment;
581
+ if (message.options.testEnvironment) {
582
+ const testEnvOption = message.options.testEnvironment;
583
+ const testEnvOptions = typeof testEnvOption === "object" ? testEnvOption : undefined;
584
+ testEnvironment = {
585
+ onEvent: testEnvOptions?.callbacks?.onEvent ? (event) => {
586
+ const conn = callbackContext.connection;
587
+ const callbackId = callbackContext.testEnvironmentOnEvent;
588
+ if (!conn || callbackId === undefined) {
589
+ return;
590
+ }
591
+ const promise = invokeClientCallback(conn, callbackId, [JSON.stringify(event)]).catch(() => {});
592
+ instance.runtime?.pendingCallbacks?.push(promise);
593
+ } : undefined,
594
+ testTimeout: testEnvOptions?.testTimeout
595
+ };
596
+ if (testEnvOptions?.callbacks?.onEvent) {
597
+ instance.callbacks.set(testEnvOptions.callbacks.onEvent.callbackId, {
598
+ ...testEnvOptions.callbacks.onEvent,
599
+ name: "testEnvironment.onEvent"
600
+ });
601
+ }
602
+ }
603
+ let playwrightOptions;
604
+ const playwrightCallbacks = message.options.callbacks?.playwright;
605
+ if (playwrightCallbacks) {
606
+ playwrightOptions = {
607
+ 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
+ try {
620
+ const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)], 60000);
621
+ return JSON.parse(resultJson);
622
+ } catch (err) {
623
+ const error = err;
624
+ return { ok: false, error: { name: error.name, message: error.message } };
625
+ }
626
+ },
627
+ console: playwrightCallbacks.console,
628
+ onEvent: (event) => {
629
+ const conn = callbackContext.connection;
630
+ if (!conn) {
631
+ return;
632
+ }
633
+ if (event.type === "browserConsoleLog" && callbackContext.playwright.onBrowserConsoleLogCallbackId !== undefined) {
634
+ const promise = invokeClientCallback(conn, callbackContext.playwright.onBrowserConsoleLogCallbackId, [{ level: event.level, stdout: event.stdout, timestamp: event.timestamp }]).catch(() => {});
635
+ instance.runtime?.pendingCallbacks?.push(promise);
636
+ } else if (event.type === "networkRequest" && callbackContext.playwright.onNetworkRequestCallbackId !== undefined) {
637
+ const promise = invokeClientCallback(conn, callbackContext.playwright.onNetworkRequestCallbackId, [event]).catch(() => {});
638
+ instance.runtime?.pendingCallbacks?.push(promise);
639
+ } else if (event.type === "networkResponse" && callbackContext.playwright.onNetworkResponseCallbackId !== undefined) {
640
+ const promise = invokeClientCallback(conn, callbackContext.playwright.onNetworkResponseCallbackId, [event]).catch(() => {});
641
+ instance.runtime?.pendingCallbacks?.push(promise);
642
+ }
643
+ }
644
+ };
645
+ }
646
+ const runtime = await createRuntime({
387
647
  memoryLimitMB: message.options.memoryLimitMB ?? state.options.defaultMemoryLimitMB,
388
648
  cwd: message.options.cwd,
389
649
  console: {
@@ -393,28 +653,31 @@ async function handleCreateRuntime(message, connection, state) {
393
653
  if (!conn || callbackId === undefined)
394
654
  return;
395
655
  const promise = invokeClientCallback(conn, callbackId, [entry]).catch(() => {});
396
- pendingCallbacks.push(promise);
656
+ runtime.pendingCallbacks.push(promise);
397
657
  }
398
658
  },
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);
659
+ 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
+ const serialized = {
666
+ url,
667
+ method: init.method,
668
+ headers: init.headers,
669
+ body: init.rawBody
670
+ };
671
+ const result = await invokeClientCallback(conn, callbackId, [serialized], 60000);
672
+ if (result && typeof result === "object" && result.__streamingResponse) {
673
+ const response = result.response;
674
+ response.__isCallbackStream = true;
675
+ return response;
414
676
  }
677
+ return deserializeResponse(result);
415
678
  },
416
679
  fs: {
417
- getDirectory: async (path2) => {
680
+ getDirectory: async (dirPath) => {
418
681
  const conn = callbackContext.connection;
419
682
  if (!conn) {
420
683
  throw new Error("FS callbacks not available");
@@ -423,35 +686,17 @@ async function handleCreateRuntime(message, connection, state) {
423
686
  connection: conn,
424
687
  callbackContext,
425
688
  invokeClientCallback,
426
- basePath: path2
689
+ basePath: dirPath
427
690
  });
428
691
  }
429
- }
692
+ },
693
+ moduleLoader,
694
+ customFunctions: bridgedCustomFunctions,
695
+ customFunctionsMarshalOptions: customFnMarshalOptions,
696
+ testEnvironment,
697
+ playwright: playwrightOptions
430
698
  });
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
- }
699
+ instance.runtime = runtime;
455
700
  if (consoleCallbacks?.onEntry) {
456
701
  instance.callbacks.set(consoleCallbacks.onEntry.callbackId, {
457
702
  ...consoleCallbacks.onEntry,
@@ -478,52 +723,33 @@ async function handleCreateRuntime(message, connection, state) {
478
723
  }
479
724
  }
480
725
  }
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
726
+ if (playwrightCallbacks) {
727
+ instance.callbacks.set(playwrightCallbacks.handlerCallbackId, {
728
+ callbackId: playwrightCallbacks.handlerCallbackId,
729
+ name: "playwright.handler",
730
+ type: "async"
491
731
  });
492
- instance.testEnvironmentEnabled = true;
493
- if (onEventCallback) {
494
- instance.callbacks.set(onEventCallback.callbackId, {
495
- ...onEventCallback,
496
- name: "testEnvironment.onEvent"
732
+ if (playwrightCallbacks.onBrowserConsoleLogCallbackId !== undefined) {
733
+ instance.callbacks.set(playwrightCallbacks.onBrowserConsoleLogCallbackId, {
734
+ callbackId: playwrightCallbacks.onBrowserConsoleLogCallbackId,
735
+ name: "playwright.onBrowserConsoleLog",
736
+ type: "sync"
737
+ });
738
+ }
739
+ if (playwrightCallbacks.onNetworkRequestCallbackId !== undefined) {
740
+ instance.callbacks.set(playwrightCallbacks.onNetworkRequestCallbackId, {
741
+ callbackId: playwrightCallbacks.onNetworkRequestCallbackId,
742
+ name: "playwright.onNetworkRequest",
743
+ type: "sync"
744
+ });
745
+ }
746
+ if (playwrightCallbacks.onNetworkResponseCallbackId !== undefined) {
747
+ instance.callbacks.set(playwrightCallbacks.onNetworkResponseCallbackId, {
748
+ callbackId: playwrightCallbacks.onNetworkResponseCallbackId,
749
+ name: "playwright.onNetworkResponse",
750
+ type: "sync"
497
751
  });
498
752
  }
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
753
  }
528
754
  state.isolates.set(isolateId, instance);
529
755
  connection.isolates.add(isolateId);
@@ -531,7 +757,11 @@ async function handleCreateRuntime(message, connection, state) {
531
757
  if (namespaceId != null) {
532
758
  state.namespacedRuntimes.set(namespaceId, instance);
533
759
  }
534
- instance.runtime.fetch.onWebSocketCommand((cmd) => {
760
+ runtime.fetch.onWebSocketCommand((cmd) => {
761
+ const targetConnection = callbackContext.connection;
762
+ if (!targetConnection) {
763
+ return;
764
+ }
535
765
  let data;
536
766
  if (cmd.data instanceof ArrayBuffer) {
537
767
  data = new Uint8Array(cmd.data);
@@ -549,7 +779,49 @@ async function handleCreateRuntime(message, connection, state) {
549
779
  reason: cmd.reason
550
780
  }
551
781
  };
552
- sendMessage(connection.socket, wsCommandMsg);
782
+ sendMessage(targetConnection.socket, wsCommandMsg);
783
+ });
784
+ runtime.fetch.onClientWebSocketCommand((cmd) => {
785
+ const targetConnection = callbackContext.connection;
786
+ if (!targetConnection) {
787
+ return;
788
+ }
789
+ let data;
790
+ if (cmd.data instanceof ArrayBuffer) {
791
+ data = new Uint8Array(cmd.data);
792
+ } else {
793
+ data = cmd.data;
794
+ }
795
+ if (cmd.type === "connect") {
796
+ const msg = {
797
+ type: MessageType.CLIENT_WS_CONNECT,
798
+ requestId: 0,
799
+ isolateId,
800
+ socketId: cmd.socketId,
801
+ url: cmd.url,
802
+ protocols: cmd.protocols
803
+ };
804
+ sendMessage(targetConnection.socket, msg);
805
+ } else if (cmd.type === "send") {
806
+ const msg = {
807
+ type: MessageType.CLIENT_WS_SEND,
808
+ requestId: 0,
809
+ isolateId,
810
+ socketId: cmd.socketId,
811
+ data
812
+ };
813
+ sendMessage(targetConnection.socket, msg);
814
+ } else if (cmd.type === "close") {
815
+ const msg = {
816
+ type: MessageType.CLIENT_WS_CLOSE,
817
+ requestId: 0,
818
+ isolateId,
819
+ socketId: cmd.socketId,
820
+ code: cmd.code,
821
+ reason: cmd.reason
822
+ };
823
+ sendMessage(targetConnection.socket, msg);
824
+ }
553
825
  });
554
826
  sendOk(connection.socket, message.requestId, { isolateId, reused: false });
555
827
  } catch (err) {
@@ -572,10 +844,7 @@ async function handleDisposeRuntime(message, connection, state) {
572
844
  if (instance.namespaceId != null) {
573
845
  softDeleteRuntime(instance, state);
574
846
  } else {
575
- if (instance.playwrightHandle) {
576
- instance.playwrightHandle.dispose();
577
- }
578
- instance.runtime.dispose();
847
+ await instance.runtime.dispose();
579
848
  state.isolates.delete(message.isolateId);
580
849
  }
581
850
  sendOk(connection.socket, message.requestId);
@@ -592,45 +861,13 @@ async function handleEval(message, connection, state) {
592
861
  }
593
862
  instance.lastActivity = Date.now();
594
863
  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
864
+ await instance.runtime.eval(message.code, {
865
+ filename: message.filename,
866
+ maxExecutionMs: message.maxExecutionMs
605
867
  });
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
868
  sendOk(connection.socket, message.requestId, { value: undefined });
629
869
  } catch (err) {
630
870
  const error = err;
631
- if (error.stack && instance.sourceMaps?.size) {
632
- error.stack = mapErrorStack(error.stack, instance.sourceMaps);
633
- }
634
871
  const isTimeoutError = error.message?.includes("Script execution timed out");
635
872
  sendError(connection.socket, message.requestId, isTimeoutError ? ErrorCode.ISOLATE_TIMEOUT : ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
636
873
  }
@@ -740,6 +977,35 @@ async function handleWsClose(message, connection, state) {
740
977
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
741
978
  }
742
979
  }
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) {
996
+ const instance = state.isolates.get(message.isolateId);
997
+ if (!instance)
998
+ return;
999
+ 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);
1008
+ }
743
1009
  async function handleFetchGetUpgradeRequest(message, connection, state) {
744
1010
  const instance = state.isolates.get(message.isolateId);
745
1011
  if (!instance) {
@@ -918,523 +1184,6 @@ async function invokeClientCallback(connection, callbackId, args, timeout = 1e4)
918
1184
  sendMessage(connection.socket, invoke);
919
1185
  });
920
1186
  }
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
1187
  function deserializeResponse(data) {
1439
1188
  return new Response(data.body, {
1440
1189
  status: data.status,
@@ -1701,28 +1450,11 @@ async function handleRunTests(message, connection, state) {
1701
1450
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1702
1451
  return;
1703
1452
  }
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
1453
  instance.lastActivity = Date.now();
1709
1454
  try {
1710
1455
  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
- }
1456
+ const results = await instance.runtime.testEnvironment.runTests(timeout);
1457
+ sendOk(connection.socket, message.requestId, results);
1726
1458
  } catch (err) {
1727
1459
  const error = err;
1728
1460
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
@@ -1734,13 +1466,9 @@ async function handleResetTestEnv(message, connection, state) {
1734
1466
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1735
1467
  return;
1736
1468
  }
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
1469
  instance.lastActivity = Date.now();
1742
1470
  try {
1743
- await instance.runtime.context.eval("__resetTestEnvironment()", { promise: true });
1471
+ instance.runtime.testEnvironment.reset();
1744
1472
  sendOk(connection.socket, message.requestId);
1745
1473
  } catch (err) {
1746
1474
  const error = err;
@@ -1753,13 +1481,9 @@ async function handleHasTests(message, connection, state) {
1753
1481
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1754
1482
  return;
1755
1483
  }
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
1484
  instance.lastActivity = Date.now();
1761
1485
  try {
1762
- const result = hasTestsInContext(instance.runtime.context);
1486
+ const result = instance.runtime.testEnvironment.hasTests();
1763
1487
  sendOk(connection.socket, message.requestId, result);
1764
1488
  } catch (err) {
1765
1489
  const error = err;
@@ -1772,42 +1496,24 @@ async function handleGetTestCount(message, connection, state) {
1772
1496
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1773
1497
  return;
1774
1498
  }
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
1499
  instance.lastActivity = Date.now();
1780
1500
  try {
1781
- const result = getTestCountInContext(instance.runtime.context);
1501
+ const result = instance.runtime.testEnvironment.getTestCount();
1782
1502
  sendOk(connection.socket, message.requestId, result);
1783
1503
  } catch (err) {
1784
1504
  const error = err;
1785
1505
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
1786
1506
  }
1787
1507
  }
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
1508
  async function handleGetCollectedData(message, connection, state) {
1795
1509
  const instance = state.isolates.get(message.isolateId);
1796
1510
  if (!instance) {
1797
1511
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1798
1512
  return;
1799
1513
  }
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
1514
  instance.lastActivity = Date.now();
1805
1515
  try {
1806
- const data = {
1807
- browserConsoleLogs: instance.playwrightHandle.getBrowserConsoleLogs(),
1808
- networkRequests: instance.playwrightHandle.getNetworkRequests(),
1809
- networkResponses: instance.playwrightHandle.getNetworkResponses()
1810
- };
1516
+ const data = instance.runtime.playwright.getCollectedData();
1811
1517
  sendOk(connection.socket, message.requestId, data);
1812
1518
  } catch (err) {
1813
1519
  const error = err;
@@ -1820,13 +1526,9 @@ async function handleClearCollectedData(message, connection, state) {
1820
1526
  sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
1821
1527
  return;
1822
1528
  }
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
1529
  instance.lastActivity = Date.now();
1828
1530
  try {
1829
- instance.playwrightHandle.clearCollected();
1531
+ instance.runtime.playwright.clearCollectedData();
1830
1532
  sendOk(connection.socket, message.requestId);
1831
1533
  } catch (err) {
1832
1534
  const error = err;
@@ -1837,4 +1539,4 @@ export {
1837
1539
  handleConnection
1838
1540
  };
1839
1541
 
1840
- //# debugId=514032B8427B94A564756E2164756E21
1542
+ //# debugId=C809F161C354C84D64756E2164756E21