@ricsam/isolate-client 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,16 +8,21 @@ import {
8
8
  STREAM_THRESHOLD,
9
9
  STREAM_CHUNK_SIZE,
10
10
  STREAM_DEFAULT_CREDIT,
11
- marshalValue
11
+ IsolateEvents,
12
+ ClientEvents,
13
+ marshalValue,
14
+ isPromiseRef,
15
+ isAsyncIteratorRef,
16
+ serializeResponse,
17
+ deserializeResponse
12
18
  } from "@ricsam/isolate-protocol";
13
19
  import {
14
20
  getDefaultPlaywrightHandlerMetadata
15
21
  } from "@ricsam/isolate-playwright/client";
16
- var DEFAULT_TIMEOUT = 30000;
17
- var TEST_REQUEST_TIMEOUT_BUFFER_MS = 1000;
18
22
  var isolateWsCallbacks = new Map;
19
23
  var isolateClientWebSockets = new Map;
20
24
  var isolateWebSocketCallbacks = new Map;
25
+ var isolateEventListeners = new Map;
21
26
  async function connect(options = {}) {
22
27
  const socket = await createSocket(options);
23
28
  const state = {
@@ -47,9 +52,6 @@ async function connect(options = {}) {
47
52
  socket.on("close", () => {
48
53
  state.connected = false;
49
54
  for (const [, pending] of state.pendingRequests) {
50
- if (pending.timeoutId) {
51
- clearTimeout(pending.timeoutId);
52
- }
53
55
  pending.reject(new Error("Connection closed"));
54
56
  }
55
57
  state.pendingRequests.clear();
@@ -88,7 +90,7 @@ async function connect(options = {}) {
88
90
  }
89
91
  function createSocket(options) {
90
92
  return new Promise((resolve, reject) => {
91
- const timeout = options.timeout ?? DEFAULT_TIMEOUT;
93
+ const timeout = options.timeout;
92
94
  let socket;
93
95
  const onError = (err) => {
94
96
  reject(err);
@@ -103,13 +105,15 @@ function createSocket(options) {
103
105
  socket = netConnect(options.port ?? 47891, options.host ?? "127.0.0.1", onConnect);
104
106
  }
105
107
  socket.on("error", onError);
106
- const timeoutId = setTimeout(() => {
107
- socket.destroy();
108
- reject(new Error("Connection timeout"));
109
- }, timeout);
110
- socket.once("connect", () => {
111
- clearTimeout(timeoutId);
112
- });
108
+ if (timeout && timeout > 0) {
109
+ const timeoutId = setTimeout(() => {
110
+ socket.destroy();
111
+ reject(new Error("Connection timeout"));
112
+ }, timeout);
113
+ socket.once("connect", () => {
114
+ clearTimeout(timeoutId);
115
+ });
116
+ }
113
117
  });
114
118
  }
115
119
  function handleMessage(message, state) {
@@ -119,8 +123,6 @@ function handleMessage(message, state) {
119
123
  const pending = state.pendingRequests.get(response.requestId);
120
124
  if (pending) {
121
125
  state.pendingRequests.delete(response.requestId);
122
- if (pending.timeoutId)
123
- clearTimeout(pending.timeoutId);
124
126
  pending.resolve(response.data);
125
127
  }
126
128
  break;
@@ -130,8 +132,6 @@ function handleMessage(message, state) {
130
132
  const pending = state.pendingRequests.get(response.requestId);
131
133
  if (pending) {
132
134
  state.pendingRequests.delete(response.requestId);
133
- if (pending.timeoutId)
134
- clearTimeout(pending.timeoutId);
135
135
  const error = new Error(response.message);
136
136
  if (response.details) {
137
137
  error.name = response.details.name;
@@ -158,42 +158,9 @@ function handleMessage(message, state) {
158
158
  }
159
159
  break;
160
160
  }
161
- case MessageType.WS_COMMAND: {
161
+ case MessageType.ISOLATE_EVENT: {
162
162
  const msg = message;
163
- const callbacks = isolateWsCallbacks.get(msg.isolateId);
164
- if (callbacks) {
165
- let data;
166
- if (msg.command.data instanceof Uint8Array) {
167
- data = msg.command.data.buffer.slice(msg.command.data.byteOffset, msg.command.data.byteOffset + msg.command.data.byteLength);
168
- } else {
169
- data = msg.command.data;
170
- }
171
- const cmd = {
172
- type: msg.command.type,
173
- connectionId: msg.command.connectionId,
174
- data,
175
- code: msg.command.code,
176
- reason: msg.command.reason
177
- };
178
- for (const cb of callbacks) {
179
- cb(cmd);
180
- }
181
- }
182
- break;
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);
163
+ handleIsolateEvent(msg, state);
197
164
  break;
198
165
  }
199
166
  case MessageType.RESPONSE_STREAM_START: {
@@ -263,8 +230,6 @@ function handleMessage(message, state) {
263
230
  const pending = state.pendingRequests.get(msg.requestId);
264
231
  if (pending) {
265
232
  state.pendingRequests.delete(msg.requestId);
266
- if (pending.timeoutId)
267
- clearTimeout(pending.timeoutId);
268
233
  const response = new Response(readableStream, {
269
234
  status: msg.metadata?.status ?? 200,
270
235
  statusText: msg.metadata?.statusText ?? "OK",
@@ -389,25 +354,24 @@ function sendMessage(socket, message) {
389
354
  const frame = buildFrame(message);
390
355
  socket.write(frame);
391
356
  }
392
- function sendRequest(state, message, timeout = DEFAULT_TIMEOUT) {
357
+ function sendRequest(state, message) {
393
358
  return new Promise((resolve, reject) => {
394
359
  if (!state.connected) {
395
360
  reject(new Error("Not connected"));
396
361
  return;
397
362
  }
398
363
  const requestId = message.requestId;
399
- const timeoutId = setTimeout(() => {
400
- state.pendingRequests.delete(requestId);
401
- reject(new Error("Request timeout"));
402
- }, timeout);
403
364
  state.pendingRequests.set(requestId, {
404
365
  resolve,
405
- reject,
406
- timeoutId
366
+ reject
407
367
  });
408
368
  sendMessage(state.socket, message);
409
369
  });
410
370
  }
371
+ function isBenignDisposeError(error) {
372
+ const message = error instanceof Error ? error.message : String(error ?? "");
373
+ return /isolate not owned by this connection|isolate not found|not connected|connection closed/i.test(message);
374
+ }
411
375
  async function createRuntime(state, options = {}, namespaceId) {
412
376
  const callbacks = {};
413
377
  if (options.console) {
@@ -548,11 +512,18 @@ async function createRuntime(state, options = {}, namespaceId) {
548
512
  const reused = result.reused ?? false;
549
513
  const wsCommandCallbacks = new Set;
550
514
  isolateWsCallbacks.set(isolateId, wsCommandCallbacks);
515
+ if (options.onWebSocketCommand) {
516
+ wsCommandCallbacks.add(options.onWebSocketCommand);
517
+ }
551
518
  if (options.webSocket) {
552
519
  isolateWebSocketCallbacks.set(isolateId, options.webSocket);
553
520
  }
554
521
  const fetchHandle = {
555
522
  async dispatchRequest(req, opts) {
523
+ const signal = opts?.signal;
524
+ if (signal?.aborted) {
525
+ throw new DOMException("The operation was aborted", "AbortError");
526
+ }
556
527
  const reqId = state.nextRequestId++;
557
528
  const serialized = await serializeRequestWithStreaming(state, req);
558
529
  const { bodyStream, ...serializableRequest } = serialized;
@@ -560,8 +531,7 @@ async function createRuntime(state, options = {}, namespaceId) {
560
531
  type: MessageType.DISPATCH_REQUEST,
561
532
  requestId: reqId,
562
533
  isolateId,
563
- request: serializableRequest,
564
- options: opts
534
+ request: serializableRequest
565
535
  };
566
536
  const handleResponse = (res) => {
567
537
  if (res.__streaming && res.response instanceof Response) {
@@ -569,15 +539,32 @@ async function createRuntime(state, options = {}, namespaceId) {
569
539
  }
570
540
  return deserializeResponse(res.response);
571
541
  };
572
- if (serialized.bodyStreamId !== undefined && bodyStream) {
573
- const streamId = serialized.bodyStreamId;
574
- const responsePromise = sendRequest(state, request2, opts?.timeout ?? DEFAULT_TIMEOUT);
575
- await sendBodyStream(state, streamId, bodyStream);
576
- const res = await responsePromise;
577
- return handleResponse(res);
578
- } else {
579
- const res = await sendRequest(state, request2, opts?.timeout ?? DEFAULT_TIMEOUT);
580
- return handleResponse(res);
542
+ let onAbort;
543
+ if (signal) {
544
+ onAbort = () => {
545
+ const pending = state.pendingRequests.get(reqId);
546
+ if (pending) {
547
+ state.pendingRequests.delete(reqId);
548
+ pending.reject(new DOMException("The operation was aborted", "AbortError"));
549
+ }
550
+ };
551
+ signal.addEventListener("abort", onAbort, { once: true });
552
+ }
553
+ try {
554
+ if (serialized.bodyStreamId !== undefined && bodyStream) {
555
+ const streamId = serialized.bodyStreamId;
556
+ const responsePromise = sendRequest(state, request2);
557
+ await sendBodyStream(state, streamId, bodyStream);
558
+ const res = await responsePromise;
559
+ return handleResponse(res);
560
+ } else {
561
+ const res = await sendRequest(state, request2);
562
+ return handleResponse(res);
563
+ }
564
+ } finally {
565
+ if (signal && onAbort) {
566
+ signal.removeEventListener("abort", onAbort);
567
+ }
581
568
  }
582
569
  },
583
570
  async getUpgradeRequest() {
@@ -724,8 +711,7 @@ async function createRuntime(state, options = {}, namespaceId) {
724
711
  isolateId,
725
712
  timeout
726
713
  };
727
- const requestTimeout = timeout === undefined ? DEFAULT_TIMEOUT : Math.max(timeout + TEST_REQUEST_TIMEOUT_BUFFER_MS, DEFAULT_TIMEOUT);
728
- return sendRequest(state, req, requestTimeout);
714
+ return sendRequest(state, req);
729
715
  },
730
716
  async hasTests() {
731
717
  if (!testEnvironmentEnabled) {
@@ -800,17 +786,47 @@ async function createRuntime(state, options = {}, namespaceId) {
800
786
  requestId: reqId,
801
787
  isolateId,
802
788
  code,
803
- filename: options2?.filename,
804
- maxExecutionMs: options2?.maxExecutionMs
789
+ filename: options2?.filename
805
790
  };
806
791
  await sendRequest(state, req);
807
792
  },
793
+ on(event, callback) {
794
+ let listeners = isolateEventListeners.get(isolateId);
795
+ if (!listeners) {
796
+ listeners = new Map;
797
+ isolateEventListeners.set(isolateId, listeners);
798
+ }
799
+ let eventListeners = listeners.get(event);
800
+ if (!eventListeners) {
801
+ eventListeners = new Set;
802
+ listeners.set(event, eventListeners);
803
+ }
804
+ eventListeners.add(callback);
805
+ return () => {
806
+ eventListeners.delete(callback);
807
+ if (eventListeners.size === 0) {
808
+ listeners.delete(event);
809
+ if (listeners.size === 0) {
810
+ isolateEventListeners.delete(isolateId);
811
+ }
812
+ }
813
+ };
814
+ },
815
+ emit(event, payload) {
816
+ sendMessage(state.socket, {
817
+ type: MessageType.CLIENT_EVENT,
818
+ isolateId,
819
+ event,
820
+ payload
821
+ });
822
+ },
808
823
  dispose: async () => {
809
824
  for (const cleanup of pageListenerCleanups) {
810
825
  cleanup();
811
826
  }
812
827
  isolateWsCallbacks.delete(isolateId);
813
828
  isolateWebSocketCallbacks.delete(isolateId);
829
+ isolateEventListeners.delete(isolateId);
814
830
  const clientSockets = isolateClientWebSockets.get(isolateId);
815
831
  if (clientSockets) {
816
832
  for (const ws of clientSockets.values()) {
@@ -826,7 +842,13 @@ async function createRuntime(state, options = {}, namespaceId) {
826
842
  requestId: reqId,
827
843
  isolateId
828
844
  };
829
- await sendRequest(state, req);
845
+ try {
846
+ await sendRequest(state, req);
847
+ } catch (error) {
848
+ if (!isBenignDisposeError(error)) {
849
+ throw error;
850
+ }
851
+ }
830
852
  }
831
853
  };
832
854
  }
@@ -858,8 +880,8 @@ function registerFetchCallback(state, callback) {
858
880
  const init = {
859
881
  method: data.method,
860
882
  headers: data.headers,
861
- rawBody: data.body,
862
- body: data.body,
883
+ rawBody: data.body ?? null,
884
+ body: data.body ?? null,
863
885
  signal: new AbortController().signal
864
886
  };
865
887
  const response = await callback(data.url, init);
@@ -1015,12 +1037,6 @@ var clientIteratorSessions = new Map;
1015
1037
  var nextClientIteratorId = 1;
1016
1038
  var returnedPromiseRegistry = new Map;
1017
1039
  var returnedIteratorRegistry = new Map;
1018
- function isPromiseRef(value) {
1019
- return typeof value === "object" && value !== null && value.__type === "PromiseRef";
1020
- }
1021
- function isAsyncIteratorRef(value) {
1022
- return typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
1023
- }
1024
1040
  function registerCustomFunctions(state, customFunctions) {
1025
1041
  const registrations = {};
1026
1042
  for (const [name, def] of Object.entries(customFunctions)) {
@@ -1204,29 +1220,6 @@ function registerCustomFunctions(state, customFunctions) {
1204
1220
  }
1205
1221
  return registrations;
1206
1222
  }
1207
- async function serializeResponse(response) {
1208
- const headers = [];
1209
- response.headers.forEach((value, key) => {
1210
- headers.push([key, value]);
1211
- });
1212
- let body = null;
1213
- if (response.body) {
1214
- body = new Uint8Array(await response.arrayBuffer());
1215
- }
1216
- return {
1217
- status: response.status,
1218
- statusText: response.statusText,
1219
- headers,
1220
- body
1221
- };
1222
- }
1223
- function deserializeResponse(data) {
1224
- return new Response(data.body, {
1225
- status: data.status,
1226
- statusText: data.statusText,
1227
- headers: data.headers
1228
- });
1229
- }
1230
1223
  async function serializeRequestWithStreaming(state, request) {
1231
1224
  const headers = [];
1232
1225
  request.headers.forEach((value, key) => {
@@ -1325,8 +1318,62 @@ async function sendBodyStream(state, streamId, body) {
1325
1318
  state.uploadStreams.delete(streamId);
1326
1319
  }
1327
1320
  }
1328
- function handleClientWsConnect(message, state) {
1329
- const { isolateId, socketId, url, protocols } = message;
1321
+ function handleIsolateEvent(message, state) {
1322
+ switch (message.event) {
1323
+ case IsolateEvents.WS_COMMAND: {
1324
+ const payload = message.payload;
1325
+ const callbacks = isolateWsCallbacks.get(message.isolateId);
1326
+ if (callbacks) {
1327
+ let data;
1328
+ if (payload.data instanceof Uint8Array) {
1329
+ data = payload.data.buffer.slice(payload.data.byteOffset, payload.data.byteOffset + payload.data.byteLength);
1330
+ } else {
1331
+ data = payload.data;
1332
+ }
1333
+ const cmd = {
1334
+ type: payload.type,
1335
+ connectionId: payload.connectionId,
1336
+ data,
1337
+ code: payload.code,
1338
+ reason: payload.reason
1339
+ };
1340
+ for (const cb of callbacks) {
1341
+ cb(cmd);
1342
+ }
1343
+ }
1344
+ break;
1345
+ }
1346
+ case IsolateEvents.WS_CLIENT_CONNECT: {
1347
+ const payload = message.payload;
1348
+ handleClientWsConnect(message.isolateId, payload, state);
1349
+ break;
1350
+ }
1351
+ case IsolateEvents.WS_CLIENT_SEND: {
1352
+ const payload = message.payload;
1353
+ handleClientWsSend(message.isolateId, payload, state);
1354
+ break;
1355
+ }
1356
+ case IsolateEvents.WS_CLIENT_CLOSE: {
1357
+ const payload = message.payload;
1358
+ handleClientWsClose(message.isolateId, payload, state);
1359
+ break;
1360
+ }
1361
+ default: {
1362
+ const listeners = isolateEventListeners.get(message.isolateId);
1363
+ if (listeners) {
1364
+ const eventListeners = listeners.get(message.event);
1365
+ if (eventListeners) {
1366
+ for (const cb of eventListeners) {
1367
+ cb(message.payload);
1368
+ }
1369
+ }
1370
+ }
1371
+ break;
1372
+ }
1373
+ }
1374
+ }
1375
+ function handleClientWsConnect(isolateId, payload, state) {
1376
+ const { socketId, url, protocols } = payload;
1330
1377
  let sockets = isolateClientWebSockets.get(isolateId);
1331
1378
  if (!sockets) {
1332
1379
  sockets = new Map;
@@ -1335,14 +1382,12 @@ function handleClientWsConnect(message, state) {
1335
1382
  const setupWebSocket = (ws) => {
1336
1383
  sockets.set(socketId, ws);
1337
1384
  ws.onopen = () => {
1338
- const msg = {
1339
- type: MessageType.CLIENT_WS_OPENED,
1385
+ sendMessage(state.socket, {
1386
+ type: MessageType.CLIENT_EVENT,
1340
1387
  isolateId,
1341
- socketId,
1342
- protocol: ws.protocol || "",
1343
- extensions: ws.extensions || ""
1344
- };
1345
- sendMessage(state.socket, msg);
1388
+ event: ClientEvents.WS_CLIENT_OPENED,
1389
+ payload: { socketId, protocol: ws.protocol || "", extensions: ws.extensions || "" }
1390
+ });
1346
1391
  };
1347
1392
  ws.onmessage = (event) => {
1348
1393
  let data;
@@ -1352,44 +1397,39 @@ function handleClientWsConnect(message, state) {
1352
1397
  data = new Uint8Array(event.data);
1353
1398
  } else if (event.data instanceof Blob) {
1354
1399
  event.data.arrayBuffer().then((buffer) => {
1355
- const msg2 = {
1356
- type: MessageType.CLIENT_WS_MESSAGE,
1400
+ sendMessage(state.socket, {
1401
+ type: MessageType.CLIENT_EVENT,
1357
1402
  isolateId,
1358
- socketId,
1359
- data: new Uint8Array(buffer)
1360
- };
1361
- sendMessage(state.socket, msg2);
1403
+ event: ClientEvents.WS_CLIENT_MESSAGE,
1404
+ payload: { socketId, data: new Uint8Array(buffer) }
1405
+ });
1362
1406
  });
1363
1407
  return;
1364
1408
  } else {
1365
1409
  data = String(event.data);
1366
1410
  }
1367
- const msg = {
1368
- type: MessageType.CLIENT_WS_MESSAGE,
1411
+ sendMessage(state.socket, {
1412
+ type: MessageType.CLIENT_EVENT,
1369
1413
  isolateId,
1370
- socketId,
1371
- data
1372
- };
1373
- sendMessage(state.socket, msg);
1414
+ event: ClientEvents.WS_CLIENT_MESSAGE,
1415
+ payload: { socketId, data }
1416
+ });
1374
1417
  };
1375
1418
  ws.onerror = () => {
1376
- const msg = {
1377
- type: MessageType.CLIENT_WS_ERROR,
1419
+ sendMessage(state.socket, {
1420
+ type: MessageType.CLIENT_EVENT,
1378
1421
  isolateId,
1379
- socketId
1380
- };
1381
- sendMessage(state.socket, msg);
1422
+ event: ClientEvents.WS_CLIENT_ERROR,
1423
+ payload: { socketId }
1424
+ });
1382
1425
  };
1383
1426
  ws.onclose = (event) => {
1384
- const msg = {
1385
- type: MessageType.CLIENT_WS_CLOSED,
1427
+ sendMessage(state.socket, {
1428
+ type: MessageType.CLIENT_EVENT,
1386
1429
  isolateId,
1387
- socketId,
1388
- code: event.code,
1389
- reason: event.reason,
1390
- wasClean: event.wasClean
1391
- };
1392
- sendMessage(state.socket, msg);
1430
+ event: ClientEvents.WS_CLIENT_CLOSED,
1431
+ payload: { socketId, code: event.code, reason: event.reason, wasClean: event.wasClean }
1432
+ });
1393
1433
  sockets?.delete(socketId);
1394
1434
  if (sockets?.size === 0) {
1395
1435
  isolateClientWebSockets.delete(isolateId);
@@ -1397,21 +1437,18 @@ function handleClientWsConnect(message, state) {
1397
1437
  };
1398
1438
  };
1399
1439
  const sendConnectionFailed = (reason) => {
1400
- const errorMsg = {
1401
- type: MessageType.CLIENT_WS_ERROR,
1440
+ sendMessage(state.socket, {
1441
+ type: MessageType.CLIENT_EVENT,
1402
1442
  isolateId,
1403
- socketId
1404
- };
1405
- sendMessage(state.socket, errorMsg);
1406
- const closeMsg = {
1407
- type: MessageType.CLIENT_WS_CLOSED,
1443
+ event: ClientEvents.WS_CLIENT_ERROR,
1444
+ payload: { socketId }
1445
+ });
1446
+ sendMessage(state.socket, {
1447
+ type: MessageType.CLIENT_EVENT,
1408
1448
  isolateId,
1409
- socketId,
1410
- code: 1006,
1411
- reason,
1412
- wasClean: false
1413
- };
1414
- sendMessage(state.socket, closeMsg);
1449
+ event: ClientEvents.WS_CLIENT_CLOSED,
1450
+ payload: { socketId, code: 1006, reason, wasClean: false }
1451
+ });
1415
1452
  };
1416
1453
  const callback = isolateWebSocketCallbacks.get(isolateId);
1417
1454
  if (callback) {
@@ -1444,8 +1481,8 @@ function handleClientWsConnect(message, state) {
1444
1481
  }
1445
1482
  }
1446
1483
  }
1447
- function handleClientWsSend(message, state) {
1448
- const { isolateId, socketId, data } = message;
1484
+ function handleClientWsSend(isolateId, payload, state) {
1485
+ const { socketId, data } = payload;
1449
1486
  const sockets = isolateClientWebSockets.get(isolateId);
1450
1487
  const ws = sockets?.get(socketId);
1451
1488
  if (!ws || ws.readyState !== WebSocket.OPEN) {
@@ -1461,8 +1498,8 @@ function handleClientWsSend(message, state) {
1461
1498
  ws.send(data);
1462
1499
  }
1463
1500
  }
1464
- function handleClientWsClose(message, state) {
1465
- const { isolateId, socketId, code, reason } = message;
1501
+ function handleClientWsClose(isolateId, payload, state) {
1502
+ const { socketId, code, reason } = payload;
1466
1503
  const sockets = isolateClientWebSockets.get(isolateId);
1467
1504
  const ws = sockets?.get(socketId);
1468
1505
  if (!ws) {
@@ -1473,7 +1510,8 @@ function handleClientWsClose(message, state) {
1473
1510
  }
1474
1511
  }
1475
1512
  export {
1513
+ isBenignDisposeError,
1476
1514
  connect
1477
1515
  };
1478
1516
 
1479
- //# debugId=D4986DB48143DFE264756E2164756E21
1517
+ //# debugId=A84790A2E8D5115464756E2164756E21