@ricsam/isolate-daemon 0.1.1 → 0.1.5
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 +38 -7
- package/bin/daemon.js +1 -1
- package/dist/cjs/connection.cjs +1058 -133
- package/dist/cjs/connection.cjs.map +3 -3
- package/dist/cjs/index.cjs +2 -2
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/connection.mjs +1057 -138
- package/dist/mjs/connection.mjs.map +3 -3
- package/dist/mjs/index.mjs +2 -2
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/types/types.d.ts +48 -11
- package/package.json +1 -1
package/dist/mjs/connection.mjs
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/isolate-daemon/src/connection.ts
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
|
+
import ivm from "isolated-vm";
|
|
4
5
|
import {
|
|
5
6
|
createFrameParser,
|
|
6
7
|
buildFrame,
|
|
7
8
|
MessageType,
|
|
8
|
-
ErrorCode
|
|
9
|
+
ErrorCode,
|
|
10
|
+
STREAM_THRESHOLD,
|
|
11
|
+
STREAM_CHUNK_SIZE,
|
|
12
|
+
STREAM_DEFAULT_CREDIT,
|
|
13
|
+
marshalValue,
|
|
14
|
+
unmarshalValue
|
|
9
15
|
} from "@ricsam/isolate-protocol";
|
|
10
16
|
import { createCallbackFileSystemHandler } from "./callback-fs-handler.mjs";
|
|
11
17
|
import {
|
|
12
18
|
setupTestEnvironment,
|
|
13
|
-
runTests as runTestsInContext
|
|
19
|
+
runTests as runTestsInContext,
|
|
20
|
+
hasTests as hasTestsInContext,
|
|
21
|
+
getTestCount as getTestCountInContext
|
|
14
22
|
} from "@ricsam/isolate-test-environment";
|
|
15
23
|
import {
|
|
16
|
-
setupPlaywright
|
|
17
|
-
runPlaywrightTests,
|
|
18
|
-
resetPlaywrightTests
|
|
24
|
+
setupPlaywright
|
|
19
25
|
} from "@ricsam/isolate-playwright";
|
|
20
|
-
import {
|
|
21
|
-
|
|
26
|
+
import {
|
|
27
|
+
createInternalRuntime
|
|
28
|
+
} from "@ricsam/isolate-runtime";
|
|
22
29
|
function handleConnection(socket, state) {
|
|
23
30
|
const connection = {
|
|
24
31
|
socket,
|
|
@@ -27,7 +34,9 @@ function handleConnection(socket, state) {
|
|
|
27
34
|
pendingCallbacks: new Map,
|
|
28
35
|
nextRequestId: 1,
|
|
29
36
|
nextCallbackId: 1,
|
|
30
|
-
nextStreamId: 1
|
|
37
|
+
nextStreamId: 1,
|
|
38
|
+
activeStreams: new Map,
|
|
39
|
+
streamReceivers: new Map
|
|
31
40
|
};
|
|
32
41
|
state.connections.set(socket, connection);
|
|
33
42
|
const parser = createFrameParser();
|
|
@@ -51,12 +60,6 @@ function handleConnection(socket, state) {
|
|
|
51
60
|
if (instance.playwrightHandle) {
|
|
52
61
|
instance.playwrightHandle.dispose();
|
|
53
62
|
}
|
|
54
|
-
if (instance.browserContext) {
|
|
55
|
-
instance.browserContext.close().catch(() => {});
|
|
56
|
-
}
|
|
57
|
-
if (instance.browser) {
|
|
58
|
-
instance.browser.close().catch(() => {});
|
|
59
|
-
}
|
|
60
63
|
instance.runtime.dispose();
|
|
61
64
|
} catch {}
|
|
62
65
|
state.isolates.delete(isolateId);
|
|
@@ -108,20 +111,56 @@ async function handleMessage(message, connection, state) {
|
|
|
108
111
|
case MessageType.DISPATCH_REQUEST:
|
|
109
112
|
await handleDispatchRequest(message, connection, state);
|
|
110
113
|
break;
|
|
111
|
-
case MessageType.TICK:
|
|
112
|
-
await handleTick(message, connection, state);
|
|
113
|
-
break;
|
|
114
114
|
case MessageType.CALLBACK_RESPONSE:
|
|
115
115
|
handleCallbackResponse(message, connection);
|
|
116
116
|
break;
|
|
117
|
-
case MessageType.
|
|
118
|
-
await
|
|
117
|
+
case MessageType.WS_OPEN:
|
|
118
|
+
await handleWsOpen(message, connection, state);
|
|
119
|
+
break;
|
|
120
|
+
case MessageType.WS_MESSAGE:
|
|
121
|
+
await handleWsMessage(message, connection, state);
|
|
122
|
+
break;
|
|
123
|
+
case MessageType.WS_CLOSE:
|
|
124
|
+
await handleWsClose(message, connection, state);
|
|
125
|
+
break;
|
|
126
|
+
case MessageType.FETCH_GET_UPGRADE_REQUEST:
|
|
127
|
+
await handleFetchGetUpgradeRequest(message, connection, state);
|
|
128
|
+
break;
|
|
129
|
+
case MessageType.FETCH_HAS_SERVE_HANDLER:
|
|
130
|
+
await handleFetchHasServeHandler(message, connection, state);
|
|
131
|
+
break;
|
|
132
|
+
case MessageType.FETCH_HAS_ACTIVE_CONNECTIONS:
|
|
133
|
+
await handleFetchHasActiveConnections(message, connection, state);
|
|
134
|
+
break;
|
|
135
|
+
case MessageType.FETCH_WS_ERROR:
|
|
136
|
+
await handleFetchWsError(message, connection, state);
|
|
137
|
+
break;
|
|
138
|
+
case MessageType.TIMERS_CLEAR_ALL:
|
|
139
|
+
await handleTimersClearAll(message, connection, state);
|
|
140
|
+
break;
|
|
141
|
+
case MessageType.CONSOLE_RESET:
|
|
142
|
+
await handleConsoleReset(message, connection, state);
|
|
143
|
+
break;
|
|
144
|
+
case MessageType.CONSOLE_GET_TIMERS:
|
|
145
|
+
await handleConsoleGetTimers(message, connection, state);
|
|
146
|
+
break;
|
|
147
|
+
case MessageType.CONSOLE_GET_COUNTERS:
|
|
148
|
+
await handleConsoleGetCounters(message, connection, state);
|
|
149
|
+
break;
|
|
150
|
+
case MessageType.CONSOLE_GET_GROUP_DEPTH:
|
|
151
|
+
await handleConsoleGetGroupDepth(message, connection, state);
|
|
119
152
|
break;
|
|
120
153
|
case MessageType.RUN_TESTS:
|
|
121
154
|
await handleRunTests(message, connection, state);
|
|
122
155
|
break;
|
|
123
|
-
case MessageType.
|
|
124
|
-
await
|
|
156
|
+
case MessageType.RESET_TEST_ENV:
|
|
157
|
+
await handleResetTestEnv(message, connection, state);
|
|
158
|
+
break;
|
|
159
|
+
case MessageType.HAS_TESTS:
|
|
160
|
+
await handleHasTests(message, connection, state);
|
|
161
|
+
break;
|
|
162
|
+
case MessageType.GET_TEST_COUNT:
|
|
163
|
+
await handleGetTestCount(message, connection, state);
|
|
125
164
|
break;
|
|
126
165
|
case MessageType.RUN_PLAYWRIGHT_TESTS:
|
|
127
166
|
await handleRunPlaywrightTests(message, connection, state);
|
|
@@ -132,9 +171,24 @@ async function handleMessage(message, connection, state) {
|
|
|
132
171
|
case MessageType.GET_COLLECTED_DATA:
|
|
133
172
|
await handleGetCollectedData(message, connection, state);
|
|
134
173
|
break;
|
|
174
|
+
case MessageType.CLEAR_COLLECTED_DATA:
|
|
175
|
+
await handleClearCollectedData(message, connection, state);
|
|
176
|
+
break;
|
|
135
177
|
case MessageType.PING:
|
|
136
178
|
sendMessage(connection.socket, { type: MessageType.PONG });
|
|
137
179
|
break;
|
|
180
|
+
case MessageType.STREAM_PUSH:
|
|
181
|
+
handleStreamPush(message, connection);
|
|
182
|
+
break;
|
|
183
|
+
case MessageType.STREAM_PULL:
|
|
184
|
+
handleStreamPull(message, connection);
|
|
185
|
+
break;
|
|
186
|
+
case MessageType.STREAM_CLOSE:
|
|
187
|
+
handleStreamClose(message, connection);
|
|
188
|
+
break;
|
|
189
|
+
case MessageType.STREAM_ERROR:
|
|
190
|
+
handleStreamError(message, connection);
|
|
191
|
+
break;
|
|
138
192
|
default:
|
|
139
193
|
sendError(connection.socket, message.requestId ?? 0, ErrorCode.UNKNOWN_MESSAGE_TYPE, `Unknown message type: ${message.type}`);
|
|
140
194
|
}
|
|
@@ -149,16 +203,16 @@ async function handleCreateRuntime(message, connection, state) {
|
|
|
149
203
|
const consoleCallbacks = message.options.callbacks?.console;
|
|
150
204
|
const fetchCallback = message.options.callbacks?.fetch;
|
|
151
205
|
const fsCallbacks = message.options.callbacks?.fs;
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
206
|
+
const moduleLoaderCallback = message.options.callbacks?.moduleLoader;
|
|
207
|
+
const customCallbacks = message.options.callbacks?.custom;
|
|
208
|
+
const pendingCallbacks = [];
|
|
209
|
+
const runtime = await createInternalRuntime({
|
|
210
|
+
memoryLimitMB: message.options.memoryLimitMB ?? state.options.defaultMemoryLimitMB,
|
|
211
|
+
cwd: message.options.cwd,
|
|
212
|
+
console: consoleCallbacks?.onEntry ? {
|
|
213
|
+
onEntry: (entry) => {
|
|
214
|
+
const promise = invokeClientCallback(connection, consoleCallbacks.onEntry.callbackId, [entry]).catch(() => {});
|
|
215
|
+
pendingCallbacks.push(promise);
|
|
162
216
|
}
|
|
163
217
|
} : undefined,
|
|
164
218
|
fetch: fetchCallback ? {
|
|
@@ -185,14 +239,25 @@ async function handleCreateRuntime(message, connection, state) {
|
|
|
185
239
|
ownerConnection: connection.socket,
|
|
186
240
|
callbacks: new Map,
|
|
187
241
|
createdAt: Date.now(),
|
|
188
|
-
lastActivity: Date.now()
|
|
242
|
+
lastActivity: Date.now(),
|
|
243
|
+
pendingCallbacks,
|
|
244
|
+
returnedCallbacks: new Map,
|
|
245
|
+
returnedPromises: new Map,
|
|
246
|
+
returnedIterators: new Map,
|
|
247
|
+
nextLocalCallbackId: 1e6
|
|
189
248
|
};
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
249
|
+
if (moduleLoaderCallback) {
|
|
250
|
+
instance.moduleLoaderCallbackId = moduleLoaderCallback.callbackId;
|
|
251
|
+
instance.moduleCache = new Map;
|
|
252
|
+
}
|
|
253
|
+
if (customCallbacks) {
|
|
254
|
+
await setupCustomFunctions(runtime.context, customCallbacks, connection, instance);
|
|
255
|
+
}
|
|
256
|
+
if (consoleCallbacks?.onEntry) {
|
|
257
|
+
instance.callbacks.set(consoleCallbacks.onEntry.callbackId, {
|
|
258
|
+
...consoleCallbacks.onEntry,
|
|
259
|
+
name: "onEntry"
|
|
260
|
+
});
|
|
196
261
|
}
|
|
197
262
|
if (fetchCallback) {
|
|
198
263
|
instance.callbacks.set(fetchCallback.callbackId, fetchCallback);
|
|
@@ -204,9 +269,86 @@ async function handleCreateRuntime(message, connection, state) {
|
|
|
204
269
|
}
|
|
205
270
|
}
|
|
206
271
|
}
|
|
272
|
+
if (moduleLoaderCallback) {
|
|
273
|
+
instance.callbacks.set(moduleLoaderCallback.callbackId, moduleLoaderCallback);
|
|
274
|
+
}
|
|
275
|
+
if (customCallbacks) {
|
|
276
|
+
for (const [name, reg] of Object.entries(customCallbacks)) {
|
|
277
|
+
if (reg) {
|
|
278
|
+
instance.callbacks.set(reg.callbackId, { ...reg, name });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (message.options.testEnvironment) {
|
|
283
|
+
const testEnvOption = message.options.testEnvironment;
|
|
284
|
+
const testEnvOptions = typeof testEnvOption === "object" ? testEnvOption : undefined;
|
|
285
|
+
const onEventCallback = testEnvOptions?.callbacks?.onEvent;
|
|
286
|
+
await setupTestEnvironment(runtime.context, {
|
|
287
|
+
onEvent: onEventCallback ? (event) => {
|
|
288
|
+
const promise = invokeClientCallback(connection, onEventCallback.callbackId, [JSON.stringify(event)]).catch(() => {});
|
|
289
|
+
pendingCallbacks.push(promise);
|
|
290
|
+
} : undefined,
|
|
291
|
+
testTimeout: testEnvOptions?.testTimeout
|
|
292
|
+
});
|
|
293
|
+
instance.testEnvironmentEnabled = true;
|
|
294
|
+
if (onEventCallback) {
|
|
295
|
+
instance.callbacks.set(onEventCallback.callbackId, {
|
|
296
|
+
...onEventCallback,
|
|
297
|
+
name: "testEnvironment.onEvent"
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const playwrightCallbacks = message.options.callbacks?.playwright;
|
|
302
|
+
if (playwrightCallbacks) {
|
|
303
|
+
const handler = async (op) => {
|
|
304
|
+
try {
|
|
305
|
+
const resultJson = await invokeClientCallback(connection, playwrightCallbacks.handlerCallbackId, [JSON.stringify(op)]);
|
|
306
|
+
return JSON.parse(resultJson);
|
|
307
|
+
} catch (err) {
|
|
308
|
+
const error = err;
|
|
309
|
+
return { ok: false, error: { name: error.name, message: error.message } };
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
instance.playwrightHandle = await setupPlaywright(runtime.context, {
|
|
313
|
+
handler,
|
|
314
|
+
console: playwrightCallbacks.console,
|
|
315
|
+
onEvent: (event) => {
|
|
316
|
+
if (event.type === "browserConsoleLog" && playwrightCallbacks.onBrowserConsoleLogCallbackId) {
|
|
317
|
+
const promise = invokeClientCallback(connection, playwrightCallbacks.onBrowserConsoleLogCallbackId, [{ level: event.level, args: event.args, timestamp: event.timestamp }]).catch(() => {});
|
|
318
|
+
pendingCallbacks.push(promise);
|
|
319
|
+
} else if (event.type === "networkRequest" && playwrightCallbacks.onNetworkRequestCallbackId) {
|
|
320
|
+
const promise = invokeClientCallback(connection, playwrightCallbacks.onNetworkRequestCallbackId, [event]).catch(() => {});
|
|
321
|
+
pendingCallbacks.push(promise);
|
|
322
|
+
} else if (event.type === "networkResponse" && playwrightCallbacks.onNetworkResponseCallbackId) {
|
|
323
|
+
const promise = invokeClientCallback(connection, playwrightCallbacks.onNetworkResponseCallbackId, [event]).catch(() => {});
|
|
324
|
+
pendingCallbacks.push(promise);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
207
329
|
state.isolates.set(isolateId, instance);
|
|
208
330
|
connection.isolates.add(isolateId);
|
|
209
331
|
state.stats.totalIsolatesCreated++;
|
|
332
|
+
instance.runtime.fetch.onWebSocketCommand((cmd) => {
|
|
333
|
+
let data;
|
|
334
|
+
if (cmd.data instanceof ArrayBuffer) {
|
|
335
|
+
data = new Uint8Array(cmd.data);
|
|
336
|
+
} else {
|
|
337
|
+
data = cmd.data;
|
|
338
|
+
}
|
|
339
|
+
const wsCommandMsg = {
|
|
340
|
+
type: MessageType.WS_COMMAND,
|
|
341
|
+
isolateId,
|
|
342
|
+
command: {
|
|
343
|
+
type: cmd.type,
|
|
344
|
+
connectionId: cmd.connectionId,
|
|
345
|
+
data,
|
|
346
|
+
code: cmd.code,
|
|
347
|
+
reason: cmd.reason
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
sendMessage(connection.socket, wsCommandMsg);
|
|
351
|
+
});
|
|
210
352
|
sendOk(connection.socket, message.requestId, { isolateId });
|
|
211
353
|
} catch (err) {
|
|
212
354
|
const error = err;
|
|
@@ -227,12 +369,6 @@ async function handleDisposeRuntime(message, connection, state) {
|
|
|
227
369
|
if (instance.playwrightHandle) {
|
|
228
370
|
instance.playwrightHandle.dispose();
|
|
229
371
|
}
|
|
230
|
-
if (instance.browserContext) {
|
|
231
|
-
await instance.browserContext.close();
|
|
232
|
-
}
|
|
233
|
-
if (instance.browser) {
|
|
234
|
-
await instance.browser.close();
|
|
235
|
-
}
|
|
236
372
|
instance.runtime.dispose();
|
|
237
373
|
state.isolates.delete(message.isolateId);
|
|
238
374
|
connection.isolates.delete(message.isolateId);
|
|
@@ -250,13 +386,26 @@ async function handleEval(message, connection, state) {
|
|
|
250
386
|
}
|
|
251
387
|
instance.lastActivity = Date.now();
|
|
252
388
|
try {
|
|
253
|
-
const
|
|
254
|
-
filename: message.filename
|
|
389
|
+
const mod = await instance.runtime.isolate.compileModule(message.code, {
|
|
390
|
+
filename: message.filename ?? "<eval>"
|
|
255
391
|
});
|
|
256
|
-
|
|
392
|
+
if (instance.moduleLoaderCallbackId) {
|
|
393
|
+
const resolver = createModuleResolver(instance, connection);
|
|
394
|
+
await mod.instantiate(instance.runtime.context, resolver);
|
|
395
|
+
} else {
|
|
396
|
+
await mod.instantiate(instance.runtime.context, (specifier) => {
|
|
397
|
+
throw new Error(`No module loader registered. Cannot import: ${specifier}`);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const timeout = message.maxExecutionMs;
|
|
401
|
+
await mod.evaluate(timeout ? { timeout } : undefined);
|
|
402
|
+
await Promise.all(instance.pendingCallbacks);
|
|
403
|
+
instance.pendingCallbacks.length = 0;
|
|
404
|
+
sendOk(connection.socket, message.requestId, { value: undefined });
|
|
257
405
|
} catch (err) {
|
|
258
406
|
const error = err;
|
|
259
|
-
|
|
407
|
+
const isTimeoutError = error.message?.includes("Script execution timed out");
|
|
408
|
+
sendError(connection.socket, message.requestId, isTimeoutError ? ErrorCode.ISOLATE_TIMEOUT : ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
260
409
|
}
|
|
261
410
|
}
|
|
262
411
|
async function handleDispatchRequest(message, connection, state) {
|
|
@@ -267,24 +416,135 @@ async function handleDispatchRequest(message, connection, state) {
|
|
|
267
416
|
}
|
|
268
417
|
instance.lastActivity = Date.now();
|
|
269
418
|
try {
|
|
419
|
+
let requestBody = null;
|
|
420
|
+
if (message.request.bodyStreamId !== undefined) {
|
|
421
|
+
requestBody = await receiveStreamedBody(connection, message.request.bodyStreamId);
|
|
422
|
+
} else if (message.request.body) {
|
|
423
|
+
requestBody = message.request.body;
|
|
424
|
+
}
|
|
270
425
|
const request = new Request(message.request.url, {
|
|
271
426
|
method: message.request.method,
|
|
272
427
|
headers: message.request.headers,
|
|
273
|
-
body:
|
|
428
|
+
body: requestBody
|
|
274
429
|
});
|
|
275
|
-
const response = await instance.runtime.fetch.dispatchRequest(request
|
|
276
|
-
|
|
277
|
-
|
|
430
|
+
const response = await instance.runtime.fetch.dispatchRequest(request);
|
|
431
|
+
const contentLength = response.headers.get("content-length");
|
|
432
|
+
const knownSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
433
|
+
if (knownSize !== null && knownSize > STREAM_THRESHOLD) {
|
|
434
|
+
await sendStreamedResponse(connection, message.requestId, response);
|
|
435
|
+
} else {
|
|
436
|
+
const clonedResponse = response.clone();
|
|
437
|
+
try {
|
|
438
|
+
const serialized = await serializeResponse(response);
|
|
439
|
+
if (serialized.body && serialized.body.length > STREAM_THRESHOLD) {
|
|
440
|
+
await sendStreamedResponse(connection, message.requestId, clonedResponse);
|
|
441
|
+
} else {
|
|
442
|
+
sendOk(connection.socket, message.requestId, { response: serialized });
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
await sendStreamedResponse(connection, message.requestId, clonedResponse);
|
|
278
446
|
}
|
|
447
|
+
}
|
|
448
|
+
} catch (err) {
|
|
449
|
+
const error = err;
|
|
450
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function receiveStreamedBody(connection, streamId) {
|
|
454
|
+
return new Promise((resolve, reject) => {
|
|
455
|
+
const receiver = {
|
|
456
|
+
streamId,
|
|
457
|
+
requestId: 0,
|
|
458
|
+
chunks: [],
|
|
459
|
+
totalBytes: 0,
|
|
460
|
+
resolve,
|
|
461
|
+
reject
|
|
462
|
+
};
|
|
463
|
+
connection.streamReceivers.set(streamId, receiver);
|
|
464
|
+
sendMessage(connection.socket, {
|
|
465
|
+
type: MessageType.STREAM_PULL,
|
|
466
|
+
streamId,
|
|
467
|
+
maxBytes: STREAM_DEFAULT_CREDIT
|
|
279
468
|
});
|
|
280
|
-
|
|
281
|
-
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
async function handleWsOpen(message, connection, state) {
|
|
472
|
+
const instance = state.isolates.get(message.isolateId);
|
|
473
|
+
if (!instance) {
|
|
474
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
instance.lastActivity = Date.now();
|
|
478
|
+
try {
|
|
479
|
+
instance.runtime.fetch.dispatchWebSocketOpen(message.connectionId);
|
|
480
|
+
sendOk(connection.socket, message.requestId);
|
|
481
|
+
} catch (err) {
|
|
482
|
+
const error = err;
|
|
483
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async function handleWsMessage(message, connection, state) {
|
|
487
|
+
const instance = state.isolates.get(message.isolateId);
|
|
488
|
+
if (!instance) {
|
|
489
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
instance.lastActivity = Date.now();
|
|
493
|
+
try {
|
|
494
|
+
const data = message.data instanceof Uint8Array ? message.data.buffer.slice(message.data.byteOffset, message.data.byteOffset + message.data.byteLength) : message.data;
|
|
495
|
+
instance.runtime.fetch.dispatchWebSocketMessage(message.connectionId, data);
|
|
496
|
+
sendOk(connection.socket, message.requestId);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
const error = err;
|
|
499
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function handleWsClose(message, connection, state) {
|
|
503
|
+
const instance = state.isolates.get(message.isolateId);
|
|
504
|
+
if (!instance) {
|
|
505
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
instance.lastActivity = Date.now();
|
|
509
|
+
try {
|
|
510
|
+
instance.runtime.fetch.dispatchWebSocketClose(message.connectionId, message.code, message.reason);
|
|
511
|
+
sendOk(connection.socket, message.requestId);
|
|
512
|
+
} catch (err) {
|
|
513
|
+
const error = err;
|
|
514
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function handleFetchGetUpgradeRequest(message, connection, state) {
|
|
518
|
+
const instance = state.isolates.get(message.isolateId);
|
|
519
|
+
if (!instance) {
|
|
520
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
instance.lastActivity = Date.now();
|
|
524
|
+
try {
|
|
525
|
+
const upgradeRequest = instance.runtime.fetch.getUpgradeRequest();
|
|
526
|
+
sendOk(connection.socket, message.requestId, upgradeRequest);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
const error = err;
|
|
529
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function handleFetchHasServeHandler(message, connection, state) {
|
|
533
|
+
const instance = state.isolates.get(message.isolateId);
|
|
534
|
+
if (!instance) {
|
|
535
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
instance.lastActivity = Date.now();
|
|
539
|
+
try {
|
|
540
|
+
const hasHandler = instance.runtime.fetch.hasServeHandler();
|
|
541
|
+
sendOk(connection.socket, message.requestId, hasHandler);
|
|
282
542
|
} catch (err) {
|
|
283
543
|
const error = err;
|
|
284
544
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
285
545
|
}
|
|
286
546
|
}
|
|
287
|
-
async function
|
|
547
|
+
async function handleFetchHasActiveConnections(message, connection, state) {
|
|
288
548
|
const instance = state.isolates.get(message.isolateId);
|
|
289
549
|
if (!instance) {
|
|
290
550
|
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
@@ -292,13 +552,103 @@ async function handleTick(message, connection, state) {
|
|
|
292
552
|
}
|
|
293
553
|
instance.lastActivity = Date.now();
|
|
294
554
|
try {
|
|
295
|
-
instance.runtime.
|
|
555
|
+
const hasConnections = instance.runtime.fetch.hasActiveConnections();
|
|
556
|
+
sendOk(connection.socket, message.requestId, hasConnections);
|
|
557
|
+
} catch (err) {
|
|
558
|
+
const error = err;
|
|
559
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function handleFetchWsError(message, connection, state) {
|
|
563
|
+
const instance = state.isolates.get(message.isolateId);
|
|
564
|
+
if (!instance) {
|
|
565
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
instance.lastActivity = Date.now();
|
|
569
|
+
try {
|
|
570
|
+
instance.runtime.fetch.dispatchWebSocketError(message.connectionId, new Error(message.error));
|
|
296
571
|
sendOk(connection.socket, message.requestId);
|
|
297
572
|
} catch (err) {
|
|
298
573
|
const error = err;
|
|
299
574
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
300
575
|
}
|
|
301
576
|
}
|
|
577
|
+
async function handleTimersClearAll(message, connection, state) {
|
|
578
|
+
const instance = state.isolates.get(message.isolateId);
|
|
579
|
+
if (!instance) {
|
|
580
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
instance.lastActivity = Date.now();
|
|
584
|
+
try {
|
|
585
|
+
instance.runtime.timers.clearAll();
|
|
586
|
+
sendOk(connection.socket, message.requestId);
|
|
587
|
+
} catch (err) {
|
|
588
|
+
const error = err;
|
|
589
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function handleConsoleReset(message, connection, state) {
|
|
593
|
+
const instance = state.isolates.get(message.isolateId);
|
|
594
|
+
if (!instance) {
|
|
595
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
instance.lastActivity = Date.now();
|
|
599
|
+
try {
|
|
600
|
+
instance.runtime.console.reset();
|
|
601
|
+
sendOk(connection.socket, message.requestId);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
const error = err;
|
|
604
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async function handleConsoleGetTimers(message, connection, state) {
|
|
608
|
+
const instance = state.isolates.get(message.isolateId);
|
|
609
|
+
if (!instance) {
|
|
610
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
instance.lastActivity = Date.now();
|
|
614
|
+
try {
|
|
615
|
+
const timers = instance.runtime.console.getTimers();
|
|
616
|
+
sendOk(connection.socket, message.requestId, Object.fromEntries(timers));
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const error = err;
|
|
619
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function handleConsoleGetCounters(message, connection, state) {
|
|
623
|
+
const instance = state.isolates.get(message.isolateId);
|
|
624
|
+
if (!instance) {
|
|
625
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
instance.lastActivity = Date.now();
|
|
629
|
+
try {
|
|
630
|
+
const counters = instance.runtime.console.getCounters();
|
|
631
|
+
sendOk(connection.socket, message.requestId, Object.fromEntries(counters));
|
|
632
|
+
} catch (err) {
|
|
633
|
+
const error = err;
|
|
634
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async function handleConsoleGetGroupDepth(message, connection, state) {
|
|
638
|
+
const instance = state.isolates.get(message.isolateId);
|
|
639
|
+
if (!instance) {
|
|
640
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
instance.lastActivity = Date.now();
|
|
644
|
+
try {
|
|
645
|
+
const depth = instance.runtime.console.getGroupDepth();
|
|
646
|
+
sendOk(connection.socket, message.requestId, depth);
|
|
647
|
+
} catch (err) {
|
|
648
|
+
const error = err;
|
|
649
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
302
652
|
function handleCallbackResponse(message, connection) {
|
|
303
653
|
const pending = connection.pendingCallbacks.get(message.requestId);
|
|
304
654
|
if (!pending) {
|
|
@@ -342,6 +692,477 @@ async function invokeClientCallback(connection, callbackId, args, timeout = 1e4)
|
|
|
342
692
|
sendMessage(connection.socket, invoke);
|
|
343
693
|
});
|
|
344
694
|
}
|
|
695
|
+
var ISOLATE_MARSHAL_CODE = `
|
|
696
|
+
(function() {
|
|
697
|
+
// Marshal a value (JavaScript \u2192 Ref)
|
|
698
|
+
function marshalForHost(value, depth = 0) {
|
|
699
|
+
if (depth > 100) throw new Error('Maximum marshalling depth exceeded');
|
|
700
|
+
|
|
701
|
+
if (value === null) return null;
|
|
702
|
+
if (value === undefined) return { __type: 'UndefinedRef' };
|
|
703
|
+
|
|
704
|
+
const type = typeof value;
|
|
705
|
+
if (type === 'string' || type === 'number' || type === 'boolean') return value;
|
|
706
|
+
if (type === 'bigint') return { __type: 'BigIntRef', value: value.toString() };
|
|
707
|
+
if (type === 'function') throw new Error('Cannot marshal functions from isolate');
|
|
708
|
+
if (type === 'symbol') throw new Error('Cannot marshal Symbol values');
|
|
709
|
+
|
|
710
|
+
if (type === 'object') {
|
|
711
|
+
if (value instanceof Date) {
|
|
712
|
+
return { __type: 'DateRef', timestamp: value.getTime() };
|
|
713
|
+
}
|
|
714
|
+
if (value instanceof RegExp) {
|
|
715
|
+
return { __type: 'RegExpRef', source: value.source, flags: value.flags };
|
|
716
|
+
}
|
|
717
|
+
if (value instanceof URL) {
|
|
718
|
+
return { __type: 'URLRef', href: value.href };
|
|
719
|
+
}
|
|
720
|
+
if (typeof Headers !== 'undefined' && value instanceof Headers) {
|
|
721
|
+
const pairs = [];
|
|
722
|
+
value.forEach((v, k) => pairs.push([k, v]));
|
|
723
|
+
return { __type: 'HeadersRef', pairs };
|
|
724
|
+
}
|
|
725
|
+
if (value instanceof Uint8Array) {
|
|
726
|
+
return { __type: 'Uint8ArrayRef', data: Array.from(value) };
|
|
727
|
+
}
|
|
728
|
+
if (value instanceof ArrayBuffer) {
|
|
729
|
+
return { __type: 'Uint8ArrayRef', data: Array.from(new Uint8Array(value)) };
|
|
730
|
+
}
|
|
731
|
+
if (typeof Request !== 'undefined' && value instanceof Request) {
|
|
732
|
+
throw new Error('Cannot marshal Request from isolate. Use fetch callback instead.');
|
|
733
|
+
}
|
|
734
|
+
if (typeof Response !== 'undefined' && value instanceof Response) {
|
|
735
|
+
throw new Error('Cannot marshal Response from isolate. Return plain objects instead.');
|
|
736
|
+
}
|
|
737
|
+
if (typeof File !== 'undefined' && value instanceof File) {
|
|
738
|
+
throw new Error('Cannot marshal File from isolate.');
|
|
739
|
+
}
|
|
740
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob) {
|
|
741
|
+
throw new Error('Cannot marshal Blob from isolate.');
|
|
742
|
+
}
|
|
743
|
+
if (typeof FormData !== 'undefined' && value instanceof FormData) {
|
|
744
|
+
throw new Error('Cannot marshal FormData from isolate.');
|
|
745
|
+
}
|
|
746
|
+
if (Array.isArray(value)) {
|
|
747
|
+
return value.map(v => marshalForHost(v, depth + 1));
|
|
748
|
+
}
|
|
749
|
+
// Plain object
|
|
750
|
+
const result = {};
|
|
751
|
+
for (const key of Object.keys(value)) {
|
|
752
|
+
result[key] = marshalForHost(value[key], depth + 1);
|
|
753
|
+
}
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
756
|
+
return value;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Unmarshal a value (Ref \u2192 JavaScript)
|
|
760
|
+
function unmarshalFromHost(value, depth = 0) {
|
|
761
|
+
if (depth > 100) throw new Error('Maximum unmarshalling depth exceeded');
|
|
762
|
+
|
|
763
|
+
if (value === null) return null;
|
|
764
|
+
if (typeof value !== 'object') return value;
|
|
765
|
+
|
|
766
|
+
if (value.__type) {
|
|
767
|
+
switch (value.__type) {
|
|
768
|
+
case 'UndefinedRef': return undefined;
|
|
769
|
+
case 'DateRef': return new Date(value.timestamp);
|
|
770
|
+
case 'RegExpRef': return new RegExp(value.source, value.flags);
|
|
771
|
+
case 'BigIntRef': return BigInt(value.value);
|
|
772
|
+
case 'URLRef': return new URL(value.href);
|
|
773
|
+
case 'HeadersRef': return new Headers(value.pairs);
|
|
774
|
+
case 'Uint8ArrayRef': return new Uint8Array(value.data);
|
|
775
|
+
case 'RequestRef': {
|
|
776
|
+
const init = {
|
|
777
|
+
method: value.method,
|
|
778
|
+
headers: value.headers,
|
|
779
|
+
body: value.body ? new Uint8Array(value.body) : null,
|
|
780
|
+
};
|
|
781
|
+
if (value.mode) init.mode = value.mode;
|
|
782
|
+
if (value.credentials) init.credentials = value.credentials;
|
|
783
|
+
if (value.cache) init.cache = value.cache;
|
|
784
|
+
if (value.redirect) init.redirect = value.redirect;
|
|
785
|
+
if (value.referrer) init.referrer = value.referrer;
|
|
786
|
+
if (value.referrerPolicy) init.referrerPolicy = value.referrerPolicy;
|
|
787
|
+
if (value.integrity) init.integrity = value.integrity;
|
|
788
|
+
return new Request(value.url, init);
|
|
789
|
+
}
|
|
790
|
+
case 'ResponseRef': {
|
|
791
|
+
return new Response(value.body ? new Uint8Array(value.body) : null, {
|
|
792
|
+
status: value.status,
|
|
793
|
+
statusText: value.statusText,
|
|
794
|
+
headers: value.headers,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
case 'FileRef': {
|
|
798
|
+
if (!value.name) {
|
|
799
|
+
return new Blob([new Uint8Array(value.data)], { type: value.type });
|
|
800
|
+
}
|
|
801
|
+
return new File([new Uint8Array(value.data)], value.name, {
|
|
802
|
+
type: value.type,
|
|
803
|
+
lastModified: value.lastModified,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
case 'FormDataRef': {
|
|
807
|
+
const fd = new FormData();
|
|
808
|
+
for (const [key, entry] of value.entries) {
|
|
809
|
+
if (typeof entry === 'string') {
|
|
810
|
+
fd.append(key, entry);
|
|
811
|
+
} else {
|
|
812
|
+
const file = unmarshalFromHost(entry, depth + 1);
|
|
813
|
+
fd.append(key, file);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return fd;
|
|
817
|
+
}
|
|
818
|
+
case 'CallbackRef': {
|
|
819
|
+
// Create a proxy function that invokes the callback
|
|
820
|
+
const callbackId = value.callbackId;
|
|
821
|
+
return function(...args) {
|
|
822
|
+
const argsJson = JSON.stringify(marshalForHost(args));
|
|
823
|
+
const resultJson = __customFn_invoke.applySyncPromise(undefined, [callbackId, argsJson]);
|
|
824
|
+
const result = JSON.parse(resultJson);
|
|
825
|
+
if (result.ok) {
|
|
826
|
+
return unmarshalFromHost(result.value);
|
|
827
|
+
} else {
|
|
828
|
+
const error = new Error(result.error.message);
|
|
829
|
+
error.name = result.error.name;
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
case 'PromiseRef': {
|
|
835
|
+
// Create a proxy Promise that resolves via callback
|
|
836
|
+
const promiseId = value.promiseId;
|
|
837
|
+
return new Promise((resolve, reject) => {
|
|
838
|
+
try {
|
|
839
|
+
const argsJson = JSON.stringify([promiseId]);
|
|
840
|
+
const resultJson = __customFn_invoke.applySyncPromise(undefined, [value.__resolveCallbackId, argsJson]);
|
|
841
|
+
const result = JSON.parse(resultJson);
|
|
842
|
+
if (result.ok) {
|
|
843
|
+
resolve(unmarshalFromHost(result.value));
|
|
844
|
+
} else {
|
|
845
|
+
reject(new Error(result.error.message));
|
|
846
|
+
}
|
|
847
|
+
} catch (e) {
|
|
848
|
+
reject(e);
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
case 'AsyncIteratorRef': {
|
|
853
|
+
const iteratorId = value.iteratorId;
|
|
854
|
+
const nextCallbackId = value.__nextCallbackId;
|
|
855
|
+
const returnCallbackId = value.__returnCallbackId;
|
|
856
|
+
return {
|
|
857
|
+
[Symbol.asyncIterator]() { return this; },
|
|
858
|
+
async next() {
|
|
859
|
+
const argsJson = JSON.stringify([iteratorId]);
|
|
860
|
+
const resultJson = __customFn_invoke.applySyncPromise(undefined, [nextCallbackId, argsJson]);
|
|
861
|
+
const result = JSON.parse(resultJson);
|
|
862
|
+
if (!result.ok) {
|
|
863
|
+
const error = new Error(result.error.message);
|
|
864
|
+
error.name = result.error.name;
|
|
865
|
+
throw error;
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
done: result.value.done,
|
|
869
|
+
value: unmarshalFromHost(result.value.value)
|
|
870
|
+
};
|
|
871
|
+
},
|
|
872
|
+
async return(v) {
|
|
873
|
+
const argsJson = JSON.stringify([iteratorId, marshalForHost(v)]);
|
|
874
|
+
const resultJson = __customFn_invoke.applySyncPromise(undefined, [returnCallbackId, argsJson]);
|
|
875
|
+
const result = JSON.parse(resultJson);
|
|
876
|
+
return { done: true, value: result.ok ? unmarshalFromHost(result.value) : undefined };
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
default:
|
|
881
|
+
// Unknown ref type, return as-is
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (Array.isArray(value)) {
|
|
887
|
+
return value.map(v => unmarshalFromHost(v, depth + 1));
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Plain object - recursively unmarshal
|
|
891
|
+
const result = {};
|
|
892
|
+
for (const key of Object.keys(value)) {
|
|
893
|
+
result[key] = unmarshalFromHost(value[key], depth + 1);
|
|
894
|
+
}
|
|
895
|
+
return result;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
globalThis.__marshalForHost = marshalForHost;
|
|
899
|
+
globalThis.__unmarshalFromHost = unmarshalFromHost;
|
|
900
|
+
})();
|
|
901
|
+
`;
|
|
902
|
+
var LOCAL_CALLBACK_THRESHOLD = 1e6;
|
|
903
|
+
function isPromiseRef(value) {
|
|
904
|
+
return typeof value === "object" && value !== null && value.__type === "PromiseRef";
|
|
905
|
+
}
|
|
906
|
+
function isAsyncIteratorRef(value) {
|
|
907
|
+
return typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
|
|
908
|
+
}
|
|
909
|
+
function isLocalCallbackId(callbackId) {
|
|
910
|
+
return callbackId >= LOCAL_CALLBACK_THRESHOLD;
|
|
911
|
+
}
|
|
912
|
+
async function setupCustomFunctions(context, customCallbacks, connection, instance) {
|
|
913
|
+
const global = context.global;
|
|
914
|
+
function createMarshalContext() {
|
|
915
|
+
return {
|
|
916
|
+
registerCallback: (fn) => {
|
|
917
|
+
const callbackId = instance.nextLocalCallbackId++;
|
|
918
|
+
instance.returnedCallbacks.set(callbackId, fn);
|
|
919
|
+
return callbackId;
|
|
920
|
+
},
|
|
921
|
+
registerPromise: (promise) => {
|
|
922
|
+
const promiseId = instance.nextLocalCallbackId++;
|
|
923
|
+
instance.returnedPromises.set(promiseId, promise);
|
|
924
|
+
return promiseId;
|
|
925
|
+
},
|
|
926
|
+
registerIterator: (iterator) => {
|
|
927
|
+
const iteratorId = instance.nextLocalCallbackId++;
|
|
928
|
+
instance.returnedIterators.set(iteratorId, iterator);
|
|
929
|
+
return iteratorId;
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function addCallbackIdsToRefs(value) {
|
|
934
|
+
if (value === null || typeof value !== "object") {
|
|
935
|
+
return value;
|
|
936
|
+
}
|
|
937
|
+
if (isPromiseRef(value)) {
|
|
938
|
+
if ("__resolveCallbackId" in value) {
|
|
939
|
+
return value;
|
|
940
|
+
}
|
|
941
|
+
const resolveCallbackId = instance.nextLocalCallbackId++;
|
|
942
|
+
instance.returnedCallbacks.set(resolveCallbackId, async (promiseId) => {
|
|
943
|
+
const promise = instance.returnedPromises.get(promiseId);
|
|
944
|
+
if (!promise) {
|
|
945
|
+
throw new Error(`Promise ${promiseId} not found`);
|
|
946
|
+
}
|
|
947
|
+
const result2 = await promise;
|
|
948
|
+
instance.returnedPromises.delete(promiseId);
|
|
949
|
+
const ctx = createMarshalContext();
|
|
950
|
+
const marshalled = await marshalValue(result2, ctx);
|
|
951
|
+
return addCallbackIdsToRefs(marshalled);
|
|
952
|
+
});
|
|
953
|
+
return {
|
|
954
|
+
...value,
|
|
955
|
+
__resolveCallbackId: resolveCallbackId
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (isAsyncIteratorRef(value)) {
|
|
959
|
+
if ("__nextCallbackId" in value) {
|
|
960
|
+
return value;
|
|
961
|
+
}
|
|
962
|
+
const nextCallbackId = instance.nextLocalCallbackId++;
|
|
963
|
+
instance.returnedCallbacks.set(nextCallbackId, async (iteratorId) => {
|
|
964
|
+
const iterator = instance.returnedIterators.get(iteratorId);
|
|
965
|
+
if (!iterator) {
|
|
966
|
+
throw new Error(`Iterator ${iteratorId} not found`);
|
|
967
|
+
}
|
|
968
|
+
const result2 = await iterator.next();
|
|
969
|
+
if (result2.done) {
|
|
970
|
+
instance.returnedIterators.delete(iteratorId);
|
|
971
|
+
}
|
|
972
|
+
const ctx = createMarshalContext();
|
|
973
|
+
const marshalledValue = await marshalValue(result2.value, ctx);
|
|
974
|
+
return {
|
|
975
|
+
done: result2.done,
|
|
976
|
+
value: addCallbackIdsToRefs(marshalledValue)
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
const returnCallbackId = instance.nextLocalCallbackId++;
|
|
980
|
+
instance.returnedCallbacks.set(returnCallbackId, async (iteratorId, returnValue) => {
|
|
981
|
+
const iterator = instance.returnedIterators.get(iteratorId);
|
|
982
|
+
instance.returnedIterators.delete(iteratorId);
|
|
983
|
+
if (!iterator || !iterator.return) {
|
|
984
|
+
return { done: true, value: undefined };
|
|
985
|
+
}
|
|
986
|
+
const result2 = await iterator.return(returnValue);
|
|
987
|
+
const ctx = createMarshalContext();
|
|
988
|
+
const marshalledValue = await marshalValue(result2.value, ctx);
|
|
989
|
+
return {
|
|
990
|
+
done: true,
|
|
991
|
+
value: addCallbackIdsToRefs(marshalledValue)
|
|
992
|
+
};
|
|
993
|
+
});
|
|
994
|
+
return {
|
|
995
|
+
...value,
|
|
996
|
+
__nextCallbackId: nextCallbackId,
|
|
997
|
+
__returnCallbackId: returnCallbackId
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
if (Array.isArray(value)) {
|
|
1001
|
+
return value.map((item) => addCallbackIdsToRefs(item));
|
|
1002
|
+
}
|
|
1003
|
+
const result = {};
|
|
1004
|
+
for (const key of Object.keys(value)) {
|
|
1005
|
+
result[key] = addCallbackIdsToRefs(value[key]);
|
|
1006
|
+
}
|
|
1007
|
+
return result;
|
|
1008
|
+
}
|
|
1009
|
+
const invokeCallbackRef = new ivm.Reference(async (callbackId, argsJson) => {
|
|
1010
|
+
const marshalledArgs = JSON.parse(argsJson);
|
|
1011
|
+
const args = unmarshalValue(marshalledArgs);
|
|
1012
|
+
try {
|
|
1013
|
+
let result;
|
|
1014
|
+
if (isLocalCallbackId(callbackId)) {
|
|
1015
|
+
const callback = instance.returnedCallbacks.get(callbackId);
|
|
1016
|
+
if (!callback) {
|
|
1017
|
+
throw new Error(`Local callback ${callbackId} not found`);
|
|
1018
|
+
}
|
|
1019
|
+
result = await callback(...args);
|
|
1020
|
+
} else {
|
|
1021
|
+
result = await invokeClientCallback(connection, callbackId, args);
|
|
1022
|
+
}
|
|
1023
|
+
const ctx = createMarshalContext();
|
|
1024
|
+
const marshalledResult = await marshalValue({ ok: true, value: result }, ctx);
|
|
1025
|
+
const processedResult = addCallbackIdsToRefs(marshalledResult);
|
|
1026
|
+
return JSON.stringify(processedResult);
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
const err = error;
|
|
1029
|
+
return JSON.stringify({
|
|
1030
|
+
ok: false,
|
|
1031
|
+
error: { message: err.message, name: err.name }
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
global.setSync("__customFn_invoke", invokeCallbackRef);
|
|
1036
|
+
context.evalSync(ISOLATE_MARSHAL_CODE);
|
|
1037
|
+
for (const [name, registration] of Object.entries(customCallbacks)) {
|
|
1038
|
+
if (name.includes(":")) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (registration.type === "sync") {
|
|
1042
|
+
context.evalSync(`
|
|
1043
|
+
globalThis.${name} = function(...args) {
|
|
1044
|
+
const argsJson = JSON.stringify(__marshalForHost(args));
|
|
1045
|
+
const resultJson = __customFn_invoke.applySyncPromise(
|
|
1046
|
+
undefined,
|
|
1047
|
+
[${registration.callbackId}, argsJson]
|
|
1048
|
+
);
|
|
1049
|
+
const result = JSON.parse(resultJson);
|
|
1050
|
+
if (result.ok) {
|
|
1051
|
+
return __unmarshalFromHost(result.value);
|
|
1052
|
+
} else {
|
|
1053
|
+
const error = new Error(result.error.message);
|
|
1054
|
+
error.name = result.error.name;
|
|
1055
|
+
throw error;
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
`);
|
|
1059
|
+
} else if (registration.type === "asyncIterator") {
|
|
1060
|
+
const startReg = customCallbacks[`${name}:start`];
|
|
1061
|
+
const nextReg = customCallbacks[`${name}:next`];
|
|
1062
|
+
const returnReg = customCallbacks[`${name}:return`];
|
|
1063
|
+
const throwReg = customCallbacks[`${name}:throw`];
|
|
1064
|
+
if (!startReg || !nextReg || !returnReg || !throwReg) {
|
|
1065
|
+
throw new Error(`Missing companion callbacks for asyncIterator function "${name}"`);
|
|
1066
|
+
}
|
|
1067
|
+
context.evalSync(`
|
|
1068
|
+
globalThis.${name} = function(...args) {
|
|
1069
|
+
// Start the iterator and get the iteratorId
|
|
1070
|
+
const argsJson = JSON.stringify(__marshalForHost(args));
|
|
1071
|
+
const startResultJson = __customFn_invoke.applySyncPromise(
|
|
1072
|
+
undefined,
|
|
1073
|
+
[${startReg.callbackId}, argsJson]
|
|
1074
|
+
);
|
|
1075
|
+
const startResult = JSON.parse(startResultJson);
|
|
1076
|
+
if (!startResult.ok) {
|
|
1077
|
+
const error = new Error(startResult.error.message);
|
|
1078
|
+
error.name = startResult.error.name;
|
|
1079
|
+
throw error;
|
|
1080
|
+
}
|
|
1081
|
+
const iteratorId = __unmarshalFromHost(startResult.value).iteratorId;
|
|
1082
|
+
|
|
1083
|
+
return {
|
|
1084
|
+
[Symbol.asyncIterator]() { return this; },
|
|
1085
|
+
async next() {
|
|
1086
|
+
const argsJson = JSON.stringify(__marshalForHost([iteratorId]));
|
|
1087
|
+
const resultJson = __customFn_invoke.applySyncPromise(
|
|
1088
|
+
undefined,
|
|
1089
|
+
[${nextReg.callbackId}, argsJson]
|
|
1090
|
+
);
|
|
1091
|
+
const result = JSON.parse(resultJson);
|
|
1092
|
+
if (!result.ok) {
|
|
1093
|
+
const error = new Error(result.error.message);
|
|
1094
|
+
error.name = result.error.name;
|
|
1095
|
+
throw error;
|
|
1096
|
+
}
|
|
1097
|
+
const val = __unmarshalFromHost(result.value);
|
|
1098
|
+
return { done: val.done, value: val.value };
|
|
1099
|
+
},
|
|
1100
|
+
async return(v) {
|
|
1101
|
+
const argsJson = JSON.stringify(__marshalForHost([iteratorId, v]));
|
|
1102
|
+
const resultJson = __customFn_invoke.applySyncPromise(
|
|
1103
|
+
undefined,
|
|
1104
|
+
[${returnReg.callbackId}, argsJson]
|
|
1105
|
+
);
|
|
1106
|
+
const result = JSON.parse(resultJson);
|
|
1107
|
+
return { done: true, value: result.ok ? __unmarshalFromHost(result.value) : undefined };
|
|
1108
|
+
},
|
|
1109
|
+
async throw(e) {
|
|
1110
|
+
const argsJson = JSON.stringify(__marshalForHost([iteratorId, { message: e?.message, name: e?.name }]));
|
|
1111
|
+
const resultJson = __customFn_invoke.applySyncPromise(
|
|
1112
|
+
undefined,
|
|
1113
|
+
[${throwReg.callbackId}, argsJson]
|
|
1114
|
+
);
|
|
1115
|
+
const result = JSON.parse(resultJson);
|
|
1116
|
+
if (!result.ok) {
|
|
1117
|
+
const error = new Error(result.error.message);
|
|
1118
|
+
error.name = result.error.name;
|
|
1119
|
+
throw error;
|
|
1120
|
+
}
|
|
1121
|
+
const val = __unmarshalFromHost(result.value);
|
|
1122
|
+
return { done: val.done, value: val.value };
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
`);
|
|
1127
|
+
} else if (registration.type === "async") {
|
|
1128
|
+
context.evalSync(`
|
|
1129
|
+
globalThis.${name} = async function(...args) {
|
|
1130
|
+
const argsJson = JSON.stringify(__marshalForHost(args));
|
|
1131
|
+
const resultJson = __customFn_invoke.applySyncPromise(
|
|
1132
|
+
undefined,
|
|
1133
|
+
[${registration.callbackId}, argsJson]
|
|
1134
|
+
);
|
|
1135
|
+
const result = JSON.parse(resultJson);
|
|
1136
|
+
if (result.ok) {
|
|
1137
|
+
return __unmarshalFromHost(result.value);
|
|
1138
|
+
} else {
|
|
1139
|
+
const error = new Error(result.error.message);
|
|
1140
|
+
error.name = result.error.name;
|
|
1141
|
+
throw error;
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
`);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function createModuleResolver(instance, connection) {
|
|
1149
|
+
return async (specifier, _referrer) => {
|
|
1150
|
+
const cached = instance.moduleCache?.get(specifier);
|
|
1151
|
+
if (cached)
|
|
1152
|
+
return cached;
|
|
1153
|
+
if (!instance.moduleLoaderCallbackId) {
|
|
1154
|
+
throw new Error(`Module not found: ${specifier}`);
|
|
1155
|
+
}
|
|
1156
|
+
const code = await invokeClientCallback(connection, instance.moduleLoaderCallbackId, [specifier]);
|
|
1157
|
+
const mod = await instance.runtime.isolate.compileModule(code, {
|
|
1158
|
+
filename: specifier
|
|
1159
|
+
});
|
|
1160
|
+
const resolver = createModuleResolver(instance, connection);
|
|
1161
|
+
await mod.instantiate(instance.runtime.context, resolver);
|
|
1162
|
+
instance.moduleCache?.set(specifier, mod);
|
|
1163
|
+
return mod;
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
345
1166
|
async function serializeRequest(request) {
|
|
346
1167
|
const headers = [];
|
|
347
1168
|
request.headers.forEach((value, key) => {
|
|
@@ -381,19 +1202,144 @@ function deserializeResponse(data) {
|
|
|
381
1202
|
headers: data.headers
|
|
382
1203
|
});
|
|
383
1204
|
}
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
if (!
|
|
387
|
-
|
|
1205
|
+
function handleStreamPush(message, connection) {
|
|
1206
|
+
const receiver = connection.streamReceivers.get(message.streamId);
|
|
1207
|
+
if (!receiver) {
|
|
1208
|
+
sendMessage(connection.socket, {
|
|
1209
|
+
type: MessageType.STREAM_ERROR,
|
|
1210
|
+
streamId: message.streamId,
|
|
1211
|
+
error: "Stream not found"
|
|
1212
|
+
});
|
|
388
1213
|
return;
|
|
389
1214
|
}
|
|
390
|
-
|
|
1215
|
+
receiver.chunks.push(message.chunk);
|
|
1216
|
+
receiver.totalBytes += message.chunk.length;
|
|
1217
|
+
sendMessage(connection.socket, {
|
|
1218
|
+
type: MessageType.STREAM_PULL,
|
|
1219
|
+
streamId: message.streamId,
|
|
1220
|
+
maxBytes: STREAM_DEFAULT_CREDIT
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
function handleStreamPull(message, connection) {
|
|
1224
|
+
const session = connection.activeStreams.get(message.streamId);
|
|
1225
|
+
if (!session) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
session.credit += message.maxBytes;
|
|
1229
|
+
if (session.creditResolver) {
|
|
1230
|
+
session.creditResolver();
|
|
1231
|
+
session.creditResolver = undefined;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function handleStreamClose(message, connection) {
|
|
1235
|
+
const receiver = connection.streamReceivers.get(message.streamId);
|
|
1236
|
+
if (!receiver) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
const totalLength = receiver.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1240
|
+
const body = new Uint8Array(totalLength);
|
|
1241
|
+
let offset = 0;
|
|
1242
|
+
for (const chunk of receiver.chunks) {
|
|
1243
|
+
body.set(chunk, offset);
|
|
1244
|
+
offset += chunk.length;
|
|
1245
|
+
}
|
|
1246
|
+
receiver.resolve(body);
|
|
1247
|
+
connection.streamReceivers.delete(message.streamId);
|
|
1248
|
+
}
|
|
1249
|
+
function handleStreamError(message, connection) {
|
|
1250
|
+
const receiver = connection.streamReceivers.get(message.streamId);
|
|
1251
|
+
if (receiver) {
|
|
1252
|
+
receiver.reject(new Error(message.error));
|
|
1253
|
+
connection.streamReceivers.delete(message.streamId);
|
|
1254
|
+
}
|
|
1255
|
+
const session = connection.activeStreams.get(message.streamId);
|
|
1256
|
+
if (session) {
|
|
1257
|
+
session.state = "closed";
|
|
1258
|
+
connection.activeStreams.delete(message.streamId);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function waitForCredit(session) {
|
|
1262
|
+
return new Promise((resolve) => {
|
|
1263
|
+
session.creditResolver = resolve;
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
async function sendStreamedResponse(connection, requestId, response) {
|
|
1267
|
+
const streamId = connection.nextStreamId++;
|
|
1268
|
+
const headers = [];
|
|
1269
|
+
response.headers.forEach((value, key) => {
|
|
1270
|
+
headers.push([key, value]);
|
|
1271
|
+
});
|
|
1272
|
+
const startMsg = {
|
|
1273
|
+
type: MessageType.RESPONSE_STREAM_START,
|
|
1274
|
+
requestId,
|
|
1275
|
+
streamId,
|
|
1276
|
+
metadata: {
|
|
1277
|
+
status: response.status,
|
|
1278
|
+
statusText: response.statusText,
|
|
1279
|
+
headers
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
sendMessage(connection.socket, startMsg);
|
|
1283
|
+
if (!response.body) {
|
|
1284
|
+
const endMsg = {
|
|
1285
|
+
type: MessageType.RESPONSE_STREAM_END,
|
|
1286
|
+
requestId,
|
|
1287
|
+
streamId
|
|
1288
|
+
};
|
|
1289
|
+
sendMessage(connection.socket, endMsg);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const session = {
|
|
1293
|
+
streamId,
|
|
1294
|
+
direction: "download",
|
|
1295
|
+
requestId,
|
|
1296
|
+
state: "active",
|
|
1297
|
+
bytesTransferred: 0,
|
|
1298
|
+
credit: STREAM_DEFAULT_CREDIT
|
|
1299
|
+
};
|
|
1300
|
+
connection.activeStreams.set(streamId, session);
|
|
1301
|
+
const reader = response.body.getReader();
|
|
391
1302
|
try {
|
|
392
|
-
|
|
393
|
-
|
|
1303
|
+
while (true) {
|
|
1304
|
+
while (session.credit < STREAM_CHUNK_SIZE && session.state === "active") {
|
|
1305
|
+
await waitForCredit(session);
|
|
1306
|
+
}
|
|
1307
|
+
if (session.state !== "active") {
|
|
1308
|
+
throw new Error("Stream cancelled");
|
|
1309
|
+
}
|
|
1310
|
+
const { done, value } = await reader.read();
|
|
1311
|
+
if (done) {
|
|
1312
|
+
const endMsg = {
|
|
1313
|
+
type: MessageType.RESPONSE_STREAM_END,
|
|
1314
|
+
requestId,
|
|
1315
|
+
streamId
|
|
1316
|
+
};
|
|
1317
|
+
sendMessage(connection.socket, endMsg);
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
for (let offset = 0;offset < value.length; offset += STREAM_CHUNK_SIZE) {
|
|
1321
|
+
const chunk = value.slice(offset, offset + STREAM_CHUNK_SIZE);
|
|
1322
|
+
const chunkMsg = {
|
|
1323
|
+
type: MessageType.RESPONSE_STREAM_CHUNK,
|
|
1324
|
+
requestId,
|
|
1325
|
+
streamId,
|
|
1326
|
+
chunk
|
|
1327
|
+
};
|
|
1328
|
+
sendMessage(connection.socket, chunkMsg);
|
|
1329
|
+
session.credit -= chunk.length;
|
|
1330
|
+
session.bytesTransferred += chunk.length;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
394
1333
|
} catch (err) {
|
|
395
|
-
const
|
|
396
|
-
|
|
1334
|
+
const errorMsg = {
|
|
1335
|
+
type: MessageType.STREAM_ERROR,
|
|
1336
|
+
streamId,
|
|
1337
|
+
error: err.message
|
|
1338
|
+
};
|
|
1339
|
+
sendMessage(connection.socket, errorMsg);
|
|
1340
|
+
} finally {
|
|
1341
|
+
reader.releaseLock();
|
|
1342
|
+
connection.activeStreams.delete(streamId);
|
|
397
1343
|
}
|
|
398
1344
|
}
|
|
399
1345
|
async function handleRunTests(message, connection, state) {
|
|
@@ -402,6 +1348,10 @@ async function handleRunTests(message, connection, state) {
|
|
|
402
1348
|
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
403
1349
|
return;
|
|
404
1350
|
}
|
|
1351
|
+
if (!instance.testEnvironmentEnabled) {
|
|
1352
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
405
1355
|
instance.lastActivity = Date.now();
|
|
406
1356
|
try {
|
|
407
1357
|
const timeout = message.timeout ?? 30000;
|
|
@@ -418,119 +1368,69 @@ async function handleRunTests(message, connection, state) {
|
|
|
418
1368
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
419
1369
|
}
|
|
420
1370
|
}
|
|
421
|
-
async function
|
|
1371
|
+
async function handleResetTestEnv(message, connection, state) {
|
|
422
1372
|
const instance = state.isolates.get(message.isolateId);
|
|
423
1373
|
if (!instance) {
|
|
424
1374
|
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
425
1375
|
return;
|
|
426
1376
|
}
|
|
427
|
-
if (instance.
|
|
428
|
-
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "
|
|
1377
|
+
if (!instance.testEnvironmentEnabled) {
|
|
1378
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
|
|
429
1379
|
return;
|
|
430
1380
|
}
|
|
431
1381
|
instance.lastActivity = Date.now();
|
|
432
1382
|
try {
|
|
433
|
-
|
|
434
|
-
const headless = message.options.headless ?? true;
|
|
435
|
-
let browser;
|
|
436
|
-
switch (browserType) {
|
|
437
|
-
case "firefox":
|
|
438
|
-
browser = await firefox.launch({ headless });
|
|
439
|
-
break;
|
|
440
|
-
case "webkit":
|
|
441
|
-
browser = await webkit.launch({ headless });
|
|
442
|
-
break;
|
|
443
|
-
default:
|
|
444
|
-
browser = await chromium.launch({ headless });
|
|
445
|
-
}
|
|
446
|
-
const browserContext = await browser.newContext();
|
|
447
|
-
const page = await browserContext.newPage();
|
|
448
|
-
const playwrightHandle = await setupPlaywright(instance.runtime.context, {
|
|
449
|
-
page,
|
|
450
|
-
baseUrl: message.options.baseURL,
|
|
451
|
-
onConsoleLog: (level, ...args) => {
|
|
452
|
-
const event = {
|
|
453
|
-
type: MessageType.PLAYWRIGHT_EVENT,
|
|
454
|
-
isolateId: message.isolateId,
|
|
455
|
-
eventType: "consoleLog",
|
|
456
|
-
payload: { level, args }
|
|
457
|
-
};
|
|
458
|
-
sendMessage(connection.socket, event);
|
|
459
|
-
},
|
|
460
|
-
onNetworkRequest: (info) => {
|
|
461
|
-
const event = {
|
|
462
|
-
type: MessageType.PLAYWRIGHT_EVENT,
|
|
463
|
-
isolateId: message.isolateId,
|
|
464
|
-
eventType: "networkRequest",
|
|
465
|
-
payload: info
|
|
466
|
-
};
|
|
467
|
-
sendMessage(connection.socket, event);
|
|
468
|
-
},
|
|
469
|
-
onNetworkResponse: (info) => {
|
|
470
|
-
const event = {
|
|
471
|
-
type: MessageType.PLAYWRIGHT_EVENT,
|
|
472
|
-
isolateId: message.isolateId,
|
|
473
|
-
eventType: "networkResponse",
|
|
474
|
-
payload: info
|
|
475
|
-
};
|
|
476
|
-
sendMessage(connection.socket, event);
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
instance.browser = browser;
|
|
480
|
-
instance.browserContext = browserContext;
|
|
481
|
-
instance.page = page;
|
|
482
|
-
instance.playwrightHandle = playwrightHandle;
|
|
1383
|
+
await instance.runtime.context.eval("__resetTestEnvironment()", { promise: true });
|
|
483
1384
|
sendOk(connection.socket, message.requestId);
|
|
484
1385
|
} catch (err) {
|
|
485
1386
|
const error = err;
|
|
486
1387
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
487
1388
|
}
|
|
488
1389
|
}
|
|
489
|
-
async function
|
|
1390
|
+
async function handleHasTests(message, connection, state) {
|
|
490
1391
|
const instance = state.isolates.get(message.isolateId);
|
|
491
1392
|
if (!instance) {
|
|
492
1393
|
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
493
1394
|
return;
|
|
494
1395
|
}
|
|
495
|
-
if (!instance.
|
|
496
|
-
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "
|
|
1396
|
+
if (!instance.testEnvironmentEnabled) {
|
|
1397
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
|
|
497
1398
|
return;
|
|
498
1399
|
}
|
|
499
1400
|
instance.lastActivity = Date.now();
|
|
500
1401
|
try {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
setTimeout(() => reject(new Error("Playwright test timeout")), timeout);
|
|
504
|
-
});
|
|
505
|
-
const results = await Promise.race([
|
|
506
|
-
runPlaywrightTests(instance.runtime.context),
|
|
507
|
-
timeoutPromise
|
|
508
|
-
]);
|
|
509
|
-
sendOk(connection.socket, message.requestId, results);
|
|
1402
|
+
const result = hasTestsInContext(instance.runtime.context);
|
|
1403
|
+
sendOk(connection.socket, message.requestId, result);
|
|
510
1404
|
} catch (err) {
|
|
511
1405
|
const error = err;
|
|
512
1406
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
513
1407
|
}
|
|
514
1408
|
}
|
|
515
|
-
async function
|
|
1409
|
+
async function handleGetTestCount(message, connection, state) {
|
|
516
1410
|
const instance = state.isolates.get(message.isolateId);
|
|
517
1411
|
if (!instance) {
|
|
518
1412
|
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
519
1413
|
return;
|
|
520
1414
|
}
|
|
521
|
-
if (!instance.
|
|
522
|
-
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "
|
|
1415
|
+
if (!instance.testEnvironmentEnabled) {
|
|
1416
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Test environment not enabled. Set testEnvironment: true in createRuntime options.");
|
|
523
1417
|
return;
|
|
524
1418
|
}
|
|
525
1419
|
instance.lastActivity = Date.now();
|
|
526
1420
|
try {
|
|
527
|
-
|
|
528
|
-
sendOk(connection.socket, message.requestId);
|
|
1421
|
+
const result = getTestCountInContext(instance.runtime.context);
|
|
1422
|
+
sendOk(connection.socket, message.requestId, result);
|
|
529
1423
|
} catch (err) {
|
|
530
1424
|
const error = err;
|
|
531
1425
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
532
1426
|
}
|
|
533
1427
|
}
|
|
1428
|
+
async function handleRunPlaywrightTests(message, connection, _state) {
|
|
1429
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "playwright.runTests() has been removed. Use testEnvironment.runTests() instead.");
|
|
1430
|
+
}
|
|
1431
|
+
async function handleResetPlaywrightTests(message, connection, _state) {
|
|
1432
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "playwright.reset() has been removed. Use testEnvironment.reset() instead.");
|
|
1433
|
+
}
|
|
534
1434
|
async function handleGetCollectedData(message, connection, state) {
|
|
535
1435
|
const instance = state.isolates.get(message.isolateId);
|
|
536
1436
|
if (!instance) {
|
|
@@ -538,13 +1438,13 @@ async function handleGetCollectedData(message, connection, state) {
|
|
|
538
1438
|
return;
|
|
539
1439
|
}
|
|
540
1440
|
if (!instance.playwrightHandle) {
|
|
541
|
-
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Playwright not
|
|
1441
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Playwright not configured. Provide playwright.page in createRuntime options.");
|
|
542
1442
|
return;
|
|
543
1443
|
}
|
|
544
1444
|
instance.lastActivity = Date.now();
|
|
545
1445
|
try {
|
|
546
1446
|
const data = {
|
|
547
|
-
|
|
1447
|
+
browserConsoleLogs: instance.playwrightHandle.getBrowserConsoleLogs(),
|
|
548
1448
|
networkRequests: instance.playwrightHandle.getNetworkRequests(),
|
|
549
1449
|
networkResponses: instance.playwrightHandle.getNetworkResponses()
|
|
550
1450
|
};
|
|
@@ -554,8 +1454,27 @@ async function handleGetCollectedData(message, connection, state) {
|
|
|
554
1454
|
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
555
1455
|
}
|
|
556
1456
|
}
|
|
1457
|
+
async function handleClearCollectedData(message, connection, state) {
|
|
1458
|
+
const instance = state.isolates.get(message.isolateId);
|
|
1459
|
+
if (!instance) {
|
|
1460
|
+
sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_NOT_FOUND, `Isolate not found: ${message.isolateId}`);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
if (!instance.playwrightHandle) {
|
|
1464
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, "Playwright not configured. Provide playwright.page in createRuntime options.");
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
instance.lastActivity = Date.now();
|
|
1468
|
+
try {
|
|
1469
|
+
instance.playwrightHandle.clearCollected();
|
|
1470
|
+
sendOk(connection.socket, message.requestId);
|
|
1471
|
+
} catch (err) {
|
|
1472
|
+
const error = err;
|
|
1473
|
+
sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
557
1476
|
export {
|
|
558
1477
|
handleConnection
|
|
559
1478
|
};
|
|
560
1479
|
|
|
561
|
-
//# debugId=
|
|
1480
|
+
//# debugId=8FEF67C0571D870E64756E2164756E21
|