sandboxedjs 0.1.29 → 0.1.31
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/LICENSE +1 -7
- package/README.md +137 -92
- package/bin/sandboxedjs-serve.mjs +24 -0
- package/dist/browser-host.cjs +112 -0
- package/dist/browser-host.cjs.map +1 -0
- package/dist/browser-host.d.cts +25 -0
- package/dist/browser-host.d.ts +25 -0
- package/dist/browser-host.js +109 -0
- package/dist/browser-host.js.map +1 -0
- package/dist/index.cjs +464 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -16
- package/dist/index.d.ts +139 -16
- package/dist/index.js +462 -179
- package/dist/index.js.map +1 -1
- package/dist/service-worker.js +112 -0
- package/dist/service-worker.js.map +1 -0
- package/dist/worker-entry.js +140 -116
- package/dist/worker-entry.js.map +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/preview/router.ts
|
|
2
|
+
var CLAIM = /(?:^|\/)__sbx__\/(\d+)(?:\/|$)/;
|
|
3
|
+
var NOT_CONNECTED = 503;
|
|
4
|
+
var PreviewRouter = class {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
options;
|
|
9
|
+
clientPorts = /* @__PURE__ */ new Map();
|
|
10
|
+
pending = /* @__PURE__ */ new Map();
|
|
11
|
+
nextId = 1;
|
|
12
|
+
connected = false;
|
|
13
|
+
/** The page that answers requests has arrived. */
|
|
14
|
+
connect() {
|
|
15
|
+
this.connected = true;
|
|
16
|
+
}
|
|
17
|
+
/** Read a port out of a claim path, or null when this is not one. */
|
|
18
|
+
claimedPort(pathname) {
|
|
19
|
+
const match = CLAIM.exec(pathname);
|
|
20
|
+
return match ? Number(match[1]) : null;
|
|
21
|
+
}
|
|
22
|
+
/** Remember that everything from `clientId` belongs to `port`. */
|
|
23
|
+
bind(clientId, port) {
|
|
24
|
+
if (clientId) this.clientPorts.set(clientId, port);
|
|
25
|
+
}
|
|
26
|
+
/** Which port this client is previewing, or undefined for ordinary traffic. */
|
|
27
|
+
portFor(clientId) {
|
|
28
|
+
return this.clientPorts.get(clientId);
|
|
29
|
+
}
|
|
30
|
+
/** A response has come back from the page. */
|
|
31
|
+
settle(id, response) {
|
|
32
|
+
const resolve = this.pending.get(id);
|
|
33
|
+
if (!resolve) return;
|
|
34
|
+
this.pending.delete(id);
|
|
35
|
+
resolve(response);
|
|
36
|
+
}
|
|
37
|
+
/** Forget a client that has gone away. */
|
|
38
|
+
release(clientId) {
|
|
39
|
+
this.clientPorts.delete(clientId);
|
|
40
|
+
}
|
|
41
|
+
/** Ask the page for one response and rebuild it as a `Response`. */
|
|
42
|
+
async fetch(port, path, request) {
|
|
43
|
+
if (!this.connected) {
|
|
44
|
+
return new Response("The sandbox preview is not connected yet.", { status: NOT_CONNECTED });
|
|
45
|
+
}
|
|
46
|
+
const body = request.method === "GET" || request.method === "HEAD" ? void 0 : await request.arrayBuffer();
|
|
47
|
+
const id = this.nextId++;
|
|
48
|
+
const answered = new Promise((resolve) => this.pending.set(id, resolve));
|
|
49
|
+
this.options.send(
|
|
50
|
+
{ id, port, path, method: request.method, headers: headerRecord(request.headers), ...body ? { body } : {} },
|
|
51
|
+
body ? [body] : []
|
|
52
|
+
);
|
|
53
|
+
const timeoutMs = this.options.timeoutMs ?? 3e4;
|
|
54
|
+
const result = await Promise.race([
|
|
55
|
+
answered,
|
|
56
|
+
new Promise(
|
|
57
|
+
(resolve) => setTimeout(
|
|
58
|
+
() => resolve({ status: 504, statusText: "Gateway Timeout", headers: {}, body: new ArrayBuffer(0) }),
|
|
59
|
+
timeoutMs
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
]);
|
|
63
|
+
this.pending.delete(id);
|
|
64
|
+
const headers = new Headers(result.headers);
|
|
65
|
+
headers.set("Cross-Origin-Resource-Policy", "cross-origin");
|
|
66
|
+
headers.set("Cross-Origin-Embedder-Policy", "require-corp");
|
|
67
|
+
return new Response(result.body, { status: result.status, statusText: result.statusText, headers });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function headerRecord(headers) {
|
|
71
|
+
const out = {};
|
|
72
|
+
headers.forEach((value, key) => {
|
|
73
|
+
out[key] = value;
|
|
74
|
+
});
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/preview/service-worker.ts
|
|
79
|
+
var worker = globalThis;
|
|
80
|
+
var host = null;
|
|
81
|
+
var router = new PreviewRouter({
|
|
82
|
+
send: (request, transfer) => host?.postMessage(request, transfer)
|
|
83
|
+
});
|
|
84
|
+
worker.addEventListener("install", () => void worker.skipWaiting());
|
|
85
|
+
worker.addEventListener("activate", (event) => event.waitUntil(worker.clients.claim()));
|
|
86
|
+
worker.addEventListener("message", (event) => {
|
|
87
|
+
const data = event.data;
|
|
88
|
+
if (data?.type !== "sandboxedjs:connect") return;
|
|
89
|
+
host = event.ports[0] ?? null;
|
|
90
|
+
if (!host) return;
|
|
91
|
+
host.onmessage = (message) => {
|
|
92
|
+
const reply = message.data;
|
|
93
|
+
router.settle(reply.id, reply.response);
|
|
94
|
+
};
|
|
95
|
+
host.start();
|
|
96
|
+
router.connect();
|
|
97
|
+
});
|
|
98
|
+
worker.addEventListener("fetch", (event) => {
|
|
99
|
+
const url = new URL(event.request.url);
|
|
100
|
+
if (url.origin !== worker.location.origin) return;
|
|
101
|
+
const claimed = router.claimedPort(url.pathname);
|
|
102
|
+
if (claimed !== null && event.request.mode === "navigate") {
|
|
103
|
+
router.bind(event.resultingClientId || event.clientId, claimed);
|
|
104
|
+
event.respondWith(router.fetch(claimed, "/", event.request));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const port = router.portFor(event.clientId);
|
|
108
|
+
if (port === void 0) return;
|
|
109
|
+
event.respondWith(router.fetch(port, url.pathname + url.search, event.request));
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=service-worker.js.map
|
|
112
|
+
//# sourceMappingURL=service-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preview/router.ts","../src/preview/service-worker.ts"],"names":[],"mappings":"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","file":"service-worker.js","sourcesContent":["/**\n * The routing a preview service worker does, separated from the worker itself.\n *\n * A service worker is awkward to test — it needs a browser, a registration and\n * a secure context — but almost none of what makes this correct is about being\n * a service worker. The interesting part is the bookkeeping: which client is\n * previewing which port, which requests belong to a preview at all, and how a\n * response is rebuilt on the way back. That lives here, where it can be\n * exercised directly.\n *\n * The rule this implements, and the reason it exists: requests are routed by\n * **who is asking**, not by what the path looks like. An iframe navigates once\n * to `/__sbx__/<port>/`; every later request from that client — however\n * absolute its path — is recognised by its client id. Nothing has to rewrite\n * `/src/main.js` or `/@vite/client`, which is what makes a real dev server\n * work rather than only a single page.\n */\n\n/**\n * The one-time path an iframe navigates to in order to claim a port.\n *\n * Deliberately not anchored to the start of the path. A service worker's scope\n * is the directory its script was served from, so in a published package the\n * claim arrives under something like\n * `/node_modules/sandboxedjs/dist/__sbx__/5173/` — anchoring here would match\n * only when the worker happened to be served from the site root.\n */\nconst CLAIM = /(?:^|\\/)__sbx__\\/(\\d+)(?:\\/|$)/;\n\nexport interface ProxiedRequest {\n id: number;\n port: number;\n path: string;\n method: string;\n headers: Record<string, string>;\n body?: ArrayBuffer;\n}\n\nexport interface ProxiedResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: ArrayBuffer;\n}\n\nexport interface PreviewRouterOptions {\n /** Hand a request to whichever page owns the container. */\n send(request: ProxiedRequest, transfer: ArrayBuffer[]): void;\n /** How long to wait for that page before giving up. */\n timeoutMs?: number;\n}\n\n/** The port to answer with when nothing has connected yet. */\nconst NOT_CONNECTED = 503;\n\nexport class PreviewRouter {\n private readonly clientPorts = new Map<string, number>();\n private readonly pending = new Map<number, (value: ProxiedResponse) => void>();\n private nextId = 1;\n private connected = false;\n\n constructor(private readonly options: PreviewRouterOptions) {}\n\n /** The page that answers requests has arrived. */\n connect(): void {\n this.connected = true;\n }\n\n /** Read a port out of a claim path, or null when this is not one. */\n claimedPort(pathname: string): number | null {\n const match = CLAIM.exec(pathname);\n return match ? Number(match[1]) : null;\n }\n\n /** Remember that everything from `clientId` belongs to `port`. */\n bind(clientId: string, port: number): void {\n if (clientId) this.clientPorts.set(clientId, port);\n }\n\n /** Which port this client is previewing, or undefined for ordinary traffic. */\n portFor(clientId: string): number | undefined {\n return this.clientPorts.get(clientId);\n }\n\n /** A response has come back from the page. */\n settle(id: number, response: ProxiedResponse): void {\n const resolve = this.pending.get(id);\n if (!resolve) return;\n this.pending.delete(id);\n resolve(response);\n }\n\n /** Forget a client that has gone away. */\n release(clientId: string): void {\n this.clientPorts.delete(clientId);\n }\n\n /** Ask the page for one response and rebuild it as a `Response`. */\n async fetch(port: number, path: string, request: Request): Promise<Response> {\n if (!this.connected) {\n return new Response(\"The sandbox preview is not connected yet.\", { status: NOT_CONNECTED });\n }\n const body = request.method === \"GET\" || request.method === \"HEAD\" ? undefined : await request.arrayBuffer();\n const id = this.nextId++;\n const answered = new Promise<ProxiedResponse>((resolve) => this.pending.set(id, resolve));\n\n this.options.send(\n { id, port, path, method: request.method, headers: headerRecord(request.headers), ...(body ? { body } : {}) },\n body ? [body] : [],\n );\n\n const timeoutMs = this.options.timeoutMs ?? 30_000;\n const result = await Promise.race([\n answered,\n new Promise<ProxiedResponse>((resolve) =>\n setTimeout(\n () => resolve({ status: 504, statusText: \"Gateway Timeout\", headers: {}, body: new ArrayBuffer(0) }),\n timeoutMs,\n ),\n ),\n ]);\n this.pending.delete(id);\n\n const headers = new Headers(result.headers);\n /*\n * Two headers, for two different refusals, both of which a cross-origin\n * isolated page makes — and pages that run Rolldown have to be isolated.\n *\n * `Cross-Origin-Resource-Policy` lets the response be loaded as a\n * subresource at all. `Cross-Origin-Embedder-Policy` lets it be *framed*:\n * without it the document is not isolation-compatible, so the iframe is\n * replaced by an error page and the parent sees an opaque, cross-origin\n * frame rather than the site it asked for. Everything served here carries\n * CORP, so the stricter policy is one this preview can satisfy.\n */\n headers.set(\"Cross-Origin-Resource-Policy\", \"cross-origin\");\n headers.set(\"Cross-Origin-Embedder-Policy\", \"require-corp\");\n return new Response(result.body, { status: result.status, statusText: result.statusText, headers });\n }\n}\n\n/** `Headers` is iterable at runtime; the DOM lib used here does not say so. */\nfunction headerRecord(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n (headers as unknown as { forEach(fn: (value: string, key: string) => void): void })\n .forEach((value, key) => { out[key] = value; });\n return out;\n}\n","/**\n * A service worker that gives a container's HTTP servers real URLs.\n *\n * Without one, a page can reach a server inside the container only through\n * `box.request(port, …)`, which hands back a single response. That is enough to\n * read an API or drop some HTML into an element, but not to *run* a site: a\n * dev server emits absolute URLs like `/src/main.js` and `/@vite/client`, and\n * nothing in the page resolves those to the sandbox.\n *\n * This file is deliberately only the glue. The routing lives in\n * {@link PreviewRouter}, where it can be tested without a browser — a service\n * worker otherwise needs a registration, a secure context and a real page\n * before any of its behaviour can be observed at all.\n */\n\nimport { PreviewRouter, type ProxiedResponse } from \"./router.js\";\n\n/*\n * Typed locally rather than through `lib.webworker`: this package compiles with\n * the DOM lib, and the two declare a conflicting `self`. Only the handful of\n * members actually used are described here.\n */\ninterface FetchLikeEvent extends Event {\n request: Request;\n clientId: string;\n resultingClientId: string;\n respondWith(response: Promise<Response> | Response): void;\n}\ninterface ExtendableLikeEvent extends Event {\n waitUntil(promise: Promise<unknown>): void;\n}\ninterface MessageLikeEvent extends Event {\n data: unknown;\n ports: readonly MessagePort[];\n}\ninterface WorkerScope {\n location: { origin: string };\n clients: { claim(): Promise<void> };\n skipWaiting(): Promise<void>;\n addEventListener(type: \"install\" | \"activate\", listener: (event: ExtendableLikeEvent) => void): void;\n addEventListener(type: \"fetch\", listener: (event: FetchLikeEvent) => void): void;\n addEventListener(type: \"message\", listener: (event: MessageLikeEvent) => void): void;\n}\n\nconst worker = globalThis as unknown as WorkerScope;\n\nlet host: MessagePort | null = null;\nconst router = new PreviewRouter({\n send: (request, transfer) => host?.postMessage(request, transfer),\n});\n\nworker.addEventListener(\"install\", () => void worker.skipWaiting());\nworker.addEventListener(\"activate\", (event) => event.waitUntil(worker.clients.claim()));\n\nworker.addEventListener(\"message\", (event) => {\n const data = event.data as { type?: string } | null;\n if (data?.type !== \"sandboxedjs:connect\") return;\n host = event.ports[0] ?? null;\n if (!host) return;\n host.onmessage = (message) => {\n const reply = message.data as { id: number; response: ProxiedResponse };\n router.settle(reply.id, reply.response);\n };\n host.start();\n router.connect();\n});\n\nworker.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== worker.location.origin) return;\n\n const claimed = router.claimedPort(url.pathname);\n if (claimed !== null && event.request.mode === \"navigate\") {\n /* A navigation's client does not exist yet, so it is named by\n * `resultingClientId`. Binding it here is what lets every later request\n * from that document be recognised without touching its URLs. */\n router.bind(event.resultingClientId || event.clientId, claimed);\n event.respondWith(router.fetch(claimed, \"/\", event.request));\n return;\n }\n\n const port = router.portFor(event.clientId);\n if (port === undefined) return; // not a preview: the host page's own traffic\n event.respondWith(router.fetch(port, url.pathname + url.search, event.request));\n});\n"]}
|
package/dist/worker-entry.js
CHANGED
|
@@ -27251,6 +27251,7 @@ var VirtualHttpServer = class extends import_events.default {
|
|
|
27251
27251
|
owner;
|
|
27252
27252
|
listening = false;
|
|
27253
27253
|
portValue = null;
|
|
27254
|
+
referenced = true;
|
|
27254
27255
|
listen(...args) {
|
|
27255
27256
|
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
27256
27257
|
const first = args[0];
|
|
@@ -27279,11 +27280,16 @@ var VirtualHttpServer = class extends import_events.default {
|
|
|
27279
27280
|
return this.portValue === null ? null : { address: "0.0.0.0", family: "IPv4", port: this.portValue };
|
|
27280
27281
|
}
|
|
27281
27282
|
ref() {
|
|
27283
|
+
this.referenced = true;
|
|
27282
27284
|
return this;
|
|
27283
27285
|
}
|
|
27284
27286
|
unref() {
|
|
27287
|
+
this.referenced = false;
|
|
27285
27288
|
return this;
|
|
27286
27289
|
}
|
|
27290
|
+
hasRef() {
|
|
27291
|
+
return this.referenced;
|
|
27292
|
+
}
|
|
27287
27293
|
setTimeout(_milliseconds, callback) {
|
|
27288
27294
|
if (callback) this.on("timeout", callback);
|
|
27289
27295
|
return this;
|
|
@@ -27293,13 +27299,16 @@ var VirtualHttpRouter = class {
|
|
|
27293
27299
|
servers = /* @__PURE__ */ new Map();
|
|
27294
27300
|
/** Notified when a server begins listening, for `onServerReady`. */
|
|
27295
27301
|
onListen;
|
|
27302
|
+
onClose;
|
|
27296
27303
|
register(port, server, owner) {
|
|
27297
27304
|
if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
|
|
27298
27305
|
this.servers.set(port, { server, owner });
|
|
27299
27306
|
this.onListen?.(port);
|
|
27300
27307
|
}
|
|
27301
27308
|
unregister(port, server) {
|
|
27302
|
-
if (this.servers.get(port)?.server
|
|
27309
|
+
if (this.servers.get(port)?.server !== server) return;
|
|
27310
|
+
this.servers.delete(port);
|
|
27311
|
+
this.onClose?.(port);
|
|
27303
27312
|
}
|
|
27304
27313
|
/** Whether anything in this container is listening on `port`. */
|
|
27305
27314
|
activePortsIncludes(port) {
|
|
@@ -27308,6 +27317,9 @@ var VirtualHttpRouter = class {
|
|
|
27308
27317
|
activePorts(owner) {
|
|
27309
27318
|
return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
|
|
27310
27319
|
}
|
|
27320
|
+
referencedPorts(owner) {
|
|
27321
|
+
return [...this.servers].filter(([, item]) => item.owner === owner && item.server.hasRef()).map(([port]) => port);
|
|
27322
|
+
}
|
|
27311
27323
|
closeOwner(owner) {
|
|
27312
27324
|
for (const { server, owner: value } of [...this.servers.values()]) if (value === owner) server.close();
|
|
27313
27325
|
}
|
|
@@ -28582,6 +28594,102 @@ function toBytes2(data2, encoding) {
|
|
|
28582
28594
|
return typeof data2 === "string" ? Buffer2.from(data2, encoding) : data2;
|
|
28583
28595
|
}
|
|
28584
28596
|
|
|
28597
|
+
// src/runtime/sync-channel.ts
|
|
28598
|
+
var STATE = 0;
|
|
28599
|
+
var LENGTH2 = 1;
|
|
28600
|
+
var MORE = 2;
|
|
28601
|
+
var STATE_IDLE = 0;
|
|
28602
|
+
var STATE_REQUEST = 1;
|
|
28603
|
+
var STATE_RESPONSE = 2;
|
|
28604
|
+
var STATE_CONTINUE = 3;
|
|
28605
|
+
var WAIT_SLICE_MS = 250;
|
|
28606
|
+
function syncChannelUnavailableReason() {
|
|
28607
|
+
if (typeof globalThis.crossOriginIsolated === "boolean" && !globalThis.crossOriginIsolated) {
|
|
28608
|
+
return "The browser host is not cross-origin isolated. Serve the host app over HTTPS or localhost with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. For a built app use sandboxedjs-serve <directory>. Embedded browsers or iframe policies may also prevent isolation.";
|
|
28609
|
+
}
|
|
28610
|
+
if (typeof SharedArrayBuffer !== "function") return "SharedArrayBuffer is unavailable in this host.";
|
|
28611
|
+
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return "Atomics.wait is unavailable in this host.";
|
|
28612
|
+
return null;
|
|
28613
|
+
}
|
|
28614
|
+
var SyncChannelClosedError = class extends Error {
|
|
28615
|
+
code = "ERR_SYNC_CHANNEL_CLOSED";
|
|
28616
|
+
constructor() {
|
|
28617
|
+
super("the sandbox runtime host went away while a synchronous call was waiting");
|
|
28618
|
+
}
|
|
28619
|
+
};
|
|
28620
|
+
var SyncChannelClient = class {
|
|
28621
|
+
constructor(buffers, wake) {
|
|
28622
|
+
this.wake = wake;
|
|
28623
|
+
this.control = new Int32Array(buffers.control);
|
|
28624
|
+
this.data = new Uint8Array(buffers.data);
|
|
28625
|
+
this.capacity = this.data.length;
|
|
28626
|
+
}
|
|
28627
|
+
wake;
|
|
28628
|
+
control;
|
|
28629
|
+
data;
|
|
28630
|
+
capacity;
|
|
28631
|
+
/** Send a request and block until the whole response has come back. */
|
|
28632
|
+
call(request) {
|
|
28633
|
+
this.send(request);
|
|
28634
|
+
return this.receive();
|
|
28635
|
+
}
|
|
28636
|
+
/** Write the request across, a window at a time. */
|
|
28637
|
+
send(request) {
|
|
28638
|
+
let offset2 = 0;
|
|
28639
|
+
for (; ; ) {
|
|
28640
|
+
const size = Math.min(this.capacity, request.length - offset2);
|
|
28641
|
+
this.data.set(request.subarray(offset2, offset2 + size), 0);
|
|
28642
|
+
offset2 += size;
|
|
28643
|
+
Atomics.store(this.control, LENGTH2, size);
|
|
28644
|
+
Atomics.store(this.control, MORE, offset2 < request.length ? 1 : 0);
|
|
28645
|
+
this.publish(STATE_REQUEST);
|
|
28646
|
+
const next = this.waitWhile(STATE_REQUEST);
|
|
28647
|
+
if (next === STATE_RESPONSE) return;
|
|
28648
|
+
if (next !== STATE_CONTINUE) throw new SyncChannelClosedError();
|
|
28649
|
+
}
|
|
28650
|
+
}
|
|
28651
|
+
/** Read the response back, a window at a time. */
|
|
28652
|
+
receive() {
|
|
28653
|
+
const parts = [];
|
|
28654
|
+
let total = 0;
|
|
28655
|
+
for (; ; ) {
|
|
28656
|
+
const size = Atomics.load(this.control, LENGTH2);
|
|
28657
|
+
parts.push(this.data.slice(0, size));
|
|
28658
|
+
total += size;
|
|
28659
|
+
if (Atomics.load(this.control, MORE) === 0) break;
|
|
28660
|
+
this.publish(STATE_CONTINUE);
|
|
28661
|
+
if (this.waitWhile(STATE_CONTINUE) !== STATE_RESPONSE) throw new SyncChannelClosedError();
|
|
28662
|
+
}
|
|
28663
|
+
Atomics.store(this.control, STATE, STATE_IDLE);
|
|
28664
|
+
if (parts.length === 1) return parts[0];
|
|
28665
|
+
const joined = new Uint8Array(total);
|
|
28666
|
+
let at2 = 0;
|
|
28667
|
+
for (const part of parts) {
|
|
28668
|
+
joined.set(part, at2);
|
|
28669
|
+
at2 += part.length;
|
|
28670
|
+
}
|
|
28671
|
+
return joined;
|
|
28672
|
+
}
|
|
28673
|
+
publish(state) {
|
|
28674
|
+
Atomics.store(this.control, STATE, state);
|
|
28675
|
+
Atomics.notify(this.control, STATE);
|
|
28676
|
+
this.wake();
|
|
28677
|
+
}
|
|
28678
|
+
/**
|
|
28679
|
+
* Block until the state moves off `state`.
|
|
28680
|
+
*
|
|
28681
|
+
* Waking periodically rather than waiting forever is what lets a torn-down
|
|
28682
|
+
* host release a worker instead of stranding it.
|
|
28683
|
+
*/
|
|
28684
|
+
waitWhile(state) {
|
|
28685
|
+
for (; ; ) {
|
|
28686
|
+
Atomics.wait(this.control, STATE, state, WAIT_SLICE_MS);
|
|
28687
|
+
const current2 = Atomics.load(this.control, STATE);
|
|
28688
|
+
if (current2 !== state) return current2;
|
|
28689
|
+
}
|
|
28690
|
+
}
|
|
28691
|
+
};
|
|
28692
|
+
|
|
28585
28693
|
// src/runtime/child-process-module.ts
|
|
28586
28694
|
var import_events2 = __toESM(require_events());
|
|
28587
28695
|
var import_stream_browserify3 = __toESM(require_stream_browserify());
|
|
@@ -28657,7 +28765,8 @@ var ChildProcess = class extends import_events2.default {
|
|
|
28657
28765
|
queueMicrotask(() => this.emit("close", code, null));
|
|
28658
28766
|
}
|
|
28659
28767
|
};
|
|
28660
|
-
function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
|
|
28768
|
+
function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv = () => ({})) {
|
|
28769
|
+
const environmentFor = (options) => options.env ? { ...options.env } : defaultEnv();
|
|
28661
28770
|
const throughShell = (command, options) => {
|
|
28662
28771
|
const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
|
|
28663
28772
|
return { file: shell, args: ["-c", command] };
|
|
@@ -28668,7 +28777,7 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
|
|
|
28668
28777
|
command: resolved.file,
|
|
28669
28778
|
args: resolved.args,
|
|
28670
28779
|
cwd: options.cwd ?? defaultCwd(),
|
|
28671
|
-
|
|
28780
|
+
env: environmentFor(options)
|
|
28672
28781
|
});
|
|
28673
28782
|
return new ChildProcess(handle, resolved.file, resolved.args);
|
|
28674
28783
|
};
|
|
@@ -28714,11 +28823,11 @@ ${err2.join("")}`),
|
|
|
28714
28823
|
exec,
|
|
28715
28824
|
execFile,
|
|
28716
28825
|
fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
|
|
28717
|
-
...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
|
|
28826
|
+
...buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor),
|
|
28718
28827
|
ChildProcess
|
|
28719
28828
|
};
|
|
28720
28829
|
}
|
|
28721
|
-
function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
|
|
28830
|
+
function buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor) {
|
|
28722
28831
|
if (!syncSpawn) {
|
|
28723
28832
|
return {
|
|
28724
28833
|
execSync: unavailable("execSync"),
|
|
@@ -28729,12 +28838,14 @@ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
|
|
|
28729
28838
|
const run2 = (file, args, options) => {
|
|
28730
28839
|
const resolved = options.shell ? throughShell([file, ...args].join(" "), options) : { file, args };
|
|
28731
28840
|
const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
|
|
28841
|
+
const inherit = options.stdio === "inherit" || Array.isArray(options.stdio) && options.stdio[0] === "inherit";
|
|
28732
28842
|
return syncSpawn({
|
|
28733
28843
|
command: resolved.file,
|
|
28734
28844
|
args: resolved.args,
|
|
28735
28845
|
cwd: options.cwd ?? defaultCwd(),
|
|
28736
|
-
|
|
28737
|
-
...input === void 0 ? {} : { input }
|
|
28846
|
+
env: environmentFor(options),
|
|
28847
|
+
...input === void 0 ? {} : { input },
|
|
28848
|
+
...inherit ? { inheritStdio: true } : {}
|
|
28738
28849
|
});
|
|
28739
28850
|
};
|
|
28740
28851
|
const asOutput = (text, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text) : text;
|
|
@@ -28786,7 +28897,7 @@ function unavailable(name) {
|
|
|
28786
28897
|
const list = Array.isArray(args[1]) ? args[1].map(String) : [];
|
|
28787
28898
|
const command = file ? [file, ...list].join(" ") : void 0;
|
|
28788
28899
|
const error = new Error(
|
|
28789
|
-
`child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child.
|
|
28900
|
+
`child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Enable the SandboxedJs worker runtime with createContainer({ isolation: "worker" }) and fix any boot prerequisite it reports. ` + (syncChannelUnavailableReason() ?? "This process is using the realm runtime, which cannot provide synchronous child processes.")
|
|
28790
28901
|
);
|
|
28791
28902
|
error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
28792
28903
|
throw error;
|
|
@@ -29112,6 +29223,11 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
29112
29223
|
edit(sequence, key) {
|
|
29113
29224
|
if (this.closed) return;
|
|
29114
29225
|
const name = key.name;
|
|
29226
|
+
if (key.ctrl && name === "c") {
|
|
29227
|
+
if (this.listenerCount("SIGINT") > 0) this.emit("SIGINT");
|
|
29228
|
+
else this.close();
|
|
29229
|
+
return;
|
|
29230
|
+
}
|
|
29115
29231
|
if (name === "return" || name === "enter") {
|
|
29116
29232
|
const value = this.line;
|
|
29117
29233
|
this.line = "";
|
|
@@ -29207,7 +29323,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
29207
29323
|
};
|
|
29208
29324
|
const createInterface = (options, output) => {
|
|
29209
29325
|
if (options && typeof options === "object" && !options.on) {
|
|
29210
|
-
const resolvedOutput2 = options.output
|
|
29326
|
+
const resolvedOutput2 = options.output;
|
|
29211
29327
|
const instance = new Interface(
|
|
29212
29328
|
options.input ?? defaultInput(),
|
|
29213
29329
|
resolvedOutput2,
|
|
@@ -29216,7 +29332,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
|
|
|
29216
29332
|
if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
|
|
29217
29333
|
return instance;
|
|
29218
29334
|
}
|
|
29219
|
-
const resolvedOutput = output
|
|
29335
|
+
const resolvedOutput = output;
|
|
29220
29336
|
return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
|
|
29221
29337
|
};
|
|
29222
29338
|
const noop = () => {
|
|
@@ -29456,7 +29572,7 @@ function createCoreModules(options) {
|
|
|
29456
29572
|
};
|
|
29457
29573
|
const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
|
|
29458
29574
|
const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
|
|
29459
|
-
const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
|
|
29575
|
+
const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn, () => ({ ...processObject.env })) : createUnsupportedModule("child_process");
|
|
29460
29576
|
const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
|
|
29461
29577
|
const dns = createDnsModule();
|
|
29462
29578
|
const builtins = {
|
|
@@ -29489,7 +29605,7 @@ function createCoreModules(options) {
|
|
|
29489
29605
|
"stream/promises": createStreamPromises(),
|
|
29490
29606
|
string_decoder: import_string_decoder.default,
|
|
29491
29607
|
timers: { ...import_timers_browserify.default, ...timers.api },
|
|
29492
|
-
"timers/promises": createTimerPromises(),
|
|
29608
|
+
"timers/promises": createTimerPromises(timers.api),
|
|
29493
29609
|
tty: { isatty: () => false, ReadStream: import_stream_browserify4.default.Readable, WriteStream: import_stream_browserify4.default.Writable },
|
|
29494
29610
|
url: url_module_default,
|
|
29495
29611
|
util: util_module_default,
|
|
@@ -30234,10 +30350,10 @@ function createTrackedTimers() {
|
|
|
30234
30350
|
cancelAll
|
|
30235
30351
|
};
|
|
30236
30352
|
}
|
|
30237
|
-
function createTimerPromises() {
|
|
30353
|
+
function createTimerPromises(timers) {
|
|
30238
30354
|
return {
|
|
30239
|
-
setTimeout: (delay, value) => new Promise((resolve2) => setTimeout(resolve2, delay, value)),
|
|
30240
|
-
setImmediate: (value) => new Promise((resolve2) =>
|
|
30355
|
+
setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
|
|
30356
|
+
setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
|
|
30241
30357
|
};
|
|
30242
30358
|
}
|
|
30243
30359
|
function createAsyncHooksModule() {
|
|
@@ -30488,94 +30604,6 @@ var RemoteVolume = class {
|
|
|
30488
30604
|
}
|
|
30489
30605
|
};
|
|
30490
30606
|
|
|
30491
|
-
// src/runtime/sync-channel.ts
|
|
30492
|
-
var STATE = 0;
|
|
30493
|
-
var LENGTH2 = 1;
|
|
30494
|
-
var MORE = 2;
|
|
30495
|
-
var STATE_IDLE = 0;
|
|
30496
|
-
var STATE_REQUEST = 1;
|
|
30497
|
-
var STATE_RESPONSE = 2;
|
|
30498
|
-
var STATE_CONTINUE = 3;
|
|
30499
|
-
var WAIT_SLICE_MS = 250;
|
|
30500
|
-
var SyncChannelClosedError = class extends Error {
|
|
30501
|
-
code = "ERR_SYNC_CHANNEL_CLOSED";
|
|
30502
|
-
constructor() {
|
|
30503
|
-
super("the sandbox runtime host went away while a synchronous call was waiting");
|
|
30504
|
-
}
|
|
30505
|
-
};
|
|
30506
|
-
var SyncChannelClient = class {
|
|
30507
|
-
constructor(buffers, wake) {
|
|
30508
|
-
this.wake = wake;
|
|
30509
|
-
this.control = new Int32Array(buffers.control);
|
|
30510
|
-
this.data = new Uint8Array(buffers.data);
|
|
30511
|
-
this.capacity = this.data.length;
|
|
30512
|
-
}
|
|
30513
|
-
wake;
|
|
30514
|
-
control;
|
|
30515
|
-
data;
|
|
30516
|
-
capacity;
|
|
30517
|
-
/** Send a request and block until the whole response has come back. */
|
|
30518
|
-
call(request) {
|
|
30519
|
-
this.send(request);
|
|
30520
|
-
return this.receive();
|
|
30521
|
-
}
|
|
30522
|
-
/** Write the request across, a window at a time. */
|
|
30523
|
-
send(request) {
|
|
30524
|
-
let offset2 = 0;
|
|
30525
|
-
for (; ; ) {
|
|
30526
|
-
const size = Math.min(this.capacity, request.length - offset2);
|
|
30527
|
-
this.data.set(request.subarray(offset2, offset2 + size), 0);
|
|
30528
|
-
offset2 += size;
|
|
30529
|
-
Atomics.store(this.control, LENGTH2, size);
|
|
30530
|
-
Atomics.store(this.control, MORE, offset2 < request.length ? 1 : 0);
|
|
30531
|
-
this.publish(STATE_REQUEST);
|
|
30532
|
-
const next = this.waitWhile(STATE_REQUEST);
|
|
30533
|
-
if (next === STATE_RESPONSE) return;
|
|
30534
|
-
if (next !== STATE_CONTINUE) throw new SyncChannelClosedError();
|
|
30535
|
-
}
|
|
30536
|
-
}
|
|
30537
|
-
/** Read the response back, a window at a time. */
|
|
30538
|
-
receive() {
|
|
30539
|
-
const parts = [];
|
|
30540
|
-
let total = 0;
|
|
30541
|
-
for (; ; ) {
|
|
30542
|
-
const size = Atomics.load(this.control, LENGTH2);
|
|
30543
|
-
parts.push(this.data.slice(0, size));
|
|
30544
|
-
total += size;
|
|
30545
|
-
if (Atomics.load(this.control, MORE) === 0) break;
|
|
30546
|
-
this.publish(STATE_CONTINUE);
|
|
30547
|
-
if (this.waitWhile(STATE_CONTINUE) !== STATE_RESPONSE) throw new SyncChannelClosedError();
|
|
30548
|
-
}
|
|
30549
|
-
Atomics.store(this.control, STATE, STATE_IDLE);
|
|
30550
|
-
if (parts.length === 1) return parts[0];
|
|
30551
|
-
const joined = new Uint8Array(total);
|
|
30552
|
-
let at2 = 0;
|
|
30553
|
-
for (const part of parts) {
|
|
30554
|
-
joined.set(part, at2);
|
|
30555
|
-
at2 += part.length;
|
|
30556
|
-
}
|
|
30557
|
-
return joined;
|
|
30558
|
-
}
|
|
30559
|
-
publish(state) {
|
|
30560
|
-
Atomics.store(this.control, STATE, state);
|
|
30561
|
-
Atomics.notify(this.control, STATE);
|
|
30562
|
-
this.wake();
|
|
30563
|
-
}
|
|
30564
|
-
/**
|
|
30565
|
-
* Block until the state moves off `state`.
|
|
30566
|
-
*
|
|
30567
|
-
* Waking periodically rather than waiting forever is what lets a torn-down
|
|
30568
|
-
* host release a worker instead of stranding it.
|
|
30569
|
-
*/
|
|
30570
|
-
waitWhile(state) {
|
|
30571
|
-
for (; ; ) {
|
|
30572
|
-
Atomics.wait(this.control, STATE, state, WAIT_SLICE_MS);
|
|
30573
|
-
const current2 = Atomics.load(this.control, STATE);
|
|
30574
|
-
if (current2 !== state) return current2;
|
|
30575
|
-
}
|
|
30576
|
-
}
|
|
30577
|
-
};
|
|
30578
|
-
|
|
30579
30607
|
// src/runtime/sync-syscalls.ts
|
|
30580
30608
|
var SPAWN_OP = "spawnSync";
|
|
30581
30609
|
function spawnSyncOverChannel(channel, request) {
|
|
@@ -30652,6 +30680,7 @@ async function run(port, start) {
|
|
|
30652
30680
|
const router = new VirtualHttpRouter();
|
|
30653
30681
|
const owner = "worker";
|
|
30654
30682
|
router.onListen = (listening) => port.postMessage({ type: "listen", port: listening });
|
|
30683
|
+
router.onClose = (closed) => port.postMessage({ type: "close-port", port: closed });
|
|
30655
30684
|
let requestedExit = 0;
|
|
30656
30685
|
let engine;
|
|
30657
30686
|
let finished = false;
|
|
@@ -30680,6 +30709,8 @@ async function run(port, start) {
|
|
|
30680
30709
|
const finish = (code) => {
|
|
30681
30710
|
if (finished) return;
|
|
30682
30711
|
finished = true;
|
|
30712
|
+
core.cancelTimers();
|
|
30713
|
+
router.closeOwner(owner);
|
|
30683
30714
|
port.postMessage({ type: "exit", code });
|
|
30684
30715
|
};
|
|
30685
30716
|
port.onMessage((message) => {
|
|
@@ -30701,7 +30732,6 @@ async function run(port, start) {
|
|
|
30701
30732
|
try {
|
|
30702
30733
|
await engine.run(start.script);
|
|
30703
30734
|
await settle(router, owner, core, () => finished);
|
|
30704
|
-
if (router.activePorts(owner).length) return;
|
|
30705
30735
|
finish(requestedExit);
|
|
30706
30736
|
} catch (error) {
|
|
30707
30737
|
if (error instanceof ProcessExit) {
|
|
@@ -30713,18 +30743,12 @@ async function run(port, start) {
|
|
|
30713
30743
|
}
|
|
30714
30744
|
}
|
|
30715
30745
|
async function settle(router, owner, core, killed) {
|
|
30716
|
-
|
|
30717
|
-
|
|
30718
|
-
|
|
30719
|
-
|
|
30720
|
-
|
|
30721
|
-
|
|
30722
|
-
while (!router.activePorts(owner).length && !killed() && Date.now() < deadline) {
|
|
30723
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
30724
|
-
}
|
|
30725
|
-
}
|
|
30726
|
-
while (!router.activePorts(owner).length && !killed() && (core.pendingHandles() > 0 || core.pendingRequests() > 0 || core.readingStdin())) {
|
|
30727
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
30746
|
+
let idleTurns = 0;
|
|
30747
|
+
while (!killed()) {
|
|
30748
|
+
const busy = router.referencedPorts(owner).length > 0 || core.pendingHandles() > 0 || core.pendingRequests() > 0 || core.readingStdin();
|
|
30749
|
+
idleTurns = busy ? 0 : idleTurns + 1;
|
|
30750
|
+
if (idleTurns >= DRAIN_TURNS) return;
|
|
30751
|
+
await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
|
|
30728
30752
|
}
|
|
30729
30753
|
}
|
|
30730
30754
|
async function answer(port, router, message) {
|