@ai-ecoverse/slicc-kernel 1.22.0 → 1.23.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 -3
- 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 +663 -62
- 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 +1190 -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:
|
|
@@ -1817,124 +1949,823 @@ var OpfsFs = class {
|
|
|
1817
1949
|
}
|
|
1818
1950
|
};
|
|
1819
1951
|
|
|
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;
|
|
1952
|
+
// src/kernel/net/http1.ts
|
|
1953
|
+
var HttpError = class extends Error {
|
|
1954
|
+
status;
|
|
1955
|
+
constructor(status, message2) {
|
|
1956
|
+
super(message2);
|
|
1957
|
+
this.status = status;
|
|
1837
1958
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1959
|
+
};
|
|
1960
|
+
var CR = 13;
|
|
1961
|
+
var LF = 10;
|
|
1962
|
+
var READ_SIZE = 64 * 1024;
|
|
1963
|
+
var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
1964
|
+
var REASON = {
|
|
1965
|
+
100: "Continue",
|
|
1966
|
+
200: "OK",
|
|
1967
|
+
400: "Bad Request",
|
|
1968
|
+
403: "Forbidden",
|
|
1969
|
+
404: "Not Found",
|
|
1970
|
+
405: "Method Not Allowed",
|
|
1971
|
+
408: "Request Timeout",
|
|
1972
|
+
411: "Length Required",
|
|
1973
|
+
413: "Content Too Large",
|
|
1974
|
+
417: "Expectation Failed",
|
|
1975
|
+
421: "Misdirected Request",
|
|
1976
|
+
431: "Request Header Fields Too Large",
|
|
1977
|
+
501: "Not Implemented",
|
|
1978
|
+
502: "Bad Gateway",
|
|
1979
|
+
503: "Service Unavailable",
|
|
1980
|
+
504: "Gateway Timeout",
|
|
1981
|
+
505: "HTTP Version Not Supported"
|
|
1982
|
+
};
|
|
1983
|
+
var Incoming = class {
|
|
1984
|
+
source;
|
|
1985
|
+
buf = new Uint8Array(0);
|
|
1986
|
+
eof = false;
|
|
1987
|
+
constructor(source) {
|
|
1988
|
+
this.source = source;
|
|
1846
1989
|
}
|
|
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 };
|
|
1990
|
+
get buffered() {
|
|
1991
|
+
return this.buf.length;
|
|
1866
1992
|
}
|
|
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;
|
|
1993
|
+
async fill(signal) {
|
|
1994
|
+
if (this.eof) return false;
|
|
1995
|
+
const chunk2 = await this.source.read(READ_SIZE, signal);
|
|
1996
|
+
if (chunk2.length === 0) {
|
|
1997
|
+
this.eof = true;
|
|
1998
|
+
return false;
|
|
1896
1999
|
}
|
|
1897
|
-
const
|
|
1898
|
-
|
|
2000
|
+
const next = new Uint8Array(this.buf.length + chunk2.length);
|
|
2001
|
+
next.set(this.buf);
|
|
2002
|
+
next.set(chunk2, this.buf.length);
|
|
2003
|
+
this.buf = next;
|
|
2004
|
+
return true;
|
|
1899
2005
|
}
|
|
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;
|
|
2006
|
+
take(n) {
|
|
2007
|
+
const out = this.buf.slice(0, n);
|
|
2008
|
+
this.buf = this.buf.subarray(n);
|
|
2009
|
+
return out;
|
|
1908
2010
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2011
|
+
async some(max, signal) {
|
|
2012
|
+
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
2013
|
+
if (this.eof) return new Uint8Array(0);
|
|
2014
|
+
const chunk2 = await this.source.read(max, signal);
|
|
2015
|
+
if (chunk2.length === 0) this.eof = true;
|
|
2016
|
+
return chunk2;
|
|
2017
|
+
}
|
|
2018
|
+
async head(limit, signal) {
|
|
2019
|
+
let from = 0;
|
|
2020
|
+
for (; ; ) {
|
|
2021
|
+
const end = headEnd(this.buf, from);
|
|
2022
|
+
if (end > 0) {
|
|
2023
|
+
if (end > limit) throw new HttpError(431, "request head too large");
|
|
2024
|
+
return this.take(end);
|
|
2025
|
+
}
|
|
2026
|
+
if (this.buf.length > limit) throw new HttpError(431, "request head too large");
|
|
2027
|
+
from = Math.max(0, this.buf.length - 3);
|
|
2028
|
+
if (!await this.fill(signal)) {
|
|
2029
|
+
if (this.buf.length === 0) return null;
|
|
2030
|
+
throw new HttpError(400, "connection closed inside a request head");
|
|
2031
|
+
}
|
|
1919
2032
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
2033
|
+
}
|
|
2034
|
+
async line(limit, signal) {
|
|
2035
|
+
let from = 0;
|
|
2036
|
+
for (; ; ) {
|
|
2037
|
+
const at = this.buf.indexOf(LF, from);
|
|
2038
|
+
if (at >= 0) {
|
|
2039
|
+
const line = this.take(at + 1);
|
|
2040
|
+
const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
|
|
2041
|
+
return text;
|
|
2042
|
+
}
|
|
2043
|
+
if (this.buf.length > limit) throw new HttpError(400, "line too long");
|
|
2044
|
+
from = this.buf.length;
|
|
2045
|
+
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
1922
2046
|
}
|
|
1923
2047
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
const roots = [];
|
|
1928
|
-
const global = `${home}/global`;
|
|
1929
|
-
for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
|
|
1930
|
-
for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
|
|
1931
|
-
const dir = `${global}/${version}/${group}`;
|
|
1932
|
-
const st = await fs.lstat(dir).catch(() => void 0);
|
|
1933
|
-
if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
|
|
1934
|
-
roots.push(`${dir}/node_modules`);
|
|
2048
|
+
async exactly(n, signal) {
|
|
2049
|
+
while (this.buf.length < n) {
|
|
2050
|
+
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
1935
2051
|
}
|
|
2052
|
+
return this.take(n);
|
|
1936
2053
|
}
|
|
1937
|
-
|
|
2054
|
+
};
|
|
2055
|
+
function headEnd(buf, from) {
|
|
2056
|
+
for (let i = from; i < buf.length; i++) {
|
|
2057
|
+
if (buf[i] !== LF) continue;
|
|
2058
|
+
if (buf[i + 1] === LF) return i + 2;
|
|
2059
|
+
if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
|
|
2060
|
+
}
|
|
2061
|
+
return 0;
|
|
2062
|
+
}
|
|
2063
|
+
function latin1(bytes) {
|
|
2064
|
+
let out = "";
|
|
2065
|
+
for (let i = 0; i < bytes.length; i += 32768) {
|
|
2066
|
+
out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
2067
|
+
}
|
|
2068
|
+
return out;
|
|
2069
|
+
}
|
|
2070
|
+
function latin1Bytes(text) {
|
|
2071
|
+
const out = new Uint8Array(text.length);
|
|
2072
|
+
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
2073
|
+
return out;
|
|
2074
|
+
}
|
|
2075
|
+
function parseRequestHead(bytes) {
|
|
2076
|
+
const lines = latin1(bytes).split(/\r?\n/);
|
|
2077
|
+
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
2078
|
+
const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
|
|
2079
|
+
if (!match) throw new HttpError(400, "malformed request line");
|
|
2080
|
+
if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
|
|
2081
|
+
return {
|
|
2082
|
+
method: match[1],
|
|
2083
|
+
target: match[2],
|
|
2084
|
+
minor: Number(match[4]),
|
|
2085
|
+
headers: parseFields(lines)
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
function parseFields(lines) {
|
|
2089
|
+
const headers = [];
|
|
2090
|
+
for (const line of lines) {
|
|
2091
|
+
if (line === "") continue;
|
|
2092
|
+
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
2093
|
+
throw new HttpError(400, "obsolete line folding");
|
|
2094
|
+
}
|
|
2095
|
+
const colon = line.indexOf(":");
|
|
2096
|
+
const name2 = colon > 0 ? line.slice(0, colon) : "";
|
|
2097
|
+
if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
|
|
2098
|
+
headers.push([name2, line.slice(colon + 1).trim()]);
|
|
2099
|
+
}
|
|
2100
|
+
return headers;
|
|
2101
|
+
}
|
|
2102
|
+
function fieldValues(headers, name2) {
|
|
2103
|
+
const lower = name2.toLowerCase();
|
|
2104
|
+
return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
|
|
2105
|
+
}
|
|
2106
|
+
function fieldTokens(headers, name2) {
|
|
2107
|
+
return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
|
|
2108
|
+
}
|
|
2109
|
+
function requestFraming(headers) {
|
|
2110
|
+
const codings = fieldTokens(headers, "transfer-encoding");
|
|
2111
|
+
const lengths = fieldValues(headers, "content-length");
|
|
2112
|
+
if (codings.length > 0) {
|
|
2113
|
+
if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
|
|
2114
|
+
if (codings.length !== 1 || codings[0] !== "chunked") {
|
|
2115
|
+
throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
|
|
2116
|
+
}
|
|
2117
|
+
return { kind: "chunked" };
|
|
2118
|
+
}
|
|
2119
|
+
if (lengths.length === 0) return { kind: "none" };
|
|
2120
|
+
const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
|
|
2121
|
+
const [only] = distinct;
|
|
2122
|
+
if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
|
|
2123
|
+
throw new HttpError(400, "invalid Content-Length");
|
|
2124
|
+
}
|
|
2125
|
+
const length2 = Number(only);
|
|
2126
|
+
return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
|
|
2127
|
+
}
|
|
2128
|
+
var MAX_CHUNK_LINE = 4096;
|
|
2129
|
+
var MAX_TRAILERS = 64 * 1024;
|
|
2130
|
+
async function readBody(incoming, framing, cap, signal) {
|
|
2131
|
+
if (framing.kind === "none") return void 0;
|
|
2132
|
+
if (framing.kind === "length") {
|
|
2133
|
+
if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
2134
|
+
return incoming.exactly(framing.length, signal);
|
|
2135
|
+
}
|
|
2136
|
+
const chunks2 = [];
|
|
2137
|
+
let total = 0;
|
|
2138
|
+
for (; ; ) {
|
|
2139
|
+
const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
|
|
2140
|
+
const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
|
|
2141
|
+
if (!size) throw new HttpError(400, "malformed chunk size");
|
|
2142
|
+
const n = Number.parseInt(size[1], 16);
|
|
2143
|
+
if (n === 0) break;
|
|
2144
|
+
total += n;
|
|
2145
|
+
if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
2146
|
+
chunks2.push(await incoming.exactly(n, signal));
|
|
2147
|
+
if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
|
|
2148
|
+
}
|
|
2149
|
+
let trailers = 0;
|
|
2150
|
+
for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
|
|
2151
|
+
trailers += line.length;
|
|
2152
|
+
if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
|
|
2153
|
+
line = await incoming.line(MAX_TRAILERS, signal);
|
|
2154
|
+
}
|
|
2155
|
+
return concat(chunks2, total);
|
|
2156
|
+
}
|
|
2157
|
+
function concat(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
|
|
2158
|
+
const out = new Uint8Array(total);
|
|
2159
|
+
let at = 0;
|
|
2160
|
+
for (const chunk2 of chunks2) {
|
|
2161
|
+
out.set(chunk2, at);
|
|
2162
|
+
at += chunk2.length;
|
|
2163
|
+
}
|
|
2164
|
+
return out;
|
|
2165
|
+
}
|
|
2166
|
+
function safeValue(value) {
|
|
2167
|
+
return !/[\r\n\0]/.test(value);
|
|
2168
|
+
}
|
|
2169
|
+
function responseHead(status, reason, headers) {
|
|
2170
|
+
const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
|
|
2171
|
+
let head = `HTTP/1.1 ${status} ${phrase}\r
|
|
2172
|
+
`;
|
|
2173
|
+
for (const [name2, value] of headers) {
|
|
2174
|
+
if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
|
|
2175
|
+
`;
|
|
2176
|
+
}
|
|
2177
|
+
return latin1Bytes(`${head}\r
|
|
2178
|
+
`);
|
|
2179
|
+
}
|
|
2180
|
+
function chunk(bytes) {
|
|
2181
|
+
const size = latin1Bytes(`${bytes.length.toString(16)}\r
|
|
2182
|
+
`);
|
|
2183
|
+
const out = new Uint8Array(size.length + bytes.length + 2);
|
|
2184
|
+
out.set(size);
|
|
2185
|
+
out.set(bytes, size.length);
|
|
2186
|
+
out[out.length - 2] = CR;
|
|
2187
|
+
out[out.length - 1] = LF;
|
|
2188
|
+
return out;
|
|
2189
|
+
}
|
|
2190
|
+
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
2191
|
+
|
|
2192
|
+
// src/cdp/types.ts
|
|
2193
|
+
var CDP_PORT = 9222;
|
|
2194
|
+
var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
|
|
2195
|
+
|
|
2196
|
+
// src/cdp/websocket.ts
|
|
2197
|
+
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
2198
|
+
var MAX_MESSAGE = 256 * 1024 * 1024;
|
|
2199
|
+
var CLOSE = {
|
|
2200
|
+
normal: 1e3,
|
|
2201
|
+
protocol: 1002,
|
|
2202
|
+
unsupported: 1003,
|
|
2203
|
+
invalid: 1007,
|
|
2204
|
+
tooBig: 1009,
|
|
2205
|
+
error: 1011
|
|
2206
|
+
};
|
|
2207
|
+
var OP_TEXT = 1;
|
|
2208
|
+
var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
|
|
2209
|
+
var WsError = class extends Error {
|
|
2210
|
+
code;
|
|
2211
|
+
constructor(code, message2) {
|
|
2212
|
+
super(message2);
|
|
2213
|
+
this.code = code;
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
function field(headers, name2) {
|
|
2217
|
+
return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
|
|
2218
|
+
}
|
|
2219
|
+
async function acceptKey(headers) {
|
|
2220
|
+
const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
|
|
2221
|
+
if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
|
|
2222
|
+
throw new HttpError(400, "expected a WebSocket upgrade");
|
|
2223
|
+
}
|
|
2224
|
+
if (field(headers, "sec-websocket-version") !== "13") {
|
|
2225
|
+
throw new HttpError(400, "Sec-WebSocket-Version must be 13");
|
|
2226
|
+
}
|
|
2227
|
+
const key = field(headers, "sec-websocket-key") ?? "";
|
|
2228
|
+
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
|
|
2229
|
+
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
|
|
2230
|
+
return btoa(String.fromCharCode(...new Uint8Array(digest)));
|
|
2231
|
+
}
|
|
2232
|
+
function isUpgrade(headers) {
|
|
2233
|
+
return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
|
|
2234
|
+
}
|
|
2235
|
+
function frame(opcode, payload) {
|
|
2236
|
+
const n = payload.length;
|
|
2237
|
+
const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
|
|
2238
|
+
const out = new Uint8Array(size + n);
|
|
2239
|
+
const view = new DataView(out.buffer);
|
|
2240
|
+
out[0] = 128 | opcode;
|
|
2241
|
+
if (n < 126) out[1] = n;
|
|
2242
|
+
else if (n < 65536) {
|
|
2243
|
+
out[1] = 126;
|
|
2244
|
+
view.setUint16(2, n);
|
|
2245
|
+
} else {
|
|
2246
|
+
out[1] = 127;
|
|
2247
|
+
view.setBigUint64(2, BigInt(n));
|
|
2248
|
+
}
|
|
2249
|
+
out.set(payload, size);
|
|
2250
|
+
return out;
|
|
2251
|
+
}
|
|
2252
|
+
function closeFrame(code, reason = "") {
|
|
2253
|
+
let bytes = new TextEncoder().encode(reason);
|
|
2254
|
+
if (bytes.length > 123) bytes = bytes.subarray(0, 123);
|
|
2255
|
+
const payload = new Uint8Array(2 + bytes.length);
|
|
2256
|
+
new DataView(payload.buffer).setUint16(0, code);
|
|
2257
|
+
payload.set(bytes, 2);
|
|
2258
|
+
return frame(OP.close, payload);
|
|
2259
|
+
}
|
|
2260
|
+
function pongFrame(payload) {
|
|
2261
|
+
return frame(OP.pong, payload);
|
|
2262
|
+
}
|
|
2263
|
+
async function fill(incoming, out, signal) {
|
|
2264
|
+
for (let at = 0; at < out.length; ) {
|
|
2265
|
+
const piece = await incoming.some(out.length - at, signal);
|
|
2266
|
+
if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
|
|
2267
|
+
out.set(piece, at);
|
|
2268
|
+
at += piece.length;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
var fatal = new TextDecoder("utf-8", { fatal: true });
|
|
2272
|
+
function decode(bytes) {
|
|
2273
|
+
try {
|
|
2274
|
+
return fatal.decode(bytes);
|
|
2275
|
+
} catch {
|
|
2276
|
+
throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
var WsReader = class {
|
|
2280
|
+
incoming;
|
|
2281
|
+
max;
|
|
2282
|
+
parts = [];
|
|
2283
|
+
size = 0;
|
|
2284
|
+
continuing = false;
|
|
2285
|
+
constructor(incoming, max = MAX_MESSAGE) {
|
|
2286
|
+
this.incoming = incoming;
|
|
2287
|
+
this.max = max;
|
|
2288
|
+
}
|
|
2289
|
+
async next(signal) {
|
|
2290
|
+
for (; ; ) {
|
|
2291
|
+
const first = await this.incoming.some(1, signal);
|
|
2292
|
+
if (first.length === 0) return null;
|
|
2293
|
+
const head = new Uint8Array(2);
|
|
2294
|
+
head[0] = first[0];
|
|
2295
|
+
await fill(this.incoming, head.subarray(1), signal);
|
|
2296
|
+
const fin = (head[0] & 128) !== 0;
|
|
2297
|
+
const opcode = head[0] & 15;
|
|
2298
|
+
if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
|
|
2299
|
+
if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
|
|
2300
|
+
const length2 = await this.length(head[1] & 127, signal);
|
|
2301
|
+
const control = opcode >= 8;
|
|
2302
|
+
if (control && (length2 > 125 || !fin)) {
|
|
2303
|
+
throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
|
|
2304
|
+
}
|
|
2305
|
+
if (!control && this.size + length2 > this.max) {
|
|
2306
|
+
throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
2307
|
+
}
|
|
2308
|
+
const mask = new Uint8Array(4);
|
|
2309
|
+
await fill(this.incoming, mask, signal);
|
|
2310
|
+
const payload = new Uint8Array(length2);
|
|
2311
|
+
await fill(this.incoming, payload, signal);
|
|
2312
|
+
for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
2313
|
+
const event = this.take(opcode, fin, payload);
|
|
2314
|
+
if (event) return event;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
async length(short, signal) {
|
|
2318
|
+
if (short < 126) return short;
|
|
2319
|
+
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
2320
|
+
await fill(this.incoming, ext, signal);
|
|
2321
|
+
const view = new DataView(ext.buffer);
|
|
2322
|
+
if (short === 126) return view.getUint16(0);
|
|
2323
|
+
const long = view.getBigUint64(0);
|
|
2324
|
+
return long > BigInt(this.max) ? this.max + 1 : Number(long);
|
|
2325
|
+
}
|
|
2326
|
+
take(opcode, fin, payload) {
|
|
2327
|
+
switch (opcode) {
|
|
2328
|
+
case OP.ping:
|
|
2329
|
+
return { kind: "ping", payload };
|
|
2330
|
+
case OP.pong:
|
|
2331
|
+
return void 0;
|
|
2332
|
+
case OP.close: {
|
|
2333
|
+
if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
|
|
2334
|
+
const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
|
|
2335
|
+
return { kind: "close", code, reason: decode(payload.subarray(2)) };
|
|
2336
|
+
}
|
|
2337
|
+
case OP.text:
|
|
2338
|
+
case OP.continuation: {
|
|
2339
|
+
if (opcode === OP.continuation !== this.continuing) {
|
|
2340
|
+
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
2341
|
+
}
|
|
2342
|
+
this.parts.push(payload);
|
|
2343
|
+
this.size += payload.length;
|
|
2344
|
+
this.continuing = !fin;
|
|
2345
|
+
if (!fin) return void 0;
|
|
2346
|
+
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
2347
|
+
this.parts = [];
|
|
2348
|
+
this.size = 0;
|
|
2349
|
+
return { kind: "text", text: decode(whole) };
|
|
2350
|
+
}
|
|
2351
|
+
case OP.binary:
|
|
2352
|
+
throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
2353
|
+
default:
|
|
2354
|
+
throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
};
|
|
2358
|
+
function switchingHead(accept2) {
|
|
2359
|
+
return latin1Bytes(
|
|
2360
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
2361
|
+
Upgrade: websocket\r
|
|
2362
|
+
Connection: Upgrade\r
|
|
2363
|
+
Sec-WebSocket-Accept: ${accept2}\r
|
|
2364
|
+
\r
|
|
2365
|
+
`
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// src/cdp/facade.ts
|
|
2370
|
+
var MAX_HEAD = 64 * 1024;
|
|
2371
|
+
var LIST_TIMEOUT = 1e4;
|
|
2372
|
+
function respond(status, body, type = "text/plain; charset=utf-8") {
|
|
2373
|
+
const bytes = new TextEncoder().encode(body);
|
|
2374
|
+
const head = latin1Bytes(
|
|
2375
|
+
`HTTP/1.1 ${status} ${REASON[status]}\r
|
|
2376
|
+
Content-Type: ${type}\r
|
|
2377
|
+
Content-Length: ${bytes.length}\r
|
|
2378
|
+
Connection: close\r
|
|
2379
|
+
\r
|
|
2380
|
+
`
|
|
2381
|
+
);
|
|
2382
|
+
const out = new Uint8Array(head.length + bytes.length);
|
|
2383
|
+
out.set(head);
|
|
2384
|
+
out.set(bytes, head.length);
|
|
2385
|
+
return out;
|
|
2386
|
+
}
|
|
2387
|
+
function json(value) {
|
|
2388
|
+
return respond(200, `${JSON.stringify(value, null, 2)}
|
|
2389
|
+
`, "application/json; charset=UTF-8");
|
|
2390
|
+
}
|
|
2391
|
+
var CdpFacade = class {
|
|
2392
|
+
options;
|
|
2393
|
+
listener;
|
|
2394
|
+
stop = new AbortController();
|
|
2395
|
+
connections = /* @__PURE__ */ new Set();
|
|
2396
|
+
closed;
|
|
2397
|
+
constructor(options) {
|
|
2398
|
+
this.options = options;
|
|
2399
|
+
this.listener = options.net.listen({
|
|
2400
|
+
family: "inet",
|
|
2401
|
+
host: "127.0.0.1",
|
|
2402
|
+
port: options.port ?? CDP_PORT
|
|
2403
|
+
});
|
|
2404
|
+
this.closed = this.acceptLoop();
|
|
2405
|
+
}
|
|
2406
|
+
close() {
|
|
2407
|
+
if (this.stop.signal.aborted) return;
|
|
2408
|
+
this.stop.abort();
|
|
2409
|
+
this.listener.close();
|
|
2410
|
+
for (const conn of this.connections) conn.close();
|
|
2411
|
+
}
|
|
2412
|
+
async acceptLoop() {
|
|
2413
|
+
const served = /* @__PURE__ */ new Set();
|
|
2414
|
+
for (; ; ) {
|
|
2415
|
+
let conn;
|
|
2416
|
+
try {
|
|
2417
|
+
conn = await this.listener.accept(this.stop.signal);
|
|
2418
|
+
} catch {
|
|
2419
|
+
break;
|
|
2420
|
+
}
|
|
2421
|
+
const done4 = this.serve(conn).finally(() => served.delete(done4));
|
|
2422
|
+
served.add(done4);
|
|
2423
|
+
}
|
|
2424
|
+
await Promise.allSettled([...served]);
|
|
2425
|
+
}
|
|
2426
|
+
async serve(conn) {
|
|
2427
|
+
this.connections.add(conn);
|
|
2428
|
+
const incoming = new Incoming(conn);
|
|
2429
|
+
try {
|
|
2430
|
+
const head = await incoming.head(MAX_HEAD, this.stop.signal);
|
|
2431
|
+
if (head) await this.route(conn, incoming, parseRequestHead(head));
|
|
2432
|
+
} catch (e) {
|
|
2433
|
+
if (e instanceof HttpError) await this.write(conn, respond(e.status, `${e.message}
|
|
2434
|
+
`));
|
|
2435
|
+
} finally {
|
|
2436
|
+
this.connections.delete(conn);
|
|
2437
|
+
conn.close();
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
async write(conn, bytes) {
|
|
2441
|
+
try {
|
|
2442
|
+
await conn.write(bytes, this.stop.signal);
|
|
2443
|
+
} catch {
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
async route(conn, incoming, req) {
|
|
2447
|
+
const url = new URL(req.target, "http://127.0.0.1");
|
|
2448
|
+
const runtime = url.searchParams.get("runtime") ?? void 0;
|
|
2449
|
+
const host = req.headers.find(([n]) => n.toLowerCase() === "host")?.[1] ?? "127.0.0.1";
|
|
2450
|
+
if (req.method !== "GET") throw new HttpError(405, `${req.method} is not supported here`);
|
|
2451
|
+
const { hosts } = this.options;
|
|
2452
|
+
if (url.pathname === hosts.path && isUpgrade(req.headers)) {
|
|
2453
|
+
return this.upgrade(conn, incoming, req, runtime);
|
|
2454
|
+
}
|
|
2455
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
2456
|
+
if (path !== "/json/version" && path !== "/json/list" && path !== "/json") {
|
|
2457
|
+
throw new HttpError(404, "not found: try /json/version");
|
|
2458
|
+
}
|
|
2459
|
+
if (!this.options.hosts.available(runtime)) {
|
|
2460
|
+
throw new HttpError(503, NO_CDP_HOST);
|
|
2461
|
+
}
|
|
2462
|
+
if (path === "/json/version") {
|
|
2463
|
+
await this.write(
|
|
2464
|
+
conn,
|
|
2465
|
+
json({
|
|
2466
|
+
Browser: "slicc-kernel",
|
|
2467
|
+
"Protocol-Version": "1.3",
|
|
2468
|
+
webSocketDebuggerUrl: `ws://${host}${hosts.path}${url.search}`
|
|
2469
|
+
})
|
|
2470
|
+
);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
await this.write(conn, json(await this.list(runtime)));
|
|
2474
|
+
}
|
|
2475
|
+
async list(runtime) {
|
|
2476
|
+
const conn = await this.host(runtime);
|
|
2477
|
+
try {
|
|
2478
|
+
const reply = await new Promise(
|
|
2479
|
+
(resolve, reject) => {
|
|
2480
|
+
const timer = setTimeout(
|
|
2481
|
+
() => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
|
|
2482
|
+
this.options.listTimeout ?? LIST_TIMEOUT
|
|
2483
|
+
);
|
|
2484
|
+
conn.onclose = (reason) => {
|
|
2485
|
+
clearTimeout(timer);
|
|
2486
|
+
reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
|
|
2487
|
+
};
|
|
2488
|
+
conn.onmessage = (message2) => {
|
|
2489
|
+
const parsed = JSON.parse(message2);
|
|
2490
|
+
if (parsed.id !== 1) return;
|
|
2491
|
+
clearTimeout(timer);
|
|
2492
|
+
resolve(parsed);
|
|
2493
|
+
};
|
|
2494
|
+
conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
|
|
2495
|
+
}
|
|
2496
|
+
);
|
|
2497
|
+
if (!reply.result) {
|
|
2498
|
+
throw new HttpError(502, `Target.getTargets failed: ${JSON.stringify(reply.error)}`);
|
|
2499
|
+
}
|
|
2500
|
+
return (reply.result.targetInfos ?? []).map((t) => ({
|
|
2501
|
+
description: "",
|
|
2502
|
+
id: t.targetId,
|
|
2503
|
+
title: t.title,
|
|
2504
|
+
type: t.type,
|
|
2505
|
+
url: t.url
|
|
2506
|
+
}));
|
|
2507
|
+
} finally {
|
|
2508
|
+
conn.close();
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
host(runtime) {
|
|
2512
|
+
return this.options.hosts.connect(runtime ? { runtime } : {});
|
|
2513
|
+
}
|
|
2514
|
+
async upgrade(conn, incoming, req, runtime) {
|
|
2515
|
+
const accept2 = await acceptKey(req.headers);
|
|
2516
|
+
const host = await this.host(runtime);
|
|
2517
|
+
await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
var Relay = class {
|
|
2521
|
+
writes = Promise.resolve();
|
|
2522
|
+
ended = false;
|
|
2523
|
+
conn;
|
|
2524
|
+
incoming;
|
|
2525
|
+
host;
|
|
2526
|
+
max;
|
|
2527
|
+
stop;
|
|
2528
|
+
constructor(conn, incoming, host, max, stop) {
|
|
2529
|
+
this.conn = conn;
|
|
2530
|
+
this.incoming = incoming;
|
|
2531
|
+
this.host = host;
|
|
2532
|
+
this.max = max;
|
|
2533
|
+
this.stop = stop;
|
|
2534
|
+
}
|
|
2535
|
+
send(bytes) {
|
|
2536
|
+
this.writes = this.writes.then(() => this.conn.write(bytes, this.stop)).catch(() => void 0);
|
|
2537
|
+
return this.writes;
|
|
2538
|
+
}
|
|
2539
|
+
end(code, reason) {
|
|
2540
|
+
if (this.ended) return this.writes;
|
|
2541
|
+
this.ended = true;
|
|
2542
|
+
return this.send(closeFrame(code, reason));
|
|
2543
|
+
}
|
|
2544
|
+
async start(head) {
|
|
2545
|
+
await this.send(head);
|
|
2546
|
+
const hostGone = new Promise((resolve) => {
|
|
2547
|
+
this.host.onclose = (reason) => {
|
|
2548
|
+
void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
|
|
2549
|
+
};
|
|
2550
|
+
});
|
|
2551
|
+
this.host.onmessage = (message2) => {
|
|
2552
|
+
if (this.ended) return;
|
|
2553
|
+
const bytes = new TextEncoder().encode(message2);
|
|
2554
|
+
if (bytes.length > this.max)
|
|
2555
|
+
void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
2556
|
+
else void this.send(frame(OP_TEXT, bytes));
|
|
2557
|
+
};
|
|
2558
|
+
await Promise.race([this.pump(), hostGone]);
|
|
2559
|
+
await this.writes;
|
|
2560
|
+
}
|
|
2561
|
+
async pump() {
|
|
2562
|
+
const reader = new WsReader(this.incoming, this.max);
|
|
2563
|
+
try {
|
|
2564
|
+
for (; ; ) {
|
|
2565
|
+
const event = await reader.next(this.stop);
|
|
2566
|
+
if (!event) return;
|
|
2567
|
+
if (event.kind === "text") this.host.send(event.text);
|
|
2568
|
+
else if (event.kind === "ping") void this.send(pongFrame(event.payload));
|
|
2569
|
+
else {
|
|
2570
|
+
await this.end(event.code === 1005 ? CLOSE.normal : event.code, "");
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
} catch (e) {
|
|
2575
|
+
if (e instanceof WsError) await this.end(e.code, e.message);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
function enableCdp(net, hosts) {
|
|
2580
|
+
let facade;
|
|
2581
|
+
net.kernelOnly(CDP_PORT, true);
|
|
2582
|
+
net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
|
|
2583
|
+
const started = new CdpFacade({ net, hosts });
|
|
2584
|
+
facade = started;
|
|
2585
|
+
void started.closed.then(() => {
|
|
2586
|
+
if (facade === started) facade = void 0;
|
|
2587
|
+
});
|
|
2588
|
+
});
|
|
2589
|
+
return () => facade;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// src/cdp/hosts.ts
|
|
2593
|
+
var CdpHosts = class {
|
|
2594
|
+
embedder;
|
|
2595
|
+
clients = [];
|
|
2596
|
+
closed = false;
|
|
2597
|
+
path = `/devtools/browser/${crypto.randomUUID()}`;
|
|
2598
|
+
url = `ws://127.0.0.1:${CDP_PORT}${this.path}`;
|
|
2599
|
+
constructor(hook) {
|
|
2600
|
+
this.embedder = hook ? { hook, runtime: void 0, open: /* @__PURE__ */ new Set() } : void 0;
|
|
2601
|
+
}
|
|
2602
|
+
register(hook, runtime) {
|
|
2603
|
+
const entry = { hook, runtime, open: /* @__PURE__ */ new Set() };
|
|
2604
|
+
this.clients.push(entry);
|
|
2605
|
+
return () => {
|
|
2606
|
+
const at = this.clients.indexOf(entry);
|
|
2607
|
+
if (at >= 0) this.clients.splice(at, 1);
|
|
2608
|
+
closeAll(entry);
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
pick(runtime) {
|
|
2612
|
+
if (this.closed) return void 0;
|
|
2613
|
+
const named = runtime ? this.clients.findLast((c) => c.runtime === runtime) : void 0;
|
|
2614
|
+
return named ?? this.embedder ?? this.clients.at(-1);
|
|
2615
|
+
}
|
|
2616
|
+
available(runtime) {
|
|
2617
|
+
return this.pick(runtime) !== void 0;
|
|
2618
|
+
}
|
|
2619
|
+
async connect(request) {
|
|
2620
|
+
const entry = this.pick(request.runtime);
|
|
2621
|
+
if (!entry) throw new HttpError(503, NO_CDP_HOST);
|
|
2622
|
+
let conn;
|
|
2623
|
+
try {
|
|
2624
|
+
conn = await entry.hook(request.runtime ? { runtime: request.runtime } : {});
|
|
2625
|
+
} catch (e) {
|
|
2626
|
+
throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
|
|
2627
|
+
}
|
|
2628
|
+
if (this.closed || !(entry === this.embedder || this.clients.includes(entry))) {
|
|
2629
|
+
conn.close();
|
|
2630
|
+
throw new HttpError(503, NO_CDP_HOST);
|
|
2631
|
+
}
|
|
2632
|
+
entry.open.add(conn);
|
|
2633
|
+
const close = conn.close.bind(conn);
|
|
2634
|
+
conn.close = () => {
|
|
2635
|
+
if (entry.open.delete(conn)) close();
|
|
2636
|
+
};
|
|
2637
|
+
return conn;
|
|
2638
|
+
}
|
|
2639
|
+
close() {
|
|
2640
|
+
this.closed = true;
|
|
2641
|
+
for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
function closeAll(entry) {
|
|
2645
|
+
for (const conn of [...entry.open]) {
|
|
2646
|
+
conn.close();
|
|
2647
|
+
conn.onclose?.("the CDP host is gone");
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// src/commands.ts
|
|
2652
|
+
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
2653
|
+
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2654
|
+
var PACKAGE = /\$\{package\}/g;
|
|
2655
|
+
function inside(pkg, rel2) {
|
|
2656
|
+
if (typeof rel2 !== "string" || rel2 === "") return void 0;
|
|
2657
|
+
const clean = rel2.replace(/^\.\//, "");
|
|
2658
|
+
if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
|
|
2659
|
+
return `${pkg}/${clean}`;
|
|
2660
|
+
}
|
|
2661
|
+
function envOf(pkg, raw) {
|
|
2662
|
+
const env = {};
|
|
2663
|
+
if (!raw || typeof raw !== "object") return env;
|
|
2664
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2665
|
+
if (!ENV_KEY.test(key)) continue;
|
|
2666
|
+
if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
|
|
2667
|
+
else if (value === null) env[key] = null;
|
|
2668
|
+
}
|
|
2669
|
+
return env;
|
|
2670
|
+
}
|
|
2671
|
+
function envFields(merged) {
|
|
2672
|
+
const env = {};
|
|
2673
|
+
const unset = [];
|
|
2674
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
2675
|
+
if (value === null) unset.push(key);
|
|
2676
|
+
else env[key] = value;
|
|
2677
|
+
}
|
|
2678
|
+
return {
|
|
2679
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
2680
|
+
...unset.length > 0 ? { unset } : {}
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
function argsOf(pkg, raw) {
|
|
2684
|
+
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
2685
|
+
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
2686
|
+
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
2687
|
+
}
|
|
2688
|
+
function abiOf(raw, fallback) {
|
|
2689
|
+
const abi = raw ?? fallback;
|
|
2690
|
+
return abi === "emscripten" || abi === "wasi" ? abi : void 0;
|
|
2691
|
+
}
|
|
2692
|
+
function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
2693
|
+
const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
|
|
2694
|
+
const script = inside(pkg, raw.script);
|
|
2695
|
+
if (script) {
|
|
2696
|
+
return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
|
|
2697
|
+
}
|
|
2698
|
+
const abi = abiOf(raw.abi, packageAbi);
|
|
2699
|
+
const wasm = inside(pkg, raw.wasm);
|
|
2700
|
+
const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
|
|
2701
|
+
if (!abi || !glue || !wasm) return void 0;
|
|
2702
|
+
const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
|
|
2703
|
+
const args = argsOf(pkg, raw.args);
|
|
2704
|
+
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
2705
|
+
return {
|
|
2706
|
+
name: name2,
|
|
2707
|
+
abi,
|
|
2708
|
+
glue,
|
|
2709
|
+
wasm,
|
|
2710
|
+
argv0,
|
|
2711
|
+
...args ? { args } : {},
|
|
2712
|
+
...withEnv,
|
|
2713
|
+
...imports ? { imports } : {}
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
function commandsOf(pkg, manifest) {
|
|
2717
|
+
const slicc = manifest.slicc;
|
|
2718
|
+
if (!slicc || typeof slicc !== "object") return [];
|
|
2719
|
+
const packageAbi = abiOf(slicc.abi, "emscripten");
|
|
2720
|
+
const entries = slicc.commands;
|
|
2721
|
+
if (!packageAbi || !entries || typeof entries !== "object") return [];
|
|
2722
|
+
const shared = envOf(pkg, slicc.env);
|
|
2723
|
+
const out = [];
|
|
2724
|
+
for (const [name2, raw] of Object.entries(entries)) {
|
|
2725
|
+
if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
|
|
2726
|
+
continue;
|
|
2727
|
+
}
|
|
2728
|
+
const command = commandOf(pkg, name2, raw, packageAbi, shared);
|
|
2729
|
+
if (command) out.push(command);
|
|
2730
|
+
}
|
|
2731
|
+
return out;
|
|
2732
|
+
}
|
|
2733
|
+
async function withPackagePaths(fs, pkg, command) {
|
|
2734
|
+
if (!command.env) return command;
|
|
2735
|
+
const env = {};
|
|
2736
|
+
for (const [key, value] of Object.entries(command.env)) {
|
|
2737
|
+
const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
|
|
2738
|
+
env[key] = path && await fs.exists(path) ? path : value;
|
|
2739
|
+
}
|
|
2740
|
+
return { ...command, env };
|
|
2741
|
+
}
|
|
2742
|
+
async function packages(fs, modules) {
|
|
2743
|
+
const names = await fs.readdir(modules).catch(() => []);
|
|
2744
|
+
const dirs = [];
|
|
2745
|
+
for (const name2 of names) {
|
|
2746
|
+
if (name2.startsWith(".")) continue;
|
|
2747
|
+
if (!name2.startsWith("@")) {
|
|
2748
|
+
dirs.push(`${modules}/${name2}`);
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
|
|
2752
|
+
dirs.push(`${modules}/${name2}/${inner}`);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
return dirs;
|
|
2756
|
+
}
|
|
2757
|
+
async function pnpmGlobalRoots(fs, home) {
|
|
2758
|
+
const roots = [];
|
|
2759
|
+
const global = `${home}/global`;
|
|
2760
|
+
for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
|
|
2761
|
+
for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
|
|
2762
|
+
const dir = `${global}/${version}/${group}`;
|
|
2763
|
+
const st = await fs.lstat(dir).catch(() => void 0);
|
|
2764
|
+
if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
|
|
2765
|
+
roots.push(`${dir}/node_modules`);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
return roots;
|
|
1938
2769
|
}
|
|
1939
2770
|
async function packagesIn(fs, roots) {
|
|
1940
2771
|
const dirs = [];
|
|
@@ -2203,7 +3034,7 @@ var CONTINUED_STATUS = 65535;
|
|
|
2203
3034
|
function waitStatus(code, termsig) {
|
|
2204
3035
|
return termsig ? termsig & 127 : (code & 255) << 8;
|
|
2205
3036
|
}
|
|
2206
|
-
function
|
|
3037
|
+
function concat2(chunks2) {
|
|
2207
3038
|
const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
|
|
2208
3039
|
let offset = 0;
|
|
2209
3040
|
for (const chunk2 of chunks2) {
|
|
@@ -2360,7 +3191,7 @@ var ChildTable = class {
|
|
|
2360
3191
|
if (!slots || !chunks2) return new Uint8Array(0);
|
|
2361
3192
|
slots.delete(slot);
|
|
2362
3193
|
if (slots.size === 0) this.leftovers.delete(pid);
|
|
2363
|
-
return
|
|
3194
|
+
return concat2(chunks2);
|
|
2364
3195
|
}
|
|
2365
3196
|
};
|
|
2366
3197
|
|
|
@@ -2528,7 +3359,7 @@ function statOf(attr, path, mount) {
|
|
|
2528
3359
|
...mount.caps.ranges ? { ranged: true } : {}
|
|
2529
3360
|
};
|
|
2530
3361
|
}
|
|
2531
|
-
function
|
|
3362
|
+
function concat3(chunks2, total) {
|
|
2532
3363
|
if (chunks2.length === 1 && chunks2[0]?.length === total) return chunks2[0];
|
|
2533
3364
|
const out = new Uint8Array(total);
|
|
2534
3365
|
let at = 0;
|
|
@@ -2557,6 +3388,7 @@ var MountTable = class {
|
|
|
2557
3388
|
mounts = /* @__PURE__ */ new Map();
|
|
2558
3389
|
devices = 0;
|
|
2559
3390
|
pending = /* @__PURE__ */ new Set();
|
|
3391
|
+
notes = /* @__PURE__ */ new Map();
|
|
2560
3392
|
deps;
|
|
2561
3393
|
constructor(deps) {
|
|
2562
3394
|
this.deps = deps;
|
|
@@ -2565,7 +3397,7 @@ var MountTable = class {
|
|
|
2565
3397
|
return this.mounts.get(target);
|
|
2566
3398
|
}
|
|
2567
3399
|
list() {
|
|
2568
|
-
|
|
3400
|
+
const mounted = [...this.mounts.values()].map(
|
|
2569
3401
|
({ type, source, target, options, state, error, present }) => ({
|
|
2570
3402
|
type,
|
|
2571
3403
|
source,
|
|
@@ -2575,6 +3407,19 @@ var MountTable = class {
|
|
|
2575
3407
|
...error ? { error } : {}
|
|
2576
3408
|
})
|
|
2577
3409
|
);
|
|
3410
|
+
const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
|
|
3411
|
+
return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
|
|
3412
|
+
}
|
|
3413
|
+
note(spec, state, error) {
|
|
3414
|
+
const target = normalizePath(spec.target);
|
|
3415
|
+
this.notes.set(target, {
|
|
3416
|
+
type: spec.type,
|
|
3417
|
+
source: spec.source,
|
|
3418
|
+
target,
|
|
3419
|
+
options: { ...spec.options },
|
|
3420
|
+
state,
|
|
3421
|
+
...error ? { error } : {}
|
|
3422
|
+
});
|
|
2578
3423
|
}
|
|
2579
3424
|
locate(path) {
|
|
2580
3425
|
let best;
|
|
@@ -2651,13 +3496,19 @@ var MountTable = class {
|
|
|
2651
3496
|
};
|
|
2652
3497
|
mounted = mount;
|
|
2653
3498
|
this.mounts.set(target, mount);
|
|
3499
|
+
this.notes.delete(target);
|
|
2654
3500
|
return this.list().find((m) => m.target === target);
|
|
2655
3501
|
}
|
|
3502
|
+
unnote(target) {
|
|
3503
|
+
this.notes.delete(normalizePath(target));
|
|
3504
|
+
}
|
|
2656
3505
|
umount(target, detach = false) {
|
|
2657
3506
|
const at = normalizePath(target);
|
|
2658
3507
|
const mount = this.mounts.get(at);
|
|
3508
|
+
if (!mount && this.notes.delete(at)) return;
|
|
2659
3509
|
if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
|
|
2660
3510
|
if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
|
|
3511
|
+
this.notes.delete(at);
|
|
2661
3512
|
this.mounts.delete(at);
|
|
2662
3513
|
mount.conn.close();
|
|
2663
3514
|
mount.dispose();
|
|
@@ -2803,7 +3654,7 @@ var MountFs = class {
|
|
|
2803
3654
|
} finally {
|
|
2804
3655
|
await mount.conn.call({ op: "release", fh });
|
|
2805
3656
|
}
|
|
2806
|
-
return
|
|
3657
|
+
return concat3(chunks2, total);
|
|
2807
3658
|
}
|
|
2808
3659
|
async writeFile(path, content) {
|
|
2809
3660
|
const real = await this.follow(path, 0, true);
|
|
@@ -2867,7 +3718,7 @@ var MountFs = class {
|
|
|
2867
3718
|
} finally {
|
|
2868
3719
|
await mount.conn.call({ op: "release", fh });
|
|
2869
3720
|
}
|
|
2870
|
-
return
|
|
3721
|
+
return concat3(chunks2, total);
|
|
2871
3722
|
}
|
|
2872
3723
|
fits(mount, real, end) {
|
|
2873
3724
|
if (mount.maxFile > 0 && end > mount.maxFile) {
|
|
@@ -3120,7 +3971,7 @@ async function storageUsage() {
|
|
|
3120
3971
|
return { quota: estimate.quota, usage: estimate.usage ?? 0 };
|
|
3121
3972
|
}
|
|
3122
3973
|
var done = { ok: true, kind: "void" };
|
|
3123
|
-
function
|
|
3974
|
+
function json2(value) {
|
|
3124
3975
|
return { ok: true, kind: "json", json: value };
|
|
3125
3976
|
}
|
|
3126
3977
|
async function run(entry, path, req) {
|
|
@@ -3132,15 +3983,15 @@ async function run(entry, path, req) {
|
|
|
3132
3983
|
await fs.writeFile(path, req.body ?? new Uint8Array(0));
|
|
3133
3984
|
return done;
|
|
3134
3985
|
case "exists":
|
|
3135
|
-
return
|
|
3986
|
+
return json2(await fs.exists(path));
|
|
3136
3987
|
case "stat":
|
|
3137
|
-
return
|
|
3988
|
+
return json2(statJson(await fs.stat(path)));
|
|
3138
3989
|
case "lstat":
|
|
3139
|
-
return
|
|
3990
|
+
return json2(statJson(await fs.lstat(path)));
|
|
3140
3991
|
case "readdir":
|
|
3141
|
-
return
|
|
3992
|
+
return json2(await fs.readdir(path));
|
|
3142
3993
|
case "readdir-stat":
|
|
3143
|
-
return
|
|
3994
|
+
return json2(await readdirStat(fs, path));
|
|
3144
3995
|
case "mkdir":
|
|
3145
3996
|
await fs.mkdir(path, { recursive: true });
|
|
3146
3997
|
return done;
|
|
@@ -3166,7 +4017,7 @@ async function run(entry, path, req) {
|
|
|
3166
4017
|
await fs.symlink(req.arg2 ?? "", path);
|
|
3167
4018
|
return done;
|
|
3168
4019
|
case "readlink":
|
|
3169
|
-
return
|
|
4020
|
+
return json2(await fs.readlink(path));
|
|
3170
4021
|
case "chmod":
|
|
3171
4022
|
await fs.chmod(path, req.mode ?? 0);
|
|
3172
4023
|
return done;
|
|
@@ -3197,7 +4048,7 @@ async function dispatchSyncFs(req) {
|
|
|
3197
4048
|
const { fs, cwd } = entry;
|
|
3198
4049
|
try {
|
|
3199
4050
|
const path = fs.resolvePath(cwd, req.path);
|
|
3200
|
-
if (req.op === "statfs") return
|
|
4051
|
+
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
3201
4052
|
if (req.op === "hold") {
|
|
3202
4053
|
entry.hold?.(path, req.mode === 1);
|
|
3203
4054
|
return done;
|
|
@@ -3370,7 +4221,7 @@ var HOST_OPS = [
|
|
|
3370
4221
|
"async-cancel",
|
|
3371
4222
|
"async-close"
|
|
3372
4223
|
];
|
|
3373
|
-
var
|
|
4224
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3374
4225
|
var done2 = { ok: true, kind: "void" };
|
|
3375
4226
|
var LockTable = class {
|
|
3376
4227
|
held = /* @__PURE__ */ new Map();
|
|
@@ -3411,361 +4262,124 @@ var AsyncOps = class {
|
|
|
3411
4262
|
}
|
|
3412
4263
|
submit(req) {
|
|
3413
4264
|
const id = this.hold();
|
|
3414
|
-
this.run(req).then(
|
|
3415
|
-
(result) => this.complete(id, result),
|
|
3416
|
-
(err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
|
|
3417
|
-
);
|
|
3418
|
-
return id;
|
|
3419
|
-
}
|
|
3420
|
-
resolve(value) {
|
|
3421
|
-
const id = this.hold();
|
|
3422
|
-
this.complete(id, json2(value));
|
|
3423
|
-
return id;
|
|
3424
|
-
}
|
|
3425
|
-
hold() {
|
|
3426
|
-
const id = this.next++;
|
|
3427
|
-
this.results.set(id, void 0);
|
|
3428
|
-
return id;
|
|
3429
|
-
}
|
|
3430
|
-
take(id) {
|
|
3431
|
-
if (!this.results.has(id)) throw new KernelError("EINVAL");
|
|
3432
|
-
const result = this.results.get(id);
|
|
3433
|
-
if (!result) throw new KernelError("EAGAIN");
|
|
3434
|
-
this.cancel(id);
|
|
3435
|
-
return result;
|
|
3436
|
-
}
|
|
3437
|
-
cancel(id) {
|
|
3438
|
-
this.results.delete(id);
|
|
3439
|
-
const at = this.ready.indexOf(id);
|
|
3440
|
-
if (at >= 0) this.ready.splice(at, 1);
|
|
3441
|
-
}
|
|
3442
|
-
async wait(timeoutMs, signal) {
|
|
3443
|
-
for (; ; ) {
|
|
3444
|
-
if (this.closed) return -1;
|
|
3445
|
-
const id = this.ready.shift();
|
|
3446
|
-
if (id !== void 0) return id;
|
|
3447
|
-
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
3448
|
-
const woke = await this.sleep(timeoutMs, signal);
|
|
3449
|
-
if (!woke) return 0;
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
close() {
|
|
3453
|
-
this.closed = true;
|
|
3454
|
-
this.wake();
|
|
3455
|
-
}
|
|
3456
|
-
sleep(timeoutMs, signal) {
|
|
3457
|
-
return new Promise((resolve, reject) => {
|
|
3458
|
-
let timer;
|
|
3459
|
-
const cleanup = () => {
|
|
3460
|
-
clearTimeout(timer);
|
|
3461
|
-
this.waiters.delete(wake);
|
|
3462
|
-
signal.removeEventListener("abort", abort);
|
|
3463
|
-
};
|
|
3464
|
-
const wake = () => {
|
|
3465
|
-
cleanup();
|
|
3466
|
-
resolve(true);
|
|
3467
|
-
};
|
|
3468
|
-
const abort = () => {
|
|
3469
|
-
cleanup();
|
|
3470
|
-
reject(new KernelError("EINTR"));
|
|
3471
|
-
};
|
|
3472
|
-
this.waiters.add(wake);
|
|
3473
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
3474
|
-
if (timeoutMs !== void 0) {
|
|
3475
|
-
timer = setTimeout(() => {
|
|
3476
|
-
cleanup();
|
|
3477
|
-
resolve(false);
|
|
3478
|
-
}, timeoutMs);
|
|
3479
|
-
}
|
|
3480
|
-
});
|
|
3481
|
-
}
|
|
3482
|
-
complete(id, result) {
|
|
3483
|
-
if (!this.results.has(id)) return;
|
|
3484
|
-
this.results.set(id, result);
|
|
3485
|
-
this.ready.push(id);
|
|
3486
|
-
this.wake();
|
|
3487
|
-
}
|
|
3488
|
-
wake() {
|
|
3489
|
-
for (const wake of [...this.waiters]) wake();
|
|
3490
|
-
}
|
|
3491
|
-
};
|
|
3492
|
-
async function hostSyscall(req, ctx) {
|
|
3493
|
-
switch (req.op) {
|
|
3494
|
-
case "lock":
|
|
3495
|
-
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
3496
|
-
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
3497
|
-
throw new KernelError("EAGAIN");
|
|
3498
|
-
return done2;
|
|
3499
|
-
case "unlock":
|
|
3500
|
-
ctx.locks?.unlock(ctx.pid, req.path);
|
|
3501
|
-
return done2;
|
|
3502
|
-
case "async-submit":
|
|
3503
|
-
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
3504
|
-
return json2(ctx.ops.submit(req.req));
|
|
3505
|
-
case "async-wait":
|
|
3506
|
-
return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
3507
|
-
case "async-take":
|
|
3508
|
-
return ctx.ops.take(req.id);
|
|
3509
|
-
case "async-resolve":
|
|
3510
|
-
return json2(ctx.ops.resolve(req.value));
|
|
3511
|
-
case "async-hold":
|
|
3512
|
-
return json2(ctx.ops.hold());
|
|
3513
|
-
case "async-cancel":
|
|
3514
|
-
ctx.ops.cancel(req.id);
|
|
3515
|
-
return done2;
|
|
3516
|
-
case "async-close":
|
|
3517
|
-
ctx.ops.close();
|
|
3518
|
-
return done2;
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
// src/kernel/net/loopback-names.ts
|
|
3523
|
-
var HOST_LOOPBACK = "host.slicc.internal";
|
|
3524
|
-
var PAGE_LOOPBACK = "kernel.localhost";
|
|
3525
|
-
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
3526
|
-
function toHostLoopback(href) {
|
|
3527
|
-
const url = new URL(href);
|
|
3528
|
-
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
3529
|
-
url.hostname = "127.0.0.1";
|
|
3530
|
-
return url.href;
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
// src/kernel/net/http1.ts
|
|
3534
|
-
var HttpError = class extends Error {
|
|
3535
|
-
status;
|
|
3536
|
-
constructor(status, message2) {
|
|
3537
|
-
super(message2);
|
|
3538
|
-
this.status = status;
|
|
3539
|
-
}
|
|
3540
|
-
};
|
|
3541
|
-
var CR = 13;
|
|
3542
|
-
var LF = 10;
|
|
3543
|
-
var READ_SIZE = 64 * 1024;
|
|
3544
|
-
var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
3545
|
-
var REASON = {
|
|
3546
|
-
100: "Continue",
|
|
3547
|
-
200: "OK",
|
|
3548
|
-
400: "Bad Request",
|
|
3549
|
-
403: "Forbidden",
|
|
3550
|
-
408: "Request Timeout",
|
|
3551
|
-
411: "Length Required",
|
|
3552
|
-
413: "Content Too Large",
|
|
3553
|
-
417: "Expectation Failed",
|
|
3554
|
-
421: "Misdirected Request",
|
|
3555
|
-
431: "Request Header Fields Too Large",
|
|
3556
|
-
501: "Not Implemented",
|
|
3557
|
-
502: "Bad Gateway",
|
|
3558
|
-
503: "Service Unavailable",
|
|
3559
|
-
505: "HTTP Version Not Supported"
|
|
3560
|
-
};
|
|
3561
|
-
var Incoming = class {
|
|
3562
|
-
source;
|
|
3563
|
-
buf = new Uint8Array(0);
|
|
3564
|
-
eof = false;
|
|
3565
|
-
constructor(source) {
|
|
3566
|
-
this.source = source;
|
|
3567
|
-
}
|
|
3568
|
-
get buffered() {
|
|
3569
|
-
return this.buf.length;
|
|
3570
|
-
}
|
|
3571
|
-
async fill(signal) {
|
|
3572
|
-
if (this.eof) return false;
|
|
3573
|
-
const chunk2 = await this.source.read(READ_SIZE, signal);
|
|
3574
|
-
if (chunk2.length === 0) {
|
|
3575
|
-
this.eof = true;
|
|
3576
|
-
return false;
|
|
3577
|
-
}
|
|
3578
|
-
const next = new Uint8Array(this.buf.length + chunk2.length);
|
|
3579
|
-
next.set(this.buf);
|
|
3580
|
-
next.set(chunk2, this.buf.length);
|
|
3581
|
-
this.buf = next;
|
|
3582
|
-
return true;
|
|
3583
|
-
}
|
|
3584
|
-
take(n) {
|
|
3585
|
-
const out = this.buf.slice(0, n);
|
|
3586
|
-
this.buf = this.buf.subarray(n);
|
|
3587
|
-
return out;
|
|
3588
|
-
}
|
|
3589
|
-
async some(max, signal) {
|
|
3590
|
-
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
3591
|
-
if (this.eof) return new Uint8Array(0);
|
|
3592
|
-
const chunk2 = await this.source.read(max, signal);
|
|
3593
|
-
if (chunk2.length === 0) this.eof = true;
|
|
3594
|
-
return chunk2;
|
|
3595
|
-
}
|
|
3596
|
-
async head(limit, signal) {
|
|
3597
|
-
let from = 0;
|
|
3598
|
-
for (; ; ) {
|
|
3599
|
-
const end = headEnd(this.buf, from);
|
|
3600
|
-
if (end > 0) {
|
|
3601
|
-
if (end > limit) throw new HttpError(431, "request head too large");
|
|
3602
|
-
return this.take(end);
|
|
3603
|
-
}
|
|
3604
|
-
if (this.buf.length > limit) throw new HttpError(431, "request head too large");
|
|
3605
|
-
from = Math.max(0, this.buf.length - 3);
|
|
3606
|
-
if (!await this.fill(signal)) {
|
|
3607
|
-
if (this.buf.length === 0) return null;
|
|
3608
|
-
throw new HttpError(400, "connection closed inside a request head");
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
async line(limit, signal) {
|
|
3613
|
-
let from = 0;
|
|
3614
|
-
for (; ; ) {
|
|
3615
|
-
const at = this.buf.indexOf(LF, from);
|
|
3616
|
-
if (at >= 0) {
|
|
3617
|
-
const line = this.take(at + 1);
|
|
3618
|
-
const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
|
|
3619
|
-
return text;
|
|
3620
|
-
}
|
|
3621
|
-
if (this.buf.length > limit) throw new HttpError(400, "line too long");
|
|
3622
|
-
from = this.buf.length;
|
|
3623
|
-
if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
|
|
3624
|
-
}
|
|
4265
|
+
this.run(req).then(
|
|
4266
|
+
(result) => this.complete(id, result),
|
|
4267
|
+
(err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
|
|
4268
|
+
);
|
|
4269
|
+
return id;
|
|
3625
4270
|
}
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
return this.take(n);
|
|
4271
|
+
resolve(value) {
|
|
4272
|
+
const id = this.hold();
|
|
4273
|
+
this.complete(id, json3(value));
|
|
4274
|
+
return id;
|
|
3631
4275
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
if (buf[i + 1] === LF) return i + 2;
|
|
3637
|
-
if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
|
|
4276
|
+
hold() {
|
|
4277
|
+
const id = this.next++;
|
|
4278
|
+
this.results.set(id, void 0);
|
|
4279
|
+
return id;
|
|
3638
4280
|
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
4281
|
+
take(id) {
|
|
4282
|
+
if (!this.results.has(id)) throw new KernelError("EINVAL");
|
|
4283
|
+
const result = this.results.get(id);
|
|
4284
|
+
if (!result) throw new KernelError("EAGAIN");
|
|
4285
|
+
this.cancel(id);
|
|
4286
|
+
return result;
|
|
3645
4287
|
}
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
|
|
3651
|
-
return out;
|
|
3652
|
-
}
|
|
3653
|
-
function parseRequestHead(bytes) {
|
|
3654
|
-
const lines = latin1(bytes).split(/\r?\n/);
|
|
3655
|
-
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
3656
|
-
const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
|
|
3657
|
-
if (!match) throw new HttpError(400, "malformed request line");
|
|
3658
|
-
if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
|
|
3659
|
-
return {
|
|
3660
|
-
method: match[1],
|
|
3661
|
-
target: match[2],
|
|
3662
|
-
minor: Number(match[4]),
|
|
3663
|
-
headers: parseFields(lines)
|
|
3664
|
-
};
|
|
3665
|
-
}
|
|
3666
|
-
function parseFields(lines) {
|
|
3667
|
-
const headers = [];
|
|
3668
|
-
for (const line of lines) {
|
|
3669
|
-
if (line === "") continue;
|
|
3670
|
-
if (line.startsWith(" ") || line.startsWith(" ")) {
|
|
3671
|
-
throw new HttpError(400, "obsolete line folding");
|
|
3672
|
-
}
|
|
3673
|
-
const colon = line.indexOf(":");
|
|
3674
|
-
const name2 = colon > 0 ? line.slice(0, colon) : "";
|
|
3675
|
-
if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
|
|
3676
|
-
headers.push([name2, line.slice(colon + 1).trim()]);
|
|
4288
|
+
cancel(id) {
|
|
4289
|
+
this.results.delete(id);
|
|
4290
|
+
const at = this.ready.indexOf(id);
|
|
4291
|
+
if (at >= 0) this.ready.splice(at, 1);
|
|
3677
4292
|
}
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
}
|
|
3687
|
-
function requestFraming(headers) {
|
|
3688
|
-
const codings = fieldTokens(headers, "transfer-encoding");
|
|
3689
|
-
const lengths = fieldValues(headers, "content-length");
|
|
3690
|
-
if (codings.length > 0) {
|
|
3691
|
-
if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
|
|
3692
|
-
if (codings.length !== 1 || codings[0] !== "chunked") {
|
|
3693
|
-
throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
|
|
4293
|
+
async wait(timeoutMs, signal) {
|
|
4294
|
+
for (; ; ) {
|
|
4295
|
+
if (this.closed) return -1;
|
|
4296
|
+
const id = this.ready.shift();
|
|
4297
|
+
if (id !== void 0) return id;
|
|
4298
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
4299
|
+
const woke = await this.sleep(timeoutMs, signal);
|
|
4300
|
+
if (!woke) return 0;
|
|
3694
4301
|
}
|
|
3695
|
-
return { kind: "chunked" };
|
|
3696
|
-
}
|
|
3697
|
-
if (lengths.length === 0) return { kind: "none" };
|
|
3698
|
-
const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
|
|
3699
|
-
const [only] = distinct;
|
|
3700
|
-
if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
|
|
3701
|
-
throw new HttpError(400, "invalid Content-Length");
|
|
3702
4302
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
var MAX_CHUNK_LINE = 4096;
|
|
3707
|
-
var MAX_TRAILERS = 64 * 1024;
|
|
3708
|
-
async function readBody(incoming, framing, cap, signal) {
|
|
3709
|
-
if (framing.kind === "none") return void 0;
|
|
3710
|
-
if (framing.kind === "length") {
|
|
3711
|
-
if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
3712
|
-
return incoming.exactly(framing.length, signal);
|
|
4303
|
+
close() {
|
|
4304
|
+
this.closed = true;
|
|
4305
|
+
this.wake();
|
|
3713
4306
|
}
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
4307
|
+
sleep(timeoutMs, signal) {
|
|
4308
|
+
return new Promise((resolve, reject) => {
|
|
4309
|
+
let timer;
|
|
4310
|
+
const cleanup = () => {
|
|
4311
|
+
clearTimeout(timer);
|
|
4312
|
+
this.waiters.delete(wake);
|
|
4313
|
+
signal.removeEventListener("abort", abort);
|
|
4314
|
+
};
|
|
4315
|
+
const wake = () => {
|
|
4316
|
+
cleanup();
|
|
4317
|
+
resolve(true);
|
|
4318
|
+
};
|
|
4319
|
+
const abort = () => {
|
|
4320
|
+
cleanup();
|
|
4321
|
+
reject(new KernelError("EINTR"));
|
|
4322
|
+
};
|
|
4323
|
+
this.waiters.add(wake);
|
|
4324
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
4325
|
+
if (timeoutMs !== void 0) {
|
|
4326
|
+
timer = setTimeout(() => {
|
|
4327
|
+
cleanup();
|
|
4328
|
+
resolve(false);
|
|
4329
|
+
}, timeoutMs);
|
|
4330
|
+
}
|
|
4331
|
+
});
|
|
3726
4332
|
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
4333
|
+
complete(id, result) {
|
|
4334
|
+
if (!this.results.has(id)) return;
|
|
4335
|
+
this.results.set(id, result);
|
|
4336
|
+
this.ready.push(id);
|
|
4337
|
+
this.wake();
|
|
3732
4338
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
function concat3(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
|
|
3736
|
-
const out = new Uint8Array(total);
|
|
3737
|
-
let at = 0;
|
|
3738
|
-
for (const chunk2 of chunks2) {
|
|
3739
|
-
out.set(chunk2, at);
|
|
3740
|
-
at += chunk2.length;
|
|
4339
|
+
wake() {
|
|
4340
|
+
for (const wake of [...this.waiters]) wake();
|
|
3741
4341
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
4342
|
+
};
|
|
4343
|
+
async function hostSyscall(req, ctx) {
|
|
4344
|
+
switch (req.op) {
|
|
4345
|
+
case "lock":
|
|
4346
|
+
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
4347
|
+
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
4348
|
+
throw new KernelError("EAGAIN");
|
|
4349
|
+
return done2;
|
|
4350
|
+
case "unlock":
|
|
4351
|
+
ctx.locks?.unlock(ctx.pid, req.path);
|
|
4352
|
+
return done2;
|
|
4353
|
+
case "async-submit":
|
|
4354
|
+
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
4355
|
+
return json3(ctx.ops.submit(req.req));
|
|
4356
|
+
case "async-wait":
|
|
4357
|
+
return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
4358
|
+
case "async-take":
|
|
4359
|
+
return ctx.ops.take(req.id);
|
|
4360
|
+
case "async-resolve":
|
|
4361
|
+
return json3(ctx.ops.resolve(req.value));
|
|
4362
|
+
case "async-hold":
|
|
4363
|
+
return json3(ctx.ops.hold());
|
|
4364
|
+
case "async-cancel":
|
|
4365
|
+
ctx.ops.cancel(req.id);
|
|
4366
|
+
return done2;
|
|
4367
|
+
case "async-close":
|
|
4368
|
+
ctx.ops.close();
|
|
4369
|
+
return done2;
|
|
3754
4370
|
}
|
|
3755
|
-
return latin1Bytes(`${head}\r
|
|
3756
|
-
`);
|
|
3757
4371
|
}
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
4372
|
+
|
|
4373
|
+
// src/kernel/net/loopback-names.ts
|
|
4374
|
+
var HOST_LOOPBACK = "host.slicc.internal";
|
|
4375
|
+
var PAGE_LOOPBACK = "kernel.localhost";
|
|
4376
|
+
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
4377
|
+
function toHostLoopback(href) {
|
|
4378
|
+
const url = new URL(href);
|
|
4379
|
+
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
4380
|
+
url.hostname = "127.0.0.1";
|
|
4381
|
+
return url.href;
|
|
3767
4382
|
}
|
|
3768
|
-
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
3769
4383
|
|
|
3770
4384
|
// src/kernel/net/proxy-service.ts
|
|
3771
4385
|
var REALM_PROXY_PORT = 3128;
|
|
@@ -5358,23 +5972,23 @@ function terminalOf(ctx, fd) {
|
|
|
5358
5972
|
if (!tty) throw new KernelError("ENOTTY");
|
|
5359
5973
|
return tty;
|
|
5360
5974
|
}
|
|
5361
|
-
var
|
|
5975
|
+
var json4 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5362
5976
|
var done3 = { ok: true, kind: "void" };
|
|
5363
5977
|
function ptySyscall(req, ctx) {
|
|
5364
5978
|
switch (req.op) {
|
|
5365
5979
|
case "pty-open": {
|
|
5366
5980
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5367
|
-
return
|
|
5981
|
+
return json4(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5368
5982
|
}
|
|
5369
5983
|
case "pty-slave-open": {
|
|
5370
5984
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5371
5985
|
const pair = ctx.ptys.get(req.n);
|
|
5372
5986
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
5373
5987
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
5374
|
-
return
|
|
5988
|
+
return json4(fd);
|
|
5375
5989
|
}
|
|
5376
5990
|
case "pty-number":
|
|
5377
|
-
return
|
|
5991
|
+
return json4(masterOf(ctx, req.fd).index);
|
|
5378
5992
|
case "pty-lock":
|
|
5379
5993
|
masterOf(ctx, req.fd).locked = req.lock;
|
|
5380
5994
|
return done3;
|
|
@@ -5386,7 +6000,7 @@ function ptySyscall(req, ctx) {
|
|
|
5386
6000
|
return done3;
|
|
5387
6001
|
}
|
|
5388
6002
|
case "pty-list":
|
|
5389
|
-
return
|
|
6003
|
+
return json4(ctx.ptys?.numbers() ?? []);
|
|
5390
6004
|
case "pty-packet":
|
|
5391
6005
|
masterOf(ctx, req.fd).packet = req.on;
|
|
5392
6006
|
return done3;
|
|
@@ -5464,7 +6078,7 @@ function socketAt(fds, fd) {
|
|
|
5464
6078
|
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
5465
6079
|
return file;
|
|
5466
6080
|
}
|
|
5467
|
-
var ok = (
|
|
6081
|
+
var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
5468
6082
|
async function socketSyscall(req, proc) {
|
|
5469
6083
|
const { fds, net } = proc;
|
|
5470
6084
|
switch (req.op) {
|
|
@@ -7428,8 +8042,8 @@ function umountCall(target, flags) {
|
|
|
7428
8042
|
var FSTAB_PATH = "/etc/fstab";
|
|
7429
8043
|
var FSTAB_RETRIES = [1e3, 4e3, 16e3];
|
|
7430
8044
|
var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
|
|
7431
|
-
function unescape(
|
|
7432
|
-
return
|
|
8045
|
+
function unescape(field2) {
|
|
8046
|
+
return field2.replace(
|
|
7433
8047
|
/\\([0-7]{3})/g,
|
|
7434
8048
|
(_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
|
|
7435
8049
|
);
|
|
@@ -7468,28 +8082,34 @@ function wait(ms, signal) {
|
|
|
7468
8082
|
});
|
|
7469
8083
|
}
|
|
7470
8084
|
async function mountFstab(lines, mount, signal, retries = FSTAB_RETRIES, discard = () => {
|
|
8085
|
+
}, settled = () => {
|
|
7471
8086
|
}) {
|
|
7472
8087
|
return Promise.all(
|
|
7473
|
-
lines.map(async (
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
const entry = await mount(spec);
|
|
7478
|
-
if (!signal.aborted) return { line, target: spec.target, entry };
|
|
7479
|
-
discard(entry);
|
|
7480
|
-
failed = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
|
|
7481
|
-
break;
|
|
7482
|
-
} catch (err) {
|
|
7483
|
-
failed = err;
|
|
7484
|
-
if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
|
|
7485
|
-
await wait(retries[attempt], signal);
|
|
7486
|
-
}
|
|
7487
|
-
}
|
|
7488
|
-
const code = codeOf3(failed);
|
|
7489
|
-
return { line, target: spec.target, code, error: String(failed?.message ?? code) };
|
|
8088
|
+
lines.map(async (each) => {
|
|
8089
|
+
const result = await mountLine(each, mount, signal, retries, discard);
|
|
8090
|
+
settled(each, result);
|
|
8091
|
+
return result;
|
|
7490
8092
|
})
|
|
7491
8093
|
);
|
|
7492
8094
|
}
|
|
8095
|
+
async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
|
|
8096
|
+
let failed = invalid;
|
|
8097
|
+
for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
|
|
8098
|
+
try {
|
|
8099
|
+
const entry = await mount(spec);
|
|
8100
|
+
if (!signal.aborted) return { line, target: spec.target, entry };
|
|
8101
|
+
discard(entry);
|
|
8102
|
+
failed = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
|
|
8103
|
+
break;
|
|
8104
|
+
} catch (err) {
|
|
8105
|
+
failed = err;
|
|
8106
|
+
if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
|
|
8107
|
+
await wait(retries[attempt], signal);
|
|
8108
|
+
}
|
|
8109
|
+
}
|
|
8110
|
+
const code = codeOf3(failed);
|
|
8111
|
+
return { line, target: spec.target, code, error: String(failed?.message ?? code) };
|
|
8112
|
+
}
|
|
7493
8113
|
|
|
7494
8114
|
// src/mount/hostfs.ts
|
|
7495
8115
|
var HOSTFS_IO = 16 * 1024 * 1024;
|
|
@@ -8080,7 +8700,7 @@ function importedMemory(bytes) {
|
|
|
8080
8700
|
const count = u32();
|
|
8081
8701
|
for (let i = 0; i < count; i++) {
|
|
8082
8702
|
const module = name2();
|
|
8083
|
-
const
|
|
8703
|
+
const field2 = name2();
|
|
8084
8704
|
const kind = bytes[at++];
|
|
8085
8705
|
if (kind === 0) u32();
|
|
8086
8706
|
else if (kind === 1) {
|
|
@@ -8088,7 +8708,7 @@ function importedMemory(bytes) {
|
|
|
8088
8708
|
limits();
|
|
8089
8709
|
} else if (kind === 2) {
|
|
8090
8710
|
const l = limits();
|
|
8091
|
-
return { module, name:
|
|
8711
|
+
return { module, name: field2, ...l };
|
|
8092
8712
|
} else if (kind === 3) at += 2;
|
|
8093
8713
|
else if (kind === 4) {
|
|
8094
8714
|
at++;
|
|
@@ -8179,12 +8799,12 @@ function readImports(r, types) {
|
|
|
8179
8799
|
const foreign = {};
|
|
8180
8800
|
for (let i = r.u32(); i > 0; i--) {
|
|
8181
8801
|
const module = r.name();
|
|
8182
|
-
const
|
|
8802
|
+
const field2 = r.name();
|
|
8183
8803
|
const kind = r.byte();
|
|
8184
8804
|
if (kind !== 0) skipImport(r, kind);
|
|
8185
8805
|
else {
|
|
8186
8806
|
const type = types[r.u32()] ?? "other";
|
|
8187
|
-
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[
|
|
8807
|
+
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
|
|
8188
8808
|
}
|
|
8189
8809
|
}
|
|
8190
8810
|
return foreign;
|
|
@@ -8291,6 +8911,7 @@ var Launcher = class {
|
|
|
8291
8911
|
locks = new LockTable();
|
|
8292
8912
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
8293
8913
|
net = new LoopbackNet();
|
|
8914
|
+
cdp;
|
|
8294
8915
|
ca;
|
|
8295
8916
|
transport;
|
|
8296
8917
|
watchers = new FsWatchers();
|
|
@@ -8301,6 +8922,7 @@ var Launcher = class {
|
|
|
8301
8922
|
onMountPending;
|
|
8302
8923
|
removable = /* @__PURE__ */ new Map();
|
|
8303
8924
|
hostfs;
|
|
8925
|
+
fstabRetries;
|
|
8304
8926
|
inserted = /* @__PURE__ */ new Map();
|
|
8305
8927
|
openFiles = /* @__PURE__ */ new Set();
|
|
8306
8928
|
held = /* @__PURE__ */ new Set();
|
|
@@ -8322,10 +8944,12 @@ var Launcher = class {
|
|
|
8322
8944
|
this.media = options.media ?? memoryMedia();
|
|
8323
8945
|
this.onMountPending = options.onMountPending;
|
|
8324
8946
|
this.hostfs = options;
|
|
8947
|
+
this.fstabRetries = options.fstabRetries ?? FSTAB_RETRIES;
|
|
8325
8948
|
this.processMounts = options.processMounts ?? true;
|
|
8326
8949
|
this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
|
|
8327
8950
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
8328
|
-
this.
|
|
8951
|
+
this.cdp = new CdpHosts(options.cdp);
|
|
8952
|
+
this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
|
|
8329
8953
|
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
8330
8954
|
this.transport = options.transport ?? missingTransport();
|
|
8331
8955
|
enableNetwork(this.net, {
|
|
@@ -8333,6 +8957,7 @@ var Launcher = class {
|
|
|
8333
8957
|
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
8334
8958
|
ca: this.ca
|
|
8335
8959
|
});
|
|
8960
|
+
enableCdp(this.net, this.cdp);
|
|
8336
8961
|
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
8337
8962
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
8338
8963
|
this.catalog = void 0;
|
|
@@ -8755,12 +9380,22 @@ var Launcher = class {
|
|
|
8755
9380
|
}
|
|
8756
9381
|
async mountFstab() {
|
|
8757
9382
|
const text = await this.base.readFile(FSTAB_PATH).catch(() => "");
|
|
9383
|
+
const lines = parseFstab(text);
|
|
9384
|
+
for (const { spec } of lines) this.mounts.note(spec, "pending");
|
|
8758
9385
|
return mountFstab(
|
|
8759
|
-
|
|
8760
|
-
(spec) => this.mount(spec)
|
|
9386
|
+
lines,
|
|
9387
|
+
(spec) => this.mount(spec).catch((err) => {
|
|
9388
|
+
this.mounts.note(spec, "pending", err?.message ?? String(err));
|
|
9389
|
+
throw err;
|
|
9390
|
+
}),
|
|
8761
9391
|
this.booting.signal,
|
|
8762
|
-
|
|
8763
|
-
(entry) => this.umount(entry.target, true)
|
|
9392
|
+
this.fstabRetries,
|
|
9393
|
+
(entry) => this.umount(entry.target, true),
|
|
9394
|
+
({ spec }, result) => {
|
|
9395
|
+
if (!result.code || result.code === "ECANCELED" || this.booting.signal.aborted) {
|
|
9396
|
+
this.mounts.unnote(spec.target);
|
|
9397
|
+
} else this.mounts.note(spec, "failed", result.error);
|
|
9398
|
+
}
|
|
8764
9399
|
);
|
|
8765
9400
|
}
|
|
8766
9401
|
environment(cwd, extra) {
|
|
@@ -9394,6 +10029,8 @@ var Channel = class {
|
|
|
9394
10029
|
failure;
|
|
9395
10030
|
pending = /* @__PURE__ */ new Map();
|
|
9396
10031
|
watching = /* @__PURE__ */ new Map();
|
|
10032
|
+
cdpHooks = /* @__PURE__ */ new Map();
|
|
10033
|
+
cdpOpen = /* @__PURE__ */ new Set();
|
|
9397
10034
|
protocol = PROTOCOL;
|
|
9398
10035
|
nextId = 0;
|
|
9399
10036
|
port;
|
|
@@ -9414,6 +10051,8 @@ var Channel = class {
|
|
|
9414
10051
|
this.failure = error;
|
|
9415
10052
|
this.greeted.reject(error);
|
|
9416
10053
|
this.remote?.fail(error);
|
|
10054
|
+
for (const close of [...this.cdpOpen]) close();
|
|
10055
|
+
this.cdpOpen.clear();
|
|
9417
10056
|
for (const call of this.pending.values()) call.reject(error);
|
|
9418
10057
|
this.pending.clear();
|
|
9419
10058
|
this.release();
|
|
@@ -9434,6 +10073,10 @@ var Channel = class {
|
|
|
9434
10073
|
return this.request(call).done;
|
|
9435
10074
|
}
|
|
9436
10075
|
receive(data) {
|
|
10076
|
+
if (data.cdpOpen && data.port) {
|
|
10077
|
+
this.openCdp(data);
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
9437
10080
|
if (data.watch !== void 0) {
|
|
9438
10081
|
this.watching.get(data.watch)?.(data.change);
|
|
9439
10082
|
return;
|
|
@@ -9465,7 +10108,39 @@ var Channel = class {
|
|
|
9465
10108
|
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
9466
10109
|
else call.resolve(reply.result);
|
|
9467
10110
|
}
|
|
10111
|
+
openCdp({ cdpOpen: { registration, ...request }, port }) {
|
|
10112
|
+
const hook = registration === void 0 ? void 0 : this.cdpHooks.get(registration);
|
|
10113
|
+
if (!hook || this.failure) {
|
|
10114
|
+
port.postMessage({ error: "this client no longer serves CDP" });
|
|
10115
|
+
port.close();
|
|
10116
|
+
return;
|
|
10117
|
+
}
|
|
10118
|
+
const close = serveCdpPort(port, hook, request, () => this.cdpOpen.delete(close));
|
|
10119
|
+
this.cdpOpen.add(close);
|
|
10120
|
+
}
|
|
9468
10121
|
};
|
|
10122
|
+
async function serveCdpOn(channel, hook, options) {
|
|
10123
|
+
await since(channel, 5, "CDP serving", async () => {
|
|
10124
|
+
});
|
|
10125
|
+
const req = {
|
|
10126
|
+
op: "serve-cdp",
|
|
10127
|
+
...options.runtime ? { runtime: options.runtime } : {}
|
|
10128
|
+
};
|
|
10129
|
+
const { id, done: done4 } = channel.request(req);
|
|
10130
|
+
channel.cdpHooks.set(id, hook);
|
|
10131
|
+
try {
|
|
10132
|
+
await done4;
|
|
10133
|
+
} catch (error) {
|
|
10134
|
+
channel.cdpHooks.delete(id);
|
|
10135
|
+
throw error;
|
|
10136
|
+
}
|
|
10137
|
+
return {
|
|
10138
|
+
close: () => {
|
|
10139
|
+
if (!channel.cdpHooks.delete(id)) return;
|
|
10140
|
+
void channel.call({ op: "unserve-cdp", registration: id }).catch(() => void 0);
|
|
10141
|
+
}
|
|
10142
|
+
};
|
|
10143
|
+
}
|
|
9469
10144
|
async function dialOn(channel, { port, host }) {
|
|
9470
10145
|
const req = { op: "dial", port, ...host ? { host } : {} };
|
|
9471
10146
|
return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
|
|
@@ -9651,6 +10326,7 @@ async function attachKernel(port, options = {}) {
|
|
|
9651
10326
|
dial: (options2) => dialOn(channel, options2),
|
|
9652
10327
|
loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
|
|
9653
10328
|
fs: fsOn(channel),
|
|
10329
|
+
serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
|
|
9654
10330
|
async close(o = {}) {
|
|
9655
10331
|
if (channel.failure) return;
|
|
9656
10332
|
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
@@ -9731,7 +10407,9 @@ async function createNodeKernel(options = {}) {
|
|
|
9731
10407
|
...options.modules ? { modules: options.modules } : {},
|
|
9732
10408
|
...options.env ? { env: options.env } : {},
|
|
9733
10409
|
...transport ? { transport } : {},
|
|
9734
|
-
...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {}
|
|
10410
|
+
...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
|
|
10411
|
+
...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
|
|
10412
|
+
...options.cdp ? { cdp: options.cdp } : {}
|
|
9735
10413
|
});
|
|
9736
10414
|
await launcher.prepare();
|
|
9737
10415
|
const guard = (work) => new Promise((resolve, reject) => {
|
|
@@ -9811,6 +10489,7 @@ async function createNodeKernel(options = {}) {
|
|
|
9811
10489
|
for (const client of [...clients]) client.detach();
|
|
9812
10490
|
for (const reject of [...pending]) reject(new Error(TERMINATED));
|
|
9813
10491
|
for (const kill of [...live]) kill();
|
|
10492
|
+
launcher.cdp.close();
|
|
9814
10493
|
launcher.unmountAll();
|
|
9815
10494
|
}
|
|
9816
10495
|
};
|