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