@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.
@@ -10,9 +10,14 @@ import {
10
10
  STREAM_DEFAULT_CREDIT,
11
11
  marshalValue
12
12
  } from "@ricsam/isolate-protocol";
13
- import { createPlaywrightHandler } from "@ricsam/isolate-playwright/client";
13
+ import {
14
+ getDefaultPlaywrightHandlerMetadata
15
+ } from "@ricsam/isolate-playwright/client";
14
16
  var DEFAULT_TIMEOUT = 30000;
17
+ var TEST_REQUEST_TIMEOUT_BUFFER_MS = 1000;
15
18
  var isolateWsCallbacks = new Map;
19
+ var isolateClientWebSockets = new Map;
20
+ var isolateWebSocketCallbacks = new Map;
16
21
  async function connect(options = {}) {
17
22
  const socket = await createSocket(options);
18
23
  const state = {
@@ -176,6 +181,21 @@ function handleMessage(message, state) {
176
181
  }
177
182
  break;
178
183
  }
184
+ case MessageType.CLIENT_WS_CONNECT: {
185
+ const msg = message;
186
+ handleClientWsConnect(msg, state);
187
+ break;
188
+ }
189
+ case MessageType.CLIENT_WS_SEND: {
190
+ const msg = message;
191
+ handleClientWsSend(msg, state);
192
+ break;
193
+ }
194
+ case MessageType.CLIENT_WS_CLOSE: {
195
+ const msg = message;
196
+ handleClientWsClose(msg, state);
197
+ break;
198
+ }
179
199
  case MessageType.RESPONSE_STREAM_START: {
180
200
  const msg = message;
181
201
  const receiver = {
@@ -411,74 +431,76 @@ async function createRuntime(state, options = {}, namespaceId) {
411
431
  const networkResponses = [];
412
432
  const pageListenerCleanups = [];
413
433
  if (options.playwright) {
414
- playwrightHandler = createPlaywrightHandler(options.playwright.page, {
415
- timeout: options.playwright.timeout,
416
- baseUrl: options.playwright.baseUrl
417
- });
434
+ playwrightHandler = options.playwright.handler;
435
+ if (!playwrightHandler) {
436
+ throw new Error("playwright.handler is required when using playwright options");
437
+ }
438
+ const page = getDefaultPlaywrightHandlerMetadata(playwrightHandler)?.page;
418
439
  const handlerCallbackId = state.nextCallbackId++;
419
440
  state.callbacks.set(handlerCallbackId, async (opJson) => {
420
441
  const op = JSON.parse(opJson);
421
442
  const result2 = await playwrightHandler(op);
422
443
  return JSON.stringify(result2);
423
444
  });
424
- const page = options.playwright.page;
425
- const onConsole = (msg) => {
426
- const entry = {
427
- level: msg.type(),
428
- stdout: msg.text(),
429
- timestamp: Date.now()
445
+ if (page) {
446
+ const onConsole = (msg) => {
447
+ const entry = {
448
+ level: msg.type(),
449
+ stdout: msg.text(),
450
+ timestamp: Date.now()
451
+ };
452
+ browserConsoleLogs.push(entry);
453
+ if (options.playwright.onEvent) {
454
+ options.playwright.onEvent({
455
+ type: "browserConsoleLog",
456
+ ...entry
457
+ });
458
+ }
459
+ if (options.playwright.console && options.console?.onEntry) {
460
+ options.console.onEntry({
461
+ type: "browserOutput",
462
+ ...entry
463
+ });
464
+ } else if (options.playwright.console) {
465
+ const prefix = entry.level === "error" ? "[browser:error]" : "[browser]";
466
+ console.log(prefix, entry.stdout);
467
+ }
430
468
  };
431
- browserConsoleLogs.push(entry);
432
- if (options.playwright.onEvent) {
433
- options.playwright.onEvent({
434
- type: "browserConsoleLog",
435
- ...entry
436
- });
437
- }
438
- if (options.playwright.console && options.console?.onEntry) {
439
- options.console.onEntry({
440
- type: "browserOutput",
441
- ...entry
442
- });
443
- } else if (options.playwright.console) {
444
- const prefix = entry.level === "error" ? "[browser:error]" : "[browser]";
445
- console.log(prefix, entry.stdout);
446
- }
447
- };
448
- const onRequest = (request2) => {
449
- const info = {
450
- url: request2.url(),
451
- method: request2.method(),
452
- headers: request2.headers(),
453
- timestamp: Date.now()
469
+ const onRequest = (request2) => {
470
+ const info = {
471
+ url: request2.url(),
472
+ method: request2.method(),
473
+ headers: request2.headers(),
474
+ timestamp: Date.now()
475
+ };
476
+ networkRequests.push(info);
477
+ if (options.playwright.onEvent) {
478
+ options.playwright.onEvent({
479
+ type: "networkRequest",
480
+ ...info
481
+ });
482
+ }
454
483
  };
455
- networkRequests.push(info);
456
- if (options.playwright.onEvent) {
457
- options.playwright.onEvent({
458
- type: "networkRequest",
459
- ...info
460
- });
461
- }
462
- };
463
- const onResponse = (response) => {
464
- const info = {
465
- url: response.url(),
466
- status: response.status(),
467
- headers: response.headers(),
468
- timestamp: Date.now()
484
+ const onResponse = (response) => {
485
+ const info = {
486
+ url: response.url(),
487
+ status: response.status(),
488
+ headers: response.headers(),
489
+ timestamp: Date.now()
490
+ };
491
+ networkResponses.push(info);
492
+ if (options.playwright.onEvent) {
493
+ options.playwright.onEvent({
494
+ type: "networkResponse",
495
+ ...info
496
+ });
497
+ }
469
498
  };
470
- networkResponses.push(info);
471
- if (options.playwright.onEvent) {
472
- options.playwright.onEvent({
473
- type: "networkResponse",
474
- ...info
475
- });
476
- }
477
- };
478
- page.on("console", onConsole);
479
- page.on("request", onRequest);
480
- page.on("response", onResponse);
481
- pageListenerCleanups.push(() => page.removeListener("console", onConsole), () => page.removeListener("request", onRequest), () => page.removeListener("response", onResponse));
499
+ page.on("console", onConsole);
500
+ page.on("request", onRequest);
501
+ page.on("response", onResponse);
502
+ pageListenerCleanups.push(() => page.removeListener("console", onConsole), () => page.removeListener("request", onRequest), () => page.removeListener("response", onResponse));
503
+ }
482
504
  callbacks.playwright = {
483
505
  handlerCallbackId,
484
506
  console: options.playwright.console && !options.console?.onEntry
@@ -526,6 +548,9 @@ async function createRuntime(state, options = {}, namespaceId) {
526
548
  const reused = result.reused ?? false;
527
549
  const wsCommandCallbacks = new Set;
528
550
  isolateWsCallbacks.set(isolateId, wsCommandCallbacks);
551
+ if (options.webSocket) {
552
+ isolateWebSocketCallbacks.set(isolateId, options.webSocket);
553
+ }
529
554
  const fetchHandle = {
530
555
  async dispatchRequest(req, opts) {
531
556
  const reqId = state.nextRequestId++;
@@ -699,7 +724,8 @@ async function createRuntime(state, options = {}, namespaceId) {
699
724
  isolateId,
700
725
  timeout
701
726
  };
702
- return sendRequest(state, req, timeout ?? DEFAULT_TIMEOUT);
727
+ const requestTimeout = timeout === undefined ? DEFAULT_TIMEOUT : Math.max(timeout + TEST_REQUEST_TIMEOUT_BUFFER_MS, DEFAULT_TIMEOUT);
728
+ return sendRequest(state, req, requestTimeout);
703
729
  },
704
730
  async hasTests() {
705
731
  if (!testEnvironmentEnabled) {
@@ -741,7 +767,7 @@ async function createRuntime(state, options = {}, namespaceId) {
741
767
  const playwrightHandle = {
742
768
  getCollectedData() {
743
769
  if (!playwrightEnabled) {
744
- throw new Error("Playwright not configured. Provide playwright.page in createRuntime options.");
770
+ throw new Error("Playwright not configured. Provide playwright.handler in createRuntime options.");
745
771
  }
746
772
  return {
747
773
  browserConsoleLogs: [...browserConsoleLogs],
@@ -751,7 +777,7 @@ async function createRuntime(state, options = {}, namespaceId) {
751
777
  },
752
778
  clearCollectedData() {
753
779
  if (!playwrightEnabled) {
754
- throw new Error("Playwright not configured. Provide playwright.page in createRuntime options.");
780
+ throw new Error("Playwright not configured. Provide playwright.handler in createRuntime options.");
755
781
  }
756
782
  browserConsoleLogs.length = 0;
757
783
  networkRequests.length = 0;
@@ -760,7 +786,6 @@ async function createRuntime(state, options = {}, namespaceId) {
760
786
  };
761
787
  return {
762
788
  id: isolateId,
763
- isolateId,
764
789
  reused,
765
790
  fetch: fetchHandle,
766
791
  timers: timersHandle,
@@ -776,8 +801,7 @@ async function createRuntime(state, options = {}, namespaceId) {
776
801
  isolateId,
777
802
  code,
778
803
  filename: options2?.filename,
779
- maxExecutionMs: options2?.maxExecutionMs,
780
- module: true
804
+ maxExecutionMs: options2?.maxExecutionMs
781
805
  };
782
806
  await sendRequest(state, req);
783
807
  },
@@ -786,6 +810,16 @@ async function createRuntime(state, options = {}, namespaceId) {
786
810
  cleanup();
787
811
  }
788
812
  isolateWsCallbacks.delete(isolateId);
813
+ isolateWebSocketCallbacks.delete(isolateId);
814
+ const clientSockets = isolateClientWebSockets.get(isolateId);
815
+ if (clientSockets) {
816
+ for (const ws of clientSockets.values()) {
817
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
818
+ ws.close(1000, "Isolate disposed");
819
+ }
820
+ }
821
+ isolateClientWebSockets.delete(isolateId);
822
+ }
789
823
  const reqId = state.nextRequestId++;
790
824
  const req = {
791
825
  type: MessageType.DISPOSE_RUNTIME,
@@ -820,8 +854,15 @@ function registerFetchCallback(state, callback) {
820
854
  const callbackId = state.nextCallbackId++;
821
855
  state.callbacksNeedingRequestId.add(callbackId);
822
856
  state.callbacks.set(callbackId, async (serialized, requestId) => {
823
- const request = deserializeRequest(serialized);
824
- const response = await callback(request);
857
+ const data = serialized;
858
+ const init = {
859
+ method: data.method,
860
+ headers: data.headers,
861
+ rawBody: data.body,
862
+ body: data.body,
863
+ signal: new AbortController().signal
864
+ };
865
+ const response = await callback(data.url, init);
825
866
  const contentLength = response.headers.get("content-length");
826
867
  const knownSize = contentLength ? parseInt(contentLength, 10) : null;
827
868
  const isNetworkResponse = response.url && (response.url.startsWith("http://") || response.url.startsWith("https://"));
@@ -1179,13 +1220,6 @@ async function serializeResponse(response) {
1179
1220
  body
1180
1221
  };
1181
1222
  }
1182
- function deserializeRequest(data) {
1183
- return new Request(data.url, {
1184
- method: data.method,
1185
- headers: data.headers,
1186
- body: data.body
1187
- });
1188
- }
1189
1223
  function deserializeResponse(data) {
1190
1224
  return new Response(data.body, {
1191
1225
  status: data.status,
@@ -1291,8 +1325,155 @@ async function sendBodyStream(state, streamId, body) {
1291
1325
  state.uploadStreams.delete(streamId);
1292
1326
  }
1293
1327
  }
1328
+ function handleClientWsConnect(message, state) {
1329
+ const { isolateId, socketId, url, protocols } = message;
1330
+ let sockets = isolateClientWebSockets.get(isolateId);
1331
+ if (!sockets) {
1332
+ sockets = new Map;
1333
+ isolateClientWebSockets.set(isolateId, sockets);
1334
+ }
1335
+ const setupWebSocket = (ws) => {
1336
+ sockets.set(socketId, ws);
1337
+ ws.onopen = () => {
1338
+ const msg = {
1339
+ type: MessageType.CLIENT_WS_OPENED,
1340
+ isolateId,
1341
+ socketId,
1342
+ protocol: ws.protocol || "",
1343
+ extensions: ws.extensions || ""
1344
+ };
1345
+ sendMessage(state.socket, msg);
1346
+ };
1347
+ ws.onmessage = (event) => {
1348
+ let data;
1349
+ if (typeof event.data === "string") {
1350
+ data = event.data;
1351
+ } else if (event.data instanceof ArrayBuffer) {
1352
+ data = new Uint8Array(event.data);
1353
+ } else if (event.data instanceof Blob) {
1354
+ event.data.arrayBuffer().then((buffer) => {
1355
+ const msg2 = {
1356
+ type: MessageType.CLIENT_WS_MESSAGE,
1357
+ isolateId,
1358
+ socketId,
1359
+ data: new Uint8Array(buffer)
1360
+ };
1361
+ sendMessage(state.socket, msg2);
1362
+ });
1363
+ return;
1364
+ } else {
1365
+ data = String(event.data);
1366
+ }
1367
+ const msg = {
1368
+ type: MessageType.CLIENT_WS_MESSAGE,
1369
+ isolateId,
1370
+ socketId,
1371
+ data
1372
+ };
1373
+ sendMessage(state.socket, msg);
1374
+ };
1375
+ ws.onerror = () => {
1376
+ const msg = {
1377
+ type: MessageType.CLIENT_WS_ERROR,
1378
+ isolateId,
1379
+ socketId
1380
+ };
1381
+ sendMessage(state.socket, msg);
1382
+ };
1383
+ ws.onclose = (event) => {
1384
+ const msg = {
1385
+ type: MessageType.CLIENT_WS_CLOSED,
1386
+ isolateId,
1387
+ socketId,
1388
+ code: event.code,
1389
+ reason: event.reason,
1390
+ wasClean: event.wasClean
1391
+ };
1392
+ sendMessage(state.socket, msg);
1393
+ sockets?.delete(socketId);
1394
+ if (sockets?.size === 0) {
1395
+ isolateClientWebSockets.delete(isolateId);
1396
+ }
1397
+ };
1398
+ };
1399
+ const sendConnectionFailed = (reason) => {
1400
+ const errorMsg = {
1401
+ type: MessageType.CLIENT_WS_ERROR,
1402
+ isolateId,
1403
+ socketId
1404
+ };
1405
+ sendMessage(state.socket, errorMsg);
1406
+ const closeMsg = {
1407
+ type: MessageType.CLIENT_WS_CLOSED,
1408
+ isolateId,
1409
+ socketId,
1410
+ code: 1006,
1411
+ reason,
1412
+ wasClean: false
1413
+ };
1414
+ sendMessage(state.socket, closeMsg);
1415
+ };
1416
+ const callback = isolateWebSocketCallbacks.get(isolateId);
1417
+ if (callback) {
1418
+ try {
1419
+ const result = callback(url, protocols || []);
1420
+ if (result instanceof Promise) {
1421
+ result.then((ws) => {
1422
+ if (ws === null) {
1423
+ sendConnectionFailed("Connection blocked");
1424
+ } else {
1425
+ setupWebSocket(ws);
1426
+ }
1427
+ }).catch(() => {
1428
+ sendConnectionFailed("Callback error");
1429
+ });
1430
+ } else if (result === null) {
1431
+ sendConnectionFailed("Connection blocked");
1432
+ } else {
1433
+ setupWebSocket(result);
1434
+ }
1435
+ } catch {
1436
+ sendConnectionFailed("Callback error");
1437
+ }
1438
+ } else {
1439
+ try {
1440
+ const ws = protocols && protocols.length > 0 ? new WebSocket(url, protocols) : new WebSocket(url);
1441
+ setupWebSocket(ws);
1442
+ } catch {
1443
+ sendConnectionFailed("Connection failed");
1444
+ }
1445
+ }
1446
+ }
1447
+ function handleClientWsSend(message, state) {
1448
+ const { isolateId, socketId, data } = message;
1449
+ const sockets = isolateClientWebSockets.get(isolateId);
1450
+ const ws = sockets?.get(socketId);
1451
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
1452
+ return;
1453
+ }
1454
+ if (typeof data === "string" && data.startsWith("__BINARY__")) {
1455
+ const base64 = data.slice(10);
1456
+ const binary = Buffer.from(base64, "base64");
1457
+ ws.send(binary);
1458
+ } else if (data instanceof Uint8Array) {
1459
+ ws.send(data);
1460
+ } else {
1461
+ ws.send(data);
1462
+ }
1463
+ }
1464
+ function handleClientWsClose(message, state) {
1465
+ const { isolateId, socketId, code, reason } = message;
1466
+ const sockets = isolateClientWebSockets.get(isolateId);
1467
+ const ws = sockets?.get(socketId);
1468
+ if (!ws) {
1469
+ return;
1470
+ }
1471
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
1472
+ ws.close(code ?? 1000, reason ?? "");
1473
+ }
1474
+ }
1294
1475
  export {
1295
1476
  connect
1296
1477
  };
1297
1478
 
1298
- //# debugId=5BC447A77FF8EEAE64756E2164756E21
1479
+ //# debugId=D4986DB48143DFE264756E2164756E21