@ai-ecoverse/slicc-kernel 1.22.1 → 1.24.0
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 +31 -4
- package/dist/driver-worker.js +87 -1
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +126 -15
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +2243 -1581
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +87 -1
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node.js +1308 -511
- package/dist/node.js.map +4 -4
- package/package.json +1 -1
package/dist/node.js
CHANGED
|
@@ -1,6 +1,92 @@
|
|
|
1
1
|
// src/node.ts
|
|
2
2
|
import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
|
|
3
3
|
|
|
4
|
+
// src/cdp/port.ts
|
|
5
|
+
function serveCdpPort(port, hook, request, onDone) {
|
|
6
|
+
let conn;
|
|
7
|
+
let done4 = false;
|
|
8
|
+
const end = () => {
|
|
9
|
+
done4 = true;
|
|
10
|
+
port.close();
|
|
11
|
+
onDone?.();
|
|
12
|
+
};
|
|
13
|
+
const finish = (reason) => {
|
|
14
|
+
if (done4) return;
|
|
15
|
+
port.postMessage({ close: reason });
|
|
16
|
+
end();
|
|
17
|
+
};
|
|
18
|
+
port.addEventListener("message", ({ data }) => {
|
|
19
|
+
if (data.m !== void 0) conn?.send(data.m);
|
|
20
|
+
else if (data.close && !done4) {
|
|
21
|
+
end();
|
|
22
|
+
conn?.close();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
port.start?.();
|
|
26
|
+
new Promise((resolve) => resolve(hook(request))).then(
|
|
27
|
+
(opened2) => {
|
|
28
|
+
conn = opened2;
|
|
29
|
+
if (done4) return opened2.close();
|
|
30
|
+
opened2.onmessage = (m) => {
|
|
31
|
+
if (!done4) port.postMessage({ m });
|
|
32
|
+
};
|
|
33
|
+
opened2.onclose = (reason) => finish(reason ?? null);
|
|
34
|
+
port.postMessage({ open: true });
|
|
35
|
+
},
|
|
36
|
+
(e) => {
|
|
37
|
+
if (done4) return;
|
|
38
|
+
port.postMessage({ error: e instanceof Error ? e.message : String(e) });
|
|
39
|
+
end();
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
return () => {
|
|
43
|
+
if (done4) return;
|
|
44
|
+
conn?.close();
|
|
45
|
+
finish("the CDP host is gone");
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function portCdpHook(ask, gone) {
|
|
49
|
+
return (request) => new Promise((resolve, reject) => {
|
|
50
|
+
const { port1, port2 } = new MessageChannel();
|
|
51
|
+
let closed = false;
|
|
52
|
+
const conn = {
|
|
53
|
+
send: (m) => {
|
|
54
|
+
if (!closed) port1.postMessage({ m });
|
|
55
|
+
},
|
|
56
|
+
onmessage: null,
|
|
57
|
+
onclose: null,
|
|
58
|
+
close: () => {
|
|
59
|
+
if (closed) return;
|
|
60
|
+
closed = true;
|
|
61
|
+
port1.postMessage({ close: true });
|
|
62
|
+
port1.close();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
port1.addEventListener("message", ({ data }) => {
|
|
66
|
+
if ("m" in data) conn.onmessage?.(data.m);
|
|
67
|
+
else if ("open" in data) resolve(conn);
|
|
68
|
+
else if ("error" in data) {
|
|
69
|
+
closed = true;
|
|
70
|
+
port1.close();
|
|
71
|
+
reject(new Error(data.error));
|
|
72
|
+
} else if (!closed) {
|
|
73
|
+
closed = true;
|
|
74
|
+
port1.close();
|
|
75
|
+
conn.onclose?.(data.close ?? void 0);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
port1.start();
|
|
79
|
+
void gone?.then(() => {
|
|
80
|
+
if (closed) return;
|
|
81
|
+
closed = true;
|
|
82
|
+
port1.close();
|
|
83
|
+
reject(new Error("the CDP host is gone"));
|
|
84
|
+
conn.onclose?.("the CDP host is gone");
|
|
85
|
+
});
|
|
86
|
+
ask(request, port2);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
4
90
|
// src/fs/types.ts
|
|
5
91
|
function rangedOps(fs) {
|
|
6
92
|
const { pread, pwrite, truncate } = fs;
|
|
@@ -637,7 +723,12 @@ function keyOf(addr) {
|
|
|
637
723
|
var LoopbackNet = class {
|
|
638
724
|
bound = /* @__PURE__ */ new Map();
|
|
639
725
|
activators = /* @__PURE__ */ new Map();
|
|
726
|
+
internal = /* @__PURE__ */ new Set();
|
|
640
727
|
nextEphemeral = EPHEMERAL_FIRST;
|
|
728
|
+
kernelOnly(port, reserve = false) {
|
|
729
|
+
if (reserve) this.internal.add(port);
|
|
730
|
+
return this.internal.has(port);
|
|
731
|
+
}
|
|
641
732
|
activate(addr, start) {
|
|
642
733
|
this.activators.set(keyOf(addr), start);
|
|
643
734
|
}
|
|
@@ -698,6 +789,14 @@ var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "
|
|
|
698
789
|
function dialSocket(net, port, host = "127.0.0.1") {
|
|
699
790
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
|
|
700
791
|
const target = LOOPBACK_NAMES.has(host.toLowerCase()) ? "127.0.0.1" : host;
|
|
792
|
+
if (net.kernelOnly(port)) {
|
|
793
|
+
throw Object.assign(
|
|
794
|
+
new Error(`ECONNREFUSED: port ${port} only takes connections from inside the kernel`),
|
|
795
|
+
{
|
|
796
|
+
code: "ECONNREFUSED"
|
|
797
|
+
}
|
|
798
|
+
);
|
|
799
|
+
}
|
|
701
800
|
return net.connect({ family: "inet", host: target, port });
|
|
702
801
|
}
|
|
703
802
|
var codeOf = (err) => {
|
|
@@ -985,7 +1084,7 @@ function serveTransport(peer, transport) {
|
|
|
985
1084
|
}
|
|
986
1085
|
|
|
987
1086
|
// src/client/protocol.ts
|
|
988
|
-
var PROTOCOL = [1,
|
|
1087
|
+
var PROTOCOL = [1, 5];
|
|
989
1088
|
var FS_METHODS = [
|
|
990
1089
|
"readFile",
|
|
991
1090
|
"writeFile",
|
|
@@ -1099,6 +1198,43 @@ function joinable(l, pgid, ours) {
|
|
|
1099
1198
|
if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
|
|
1100
1199
|
if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
|
|
1101
1200
|
}
|
|
1201
|
+
function cdpRegistry(send, gone) {
|
|
1202
|
+
const registered = /* @__PURE__ */ new Map();
|
|
1203
|
+
const drop = (id) => {
|
|
1204
|
+
registered.get(id)?.();
|
|
1205
|
+
return registered.delete(id);
|
|
1206
|
+
};
|
|
1207
|
+
return {
|
|
1208
|
+
serve(l, req) {
|
|
1209
|
+
const hook = portCdpHook(
|
|
1210
|
+
(request, cdp) => send({ cdpOpen: { ...request, registration: req.id }, port: cdp }, [
|
|
1211
|
+
cdp
|
|
1212
|
+
]),
|
|
1213
|
+
gone
|
|
1214
|
+
);
|
|
1215
|
+
registered.set(req.id, l.cdp.register(hook, req.runtime));
|
|
1216
|
+
return req.id;
|
|
1217
|
+
},
|
|
1218
|
+
drop,
|
|
1219
|
+
close: () => {
|
|
1220
|
+
for (const id of [...registered.keys()]) drop(id);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function watchOp(l, req, watches, send) {
|
|
1225
|
+
if (req.op === "unwatch") {
|
|
1226
|
+
watches.get(req.watch)?.();
|
|
1227
|
+
watches.delete(req.watch);
|
|
1228
|
+
return { result: true };
|
|
1229
|
+
}
|
|
1230
|
+
const unwatch = l.watchers.watch(
|
|
1231
|
+
req.paths,
|
|
1232
|
+
{ recursive: req.recursive },
|
|
1233
|
+
(change) => send({ watch: req.id, change })
|
|
1234
|
+
);
|
|
1235
|
+
watches.set(req.id, unwatch);
|
|
1236
|
+
return { result: req.id };
|
|
1237
|
+
}
|
|
1102
1238
|
function serveClient(port, host) {
|
|
1103
1239
|
let state = "new";
|
|
1104
1240
|
const terminals = /* @__PURE__ */ new Map();
|
|
@@ -1111,6 +1247,7 @@ function serveClient(port, host) {
|
|
|
1111
1247
|
if (state !== "closed") port.postMessage(message2, transfer);
|
|
1112
1248
|
};
|
|
1113
1249
|
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
1250
|
+
const cdps = cdpRegistry(send, ended.promise);
|
|
1114
1251
|
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
1115
1252
|
if (state === "closed") return;
|
|
1116
1253
|
send({ bye });
|
|
@@ -1118,6 +1255,7 @@ function serveClient(port, host) {
|
|
|
1118
1255
|
net?.close();
|
|
1119
1256
|
for (const unwatch of watches.values()) unwatch();
|
|
1120
1257
|
watches.clear();
|
|
1258
|
+
cdps.close();
|
|
1121
1259
|
for (const session of terminals.values()) session.close();
|
|
1122
1260
|
terminals.clear();
|
|
1123
1261
|
if (kill) killGroups(host, [...groups]);
|
|
@@ -1212,19 +1350,9 @@ function serveClient(port, host) {
|
|
|
1212
1350
|
throw new Error(`unknown file system call ${req.method}`);
|
|
1213
1351
|
}
|
|
1214
1352
|
return fsCall(l.fs, req.method, req.args);
|
|
1215
|
-
case "watch":
|
|
1216
|
-
const unwatch = l.watchers.watch(
|
|
1217
|
-
req.paths,
|
|
1218
|
-
{ recursive: req.recursive },
|
|
1219
|
-
(change) => send({ watch: req.id, change })
|
|
1220
|
-
);
|
|
1221
|
-
watches.set(req.id, unwatch);
|
|
1222
|
-
return { result: req.id };
|
|
1223
|
-
}
|
|
1353
|
+
case "watch":
|
|
1224
1354
|
case "unwatch":
|
|
1225
|
-
|
|
1226
|
-
watches.delete(req.watch);
|
|
1227
|
-
return { result: true };
|
|
1355
|
+
return watchOp(l, req, watches, send);
|
|
1228
1356
|
case "mount":
|
|
1229
1357
|
return { result: await l.mount(req.spec) };
|
|
1230
1358
|
case "umount":
|
|
@@ -1234,6 +1362,10 @@ function serveClient(port, host) {
|
|
|
1234
1362
|
return { result: l.mounts.list() };
|
|
1235
1363
|
case "dial":
|
|
1236
1364
|
return dialFor(l, req, watches);
|
|
1365
|
+
case "serve-cdp":
|
|
1366
|
+
return { result: cdps.serve(l, req) };
|
|
1367
|
+
case "unserve-cdp":
|
|
1368
|
+
return { result: cdps.drop(req.registration) };
|
|
1237
1369
|
case "detach":
|
|
1238
1370
|
return { result: true };
|
|
1239
1371
|
default:
|
|
@@ -1367,6 +1499,56 @@ function prefixes(path) {
|
|
|
1367
1499
|
function within(path, root) {
|
|
1368
1500
|
return path === root || path.startsWith(`${root}/`);
|
|
1369
1501
|
}
|
|
1502
|
+
var LOCKED = /* @__PURE__ */ new Set(["NotReadableError", "NoModificationAllowedError", "InvalidStateError"]);
|
|
1503
|
+
var SYNC_BACKOFF_MS = [5, 10, 20, 40, 80];
|
|
1504
|
+
async function openSync(handle) {
|
|
1505
|
+
const sync = handle.createSyncAccessHandle;
|
|
1506
|
+
if (!sync) return void 0;
|
|
1507
|
+
for (let attempt = 0; ; attempt++) {
|
|
1508
|
+
try {
|
|
1509
|
+
return await sync.call(handle, { mode: "readwrite-unsafe" });
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
const delay = SYNC_BACKOFF_MS[attempt];
|
|
1512
|
+
if (!LOCKED.has(err?.name)) throw err;
|
|
1513
|
+
if (delay === void 0) return void 0;
|
|
1514
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
function writeAll(access, at, bytes) {
|
|
1519
|
+
for (let done4 = 0; done4 < bytes.length; ) {
|
|
1520
|
+
const n = access.write(bytes.subarray(done4), { at: at + done4 });
|
|
1521
|
+
if (!(n > 0))
|
|
1522
|
+
throw Object.assign(new Error("the file system took no more bytes"), { code: "ENOSPC" });
|
|
1523
|
+
done4 += n;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async function positioned(handle, op) {
|
|
1527
|
+
const access = await openSync(handle);
|
|
1528
|
+
if (access) {
|
|
1529
|
+
try {
|
|
1530
|
+
op({
|
|
1531
|
+
write: (at, bytes) => writeAll(access, at, bytes),
|
|
1532
|
+
truncate: (n) => access.truncate(n)
|
|
1533
|
+
});
|
|
1534
|
+
access.flush();
|
|
1535
|
+
} finally {
|
|
1536
|
+
access.close();
|
|
1537
|
+
}
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const writable = await handle.createWritable({ keepExistingData: true });
|
|
1541
|
+
try {
|
|
1542
|
+
await op({
|
|
1543
|
+
write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
|
|
1544
|
+
truncate: (n) => writable.truncate(n)
|
|
1545
|
+
});
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
await writable.abort();
|
|
1548
|
+
throw err;
|
|
1549
|
+
}
|
|
1550
|
+
await writable.close();
|
|
1551
|
+
}
|
|
1370
1552
|
function isHandle(child) {
|
|
1371
1553
|
return "kind" in child;
|
|
1372
1554
|
}
|
|
@@ -1376,9 +1558,11 @@ var OpfsFs = class {
|
|
|
1376
1558
|
dirs = /* @__PURE__ */ new Map();
|
|
1377
1559
|
checked = /* @__PURE__ */ new WeakMap();
|
|
1378
1560
|
channel;
|
|
1379
|
-
|
|
1561
|
+
ranged;
|
|
1562
|
+
constructor(root, meta = new MemoryMeta(), channel, options = {}) {
|
|
1380
1563
|
this.root = root;
|
|
1381
1564
|
this.meta = meta;
|
|
1565
|
+
this.ranged = options.ranged === true;
|
|
1382
1566
|
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
1383
1567
|
if (this.channel) {
|
|
1384
1568
|
this.channel.onmessage = ({ data }) => {
|
|
@@ -1515,7 +1699,8 @@ var OpfsFs = class {
|
|
|
1515
1699
|
mtime: new Date(mtime),
|
|
1516
1700
|
atime: new Date(meta?.atimeMs ?? mtime),
|
|
1517
1701
|
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
1518
|
-
ino: meta?.ino ?? inodeOf(path)
|
|
1702
|
+
ino: meta?.ino ?? inodeOf(path),
|
|
1703
|
+
...this.ranged && !directory ? { ranged: true } : {}
|
|
1519
1704
|
};
|
|
1520
1705
|
}
|
|
1521
1706
|
linkStat(entry) {
|
|
@@ -1549,20 +1734,47 @@ var OpfsFs = class {
|
|
|
1549
1734
|
() => false
|
|
1550
1735
|
);
|
|
1551
1736
|
}
|
|
1552
|
-
async
|
|
1553
|
-
const handle = (await this.locate(path, true)).handle;
|
|
1554
|
-
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
1737
|
+
async retrying(path, op, locks) {
|
|
1555
1738
|
for (let attempt = 1; ; attempt++) {
|
|
1556
1739
|
try {
|
|
1557
|
-
return
|
|
1740
|
+
return await op();
|
|
1558
1741
|
} catch (err) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1742
|
+
const name2 = err?.name;
|
|
1743
|
+
const again = name2 === "NotReadableError" || locks && LOCKED.has(name2);
|
|
1744
|
+
if (!again || attempt >= READ_ATTEMPTS) throw translate(err, path);
|
|
1562
1745
|
await new Promise((resolve) => setTimeout(resolve, attempt * 10));
|
|
1563
1746
|
}
|
|
1564
1747
|
}
|
|
1565
1748
|
}
|
|
1749
|
+
async existing(path) {
|
|
1750
|
+
const handle = (await this.locate(path, true)).handle;
|
|
1751
|
+
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
1752
|
+
return handle;
|
|
1753
|
+
}
|
|
1754
|
+
async readFileBuffer(path) {
|
|
1755
|
+
const handle = await this.existing(path);
|
|
1756
|
+
return this.retrying(
|
|
1757
|
+
path,
|
|
1758
|
+
async () => new Uint8Array(await (await handle.getFile()).arrayBuffer()),
|
|
1759
|
+
false
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
async pread(path, offset, length2) {
|
|
1763
|
+
const handle = await this.existing(path);
|
|
1764
|
+
return this.retrying(
|
|
1765
|
+
path,
|
|
1766
|
+
async () => new Uint8Array(await (await handle.getFile()).slice(offset, offset + length2).arrayBuffer()),
|
|
1767
|
+
false
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
async pwrite(path, offset, bytes) {
|
|
1771
|
+
const handle = (await this.fileFor(path))[0];
|
|
1772
|
+
await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
|
|
1773
|
+
}
|
|
1774
|
+
async truncate(path, size) {
|
|
1775
|
+
const handle = await this.existing(path);
|
|
1776
|
+
await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
|
|
1777
|
+
}
|
|
1566
1778
|
async readFile(path) {
|
|
1567
1779
|
return new TextDecoder().decode(await this.readFileBuffer(path));
|
|
1568
1780
|
}
|
|
@@ -1576,16 +1788,21 @@ var OpfsFs = class {
|
|
|
1576
1788
|
const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
|
|
1577
1789
|
return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
|
|
1578
1790
|
}
|
|
1579
|
-
async
|
|
1791
|
+
async fileFor(path) {
|
|
1580
1792
|
const [target, existed] = await this.writeTarget(path);
|
|
1581
1793
|
const [dir, name2] = await this.parent(target);
|
|
1582
|
-
let file;
|
|
1583
1794
|
try {
|
|
1584
|
-
|
|
1795
|
+
return [
|
|
1796
|
+
await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
|
|
1797
|
+
existed
|
|
1798
|
+
];
|
|
1585
1799
|
} catch (err) {
|
|
1586
1800
|
const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
|
|
1587
1801
|
throw code ? fsError(code, path) : translate(err, path);
|
|
1588
1802
|
}
|
|
1803
|
+
}
|
|
1804
|
+
async writeFile(path, content) {
|
|
1805
|
+
const [file, existed] = await this.fileFor(path);
|
|
1589
1806
|
if (!existed && content.length === 0) return;
|
|
1590
1807
|
const writable = await file.createWritable();
|
|
1591
1808
|
await writable.write(content);
|
|
@@ -1817,121 +2034,820 @@ var OpfsFs = class {
|
|
|
1817
2034
|
}
|
|
1818
2035
|
};
|
|
1819
2036
|
|
|
1820
|
-
// src/
|
|
1821
|
-
var
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
const clean = rel2.replace(/^\.\//, "");
|
|
1827
|
-
if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
|
|
1828
|
-
return `${pkg}/${clean}`;
|
|
1829
|
-
}
|
|
1830
|
-
function envOf(pkg, raw) {
|
|
1831
|
-
const env = {};
|
|
1832
|
-
if (!raw || typeof raw !== "object") return env;
|
|
1833
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
1834
|
-
if (!ENV_KEY.test(key)) continue;
|
|
1835
|
-
if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
1836
|
-
else if (value === null) env[key] = null;
|
|
2037
|
+
// src/kernel/net/http1.ts
|
|
2038
|
+
var HttpError = class extends Error {
|
|
2039
|
+
status;
|
|
2040
|
+
constructor(status, message2) {
|
|
2041
|
+
super(message2);
|
|
2042
|
+
this.status = status;
|
|
1837
2043
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2044
|
+
};
|
|
2045
|
+
var CR = 13;
|
|
2046
|
+
var LF = 10;
|
|
2047
|
+
var READ_SIZE = 64 * 1024;
|
|
2048
|
+
var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
2049
|
+
var REASON = {
|
|
2050
|
+
100: "Continue",
|
|
2051
|
+
200: "OK",
|
|
2052
|
+
400: "Bad Request",
|
|
2053
|
+
403: "Forbidden",
|
|
2054
|
+
404: "Not Found",
|
|
2055
|
+
405: "Method Not Allowed",
|
|
2056
|
+
408: "Request Timeout",
|
|
2057
|
+
411: "Length Required",
|
|
2058
|
+
413: "Content Too Large",
|
|
2059
|
+
417: "Expectation Failed",
|
|
2060
|
+
421: "Misdirected Request",
|
|
2061
|
+
431: "Request Header Fields Too Large",
|
|
2062
|
+
501: "Not Implemented",
|
|
2063
|
+
502: "Bad Gateway",
|
|
2064
|
+
503: "Service Unavailable",
|
|
2065
|
+
504: "Gateway Timeout",
|
|
2066
|
+
505: "HTTP Version Not Supported"
|
|
2067
|
+
};
|
|
2068
|
+
var Incoming = class {
|
|
2069
|
+
source;
|
|
2070
|
+
buf = new Uint8Array(0);
|
|
2071
|
+
eof = false;
|
|
2072
|
+
constructor(source) {
|
|
2073
|
+
this.source = source;
|
|
1846
2074
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
...unset.length > 0 ? { unset } : {}
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
function argsOf(pkg, raw) {
|
|
1853
|
-
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
1854
|
-
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
1855
|
-
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
1856
|
-
}
|
|
1857
|
-
function abiOf(raw, fallback) {
|
|
1858
|
-
const abi = raw ?? fallback;
|
|
1859
|
-
return abi === "emscripten" || abi === "wasi" ? abi : void 0;
|
|
1860
|
-
}
|
|
1861
|
-
function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
1862
|
-
const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
|
|
1863
|
-
const script = inside(pkg, raw.script);
|
|
1864
|
-
if (script) {
|
|
1865
|
-
return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
|
|
2075
|
+
get buffered() {
|
|
2076
|
+
return this.buf.length;
|
|
1866
2077
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
1874
|
-
return {
|
|
1875
|
-
name: name2,
|
|
1876
|
-
abi,
|
|
1877
|
-
glue,
|
|
1878
|
-
wasm,
|
|
1879
|
-
argv0,
|
|
1880
|
-
...args ? { args } : {},
|
|
1881
|
-
...withEnv,
|
|
1882
|
-
...imports ? { imports } : {}
|
|
1883
|
-
};
|
|
1884
|
-
}
|
|
1885
|
-
function commandsOf(pkg, manifest) {
|
|
1886
|
-
const slicc = manifest.slicc;
|
|
1887
|
-
if (!slicc || typeof slicc !== "object") return [];
|
|
1888
|
-
const packageAbi = abiOf(slicc.abi, "emscripten");
|
|
1889
|
-
const entries = slicc.commands;
|
|
1890
|
-
if (!packageAbi || !entries || typeof entries !== "object") return [];
|
|
1891
|
-
const shared = envOf(pkg, slicc.env);
|
|
1892
|
-
const out = [];
|
|
1893
|
-
for (const [name2, raw] of Object.entries(entries)) {
|
|
1894
|
-
if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
|
|
1895
|
-
continue;
|
|
2078
|
+
async fill(signal) {
|
|
2079
|
+
if (this.eof) return false;
|
|
2080
|
+
const chunk2 = await this.source.read(READ_SIZE, signal);
|
|
2081
|
+
if (chunk2.length === 0) {
|
|
2082
|
+
this.eof = true;
|
|
2083
|
+
return false;
|
|
1896
2084
|
}
|
|
1897
|
-
const
|
|
1898
|
-
|
|
2085
|
+
const next = new Uint8Array(this.buf.length + chunk2.length);
|
|
2086
|
+
next.set(this.buf);
|
|
2087
|
+
next.set(chunk2, this.buf.length);
|
|
2088
|
+
this.buf = next;
|
|
2089
|
+
return true;
|
|
1899
2090
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
const env = {};
|
|
1905
|
-
for (const [key, value] of Object.entries(command.env)) {
|
|
1906
|
-
const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
|
|
1907
|
-
env[key] = path && await fs.exists(path) ? path : value;
|
|
2091
|
+
take(n) {
|
|
2092
|
+
const out = this.buf.slice(0, n);
|
|
2093
|
+
this.buf = this.buf.subarray(n);
|
|
2094
|
+
return out;
|
|
1908
2095
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
if (name2.startsWith(".")) continue;
|
|
1916
|
-
if (!name2.startsWith("@")) {
|
|
1917
|
-
dirs.push(`${modules}/${name2}`);
|
|
1918
|
-
continue;
|
|
1919
|
-
}
|
|
1920
|
-
for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
|
|
1921
|
-
dirs.push(`${modules}/${name2}/${inner}`);
|
|
1922
|
-
}
|
|
2096
|
+
async some(max, signal) {
|
|
2097
|
+
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
2098
|
+
if (this.eof) return new Uint8Array(0);
|
|
2099
|
+
const chunk2 = await this.source.read(max, signal);
|
|
2100
|
+
if (chunk2.length === 0) this.eof = true;
|
|
2101
|
+
return chunk2;
|
|
1923
2102
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2103
|
+
async head(limit, signal) {
|
|
2104
|
+
let from = 0;
|
|
2105
|
+
for (; ; ) {
|
|
2106
|
+
const end = headEnd(this.buf, from);
|
|
2107
|
+
if (end > 0) {
|
|
2108
|
+
if (end > limit) throw new HttpError(431, "request head too large");
|
|
2109
|
+
return this.take(end);
|
|
2110
|
+
}
|
|
2111
|
+
if (this.buf.length > limit) throw new HttpError(431, "request head too large");
|
|
2112
|
+
from = Math.max(0, this.buf.length - 3);
|
|
2113
|
+
if (!await this.fill(signal)) {
|
|
2114
|
+
if (this.buf.length === 0) return null;
|
|
2115
|
+
throw new HttpError(400, "connection closed inside a request head");
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
async line(limit, signal) {
|
|
2120
|
+
let from = 0;
|
|
2121
|
+
for (; ; ) {
|
|
2122
|
+
const at = this.buf.indexOf(LF, from);
|
|
2123
|
+
if (at >= 0) {
|
|
2124
|
+
const line = this.take(at + 1);
|
|
2125
|
+
const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
|
|
2126
|
+
return text;
|
|
2127
|
+
}
|
|
2128
|
+
if (this.buf.length > limit) throw new HttpError(400, "line too long");
|
|
2129
|
+
from = this.buf.length;
|
|
2130
|
+
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
async exactly(n, signal) {
|
|
2134
|
+
while (this.buf.length < n) {
|
|
2135
|
+
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
2136
|
+
}
|
|
2137
|
+
return this.take(n);
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
function headEnd(buf, from) {
|
|
2141
|
+
for (let i = from; i < buf.length; i++) {
|
|
2142
|
+
if (buf[i] !== LF) continue;
|
|
2143
|
+
if (buf[i + 1] === LF) return i + 2;
|
|
2144
|
+
if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
|
|
2145
|
+
}
|
|
2146
|
+
return 0;
|
|
2147
|
+
}
|
|
2148
|
+
function latin1(bytes) {
|
|
2149
|
+
let out = "";
|
|
2150
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
2151
|
+
out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
2152
|
+
}
|
|
2153
|
+
return out;
|
|
2154
|
+
}
|
|
2155
|
+
function latin1Bytes(text) {
|
|
2156
|
+
const out = new Uint8Array(text.length);
|
|
2157
|
+
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
2158
|
+
return out;
|
|
2159
|
+
}
|
|
2160
|
+
function parseRequestHead(bytes) {
|
|
2161
|
+
const lines = latin1(bytes).split(/\r?\n/);
|
|
2162
|
+
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
2163
|
+
const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
|
|
2164
|
+
if (!match) throw new HttpError(400, "malformed request line");
|
|
2165
|
+
if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
|
|
2166
|
+
return {
|
|
2167
|
+
method: match[1],
|
|
2168
|
+
target: match[2],
|
|
2169
|
+
minor: Number(match[4]),
|
|
2170
|
+
headers: parseFields(lines)
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
function parseFields(lines) {
|
|
2174
|
+
const headers = [];
|
|
2175
|
+
for (const line of lines) {
|
|
2176
|
+
if (line === "") continue;
|
|
2177
|
+
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
2178
|
+
throw new HttpError(400, "obsolete line folding");
|
|
2179
|
+
}
|
|
2180
|
+
const colon = line.indexOf(":");
|
|
2181
|
+
const name2 = colon > 0 ? line.slice(0, colon) : "";
|
|
2182
|
+
if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
|
|
2183
|
+
headers.push([name2, line.slice(colon + 1).trim()]);
|
|
2184
|
+
}
|
|
2185
|
+
return headers;
|
|
2186
|
+
}
|
|
2187
|
+
function fieldValues(headers, name2) {
|
|
2188
|
+
const lower = name2.toLowerCase();
|
|
2189
|
+
return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
|
|
2190
|
+
}
|
|
2191
|
+
function fieldTokens(headers, name2) {
|
|
2192
|
+
return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
|
|
2193
|
+
}
|
|
2194
|
+
function requestFraming(headers) {
|
|
2195
|
+
const codings = fieldTokens(headers, "transfer-encoding");
|
|
2196
|
+
const lengths = fieldValues(headers, "content-length");
|
|
2197
|
+
if (codings.length > 0) {
|
|
2198
|
+
if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
|
|
2199
|
+
if (codings.length !== 1 || codings[0] !== "chunked") {
|
|
2200
|
+
throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
|
|
2201
|
+
}
|
|
2202
|
+
return { kind: "chunked" };
|
|
2203
|
+
}
|
|
2204
|
+
if (lengths.length === 0) return { kind: "none" };
|
|
2205
|
+
const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
|
|
2206
|
+
const [only] = distinct;
|
|
2207
|
+
if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
|
|
2208
|
+
throw new HttpError(400, "invalid Content-Length");
|
|
2209
|
+
}
|
|
2210
|
+
const length2 = Number(only);
|
|
2211
|
+
return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
|
|
2212
|
+
}
|
|
2213
|
+
var MAX_CHUNK_LINE = 4096;
|
|
2214
|
+
var MAX_TRAILERS = 64 * 1024;
|
|
2215
|
+
async function readBody(incoming, framing, cap, signal) {
|
|
2216
|
+
if (framing.kind === "none") return void 0;
|
|
2217
|
+
if (framing.kind === "length") {
|
|
2218
|
+
if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
2219
|
+
return incoming.exactly(framing.length, signal);
|
|
2220
|
+
}
|
|
2221
|
+
const chunks2 = [];
|
|
2222
|
+
let total = 0;
|
|
2223
|
+
for (; ; ) {
|
|
2224
|
+
const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
|
|
2225
|
+
const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
|
|
2226
|
+
if (!size) throw new HttpError(400, "malformed chunk size");
|
|
2227
|
+
const n = Number.parseInt(size[1], 16);
|
|
2228
|
+
if (n === 0) break;
|
|
2229
|
+
total += n;
|
|
2230
|
+
if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
2231
|
+
chunks2.push(await incoming.exactly(n, signal));
|
|
2232
|
+
if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
|
|
2233
|
+
}
|
|
2234
|
+
let trailers = 0;
|
|
2235
|
+
for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
|
|
2236
|
+
trailers += line.length;
|
|
2237
|
+
if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
|
|
2238
|
+
line = await incoming.line(MAX_TRAILERS, signal);
|
|
2239
|
+
}
|
|
2240
|
+
return concat(chunks2, total);
|
|
2241
|
+
}
|
|
2242
|
+
function concat(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
|
|
2243
|
+
const out = new Uint8Array(total);
|
|
2244
|
+
let at = 0;
|
|
2245
|
+
for (const chunk2 of chunks2) {
|
|
2246
|
+
out.set(chunk2, at);
|
|
2247
|
+
at += chunk2.length;
|
|
2248
|
+
}
|
|
2249
|
+
return out;
|
|
2250
|
+
}
|
|
2251
|
+
function safeValue(value) {
|
|
2252
|
+
return !/[\r\n\0]/.test(value);
|
|
2253
|
+
}
|
|
2254
|
+
function responseHead(status, reason, headers) {
|
|
2255
|
+
const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
|
|
2256
|
+
let head = `HTTP/1.1 ${status} ${phrase}\r
|
|
2257
|
+
`;
|
|
2258
|
+
for (const [name2, value] of headers) {
|
|
2259
|
+
if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
|
|
2260
|
+
`;
|
|
2261
|
+
}
|
|
2262
|
+
return latin1Bytes(`${head}\r
|
|
2263
|
+
`);
|
|
2264
|
+
}
|
|
2265
|
+
function chunk(bytes) {
|
|
2266
|
+
const size = latin1Bytes(`${bytes.length.toString(16)}\r
|
|
2267
|
+
`);
|
|
2268
|
+
const out = new Uint8Array(size.length + bytes.length + 2);
|
|
2269
|
+
out.set(size);
|
|
2270
|
+
out.set(bytes, size.length);
|
|
2271
|
+
out[out.length - 2] = CR;
|
|
2272
|
+
out[out.length - 1] = LF;
|
|
2273
|
+
return out;
|
|
2274
|
+
}
|
|
2275
|
+
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
2276
|
+
|
|
2277
|
+
// src/cdp/types.ts
|
|
2278
|
+
var CDP_PORT = 9222;
|
|
2279
|
+
var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
|
|
2280
|
+
|
|
2281
|
+
// src/cdp/websocket.ts
|
|
2282
|
+
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
2283
|
+
var MAX_MESSAGE = 256 * 1024 * 1024;
|
|
2284
|
+
var CLOSE = {
|
|
2285
|
+
normal: 1e3,
|
|
2286
|
+
protocol: 1002,
|
|
2287
|
+
unsupported: 1003,
|
|
2288
|
+
invalid: 1007,
|
|
2289
|
+
tooBig: 1009,
|
|
2290
|
+
error: 1011
|
|
2291
|
+
};
|
|
2292
|
+
var OP_TEXT = 1;
|
|
2293
|
+
var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
|
|
2294
|
+
var WsError = class extends Error {
|
|
2295
|
+
code;
|
|
2296
|
+
constructor(code, message2) {
|
|
2297
|
+
super(message2);
|
|
2298
|
+
this.code = code;
|
|
2299
|
+
}
|
|
2300
|
+
};
|
|
2301
|
+
function field(headers, name2) {
|
|
2302
|
+
return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
|
|
2303
|
+
}
|
|
2304
|
+
async function acceptKey(headers) {
|
|
2305
|
+
const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
|
|
2306
|
+
if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
|
|
2307
|
+
throw new HttpError(400, "expected a WebSocket upgrade");
|
|
2308
|
+
}
|
|
2309
|
+
if (field(headers, "sec-websocket-version") !== "13") {
|
|
2310
|
+
throw new HttpError(400, "Sec-WebSocket-Version must be 13");
|
|
2311
|
+
}
|
|
2312
|
+
const key = field(headers, "sec-websocket-key") ?? "";
|
|
2313
|
+
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
|
|
2314
|
+
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
|
|
2315
|
+
return btoa(String.fromCharCode(...new Uint8Array(digest)));
|
|
2316
|
+
}
|
|
2317
|
+
function isUpgrade(headers) {
|
|
2318
|
+
return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
|
|
2319
|
+
}
|
|
2320
|
+
function frame(opcode, payload) {
|
|
2321
|
+
const n = payload.length;
|
|
2322
|
+
const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
|
|
2323
|
+
const out = new Uint8Array(size + n);
|
|
2324
|
+
const view = new DataView(out.buffer);
|
|
2325
|
+
out[0] = 128 | opcode;
|
|
2326
|
+
if (n < 126) out[1] = n;
|
|
2327
|
+
else if (n < 65536) {
|
|
2328
|
+
out[1] = 126;
|
|
2329
|
+
view.setUint16(2, n);
|
|
2330
|
+
} else {
|
|
2331
|
+
out[1] = 127;
|
|
2332
|
+
view.setBigUint64(2, BigInt(n));
|
|
2333
|
+
}
|
|
2334
|
+
out.set(payload, size);
|
|
2335
|
+
return out;
|
|
2336
|
+
}
|
|
2337
|
+
function closeFrame(code, reason = "") {
|
|
2338
|
+
let bytes = new TextEncoder().encode(reason);
|
|
2339
|
+
if (bytes.length > 123) bytes = bytes.subarray(0, 123);
|
|
2340
|
+
const payload = new Uint8Array(2 + bytes.length);
|
|
2341
|
+
new DataView(payload.buffer).setUint16(0, code);
|
|
2342
|
+
payload.set(bytes, 2);
|
|
2343
|
+
return frame(OP.close, payload);
|
|
2344
|
+
}
|
|
2345
|
+
function pongFrame(payload) {
|
|
2346
|
+
return frame(OP.pong, payload);
|
|
2347
|
+
}
|
|
2348
|
+
async function fill(incoming, out, signal) {
|
|
2349
|
+
for (let at = 0; at < out.length; ) {
|
|
2350
|
+
const piece = await incoming.some(out.length - at, signal);
|
|
2351
|
+
if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
|
|
2352
|
+
out.set(piece, at);
|
|
2353
|
+
at += piece.length;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
var fatal = new TextDecoder("utf-8", { fatal: true });
|
|
2357
|
+
function decode(bytes) {
|
|
2358
|
+
try {
|
|
2359
|
+
return fatal.decode(bytes);
|
|
2360
|
+
} catch {
|
|
2361
|
+
throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
var WsReader = class {
|
|
2365
|
+
incoming;
|
|
2366
|
+
max;
|
|
2367
|
+
parts = [];
|
|
2368
|
+
size = 0;
|
|
2369
|
+
continuing = false;
|
|
2370
|
+
constructor(incoming, max = MAX_MESSAGE) {
|
|
2371
|
+
this.incoming = incoming;
|
|
2372
|
+
this.max = max;
|
|
2373
|
+
}
|
|
2374
|
+
async next(signal) {
|
|
2375
|
+
for (; ; ) {
|
|
2376
|
+
const first = await this.incoming.some(1, signal);
|
|
2377
|
+
if (first.length === 0) return null;
|
|
2378
|
+
const head = new Uint8Array(2);
|
|
2379
|
+
head[0] = first[0];
|
|
2380
|
+
await fill(this.incoming, head.subarray(1), signal);
|
|
2381
|
+
const fin = (head[0] & 128) !== 0;
|
|
2382
|
+
const opcode = head[0] & 15;
|
|
2383
|
+
if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
|
|
2384
|
+
if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
|
|
2385
|
+
const length2 = await this.length(head[1] & 127, signal);
|
|
2386
|
+
const control = opcode >= 8;
|
|
2387
|
+
if (control && (length2 > 125 || !fin)) {
|
|
2388
|
+
throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
|
|
2389
|
+
}
|
|
2390
|
+
if (!control && this.size + length2 > this.max) {
|
|
2391
|
+
throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
2392
|
+
}
|
|
2393
|
+
const mask = new Uint8Array(4);
|
|
2394
|
+
await fill(this.incoming, mask, signal);
|
|
2395
|
+
const payload = new Uint8Array(length2);
|
|
2396
|
+
await fill(this.incoming, payload, signal);
|
|
2397
|
+
for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
2398
|
+
const event = this.take(opcode, fin, payload);
|
|
2399
|
+
if (event) return event;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
async length(short, signal) {
|
|
2403
|
+
if (short < 126) return short;
|
|
2404
|
+
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
2405
|
+
await fill(this.incoming, ext, signal);
|
|
2406
|
+
const view = new DataView(ext.buffer);
|
|
2407
|
+
if (short === 126) return view.getUint16(0);
|
|
2408
|
+
const long = view.getBigUint64(0);
|
|
2409
|
+
return long > BigInt(this.max) ? this.max + 1 : Number(long);
|
|
2410
|
+
}
|
|
2411
|
+
take(opcode, fin, payload) {
|
|
2412
|
+
switch (opcode) {
|
|
2413
|
+
case OP.ping:
|
|
2414
|
+
return { kind: "ping", payload };
|
|
2415
|
+
case OP.pong:
|
|
2416
|
+
return void 0;
|
|
2417
|
+
case OP.close: {
|
|
2418
|
+
if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
|
|
2419
|
+
const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
|
|
2420
|
+
return { kind: "close", code, reason: decode(payload.subarray(2)) };
|
|
2421
|
+
}
|
|
2422
|
+
case OP.text:
|
|
2423
|
+
case OP.continuation: {
|
|
2424
|
+
if (opcode === OP.continuation !== this.continuing) {
|
|
2425
|
+
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
2426
|
+
}
|
|
2427
|
+
this.parts.push(payload);
|
|
2428
|
+
this.size += payload.length;
|
|
2429
|
+
this.continuing = !fin;
|
|
2430
|
+
if (!fin) return void 0;
|
|
2431
|
+
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
2432
|
+
this.parts = [];
|
|
2433
|
+
this.size = 0;
|
|
2434
|
+
return { kind: "text", text: decode(whole) };
|
|
2435
|
+
}
|
|
2436
|
+
case OP.binary:
|
|
2437
|
+
throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
2438
|
+
default:
|
|
2439
|
+
throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
function switchingHead(accept2) {
|
|
2444
|
+
return latin1Bytes(
|
|
2445
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
2446
|
+
Upgrade: websocket\r
|
|
2447
|
+
Connection: Upgrade\r
|
|
2448
|
+
Sec-WebSocket-Accept: ${accept2}\r
|
|
2449
|
+
\r
|
|
2450
|
+
`
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// src/cdp/facade.ts
|
|
2455
|
+
var MAX_HEAD = 64 * 1024;
|
|
2456
|
+
var LIST_TIMEOUT = 1e4;
|
|
2457
|
+
function respond(status, body, type = "text/plain; charset=utf-8") {
|
|
2458
|
+
const bytes = new TextEncoder().encode(body);
|
|
2459
|
+
const head = latin1Bytes(
|
|
2460
|
+
`HTTP/1.1 ${status} ${REASON[status]}\r
|
|
2461
|
+
Content-Type: ${type}\r
|
|
2462
|
+
Content-Length: ${bytes.length}\r
|
|
2463
|
+
Connection: close\r
|
|
2464
|
+
\r
|
|
2465
|
+
`
|
|
2466
|
+
);
|
|
2467
|
+
const out = new Uint8Array(head.length + bytes.length);
|
|
2468
|
+
out.set(head);
|
|
2469
|
+
out.set(bytes, head.length);
|
|
2470
|
+
return out;
|
|
2471
|
+
}
|
|
2472
|
+
function json(value) {
|
|
2473
|
+
return respond(200, `${JSON.stringify(value, null, 2)}
|
|
2474
|
+
`, "application/json; charset=UTF-8");
|
|
2475
|
+
}
|
|
2476
|
+
var CdpFacade = class {
|
|
2477
|
+
options;
|
|
2478
|
+
listener;
|
|
2479
|
+
stop = new AbortController();
|
|
2480
|
+
connections = /* @__PURE__ */ new Set();
|
|
2481
|
+
closed;
|
|
2482
|
+
constructor(options) {
|
|
2483
|
+
this.options = options;
|
|
2484
|
+
this.listener = options.net.listen({
|
|
2485
|
+
family: "inet",
|
|
2486
|
+
host: "127.0.0.1",
|
|
2487
|
+
port: options.port ?? CDP_PORT
|
|
2488
|
+
});
|
|
2489
|
+
this.closed = this.acceptLoop();
|
|
2490
|
+
}
|
|
2491
|
+
close() {
|
|
2492
|
+
if (this.stop.signal.aborted) return;
|
|
2493
|
+
this.stop.abort();
|
|
2494
|
+
this.listener.close();
|
|
2495
|
+
for (const conn of this.connections) conn.close();
|
|
2496
|
+
}
|
|
2497
|
+
async acceptLoop() {
|
|
2498
|
+
const served = /* @__PURE__ */ new Set();
|
|
2499
|
+
for (; ; ) {
|
|
2500
|
+
let conn;
|
|
2501
|
+
try {
|
|
2502
|
+
conn = await this.listener.accept(this.stop.signal);
|
|
2503
|
+
} catch {
|
|
2504
|
+
break;
|
|
2505
|
+
}
|
|
2506
|
+
const done4 = this.serve(conn).finally(() => served.delete(done4));
|
|
2507
|
+
served.add(done4);
|
|
2508
|
+
}
|
|
2509
|
+
await Promise.allSettled([...served]);
|
|
2510
|
+
}
|
|
2511
|
+
async serve(conn) {
|
|
2512
|
+
this.connections.add(conn);
|
|
2513
|
+
const incoming = new Incoming(conn);
|
|
2514
|
+
try {
|
|
2515
|
+
const head = await incoming.head(MAX_HEAD, this.stop.signal);
|
|
2516
|
+
if (head) await this.route(conn, incoming, parseRequestHead(head));
|
|
2517
|
+
} catch (e) {
|
|
2518
|
+
if (e instanceof HttpError) await this.write(conn, respond(e.status, `${e.message}
|
|
2519
|
+
`));
|
|
2520
|
+
} finally {
|
|
2521
|
+
this.connections.delete(conn);
|
|
2522
|
+
conn.close();
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
async write(conn, bytes) {
|
|
2526
|
+
try {
|
|
2527
|
+
await conn.write(bytes, this.stop.signal);
|
|
2528
|
+
} catch {
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
async route(conn, incoming, req) {
|
|
2532
|
+
const url = new URL(req.target, "http://127.0.0.1");
|
|
2533
|
+
const runtime = url.searchParams.get("runtime") ?? void 0;
|
|
2534
|
+
const host = req.headers.find(([n]) => n.toLowerCase() === "host")?.[1] ?? "127.0.0.1";
|
|
2535
|
+
if (req.method !== "GET") throw new HttpError(405, `${req.method} is not supported here`);
|
|
2536
|
+
const { hosts } = this.options;
|
|
2537
|
+
if (url.pathname === hosts.path && isUpgrade(req.headers)) {
|
|
2538
|
+
return this.upgrade(conn, incoming, req, runtime);
|
|
2539
|
+
}
|
|
2540
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
2541
|
+
if (path !== "/json/version" && path !== "/json/list" && path !== "/json") {
|
|
2542
|
+
throw new HttpError(404, "not found: try /json/version");
|
|
2543
|
+
}
|
|
2544
|
+
if (!this.options.hosts.available(runtime)) {
|
|
2545
|
+
throw new HttpError(503, NO_CDP_HOST);
|
|
2546
|
+
}
|
|
2547
|
+
if (path === "/json/version") {
|
|
2548
|
+
await this.write(
|
|
2549
|
+
conn,
|
|
2550
|
+
json({
|
|
2551
|
+
Browser: "slicc-kernel",
|
|
2552
|
+
"Protocol-Version": "1.3",
|
|
2553
|
+
webSocketDebuggerUrl: `ws://${host}${hosts.path}${url.search}`
|
|
2554
|
+
})
|
|
2555
|
+
);
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
await this.write(conn, json(await this.list(runtime)));
|
|
2559
|
+
}
|
|
2560
|
+
async list(runtime) {
|
|
2561
|
+
const conn = await this.host(runtime);
|
|
2562
|
+
try {
|
|
2563
|
+
const reply = await new Promise(
|
|
2564
|
+
(resolve, reject) => {
|
|
2565
|
+
const timer = setTimeout(
|
|
2566
|
+
() => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
|
|
2567
|
+
this.options.listTimeout ?? LIST_TIMEOUT
|
|
2568
|
+
);
|
|
2569
|
+
conn.onclose = (reason) => {
|
|
2570
|
+
clearTimeout(timer);
|
|
2571
|
+
reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
|
|
2572
|
+
};
|
|
2573
|
+
conn.onmessage = (message2) => {
|
|
2574
|
+
const parsed = JSON.parse(message2);
|
|
2575
|
+
if (parsed.id !== 1) return;
|
|
2576
|
+
clearTimeout(timer);
|
|
2577
|
+
resolve(parsed);
|
|
2578
|
+
};
|
|
2579
|
+
conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
|
|
2580
|
+
}
|
|
2581
|
+
);
|
|
2582
|
+
if (!reply.result) {
|
|
2583
|
+
throw new HttpError(502, `Target.getTargets failed: ${JSON.stringify(reply.error)}`);
|
|
2584
|
+
}
|
|
2585
|
+
return (reply.result.targetInfos ?? []).map((t) => ({
|
|
2586
|
+
description: "",
|
|
2587
|
+
id: t.targetId,
|
|
2588
|
+
title: t.title,
|
|
2589
|
+
type: t.type,
|
|
2590
|
+
url: t.url
|
|
2591
|
+
}));
|
|
2592
|
+
} finally {
|
|
2593
|
+
conn.close();
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
host(runtime) {
|
|
2597
|
+
return this.options.hosts.connect(runtime ? { runtime } : {});
|
|
2598
|
+
}
|
|
2599
|
+
async upgrade(conn, incoming, req, runtime) {
|
|
2600
|
+
const accept2 = await acceptKey(req.headers);
|
|
2601
|
+
const host = await this.host(runtime);
|
|
2602
|
+
await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
|
|
2603
|
+
}
|
|
2604
|
+
};
|
|
2605
|
+
var Relay = class {
|
|
2606
|
+
writes = Promise.resolve();
|
|
2607
|
+
ended = false;
|
|
2608
|
+
conn;
|
|
2609
|
+
incoming;
|
|
2610
|
+
host;
|
|
2611
|
+
max;
|
|
2612
|
+
stop;
|
|
2613
|
+
constructor(conn, incoming, host, max, stop) {
|
|
2614
|
+
this.conn = conn;
|
|
2615
|
+
this.incoming = incoming;
|
|
2616
|
+
this.host = host;
|
|
2617
|
+
this.max = max;
|
|
2618
|
+
this.stop = stop;
|
|
2619
|
+
}
|
|
2620
|
+
send(bytes) {
|
|
2621
|
+
this.writes = this.writes.then(() => this.conn.write(bytes, this.stop)).catch(() => void 0);
|
|
2622
|
+
return this.writes;
|
|
2623
|
+
}
|
|
2624
|
+
end(code, reason) {
|
|
2625
|
+
if (this.ended) return this.writes;
|
|
2626
|
+
this.ended = true;
|
|
2627
|
+
return this.send(closeFrame(code, reason));
|
|
2628
|
+
}
|
|
2629
|
+
async start(head) {
|
|
2630
|
+
await this.send(head);
|
|
2631
|
+
const hostGone = new Promise((resolve) => {
|
|
2632
|
+
this.host.onclose = (reason) => {
|
|
2633
|
+
void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
|
|
2634
|
+
};
|
|
2635
|
+
});
|
|
2636
|
+
this.host.onmessage = (message2) => {
|
|
2637
|
+
if (this.ended) return;
|
|
2638
|
+
const bytes = new TextEncoder().encode(message2);
|
|
2639
|
+
if (bytes.length > this.max)
|
|
2640
|
+
void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
2641
|
+
else void this.send(frame(OP_TEXT, bytes));
|
|
2642
|
+
};
|
|
2643
|
+
await Promise.race([this.pump(), hostGone]);
|
|
2644
|
+
await this.writes;
|
|
2645
|
+
}
|
|
2646
|
+
async pump() {
|
|
2647
|
+
const reader = new WsReader(this.incoming, this.max);
|
|
2648
|
+
try {
|
|
2649
|
+
for (; ; ) {
|
|
2650
|
+
const event = await reader.next(this.stop);
|
|
2651
|
+
if (!event) return;
|
|
2652
|
+
if (event.kind === "text") this.host.send(event.text);
|
|
2653
|
+
else if (event.kind === "ping") void this.send(pongFrame(event.payload));
|
|
2654
|
+
else {
|
|
2655
|
+
await this.end(event.code === 1005 ? CLOSE.normal : event.code, "");
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
} catch (e) {
|
|
2660
|
+
if (e instanceof WsError) await this.end(e.code, e.message);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
function enableCdp(net, hosts) {
|
|
2665
|
+
let facade;
|
|
2666
|
+
net.kernelOnly(CDP_PORT, true);
|
|
2667
|
+
net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
|
|
2668
|
+
const started = new CdpFacade({ net, hosts });
|
|
2669
|
+
facade = started;
|
|
2670
|
+
void started.closed.then(() => {
|
|
2671
|
+
if (facade === started) facade = void 0;
|
|
2672
|
+
});
|
|
2673
|
+
});
|
|
2674
|
+
return () => facade;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/cdp/hosts.ts
|
|
2678
|
+
var CdpHosts = class {
|
|
2679
|
+
embedder;
|
|
2680
|
+
clients = [];
|
|
2681
|
+
closed = false;
|
|
2682
|
+
path = `/devtools/browser/${crypto.randomUUID()}`;
|
|
2683
|
+
url = `ws://127.0.0.1:${CDP_PORT}${this.path}`;
|
|
2684
|
+
constructor(hook) {
|
|
2685
|
+
this.embedder = hook ? { hook, runtime: void 0, open: /* @__PURE__ */ new Set() } : void 0;
|
|
2686
|
+
}
|
|
2687
|
+
register(hook, runtime) {
|
|
2688
|
+
const entry = { hook, runtime, open: /* @__PURE__ */ new Set() };
|
|
2689
|
+
this.clients.push(entry);
|
|
2690
|
+
return () => {
|
|
2691
|
+
const at = this.clients.indexOf(entry);
|
|
2692
|
+
if (at >= 0) this.clients.splice(at, 1);
|
|
2693
|
+
closeAll(entry);
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
pick(runtime) {
|
|
2697
|
+
if (this.closed) return void 0;
|
|
2698
|
+
const named = runtime ? this.clients.findLast((c) => c.runtime === runtime) : void 0;
|
|
2699
|
+
return named ?? this.embedder ?? this.clients.at(-1);
|
|
2700
|
+
}
|
|
2701
|
+
available(runtime) {
|
|
2702
|
+
return this.pick(runtime) !== void 0;
|
|
2703
|
+
}
|
|
2704
|
+
async connect(request) {
|
|
2705
|
+
const entry = this.pick(request.runtime);
|
|
2706
|
+
if (!entry) throw new HttpError(503, NO_CDP_HOST);
|
|
2707
|
+
let conn;
|
|
2708
|
+
try {
|
|
2709
|
+
conn = await entry.hook(request.runtime ? { runtime: request.runtime } : {});
|
|
2710
|
+
} catch (e) {
|
|
2711
|
+
throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
|
|
2712
|
+
}
|
|
2713
|
+
if (this.closed || !(entry === this.embedder || this.clients.includes(entry))) {
|
|
2714
|
+
conn.close();
|
|
2715
|
+
throw new HttpError(503, NO_CDP_HOST);
|
|
2716
|
+
}
|
|
2717
|
+
entry.open.add(conn);
|
|
2718
|
+
const close = conn.close.bind(conn);
|
|
2719
|
+
conn.close = () => {
|
|
2720
|
+
if (entry.open.delete(conn)) close();
|
|
2721
|
+
};
|
|
2722
|
+
return conn;
|
|
2723
|
+
}
|
|
2724
|
+
close() {
|
|
2725
|
+
this.closed = true;
|
|
2726
|
+
for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
function closeAll(entry) {
|
|
2730
|
+
for (const conn of [...entry.open]) {
|
|
2731
|
+
conn.close();
|
|
2732
|
+
conn.onclose?.("the CDP host is gone");
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// src/commands.ts
|
|
2737
|
+
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
2738
|
+
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2739
|
+
var PACKAGE = /\$\{package\}/g;
|
|
2740
|
+
function inside(pkg, rel2) {
|
|
2741
|
+
if (typeof rel2 !== "string" || rel2 === "") return void 0;
|
|
2742
|
+
const clean = rel2.replace(/^\.\//, "");
|
|
2743
|
+
if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
|
|
2744
|
+
return `${pkg}/${clean}`;
|
|
2745
|
+
}
|
|
2746
|
+
function envOf(pkg, raw) {
|
|
2747
|
+
const env = {};
|
|
2748
|
+
if (!raw || typeof raw !== "object") return env;
|
|
2749
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2750
|
+
if (!ENV_KEY.test(key)) continue;
|
|
2751
|
+
if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
2752
|
+
else if (value === null) env[key] = null;
|
|
2753
|
+
}
|
|
2754
|
+
return env;
|
|
2755
|
+
}
|
|
2756
|
+
function envFields(merged) {
|
|
2757
|
+
const env = {};
|
|
2758
|
+
const unset = [];
|
|
2759
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
2760
|
+
if (value === null) unset.push(key);
|
|
2761
|
+
else env[key] = value;
|
|
2762
|
+
}
|
|
2763
|
+
return {
|
|
2764
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
2765
|
+
...unset.length > 0 ? { unset } : {}
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
function argsOf(pkg, raw) {
|
|
2769
|
+
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
2770
|
+
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
2771
|
+
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
2772
|
+
}
|
|
2773
|
+
function abiOf(raw, fallback) {
|
|
2774
|
+
const abi = raw ?? fallback;
|
|
2775
|
+
return abi === "emscripten" || abi === "wasi" ? abi : void 0;
|
|
2776
|
+
}
|
|
2777
|
+
function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
2778
|
+
const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
|
|
2779
|
+
const script = inside(pkg, raw.script);
|
|
2780
|
+
if (script) {
|
|
2781
|
+
return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
|
|
2782
|
+
}
|
|
2783
|
+
const abi = abiOf(raw.abi, packageAbi);
|
|
2784
|
+
const wasm = inside(pkg, raw.wasm);
|
|
2785
|
+
const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
|
|
2786
|
+
if (!abi || !glue || !wasm) return void 0;
|
|
2787
|
+
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
|
|
2788
|
+
const args = argsOf(pkg, raw.args);
|
|
2789
|
+
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
2790
|
+
return {
|
|
2791
|
+
name: name2,
|
|
2792
|
+
abi,
|
|
2793
|
+
glue,
|
|
2794
|
+
wasm,
|
|
2795
|
+
argv0,
|
|
2796
|
+
...args ? { args } : {},
|
|
2797
|
+
...withEnv,
|
|
2798
|
+
...imports ? { imports } : {}
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
function commandsOf(pkg, manifest) {
|
|
2802
|
+
const slicc = manifest.slicc;
|
|
2803
|
+
if (!slicc || typeof slicc !== "object") return [];
|
|
2804
|
+
const packageAbi = abiOf(slicc.abi, "emscripten");
|
|
2805
|
+
const entries = slicc.commands;
|
|
2806
|
+
if (!packageAbi || !entries || typeof entries !== "object") return [];
|
|
2807
|
+
const shared = envOf(pkg, slicc.env);
|
|
2808
|
+
const out = [];
|
|
2809
|
+
for (const [name2, raw] of Object.entries(entries)) {
|
|
2810
|
+
if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
|
|
2811
|
+
continue;
|
|
2812
|
+
}
|
|
2813
|
+
const command = commandOf(pkg, name2, raw, packageAbi, shared);
|
|
2814
|
+
if (command) out.push(command);
|
|
2815
|
+
}
|
|
2816
|
+
return out;
|
|
2817
|
+
}
|
|
2818
|
+
async function withPackagePaths(fs, pkg, command) {
|
|
2819
|
+
if (!command.env) return command;
|
|
2820
|
+
const env = {};
|
|
2821
|
+
for (const [key, value] of Object.entries(command.env)) {
|
|
2822
|
+
const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
|
|
2823
|
+
env[key] = path && await fs.exists(path) ? path : value;
|
|
2824
|
+
}
|
|
2825
|
+
return { ...command, env };
|
|
2826
|
+
}
|
|
2827
|
+
async function packages(fs, modules) {
|
|
2828
|
+
const names = await fs.readdir(modules).catch(() => []);
|
|
2829
|
+
const dirs = [];
|
|
2830
|
+
for (const name2 of names) {
|
|
2831
|
+
if (name2.startsWith(".")) continue;
|
|
2832
|
+
if (!name2.startsWith("@")) {
|
|
2833
|
+
dirs.push(`${modules}/${name2}`);
|
|
2834
|
+
continue;
|
|
2835
|
+
}
|
|
2836
|
+
for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
|
|
2837
|
+
dirs.push(`${modules}/${name2}/${inner}`);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return dirs;
|
|
2841
|
+
}
|
|
2842
|
+
async function pnpmGlobalRoots(fs, home) {
|
|
2843
|
+
const roots = [];
|
|
2844
|
+
const global = `${home}/global`;
|
|
2845
|
+
for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
|
|
2846
|
+
for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
|
|
2847
|
+
const dir = `${global}/${version}/${group}`;
|
|
2848
|
+
const st = await fs.lstat(dir).catch(() => void 0);
|
|
2849
|
+
if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
|
|
2850
|
+
roots.push(`${dir}/node_modules`);
|
|
1935
2851
|
}
|
|
1936
2852
|
}
|
|
1937
2853
|
return roots;
|
|
@@ -2203,7 +3119,7 @@ var CONTINUED_STATUS = 65535;
|
|
|
2203
3119
|
function waitStatus(code, termsig) {
|
|
2204
3120
|
return termsig ? termsig & 127 : (code & 255) << 8;
|
|
2205
3121
|
}
|
|
2206
|
-
function
|
|
3122
|
+
function concat2(chunks2) {
|
|
2207
3123
|
const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
|
|
2208
3124
|
let offset = 0;
|
|
2209
3125
|
for (const chunk2 of chunks2) {
|
|
@@ -2360,7 +3276,7 @@ var ChildTable = class {
|
|
|
2360
3276
|
if (!slots || !chunks2) return new Uint8Array(0);
|
|
2361
3277
|
slots.delete(slot);
|
|
2362
3278
|
if (slots.size === 0) this.leftovers.delete(pid);
|
|
2363
|
-
return
|
|
3279
|
+
return concat2(chunks2);
|
|
2364
3280
|
}
|
|
2365
3281
|
};
|
|
2366
3282
|
|
|
@@ -2528,7 +3444,7 @@ function statOf(attr, path, mount) {
|
|
|
2528
3444
|
...mount.caps.ranges ? { ranged: true } : {}
|
|
2529
3445
|
};
|
|
2530
3446
|
}
|
|
2531
|
-
function
|
|
3447
|
+
function concat3(chunks2, total) {
|
|
2532
3448
|
if (chunks2.length === 1 && chunks2[0]?.length === total) return chunks2[0];
|
|
2533
3449
|
const out = new Uint8Array(total);
|
|
2534
3450
|
let at = 0;
|
|
@@ -2823,7 +3739,7 @@ var MountFs = class {
|
|
|
2823
3739
|
} finally {
|
|
2824
3740
|
await mount.conn.call({ op: "release", fh });
|
|
2825
3741
|
}
|
|
2826
|
-
return
|
|
3742
|
+
return concat3(chunks2, total);
|
|
2827
3743
|
}
|
|
2828
3744
|
async writeFile(path, content) {
|
|
2829
3745
|
const real = await this.follow(path, 0, true);
|
|
@@ -2863,6 +3779,7 @@ var MountFs = class {
|
|
|
2863
3779
|
async pread(path, offset, length2) {
|
|
2864
3780
|
const real = await this.follow(path);
|
|
2865
3781
|
const found = this.at(real);
|
|
3782
|
+
if (!found && this.base.pread) return this.base.pread(real, offset, length2);
|
|
2866
3783
|
if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
|
|
2867
3784
|
const { mount, rel: rel2 } = found;
|
|
2868
3785
|
if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
|
|
@@ -2887,7 +3804,7 @@ var MountFs = class {
|
|
|
2887
3804
|
} finally {
|
|
2888
3805
|
await mount.conn.call({ op: "release", fh });
|
|
2889
3806
|
}
|
|
2890
|
-
return
|
|
3807
|
+
return concat3(chunks2, total);
|
|
2891
3808
|
}
|
|
2892
3809
|
fits(mount, real, end) {
|
|
2893
3810
|
if (mount.maxFile > 0 && end > mount.maxFile) {
|
|
@@ -2900,6 +3817,7 @@ var MountFs = class {
|
|
|
2900
3817
|
async pwrite(path, offset, bytes, transfer = false) {
|
|
2901
3818
|
const real = await this.follow(path, 0, true);
|
|
2902
3819
|
const found = this.at(real);
|
|
3820
|
+
if (!found && this.base.pwrite) return this.base.pwrite(real, offset, bytes);
|
|
2903
3821
|
if (!found)
|
|
2904
3822
|
return this.base.writeFile(
|
|
2905
3823
|
real,
|
|
@@ -2931,6 +3849,7 @@ var MountFs = class {
|
|
|
2931
3849
|
async truncate(path, size) {
|
|
2932
3850
|
const real = await this.follow(path);
|
|
2933
3851
|
const found = this.at(real);
|
|
3852
|
+
if (!found && this.base.truncate) return this.base.truncate(real, size);
|
|
2934
3853
|
if (!found?.mount.caps.ranges) {
|
|
2935
3854
|
return this.writeFile(real, resized(await this.readFileBuffer(real), size));
|
|
2936
3855
|
}
|
|
@@ -3140,7 +4059,7 @@ async function storageUsage() {
|
|
|
3140
4059
|
return { quota: estimate.quota, usage: estimate.usage ?? 0 };
|
|
3141
4060
|
}
|
|
3142
4061
|
var done = { ok: true, kind: "void" };
|
|
3143
|
-
function
|
|
4062
|
+
function json2(value) {
|
|
3144
4063
|
return { ok: true, kind: "json", json: value };
|
|
3145
4064
|
}
|
|
3146
4065
|
async function run(entry, path, req) {
|
|
@@ -3152,15 +4071,15 @@ async function run(entry, path, req) {
|
|
|
3152
4071
|
await fs.writeFile(path, req.body ?? new Uint8Array(0));
|
|
3153
4072
|
return done;
|
|
3154
4073
|
case "exists":
|
|
3155
|
-
return
|
|
4074
|
+
return json2(await fs.exists(path));
|
|
3156
4075
|
case "stat":
|
|
3157
|
-
return
|
|
4076
|
+
return json2(statJson(await fs.stat(path)));
|
|
3158
4077
|
case "lstat":
|
|
3159
|
-
return
|
|
4078
|
+
return json2(statJson(await fs.lstat(path)));
|
|
3160
4079
|
case "readdir":
|
|
3161
|
-
return
|
|
4080
|
+
return json2(await fs.readdir(path));
|
|
3162
4081
|
case "readdir-stat":
|
|
3163
|
-
return
|
|
4082
|
+
return json2(await readdirStat(fs, path));
|
|
3164
4083
|
case "mkdir":
|
|
3165
4084
|
await fs.mkdir(path, { recursive: true });
|
|
3166
4085
|
return done;
|
|
@@ -3186,7 +4105,7 @@ async function run(entry, path, req) {
|
|
|
3186
4105
|
await fs.symlink(req.arg2 ?? "", path);
|
|
3187
4106
|
return done;
|
|
3188
4107
|
case "readlink":
|
|
3189
|
-
return
|
|
4108
|
+
return json2(await fs.readlink(path));
|
|
3190
4109
|
case "chmod":
|
|
3191
4110
|
await fs.chmod(path, req.mode ?? 0);
|
|
3192
4111
|
return done;
|
|
@@ -3217,7 +4136,7 @@ async function dispatchSyncFs(req) {
|
|
|
3217
4136
|
const { fs, cwd } = entry;
|
|
3218
4137
|
try {
|
|
3219
4138
|
const path = fs.resolvePath(cwd, req.path);
|
|
3220
|
-
if (req.op === "statfs") return
|
|
4139
|
+
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
3221
4140
|
if (req.op === "hold") {
|
|
3222
4141
|
entry.hold?.(path, req.mode === 1);
|
|
3223
4142
|
return done;
|
|
@@ -3390,7 +4309,7 @@ var HOST_OPS = [
|
|
|
3390
4309
|
"async-cancel",
|
|
3391
4310
|
"async-close"
|
|
3392
4311
|
];
|
|
3393
|
-
var
|
|
4312
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3394
4313
|
var done2 = { ok: true, kind: "void" };
|
|
3395
4314
|
var LockTable = class {
|
|
3396
4315
|
held = /* @__PURE__ */ new Map();
|
|
@@ -3439,353 +4358,116 @@ var AsyncOps = class {
|
|
|
3439
4358
|
}
|
|
3440
4359
|
resolve(value) {
|
|
3441
4360
|
const id = this.hold();
|
|
3442
|
-
this.complete(id,
|
|
3443
|
-
return id;
|
|
3444
|
-
}
|
|
3445
|
-
hold() {
|
|
3446
|
-
const id = this.next++;
|
|
3447
|
-
this.results.set(id, void 0);
|
|
3448
|
-
return id;
|
|
3449
|
-
}
|
|
3450
|
-
take(id) {
|
|
3451
|
-
if (!this.results.has(id)) throw new KernelError("EINVAL");
|
|
3452
|
-
const result = this.results.get(id);
|
|
3453
|
-
if (!result) throw new KernelError("EAGAIN");
|
|
3454
|
-
this.cancel(id);
|
|
3455
|
-
return result;
|
|
3456
|
-
}
|
|
3457
|
-
cancel(id) {
|
|
3458
|
-
this.results.delete(id);
|
|
3459
|
-
const at = this.ready.indexOf(id);
|
|
3460
|
-
if (at >= 0) this.ready.splice(at, 1);
|
|
3461
|
-
}
|
|
3462
|
-
async wait(timeoutMs, signal) {
|
|
3463
|
-
for (; ; ) {
|
|
3464
|
-
if (this.closed) return -1;
|
|
3465
|
-
const id = this.ready.shift();
|
|
3466
|
-
if (id !== void 0) return id;
|
|
3467
|
-
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
3468
|
-
const woke = await this.sleep(timeoutMs, signal);
|
|
3469
|
-
if (!woke) return 0;
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
close() {
|
|
3473
|
-
this.closed = true;
|
|
3474
|
-
this.wake();
|
|
3475
|
-
}
|
|
3476
|
-
sleep(timeoutMs, signal) {
|
|
3477
|
-
return new Promise((resolve, reject) => {
|
|
3478
|
-
let timer;
|
|
3479
|
-
const cleanup = () => {
|
|
3480
|
-
clearTimeout(timer);
|
|
3481
|
-
this.waiters.delete(wake);
|
|
3482
|
-
signal.removeEventListener("abort", abort);
|
|
3483
|
-
};
|
|
3484
|
-
const wake = () => {
|
|
3485
|
-
cleanup();
|
|
3486
|
-
resolve(true);
|
|
3487
|
-
};
|
|
3488
|
-
const abort = () => {
|
|
3489
|
-
cleanup();
|
|
3490
|
-
reject(new KernelError("EINTR"));
|
|
3491
|
-
};
|
|
3492
|
-
this.waiters.add(wake);
|
|
3493
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
3494
|
-
if (timeoutMs !== void 0) {
|
|
3495
|
-
timer = setTimeout(() => {
|
|
3496
|
-
cleanup();
|
|
3497
|
-
resolve(false);
|
|
3498
|
-
}, timeoutMs);
|
|
3499
|
-
}
|
|
3500
|
-
});
|
|
3501
|
-
}
|
|
3502
|
-
complete(id, result) {
|
|
3503
|
-
if (!this.results.has(id)) return;
|
|
3504
|
-
this.results.set(id, result);
|
|
3505
|
-
this.ready.push(id);
|
|
3506
|
-
this.wake();
|
|
3507
|
-
}
|
|
3508
|
-
wake() {
|
|
3509
|
-
for (const wake of [...this.waiters]) wake();
|
|
3510
|
-
}
|
|
3511
|
-
};
|
|
3512
|
-
async function hostSyscall(req, ctx) {
|
|
3513
|
-
switch (req.op) {
|
|
3514
|
-
case "lock":
|
|
3515
|
-
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
3516
|
-
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
3517
|
-
throw new KernelError("EAGAIN");
|
|
3518
|
-
return done2;
|
|
3519
|
-
case "unlock":
|
|
3520
|
-
ctx.locks?.unlock(ctx.pid, req.path);
|
|
3521
|
-
return done2;
|
|
3522
|
-
case "async-submit":
|
|
3523
|
-
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
3524
|
-
return json2(ctx.ops.submit(req.req));
|
|
3525
|
-
case "async-wait":
|
|
3526
|
-
return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
3527
|
-
case "async-take":
|
|
3528
|
-
return ctx.ops.take(req.id);
|
|
3529
|
-
case "async-resolve":
|
|
3530
|
-
return json2(ctx.ops.resolve(req.value));
|
|
3531
|
-
case "async-hold":
|
|
3532
|
-
return json2(ctx.ops.hold());
|
|
3533
|
-
case "async-cancel":
|
|
3534
|
-
ctx.ops.cancel(req.id);
|
|
3535
|
-
return done2;
|
|
3536
|
-
case "async-close":
|
|
3537
|
-
ctx.ops.close();
|
|
3538
|
-
return done2;
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
|
|
3542
|
-
// src/kernel/net/loopback-names.ts
|
|
3543
|
-
var HOST_LOOPBACK = "host.slicc.internal";
|
|
3544
|
-
var PAGE_LOOPBACK = "kernel.localhost";
|
|
3545
|
-
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
3546
|
-
function toHostLoopback(href) {
|
|
3547
|
-
const url = new URL(href);
|
|
3548
|
-
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
3549
|
-
url.hostname = "127.0.0.1";
|
|
3550
|
-
return url.href;
|
|
3551
|
-
}
|
|
3552
|
-
|
|
3553
|
-
// src/kernel/net/http1.ts
|
|
3554
|
-
var HttpError = class extends Error {
|
|
3555
|
-
status;
|
|
3556
|
-
constructor(status, message2) {
|
|
3557
|
-
super(message2);
|
|
3558
|
-
this.status = status;
|
|
3559
|
-
}
|
|
3560
|
-
};
|
|
3561
|
-
var CR = 13;
|
|
3562
|
-
var LF = 10;
|
|
3563
|
-
var READ_SIZE = 64 * 1024;
|
|
3564
|
-
var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
3565
|
-
var REASON = {
|
|
3566
|
-
100: "Continue",
|
|
3567
|
-
200: "OK",
|
|
3568
|
-
400: "Bad Request",
|
|
3569
|
-
403: "Forbidden",
|
|
3570
|
-
408: "Request Timeout",
|
|
3571
|
-
411: "Length Required",
|
|
3572
|
-
413: "Content Too Large",
|
|
3573
|
-
417: "Expectation Failed",
|
|
3574
|
-
421: "Misdirected Request",
|
|
3575
|
-
431: "Request Header Fields Too Large",
|
|
3576
|
-
501: "Not Implemented",
|
|
3577
|
-
502: "Bad Gateway",
|
|
3578
|
-
503: "Service Unavailable",
|
|
3579
|
-
505: "HTTP Version Not Supported"
|
|
3580
|
-
};
|
|
3581
|
-
var Incoming = class {
|
|
3582
|
-
source;
|
|
3583
|
-
buf = new Uint8Array(0);
|
|
3584
|
-
eof = false;
|
|
3585
|
-
constructor(source) {
|
|
3586
|
-
this.source = source;
|
|
3587
|
-
}
|
|
3588
|
-
get buffered() {
|
|
3589
|
-
return this.buf.length;
|
|
3590
|
-
}
|
|
3591
|
-
async fill(signal) {
|
|
3592
|
-
if (this.eof) return false;
|
|
3593
|
-
const chunk2 = await this.source.read(READ_SIZE, signal);
|
|
3594
|
-
if (chunk2.length === 0) {
|
|
3595
|
-
this.eof = true;
|
|
3596
|
-
return false;
|
|
3597
|
-
}
|
|
3598
|
-
const next = new Uint8Array(this.buf.length + chunk2.length);
|
|
3599
|
-
next.set(this.buf);
|
|
3600
|
-
next.set(chunk2, this.buf.length);
|
|
3601
|
-
this.buf = next;
|
|
3602
|
-
return true;
|
|
3603
|
-
}
|
|
3604
|
-
take(n) {
|
|
3605
|
-
const out = this.buf.slice(0, n);
|
|
3606
|
-
this.buf = this.buf.subarray(n);
|
|
3607
|
-
return out;
|
|
3608
|
-
}
|
|
3609
|
-
async some(max, signal) {
|
|
3610
|
-
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
3611
|
-
if (this.eof) return new Uint8Array(0);
|
|
3612
|
-
const chunk2 = await this.source.read(max, signal);
|
|
3613
|
-
if (chunk2.length === 0) this.eof = true;
|
|
3614
|
-
return chunk2;
|
|
3615
|
-
}
|
|
3616
|
-
async head(limit, signal) {
|
|
3617
|
-
let from = 0;
|
|
3618
|
-
for (; ; ) {
|
|
3619
|
-
const end = headEnd(this.buf, from);
|
|
3620
|
-
if (end > 0) {
|
|
3621
|
-
if (end > limit) throw new HttpError(431, "request head too large");
|
|
3622
|
-
return this.take(end);
|
|
3623
|
-
}
|
|
3624
|
-
if (this.buf.length > limit) throw new HttpError(431, "request head too large");
|
|
3625
|
-
from = Math.max(0, this.buf.length - 3);
|
|
3626
|
-
if (!await this.fill(signal)) {
|
|
3627
|
-
if (this.buf.length === 0) return null;
|
|
3628
|
-
throw new HttpError(400, "connection closed inside a request head");
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3632
|
-
async line(limit, signal) {
|
|
3633
|
-
let from = 0;
|
|
3634
|
-
for (; ; ) {
|
|
3635
|
-
const at = this.buf.indexOf(LF, from);
|
|
3636
|
-
if (at >= 0) {
|
|
3637
|
-
const line = this.take(at + 1);
|
|
3638
|
-
const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
|
|
3639
|
-
return text;
|
|
3640
|
-
}
|
|
3641
|
-
if (this.buf.length > limit) throw new HttpError(400, "line too long");
|
|
3642
|
-
from = this.buf.length;
|
|
3643
|
-
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
async exactly(n, signal) {
|
|
3647
|
-
while (this.buf.length < n) {
|
|
3648
|
-
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
3649
|
-
}
|
|
3650
|
-
return this.take(n);
|
|
4361
|
+
this.complete(id, json3(value));
|
|
4362
|
+
return id;
|
|
3651
4363
|
}
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
if (buf[i + 1] === LF) return i + 2;
|
|
3657
|
-
if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
|
|
4364
|
+
hold() {
|
|
4365
|
+
const id = this.next++;
|
|
4366
|
+
this.results.set(id, void 0);
|
|
4367
|
+
return id;
|
|
3658
4368
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
4369
|
+
take(id) {
|
|
4370
|
+
if (!this.results.has(id)) throw new KernelError("EINVAL");
|
|
4371
|
+
const result = this.results.get(id);
|
|
4372
|
+
if (!result) throw new KernelError("EAGAIN");
|
|
4373
|
+
this.cancel(id);
|
|
4374
|
+
return result;
|
|
3665
4375
|
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
3671
|
-
return out;
|
|
3672
|
-
}
|
|
3673
|
-
function parseRequestHead(bytes) {
|
|
3674
|
-
const lines = latin1(bytes).split(/\r?\n/);
|
|
3675
|
-
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
3676
|
-
const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
|
|
3677
|
-
if (!match) throw new HttpError(400, "malformed request line");
|
|
3678
|
-
if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
|
|
3679
|
-
return {
|
|
3680
|
-
method: match[1],
|
|
3681
|
-
target: match[2],
|
|
3682
|
-
minor: Number(match[4]),
|
|
3683
|
-
headers: parseFields(lines)
|
|
3684
|
-
};
|
|
3685
|
-
}
|
|
3686
|
-
function parseFields(lines) {
|
|
3687
|
-
const headers = [];
|
|
3688
|
-
for (const line of lines) {
|
|
3689
|
-
if (line === "") continue;
|
|
3690
|
-
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
3691
|
-
throw new HttpError(400, "obsolete line folding");
|
|
3692
|
-
}
|
|
3693
|
-
const colon = line.indexOf(":");
|
|
3694
|
-
const name2 = colon > 0 ? line.slice(0, colon) : "";
|
|
3695
|
-
if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
|
|
3696
|
-
headers.push([name2, line.slice(colon + 1).trim()]);
|
|
4376
|
+
cancel(id) {
|
|
4377
|
+
this.results.delete(id);
|
|
4378
|
+
const at = this.ready.indexOf(id);
|
|
4379
|
+
if (at >= 0) this.ready.splice(at, 1);
|
|
3697
4380
|
}
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
}
|
|
3707
|
-
function requestFraming(headers) {
|
|
3708
|
-
const codings = fieldTokens(headers, "transfer-encoding");
|
|
3709
|
-
const lengths = fieldValues(headers, "content-length");
|
|
3710
|
-
if (codings.length > 0) {
|
|
3711
|
-
if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
|
|
3712
|
-
if (codings.length !== 1 || codings[0] !== "chunked") {
|
|
3713
|
-
throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
|
|
4381
|
+
async wait(timeoutMs, signal) {
|
|
4382
|
+
for (; ; ) {
|
|
4383
|
+
if (this.closed) return -1;
|
|
4384
|
+
const id = this.ready.shift();
|
|
4385
|
+
if (id !== void 0) return id;
|
|
4386
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
4387
|
+
const woke = await this.sleep(timeoutMs, signal);
|
|
4388
|
+
if (!woke) return 0;
|
|
3714
4389
|
}
|
|
3715
|
-
return { kind: "chunked" };
|
|
3716
|
-
}
|
|
3717
|
-
if (lengths.length === 0) return { kind: "none" };
|
|
3718
|
-
const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
|
|
3719
|
-
const [only] = distinct;
|
|
3720
|
-
if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
|
|
3721
|
-
throw new HttpError(400, "invalid Content-Length");
|
|
3722
4390
|
}
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
var MAX_CHUNK_LINE = 4096;
|
|
3727
|
-
var MAX_TRAILERS = 64 * 1024;
|
|
3728
|
-
async function readBody(incoming, framing, cap, signal) {
|
|
3729
|
-
if (framing.kind === "none") return void 0;
|
|
3730
|
-
if (framing.kind === "length") {
|
|
3731
|
-
if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
3732
|
-
return incoming.exactly(framing.length, signal);
|
|
4391
|
+
close() {
|
|
4392
|
+
this.closed = true;
|
|
4393
|
+
this.wake();
|
|
3733
4394
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
4395
|
+
sleep(timeoutMs, signal) {
|
|
4396
|
+
return new Promise((resolve, reject) => {
|
|
4397
|
+
let timer;
|
|
4398
|
+
const cleanup = () => {
|
|
4399
|
+
clearTimeout(timer);
|
|
4400
|
+
this.waiters.delete(wake);
|
|
4401
|
+
signal.removeEventListener("abort", abort);
|
|
4402
|
+
};
|
|
4403
|
+
const wake = () => {
|
|
4404
|
+
cleanup();
|
|
4405
|
+
resolve(true);
|
|
4406
|
+
};
|
|
4407
|
+
const abort = () => {
|
|
4408
|
+
cleanup();
|
|
4409
|
+
reject(new KernelError("EINTR"));
|
|
4410
|
+
};
|
|
4411
|
+
this.waiters.add(wake);
|
|
4412
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
4413
|
+
if (timeoutMs !== void 0) {
|
|
4414
|
+
timer = setTimeout(() => {
|
|
4415
|
+
cleanup();
|
|
4416
|
+
resolve(false);
|
|
4417
|
+
}, timeoutMs);
|
|
4418
|
+
}
|
|
4419
|
+
});
|
|
3746
4420
|
}
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
4421
|
+
complete(id, result) {
|
|
4422
|
+
if (!this.results.has(id)) return;
|
|
4423
|
+
this.results.set(id, result);
|
|
4424
|
+
this.ready.push(id);
|
|
4425
|
+
this.wake();
|
|
3752
4426
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
function concat3(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
|
|
3756
|
-
const out = new Uint8Array(total);
|
|
3757
|
-
let at = 0;
|
|
3758
|
-
for (const chunk2 of chunks2) {
|
|
3759
|
-
out.set(chunk2, at);
|
|
3760
|
-
at += chunk2.length;
|
|
4427
|
+
wake() {
|
|
4428
|
+
for (const wake of [...this.waiters]) wake();
|
|
3761
4429
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
4430
|
+
};
|
|
4431
|
+
async function hostSyscall(req, ctx) {
|
|
4432
|
+
switch (req.op) {
|
|
4433
|
+
case "lock":
|
|
4434
|
+
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
4435
|
+
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
4436
|
+
throw new KernelError("EAGAIN");
|
|
4437
|
+
return done2;
|
|
4438
|
+
case "unlock":
|
|
4439
|
+
ctx.locks?.unlock(ctx.pid, req.path);
|
|
4440
|
+
return done2;
|
|
4441
|
+
case "async-submit":
|
|
4442
|
+
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
4443
|
+
return json3(ctx.ops.submit(req.req));
|
|
4444
|
+
case "async-wait":
|
|
4445
|
+
return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
4446
|
+
case "async-take":
|
|
4447
|
+
return ctx.ops.take(req.id);
|
|
4448
|
+
case "async-resolve":
|
|
4449
|
+
return json3(ctx.ops.resolve(req.value));
|
|
4450
|
+
case "async-hold":
|
|
4451
|
+
return json3(ctx.ops.hold());
|
|
4452
|
+
case "async-cancel":
|
|
4453
|
+
ctx.ops.cancel(req.id);
|
|
4454
|
+
return done2;
|
|
4455
|
+
case "async-close":
|
|
4456
|
+
ctx.ops.close();
|
|
4457
|
+
return done2;
|
|
3774
4458
|
}
|
|
3775
|
-
return latin1Bytes(`${head}\r
|
|
3776
|
-
`);
|
|
3777
4459
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
4460
|
+
|
|
4461
|
+
// src/kernel/net/loopback-names.ts
|
|
4462
|
+
var HOST_LOOPBACK = "host.slicc.internal";
|
|
4463
|
+
var PAGE_LOOPBACK = "kernel.localhost";
|
|
4464
|
+
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
4465
|
+
function toHostLoopback(href) {
|
|
4466
|
+
const url = new URL(href);
|
|
4467
|
+
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
4468
|
+
url.hostname = "127.0.0.1";
|
|
4469
|
+
return url.href;
|
|
3787
4470
|
}
|
|
3788
|
-
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
3789
4471
|
|
|
3790
4472
|
// src/kernel/net/proxy-service.ts
|
|
3791
4473
|
var REALM_PROXY_PORT = 3128;
|
|
@@ -5378,23 +6060,23 @@ function terminalOf(ctx, fd) {
|
|
|
5378
6060
|
if (!tty) throw new KernelError("ENOTTY");
|
|
5379
6061
|
return tty;
|
|
5380
6062
|
}
|
|
5381
|
-
var
|
|
6063
|
+
var json4 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5382
6064
|
var done3 = { ok: true, kind: "void" };
|
|
5383
6065
|
function ptySyscall(req, ctx) {
|
|
5384
6066
|
switch (req.op) {
|
|
5385
6067
|
case "pty-open": {
|
|
5386
6068
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5387
|
-
return
|
|
6069
|
+
return json4(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5388
6070
|
}
|
|
5389
6071
|
case "pty-slave-open": {
|
|
5390
6072
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5391
6073
|
const pair = ctx.ptys.get(req.n);
|
|
5392
6074
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
5393
6075
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
5394
|
-
return
|
|
6076
|
+
return json4(fd);
|
|
5395
6077
|
}
|
|
5396
6078
|
case "pty-number":
|
|
5397
|
-
return
|
|
6079
|
+
return json4(masterOf(ctx, req.fd).index);
|
|
5398
6080
|
case "pty-lock":
|
|
5399
6081
|
masterOf(ctx, req.fd).locked = req.lock;
|
|
5400
6082
|
return done3;
|
|
@@ -5406,7 +6088,7 @@ function ptySyscall(req, ctx) {
|
|
|
5406
6088
|
return done3;
|
|
5407
6089
|
}
|
|
5408
6090
|
case "pty-list":
|
|
5409
|
-
return
|
|
6091
|
+
return json4(ctx.ptys?.numbers() ?? []);
|
|
5410
6092
|
case "pty-packet":
|
|
5411
6093
|
masterOf(ctx, req.fd).packet = req.on;
|
|
5412
6094
|
return done3;
|
|
@@ -5484,7 +6166,7 @@ function socketAt(fds, fd) {
|
|
|
5484
6166
|
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
5485
6167
|
return file;
|
|
5486
6168
|
}
|
|
5487
|
-
var ok = (
|
|
6169
|
+
var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
5488
6170
|
async function socketSyscall(req, proc) {
|
|
5489
6171
|
const { fds, net } = proc;
|
|
5490
6172
|
switch (req.op) {
|
|
@@ -7448,8 +8130,8 @@ function umountCall(target, flags) {
|
|
|
7448
8130
|
var FSTAB_PATH = "/etc/fstab";
|
|
7449
8131
|
var FSTAB_RETRIES = [1e3, 4e3, 16e3];
|
|
7450
8132
|
var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
|
|
7451
|
-
function unescape(
|
|
7452
|
-
return
|
|
8133
|
+
function unescape(field2) {
|
|
8134
|
+
return field2.replace(
|
|
7453
8135
|
/\\([0-7]{3})/g,
|
|
7454
8136
|
(_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
|
|
7455
8137
|
);
|
|
@@ -8106,7 +8788,7 @@ function importedMemory(bytes) {
|
|
|
8106
8788
|
const count = u32();
|
|
8107
8789
|
for (let i = 0; i < count; i++) {
|
|
8108
8790
|
const module = name2();
|
|
8109
|
-
const
|
|
8791
|
+
const field2 = name2();
|
|
8110
8792
|
const kind = bytes[at++];
|
|
8111
8793
|
if (kind === 0) u32();
|
|
8112
8794
|
else if (kind === 1) {
|
|
@@ -8114,7 +8796,7 @@ function importedMemory(bytes) {
|
|
|
8114
8796
|
limits();
|
|
8115
8797
|
} else if (kind === 2) {
|
|
8116
8798
|
const l = limits();
|
|
8117
|
-
return { module, name:
|
|
8799
|
+
return { module, name: field2, ...l };
|
|
8118
8800
|
} else if (kind === 3) at += 2;
|
|
8119
8801
|
else if (kind === 4) {
|
|
8120
8802
|
at++;
|
|
@@ -8205,12 +8887,12 @@ function readImports(r, types) {
|
|
|
8205
8887
|
const foreign = {};
|
|
8206
8888
|
for (let i = r.u32(); i > 0; i--) {
|
|
8207
8889
|
const module = r.name();
|
|
8208
|
-
const
|
|
8890
|
+
const field2 = r.name();
|
|
8209
8891
|
const kind = r.byte();
|
|
8210
8892
|
if (kind !== 0) skipImport(r, kind);
|
|
8211
8893
|
else {
|
|
8212
8894
|
const type = types[r.u32()] ?? "other";
|
|
8213
|
-
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[
|
|
8895
|
+
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
|
|
8214
8896
|
}
|
|
8215
8897
|
}
|
|
8216
8898
|
return foreign;
|
|
@@ -8317,6 +8999,7 @@ var Launcher = class {
|
|
|
8317
8999
|
locks = new LockTable();
|
|
8318
9000
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
8319
9001
|
net = new LoopbackNet();
|
|
9002
|
+
cdp;
|
|
8320
9003
|
ca;
|
|
8321
9004
|
transport;
|
|
8322
9005
|
watchers = new FsWatchers();
|
|
@@ -8353,7 +9036,8 @@ var Launcher = class {
|
|
|
8353
9036
|
this.processMounts = options.processMounts ?? true;
|
|
8354
9037
|
this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
|
|
8355
9038
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
8356
|
-
this.
|
|
9039
|
+
this.cdp = new CdpHosts(options.cdp);
|
|
9040
|
+
this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
|
|
8357
9041
|
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
8358
9042
|
this.transport = options.transport ?? missingTransport();
|
|
8359
9043
|
enableNetwork(this.net, {
|
|
@@ -8361,6 +9045,7 @@ var Launcher = class {
|
|
|
8361
9045
|
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
8362
9046
|
ca: this.ca
|
|
8363
9047
|
});
|
|
9048
|
+
enableCdp(this.net, this.cdp);
|
|
8364
9049
|
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
8365
9050
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
8366
9051
|
this.catalog = void 0;
|
|
@@ -8894,40 +9579,106 @@ function valid(name2) {
|
|
|
8894
9579
|
throw new TypeError(`invalid name: ${name2}`);
|
|
8895
9580
|
}
|
|
8896
9581
|
}
|
|
9582
|
+
var CHUNK3 = 1024 * 1024;
|
|
8897
9583
|
function bytesOf(data) {
|
|
8898
9584
|
if (typeof data === "string") return new TextEncoder().encode(data);
|
|
8899
9585
|
if (data instanceof Blob) return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
|
|
8900
9586
|
if (ArrayBuffer.isView(data)) {
|
|
8901
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
9587
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
8902
9588
|
}
|
|
8903
|
-
return new Uint8Array(data)
|
|
9589
|
+
return new Uint8Array(data);
|
|
8904
9590
|
}
|
|
9591
|
+
var isParams = (data) => ["write", "seek", "truncate"].includes(data?.type) && !(data instanceof Blob);
|
|
9592
|
+
var Chunks = class {
|
|
9593
|
+
blobs;
|
|
9594
|
+
size;
|
|
9595
|
+
constructor(blobs = [], size = 0) {
|
|
9596
|
+
this.blobs = blobs;
|
|
9597
|
+
this.size = size;
|
|
9598
|
+
}
|
|
9599
|
+
pad(index, length2) {
|
|
9600
|
+
const blob = this.blobs[index] ?? new Blob([]);
|
|
9601
|
+
if (blob.size < length2)
|
|
9602
|
+
this.blobs[index] = new Blob([blob, new Uint8Array(length2 - blob.size)]);
|
|
9603
|
+
}
|
|
9604
|
+
grow(end) {
|
|
9605
|
+
if (end <= this.size) return;
|
|
9606
|
+
const last = Math.floor((end - 1) / CHUNK3);
|
|
9607
|
+
for (let i = 0; i < last; i++) this.pad(i, CHUNK3);
|
|
9608
|
+
this.pad(last, end - last * CHUNK3);
|
|
9609
|
+
this.size = end;
|
|
9610
|
+
}
|
|
9611
|
+
async write(at, bytes) {
|
|
9612
|
+
if (bytes.length === 0) return;
|
|
9613
|
+
const first = Math.floor(at / CHUNK3);
|
|
9614
|
+
for (let i = 0; i < first; i++) this.pad(i, CHUNK3);
|
|
9615
|
+
for (let done4 = 0; done4 < bytes.length; ) {
|
|
9616
|
+
const index = Math.floor((at + done4) / CHUNK3);
|
|
9617
|
+
const from = at + done4 - index * CHUNK3;
|
|
9618
|
+
const n = Math.min(CHUNK3 - from, bytes.length - done4);
|
|
9619
|
+
const view = bytes.subarray(done4, done4 + n);
|
|
9620
|
+
const part = view.buffer instanceof ArrayBuffer ? view : view.slice();
|
|
9621
|
+
const old = this.blobs[index];
|
|
9622
|
+
if (from === 0 && n >= (old?.size ?? 0)) this.blobs[index] = new Blob([part]);
|
|
9623
|
+
else {
|
|
9624
|
+
const merged = new Uint8Array(Math.max(old?.size ?? 0, from + n));
|
|
9625
|
+
if (old) merged.set(new Uint8Array(await old.arrayBuffer()));
|
|
9626
|
+
merged.set(part, from);
|
|
9627
|
+
this.blobs[index] = new Blob([merged]);
|
|
9628
|
+
}
|
|
9629
|
+
done4 += n;
|
|
9630
|
+
}
|
|
9631
|
+
this.size = Math.max(this.size, at + bytes.length);
|
|
9632
|
+
}
|
|
9633
|
+
truncate(size) {
|
|
9634
|
+
if (size >= this.size) {
|
|
9635
|
+
this.grow(size);
|
|
9636
|
+
return;
|
|
9637
|
+
}
|
|
9638
|
+
const keep = Math.ceil(size / CHUNK3);
|
|
9639
|
+
this.blobs.length = keep;
|
|
9640
|
+
if (keep > 0)
|
|
9641
|
+
this.blobs[keep - 1] = this.blobs[keep - 1].slice(0, size - (keep - 1) * CHUNK3);
|
|
9642
|
+
this.size = size;
|
|
9643
|
+
}
|
|
9644
|
+
};
|
|
8905
9645
|
var MemoryFile = class {
|
|
8906
9646
|
kind = "file";
|
|
8907
9647
|
name;
|
|
8908
9648
|
parent;
|
|
8909
|
-
|
|
9649
|
+
data = new Chunks();
|
|
8910
9650
|
lastModified = Date.now();
|
|
8911
9651
|
constructor(name2, parent) {
|
|
8912
9652
|
this.name = name2;
|
|
8913
9653
|
this.parent = parent;
|
|
8914
9654
|
}
|
|
8915
9655
|
async getFile() {
|
|
8916
|
-
return new File(
|
|
8917
|
-
}
|
|
8918
|
-
async createWritable() {
|
|
8919
|
-
const
|
|
9656
|
+
return new File(this.data.blobs, this.name, { lastModified: this.lastModified });
|
|
9657
|
+
}
|
|
9658
|
+
async createWritable({ keepExistingData = false } = {}) {
|
|
9659
|
+
const next = keepExistingData ? new Chunks([...this.data.blobs], this.data.size) : new Chunks();
|
|
9660
|
+
let position = 0;
|
|
9661
|
+
const put = async (data) => {
|
|
9662
|
+
const bytes = await bytesOf(data);
|
|
9663
|
+
await next.write(position, bytes);
|
|
9664
|
+
position += bytes.length;
|
|
9665
|
+
};
|
|
8920
9666
|
return {
|
|
8921
|
-
write: async (data) =>
|
|
9667
|
+
write: async (data) => {
|
|
9668
|
+
if (!isParams(data)) return put(data);
|
|
9669
|
+
if (data.type === "truncate") return next.truncate(data.size ?? 0);
|
|
9670
|
+
position = data.position ?? position;
|
|
9671
|
+
if (data.type === "write") await put(data.data);
|
|
9672
|
+
},
|
|
9673
|
+
seek: async (at) => {
|
|
9674
|
+
position = at;
|
|
9675
|
+
},
|
|
9676
|
+
truncate: async (size) => next.truncate(size),
|
|
8922
9677
|
close: async () => {
|
|
8923
|
-
|
|
8924
|
-
let at = 0;
|
|
8925
|
-
for (const chunk2 of chunks2) {
|
|
8926
|
-
out.set(chunk2, at);
|
|
8927
|
-
at += chunk2.length;
|
|
8928
|
-
}
|
|
8929
|
-
this.bytes = out;
|
|
9678
|
+
this.data = next;
|
|
8930
9679
|
this.lastModified = Date.now();
|
|
9680
|
+
},
|
|
9681
|
+
abort: async () => {
|
|
8931
9682
|
}
|
|
8932
9683
|
};
|
|
8933
9684
|
}
|
|
@@ -9020,6 +9771,9 @@ var MemoryDirectory = class _MemoryDirectory {
|
|
|
9020
9771
|
function memoryRoot() {
|
|
9021
9772
|
return new MemoryDirectory("", null);
|
|
9022
9773
|
}
|
|
9774
|
+
function isMemoryRoot(handle) {
|
|
9775
|
+
return handle instanceof MemoryDirectory;
|
|
9776
|
+
}
|
|
9023
9777
|
|
|
9024
9778
|
// src/kernel/net/remote-transport.ts
|
|
9025
9779
|
var TransportError = class extends Error {
|
|
@@ -9432,6 +10186,8 @@ var Channel = class {
|
|
|
9432
10186
|
failure;
|
|
9433
10187
|
pending = /* @__PURE__ */ new Map();
|
|
9434
10188
|
watching = /* @__PURE__ */ new Map();
|
|
10189
|
+
cdpHooks = /* @__PURE__ */ new Map();
|
|
10190
|
+
cdpOpen = /* @__PURE__ */ new Set();
|
|
9435
10191
|
protocol = PROTOCOL;
|
|
9436
10192
|
nextId = 0;
|
|
9437
10193
|
port;
|
|
@@ -9452,6 +10208,8 @@ var Channel = class {
|
|
|
9452
10208
|
this.failure = error;
|
|
9453
10209
|
this.greeted.reject(error);
|
|
9454
10210
|
this.remote?.fail(error);
|
|
10211
|
+
for (const close of [...this.cdpOpen]) close();
|
|
10212
|
+
this.cdpOpen.clear();
|
|
9455
10213
|
for (const call of this.pending.values()) call.reject(error);
|
|
9456
10214
|
this.pending.clear();
|
|
9457
10215
|
this.release();
|
|
@@ -9472,6 +10230,10 @@ var Channel = class {
|
|
|
9472
10230
|
return this.request(call).done;
|
|
9473
10231
|
}
|
|
9474
10232
|
receive(data) {
|
|
10233
|
+
if (data.cdpOpen && data.port) {
|
|
10234
|
+
this.openCdp(data);
|
|
10235
|
+
return;
|
|
10236
|
+
}
|
|
9475
10237
|
if (data.watch !== void 0) {
|
|
9476
10238
|
this.watching.get(data.watch)?.(data.change);
|
|
9477
10239
|
return;
|
|
@@ -9503,7 +10265,39 @@ var Channel = class {
|
|
|
9503
10265
|
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
9504
10266
|
else call.resolve(reply.result);
|
|
9505
10267
|
}
|
|
10268
|
+
openCdp({ cdpOpen: { registration, ...request }, port }) {
|
|
10269
|
+
const hook = registration === void 0 ? void 0 : this.cdpHooks.get(registration);
|
|
10270
|
+
if (!hook || this.failure) {
|
|
10271
|
+
port.postMessage({ error: "this client no longer serves CDP" });
|
|
10272
|
+
port.close();
|
|
10273
|
+
return;
|
|
10274
|
+
}
|
|
10275
|
+
const close = serveCdpPort(port, hook, request, () => this.cdpOpen.delete(close));
|
|
10276
|
+
this.cdpOpen.add(close);
|
|
10277
|
+
}
|
|
9506
10278
|
};
|
|
10279
|
+
async function serveCdpOn(channel, hook, options) {
|
|
10280
|
+
await since(channel, 5, "CDP serving", async () => {
|
|
10281
|
+
});
|
|
10282
|
+
const req = {
|
|
10283
|
+
op: "serve-cdp",
|
|
10284
|
+
...options.runtime ? { runtime: options.runtime } : {}
|
|
10285
|
+
};
|
|
10286
|
+
const { id, done: done4 } = channel.request(req);
|
|
10287
|
+
channel.cdpHooks.set(id, hook);
|
|
10288
|
+
try {
|
|
10289
|
+
await done4;
|
|
10290
|
+
} catch (error) {
|
|
10291
|
+
channel.cdpHooks.delete(id);
|
|
10292
|
+
throw error;
|
|
10293
|
+
}
|
|
10294
|
+
return {
|
|
10295
|
+
close: () => {
|
|
10296
|
+
if (!channel.cdpHooks.delete(id)) return;
|
|
10297
|
+
void channel.call({ op: "unserve-cdp", registration: id }).catch(() => void 0);
|
|
10298
|
+
}
|
|
10299
|
+
};
|
|
10300
|
+
}
|
|
9507
10301
|
async function dialOn(channel, { port, host }) {
|
|
9508
10302
|
const req = { op: "dial", port, ...host ? { host } : {} };
|
|
9509
10303
|
return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
|
|
@@ -9689,6 +10483,7 @@ async function attachKernel(port, options = {}) {
|
|
|
9689
10483
|
dial: (options2) => dialOn(channel, options2),
|
|
9690
10484
|
loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
|
|
9691
10485
|
fs: fsOn(channel),
|
|
10486
|
+
serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
|
|
9692
10487
|
async close(o = {}) {
|
|
9693
10488
|
if (channel.failure) return;
|
|
9694
10489
|
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
@@ -9750,7 +10545,7 @@ function textOf(on) {
|
|
|
9750
10545
|
}
|
|
9751
10546
|
async function createNodeKernel(options = {}) {
|
|
9752
10547
|
const root = options.root ?? memoryRoot();
|
|
9753
|
-
const fs = new OpfsFs(root);
|
|
10548
|
+
const fs = new OpfsFs(root, void 0, void 0, { ranged: isMemoryRoot(root) });
|
|
9754
10549
|
await fs.reconcile();
|
|
9755
10550
|
const live = /* @__PURE__ */ new Set();
|
|
9756
10551
|
const pending = /* @__PURE__ */ new Set();
|
|
@@ -9770,7 +10565,8 @@ async function createNodeKernel(options = {}) {
|
|
|
9770
10565
|
...options.env ? { env: options.env } : {},
|
|
9771
10566
|
...transport ? { transport } : {},
|
|
9772
10567
|
...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
|
|
9773
|
-
...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {}
|
|
10568
|
+
...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
|
|
10569
|
+
...options.cdp ? { cdp: options.cdp } : {}
|
|
9774
10570
|
});
|
|
9775
10571
|
await launcher.prepare();
|
|
9776
10572
|
const guard = (work) => new Promise((resolve, reject) => {
|
|
@@ -9850,6 +10646,7 @@ async function createNodeKernel(options = {}) {
|
|
|
9850
10646
|
for (const client of [...clients]) client.detach();
|
|
9851
10647
|
for (const reject of [...pending]) reject(new Error(TERMINATED));
|
|
9852
10648
|
for (const kill of [...live]) kill();
|
|
10649
|
+
launcher.cdp.close();
|
|
9853
10650
|
launcher.unmountAll();
|
|
9854
10651
|
}
|
|
9855
10652
|
};
|