sandboxedjs 0.2.3 → 0.2.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 +44 -13
- package/bin/sandboxedjs-egress.mjs +8 -69
- package/dist/agent.d.cts +2 -2
- package/dist/agent.d.ts +2 -2
- package/dist/{container-C9Y8dqos.d.cts → container-BQx27_d-.d.cts} +1 -1
- package/dist/{container-CIE93_Gh.d.ts → container-D-e5SMXQ.d.ts} +1 -1
- package/dist/{contracts-B6SHFjma.d.cts → contracts-BHo4LdY2.d.cts} +23 -0
- package/dist/{contracts-B6SHFjma.d.ts → contracts-BHo4LdY2.d.ts} +23 -0
- package/dist/egress.cjs +127 -0
- package/dist/egress.d.cts +51 -0
- package/dist/egress.d.ts +51 -0
- package/dist/egress.js +124 -0
- package/dist/index.cjs +120 -58
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +120 -58
- package/dist/{memory-volume-CRhXUyCN.d.cts → memory-volume-e-uJFRnG.d.cts} +1 -1
- package/dist/{memory-volume-CwiXvRfs.d.ts → memory-volume-meoRW8ST.d.ts} +1 -1
- package/dist/python-abi.d.cts +3 -3
- package/dist/python-abi.d.ts +3 -3
- package/dist/service-worker.js +70 -2
- package/dist/worker-entry.js +88 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -8374,6 +8374,60 @@ function createSysProvider(kernel) {
|
|
|
8374
8374
|
};
|
|
8375
8375
|
}
|
|
8376
8376
|
|
|
8377
|
+
// src/net/proxy-fetch.ts
|
|
8378
|
+
function toBase64(bytes2) {
|
|
8379
|
+
let text2 = "";
|
|
8380
|
+
for (let at = 0; at < bytes2.length; at += 32768) {
|
|
8381
|
+
text2 += String.fromCharCode(...bytes2.subarray(at, at + 32768));
|
|
8382
|
+
}
|
|
8383
|
+
return btoa(text2);
|
|
8384
|
+
}
|
|
8385
|
+
function fromBase64(text2) {
|
|
8386
|
+
const binary = atob(text2);
|
|
8387
|
+
const bytes2 = new Uint8Array(binary.length);
|
|
8388
|
+
for (let at = 0; at < binary.length; at += 1) bytes2[at] = binary.charCodeAt(at);
|
|
8389
|
+
return bytes2;
|
|
8390
|
+
}
|
|
8391
|
+
async function proxyExchange(proxy, request, fetchImpl = fetch) {
|
|
8392
|
+
let answer;
|
|
8393
|
+
try {
|
|
8394
|
+
answer = await fetchImpl(proxy, {
|
|
8395
|
+
method: "POST",
|
|
8396
|
+
headers: { "content-type": "application/json" },
|
|
8397
|
+
body: JSON.stringify({
|
|
8398
|
+
url: request.url,
|
|
8399
|
+
method: request.method,
|
|
8400
|
+
headers: request.headers,
|
|
8401
|
+
...request.body?.length ? { body: toBase64(request.body) } : {}
|
|
8402
|
+
})
|
|
8403
|
+
});
|
|
8404
|
+
} catch (error) {
|
|
8405
|
+
throw new Error(
|
|
8406
|
+
`the egress proxy at ${proxy} could not be reached (${error instanceof Error ? error.message : String(error)}). It performs this container's outbound requests, so nothing leaves until it answers.`
|
|
8407
|
+
);
|
|
8408
|
+
}
|
|
8409
|
+
if (!answer.ok) {
|
|
8410
|
+
throw new Error(
|
|
8411
|
+
`the egress proxy at ${proxy} refused ${request.method} ${request.url} with ${answer.status} ${answer.statusText}: ${(await answer.text()).trim().slice(0, 300)}`
|
|
8412
|
+
);
|
|
8413
|
+
}
|
|
8414
|
+
const payload = await answer.json();
|
|
8415
|
+
return {
|
|
8416
|
+
status: payload.status ?? 200,
|
|
8417
|
+
statusText: payload.statusText ?? "",
|
|
8418
|
+
headers: payload.headers ?? {},
|
|
8419
|
+
body: payload.body ? fromBase64(payload.body) : new Uint8Array(0)
|
|
8420
|
+
};
|
|
8421
|
+
}
|
|
8422
|
+
function isBrowser() {
|
|
8423
|
+
return typeof process === "undefined" || process.versions?.node == null;
|
|
8424
|
+
}
|
|
8425
|
+
function browserBlocked(hostname, message) {
|
|
8426
|
+
return new Error(
|
|
8427
|
+
`${message} \u2014 the request to ${hostname} was blocked by the browser. A page can only read a response from a host that sends CORS headers, and most APIs send none. Give the container a proxy that makes the request for it \u2014 network: { proxy }, and \`npx sandboxedjs-egress\` is one \u2014 or run the container on a server.`
|
|
8428
|
+
);
|
|
8429
|
+
}
|
|
8430
|
+
|
|
8377
8431
|
// src/net/policy.ts
|
|
8378
8432
|
function isLoopbackHostname(hostname) {
|
|
8379
8433
|
const host2 = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -8416,7 +8470,40 @@ function policedFetch(policy, fetchImpl) {
|
|
|
8416
8470
|
if (!outboundAllowed(policy, url)) {
|
|
8417
8471
|
throw Object.assign(new TypeError("fetch failed"), { cause: outboundBlockedError(url) });
|
|
8418
8472
|
}
|
|
8419
|
-
return fetchImpl
|
|
8473
|
+
if (policy.proxy) return await fetchThroughProxy(policy.proxy, fetchImpl, input, init);
|
|
8474
|
+
try {
|
|
8475
|
+
return await fetchImpl(input, init);
|
|
8476
|
+
} catch (error) {
|
|
8477
|
+
if (!isBrowser()) throw error;
|
|
8478
|
+
throw Object.assign(new TypeError("fetch failed"), {
|
|
8479
|
+
cause: browserBlocked(new URL(url).hostname, error instanceof Error ? error.message : String(error))
|
|
8480
|
+
});
|
|
8481
|
+
}
|
|
8482
|
+
});
|
|
8483
|
+
}
|
|
8484
|
+
async function fetchThroughProxy(proxy, fetchImpl, input, init) {
|
|
8485
|
+
const request = new Request(input, init);
|
|
8486
|
+
const headers = {};
|
|
8487
|
+
request.headers.forEach((value, name) => {
|
|
8488
|
+
headers[name] = value;
|
|
8489
|
+
});
|
|
8490
|
+
const bodyless = request.method === "GET" || request.method === "HEAD";
|
|
8491
|
+
const result = await proxyExchange(
|
|
8492
|
+
proxy,
|
|
8493
|
+
{
|
|
8494
|
+
url: request.url,
|
|
8495
|
+
method: request.method,
|
|
8496
|
+
headers,
|
|
8497
|
+
...bodyless ? {} : { body: new Uint8Array(await request.arrayBuffer()) }
|
|
8498
|
+
},
|
|
8499
|
+
fetchImpl
|
|
8500
|
+
);
|
|
8501
|
+
const nullBody = result.status === 204 || result.status === 205 || result.status === 304 || request.method === "HEAD";
|
|
8502
|
+
const body = nullBody ? null : result.body.slice().buffer;
|
|
8503
|
+
return new Response(body, {
|
|
8504
|
+
status: result.status,
|
|
8505
|
+
statusText: result.statusText,
|
|
8506
|
+
headers: result.headers
|
|
8420
8507
|
});
|
|
8421
8508
|
}
|
|
8422
8509
|
function policedWebSocket(Native, policy) {
|
|
@@ -17474,9 +17561,6 @@ function parseUrl(raw) {
|
|
|
17474
17561
|
return null;
|
|
17475
17562
|
}
|
|
17476
17563
|
}
|
|
17477
|
-
function isBrowser() {
|
|
17478
|
-
return typeof process === "undefined" || process.versions?.node == null;
|
|
17479
|
-
}
|
|
17480
17564
|
async function performRequest(ctx, url, init) {
|
|
17481
17565
|
const isLocal = ctx.kernel.net.isLocal(url.hostname);
|
|
17482
17566
|
if (isLocal) {
|
|
@@ -17504,7 +17588,16 @@ async function performRequest(ctx, url, init) {
|
|
|
17504
17588
|
);
|
|
17505
17589
|
}
|
|
17506
17590
|
const proxy = ctx.kernel.net.proxy;
|
|
17507
|
-
if (proxy)
|
|
17591
|
+
if (proxy) {
|
|
17592
|
+
const result = await proxyExchange(proxy, {
|
|
17593
|
+
url: url.toString(),
|
|
17594
|
+
method: init.method,
|
|
17595
|
+
headers: init.headers,
|
|
17596
|
+
...init.body?.length ? { body: init.body } : {}
|
|
17597
|
+
});
|
|
17598
|
+
ctx.kernel.net.countRx(result.body.length);
|
|
17599
|
+
return { ...result, url: url.toString() };
|
|
17600
|
+
}
|
|
17508
17601
|
let response;
|
|
17509
17602
|
try {
|
|
17510
17603
|
response = await fetch(url.toString(), {
|
|
@@ -17513,9 +17606,7 @@ async function performRequest(ctx, url, init) {
|
|
|
17513
17606
|
...init.body ? { body: init.body.slice().buffer } : {}
|
|
17514
17607
|
});
|
|
17515
17608
|
} catch (error) {
|
|
17516
|
-
throw isBrowser() ?
|
|
17517
|
-
`${error instanceof Error ? error.message : String(error)} \u2014 the request to ${url.hostname} was blocked by the browser. A page can only read a response from a host that sends CORS headers, and most APIs send none. Give the container a proxy that makes the request for it \u2014 network: { proxy }, and \`npx sandboxedjs-egress\` is one \u2014 or run the container on a server.`
|
|
17518
|
-
) : error;
|
|
17609
|
+
throw isBrowser() ? browserBlocked(url.hostname, error instanceof Error ? error.message : String(error)) : error;
|
|
17519
17610
|
}
|
|
17520
17611
|
const body = new Uint8Array(await response.arrayBuffer());
|
|
17521
17612
|
ctx.kernel.net.countRx(body.length);
|
|
@@ -17525,53 +17616,6 @@ async function performRequest(ctx, url, init) {
|
|
|
17525
17616
|
});
|
|
17526
17617
|
return { status: response.status, statusText: response.statusText, headers, body, url: response.url || url.toString() };
|
|
17527
17618
|
}
|
|
17528
|
-
async function proxyRequest(ctx, proxy, url, init) {
|
|
17529
|
-
let answer;
|
|
17530
|
-
try {
|
|
17531
|
-
answer = await fetch(proxy, {
|
|
17532
|
-
method: "POST",
|
|
17533
|
-
headers: { "content-type": "application/json" },
|
|
17534
|
-
body: JSON.stringify({
|
|
17535
|
-
url: url.toString(),
|
|
17536
|
-
method: init.method,
|
|
17537
|
-
headers: init.headers,
|
|
17538
|
-
...init.body?.length ? { body: toBase64(init.body) } : {}
|
|
17539
|
-
})
|
|
17540
|
-
});
|
|
17541
|
-
} catch (error) {
|
|
17542
|
-
throw new Error(
|
|
17543
|
-
`the egress proxy at ${proxy} could not be reached (${error instanceof Error ? error.message : String(error)}). It performs this container's outbound requests, so nothing leaves until it answers.`
|
|
17544
|
-
);
|
|
17545
|
-
}
|
|
17546
|
-
if (!answer.ok) {
|
|
17547
|
-
throw new Error(
|
|
17548
|
-
`the egress proxy at ${proxy} refused ${init.method} ${url.toString()} with ${answer.status} ${answer.statusText}: ${(await answer.text()).trim().slice(0, 300)}`
|
|
17549
|
-
);
|
|
17550
|
-
}
|
|
17551
|
-
const payload = await answer.json();
|
|
17552
|
-
const body = payload.body ? fromBase64(payload.body) : new Uint8Array(0);
|
|
17553
|
-
ctx.kernel.net.countRx(body.length);
|
|
17554
|
-
return {
|
|
17555
|
-
status: payload.status ?? 200,
|
|
17556
|
-
statusText: payload.statusText ?? "",
|
|
17557
|
-
headers: payload.headers ?? {},
|
|
17558
|
-
body,
|
|
17559
|
-
url: url.toString()
|
|
17560
|
-
};
|
|
17561
|
-
}
|
|
17562
|
-
function toBase64(bytes2) {
|
|
17563
|
-
let text2 = "";
|
|
17564
|
-
for (let at = 0; at < bytes2.length; at += 32768) {
|
|
17565
|
-
text2 += String.fromCharCode(...bytes2.subarray(at, at + 32768));
|
|
17566
|
-
}
|
|
17567
|
-
return btoa(text2);
|
|
17568
|
-
}
|
|
17569
|
-
function fromBase64(text2) {
|
|
17570
|
-
const binary = atob(text2);
|
|
17571
|
-
const bytes2 = new Uint8Array(binary.length);
|
|
17572
|
-
for (let at = 0; at < binary.length; at += 1) bytes2[at] = binary.charCodeAt(at);
|
|
17573
|
-
return bytes2;
|
|
17574
|
-
}
|
|
17575
17619
|
var curl = defineCommand({
|
|
17576
17620
|
name: "curl",
|
|
17577
17621
|
path: "/usr/bin/curl",
|
|
@@ -21013,7 +21057,7 @@ var wheels_default = {
|
|
|
21013
21057
|
|
|
21014
21058
|
// package.json
|
|
21015
21059
|
var package_default = {
|
|
21016
|
-
version: "0.2.
|
|
21060
|
+
version: "0.2.5"};
|
|
21017
21061
|
|
|
21018
21062
|
// src/python/extension-abi.ts
|
|
21019
21063
|
var EXTENSION_ABI = {
|
|
@@ -22328,8 +22372,24 @@ var VirtualTcpNetwork = class {
|
|
|
22328
22372
|
hasListener(port) {
|
|
22329
22373
|
return this.listeners.has(port);
|
|
22330
22374
|
}
|
|
22375
|
+
/** Ports the container itself listens on, which no guest asked for. */
|
|
22376
|
+
internal = /* @__PURE__ */ new Set();
|
|
22377
|
+
/**
|
|
22378
|
+
* Ports a guest is serving on.
|
|
22379
|
+
*
|
|
22380
|
+
* The container's own plumbing listens too — the HTTP egress takes a port
|
|
22381
|
+
* like any server — and those are not the guest's. Reported, they reach
|
|
22382
|
+
* every consumer of "what is running here": a port picker offers one, a
|
|
22383
|
+
* preview opens it, and the reader is looking at an internal endpoint
|
|
22384
|
+
* answering that it wanted an absolute URL, with nothing to say what it is
|
|
22385
|
+
* or why they are there.
|
|
22386
|
+
*/
|
|
22331
22387
|
ports() {
|
|
22332
|
-
return [...this.listeners.keys()].sort((a, b) => a - b);
|
|
22388
|
+
return [...this.listeners.keys()].filter((port) => !this.internal.has(port)).sort((a, b) => a - b);
|
|
22389
|
+
}
|
|
22390
|
+
/** Keep `port` out of {@link ports}: it belongs to the container, not a guest. */
|
|
22391
|
+
markInternal(port) {
|
|
22392
|
+
this.internal.add(port);
|
|
22333
22393
|
}
|
|
22334
22394
|
closeAll() {
|
|
22335
22395
|
for (const listener of [...this.listeners.values()]) this.close(listener);
|
|
@@ -23266,6 +23326,7 @@ function ensureHttpEgress(ctx) {
|
|
|
23266
23326
|
}
|
|
23267
23327
|
}
|
|
23268
23328
|
if (!listener || port === 0) return 0;
|
|
23329
|
+
sockets.markInternal?.(port);
|
|
23269
23330
|
listeners.set(sockets, port);
|
|
23270
23331
|
const accepting = listener;
|
|
23271
23332
|
void (async () => {
|
|
@@ -37808,7 +37869,8 @@ var Container = class _Container {
|
|
|
37808
37869
|
if (opts.python) configurePython(opts.python);
|
|
37809
37870
|
const network = {
|
|
37810
37871
|
allowOutbound: opts.network?.allowOutbound ?? false,
|
|
37811
|
-
allowedHosts: opts.network?.allowedHosts ?? null
|
|
37872
|
+
allowedHosts: opts.network?.allowedHosts ?? null,
|
|
37873
|
+
...opts.network?.proxy ? { proxy: opts.network.proxy } : {}
|
|
37812
37874
|
};
|
|
37813
37875
|
const podOptions = {
|
|
37814
37876
|
workdir: opts.cwd ?? "/",
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { S as Shell, a as ShellIO, b as Session, N as Node, c as Command, K as Kernel, E as ExecContext, O as OutputStream, C as Container, B as BinaryBackend, d as createContainer } from './container-
|
|
2
|
-
export { e as BinaryExecutionRegistry, f as BinaryInfo, g as BinaryRequest, h as BufferSink, i as CallbackSink, j as CommandRegistry, k as ContainerFs, l as ContainerOptions, m as ContextInit, n as Env, o as ExecOptions, p as ExecResult, q as ExecutionDecision, r as ExecutionTier, F as FileData, s as FileInput, t as FileOutput, G as GroupEntry, H as HttpResponse, I as InputStream, J as Job, u as KernelOptions, L as ListeningPort, M as MANIFEST_FORMAT, v as MANIFEST_SCHEMA_VERSION, w as MountEntry, x as NetInterface, y as NetworkOptions, z as NetworkStack, A as NullInput, D as NullOutput, P as PYTHON_VERSION, Q as PasswdEntry, R as Pipe, T as Preparation, U as PreparedBinary, V as Process, W as ProcessKind, X as ProcessOptions, Y as ProcessState, Z as ProcessTable, _ as PythonCapabilities, $ as PythonOptions, a0 as PythonProfile, a1 as PythonRuntimeManifest, a2 as ResolvedExecutable, a3 as RunOptions, a4 as RunResult, a5 as SessionInit, a6 as SessionResult, a7 as SessionRunOptions, a8 as ShellExit, a9 as ShellInit, aa as ShellOptions, ab as SpawnHandle, ac as Stdio, ad as TeeOutput, ae as UserDatabase, af as Variables, ag as WasmCommandArtifact, ah as WasmTranslator, ai as binaryDigest, aj as braceExpand, ak as captureStdio, al as configurePython, am as createContext, an as createTranslationBackend, ao as createWasmCompatibilityBackend, ap as defineCommand, aq as expandWord, ar as expandWords, as as inspectElf, at as installWasmCommands, au as isElfBinary, av as isPythonAvailable, aw as resetPidCounter, ax as shellQuote, ay as validateManifest } from './container-
|
|
3
|
-
import { V as Vfs, C as Cred, R as RuntimeVolume, a as VirtualTcpNetwork, b as RuntimeHttpResponse, O as OutboundPolicy, S as SpawnChild, c as SyncSpawn, I as IpcTransport, d as VolumeStat, e as VolumeStats, f as RuntimePod, g as RuntimePackageInstaller, h as ChildSpawnConfig, i as ChildHandle, j as RuntimeProcess, k as RuntimeSocketPeer, l as RuntimeConnection } from './contracts-
|
|
4
|
-
export { D as DirEntry, m as ROOT_CRED, n as RuntimeProcessManager, o as RuntimeProcessResult, p as Stats, q as VirtualNode, r as VirtualProvider, W as WriteOptions, s as applyChmod, t as createChildProcessModule, u as formatMode, v as makeCred, w as octalMode, x as parseUmask } from './contracts-
|
|
5
|
-
import { M as MemoryVolume, a as MemoryVolumeSnapshotEntry } from './memory-volume-
|
|
1
|
+
import { S as Shell, a as ShellIO, b as Session, N as Node, c as Command, K as Kernel, E as ExecContext, O as OutputStream, C as Container, B as BinaryBackend, d as createContainer } from './container-BQx27_d-.cjs';
|
|
2
|
+
export { e as BinaryExecutionRegistry, f as BinaryInfo, g as BinaryRequest, h as BufferSink, i as CallbackSink, j as CommandRegistry, k as ContainerFs, l as ContainerOptions, m as ContextInit, n as Env, o as ExecOptions, p as ExecResult, q as ExecutionDecision, r as ExecutionTier, F as FileData, s as FileInput, t as FileOutput, G as GroupEntry, H as HttpResponse, I as InputStream, J as Job, u as KernelOptions, L as ListeningPort, M as MANIFEST_FORMAT, v as MANIFEST_SCHEMA_VERSION, w as MountEntry, x as NetInterface, y as NetworkOptions, z as NetworkStack, A as NullInput, D as NullOutput, P as PYTHON_VERSION, Q as PasswdEntry, R as Pipe, T as Preparation, U as PreparedBinary, V as Process, W as ProcessKind, X as ProcessOptions, Y as ProcessState, Z as ProcessTable, _ as PythonCapabilities, $ as PythonOptions, a0 as PythonProfile, a1 as PythonRuntimeManifest, a2 as ResolvedExecutable, a3 as RunOptions, a4 as RunResult, a5 as SessionInit, a6 as SessionResult, a7 as SessionRunOptions, a8 as ShellExit, a9 as ShellInit, aa as ShellOptions, ab as SpawnHandle, ac as Stdio, ad as TeeOutput, ae as UserDatabase, af as Variables, ag as WasmCommandArtifact, ah as WasmTranslator, ai as binaryDigest, aj as braceExpand, ak as captureStdio, al as configurePython, am as createContext, an as createTranslationBackend, ao as createWasmCompatibilityBackend, ap as defineCommand, aq as expandWord, ar as expandWords, as as inspectElf, at as installWasmCommands, au as isElfBinary, av as isPythonAvailable, aw as resetPidCounter, ax as shellQuote, ay as validateManifest } from './container-BQx27_d-.cjs';
|
|
3
|
+
import { V as Vfs, C as Cred, R as RuntimeVolume, a as VirtualTcpNetwork, b as RuntimeHttpResponse, O as OutboundPolicy, S as SpawnChild, c as SyncSpawn, I as IpcTransport, d as VolumeStat, e as VolumeStats, f as RuntimePod, g as RuntimePackageInstaller, h as ChildSpawnConfig, i as ChildHandle, j as RuntimeProcess, k as RuntimeSocketPeer, l as RuntimeConnection } from './contracts-BHo4LdY2.cjs';
|
|
4
|
+
export { D as DirEntry, m as ROOT_CRED, n as RuntimeProcessManager, o as RuntimeProcessResult, p as Stats, q as VirtualNode, r as VirtualProvider, W as WriteOptions, s as applyChmod, t as createChildProcessModule, u as formatMode, v as makeCred, w as octalMode, x as parseUmask } from './contracts-BHo4LdY2.cjs';
|
|
5
|
+
import { M as MemoryVolume, a as MemoryVolumeSnapshotEntry } from './memory-volume-e-uJFRnG.cjs';
|
|
6
6
|
import EventEmitter from 'events/events.js';
|
|
7
7
|
import streamModule from 'stream-browserify';
|
|
8
8
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { S as Shell, a as ShellIO, b as Session, N as Node, c as Command, K as Kernel, E as ExecContext, O as OutputStream, C as Container, B as BinaryBackend, d as createContainer } from './container-
|
|
2
|
-
export { e as BinaryExecutionRegistry, f as BinaryInfo, g as BinaryRequest, h as BufferSink, i as CallbackSink, j as CommandRegistry, k as ContainerFs, l as ContainerOptions, m as ContextInit, n as Env, o as ExecOptions, p as ExecResult, q as ExecutionDecision, r as ExecutionTier, F as FileData, s as FileInput, t as FileOutput, G as GroupEntry, H as HttpResponse, I as InputStream, J as Job, u as KernelOptions, L as ListeningPort, M as MANIFEST_FORMAT, v as MANIFEST_SCHEMA_VERSION, w as MountEntry, x as NetInterface, y as NetworkOptions, z as NetworkStack, A as NullInput, D as NullOutput, P as PYTHON_VERSION, Q as PasswdEntry, R as Pipe, T as Preparation, U as PreparedBinary, V as Process, W as ProcessKind, X as ProcessOptions, Y as ProcessState, Z as ProcessTable, _ as PythonCapabilities, $ as PythonOptions, a0 as PythonProfile, a1 as PythonRuntimeManifest, a2 as ResolvedExecutable, a3 as RunOptions, a4 as RunResult, a5 as SessionInit, a6 as SessionResult, a7 as SessionRunOptions, a8 as ShellExit, a9 as ShellInit, aa as ShellOptions, ab as SpawnHandle, ac as Stdio, ad as TeeOutput, ae as UserDatabase, af as Variables, ag as WasmCommandArtifact, ah as WasmTranslator, ai as binaryDigest, aj as braceExpand, ak as captureStdio, al as configurePython, am as createContext, an as createTranslationBackend, ao as createWasmCompatibilityBackend, ap as defineCommand, aq as expandWord, ar as expandWords, as as inspectElf, at as installWasmCommands, au as isElfBinary, av as isPythonAvailable, aw as resetPidCounter, ax as shellQuote, ay as validateManifest } from './container-
|
|
3
|
-
import { V as Vfs, C as Cred, R as RuntimeVolume, a as VirtualTcpNetwork, b as RuntimeHttpResponse, O as OutboundPolicy, S as SpawnChild, c as SyncSpawn, I as IpcTransport, d as VolumeStat, e as VolumeStats, f as RuntimePod, g as RuntimePackageInstaller, h as ChildSpawnConfig, i as ChildHandle, j as RuntimeProcess, k as RuntimeSocketPeer, l as RuntimeConnection } from './contracts-
|
|
4
|
-
export { D as DirEntry, m as ROOT_CRED, n as RuntimeProcessManager, o as RuntimeProcessResult, p as Stats, q as VirtualNode, r as VirtualProvider, W as WriteOptions, s as applyChmod, t as createChildProcessModule, u as formatMode, v as makeCred, w as octalMode, x as parseUmask } from './contracts-
|
|
5
|
-
import { M as MemoryVolume, a as MemoryVolumeSnapshotEntry } from './memory-volume-
|
|
1
|
+
import { S as Shell, a as ShellIO, b as Session, N as Node, c as Command, K as Kernel, E as ExecContext, O as OutputStream, C as Container, B as BinaryBackend, d as createContainer } from './container-D-e5SMXQ.js';
|
|
2
|
+
export { e as BinaryExecutionRegistry, f as BinaryInfo, g as BinaryRequest, h as BufferSink, i as CallbackSink, j as CommandRegistry, k as ContainerFs, l as ContainerOptions, m as ContextInit, n as Env, o as ExecOptions, p as ExecResult, q as ExecutionDecision, r as ExecutionTier, F as FileData, s as FileInput, t as FileOutput, G as GroupEntry, H as HttpResponse, I as InputStream, J as Job, u as KernelOptions, L as ListeningPort, M as MANIFEST_FORMAT, v as MANIFEST_SCHEMA_VERSION, w as MountEntry, x as NetInterface, y as NetworkOptions, z as NetworkStack, A as NullInput, D as NullOutput, P as PYTHON_VERSION, Q as PasswdEntry, R as Pipe, T as Preparation, U as PreparedBinary, V as Process, W as ProcessKind, X as ProcessOptions, Y as ProcessState, Z as ProcessTable, _ as PythonCapabilities, $ as PythonOptions, a0 as PythonProfile, a1 as PythonRuntimeManifest, a2 as ResolvedExecutable, a3 as RunOptions, a4 as RunResult, a5 as SessionInit, a6 as SessionResult, a7 as SessionRunOptions, a8 as ShellExit, a9 as ShellInit, aa as ShellOptions, ab as SpawnHandle, ac as Stdio, ad as TeeOutput, ae as UserDatabase, af as Variables, ag as WasmCommandArtifact, ah as WasmTranslator, ai as binaryDigest, aj as braceExpand, ak as captureStdio, al as configurePython, am as createContext, an as createTranslationBackend, ao as createWasmCompatibilityBackend, ap as defineCommand, aq as expandWord, ar as expandWords, as as inspectElf, at as installWasmCommands, au as isElfBinary, av as isPythonAvailable, aw as resetPidCounter, ax as shellQuote, ay as validateManifest } from './container-D-e5SMXQ.js';
|
|
3
|
+
import { V as Vfs, C as Cred, R as RuntimeVolume, a as VirtualTcpNetwork, b as RuntimeHttpResponse, O as OutboundPolicy, S as SpawnChild, c as SyncSpawn, I as IpcTransport, d as VolumeStat, e as VolumeStats, f as RuntimePod, g as RuntimePackageInstaller, h as ChildSpawnConfig, i as ChildHandle, j as RuntimeProcess, k as RuntimeSocketPeer, l as RuntimeConnection } from './contracts-BHo4LdY2.js';
|
|
4
|
+
export { D as DirEntry, m as ROOT_CRED, n as RuntimeProcessManager, o as RuntimeProcessResult, p as Stats, q as VirtualNode, r as VirtualProvider, W as WriteOptions, s as applyChmod, t as createChildProcessModule, u as formatMode, v as makeCred, w as octalMode, x as parseUmask } from './contracts-BHo4LdY2.js';
|
|
5
|
+
import { M as MemoryVolume, a as MemoryVolumeSnapshotEntry } from './memory-volume-meoRW8ST.js';
|
|
6
6
|
import EventEmitter from 'events/events.js';
|
|
7
7
|
import streamModule from 'stream-browserify';
|
|
8
8
|
|
package/dist/index.js
CHANGED
|
@@ -8357,6 +8357,60 @@ function createSysProvider(kernel) {
|
|
|
8357
8357
|
};
|
|
8358
8358
|
}
|
|
8359
8359
|
|
|
8360
|
+
// src/net/proxy-fetch.ts
|
|
8361
|
+
function toBase64(bytes2) {
|
|
8362
|
+
let text2 = "";
|
|
8363
|
+
for (let at = 0; at < bytes2.length; at += 32768) {
|
|
8364
|
+
text2 += String.fromCharCode(...bytes2.subarray(at, at + 32768));
|
|
8365
|
+
}
|
|
8366
|
+
return btoa(text2);
|
|
8367
|
+
}
|
|
8368
|
+
function fromBase64(text2) {
|
|
8369
|
+
const binary = atob(text2);
|
|
8370
|
+
const bytes2 = new Uint8Array(binary.length);
|
|
8371
|
+
for (let at = 0; at < binary.length; at += 1) bytes2[at] = binary.charCodeAt(at);
|
|
8372
|
+
return bytes2;
|
|
8373
|
+
}
|
|
8374
|
+
async function proxyExchange(proxy, request, fetchImpl = fetch) {
|
|
8375
|
+
let answer;
|
|
8376
|
+
try {
|
|
8377
|
+
answer = await fetchImpl(proxy, {
|
|
8378
|
+
method: "POST",
|
|
8379
|
+
headers: { "content-type": "application/json" },
|
|
8380
|
+
body: JSON.stringify({
|
|
8381
|
+
url: request.url,
|
|
8382
|
+
method: request.method,
|
|
8383
|
+
headers: request.headers,
|
|
8384
|
+
...request.body?.length ? { body: toBase64(request.body) } : {}
|
|
8385
|
+
})
|
|
8386
|
+
});
|
|
8387
|
+
} catch (error) {
|
|
8388
|
+
throw new Error(
|
|
8389
|
+
`the egress proxy at ${proxy} could not be reached (${error instanceof Error ? error.message : String(error)}). It performs this container's outbound requests, so nothing leaves until it answers.`
|
|
8390
|
+
);
|
|
8391
|
+
}
|
|
8392
|
+
if (!answer.ok) {
|
|
8393
|
+
throw new Error(
|
|
8394
|
+
`the egress proxy at ${proxy} refused ${request.method} ${request.url} with ${answer.status} ${answer.statusText}: ${(await answer.text()).trim().slice(0, 300)}`
|
|
8395
|
+
);
|
|
8396
|
+
}
|
|
8397
|
+
const payload = await answer.json();
|
|
8398
|
+
return {
|
|
8399
|
+
status: payload.status ?? 200,
|
|
8400
|
+
statusText: payload.statusText ?? "",
|
|
8401
|
+
headers: payload.headers ?? {},
|
|
8402
|
+
body: payload.body ? fromBase64(payload.body) : new Uint8Array(0)
|
|
8403
|
+
};
|
|
8404
|
+
}
|
|
8405
|
+
function isBrowser() {
|
|
8406
|
+
return typeof process === "undefined" || process.versions?.node == null;
|
|
8407
|
+
}
|
|
8408
|
+
function browserBlocked(hostname, message) {
|
|
8409
|
+
return new Error(
|
|
8410
|
+
`${message} \u2014 the request to ${hostname} was blocked by the browser. A page can only read a response from a host that sends CORS headers, and most APIs send none. Give the container a proxy that makes the request for it \u2014 network: { proxy }, and \`npx sandboxedjs-egress\` is one \u2014 or run the container on a server.`
|
|
8411
|
+
);
|
|
8412
|
+
}
|
|
8413
|
+
|
|
8360
8414
|
// src/net/policy.ts
|
|
8361
8415
|
function isLoopbackHostname(hostname) {
|
|
8362
8416
|
const host2 = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -8399,7 +8453,40 @@ function policedFetch(policy, fetchImpl) {
|
|
|
8399
8453
|
if (!outboundAllowed(policy, url)) {
|
|
8400
8454
|
throw Object.assign(new TypeError("fetch failed"), { cause: outboundBlockedError(url) });
|
|
8401
8455
|
}
|
|
8402
|
-
return fetchImpl
|
|
8456
|
+
if (policy.proxy) return await fetchThroughProxy(policy.proxy, fetchImpl, input, init);
|
|
8457
|
+
try {
|
|
8458
|
+
return await fetchImpl(input, init);
|
|
8459
|
+
} catch (error) {
|
|
8460
|
+
if (!isBrowser()) throw error;
|
|
8461
|
+
throw Object.assign(new TypeError("fetch failed"), {
|
|
8462
|
+
cause: browserBlocked(new URL(url).hostname, error instanceof Error ? error.message : String(error))
|
|
8463
|
+
});
|
|
8464
|
+
}
|
|
8465
|
+
});
|
|
8466
|
+
}
|
|
8467
|
+
async function fetchThroughProxy(proxy, fetchImpl, input, init) {
|
|
8468
|
+
const request = new Request(input, init);
|
|
8469
|
+
const headers = {};
|
|
8470
|
+
request.headers.forEach((value, name) => {
|
|
8471
|
+
headers[name] = value;
|
|
8472
|
+
});
|
|
8473
|
+
const bodyless = request.method === "GET" || request.method === "HEAD";
|
|
8474
|
+
const result = await proxyExchange(
|
|
8475
|
+
proxy,
|
|
8476
|
+
{
|
|
8477
|
+
url: request.url,
|
|
8478
|
+
method: request.method,
|
|
8479
|
+
headers,
|
|
8480
|
+
...bodyless ? {} : { body: new Uint8Array(await request.arrayBuffer()) }
|
|
8481
|
+
},
|
|
8482
|
+
fetchImpl
|
|
8483
|
+
);
|
|
8484
|
+
const nullBody = result.status === 204 || result.status === 205 || result.status === 304 || request.method === "HEAD";
|
|
8485
|
+
const body = nullBody ? null : result.body.slice().buffer;
|
|
8486
|
+
return new Response(body, {
|
|
8487
|
+
status: result.status,
|
|
8488
|
+
statusText: result.statusText,
|
|
8489
|
+
headers: result.headers
|
|
8403
8490
|
});
|
|
8404
8491
|
}
|
|
8405
8492
|
function policedWebSocket(Native, policy) {
|
|
@@ -17457,9 +17544,6 @@ function parseUrl(raw) {
|
|
|
17457
17544
|
return null;
|
|
17458
17545
|
}
|
|
17459
17546
|
}
|
|
17460
|
-
function isBrowser() {
|
|
17461
|
-
return typeof process === "undefined" || process.versions?.node == null;
|
|
17462
|
-
}
|
|
17463
17547
|
async function performRequest(ctx, url, init) {
|
|
17464
17548
|
const isLocal = ctx.kernel.net.isLocal(url.hostname);
|
|
17465
17549
|
if (isLocal) {
|
|
@@ -17487,7 +17571,16 @@ async function performRequest(ctx, url, init) {
|
|
|
17487
17571
|
);
|
|
17488
17572
|
}
|
|
17489
17573
|
const proxy = ctx.kernel.net.proxy;
|
|
17490
|
-
if (proxy)
|
|
17574
|
+
if (proxy) {
|
|
17575
|
+
const result = await proxyExchange(proxy, {
|
|
17576
|
+
url: url.toString(),
|
|
17577
|
+
method: init.method,
|
|
17578
|
+
headers: init.headers,
|
|
17579
|
+
...init.body?.length ? { body: init.body } : {}
|
|
17580
|
+
});
|
|
17581
|
+
ctx.kernel.net.countRx(result.body.length);
|
|
17582
|
+
return { ...result, url: url.toString() };
|
|
17583
|
+
}
|
|
17491
17584
|
let response;
|
|
17492
17585
|
try {
|
|
17493
17586
|
response = await fetch(url.toString(), {
|
|
@@ -17496,9 +17589,7 @@ async function performRequest(ctx, url, init) {
|
|
|
17496
17589
|
...init.body ? { body: init.body.slice().buffer } : {}
|
|
17497
17590
|
});
|
|
17498
17591
|
} catch (error) {
|
|
17499
|
-
throw isBrowser() ?
|
|
17500
|
-
`${error instanceof Error ? error.message : String(error)} \u2014 the request to ${url.hostname} was blocked by the browser. A page can only read a response from a host that sends CORS headers, and most APIs send none. Give the container a proxy that makes the request for it \u2014 network: { proxy }, and \`npx sandboxedjs-egress\` is one \u2014 or run the container on a server.`
|
|
17501
|
-
) : error;
|
|
17592
|
+
throw isBrowser() ? browserBlocked(url.hostname, error instanceof Error ? error.message : String(error)) : error;
|
|
17502
17593
|
}
|
|
17503
17594
|
const body = new Uint8Array(await response.arrayBuffer());
|
|
17504
17595
|
ctx.kernel.net.countRx(body.length);
|
|
@@ -17508,53 +17599,6 @@ async function performRequest(ctx, url, init) {
|
|
|
17508
17599
|
});
|
|
17509
17600
|
return { status: response.status, statusText: response.statusText, headers, body, url: response.url || url.toString() };
|
|
17510
17601
|
}
|
|
17511
|
-
async function proxyRequest(ctx, proxy, url, init) {
|
|
17512
|
-
let answer;
|
|
17513
|
-
try {
|
|
17514
|
-
answer = await fetch(proxy, {
|
|
17515
|
-
method: "POST",
|
|
17516
|
-
headers: { "content-type": "application/json" },
|
|
17517
|
-
body: JSON.stringify({
|
|
17518
|
-
url: url.toString(),
|
|
17519
|
-
method: init.method,
|
|
17520
|
-
headers: init.headers,
|
|
17521
|
-
...init.body?.length ? { body: toBase64(init.body) } : {}
|
|
17522
|
-
})
|
|
17523
|
-
});
|
|
17524
|
-
} catch (error) {
|
|
17525
|
-
throw new Error(
|
|
17526
|
-
`the egress proxy at ${proxy} could not be reached (${error instanceof Error ? error.message : String(error)}). It performs this container's outbound requests, so nothing leaves until it answers.`
|
|
17527
|
-
);
|
|
17528
|
-
}
|
|
17529
|
-
if (!answer.ok) {
|
|
17530
|
-
throw new Error(
|
|
17531
|
-
`the egress proxy at ${proxy} refused ${init.method} ${url.toString()} with ${answer.status} ${answer.statusText}: ${(await answer.text()).trim().slice(0, 300)}`
|
|
17532
|
-
);
|
|
17533
|
-
}
|
|
17534
|
-
const payload = await answer.json();
|
|
17535
|
-
const body = payload.body ? fromBase64(payload.body) : new Uint8Array(0);
|
|
17536
|
-
ctx.kernel.net.countRx(body.length);
|
|
17537
|
-
return {
|
|
17538
|
-
status: payload.status ?? 200,
|
|
17539
|
-
statusText: payload.statusText ?? "",
|
|
17540
|
-
headers: payload.headers ?? {},
|
|
17541
|
-
body,
|
|
17542
|
-
url: url.toString()
|
|
17543
|
-
};
|
|
17544
|
-
}
|
|
17545
|
-
function toBase64(bytes2) {
|
|
17546
|
-
let text2 = "";
|
|
17547
|
-
for (let at = 0; at < bytes2.length; at += 32768) {
|
|
17548
|
-
text2 += String.fromCharCode(...bytes2.subarray(at, at + 32768));
|
|
17549
|
-
}
|
|
17550
|
-
return btoa(text2);
|
|
17551
|
-
}
|
|
17552
|
-
function fromBase64(text2) {
|
|
17553
|
-
const binary = atob(text2);
|
|
17554
|
-
const bytes2 = new Uint8Array(binary.length);
|
|
17555
|
-
for (let at = 0; at < binary.length; at += 1) bytes2[at] = binary.charCodeAt(at);
|
|
17556
|
-
return bytes2;
|
|
17557
|
-
}
|
|
17558
17602
|
var curl = defineCommand({
|
|
17559
17603
|
name: "curl",
|
|
17560
17604
|
path: "/usr/bin/curl",
|
|
@@ -20996,7 +21040,7 @@ var wheels_default = {
|
|
|
20996
21040
|
|
|
20997
21041
|
// package.json
|
|
20998
21042
|
var package_default = {
|
|
20999
|
-
version: "0.2.
|
|
21043
|
+
version: "0.2.5"};
|
|
21000
21044
|
|
|
21001
21045
|
// src/python/extension-abi.ts
|
|
21002
21046
|
var EXTENSION_ABI = {
|
|
@@ -22311,8 +22355,24 @@ var VirtualTcpNetwork = class {
|
|
|
22311
22355
|
hasListener(port) {
|
|
22312
22356
|
return this.listeners.has(port);
|
|
22313
22357
|
}
|
|
22358
|
+
/** Ports the container itself listens on, which no guest asked for. */
|
|
22359
|
+
internal = /* @__PURE__ */ new Set();
|
|
22360
|
+
/**
|
|
22361
|
+
* Ports a guest is serving on.
|
|
22362
|
+
*
|
|
22363
|
+
* The container's own plumbing listens too — the HTTP egress takes a port
|
|
22364
|
+
* like any server — and those are not the guest's. Reported, they reach
|
|
22365
|
+
* every consumer of "what is running here": a port picker offers one, a
|
|
22366
|
+
* preview opens it, and the reader is looking at an internal endpoint
|
|
22367
|
+
* answering that it wanted an absolute URL, with nothing to say what it is
|
|
22368
|
+
* or why they are there.
|
|
22369
|
+
*/
|
|
22314
22370
|
ports() {
|
|
22315
|
-
return [...this.listeners.keys()].sort((a, b) => a - b);
|
|
22371
|
+
return [...this.listeners.keys()].filter((port) => !this.internal.has(port)).sort((a, b) => a - b);
|
|
22372
|
+
}
|
|
22373
|
+
/** Keep `port` out of {@link ports}: it belongs to the container, not a guest. */
|
|
22374
|
+
markInternal(port) {
|
|
22375
|
+
this.internal.add(port);
|
|
22316
22376
|
}
|
|
22317
22377
|
closeAll() {
|
|
22318
22378
|
for (const listener of [...this.listeners.values()]) this.close(listener);
|
|
@@ -23249,6 +23309,7 @@ function ensureHttpEgress(ctx) {
|
|
|
23249
23309
|
}
|
|
23250
23310
|
}
|
|
23251
23311
|
if (!listener || port === 0) return 0;
|
|
23312
|
+
sockets.markInternal?.(port);
|
|
23252
23313
|
listeners.set(sockets, port);
|
|
23253
23314
|
const accepting = listener;
|
|
23254
23315
|
void (async () => {
|
|
@@ -37791,7 +37852,8 @@ var Container = class _Container {
|
|
|
37791
37852
|
if (opts.python) configurePython(opts.python);
|
|
37792
37853
|
const network = {
|
|
37793
37854
|
allowOutbound: opts.network?.allowOutbound ?? false,
|
|
37794
|
-
allowedHosts: opts.network?.allowedHosts ?? null
|
|
37855
|
+
allowedHosts: opts.network?.allowedHosts ?? null,
|
|
37856
|
+
...opts.network?.proxy ? { proxy: opts.network.proxy } : {}
|
|
37795
37857
|
};
|
|
37796
37858
|
const podOptions = {
|
|
37797
37859
|
workdir: opts.cwd ?? "/",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RuntimeVolume, d as VolumeStat, e as VolumeStats } from './contracts-
|
|
1
|
+
import { R as RuntimeVolume, d as VolumeStat, e as VolumeStats } from './contracts-BHo4LdY2.cjs';
|
|
2
2
|
|
|
3
3
|
type NodeKind = "file" | "directory" | "symlink";
|
|
4
4
|
interface MemoryVolumeSnapshotEntry {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RuntimeVolume, d as VolumeStat, e as VolumeStats } from './contracts-
|
|
1
|
+
import { R as RuntimeVolume, d as VolumeStat, e as VolumeStats } from './contracts-BHo4LdY2.js';
|
|
2
2
|
|
|
3
3
|
type NodeKind = "file" | "directory" | "symlink";
|
|
4
4
|
interface MemoryVolumeSnapshotEntry {
|
package/dist/python-abi.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { V as Vfs, C as Cred, a as VirtualTcpNetwork } from './contracts-
|
|
2
|
-
export { m as ROOT_CRED, v as makeCred } from './contracts-
|
|
3
|
-
export { M as MemoryVolume } from './memory-volume-
|
|
1
|
+
import { V as Vfs, C as Cred, a as VirtualTcpNetwork } from './contracts-BHo4LdY2.cjs';
|
|
2
|
+
export { m as ROOT_CRED, v as makeCred } from './contracts-BHo4LdY2.cjs';
|
|
3
|
+
export { M as MemoryVolume } from './memory-volume-e-uJFRnG.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Open-file descriptions: the thing a descriptor points *at*.
|
package/dist/python-abi.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { V as Vfs, C as Cred, a as VirtualTcpNetwork } from './contracts-
|
|
2
|
-
export { m as ROOT_CRED, v as makeCred } from './contracts-
|
|
3
|
-
export { M as MemoryVolume } from './memory-volume-
|
|
1
|
+
import { V as Vfs, C as Cred, a as VirtualTcpNetwork } from './contracts-BHo4LdY2.js';
|
|
2
|
+
export { m as ROOT_CRED, v as makeCred } from './contracts-BHo4LdY2.js';
|
|
3
|
+
export { M as MemoryVolume } from './memory-volume-meoRW8ST.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Open-file descriptions: the thing a descriptor points *at*.
|
package/dist/service-worker.js
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
// src/preview/fetch-client.ts
|
|
2
|
+
function installPreviewFetch(scope) {
|
|
3
|
+
const MARKER = "__sbx__/";
|
|
4
|
+
const at = scope.location.pathname.indexOf(MARKER);
|
|
5
|
+
if (at < 0) return;
|
|
6
|
+
const base = scope.location.origin + scope.location.pathname.slice(0, at + MARKER.length);
|
|
7
|
+
const LOOPBACK = /^(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[?::1\]?|[^.]+\.localhost)$/i;
|
|
8
|
+
const rewrite = (value) => {
|
|
9
|
+
let url;
|
|
10
|
+
try {
|
|
11
|
+
url = new URL(value, scope.location.href);
|
|
12
|
+
} catch {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
const secure = url.protocol === "https:" || url.protocol === "wss:";
|
|
16
|
+
if (!secure && url.protocol !== "http:" && url.protocol !== "ws:") return value;
|
|
17
|
+
if (!LOOPBACK.test(url.hostname)) return value;
|
|
18
|
+
const port = url.port || (secure ? "443" : "80");
|
|
19
|
+
return `${base}${port}${url.pathname}${url.search}${url.hash}`;
|
|
20
|
+
};
|
|
21
|
+
const originalFetch = scope.fetch;
|
|
22
|
+
if (typeof originalFetch === "function") {
|
|
23
|
+
scope.fetch = function patched(input, init) {
|
|
24
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
25
|
+
return originalFetch.call(this, rewrite(String(input)), init);
|
|
26
|
+
}
|
|
27
|
+
const moved = input && typeof input === "object" && "url" in input ? rewrite(input.url) : null;
|
|
28
|
+
if (moved === null || moved === input.url) {
|
|
29
|
+
return originalFetch.call(this, input, init);
|
|
30
|
+
}
|
|
31
|
+
return originalFetch.call(this, new Request(moved, input), init);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const open = scope.XMLHttpRequest?.prototype?.open;
|
|
35
|
+
if (typeof open === "function") {
|
|
36
|
+
scope.XMLHttpRequest.prototype.open = function patched(method, url, ...rest) {
|
|
37
|
+
return open.call(this, method, rewrite(String(url)), ...rest);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const OriginalEventSource = scope.EventSource;
|
|
41
|
+
if (typeof OriginalEventSource === "function") {
|
|
42
|
+
const Patched = function(url, init) {
|
|
43
|
+
return new OriginalEventSource(rewrite(String(url)), init);
|
|
44
|
+
};
|
|
45
|
+
Patched.prototype = OriginalEventSource.prototype;
|
|
46
|
+
for (const name of ["CONNECTING", "OPEN", "CLOSED"]) {
|
|
47
|
+
Object.defineProperty(Patched, name, { value: OriginalEventSource[name], enumerable: true });
|
|
48
|
+
}
|
|
49
|
+
scope.EventSource = Patched;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function previewFetchSource() {
|
|
53
|
+
return `(function(){try{(${installPreviewFetch.toString()})(window);}catch(error){console.warn("sandboxedjs: loopback rewriting unavailable",error);}})();`;
|
|
54
|
+
}
|
|
55
|
+
|
|
1
56
|
// src/preview/ws-client.ts
|
|
2
57
|
function installPreviewSockets(scope) {
|
|
3
58
|
const Native = scope.WebSocket;
|
|
@@ -419,6 +474,19 @@ var PreviewRouter = class {
|
|
|
419
474
|
portFor(clientId) {
|
|
420
475
|
return this.clientPorts.get(clientId);
|
|
421
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* The container port one request is for, by path first and client second.
|
|
479
|
+
*
|
|
480
|
+
* A port named in the path wins, because that is how a previewed page
|
|
481
|
+
* reaches its *other* servers: the frontend's document is bound to 3000, and
|
|
482
|
+
* its call to the backend arrives as `__sbx__/8000/…`. Reading the port from
|
|
483
|
+
* the path also means such a request needs nothing remembered about who is
|
|
484
|
+
* asking — which matters, because this worker is stopped whenever it looks
|
|
485
|
+
* idle and every binding it held is gone when it starts again.
|
|
486
|
+
*/
|
|
487
|
+
portForRequest(pathname, clientId) {
|
|
488
|
+
return this.claimedPort(pathname) ?? this.portFor(clientId);
|
|
489
|
+
}
|
|
422
490
|
/** A response has come back from the page. */
|
|
423
491
|
settle(id, response) {
|
|
424
492
|
const resolve = this.pending.get(id);
|
|
@@ -498,7 +566,7 @@ function withSockets(body, headers) {
|
|
|
498
566
|
const encoding = headers.get("content-encoding");
|
|
499
567
|
if (encoding && encoding.toLowerCase() !== "identity") return body;
|
|
500
568
|
const html = new TextDecoder().decode(body);
|
|
501
|
-
const tag = `<script>${previewSocketSource()}<\/script>`;
|
|
569
|
+
const tag = `<script>${previewSocketSource()}${previewFetchSource()}<\/script>`;
|
|
502
570
|
const head = /<head[^>]*>/i.exec(html);
|
|
503
571
|
const at = head ? head.index + head[0].length : 0;
|
|
504
572
|
const bytes = new TextEncoder().encode(html.slice(0, at) + tag + html.slice(at));
|
|
@@ -591,7 +659,7 @@ worker.addEventListener("fetch", (event) => {
|
|
|
591
659
|
event.respondWith(router.fetch(claimed, "/", event.request, event.resultingClientId || event.clientId));
|
|
592
660
|
return;
|
|
593
661
|
}
|
|
594
|
-
const port = router.
|
|
662
|
+
const port = router.portForRequest(url.pathname, event.clientId);
|
|
595
663
|
if (port === void 0) return;
|
|
596
664
|
event.respondWith(
|
|
597
665
|
router.fetch(port, router.containerPath(url.pathname, url.search), event.request, event.clientId)
|