@ai-ecoverse/slicc-kernel 1.37.1 → 1.39.1
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 +3 -1
- package/dist/driver-worker.js +123 -0
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +309 -6
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +647 -214
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +123 -0
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node-process-worker.js +378 -101
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +578 -66
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +378 -101
- package/dist/process-worker.js.map +4 -4
- package/package.json +1 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -2032,9 +2032,80 @@ var SIGNAL_BY_NAME = {
|
|
|
2032
2032
|
// src/body-idle.ts
|
|
2033
2033
|
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
2034
2034
|
|
|
2035
|
+
// src/kernel/net/ws-queue.ts
|
|
2036
|
+
var INBOUND_LIMIT = 16 * 1024 * 1024;
|
|
2037
|
+
var MESSAGE_LIMIT = 64 * 1024 * 1024;
|
|
2038
|
+
var TOO_BIG = 1009;
|
|
2039
|
+
function payloadSize(message2) {
|
|
2040
|
+
return typeof message2 === "string" ? new TextEncoder().encode(message2).length : message2.length;
|
|
2041
|
+
}
|
|
2042
|
+
var MessageQueue = class {
|
|
2043
|
+
items = [];
|
|
2044
|
+
limit;
|
|
2045
|
+
waiter;
|
|
2046
|
+
size = 0;
|
|
2047
|
+
ended = false;
|
|
2048
|
+
overflow;
|
|
2049
|
+
taken;
|
|
2050
|
+
closed;
|
|
2051
|
+
settle;
|
|
2052
|
+
constructor(overflow, limit = INBOUND_LIMIT, taken) {
|
|
2053
|
+
this.overflow = overflow;
|
|
2054
|
+
this.limit = limit;
|
|
2055
|
+
this.taken = taken;
|
|
2056
|
+
this.closed = new Promise((resolve) => {
|
|
2057
|
+
this.settle = resolve;
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
push(message2) {
|
|
2061
|
+
if (this.ended) return;
|
|
2062
|
+
const size = payloadSize(message2);
|
|
2063
|
+
const huge = size > MESSAGE_LIMIT;
|
|
2064
|
+
if (huge || this.items.length > 0 && this.size + size > this.limit) {
|
|
2065
|
+
const reason = huge ? `a message is over ${MESSAGE_LIMIT} bytes` : `over ${this.limit} bytes of messages are waiting`;
|
|
2066
|
+
this.end({ code: TOO_BIG, reason });
|
|
2067
|
+
this.overflow();
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
this.size += size;
|
|
2071
|
+
this.items.push(message2);
|
|
2072
|
+
this.wake();
|
|
2073
|
+
}
|
|
2074
|
+
end(close) {
|
|
2075
|
+
if (this.ended) return;
|
|
2076
|
+
this.ended = true;
|
|
2077
|
+
this.settle(close);
|
|
2078
|
+
this.wake();
|
|
2079
|
+
}
|
|
2080
|
+
wake() {
|
|
2081
|
+
const waiter = this.waiter;
|
|
2082
|
+
this.waiter = void 0;
|
|
2083
|
+
waiter?.();
|
|
2084
|
+
}
|
|
2085
|
+
async *[Symbol.asyncIterator]() {
|
|
2086
|
+
for (; ; ) {
|
|
2087
|
+
const next = this.items.shift();
|
|
2088
|
+
if (next !== void 0) {
|
|
2089
|
+
const n = payloadSize(next);
|
|
2090
|
+
this.size -= n;
|
|
2091
|
+
this.taken?.(n);
|
|
2092
|
+
yield next;
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
if (this.ended) return;
|
|
2096
|
+
await new Promise((resolve) => {
|
|
2097
|
+
this.waiter = resolve;
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
2102
|
+
|
|
2035
2103
|
// src/transport.ts
|
|
2104
|
+
var SEND_BUFFER = 1024 * 1024;
|
|
2105
|
+
var CLOSE_WAIT_MS = 5e3;
|
|
2106
|
+
var NO_WEBSOCKET = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
|
|
2036
2107
|
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
2037
|
-
function serveTransport(peer, transport) {
|
|
2108
|
+
function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
2038
2109
|
const open = /* @__PURE__ */ new Map();
|
|
2039
2110
|
const fail = (nid, e) => {
|
|
2040
2111
|
open.delete(nid);
|
|
@@ -2075,6 +2146,96 @@ function serveTransport(peer, transport) {
|
|
|
2075
2146
|
fail(call.nid, e);
|
|
2076
2147
|
}
|
|
2077
2148
|
};
|
|
2149
|
+
const sockets = /* @__PURE__ */ new Map();
|
|
2150
|
+
const credit = /* @__PURE__ */ new Map();
|
|
2151
|
+
const refill = (nid, n, closing = false) => {
|
|
2152
|
+
const flow = credit.get(nid);
|
|
2153
|
+
if (!flow) return;
|
|
2154
|
+
flow.inflight -= n;
|
|
2155
|
+
if (closing && !flow.closing) {
|
|
2156
|
+
flow.closing = true;
|
|
2157
|
+
flow.deadline();
|
|
2158
|
+
}
|
|
2159
|
+
flow.wake?.();
|
|
2160
|
+
};
|
|
2161
|
+
const openSocket = async (call) => {
|
|
2162
|
+
const abort = new AbortController();
|
|
2163
|
+
open.set(call.nid, { abort });
|
|
2164
|
+
try {
|
|
2165
|
+
if (!transport.websocket) throw new Error(NO_WEBSOCKET);
|
|
2166
|
+
const socket = await transport.websocket({
|
|
2167
|
+
url: call.url,
|
|
2168
|
+
protocols: call.protocols,
|
|
2169
|
+
headers: call.headers,
|
|
2170
|
+
signal: abort.signal
|
|
2171
|
+
});
|
|
2172
|
+
if (!open.delete(call.nid)) {
|
|
2173
|
+
socket.close(1e3, "cancelled");
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
sockets.set(call.nid, socket);
|
|
2177
|
+
peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
|
|
2178
|
+
void relay(call.nid, socket);
|
|
2179
|
+
} catch (e) {
|
|
2180
|
+
fail(call.nid, e);
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
const relay = async (nid, socket) => {
|
|
2184
|
+
let ending;
|
|
2185
|
+
let late;
|
|
2186
|
+
const unanswered = new Promise((resolve) => {
|
|
2187
|
+
late = () => resolve("late");
|
|
2188
|
+
});
|
|
2189
|
+
const flow = {
|
|
2190
|
+
inflight: 0,
|
|
2191
|
+
closing: false,
|
|
2192
|
+
deadline: () => AbortSignal.timeout(closeWaitMs).addEventListener("abort", late, { once: true })
|
|
2193
|
+
};
|
|
2194
|
+
credit.set(nid, flow);
|
|
2195
|
+
const timedOut = { code: 1006, reason: "the far end did not answer the close" };
|
|
2196
|
+
let oversize = false;
|
|
2197
|
+
try {
|
|
2198
|
+
const messages = socket.messages[Symbol.asyncIterator]();
|
|
2199
|
+
for (; ; ) {
|
|
2200
|
+
const next = await Promise.race([messages.next(), unanswered]);
|
|
2201
|
+
if (next === "late" || next.done) break;
|
|
2202
|
+
const data = next.value;
|
|
2203
|
+
if (flow.closing) continue;
|
|
2204
|
+
if (payloadSize(data) > MESSAGE_LIMIT) {
|
|
2205
|
+
oversize = true;
|
|
2206
|
+
break;
|
|
2207
|
+
}
|
|
2208
|
+
if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
|
|
2209
|
+
else {
|
|
2210
|
+
const bytes = data.slice();
|
|
2211
|
+
peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
|
|
2212
|
+
}
|
|
2213
|
+
flow.inflight += payloadSize(data);
|
|
2214
|
+
while (flow.inflight > INBOUND_LIMIT && !flow.closing) {
|
|
2215
|
+
await new Promise((resolve) => {
|
|
2216
|
+
flow.wake = resolve;
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
if (oversize) socket.close(1e3, "a message is too big");
|
|
2221
|
+
ending = oversize ? { code: 1009, reason: `a message is over ${MESSAGE_LIMIT} bytes` } : await Promise.race([socket.closed, unanswered.then(() => timedOut)]);
|
|
2222
|
+
} catch (e) {
|
|
2223
|
+
socket.close(1e3, "the relay failed");
|
|
2224
|
+
ending = { code: 1011, reason: e instanceof Error ? e.message : String(e) };
|
|
2225
|
+
}
|
|
2226
|
+
sockets.delete(nid);
|
|
2227
|
+
credit.delete(nid);
|
|
2228
|
+
peer.postMessage({ net: "ws-closed", nid, ...ending });
|
|
2229
|
+
};
|
|
2230
|
+
const sendOn = async (call) => {
|
|
2231
|
+
const socket = sockets.get(call.nid);
|
|
2232
|
+
if (!socket) return;
|
|
2233
|
+
socket.send(call.data);
|
|
2234
|
+
while (socket.buffered > SEND_BUFFER && sockets.has(call.nid)) {
|
|
2235
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2236
|
+
}
|
|
2237
|
+
peer.postMessage({ net: "ws-acked", nid: call.nid, n: payloadSize(call.data) });
|
|
2238
|
+
};
|
|
2078
2239
|
const read = async (nid) => {
|
|
2079
2240
|
const body = open.get(nid)?.body;
|
|
2080
2241
|
if (!body) return;
|
|
@@ -2092,6 +2253,8 @@ function serveTransport(peer, transport) {
|
|
|
2092
2253
|
}
|
|
2093
2254
|
};
|
|
2094
2255
|
const drop = (nid) => {
|
|
2256
|
+
sockets.get(nid)?.close(1e3, "cancelled");
|
|
2257
|
+
refill(nid, 0, true);
|
|
2095
2258
|
const entry = open.get(nid);
|
|
2096
2259
|
open.delete(nid);
|
|
2097
2260
|
entry?.abort.abort();
|
|
@@ -2101,10 +2264,16 @@ function serveTransport(peer, transport) {
|
|
|
2101
2264
|
answer(call) {
|
|
2102
2265
|
if (call.net === "fetch") void start(call);
|
|
2103
2266
|
else if (call.net === "read") void read(call.nid);
|
|
2267
|
+
else if (call.net === "ws-open") void openSocket(call);
|
|
2268
|
+
else if (call.net === "ws-send") void sendOn(call);
|
|
2269
|
+
else if (call.net === "ws-close") {
|
|
2270
|
+
sockets.get(call.nid)?.close(call.code, call.reason);
|
|
2271
|
+
refill(call.nid, 0, true);
|
|
2272
|
+
} else if (call.net === "ws-consumed") refill(call.nid, call.n);
|
|
2104
2273
|
else drop(call.nid);
|
|
2105
2274
|
},
|
|
2106
2275
|
close() {
|
|
2107
|
-
for (const nid of [...open.keys()]) drop(nid);
|
|
2276
|
+
for (const nid of [...open.keys(), ...sockets.keys()]) drop(nid);
|
|
2108
2277
|
}
|
|
2109
2278
|
};
|
|
2110
2279
|
}
|
|
@@ -2494,6 +2663,10 @@ var Incoming = class {
|
|
|
2494
2663
|
this.buf = this.buf.subarray(n);
|
|
2495
2664
|
return out;
|
|
2496
2665
|
}
|
|
2666
|
+
async peek(signal) {
|
|
2667
|
+
if (this.buf.length === 0) await this.fill(signal);
|
|
2668
|
+
return this.buf[0];
|
|
2669
|
+
}
|
|
2497
2670
|
async some(max, signal) {
|
|
2498
2671
|
if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
|
|
2499
2672
|
if (this.eof) return new Uint8Array(0);
|
|
@@ -2675,13 +2848,219 @@ function chunk(bytes) {
|
|
|
2675
2848
|
}
|
|
2676
2849
|
var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
|
|
2677
2850
|
|
|
2851
|
+
// src/cdp/websocket.ts
|
|
2852
|
+
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
2853
|
+
var MAX_MESSAGE = 256 * 1024 * 1024;
|
|
2854
|
+
var CLOSE = {
|
|
2855
|
+
normal: 1e3,
|
|
2856
|
+
protocol: 1002,
|
|
2857
|
+
unsupported: 1003,
|
|
2858
|
+
invalid: 1007,
|
|
2859
|
+
tooBig: 1009,
|
|
2860
|
+
error: 1011
|
|
2861
|
+
};
|
|
2862
|
+
var OP_TEXT = 1;
|
|
2863
|
+
var OP_BINARY = 2;
|
|
2864
|
+
var OP = {
|
|
2865
|
+
continuation: 0,
|
|
2866
|
+
text: OP_TEXT,
|
|
2867
|
+
binary: OP_BINARY,
|
|
2868
|
+
close: 8,
|
|
2869
|
+
ping: 9,
|
|
2870
|
+
pong: 10
|
|
2871
|
+
};
|
|
2872
|
+
var WsError = class extends Error {
|
|
2873
|
+
code;
|
|
2874
|
+
constructor(code, message2) {
|
|
2875
|
+
super(message2);
|
|
2876
|
+
this.code = code;
|
|
2877
|
+
}
|
|
2878
|
+
};
|
|
2879
|
+
function sendableCode(code) {
|
|
2880
|
+
if (code >= 3e3 && code <= 4999) return true;
|
|
2881
|
+
return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
|
|
2882
|
+
}
|
|
2883
|
+
function field(headers, name2) {
|
|
2884
|
+
return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
|
|
2885
|
+
}
|
|
2886
|
+
async function acceptKey(headers) {
|
|
2887
|
+
const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
|
|
2888
|
+
if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
|
|
2889
|
+
throw new HttpError(400, "expected a WebSocket upgrade");
|
|
2890
|
+
}
|
|
2891
|
+
if (field(headers, "sec-websocket-version") !== "13") {
|
|
2892
|
+
throw new HttpError(400, "Sec-WebSocket-Version must be 13");
|
|
2893
|
+
}
|
|
2894
|
+
const key = field(headers, "sec-websocket-key") ?? "";
|
|
2895
|
+
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
|
|
2896
|
+
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
|
|
2897
|
+
return btoa(String.fromCharCode(...new Uint8Array(digest)));
|
|
2898
|
+
}
|
|
2899
|
+
function isUpgrade(headers) {
|
|
2900
|
+
return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
|
|
2901
|
+
}
|
|
2902
|
+
function frame(opcode, payload) {
|
|
2903
|
+
const n = payload.length;
|
|
2904
|
+
const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
|
|
2905
|
+
const out = new Uint8Array(size + n);
|
|
2906
|
+
const view = new DataView(out.buffer);
|
|
2907
|
+
out[0] = 128 | opcode;
|
|
2908
|
+
if (n < 126) out[1] = n;
|
|
2909
|
+
else if (n < 65536) {
|
|
2910
|
+
out[1] = 126;
|
|
2911
|
+
view.setUint16(2, n);
|
|
2912
|
+
} else {
|
|
2913
|
+
out[1] = 127;
|
|
2914
|
+
view.setBigUint64(2, BigInt(n));
|
|
2915
|
+
}
|
|
2916
|
+
out.set(payload, size);
|
|
2917
|
+
return out;
|
|
2918
|
+
}
|
|
2919
|
+
function closeFrame(code, reason = "") {
|
|
2920
|
+
let bytes = new TextEncoder().encode(reason);
|
|
2921
|
+
if (bytes.length > 123) {
|
|
2922
|
+
let end = 123;
|
|
2923
|
+
while ((bytes[end] & 192) === 128) end--;
|
|
2924
|
+
bytes = bytes.subarray(0, end);
|
|
2925
|
+
}
|
|
2926
|
+
const payload = new Uint8Array(2 + bytes.length);
|
|
2927
|
+
new DataView(payload.buffer).setUint16(0, code);
|
|
2928
|
+
payload.set(bytes, 2);
|
|
2929
|
+
return frame(OP.close, payload);
|
|
2930
|
+
}
|
|
2931
|
+
function pongFrame(payload) {
|
|
2932
|
+
return frame(OP.pong, payload);
|
|
2933
|
+
}
|
|
2934
|
+
async function fill(incoming, out, signal) {
|
|
2935
|
+
for (let at = 0; at < out.length; ) {
|
|
2936
|
+
const piece = await incoming.some(out.length - at, signal);
|
|
2937
|
+
if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
|
|
2938
|
+
out.set(piece, at);
|
|
2939
|
+
at += piece.length;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
var fatal = new TextDecoder("utf-8", { fatal: true });
|
|
2943
|
+
function decode(bytes) {
|
|
2944
|
+
try {
|
|
2945
|
+
return fatal.decode(bytes);
|
|
2946
|
+
} catch {
|
|
2947
|
+
throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
var WsReader = class {
|
|
2951
|
+
incoming;
|
|
2952
|
+
max;
|
|
2953
|
+
parts = [];
|
|
2954
|
+
size = 0;
|
|
2955
|
+
continuing = false;
|
|
2956
|
+
binaryMessage = false;
|
|
2957
|
+
binary;
|
|
2958
|
+
constructor(incoming, max = MAX_MESSAGE, binary) {
|
|
2959
|
+
this.incoming = incoming;
|
|
2960
|
+
this.max = max;
|
|
2961
|
+
this.binary = binary;
|
|
2962
|
+
}
|
|
2963
|
+
async next(signal) {
|
|
2964
|
+
for (; ; ) {
|
|
2965
|
+
const first = await this.incoming.some(1, signal);
|
|
2966
|
+
if (first.length === 0) return null;
|
|
2967
|
+
const head = new Uint8Array(2);
|
|
2968
|
+
head[0] = first[0];
|
|
2969
|
+
await fill(this.incoming, head.subarray(1), signal);
|
|
2970
|
+
const fin = (head[0] & 128) !== 0;
|
|
2971
|
+
const opcode = head[0] & 15;
|
|
2972
|
+
if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
|
|
2973
|
+
if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
|
|
2974
|
+
const length2 = await this.length(head[1] & 127, signal);
|
|
2975
|
+
const control = opcode >= 8;
|
|
2976
|
+
if (control && (length2 > 125 || !fin)) {
|
|
2977
|
+
throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
|
|
2978
|
+
}
|
|
2979
|
+
if (!control && this.size + length2 > this.max) {
|
|
2980
|
+
throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
2981
|
+
}
|
|
2982
|
+
const mask = new Uint8Array(4);
|
|
2983
|
+
await fill(this.incoming, mask, signal);
|
|
2984
|
+
const payload = new Uint8Array(length2);
|
|
2985
|
+
await fill(this.incoming, payload, signal);
|
|
2986
|
+
for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
2987
|
+
const event = this.take(opcode, fin, payload);
|
|
2988
|
+
if (event) return event;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
data(opcode, fin, payload) {
|
|
2992
|
+
if (opcode === OP.continuation !== this.continuing) {
|
|
2993
|
+
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
2994
|
+
}
|
|
2995
|
+
if (opcode !== OP.continuation) this.binaryMessage = opcode === OP.binary;
|
|
2996
|
+
this.parts.push(payload);
|
|
2997
|
+
this.size += payload.length;
|
|
2998
|
+
this.continuing = !fin;
|
|
2999
|
+
if (!fin) return void 0;
|
|
3000
|
+
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
3001
|
+
this.parts = [];
|
|
3002
|
+
this.size = 0;
|
|
3003
|
+
return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
|
|
3004
|
+
}
|
|
3005
|
+
async length(short, signal) {
|
|
3006
|
+
if (short < 126) return short;
|
|
3007
|
+
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
3008
|
+
await fill(this.incoming, ext, signal);
|
|
3009
|
+
const view = new DataView(ext.buffer);
|
|
3010
|
+
if (short === 126) return view.getUint16(0);
|
|
3011
|
+
const long = view.getBigUint64(0);
|
|
3012
|
+
return long > BigInt(this.max) ? this.max + 1 : Number(long);
|
|
3013
|
+
}
|
|
3014
|
+
take(opcode, fin, payload) {
|
|
3015
|
+
switch (opcode) {
|
|
3016
|
+
case OP.ping:
|
|
3017
|
+
return { kind: "ping", payload };
|
|
3018
|
+
case OP.pong:
|
|
3019
|
+
return void 0;
|
|
3020
|
+
case OP.close: {
|
|
3021
|
+
if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
|
|
3022
|
+
const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
|
|
3023
|
+
if (payload.length >= 2 && !sendableCode(code)) {
|
|
3024
|
+
throw new WsError(CLOSE.protocol, `close code ${code} may not be sent`);
|
|
3025
|
+
}
|
|
3026
|
+
return { kind: "close", code, reason: decode(payload.subarray(2)) };
|
|
3027
|
+
}
|
|
3028
|
+
case OP.binary:
|
|
3029
|
+
if (!this.binary) throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
3030
|
+
return this.data(opcode, fin, payload);
|
|
3031
|
+
case OP.text:
|
|
3032
|
+
case OP.continuation:
|
|
3033
|
+
return this.data(opcode, fin, payload);
|
|
3034
|
+
default:
|
|
3035
|
+
throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
function switchingHead(accept2, protocol = "") {
|
|
3040
|
+
const chosen = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
|
|
3041
|
+
` : "";
|
|
3042
|
+
return latin1Bytes(
|
|
3043
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
3044
|
+
Upgrade: websocket\r
|
|
3045
|
+
Connection: Upgrade\r
|
|
3046
|
+
Sec-WebSocket-Accept: ${accept2}\r
|
|
3047
|
+
${chosen}\r
|
|
3048
|
+
`
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
|
|
2678
3052
|
// src/kernel/net/proxy-service.ts
|
|
3053
|
+
var SEND_BUFFER2 = 1024 * 1024;
|
|
3054
|
+
var TLS_HANDSHAKE = 22;
|
|
3055
|
+
var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
3056
|
+
var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
|
|
2679
3057
|
var REALM_PROXY_PORT = 3128;
|
|
2680
3058
|
var DEFAULT_LIMITS = {
|
|
2681
3059
|
maxConnections: 64,
|
|
2682
3060
|
maxHead: 64 * 1024,
|
|
2683
3061
|
idleMs: 12e4,
|
|
2684
|
-
bodyBudget: 128 * 1024 * 1024
|
|
3062
|
+
bodyBudget: 128 * 1024 * 1024,
|
|
3063
|
+
closeWaitMs: 5e3
|
|
2685
3064
|
};
|
|
2686
3065
|
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
2687
3066
|
"connection",
|
|
@@ -2872,11 +3251,27 @@ function plainResponse(status, message2) {
|
|
|
2872
3251
|
cancel: async () => void 0
|
|
2873
3252
|
};
|
|
2874
3253
|
}
|
|
2875
|
-
function timedSource(conn,
|
|
3254
|
+
function timedSource(conn, idle, stop) {
|
|
2876
3255
|
return {
|
|
2877
|
-
read: (max) =>
|
|
3256
|
+
read: (max, signal) => {
|
|
3257
|
+
const own = signal ? [stop, signal] : [stop];
|
|
3258
|
+
if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
|
|
3259
|
+
return conn.read(max, AbortSignal.any(own));
|
|
3260
|
+
}
|
|
2878
3261
|
};
|
|
2879
3262
|
}
|
|
3263
|
+
function webSocketTarget(req) {
|
|
3264
|
+
return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
|
|
3265
|
+
}
|
|
3266
|
+
function wireCode(code) {
|
|
3267
|
+
if (code === 1005) return 1e3;
|
|
3268
|
+
return sendableCode(code) ? code : 1011;
|
|
3269
|
+
}
|
|
3270
|
+
function quietly(ms) {
|
|
3271
|
+
return new Promise((resolve) => {
|
|
3272
|
+
AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
2880
3275
|
var RealmProxy = class {
|
|
2881
3276
|
options;
|
|
2882
3277
|
listener;
|
|
@@ -2885,6 +3280,7 @@ var RealmProxy = class {
|
|
|
2885
3280
|
slots;
|
|
2886
3281
|
bodies;
|
|
2887
3282
|
connections = /* @__PURE__ */ new Set();
|
|
3283
|
+
warnedWebSocket = false;
|
|
2888
3284
|
closed;
|
|
2889
3285
|
port;
|
|
2890
3286
|
constructor(options) {
|
|
@@ -2933,9 +3329,10 @@ var RealmProxy = class {
|
|
|
2933
3329
|
}
|
|
2934
3330
|
async serve(conn) {
|
|
2935
3331
|
this.connections.add(conn);
|
|
2936
|
-
const
|
|
3332
|
+
const idle = { ms: this.limits.idleMs };
|
|
3333
|
+
const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
|
|
2937
3334
|
try {
|
|
2938
|
-
await this.requests({ sink: conn, incoming, conn, socket: conn });
|
|
3335
|
+
await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
|
|
2939
3336
|
} finally {
|
|
2940
3337
|
this.connections.delete(conn);
|
|
2941
3338
|
conn.close();
|
|
@@ -2962,7 +3359,11 @@ var RealmProxy = class {
|
|
|
2962
3359
|
const req = parseRequestHead(head);
|
|
2963
3360
|
if (req.method === "CONNECT") {
|
|
2964
3361
|
if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
|
|
2965
|
-
await this.connect(ctx.conn, incoming, req);
|
|
3362
|
+
await this.connect(ctx.conn, incoming, req, ctx.idle);
|
|
3363
|
+
return false;
|
|
3364
|
+
}
|
|
3365
|
+
if (req.method === "GET" && isUpgrade(req.headers)) {
|
|
3366
|
+
await this.websocket(ctx, req);
|
|
2966
3367
|
return false;
|
|
2967
3368
|
}
|
|
2968
3369
|
const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
|
|
@@ -3057,33 +3458,149 @@ var RealmProxy = class {
|
|
|
3057
3458
|
if (chunked) await conn.write(LAST_CHUNK, signal);
|
|
3058
3459
|
return keep;
|
|
3059
3460
|
}
|
|
3060
|
-
async connect(conn, incoming, req) {
|
|
3461
|
+
async connect(conn, incoming, req, idle) {
|
|
3061
3462
|
const target = tunnelTarget(req);
|
|
3062
|
-
const
|
|
3063
|
-
if (
|
|
3064
|
-
throw new HttpError(
|
|
3065
|
-
501,
|
|
3066
|
-
`CONNECT ${target.host}:${target.port}: no tunnels through this proxy`
|
|
3067
|
-
);
|
|
3068
|
-
}
|
|
3069
|
-
await this.options.tunnelReady?.().catch((e) => {
|
|
3070
|
-
throw new HttpError(
|
|
3071
|
-
501,
|
|
3072
|
-
`CONNECT ${target.host}:${target.port}: ${e instanceof Error ? e.message : String(e)}`
|
|
3073
|
-
);
|
|
3074
|
-
});
|
|
3463
|
+
const refused = target.port === 443 ? await this.tunnelReady(target) : void 0;
|
|
3464
|
+
if (refused) throw refused;
|
|
3075
3465
|
await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
|
|
3076
3466
|
try {
|
|
3467
|
+
const first = await incoming.peek(this.stop.signal);
|
|
3468
|
+
if (first === void 0) return;
|
|
3469
|
+
if (first !== TLS_HANDSHAKE) {
|
|
3470
|
+
const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
|
|
3471
|
+
await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
const tunnel = this.options.tunnel;
|
|
3475
|
+
if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
|
|
3077
3476
|
await tunnel(
|
|
3078
3477
|
conn,
|
|
3079
3478
|
incoming,
|
|
3080
3479
|
target,
|
|
3081
3480
|
this.stop.signal,
|
|
3082
|
-
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn })
|
|
3481
|
+
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
|
|
3083
3482
|
);
|
|
3084
3483
|
} catch {
|
|
3085
3484
|
}
|
|
3086
3485
|
}
|
|
3486
|
+
async tunnelReady(target) {
|
|
3487
|
+
const name2 = `CONNECT ${target.host}:${target.port}`;
|
|
3488
|
+
if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
|
|
3489
|
+
try {
|
|
3490
|
+
await this.options.tunnelReady?.();
|
|
3491
|
+
return void 0;
|
|
3492
|
+
} catch (e) {
|
|
3493
|
+
return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
async websocket(ctx, req) {
|
|
3497
|
+
const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
|
|
3498
|
+
const { transport } = this.options;
|
|
3499
|
+
if (!transport.traits.websocket || !transport.websocket) {
|
|
3500
|
+
if (!this.warnedWebSocket) {
|
|
3501
|
+
this.warnedWebSocket = true;
|
|
3502
|
+
console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
|
|
3503
|
+
}
|
|
3504
|
+
throw new HttpError(501, NO_WEBSOCKET2);
|
|
3505
|
+
}
|
|
3506
|
+
const accept2 = await acceptKey(req.headers);
|
|
3507
|
+
const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
|
|
3508
|
+
const hangup = new AbortController();
|
|
3509
|
+
const waiting = new AbortController();
|
|
3510
|
+
void watchHangup(ctx.socket, hangup, waiting.signal);
|
|
3511
|
+
let socket;
|
|
3512
|
+
try {
|
|
3513
|
+
socket = await transport.websocket({
|
|
3514
|
+
url: toHostLoopback(href).replace(/^http/, "ws"),
|
|
3515
|
+
protocols,
|
|
3516
|
+
headers: forwardRequestHeaders(req.headers),
|
|
3517
|
+
signal: AbortSignal.any([this.stop.signal, hangup.signal])
|
|
3518
|
+
});
|
|
3519
|
+
} catch (e) {
|
|
3520
|
+
waiting.abort();
|
|
3521
|
+
throw new HttpError(502, e instanceof Error ? e.message : String(e));
|
|
3522
|
+
}
|
|
3523
|
+
ctx.idle.ms = Number.POSITIVE_INFINITY;
|
|
3524
|
+
try {
|
|
3525
|
+
if (hangup.signal.aborted) throw hangup.signal.reason;
|
|
3526
|
+
if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
|
|
3527
|
+
throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
|
|
3528
|
+
}
|
|
3529
|
+
await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
|
|
3530
|
+
await this.bridge(ctx, socket, hangup.signal);
|
|
3531
|
+
} catch (e) {
|
|
3532
|
+
socket.close(1001, "the client went away");
|
|
3533
|
+
throw e;
|
|
3534
|
+
} finally {
|
|
3535
|
+
waiting.abort();
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
async bridge(ctx, socket, hangup) {
|
|
3539
|
+
const stop = this.stop.signal;
|
|
3540
|
+
let chain = Promise.resolve();
|
|
3541
|
+
const toGuest = (bytes) => {
|
|
3542
|
+
chain = chain.then(() => ctx.sink.write(bytes, stop));
|
|
3543
|
+
return chain;
|
|
3544
|
+
};
|
|
3545
|
+
const done4 = new AbortController();
|
|
3546
|
+
const reading = AbortSignal.any([stop, done4.signal, hangup]);
|
|
3547
|
+
const stopping = () => socket.close(1001, "the proxy is stopping");
|
|
3548
|
+
stop.addEventListener("abort", stopping, { once: true });
|
|
3549
|
+
let guestDone = false;
|
|
3550
|
+
let closeSent = false;
|
|
3551
|
+
const outbound2 = (async () => {
|
|
3552
|
+
for await (const data of socket.messages) {
|
|
3553
|
+
if (guestDone) continue;
|
|
3554
|
+
const text = typeof data === "string";
|
|
3555
|
+
await toGuest(
|
|
3556
|
+
frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
|
|
3557
|
+
);
|
|
3558
|
+
}
|
|
3559
|
+
const closed = await socket.closed;
|
|
3560
|
+
if (guestDone) return;
|
|
3561
|
+
closeSent = true;
|
|
3562
|
+
await toGuest(closeFrame(wireCode(closed.code), closed.reason));
|
|
3563
|
+
AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
|
|
3564
|
+
once: true
|
|
3565
|
+
});
|
|
3566
|
+
})().catch(() => done4.abort());
|
|
3567
|
+
const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
|
|
3568
|
+
try {
|
|
3569
|
+
for (; ; ) {
|
|
3570
|
+
const event = await reader.next(reading);
|
|
3571
|
+
if (!event) break;
|
|
3572
|
+
if (event.kind === "ping") {
|
|
3573
|
+
await toGuest(pongFrame(event.payload));
|
|
3574
|
+
continue;
|
|
3575
|
+
}
|
|
3576
|
+
if (event.kind === "close") {
|
|
3577
|
+
guestDone = true;
|
|
3578
|
+
if (closeSent) break;
|
|
3579
|
+
const code = wireCode(event.code);
|
|
3580
|
+
socket.close(code, event.reason);
|
|
3581
|
+
const reply = await Promise.race([
|
|
3582
|
+
socket.closed,
|
|
3583
|
+
quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
|
|
3584
|
+
]);
|
|
3585
|
+
await toGuest(closeFrame(wireCode(reply.code), reply.reason));
|
|
3586
|
+
break;
|
|
3587
|
+
}
|
|
3588
|
+
while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
|
|
3589
|
+
socket.send(event.kind === "text" ? event.text : event.bytes);
|
|
3590
|
+
}
|
|
3591
|
+
} catch (e) {
|
|
3592
|
+
if (e instanceof WsError) {
|
|
3593
|
+
guestDone = true;
|
|
3594
|
+
socket.close(e.code, e.message);
|
|
3595
|
+
await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
|
|
3596
|
+
}
|
|
3597
|
+
} finally {
|
|
3598
|
+
guestDone = true;
|
|
3599
|
+
socket.close(1e3, "the client went away");
|
|
3600
|
+
stop.removeEventListener("abort", stopping);
|
|
3601
|
+
await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3087
3604
|
};
|
|
3088
3605
|
|
|
3089
3606
|
// src/kernel/net/x509.ts
|
|
@@ -3813,12 +4330,15 @@ var RemoteTransport = class {
|
|
|
3813
4330
|
port;
|
|
3814
4331
|
waiting = /* @__PURE__ */ new Map();
|
|
3815
4332
|
nextId = 0;
|
|
4333
|
+
sockets = /* @__PURE__ */ new Map();
|
|
3816
4334
|
failure;
|
|
3817
4335
|
constructor(port, traits) {
|
|
3818
4336
|
this.port = port;
|
|
3819
4337
|
this.traits = traits;
|
|
3820
4338
|
}
|
|
3821
4339
|
receive(reply) {
|
|
4340
|
+
const socket = this.sockets.get(reply.nid);
|
|
4341
|
+
if (socket && this.toSocket(reply, socket)) return;
|
|
3822
4342
|
const waiter = this.waiting.get(reply.nid);
|
|
3823
4343
|
if (!waiter) return;
|
|
3824
4344
|
this.waiting.delete(reply.nid);
|
|
@@ -3826,10 +4346,22 @@ var RemoteTransport = class {
|
|
|
3826
4346
|
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
3827
4347
|
} else waiter.resolve(reply);
|
|
3828
4348
|
}
|
|
4349
|
+
toSocket(reply, socket) {
|
|
4350
|
+
if (reply.net === "ws-message") socket.queue.push(reply.data);
|
|
4351
|
+
else if (reply.net === "ws-acked") socket.acked(reply.n);
|
|
4352
|
+
else if (reply.net === "ws-closed") {
|
|
4353
|
+
this.sockets.delete(reply.nid);
|
|
4354
|
+
socket.queue.end({ code: reply.code, reason: reply.reason });
|
|
4355
|
+
} else return false;
|
|
4356
|
+
return true;
|
|
4357
|
+
}
|
|
3829
4358
|
fail(error) {
|
|
3830
4359
|
this.failure = error;
|
|
3831
4360
|
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
3832
4361
|
this.waiting.clear();
|
|
4362
|
+
for (const socket of this.sockets.values())
|
|
4363
|
+
socket.queue.end({ code: 1011, reason: error.message });
|
|
4364
|
+
this.sockets.clear();
|
|
3833
4365
|
}
|
|
3834
4366
|
ask(call) {
|
|
3835
4367
|
if (this.failure) return Promise.reject(this.failure);
|
|
@@ -3908,6 +4440,46 @@ var RemoteTransport = class {
|
|
|
3908
4440
|
}
|
|
3909
4441
|
};
|
|
3910
4442
|
}
|
|
4443
|
+
async websocket(request2) {
|
|
4444
|
+
const nid = ++this.nextId;
|
|
4445
|
+
const { signal } = request2;
|
|
4446
|
+
signal.throwIfAborted();
|
|
4447
|
+
const abort = () => this.cancel(nid);
|
|
4448
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
4449
|
+
let opened2;
|
|
4450
|
+
try {
|
|
4451
|
+
opened2 = await this.ask({
|
|
4452
|
+
net: "ws-open",
|
|
4453
|
+
nid,
|
|
4454
|
+
url: request2.url,
|
|
4455
|
+
protocols: request2.protocols,
|
|
4456
|
+
headers: request2.headers
|
|
4457
|
+
});
|
|
4458
|
+
} finally {
|
|
4459
|
+
signal.removeEventListener("abort", abort);
|
|
4460
|
+
}
|
|
4461
|
+
const close = (code, reason) => this.port.postMessage({ net: "ws-close", nid, code, reason });
|
|
4462
|
+
const queue = new MessageQueue(
|
|
4463
|
+
() => close(1e3, "a message is too big"),
|
|
4464
|
+
Number.POSITIVE_INFINITY,
|
|
4465
|
+
(n) => this.port.postMessage({ net: "ws-consumed", nid, n })
|
|
4466
|
+
);
|
|
4467
|
+
let unacked = 0;
|
|
4468
|
+
this.sockets.set(nid, { queue, acked: (n) => void (unacked -= n) });
|
|
4469
|
+
return {
|
|
4470
|
+
protocol: opened2.protocol,
|
|
4471
|
+
get buffered() {
|
|
4472
|
+
return unacked;
|
|
4473
|
+
},
|
|
4474
|
+
send: (data) => {
|
|
4475
|
+
unacked += payloadSize(data);
|
|
4476
|
+
this.port.postMessage({ net: "ws-send", nid, data });
|
|
4477
|
+
},
|
|
4478
|
+
messages: queue,
|
|
4479
|
+
close,
|
|
4480
|
+
closed: queue.closed
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
3911
4483
|
};
|
|
3912
4484
|
|
|
3913
4485
|
// src/kernel/net/routes.ts
|
|
@@ -4086,183 +4658,10 @@ var RemoteUplink = class {
|
|
|
4086
4658
|
var CDP_PORT = 9222;
|
|
4087
4659
|
var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
|
|
4088
4660
|
|
|
4089
|
-
// src/cdp/websocket.ts
|
|
4090
|
-
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
4091
|
-
var MAX_MESSAGE = 256 * 1024 * 1024;
|
|
4092
|
-
var CLOSE = {
|
|
4093
|
-
normal: 1e3,
|
|
4094
|
-
protocol: 1002,
|
|
4095
|
-
unsupported: 1003,
|
|
4096
|
-
invalid: 1007,
|
|
4097
|
-
tooBig: 1009,
|
|
4098
|
-
error: 1011
|
|
4099
|
-
};
|
|
4100
|
-
var OP_TEXT = 1;
|
|
4101
|
-
var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
|
|
4102
|
-
var WsError = class extends Error {
|
|
4103
|
-
code;
|
|
4104
|
-
constructor(code, message2) {
|
|
4105
|
-
super(message2);
|
|
4106
|
-
this.code = code;
|
|
4107
|
-
}
|
|
4108
|
-
};
|
|
4109
|
-
function field(headers, name2) {
|
|
4110
|
-
return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
|
|
4111
|
-
}
|
|
4112
|
-
async function acceptKey(headers) {
|
|
4113
|
-
const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
|
|
4114
|
-
if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
|
|
4115
|
-
throw new HttpError(400, "expected a WebSocket upgrade");
|
|
4116
|
-
}
|
|
4117
|
-
if (field(headers, "sec-websocket-version") !== "13") {
|
|
4118
|
-
throw new HttpError(400, "Sec-WebSocket-Version must be 13");
|
|
4119
|
-
}
|
|
4120
|
-
const key = field(headers, "sec-websocket-key") ?? "";
|
|
4121
|
-
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
|
|
4122
|
-
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
|
|
4123
|
-
return btoa(String.fromCharCode(...new Uint8Array(digest)));
|
|
4124
|
-
}
|
|
4125
|
-
function isUpgrade(headers) {
|
|
4126
|
-
return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
|
|
4127
|
-
}
|
|
4128
|
-
function frame(opcode, payload) {
|
|
4129
|
-
const n = payload.length;
|
|
4130
|
-
const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
|
|
4131
|
-
const out = new Uint8Array(size + n);
|
|
4132
|
-
const view = new DataView(out.buffer);
|
|
4133
|
-
out[0] = 128 | opcode;
|
|
4134
|
-
if (n < 126) out[1] = n;
|
|
4135
|
-
else if (n < 65536) {
|
|
4136
|
-
out[1] = 126;
|
|
4137
|
-
view.setUint16(2, n);
|
|
4138
|
-
} else {
|
|
4139
|
-
out[1] = 127;
|
|
4140
|
-
view.setBigUint64(2, BigInt(n));
|
|
4141
|
-
}
|
|
4142
|
-
out.set(payload, size);
|
|
4143
|
-
return out;
|
|
4144
|
-
}
|
|
4145
|
-
function closeFrame(code, reason = "") {
|
|
4146
|
-
let bytes = new TextEncoder().encode(reason);
|
|
4147
|
-
if (bytes.length > 123) bytes = bytes.subarray(0, 123);
|
|
4148
|
-
const payload = new Uint8Array(2 + bytes.length);
|
|
4149
|
-
new DataView(payload.buffer).setUint16(0, code);
|
|
4150
|
-
payload.set(bytes, 2);
|
|
4151
|
-
return frame(OP.close, payload);
|
|
4152
|
-
}
|
|
4153
|
-
function pongFrame(payload) {
|
|
4154
|
-
return frame(OP.pong, payload);
|
|
4155
|
-
}
|
|
4156
|
-
async function fill(incoming, out, signal) {
|
|
4157
|
-
for (let at = 0; at < out.length; ) {
|
|
4158
|
-
const piece = await incoming.some(out.length - at, signal);
|
|
4159
|
-
if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
|
|
4160
|
-
out.set(piece, at);
|
|
4161
|
-
at += piece.length;
|
|
4162
|
-
}
|
|
4163
|
-
}
|
|
4164
|
-
var fatal = new TextDecoder("utf-8", { fatal: true });
|
|
4165
|
-
function decode(bytes) {
|
|
4166
|
-
try {
|
|
4167
|
-
return fatal.decode(bytes);
|
|
4168
|
-
} catch {
|
|
4169
|
-
throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
|
|
4170
|
-
}
|
|
4171
|
-
}
|
|
4172
|
-
var WsReader = class {
|
|
4173
|
-
incoming;
|
|
4174
|
-
max;
|
|
4175
|
-
parts = [];
|
|
4176
|
-
size = 0;
|
|
4177
|
-
continuing = false;
|
|
4178
|
-
constructor(incoming, max = MAX_MESSAGE) {
|
|
4179
|
-
this.incoming = incoming;
|
|
4180
|
-
this.max = max;
|
|
4181
|
-
}
|
|
4182
|
-
async next(signal) {
|
|
4183
|
-
for (; ; ) {
|
|
4184
|
-
const first = await this.incoming.some(1, signal);
|
|
4185
|
-
if (first.length === 0) return null;
|
|
4186
|
-
const head = new Uint8Array(2);
|
|
4187
|
-
head[0] = first[0];
|
|
4188
|
-
await fill(this.incoming, head.subarray(1), signal);
|
|
4189
|
-
const fin = (head[0] & 128) !== 0;
|
|
4190
|
-
const opcode = head[0] & 15;
|
|
4191
|
-
if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
|
|
4192
|
-
if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
|
|
4193
|
-
const length2 = await this.length(head[1] & 127, signal);
|
|
4194
|
-
const control = opcode >= 8;
|
|
4195
|
-
if (control && (length2 > 125 || !fin)) {
|
|
4196
|
-
throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
|
|
4197
|
-
}
|
|
4198
|
-
if (!control && this.size + length2 > this.max) {
|
|
4199
|
-
throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
4200
|
-
}
|
|
4201
|
-
const mask = new Uint8Array(4);
|
|
4202
|
-
await fill(this.incoming, mask, signal);
|
|
4203
|
-
const payload = new Uint8Array(length2);
|
|
4204
|
-
await fill(this.incoming, payload, signal);
|
|
4205
|
-
for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
4206
|
-
const event = this.take(opcode, fin, payload);
|
|
4207
|
-
if (event) return event;
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
async length(short, signal) {
|
|
4211
|
-
if (short < 126) return short;
|
|
4212
|
-
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
4213
|
-
await fill(this.incoming, ext, signal);
|
|
4214
|
-
const view = new DataView(ext.buffer);
|
|
4215
|
-
if (short === 126) return view.getUint16(0);
|
|
4216
|
-
const long = view.getBigUint64(0);
|
|
4217
|
-
return long > BigInt(this.max) ? this.max + 1 : Number(long);
|
|
4218
|
-
}
|
|
4219
|
-
take(opcode, fin, payload) {
|
|
4220
|
-
switch (opcode) {
|
|
4221
|
-
case OP.ping:
|
|
4222
|
-
return { kind: "ping", payload };
|
|
4223
|
-
case OP.pong:
|
|
4224
|
-
return void 0;
|
|
4225
|
-
case OP.close: {
|
|
4226
|
-
if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
|
|
4227
|
-
const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
|
|
4228
|
-
return { kind: "close", code, reason: decode(payload.subarray(2)) };
|
|
4229
|
-
}
|
|
4230
|
-
case OP.text:
|
|
4231
|
-
case OP.continuation: {
|
|
4232
|
-
if (opcode === OP.continuation !== this.continuing) {
|
|
4233
|
-
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
4234
|
-
}
|
|
4235
|
-
this.parts.push(payload);
|
|
4236
|
-
this.size += payload.length;
|
|
4237
|
-
this.continuing = !fin;
|
|
4238
|
-
if (!fin) return void 0;
|
|
4239
|
-
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
4240
|
-
this.parts = [];
|
|
4241
|
-
this.size = 0;
|
|
4242
|
-
return { kind: "text", text: decode(whole) };
|
|
4243
|
-
}
|
|
4244
|
-
case OP.binary:
|
|
4245
|
-
throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
4246
|
-
default:
|
|
4247
|
-
throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
|
|
4248
|
-
}
|
|
4249
|
-
}
|
|
4250
|
-
};
|
|
4251
|
-
function switchingHead(accept2) {
|
|
4252
|
-
return latin1Bytes(
|
|
4253
|
-
`HTTP/1.1 101 Switching Protocols\r
|
|
4254
|
-
Upgrade: websocket\r
|
|
4255
|
-
Connection: Upgrade\r
|
|
4256
|
-
Sec-WebSocket-Accept: ${accept2}\r
|
|
4257
|
-
\r
|
|
4258
|
-
`
|
|
4259
|
-
);
|
|
4260
|
-
}
|
|
4261
|
-
|
|
4262
4661
|
// src/cdp/facade.ts
|
|
4263
4662
|
var MAX_HEAD = 64 * 1024;
|
|
4264
4663
|
var LIST_TIMEOUT = 1e4;
|
|
4265
|
-
var
|
|
4664
|
+
var CLOSE_WAIT_MS2 = 1e3;
|
|
4266
4665
|
function respond(status, body, type = "text/plain; charset=utf-8") {
|
|
4267
4666
|
const bytes = new TextEncoder().encode(body);
|
|
4268
4667
|
const head = latin1Bytes(
|
|
@@ -4410,7 +4809,7 @@ var CdpFacade = class {
|
|
|
4410
4809
|
const host = await this.host(runtime);
|
|
4411
4810
|
const limits = {
|
|
4412
4811
|
max: this.options.maxMessage ?? MAX_MESSAGE,
|
|
4413
|
-
closeWaitMs: this.options.closeWaitMs ??
|
|
4812
|
+
closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS2
|
|
4414
4813
|
};
|
|
4415
4814
|
await new Relay(conn, incoming, host, limits, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
|
|
4416
4815
|
}
|
|
@@ -9060,7 +9459,42 @@ var Dying = class {
|
|
|
9060
9459
|
return [typeof reported === "number" ? reported : CRASHED, void 0];
|
|
9061
9460
|
}
|
|
9062
9461
|
};
|
|
9063
|
-
function
|
|
9462
|
+
function notifiers(header2) {
|
|
9463
|
+
return {
|
|
9464
|
+
onPending(sig) {
|
|
9465
|
+
Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig));
|
|
9466
|
+
Atomics.notify(header2, SAB_I_SIGNALS);
|
|
9467
|
+
},
|
|
9468
|
+
onAsync() {
|
|
9469
|
+
Atomics.add(header2, SAB_I_ASYNC, 1);
|
|
9470
|
+
Atomics.notify(header2, SAB_I_ASYNC);
|
|
9471
|
+
}
|
|
9472
|
+
};
|
|
9473
|
+
}
|
|
9474
|
+
function publishStops(process, header2) {
|
|
9475
|
+
process.onState((state) => {
|
|
9476
|
+
Atomics.store(header2, SAB_I_STOP, state === "stopped" ? 1 : 0);
|
|
9477
|
+
Atomics.notify(header2, SAB_I_STOP);
|
|
9478
|
+
});
|
|
9479
|
+
}
|
|
9480
|
+
function transferOf(opts, lane) {
|
|
9481
|
+
const transfer = opts.fork ? [opts.fork.memory.buffer] : [];
|
|
9482
|
+
return lane ? [...transfer, lane.port] : transfer;
|
|
9483
|
+
}
|
|
9484
|
+
function openLane(token, dispatch2) {
|
|
9485
|
+
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
9486
|
+
const { port1, port2 } = new MessageChannel();
|
|
9487
|
+
const responder = attachSyncSabResponder(port1, sab, token, { dispatch: dispatch2 });
|
|
9488
|
+
port1.start();
|
|
9489
|
+
return {
|
|
9490
|
+
lane: { sab, port: port2 },
|
|
9491
|
+
dispose() {
|
|
9492
|
+
responder.dispose();
|
|
9493
|
+
port1.close();
|
|
9494
|
+
}
|
|
9495
|
+
};
|
|
9496
|
+
}
|
|
9497
|
+
function processInit(opts, sab, umask, lane) {
|
|
9064
9498
|
const base = {
|
|
9065
9499
|
type: WASM_PROCESS_INIT,
|
|
9066
9500
|
pid: opts.shownPid ?? opts.pid,
|
|
@@ -9073,7 +9507,12 @@ function processInit(opts, sab, umask) {
|
|
|
9073
9507
|
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
|
|
9074
9508
|
};
|
|
9075
9509
|
if (opts.program.abi === "js") {
|
|
9076
|
-
return {
|
|
9510
|
+
return {
|
|
9511
|
+
...base,
|
|
9512
|
+
program: opts.program,
|
|
9513
|
+
fds: inheritedFds(opts.fds),
|
|
9514
|
+
lane
|
|
9515
|
+
};
|
|
9077
9516
|
}
|
|
9078
9517
|
return {
|
|
9079
9518
|
...base,
|
|
@@ -9125,23 +9564,13 @@ function spawnWasmProcess(opts) {
|
|
|
9125
9564
|
locks: opts.locks,
|
|
9126
9565
|
onReap: opts.onReap,
|
|
9127
9566
|
resolver: opts.resolver,
|
|
9128
|
-
|
|
9129
|
-
Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig));
|
|
9130
|
-
Atomics.notify(header2, SAB_I_SIGNALS);
|
|
9131
|
-
},
|
|
9132
|
-
onAsync: () => {
|
|
9133
|
-
Atomics.add(header2, SAB_I_ASYNC, 1);
|
|
9134
|
-
Atomics.notify(header2, SAB_I_ASYNC);
|
|
9135
|
-
},
|
|
9567
|
+
...notifiers(header2),
|
|
9136
9568
|
onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
|
|
9137
9569
|
hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
|
|
9138
9570
|
raise: (sig) => signal(sig),
|
|
9139
9571
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
|
|
9140
9572
|
});
|
|
9141
|
-
process
|
|
9142
|
-
Atomics.store(header2, SAB_I_STOP, state === "stopped" ? 1 : 0);
|
|
9143
|
-
Atomics.notify(header2, SAB_I_STOP);
|
|
9144
|
-
});
|
|
9573
|
+
publishStops(process, header2);
|
|
9145
9574
|
const holds = new HeldPaths();
|
|
9146
9575
|
opts.held?.add(holds);
|
|
9147
9576
|
const token = holdingToken(opts, holds);
|
|
@@ -9150,6 +9579,7 @@ function spawnWasmProcess(opts) {
|
|
|
9150
9579
|
const dying = new Dying();
|
|
9151
9580
|
const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
|
|
9152
9581
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
|
|
9582
|
+
const lane = opts.program.abi === "js" ? openLane(token, dispatch2) : void 0;
|
|
9153
9583
|
const { promise: exited, resolve: settle3 } = Promise.withResolvers();
|
|
9154
9584
|
let done4 = false;
|
|
9155
9585
|
let endedBy;
|
|
@@ -9162,6 +9592,7 @@ function spawnWasmProcess(opts) {
|
|
|
9162
9592
|
worker.removeEventListener("error", onError);
|
|
9163
9593
|
for (const tid of [...threads.keys()]) endThread(tid);
|
|
9164
9594
|
responder.dispose();
|
|
9595
|
+
lane?.dispose();
|
|
9165
9596
|
revokeSyncFsToken(token);
|
|
9166
9597
|
opts.held?.delete(holds);
|
|
9167
9598
|
worker.terminate();
|
|
@@ -9232,8 +9663,10 @@ function spawnWasmProcess(opts) {
|
|
|
9232
9663
|
};
|
|
9233
9664
|
tw.postMessage(tinit);
|
|
9234
9665
|
};
|
|
9235
|
-
|
|
9236
|
-
|
|
9666
|
+
worker.postMessage(
|
|
9667
|
+
processInit(opts, sab, process.umask, lane?.lane),
|
|
9668
|
+
transferOf(opts, lane?.lane)
|
|
9669
|
+
);
|
|
9237
9670
|
const signal = (sig) => {
|
|
9238
9671
|
if (process.signal(sig) !== "terminate") return;
|
|
9239
9672
|
const code = sig === SIG.KILL ? 137 : 128 + sig;
|