@ai-ecoverse/slicc-kernel 1.22.1 → 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 +30 -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 +603 -40
- 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 +923 -283
- 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,6 +1949,705 @@ var OpfsFs = class {
|
|
|
1817
1949
|
}
|
|
1818
1950
|
};
|
|
1819
1951
|
|
|
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;
|
|
1958
|
+
}
|
|
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;
|
|
1989
|
+
}
|
|
1990
|
+
get buffered() {
|
|
1991
|
+
return this.buf.length;
|
|
1992
|
+
}
|
|
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;
|
|
1999
|
+
}
|
|
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;
|
|
2005
|
+
}
|
|
2006
|
+
take(n) {
|
|
2007
|
+
const out = this.buf.slice(0, n);
|
|
2008
|
+
this.buf = this.buf.subarray(n);
|
|
2009
|
+
return out;
|
|
2010
|
+
}
|
|
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
|
+
}
|
|
2032
|
+
}
|
|
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");
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
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");
|
|
2051
|
+
}
|
|
2052
|
+
return this.take(n);
|
|
2053
|
+
}
|
|
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
|
+
|
|
1820
2651
|
// src/commands.ts
|
|
1821
2652
|
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
1822
2653
|
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -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;
|
|
@@ -2823,7 +3654,7 @@ var MountFs = class {
|
|
|
2823
3654
|
} finally {
|
|
2824
3655
|
await mount.conn.call({ op: "release", fh });
|
|
2825
3656
|
}
|
|
2826
|
-
return
|
|
3657
|
+
return concat3(chunks2, total);
|
|
2827
3658
|
}
|
|
2828
3659
|
async writeFile(path, content) {
|
|
2829
3660
|
const real = await this.follow(path, 0, true);
|
|
@@ -2887,7 +3718,7 @@ var MountFs = class {
|
|
|
2887
3718
|
} finally {
|
|
2888
3719
|
await mount.conn.call({ op: "release", fh });
|
|
2889
3720
|
}
|
|
2890
|
-
return
|
|
3721
|
+
return concat3(chunks2, total);
|
|
2891
3722
|
}
|
|
2892
3723
|
fits(mount, real, end) {
|
|
2893
3724
|
if (mount.maxFile > 0 && end > mount.maxFile) {
|
|
@@ -3140,7 +3971,7 @@ async function storageUsage() {
|
|
|
3140
3971
|
return { quota: estimate.quota, usage: estimate.usage ?? 0 };
|
|
3141
3972
|
}
|
|
3142
3973
|
var done = { ok: true, kind: "void" };
|
|
3143
|
-
function
|
|
3974
|
+
function json2(value) {
|
|
3144
3975
|
return { ok: true, kind: "json", json: value };
|
|
3145
3976
|
}
|
|
3146
3977
|
async function run(entry, path, req) {
|
|
@@ -3152,15 +3983,15 @@ async function run(entry, path, req) {
|
|
|
3152
3983
|
await fs.writeFile(path, req.body ?? new Uint8Array(0));
|
|
3153
3984
|
return done;
|
|
3154
3985
|
case "exists":
|
|
3155
|
-
return
|
|
3986
|
+
return json2(await fs.exists(path));
|
|
3156
3987
|
case "stat":
|
|
3157
|
-
return
|
|
3988
|
+
return json2(statJson(await fs.stat(path)));
|
|
3158
3989
|
case "lstat":
|
|
3159
|
-
return
|
|
3990
|
+
return json2(statJson(await fs.lstat(path)));
|
|
3160
3991
|
case "readdir":
|
|
3161
|
-
return
|
|
3992
|
+
return json2(await fs.readdir(path));
|
|
3162
3993
|
case "readdir-stat":
|
|
3163
|
-
return
|
|
3994
|
+
return json2(await readdirStat(fs, path));
|
|
3164
3995
|
case "mkdir":
|
|
3165
3996
|
await fs.mkdir(path, { recursive: true });
|
|
3166
3997
|
return done;
|
|
@@ -3186,7 +4017,7 @@ async function run(entry, path, req) {
|
|
|
3186
4017
|
await fs.symlink(req.arg2 ?? "", path);
|
|
3187
4018
|
return done;
|
|
3188
4019
|
case "readlink":
|
|
3189
|
-
return
|
|
4020
|
+
return json2(await fs.readlink(path));
|
|
3190
4021
|
case "chmod":
|
|
3191
4022
|
await fs.chmod(path, req.mode ?? 0);
|
|
3192
4023
|
return done;
|
|
@@ -3217,7 +4048,7 @@ async function dispatchSyncFs(req) {
|
|
|
3217
4048
|
const { fs, cwd } = entry;
|
|
3218
4049
|
try {
|
|
3219
4050
|
const path = fs.resolvePath(cwd, req.path);
|
|
3220
|
-
if (req.op === "statfs") return
|
|
4051
|
+
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
3221
4052
|
if (req.op === "hold") {
|
|
3222
4053
|
entry.hold?.(path, req.mode === 1);
|
|
3223
4054
|
return done;
|
|
@@ -3390,7 +4221,7 @@ var HOST_OPS = [
|
|
|
3390
4221
|
"async-cancel",
|
|
3391
4222
|
"async-close"
|
|
3392
4223
|
];
|
|
3393
|
-
var
|
|
4224
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
3394
4225
|
var done2 = { ok: true, kind: "void" };
|
|
3395
4226
|
var LockTable = class {
|
|
3396
4227
|
held = /* @__PURE__ */ new Map();
|
|
@@ -3439,7 +4270,7 @@ var AsyncOps = class {
|
|
|
3439
4270
|
}
|
|
3440
4271
|
resolve(value) {
|
|
3441
4272
|
const id = this.hold();
|
|
3442
|
-
this.complete(id,
|
|
4273
|
+
this.complete(id, json3(value));
|
|
3443
4274
|
return id;
|
|
3444
4275
|
}
|
|
3445
4276
|
hold() {
|
|
@@ -3521,15 +4352,15 @@ async function hostSyscall(req, ctx) {
|
|
|
3521
4352
|
return done2;
|
|
3522
4353
|
case "async-submit":
|
|
3523
4354
|
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
3524
|
-
return
|
|
4355
|
+
return json3(ctx.ops.submit(req.req));
|
|
3525
4356
|
case "async-wait":
|
|
3526
|
-
return
|
|
4357
|
+
return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
3527
4358
|
case "async-take":
|
|
3528
4359
|
return ctx.ops.take(req.id);
|
|
3529
4360
|
case "async-resolve":
|
|
3530
|
-
return
|
|
4361
|
+
return json3(ctx.ops.resolve(req.value));
|
|
3531
4362
|
case "async-hold":
|
|
3532
|
-
return
|
|
4363
|
+
return json3(ctx.ops.hold());
|
|
3533
4364
|
case "async-cancel":
|
|
3534
4365
|
ctx.ops.cancel(req.id);
|
|
3535
4366
|
return done2;
|
|
@@ -3550,243 +4381,6 @@ function toHostLoopback(href) {
|
|
|
3550
4381
|
return url.href;
|
|
3551
4382
|
}
|
|
3552
4383
|
|
|
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);
|
|
3651
|
-
}
|
|
3652
|
-
};
|
|
3653
|
-
function headEnd(buf, from) {
|
|
3654
|
-
for (let i = from; i < buf.length; i++) {
|
|
3655
|
-
if (buf[i] !== LF) continue;
|
|
3656
|
-
if (buf[i + 1] === LF) return i + 2;
|
|
3657
|
-
if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
|
|
3658
|
-
}
|
|
3659
|
-
return 0;
|
|
3660
|
-
}
|
|
3661
|
-
function latin1(bytes) {
|
|
3662
|
-
let out = "";
|
|
3663
|
-
for (let i = 0; i < bytes.length; i += 32768) {
|
|
3664
|
-
out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
3665
|
-
}
|
|
3666
|
-
return out;
|
|
3667
|
-
}
|
|
3668
|
-
function latin1Bytes(text) {
|
|
3669
|
-
const out = new Uint8Array(text.length);
|
|
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()]);
|
|
3697
|
-
}
|
|
3698
|
-
return headers;
|
|
3699
|
-
}
|
|
3700
|
-
function fieldValues(headers, name2) {
|
|
3701
|
-
const lower = name2.toLowerCase();
|
|
3702
|
-
return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
|
|
3703
|
-
}
|
|
3704
|
-
function fieldTokens(headers, name2) {
|
|
3705
|
-
return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
|
|
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(", ")}`);
|
|
3714
|
-
}
|
|
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
|
-
}
|
|
3723
|
-
const length2 = Number(only);
|
|
3724
|
-
return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
|
|
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);
|
|
3733
|
-
}
|
|
3734
|
-
const chunks2 = [];
|
|
3735
|
-
let total = 0;
|
|
3736
|
-
for (; ; ) {
|
|
3737
|
-
const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
|
|
3738
|
-
const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
|
|
3739
|
-
if (!size) throw new HttpError(400, "malformed chunk size");
|
|
3740
|
-
const n = Number.parseInt(size[1], 16);
|
|
3741
|
-
if (n === 0) break;
|
|
3742
|
-
total += n;
|
|
3743
|
-
if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
|
|
3744
|
-
chunks2.push(await incoming.exactly(n, signal));
|
|
3745
|
-
if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
|
|
3746
|
-
}
|
|
3747
|
-
let trailers = 0;
|
|
3748
|
-
for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
|
|
3749
|
-
trailers += line.length;
|
|
3750
|
-
if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
|
|
3751
|
-
line = await incoming.line(MAX_TRAILERS, signal);
|
|
3752
|
-
}
|
|
3753
|
-
return concat3(chunks2, total);
|
|
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;
|
|
3761
|
-
}
|
|
3762
|
-
return out;
|
|
3763
|
-
}
|
|
3764
|
-
function safeValue(value) {
|
|
3765
|
-
return !/[\r\n\0]/.test(value);
|
|
3766
|
-
}
|
|
3767
|
-
function responseHead(status, reason, headers) {
|
|
3768
|
-
const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
|
|
3769
|
-
let head = `HTTP/1.1 ${status} ${phrase}\r
|
|
3770
|
-
`;
|
|
3771
|
-
for (const [name2, value] of headers) {
|
|
3772
|
-
if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
|
|
3773
|
-
`;
|
|
3774
|
-
}
|
|
3775
|
-
return latin1Bytes(`${head}\r
|
|
3776
|
-
`);
|
|
3777
|
-
}
|
|
3778
|
-
function chunk(bytes) {
|
|
3779
|
-
const size = latin1Bytes(`${bytes.length.toString(16)}\r
|
|
3780
|
-
`);
|
|
3781
|
-
const out = new Uint8Array(size.length + bytes.length + 2);
|
|
3782
|
-
out.set(size);
|
|
3783
|
-
out.set(bytes, size.length);
|
|
3784
|
-
out[out.length - 2] = CR;
|
|
3785
|
-
out[out.length - 1] = LF;
|
|
3786
|
-
return out;
|
|
3787
|
-
}
|
|
3788
|
-
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
3789
|
-
|
|
3790
4384
|
// src/kernel/net/proxy-service.ts
|
|
3791
4385
|
var REALM_PROXY_PORT = 3128;
|
|
3792
4386
|
var DEFAULT_LIMITS = {
|
|
@@ -5378,23 +5972,23 @@ function terminalOf(ctx, fd) {
|
|
|
5378
5972
|
if (!tty) throw new KernelError("ENOTTY");
|
|
5379
5973
|
return tty;
|
|
5380
5974
|
}
|
|
5381
|
-
var
|
|
5975
|
+
var json4 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5382
5976
|
var done3 = { ok: true, kind: "void" };
|
|
5383
5977
|
function ptySyscall(req, ctx) {
|
|
5384
5978
|
switch (req.op) {
|
|
5385
5979
|
case "pty-open": {
|
|
5386
5980
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5387
|
-
return
|
|
5981
|
+
return json4(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5388
5982
|
}
|
|
5389
5983
|
case "pty-slave-open": {
|
|
5390
5984
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5391
5985
|
const pair = ctx.ptys.get(req.n);
|
|
5392
5986
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
5393
5987
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
5394
|
-
return
|
|
5988
|
+
return json4(fd);
|
|
5395
5989
|
}
|
|
5396
5990
|
case "pty-number":
|
|
5397
|
-
return
|
|
5991
|
+
return json4(masterOf(ctx, req.fd).index);
|
|
5398
5992
|
case "pty-lock":
|
|
5399
5993
|
masterOf(ctx, req.fd).locked = req.lock;
|
|
5400
5994
|
return done3;
|
|
@@ -5406,7 +6000,7 @@ function ptySyscall(req, ctx) {
|
|
|
5406
6000
|
return done3;
|
|
5407
6001
|
}
|
|
5408
6002
|
case "pty-list":
|
|
5409
|
-
return
|
|
6003
|
+
return json4(ctx.ptys?.numbers() ?? []);
|
|
5410
6004
|
case "pty-packet":
|
|
5411
6005
|
masterOf(ctx, req.fd).packet = req.on;
|
|
5412
6006
|
return done3;
|
|
@@ -5484,7 +6078,7 @@ function socketAt(fds, fd) {
|
|
|
5484
6078
|
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
5485
6079
|
return file;
|
|
5486
6080
|
}
|
|
5487
|
-
var ok = (
|
|
6081
|
+
var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
5488
6082
|
async function socketSyscall(req, proc) {
|
|
5489
6083
|
const { fds, net } = proc;
|
|
5490
6084
|
switch (req.op) {
|
|
@@ -7448,8 +8042,8 @@ function umountCall(target, flags) {
|
|
|
7448
8042
|
var FSTAB_PATH = "/etc/fstab";
|
|
7449
8043
|
var FSTAB_RETRIES = [1e3, 4e3, 16e3];
|
|
7450
8044
|
var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
|
|
7451
|
-
function unescape(
|
|
7452
|
-
return
|
|
8045
|
+
function unescape(field2) {
|
|
8046
|
+
return field2.replace(
|
|
7453
8047
|
/\\([0-7]{3})/g,
|
|
7454
8048
|
(_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
|
|
7455
8049
|
);
|
|
@@ -8106,7 +8700,7 @@ function importedMemory(bytes) {
|
|
|
8106
8700
|
const count = u32();
|
|
8107
8701
|
for (let i = 0; i < count; i++) {
|
|
8108
8702
|
const module = name2();
|
|
8109
|
-
const
|
|
8703
|
+
const field2 = name2();
|
|
8110
8704
|
const kind = bytes[at++];
|
|
8111
8705
|
if (kind === 0) u32();
|
|
8112
8706
|
else if (kind === 1) {
|
|
@@ -8114,7 +8708,7 @@ function importedMemory(bytes) {
|
|
|
8114
8708
|
limits();
|
|
8115
8709
|
} else if (kind === 2) {
|
|
8116
8710
|
const l = limits();
|
|
8117
|
-
return { module, name:
|
|
8711
|
+
return { module, name: field2, ...l };
|
|
8118
8712
|
} else if (kind === 3) at += 2;
|
|
8119
8713
|
else if (kind === 4) {
|
|
8120
8714
|
at++;
|
|
@@ -8205,12 +8799,12 @@ function readImports(r, types) {
|
|
|
8205
8799
|
const foreign = {};
|
|
8206
8800
|
for (let i = r.u32(); i > 0; i--) {
|
|
8207
8801
|
const module = r.name();
|
|
8208
|
-
const
|
|
8802
|
+
const field2 = r.name();
|
|
8209
8803
|
const kind = r.byte();
|
|
8210
8804
|
if (kind !== 0) skipImport(r, kind);
|
|
8211
8805
|
else {
|
|
8212
8806
|
const type = types[r.u32()] ?? "other";
|
|
8213
|
-
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[
|
|
8807
|
+
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
|
|
8214
8808
|
}
|
|
8215
8809
|
}
|
|
8216
8810
|
return foreign;
|
|
@@ -8317,6 +8911,7 @@ var Launcher = class {
|
|
|
8317
8911
|
locks = new LockTable();
|
|
8318
8912
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
8319
8913
|
net = new LoopbackNet();
|
|
8914
|
+
cdp;
|
|
8320
8915
|
ca;
|
|
8321
8916
|
transport;
|
|
8322
8917
|
watchers = new FsWatchers();
|
|
@@ -8353,7 +8948,8 @@ var Launcher = class {
|
|
|
8353
8948
|
this.processMounts = options.processMounts ?? true;
|
|
8354
8949
|
this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
|
|
8355
8950
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
8356
|
-
this.
|
|
8951
|
+
this.cdp = new CdpHosts(options.cdp);
|
|
8952
|
+
this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
|
|
8357
8953
|
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
8358
8954
|
this.transport = options.transport ?? missingTransport();
|
|
8359
8955
|
enableNetwork(this.net, {
|
|
@@ -8361,6 +8957,7 @@ var Launcher = class {
|
|
|
8361
8957
|
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
8362
8958
|
ca: this.ca
|
|
8363
8959
|
});
|
|
8960
|
+
enableCdp(this.net, this.cdp);
|
|
8364
8961
|
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
8365
8962
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
8366
8963
|
this.catalog = void 0;
|
|
@@ -9432,6 +10029,8 @@ var Channel = class {
|
|
|
9432
10029
|
failure;
|
|
9433
10030
|
pending = /* @__PURE__ */ new Map();
|
|
9434
10031
|
watching = /* @__PURE__ */ new Map();
|
|
10032
|
+
cdpHooks = /* @__PURE__ */ new Map();
|
|
10033
|
+
cdpOpen = /* @__PURE__ */ new Set();
|
|
9435
10034
|
protocol = PROTOCOL;
|
|
9436
10035
|
nextId = 0;
|
|
9437
10036
|
port;
|
|
@@ -9452,6 +10051,8 @@ var Channel = class {
|
|
|
9452
10051
|
this.failure = error;
|
|
9453
10052
|
this.greeted.reject(error);
|
|
9454
10053
|
this.remote?.fail(error);
|
|
10054
|
+
for (const close of [...this.cdpOpen]) close();
|
|
10055
|
+
this.cdpOpen.clear();
|
|
9455
10056
|
for (const call of this.pending.values()) call.reject(error);
|
|
9456
10057
|
this.pending.clear();
|
|
9457
10058
|
this.release();
|
|
@@ -9472,6 +10073,10 @@ var Channel = class {
|
|
|
9472
10073
|
return this.request(call).done;
|
|
9473
10074
|
}
|
|
9474
10075
|
receive(data) {
|
|
10076
|
+
if (data.cdpOpen && data.port) {
|
|
10077
|
+
this.openCdp(data);
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
9475
10080
|
if (data.watch !== void 0) {
|
|
9476
10081
|
this.watching.get(data.watch)?.(data.change);
|
|
9477
10082
|
return;
|
|
@@ -9503,7 +10108,39 @@ var Channel = class {
|
|
|
9503
10108
|
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
9504
10109
|
else call.resolve(reply.result);
|
|
9505
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
|
+
}
|
|
9506
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
|
+
}
|
|
9507
10144
|
async function dialOn(channel, { port, host }) {
|
|
9508
10145
|
const req = { op: "dial", port, ...host ? { host } : {} };
|
|
9509
10146
|
return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
|
|
@@ -9689,6 +10326,7 @@ async function attachKernel(port, options = {}) {
|
|
|
9689
10326
|
dial: (options2) => dialOn(channel, options2),
|
|
9690
10327
|
loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
|
|
9691
10328
|
fs: fsOn(channel),
|
|
10329
|
+
serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
|
|
9692
10330
|
async close(o = {}) {
|
|
9693
10331
|
if (channel.failure) return;
|
|
9694
10332
|
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
@@ -9770,7 +10408,8 @@ async function createNodeKernel(options = {}) {
|
|
|
9770
10408
|
...options.env ? { env: options.env } : {},
|
|
9771
10409
|
...transport ? { transport } : {},
|
|
9772
10410
|
...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
|
|
9773
|
-
...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {}
|
|
10411
|
+
...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
|
|
10412
|
+
...options.cdp ? { cdp: options.cdp } : {}
|
|
9774
10413
|
});
|
|
9775
10414
|
await launcher.prepare();
|
|
9776
10415
|
const guard = (work) => new Promise((resolve, reject) => {
|
|
@@ -9850,6 +10489,7 @@ async function createNodeKernel(options = {}) {
|
|
|
9850
10489
|
for (const client of [...clients]) client.detach();
|
|
9851
10490
|
for (const reject of [...pending]) reject(new Error(TERMINATED));
|
|
9852
10491
|
for (const kill of [...live]) kill();
|
|
10492
|
+
launcher.cdp.close();
|
|
9853
10493
|
launcher.unmountAll();
|
|
9854
10494
|
}
|
|
9855
10495
|
};
|