@ricsam/isolate-client 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.
@@ -50,7 +50,10 @@ var import_node_path = __toESM(require("node:path"));
50
50
  var import_isolate_protocol = require("@ricsam/isolate-protocol");
51
51
  var import_client = require("@ricsam/isolate-playwright/client");
52
52
  var DEFAULT_TIMEOUT = 30000;
53
+ var TEST_REQUEST_TIMEOUT_BUFFER_MS = 1000;
53
54
  var isolateWsCallbacks = new Map;
55
+ var isolateClientWebSockets = new Map;
56
+ var isolateWebSocketCallbacks = new Map;
54
57
  async function connect(options = {}) {
55
58
  const socket = await createSocket(options);
56
59
  const state = {
@@ -214,6 +217,21 @@ function handleMessage(message, state) {
214
217
  }
215
218
  break;
216
219
  }
220
+ case import_isolate_protocol.MessageType.CLIENT_WS_CONNECT: {
221
+ const msg = message;
222
+ handleClientWsConnect(msg, state);
223
+ break;
224
+ }
225
+ case import_isolate_protocol.MessageType.CLIENT_WS_SEND: {
226
+ const msg = message;
227
+ handleClientWsSend(msg, state);
228
+ break;
229
+ }
230
+ case import_isolate_protocol.MessageType.CLIENT_WS_CLOSE: {
231
+ const msg = message;
232
+ handleClientWsClose(msg, state);
233
+ break;
234
+ }
217
235
  case import_isolate_protocol.MessageType.RESPONSE_STREAM_START: {
218
236
  const msg = message;
219
237
  const receiver = {
@@ -449,74 +467,76 @@ async function createRuntime(state, options = {}, namespaceId) {
449
467
  const networkResponses = [];
450
468
  const pageListenerCleanups = [];
451
469
  if (options.playwright) {
452
- playwrightHandler = import_client.createPlaywrightHandler(options.playwright.page, {
453
- timeout: options.playwright.timeout,
454
- baseUrl: options.playwright.baseUrl
455
- });
470
+ playwrightHandler = options.playwright.handler;
471
+ if (!playwrightHandler) {
472
+ throw new Error("playwright.handler is required when using playwright options");
473
+ }
474
+ const page = import_client.getDefaultPlaywrightHandlerMetadata(playwrightHandler)?.page;
456
475
  const handlerCallbackId = state.nextCallbackId++;
457
476
  state.callbacks.set(handlerCallbackId, async (opJson) => {
458
477
  const op = JSON.parse(opJson);
459
478
  const result2 = await playwrightHandler(op);
460
479
  return JSON.stringify(result2);
461
480
  });
462
- const page = options.playwright.page;
463
- const onConsole = (msg) => {
464
- const entry = {
465
- level: msg.type(),
466
- stdout: msg.text(),
467
- timestamp: Date.now()
481
+ if (page) {
482
+ const onConsole = (msg) => {
483
+ const entry = {
484
+ level: msg.type(),
485
+ stdout: msg.text(),
486
+ timestamp: Date.now()
487
+ };
488
+ browserConsoleLogs.push(entry);
489
+ if (options.playwright.onEvent) {
490
+ options.playwright.onEvent({
491
+ type: "browserConsoleLog",
492
+ ...entry
493
+ });
494
+ }
495
+ if (options.playwright.console && options.console?.onEntry) {
496
+ options.console.onEntry({
497
+ type: "browserOutput",
498
+ ...entry
499
+ });
500
+ } else if (options.playwright.console) {
501
+ const prefix = entry.level === "error" ? "[browser:error]" : "[browser]";
502
+ console.log(prefix, entry.stdout);
503
+ }
468
504
  };
469
- browserConsoleLogs.push(entry);
470
- if (options.playwright.onEvent) {
471
- options.playwright.onEvent({
472
- type: "browserConsoleLog",
473
- ...entry
474
- });
475
- }
476
- if (options.playwright.console && options.console?.onEntry) {
477
- options.console.onEntry({
478
- type: "browserOutput",
479
- ...entry
480
- });
481
- } else if (options.playwright.console) {
482
- const prefix = entry.level === "error" ? "[browser:error]" : "[browser]";
483
- console.log(prefix, entry.stdout);
484
- }
485
- };
486
- const onRequest = (request2) => {
487
- const info = {
488
- url: request2.url(),
489
- method: request2.method(),
490
- headers: request2.headers(),
491
- timestamp: Date.now()
505
+ const onRequest = (request2) => {
506
+ const info = {
507
+ url: request2.url(),
508
+ method: request2.method(),
509
+ headers: request2.headers(),
510
+ timestamp: Date.now()
511
+ };
512
+ networkRequests.push(info);
513
+ if (options.playwright.onEvent) {
514
+ options.playwright.onEvent({
515
+ type: "networkRequest",
516
+ ...info
517
+ });
518
+ }
492
519
  };
493
- networkRequests.push(info);
494
- if (options.playwright.onEvent) {
495
- options.playwright.onEvent({
496
- type: "networkRequest",
497
- ...info
498
- });
499
- }
500
- };
501
- const onResponse = (response) => {
502
- const info = {
503
- url: response.url(),
504
- status: response.status(),
505
- headers: response.headers(),
506
- timestamp: Date.now()
520
+ const onResponse = (response) => {
521
+ const info = {
522
+ url: response.url(),
523
+ status: response.status(),
524
+ headers: response.headers(),
525
+ timestamp: Date.now()
526
+ };
527
+ networkResponses.push(info);
528
+ if (options.playwright.onEvent) {
529
+ options.playwright.onEvent({
530
+ type: "networkResponse",
531
+ ...info
532
+ });
533
+ }
507
534
  };
508
- networkResponses.push(info);
509
- if (options.playwright.onEvent) {
510
- options.playwright.onEvent({
511
- type: "networkResponse",
512
- ...info
513
- });
514
- }
515
- };
516
- page.on("console", onConsole);
517
- page.on("request", onRequest);
518
- page.on("response", onResponse);
519
- pageListenerCleanups.push(() => page.removeListener("console", onConsole), () => page.removeListener("request", onRequest), () => page.removeListener("response", onResponse));
535
+ page.on("console", onConsole);
536
+ page.on("request", onRequest);
537
+ page.on("response", onResponse);
538
+ pageListenerCleanups.push(() => page.removeListener("console", onConsole), () => page.removeListener("request", onRequest), () => page.removeListener("response", onResponse));
539
+ }
520
540
  callbacks.playwright = {
521
541
  handlerCallbackId,
522
542
  console: options.playwright.console && !options.console?.onEntry
@@ -564,6 +584,9 @@ async function createRuntime(state, options = {}, namespaceId) {
564
584
  const reused = result.reused ?? false;
565
585
  const wsCommandCallbacks = new Set;
566
586
  isolateWsCallbacks.set(isolateId, wsCommandCallbacks);
587
+ if (options.webSocket) {
588
+ isolateWebSocketCallbacks.set(isolateId, options.webSocket);
589
+ }
567
590
  const fetchHandle = {
568
591
  async dispatchRequest(req, opts) {
569
592
  const reqId = state.nextRequestId++;
@@ -737,7 +760,8 @@ async function createRuntime(state, options = {}, namespaceId) {
737
760
  isolateId,
738
761
  timeout
739
762
  };
740
- return sendRequest(state, req, timeout ?? DEFAULT_TIMEOUT);
763
+ const requestTimeout = timeout === undefined ? DEFAULT_TIMEOUT : Math.max(timeout + TEST_REQUEST_TIMEOUT_BUFFER_MS, DEFAULT_TIMEOUT);
764
+ return sendRequest(state, req, requestTimeout);
741
765
  },
742
766
  async hasTests() {
743
767
  if (!testEnvironmentEnabled) {
@@ -779,7 +803,7 @@ async function createRuntime(state, options = {}, namespaceId) {
779
803
  const playwrightHandle = {
780
804
  getCollectedData() {
781
805
  if (!playwrightEnabled) {
782
- throw new Error("Playwright not configured. Provide playwright.page in createRuntime options.");
806
+ throw new Error("Playwright not configured. Provide playwright.handler in createRuntime options.");
783
807
  }
784
808
  return {
785
809
  browserConsoleLogs: [...browserConsoleLogs],
@@ -789,7 +813,7 @@ async function createRuntime(state, options = {}, namespaceId) {
789
813
  },
790
814
  clearCollectedData() {
791
815
  if (!playwrightEnabled) {
792
- throw new Error("Playwright not configured. Provide playwright.page in createRuntime options.");
816
+ throw new Error("Playwright not configured. Provide playwright.handler in createRuntime options.");
793
817
  }
794
818
  browserConsoleLogs.length = 0;
795
819
  networkRequests.length = 0;
@@ -798,7 +822,6 @@ async function createRuntime(state, options = {}, namespaceId) {
798
822
  };
799
823
  return {
800
824
  id: isolateId,
801
- isolateId,
802
825
  reused,
803
826
  fetch: fetchHandle,
804
827
  timers: timersHandle,
@@ -814,8 +837,7 @@ async function createRuntime(state, options = {}, namespaceId) {
814
837
  isolateId,
815
838
  code,
816
839
  filename: options2?.filename,
817
- maxExecutionMs: options2?.maxExecutionMs,
818
- module: true
840
+ maxExecutionMs: options2?.maxExecutionMs
819
841
  };
820
842
  await sendRequest(state, req);
821
843
  },
@@ -824,6 +846,16 @@ async function createRuntime(state, options = {}, namespaceId) {
824
846
  cleanup();
825
847
  }
826
848
  isolateWsCallbacks.delete(isolateId);
849
+ isolateWebSocketCallbacks.delete(isolateId);
850
+ const clientSockets = isolateClientWebSockets.get(isolateId);
851
+ if (clientSockets) {
852
+ for (const ws of clientSockets.values()) {
853
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
854
+ ws.close(1000, "Isolate disposed");
855
+ }
856
+ }
857
+ isolateClientWebSockets.delete(isolateId);
858
+ }
827
859
  const reqId = state.nextRequestId++;
828
860
  const req = {
829
861
  type: import_isolate_protocol.MessageType.DISPOSE_RUNTIME,
@@ -858,8 +890,15 @@ function registerFetchCallback(state, callback) {
858
890
  const callbackId = state.nextCallbackId++;
859
891
  state.callbacksNeedingRequestId.add(callbackId);
860
892
  state.callbacks.set(callbackId, async (serialized, requestId) => {
861
- const request = deserializeRequest(serialized);
862
- const response = await callback(request);
893
+ const data = serialized;
894
+ const init = {
895
+ method: data.method,
896
+ headers: data.headers,
897
+ rawBody: data.body,
898
+ body: data.body,
899
+ signal: new AbortController().signal
900
+ };
901
+ const response = await callback(data.url, init);
863
902
  const contentLength = response.headers.get("content-length");
864
903
  const knownSize = contentLength ? parseInt(contentLength, 10) : null;
865
904
  const isNetworkResponse = response.url && (response.url.startsWith("http://") || response.url.startsWith("https://"));
@@ -1217,13 +1256,6 @@ async function serializeResponse(response) {
1217
1256
  body
1218
1257
  };
1219
1258
  }
1220
- function deserializeRequest(data) {
1221
- return new Request(data.url, {
1222
- method: data.method,
1223
- headers: data.headers,
1224
- body: data.body
1225
- });
1226
- }
1227
1259
  function deserializeResponse(data) {
1228
1260
  return new Response(data.body, {
1229
1261
  status: data.status,
@@ -1329,5 +1361,152 @@ async function sendBodyStream(state, streamId, body) {
1329
1361
  state.uploadStreams.delete(streamId);
1330
1362
  }
1331
1363
  }
1364
+ function handleClientWsConnect(message, state) {
1365
+ const { isolateId, socketId, url, protocols } = message;
1366
+ let sockets = isolateClientWebSockets.get(isolateId);
1367
+ if (!sockets) {
1368
+ sockets = new Map;
1369
+ isolateClientWebSockets.set(isolateId, sockets);
1370
+ }
1371
+ const setupWebSocket = (ws) => {
1372
+ sockets.set(socketId, ws);
1373
+ ws.onopen = () => {
1374
+ const msg = {
1375
+ type: import_isolate_protocol.MessageType.CLIENT_WS_OPENED,
1376
+ isolateId,
1377
+ socketId,
1378
+ protocol: ws.protocol || "",
1379
+ extensions: ws.extensions || ""
1380
+ };
1381
+ sendMessage(state.socket, msg);
1382
+ };
1383
+ ws.onmessage = (event) => {
1384
+ let data;
1385
+ if (typeof event.data === "string") {
1386
+ data = event.data;
1387
+ } else if (event.data instanceof ArrayBuffer) {
1388
+ data = new Uint8Array(event.data);
1389
+ } else if (event.data instanceof Blob) {
1390
+ event.data.arrayBuffer().then((buffer) => {
1391
+ const msg2 = {
1392
+ type: import_isolate_protocol.MessageType.CLIENT_WS_MESSAGE,
1393
+ isolateId,
1394
+ socketId,
1395
+ data: new Uint8Array(buffer)
1396
+ };
1397
+ sendMessage(state.socket, msg2);
1398
+ });
1399
+ return;
1400
+ } else {
1401
+ data = String(event.data);
1402
+ }
1403
+ const msg = {
1404
+ type: import_isolate_protocol.MessageType.CLIENT_WS_MESSAGE,
1405
+ isolateId,
1406
+ socketId,
1407
+ data
1408
+ };
1409
+ sendMessage(state.socket, msg);
1410
+ };
1411
+ ws.onerror = () => {
1412
+ const msg = {
1413
+ type: import_isolate_protocol.MessageType.CLIENT_WS_ERROR,
1414
+ isolateId,
1415
+ socketId
1416
+ };
1417
+ sendMessage(state.socket, msg);
1418
+ };
1419
+ ws.onclose = (event) => {
1420
+ const msg = {
1421
+ type: import_isolate_protocol.MessageType.CLIENT_WS_CLOSED,
1422
+ isolateId,
1423
+ socketId,
1424
+ code: event.code,
1425
+ reason: event.reason,
1426
+ wasClean: event.wasClean
1427
+ };
1428
+ sendMessage(state.socket, msg);
1429
+ sockets?.delete(socketId);
1430
+ if (sockets?.size === 0) {
1431
+ isolateClientWebSockets.delete(isolateId);
1432
+ }
1433
+ };
1434
+ };
1435
+ const sendConnectionFailed = (reason) => {
1436
+ const errorMsg = {
1437
+ type: import_isolate_protocol.MessageType.CLIENT_WS_ERROR,
1438
+ isolateId,
1439
+ socketId
1440
+ };
1441
+ sendMessage(state.socket, errorMsg);
1442
+ const closeMsg = {
1443
+ type: import_isolate_protocol.MessageType.CLIENT_WS_CLOSED,
1444
+ isolateId,
1445
+ socketId,
1446
+ code: 1006,
1447
+ reason,
1448
+ wasClean: false
1449
+ };
1450
+ sendMessage(state.socket, closeMsg);
1451
+ };
1452
+ const callback = isolateWebSocketCallbacks.get(isolateId);
1453
+ if (callback) {
1454
+ try {
1455
+ const result = callback(url, protocols || []);
1456
+ if (result instanceof Promise) {
1457
+ result.then((ws) => {
1458
+ if (ws === null) {
1459
+ sendConnectionFailed("Connection blocked");
1460
+ } else {
1461
+ setupWebSocket(ws);
1462
+ }
1463
+ }).catch(() => {
1464
+ sendConnectionFailed("Callback error");
1465
+ });
1466
+ } else if (result === null) {
1467
+ sendConnectionFailed("Connection blocked");
1468
+ } else {
1469
+ setupWebSocket(result);
1470
+ }
1471
+ } catch {
1472
+ sendConnectionFailed("Callback error");
1473
+ }
1474
+ } else {
1475
+ try {
1476
+ const ws = protocols && protocols.length > 0 ? new WebSocket(url, protocols) : new WebSocket(url);
1477
+ setupWebSocket(ws);
1478
+ } catch {
1479
+ sendConnectionFailed("Connection failed");
1480
+ }
1481
+ }
1482
+ }
1483
+ function handleClientWsSend(message, state) {
1484
+ const { isolateId, socketId, data } = message;
1485
+ const sockets = isolateClientWebSockets.get(isolateId);
1486
+ const ws = sockets?.get(socketId);
1487
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
1488
+ return;
1489
+ }
1490
+ if (typeof data === "string" && data.startsWith("__BINARY__")) {
1491
+ const base64 = data.slice(10);
1492
+ const binary = Buffer.from(base64, "base64");
1493
+ ws.send(binary);
1494
+ } else if (data instanceof Uint8Array) {
1495
+ ws.send(data);
1496
+ } else {
1497
+ ws.send(data);
1498
+ }
1499
+ }
1500
+ function handleClientWsClose(message, state) {
1501
+ const { isolateId, socketId, code, reason } = message;
1502
+ const sockets = isolateClientWebSockets.get(isolateId);
1503
+ const ws = sockets?.get(socketId);
1504
+ if (!ws) {
1505
+ return;
1506
+ }
1507
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
1508
+ ws.close(code ?? 1000, reason ?? "");
1509
+ }
1510
+ }
1332
1511
 
1333
- //# debugId=D1B49E9A426F8F8B64756E2164756E21
1512
+ //# debugId=7A85E05BA8CAD49764756E2164756E21