@ai-ecoverse/slicc-kernel 1.37.0 → 1.39.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 +4 -2
- 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 +1028 -319
- 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 +399 -111
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +904 -116
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +399 -111
- 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
|
|
@@ -4022,247 +4594,74 @@ var RemoteUplink = class {
|
|
|
4022
4594
|
}
|
|
4023
4595
|
receive(reply) {
|
|
4024
4596
|
const waiter = this.waiting.get(reply.uid);
|
|
4025
|
-
if (!waiter) return;
|
|
4026
|
-
this.waiting.delete(reply.uid);
|
|
4027
|
-
if (reply.uplink === "error") {
|
|
4028
|
-
waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));
|
|
4029
|
-
} else waiter.resolve(reply);
|
|
4030
|
-
}
|
|
4031
|
-
ask(call, signal, transfer) {
|
|
4032
|
-
const waiter = Promise.withResolvers();
|
|
4033
|
-
this.waiting.set(call.uid, waiter);
|
|
4034
|
-
const abort = () => {
|
|
4035
|
-
this.waiting.delete(call.uid);
|
|
4036
|
-
this.port.postMessage({ uplink: "cancel", uid: call.uid });
|
|
4037
|
-
waiter.reject(signal?.reason);
|
|
4038
|
-
};
|
|
4039
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
4040
|
-
const settled = () => signal?.removeEventListener("abort", abort);
|
|
4041
|
-
waiter.promise.then(settled, settled);
|
|
4042
|
-
this.port.postMessage(call, transfer);
|
|
4043
|
-
return waiter.promise;
|
|
4044
|
-
}
|
|
4045
|
-
async resolve(name2, family, signal) {
|
|
4046
|
-
const reply = await this.ask(
|
|
4047
|
-
{ uplink: "resolve", uid: ++this.nextId, name: name2, family },
|
|
4048
|
-
signal
|
|
4049
|
-
);
|
|
4050
|
-
return answerOf(reply);
|
|
4051
|
-
}
|
|
4052
|
-
async dial(req) {
|
|
4053
|
-
req.signal.throwIfAborted();
|
|
4054
|
-
const { host, port } = req;
|
|
4055
|
-
const opened2 = await this.ask(
|
|
4056
|
-
{ uplink: "dial", uid: ++this.nextId, host, port },
|
|
4057
|
-
req.signal
|
|
4058
|
-
);
|
|
4059
|
-
const { cid, localAddr, remoteAddr } = opened2;
|
|
4060
|
-
return {
|
|
4061
|
-
localAddr,
|
|
4062
|
-
remoteAddr,
|
|
4063
|
-
read: async () => {
|
|
4064
|
-
const got = await this.ask({
|
|
4065
|
-
uplink: "read",
|
|
4066
|
-
uid: ++this.nextId,
|
|
4067
|
-
cid
|
|
4068
|
-
});
|
|
4069
|
-
return got.bytes;
|
|
4070
|
-
},
|
|
4071
|
-
write: async (bytes) => {
|
|
4072
|
-
const copy = bytes.slice();
|
|
4073
|
-
const call = { uplink: "write", uid: ++this.nextId, cid, bytes: copy };
|
|
4074
|
-
const done4 = await this.ask(call, void 0, [
|
|
4075
|
-
copy.buffer
|
|
4076
|
-
]);
|
|
4077
|
-
return done4.n;
|
|
4078
|
-
},
|
|
4079
|
-
closeWrite: () => this.port.postMessage({ uplink: "close-write", cid }),
|
|
4080
|
-
close: () => this.port.postMessage({ uplink: "close", cid })
|
|
4081
|
-
};
|
|
4082
|
-
}
|
|
4083
|
-
};
|
|
4084
|
-
|
|
4085
|
-
// src/cdp/types.ts
|
|
4086
|
-
var CDP_PORT = 9222;
|
|
4087
|
-
var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
|
|
4088
|
-
|
|
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;
|
|
4597
|
+
if (!waiter) return;
|
|
4598
|
+
this.waiting.delete(reply.uid);
|
|
4599
|
+
if (reply.uplink === "error") {
|
|
4600
|
+
waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));
|
|
4601
|
+
} else waiter.resolve(reply);
|
|
4181
4602
|
}
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
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
|
-
}
|
|
4603
|
+
ask(call, signal, transfer) {
|
|
4604
|
+
const waiter = Promise.withResolvers();
|
|
4605
|
+
this.waiting.set(call.uid, waiter);
|
|
4606
|
+
const abort = () => {
|
|
4607
|
+
this.waiting.delete(call.uid);
|
|
4608
|
+
this.port.postMessage({ uplink: "cancel", uid: call.uid });
|
|
4609
|
+
waiter.reject(signal?.reason);
|
|
4610
|
+
};
|
|
4611
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4612
|
+
const settled = () => signal?.removeEventListener("abort", abort);
|
|
4613
|
+
waiter.promise.then(settled, settled);
|
|
4614
|
+
this.port.postMessage(call, transfer);
|
|
4615
|
+
return waiter.promise;
|
|
4209
4616
|
}
|
|
4210
|
-
async
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
const long = view.getBigUint64(0);
|
|
4217
|
-
return long > BigInt(this.max) ? this.max + 1 : Number(long);
|
|
4617
|
+
async resolve(name2, family, signal) {
|
|
4618
|
+
const reply = await this.ask(
|
|
4619
|
+
{ uplink: "resolve", uid: ++this.nextId, name: name2, family },
|
|
4620
|
+
signal
|
|
4621
|
+
);
|
|
4622
|
+
return answerOf(reply);
|
|
4218
4623
|
}
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
const
|
|
4240
|
-
this.
|
|
4241
|
-
this.
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
}
|
|
4624
|
+
async dial(req) {
|
|
4625
|
+
req.signal.throwIfAborted();
|
|
4626
|
+
const { host, port } = req;
|
|
4627
|
+
const opened2 = await this.ask(
|
|
4628
|
+
{ uplink: "dial", uid: ++this.nextId, host, port },
|
|
4629
|
+
req.signal
|
|
4630
|
+
);
|
|
4631
|
+
const { cid, localAddr, remoteAddr } = opened2;
|
|
4632
|
+
return {
|
|
4633
|
+
localAddr,
|
|
4634
|
+
remoteAddr,
|
|
4635
|
+
read: async () => {
|
|
4636
|
+
const got = await this.ask({
|
|
4637
|
+
uplink: "read",
|
|
4638
|
+
uid: ++this.nextId,
|
|
4639
|
+
cid
|
|
4640
|
+
});
|
|
4641
|
+
return got.bytes;
|
|
4642
|
+
},
|
|
4643
|
+
write: async (bytes) => {
|
|
4644
|
+
const copy = bytes.slice();
|
|
4645
|
+
const call = { uplink: "write", uid: ++this.nextId, cid, bytes: copy };
|
|
4646
|
+
const done4 = await this.ask(call, void 0, [
|
|
4647
|
+
copy.buffer
|
|
4648
|
+
]);
|
|
4649
|
+
return done4.n;
|
|
4650
|
+
},
|
|
4651
|
+
closeWrite: () => this.port.postMessage({ uplink: "close-write", cid }),
|
|
4652
|
+
close: () => this.port.postMessage({ uplink: "close", cid })
|
|
4653
|
+
};
|
|
4249
4654
|
}
|
|
4250
4655
|
};
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
Connection: Upgrade\r
|
|
4256
|
-
Sec-WebSocket-Accept: ${accept2}\r
|
|
4257
|
-
\r
|
|
4258
|
-
`
|
|
4259
|
-
);
|
|
4260
|
-
}
|
|
4656
|
+
|
|
4657
|
+
// src/cdp/types.ts
|
|
4658
|
+
var CDP_PORT = 9222;
|
|
4659
|
+
var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
|
|
4261
4660
|
|
|
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
|
}
|
|
@@ -4878,13 +5277,193 @@ function withCommandDirs(fs, names) {
|
|
|
4878
5277
|
};
|
|
4879
5278
|
}
|
|
4880
5279
|
|
|
5280
|
+
// src/fs/unlinked.ts
|
|
5281
|
+
var UNLINKED_DIR = ".slicc-unlinked";
|
|
5282
|
+
function webLocks() {
|
|
5283
|
+
return globalThis.navigator?.locks;
|
|
5284
|
+
}
|
|
5285
|
+
var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
5286
|
+
var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
5287
|
+
var lockName = (instance) => `slicc-unlinked:${instance}`;
|
|
5288
|
+
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
5289
|
+
var PathGate = class {
|
|
5290
|
+
tails = /* @__PURE__ */ new Map();
|
|
5291
|
+
async acquire(path) {
|
|
5292
|
+
const prev = this.tails.get(path) ?? Promise.resolve();
|
|
5293
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
5294
|
+
const tail = prev.then(() => promise);
|
|
5295
|
+
this.tails.set(path, tail);
|
|
5296
|
+
await prev;
|
|
5297
|
+
return () => {
|
|
5298
|
+
resolve();
|
|
5299
|
+
if (this.tails.get(path) === tail) this.tails.delete(path);
|
|
5300
|
+
};
|
|
5301
|
+
}
|
|
5302
|
+
async run(path, op) {
|
|
5303
|
+
const release = await this.acquire(path);
|
|
5304
|
+
try {
|
|
5305
|
+
return await op();
|
|
5306
|
+
} finally {
|
|
5307
|
+
release();
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
};
|
|
5311
|
+
var UnlinkedKeeper = class {
|
|
5312
|
+
fs;
|
|
5313
|
+
holders;
|
|
5314
|
+
rootOf;
|
|
5315
|
+
locks;
|
|
5316
|
+
busy;
|
|
5317
|
+
waiting = /* @__PURE__ */ new Set();
|
|
5318
|
+
instance = crypto.randomUUID();
|
|
5319
|
+
gate = new PathGate();
|
|
5320
|
+
queue = Promise.resolve();
|
|
5321
|
+
count = 0;
|
|
5322
|
+
constructor(fs, holders, rootOf, locks, busy = () => false) {
|
|
5323
|
+
this.fs = fs;
|
|
5324
|
+
this.holders = holders;
|
|
5325
|
+
this.rootOf = rootOf;
|
|
5326
|
+
this.locks = locks;
|
|
5327
|
+
this.busy = busy;
|
|
5328
|
+
void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
|
|
5329
|
+
}
|
|
5330
|
+
serial(op) {
|
|
5331
|
+
const next = this.queue.then(op);
|
|
5332
|
+
this.queue = next.catch(() => void 0);
|
|
5333
|
+
return next;
|
|
5334
|
+
}
|
|
5335
|
+
wrap() {
|
|
5336
|
+
const fs = this.fs;
|
|
5337
|
+
const keeper = this;
|
|
5338
|
+
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
5339
|
+
return {
|
|
5340
|
+
...fs,
|
|
5341
|
+
async rm(path, options) {
|
|
5342
|
+
const target = fs.resolvePath("/", path);
|
|
5343
|
+
const pinned = await keeper.pin(target);
|
|
5344
|
+
if (pinned.moved.has(target)) pinned.commit();
|
|
5345
|
+
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
5346
|
+
},
|
|
5347
|
+
async rename(from, to) {
|
|
5348
|
+
const pinned = await keeper.pin(fs.resolvePath("/", to));
|
|
5349
|
+
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
5350
|
+
},
|
|
5351
|
+
readdir: async (path) => visible(await fs.readdir(path)),
|
|
5352
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
|
|
5353
|
+
};
|
|
5354
|
+
}
|
|
5355
|
+
async settle(pinned, op) {
|
|
5356
|
+
try {
|
|
5357
|
+
await op();
|
|
5358
|
+
} catch (err) {
|
|
5359
|
+
await pinned.rollback();
|
|
5360
|
+
throw err;
|
|
5361
|
+
}
|
|
5362
|
+
pinned.commit();
|
|
5363
|
+
}
|
|
5364
|
+
async pin(target) {
|
|
5365
|
+
const held = /* @__PURE__ */ new Map();
|
|
5366
|
+
for (const holder of this.holders) {
|
|
5367
|
+
if (holder.owns(target)) continue;
|
|
5368
|
+
for (const path of holder.opens.keys()) {
|
|
5369
|
+
if (!within3(path, target) || holder.unlinked.has(path)) continue;
|
|
5370
|
+
held.set(path, [...held.get(path) ?? [], holder]);
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
const paths = [...held.keys()].sort();
|
|
5374
|
+
const releases = [];
|
|
5375
|
+
for (const path of paths) releases.push(await this.gate.acquire(path));
|
|
5376
|
+
const hidden = /* @__PURE__ */ new Map();
|
|
5377
|
+
for (const path of paths) {
|
|
5378
|
+
const at = await this.hide(path);
|
|
5379
|
+
if (at !== void 0) hidden.set(path, at);
|
|
5380
|
+
}
|
|
5381
|
+
const done4 = () => {
|
|
5382
|
+
for (const release of releases) release();
|
|
5383
|
+
};
|
|
5384
|
+
return {
|
|
5385
|
+
moved: new Set(hidden.keys()),
|
|
5386
|
+
commit: () => {
|
|
5387
|
+
for (const [path, at] of hidden) {
|
|
5388
|
+
const holders = held.get(path);
|
|
5389
|
+
const file = this.keptFile(at, { refs: holders.length });
|
|
5390
|
+
for (const holder of holders) holder.unlinked.set(path, file);
|
|
5391
|
+
}
|
|
5392
|
+
done4();
|
|
5393
|
+
},
|
|
5394
|
+
rollback: async () => {
|
|
5395
|
+
for (const [path, at] of hidden) {
|
|
5396
|
+
await this.serial(async () => {
|
|
5397
|
+
await Promise.allSettled([this.fs.rename(at, path)]);
|
|
5398
|
+
await this.tidy(parentOf3(at));
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
done4();
|
|
5402
|
+
}
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
keptFile(hidden, entry) {
|
|
5406
|
+
return {
|
|
5407
|
+
hidden,
|
|
5408
|
+
release: () => {
|
|
5409
|
+
if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
|
|
5410
|
+
}
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
hide(path) {
|
|
5414
|
+
return this.serial(async () => {
|
|
5415
|
+
const dir = dirOf(this.rootOf(path));
|
|
5416
|
+
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
5417
|
+
try {
|
|
5418
|
+
await this.fs.mkdir(dir, { recursive: true });
|
|
5419
|
+
await this.fs.rename(path, hidden);
|
|
5420
|
+
return hidden;
|
|
5421
|
+
} catch {
|
|
5422
|
+
await this.tidy(dir);
|
|
5423
|
+
return void 0;
|
|
5424
|
+
}
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
5427
|
+
closed(path) {
|
|
5428
|
+
if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
|
|
5429
|
+
}
|
|
5430
|
+
async drop(hidden) {
|
|
5431
|
+
if (this.busy(hidden)) {
|
|
5432
|
+
this.waiting.add(hidden);
|
|
5433
|
+
return;
|
|
5434
|
+
}
|
|
5435
|
+
await this.fs.rm(hidden).catch(() => void 0);
|
|
5436
|
+
await this.tidy(parentOf3(hidden));
|
|
5437
|
+
}
|
|
5438
|
+
async tidy(dir) {
|
|
5439
|
+
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
5440
|
+
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
5441
|
+
}
|
|
5442
|
+
sweep(root) {
|
|
5443
|
+
const query = this.locks?.query?.bind(this.locks);
|
|
5444
|
+
if (!query) return Promise.resolve();
|
|
5445
|
+
return this.serial(async () => {
|
|
5446
|
+
const dir = dirOf(root);
|
|
5447
|
+
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
5448
|
+
if (!names) return;
|
|
5449
|
+
const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
5450
|
+
for (const name2 of names) {
|
|
5451
|
+
const owner = name2.slice(0, name2.lastIndexOf("."));
|
|
5452
|
+
if (owner === this.instance || held.has(lockName(owner))) continue;
|
|
5453
|
+
await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
5454
|
+
}
|
|
5455
|
+
await this.tidy(dir);
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
};
|
|
5459
|
+
|
|
4881
5460
|
// src/fs/watch.ts
|
|
4882
5461
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
4883
5462
|
var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
4884
|
-
var
|
|
5463
|
+
var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
4885
5464
|
function touches(watcher, changed) {
|
|
4886
5465
|
return watcher.paths.some(
|
|
4887
|
-
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) :
|
|
5466
|
+
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
|
|
4888
5467
|
);
|
|
4889
5468
|
}
|
|
4890
5469
|
var FsWatchers = class {
|
|
@@ -5266,7 +5845,7 @@ function parseSize(text) {
|
|
|
5266
5845
|
if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
|
|
5267
5846
|
return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
|
|
5268
5847
|
}
|
|
5269
|
-
var
|
|
5848
|
+
var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
5270
5849
|
var HeldSet = class extends Set {
|
|
5271
5850
|
idle;
|
|
5272
5851
|
constructor(idle) {
|
|
@@ -5279,18 +5858,47 @@ var HeldSet = class extends Set {
|
|
|
5279
5858
|
}
|
|
5280
5859
|
delete(holds) {
|
|
5281
5860
|
const removed = super.delete(holds);
|
|
5282
|
-
for (const path of holds.keys()) this.idle(path);
|
|
5861
|
+
for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
|
|
5862
|
+
for (const kept of holds.unlinked.values()) kept.release();
|
|
5863
|
+
holds.unlinked.clear();
|
|
5283
5864
|
return removed;
|
|
5284
5865
|
}
|
|
5285
5866
|
};
|
|
5286
5867
|
function heldUnder(held, target) {
|
|
5287
|
-
for (const paths of held) for (const path of paths.keys()) if (
|
|
5868
|
+
for (const paths of held) for (const path of paths.keys()) if (within4(path, target)) return true;
|
|
5288
5869
|
return false;
|
|
5289
5870
|
}
|
|
5290
5871
|
var HeldPaths = class extends Map {
|
|
5291
5872
|
revoked = /* @__PURE__ */ new Set();
|
|
5873
|
+
opens = /* @__PURE__ */ new Map();
|
|
5874
|
+
unlinked = /* @__PURE__ */ new Map();
|
|
5875
|
+
aliases = /* @__PURE__ */ new Map();
|
|
5876
|
+
owning;
|
|
5292
5877
|
onIdle;
|
|
5293
|
-
|
|
5878
|
+
keyOf(path) {
|
|
5879
|
+
return this.aliases.get(path) ?? path;
|
|
5880
|
+
}
|
|
5881
|
+
keptAt(path) {
|
|
5882
|
+
return this.unlinked.get(this.keyOf(path));
|
|
5883
|
+
}
|
|
5884
|
+
holding(path) {
|
|
5885
|
+
return this.opens.has(this.keyOf(path));
|
|
5886
|
+
}
|
|
5887
|
+
hold(path, on, open = false, real) {
|
|
5888
|
+
if (open) {
|
|
5889
|
+
const key = real ?? this.keyOf(path);
|
|
5890
|
+
if (key !== path) this.aliases.set(path, key);
|
|
5891
|
+
const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
|
|
5892
|
+
if (count2 > 0) this.opens.set(key, count2);
|
|
5893
|
+
else {
|
|
5894
|
+
this.opens.delete(key);
|
|
5895
|
+
this.unlinked.get(key)?.release();
|
|
5896
|
+
this.unlinked.delete(key);
|
|
5897
|
+
for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
|
|
5898
|
+
this.onIdle?.(key);
|
|
5899
|
+
}
|
|
5900
|
+
return;
|
|
5901
|
+
}
|
|
5294
5902
|
const count = (this.get(path) ?? 0) + (on ? 1 : -1);
|
|
5295
5903
|
if (count > 0) this.set(path, count);
|
|
5296
5904
|
else {
|
|
@@ -5299,23 +5907,40 @@ var HeldPaths = class extends Map {
|
|
|
5299
5907
|
this.onIdle?.(path);
|
|
5300
5908
|
}
|
|
5301
5909
|
}
|
|
5910
|
+
async own(path, op) {
|
|
5911
|
+
this.owning = path;
|
|
5912
|
+
try {
|
|
5913
|
+
return await op();
|
|
5914
|
+
} finally {
|
|
5915
|
+
this.owning = void 0;
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
owns(path) {
|
|
5919
|
+
return this.owning === path;
|
|
5920
|
+
}
|
|
5302
5921
|
revoke(prefix) {
|
|
5303
|
-
for (const path of this.keys()) if (
|
|
5922
|
+
for (const path of this.keys()) if (within4(path, prefix)) this.revoked.add(path);
|
|
5304
5923
|
}
|
|
5305
5924
|
renamed(from, to) {
|
|
5306
5925
|
for (const [path, count] of [...this]) {
|
|
5307
|
-
if (!
|
|
5926
|
+
if (!within4(path, from)) continue;
|
|
5308
5927
|
const moved2 = to + path.slice(from.length);
|
|
5309
5928
|
this.delete(path);
|
|
5310
5929
|
this.set(moved2, (this.get(moved2) ?? 0) + count);
|
|
5311
5930
|
if (this.revoked.delete(path)) this.revoked.add(moved2);
|
|
5312
5931
|
}
|
|
5932
|
+
for (const [path, count] of [...this.opens]) {
|
|
5933
|
+
if (!within4(path, from)) continue;
|
|
5934
|
+
const moved2 = to + path.slice(from.length);
|
|
5935
|
+
this.opens.delete(path);
|
|
5936
|
+
this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
|
|
5937
|
+
}
|
|
5313
5938
|
}
|
|
5314
5939
|
isRevoked(path) {
|
|
5315
5940
|
return this.revoked.has(path);
|
|
5316
5941
|
}
|
|
5317
5942
|
};
|
|
5318
|
-
var
|
|
5943
|
+
var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
5319
5944
|
var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
|
|
5320
5945
|
function statOf(attr, path, mount) {
|
|
5321
5946
|
const mtime = new Date(attr.mtime);
|
|
@@ -5404,7 +6029,7 @@ var MountTable = class {
|
|
|
5404
6029
|
locate(path) {
|
|
5405
6030
|
let best;
|
|
5406
6031
|
for (const mount of this.mounts.values()) {
|
|
5407
|
-
if (
|
|
6032
|
+
if (within4(path, mount.target) && (!best || mount.target.length > best.target.length))
|
|
5408
6033
|
best = mount;
|
|
5409
6034
|
}
|
|
5410
6035
|
if (!best) return void 0;
|
|
@@ -5543,7 +6168,7 @@ var MountTable = class {
|
|
|
5543
6168
|
async commitUnder(prefix) {
|
|
5544
6169
|
const at = normalizePath(prefix);
|
|
5545
6170
|
await Promise.all(
|
|
5546
|
-
[...this.sessions.keys()].filter((p) =>
|
|
6171
|
+
[...this.sessions.keys()].filter((p) => within4(p, at)).map((p) => this.commit(p))
|
|
5547
6172
|
);
|
|
5548
6173
|
}
|
|
5549
6174
|
async commitOverlapping(real, offset, length2) {
|
|
@@ -5577,10 +6202,10 @@ var MountTable = class {
|
|
|
5577
6202
|
this.deps.changed?.(...full);
|
|
5578
6203
|
}
|
|
5579
6204
|
forget(mount, path) {
|
|
5580
|
-
for (const key of [...mount.attrs.keys()]) if (
|
|
6205
|
+
for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
|
|
5581
6206
|
mount.lists.delete(path);
|
|
5582
|
-
mount.lists.delete(
|
|
5583
|
-
for (const key of [...mount.lists.keys()]) if (
|
|
6207
|
+
mount.lists.delete(parentOf5(path));
|
|
6208
|
+
for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
|
|
5584
6209
|
}
|
|
5585
6210
|
async getattr(mount, path, rel2) {
|
|
5586
6211
|
if (mount.conn.failure) throw mount.conn.failure;
|
|
@@ -5645,7 +6270,7 @@ var MountFs = class {
|
|
|
5645
6270
|
if (attr.kind !== "symlink") return real;
|
|
5646
6271
|
if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
|
|
5647
6272
|
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
5648
|
-
return this.follow(this.base.resolvePath(
|
|
6273
|
+
return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
|
|
5649
6274
|
}
|
|
5650
6275
|
async open(mount, rel2, write) {
|
|
5651
6276
|
return await mount.conn.call({
|
|
@@ -6159,6 +6784,8 @@ async function ranged(fs, path, req) {
|
|
|
6159
6784
|
else await ops.truncate(path, req.length ?? 0);
|
|
6160
6785
|
return done;
|
|
6161
6786
|
}
|
|
6787
|
+
var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
|
|
6788
|
+
var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
|
|
6162
6789
|
async function dispatchSyncFs(req) {
|
|
6163
6790
|
const entry = resolveSyncFsToken(req.token);
|
|
6164
6791
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
@@ -6167,11 +6794,22 @@ async function dispatchSyncFs(req) {
|
|
|
6167
6794
|
const path = fs.resolvePath(cwd, req.path);
|
|
6168
6795
|
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
6169
6796
|
if (req.op === "hold") {
|
|
6170
|
-
|
|
6797
|
+
const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
|
|
6798
|
+
const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
|
|
6799
|
+
entry.hold?.(path, on, open, real);
|
|
6171
6800
|
return done;
|
|
6172
6801
|
}
|
|
6173
6802
|
if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
|
|
6174
|
-
|
|
6803
|
+
if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
|
|
6804
|
+
const serve = async () => {
|
|
6805
|
+
const kept = entry.unlinked?.(path);
|
|
6806
|
+
return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
|
|
6807
|
+
};
|
|
6808
|
+
return await (entry.gate ? entry.gate(path, serve) : serve());
|
|
6809
|
+
}
|
|
6810
|
+
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
6811
|
+
const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
6812
|
+
return await entry.own(target, () => run(entry, path, req));
|
|
6175
6813
|
} catch (err) {
|
|
6176
6814
|
return toErrno(err);
|
|
6177
6815
|
}
|
|
@@ -7559,7 +8197,7 @@ function isMissing(err) {
|
|
|
7559
8197
|
if (err?.code === "ENOENT") return true;
|
|
7560
8198
|
return err instanceof Error && err.message.startsWith("ENOENT");
|
|
7561
8199
|
}
|
|
7562
|
-
function
|
|
8200
|
+
function within5(path, root) {
|
|
7563
8201
|
return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
7564
8202
|
}
|
|
7565
8203
|
var VfsNode = class {
|
|
@@ -7764,7 +8402,7 @@ var VfsNodes = class {
|
|
|
7764
8402
|
return run2;
|
|
7765
8403
|
}
|
|
7766
8404
|
async entryKey(path) {
|
|
7767
|
-
const parent =
|
|
8405
|
+
const parent = parentOf6(path);
|
|
7768
8406
|
const dir = await this.real(parent, true, () => realDir(this.fs, parent));
|
|
7769
8407
|
return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
|
|
7770
8408
|
}
|
|
@@ -7812,12 +8450,12 @@ var VfsNodes = class {
|
|
|
7812
8450
|
return node;
|
|
7813
8451
|
}
|
|
7814
8452
|
holds(prefix) {
|
|
7815
|
-
for (const path of this.byPath.keys()) if (
|
|
8453
|
+
for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
|
|
7816
8454
|
return false;
|
|
7817
8455
|
}
|
|
7818
8456
|
writes(prefix) {
|
|
7819
8457
|
for (const [path, node] of this.byPath)
|
|
7820
|
-
if (node.writers > 0 &&
|
|
8458
|
+
if (node.writers > 0 && within5(path, prefix)) return true;
|
|
7821
8459
|
return false;
|
|
7822
8460
|
}
|
|
7823
8461
|
closed(node, writable = false) {
|
|
@@ -7831,24 +8469,24 @@ var VfsNodes = class {
|
|
|
7831
8469
|
this.onClosed?.(node.path);
|
|
7832
8470
|
}
|
|
7833
8471
|
async flush(path) {
|
|
7834
|
-
for (const [p, node] of this.byPath) if (
|
|
8472
|
+
for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
|
|
7835
8473
|
}
|
|
7836
8474
|
async unlinking(path) {
|
|
7837
8475
|
for (const [held, node] of [...this.byPath]) {
|
|
7838
|
-
if (!
|
|
8476
|
+
if (!within5(held, path)) continue;
|
|
7839
8477
|
await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
|
|
7840
8478
|
}
|
|
7841
8479
|
}
|
|
7842
8480
|
revoke(prefix) {
|
|
7843
8481
|
for (const [path, node] of this.byPath) {
|
|
7844
|
-
if (!
|
|
8482
|
+
if (!within5(path, prefix)) continue;
|
|
7845
8483
|
node.revoke();
|
|
7846
8484
|
this.byPath.delete(path);
|
|
7847
8485
|
}
|
|
7848
8486
|
}
|
|
7849
8487
|
unlinked(path) {
|
|
7850
8488
|
for (const [held, node] of [...this.byPath]) {
|
|
7851
|
-
if (!
|
|
8489
|
+
if (!within5(held, path)) continue;
|
|
7852
8490
|
node.orphaned = true;
|
|
7853
8491
|
this.byPath.delete(held);
|
|
7854
8492
|
}
|
|
@@ -7857,8 +8495,8 @@ var VfsNodes = class {
|
|
|
7857
8495
|
if (from === to) return;
|
|
7858
8496
|
const moved2 = [];
|
|
7859
8497
|
for (const [p, node] of this.byPath) {
|
|
7860
|
-
if (
|
|
7861
|
-
else if (
|
|
8498
|
+
if (within5(p, from)) moved2.push(node);
|
|
8499
|
+
else if (within5(p, to)) {
|
|
7862
8500
|
node.orphaned = true;
|
|
7863
8501
|
this.byPath.delete(p);
|
|
7864
8502
|
}
|
|
@@ -7881,7 +8519,7 @@ async function present(fs, path, follow = false) {
|
|
|
7881
8519
|
}
|
|
7882
8520
|
}
|
|
7883
8521
|
var MAX_LINKS5 = 40;
|
|
7884
|
-
function
|
|
8522
|
+
function parentOf6(path) {
|
|
7885
8523
|
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7886
8524
|
}
|
|
7887
8525
|
async function linkOf(fs, path) {
|
|
@@ -7894,7 +8532,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
|
|
|
7894
8532
|
const part = queue.shift();
|
|
7895
8533
|
if (part === ".") continue;
|
|
7896
8534
|
if (part === "..") {
|
|
7897
|
-
at =
|
|
8535
|
+
at = parentOf6(at);
|
|
7898
8536
|
continue;
|
|
7899
8537
|
}
|
|
7900
8538
|
const next = at === "/" ? `/${part}` : `${at}/${part}`;
|
|
@@ -7918,7 +8556,7 @@ function joinName(dir, name2) {
|
|
|
7918
8556
|
}
|
|
7919
8557
|
async function realTarget(fs, path) {
|
|
7920
8558
|
const budget = { hops: 0 };
|
|
7921
|
-
let dir = await realDir(fs,
|
|
8559
|
+
let dir = await realDir(fs, parentOf6(path), budget);
|
|
7922
8560
|
let name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
7923
8561
|
for (; ; ) {
|
|
7924
8562
|
const link2 = await linkOf(fs, joinName(dir, name2));
|
|
@@ -7926,13 +8564,13 @@ async function realTarget(fs, path) {
|
|
|
7926
8564
|
if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
|
|
7927
8565
|
budget.hops++;
|
|
7928
8566
|
const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
|
|
7929
|
-
dir = await realDir(fs,
|
|
8567
|
+
dir = await realDir(fs, parentOf6(target), budget);
|
|
7930
8568
|
name2 = target.slice(target.lastIndexOf("/") + 1);
|
|
7931
8569
|
}
|
|
7932
8570
|
return joinName(dir, name2);
|
|
7933
8571
|
}
|
|
7934
8572
|
async function targetDir(fs, path) {
|
|
7935
|
-
return
|
|
8573
|
+
return parentOf6(await realTarget(fs, path).catch(() => path));
|
|
7936
8574
|
}
|
|
7937
8575
|
async function refuseReadonly(fs, path, flags, opts = {}) {
|
|
7938
8576
|
const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
@@ -8321,7 +8959,7 @@ var WasmProcess = class {
|
|
|
8321
8959
|
const fd = this.fds.install(heldFile(), 3);
|
|
8322
8960
|
try {
|
|
8323
8961
|
const nodes = this.nodes;
|
|
8324
|
-
const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
|
|
8962
|
+
const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path));
|
|
8325
8963
|
const open = { ...req, path };
|
|
8326
8964
|
if (req.exclusive) {
|
|
8327
8965
|
await nodes.createExclusive(path);
|
|
@@ -8477,6 +9115,11 @@ var WasmProcess = class {
|
|
|
8477
9115
|
return { ok: true, kind: "json", json: await file.stat() };
|
|
8478
9116
|
}
|
|
8479
9117
|
}
|
|
9118
|
+
keptPath;
|
|
9119
|
+
async keptOr(path) {
|
|
9120
|
+
const kept = this.keptPath?.(path);
|
|
9121
|
+
return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
|
|
9122
|
+
}
|
|
8480
9123
|
async promote(req) {
|
|
8481
9124
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
8482
9125
|
if (req.share !== void 0) {
|
|
@@ -8484,7 +9127,7 @@ var WasmProcess = class {
|
|
|
8484
9127
|
return;
|
|
8485
9128
|
}
|
|
8486
9129
|
if (req.path === void 0) throw new KernelError("EINVAL");
|
|
8487
|
-
const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
|
|
9130
|
+
const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
|
|
8488
9131
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
8489
9132
|
const file = vfsFile(
|
|
8490
9133
|
this.options.fs,
|
|
@@ -8816,7 +9459,42 @@ var Dying = class {
|
|
|
8816
9459
|
return [typeof reported === "number" ? reported : CRASHED, void 0];
|
|
8817
9460
|
}
|
|
8818
9461
|
};
|
|
8819
|
-
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) {
|
|
8820
9498
|
const base = {
|
|
8821
9499
|
type: WASM_PROCESS_INIT,
|
|
8822
9500
|
pid: opts.shownPid ?? opts.pid,
|
|
@@ -8829,7 +9507,12 @@ function processInit(opts, sab, umask) {
|
|
|
8829
9507
|
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
|
|
8830
9508
|
};
|
|
8831
9509
|
if (opts.program.abi === "js") {
|
|
8832
|
-
return {
|
|
9510
|
+
return {
|
|
9511
|
+
...base,
|
|
9512
|
+
program: opts.program,
|
|
9513
|
+
fds: inheritedFds(opts.fds),
|
|
9514
|
+
lane
|
|
9515
|
+
};
|
|
8833
9516
|
}
|
|
8834
9517
|
return {
|
|
8835
9518
|
...base,
|
|
@@ -8843,6 +9526,19 @@ function inherited(opts) {
|
|
|
8843
9526
|
...opts.umask !== void 0 ? { umask: opts.umask } : {}
|
|
8844
9527
|
};
|
|
8845
9528
|
}
|
|
9529
|
+
function holdingToken(opts, holds) {
|
|
9530
|
+
return mintSyncFsToken({
|
|
9531
|
+
fs: opts.fs,
|
|
9532
|
+
cwd: opts.cwd,
|
|
9533
|
+
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
9534
|
+
hold: (path, on, open, real) => holds.hold(path, on, open, real),
|
|
9535
|
+
unlinked: (path) => holds.keptAt(path),
|
|
9536
|
+
gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
|
|
9537
|
+
own: (path, op) => holds.own(path, op),
|
|
9538
|
+
revoked: (path) => holds.isRevoked(path),
|
|
9539
|
+
renamed: (from, to) => holds.renamed(from, to)
|
|
9540
|
+
});
|
|
9541
|
+
}
|
|
8846
9542
|
function spawnWasmProcess(opts) {
|
|
8847
9543
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
8848
9544
|
const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
@@ -8868,37 +9564,22 @@ function spawnWasmProcess(opts) {
|
|
|
8868
9564
|
locks: opts.locks,
|
|
8869
9565
|
onReap: opts.onReap,
|
|
8870
9566
|
resolver: opts.resolver,
|
|
8871
|
-
|
|
8872
|
-
Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig));
|
|
8873
|
-
Atomics.notify(header2, SAB_I_SIGNALS);
|
|
8874
|
-
},
|
|
8875
|
-
onAsync: () => {
|
|
8876
|
-
Atomics.add(header2, SAB_I_ASYNC, 1);
|
|
8877
|
-
Atomics.notify(header2, SAB_I_ASYNC);
|
|
8878
|
-
},
|
|
9567
|
+
...notifiers(header2),
|
|
8879
9568
|
onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
|
|
8880
9569
|
hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
|
|
8881
9570
|
raise: (sig) => signal(sig),
|
|
8882
9571
|
...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
|
|
8883
9572
|
});
|
|
8884
|
-
process
|
|
8885
|
-
Atomics.store(header2, SAB_I_STOP, state === "stopped" ? 1 : 0);
|
|
8886
|
-
Atomics.notify(header2, SAB_I_STOP);
|
|
8887
|
-
});
|
|
9573
|
+
publishStops(process, header2);
|
|
8888
9574
|
const holds = new HeldPaths();
|
|
8889
9575
|
opts.held?.add(holds);
|
|
8890
|
-
const token =
|
|
8891
|
-
|
|
8892
|
-
cwd: opts.cwd,
|
|
8893
|
-
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
8894
|
-
hold: (path, on) => holds.hold(path, on),
|
|
8895
|
-
revoked: (path) => holds.isRevoked(path),
|
|
8896
|
-
renamed: (from, to) => holds.renamed(from, to)
|
|
8897
|
-
});
|
|
9576
|
+
const token = holdingToken(opts, holds);
|
|
9577
|
+
process.keptPath = (path) => holds.keptAt(path)?.hidden;
|
|
8898
9578
|
const worker = opts.createWorker();
|
|
8899
9579
|
const dying = new Dying();
|
|
8900
9580
|
const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
|
|
8901
9581
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
|
|
9582
|
+
const lane = opts.program.abi === "js" ? openLane(token, dispatch2) : void 0;
|
|
8902
9583
|
const { promise: exited, resolve: settle3 } = Promise.withResolvers();
|
|
8903
9584
|
let done4 = false;
|
|
8904
9585
|
let endedBy;
|
|
@@ -8911,6 +9592,7 @@ function spawnWasmProcess(opts) {
|
|
|
8911
9592
|
worker.removeEventListener("error", onError);
|
|
8912
9593
|
for (const tid of [...threads.keys()]) endThread(tid);
|
|
8913
9594
|
responder.dispose();
|
|
9595
|
+
lane?.dispose();
|
|
8914
9596
|
revokeSyncFsToken(token);
|
|
8915
9597
|
opts.held?.delete(holds);
|
|
8916
9598
|
worker.terminate();
|
|
@@ -8981,8 +9663,10 @@ function spawnWasmProcess(opts) {
|
|
|
8981
9663
|
};
|
|
8982
9664
|
tw.postMessage(tinit);
|
|
8983
9665
|
};
|
|
8984
|
-
|
|
8985
|
-
|
|
9666
|
+
worker.postMessage(
|
|
9667
|
+
processInit(opts, sab, process.umask, lane?.lane),
|
|
9668
|
+
transferOf(opts, lane?.lane)
|
|
9669
|
+
);
|
|
8986
9670
|
const signal = (sig) => {
|
|
8987
9671
|
if (process.signal(sig) !== "terminate") return;
|
|
8988
9672
|
const code = sig === SIG.KILL ? 137 : 128 + sig;
|
|
@@ -10534,6 +11218,7 @@ var Launcher = class {
|
|
|
10534
11218
|
deciding = /* @__PURE__ */ new Map();
|
|
10535
11219
|
identities = { asked: 0, timedOut: 0 };
|
|
10536
11220
|
nodes;
|
|
11221
|
+
unlinked;
|
|
10537
11222
|
held = new HeldSet((path) => this.released(path));
|
|
10538
11223
|
processMounts;
|
|
10539
11224
|
booting = new AbortController();
|
|
@@ -10578,7 +11263,15 @@ var Launcher = class {
|
|
|
10578
11263
|
const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
10579
11264
|
this.nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
10580
11265
|
this.openFiles.add(this.nodes);
|
|
10581
|
-
this.
|
|
11266
|
+
this.unlinked = new UnlinkedKeeper(
|
|
11267
|
+
fs,
|
|
11268
|
+
this.held,
|
|
11269
|
+
(path) => this.mountRoot(path),
|
|
11270
|
+
webLocks(),
|
|
11271
|
+
(path) => [...this.openFiles].some((nodes) => nodes.holds(path))
|
|
11272
|
+
);
|
|
11273
|
+
this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
|
|
11274
|
+
void this.unlinked.sweep("/");
|
|
10582
11275
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
10583
11276
|
this.catalog = void 0;
|
|
10584
11277
|
this.binfmts = void 0;
|
|
@@ -10788,6 +11481,7 @@ var Launcher = class {
|
|
|
10788
11481
|
openFiles: this.openFiles,
|
|
10789
11482
|
nodes: this.nodes,
|
|
10790
11483
|
held: this.held,
|
|
11484
|
+
unlinkGate: this.unlinked.gate,
|
|
10791
11485
|
statfs: (path) => this.mounts.statfs(path),
|
|
10792
11486
|
mounts: () => this.mounts.list(),
|
|
10793
11487
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
@@ -10945,6 +11639,7 @@ var Launcher = class {
|
|
|
10945
11639
|
}
|
|
10946
11640
|
released(path) {
|
|
10947
11641
|
const at = normalizePath(path);
|
|
11642
|
+
this.unlinked.closed(at);
|
|
10948
11643
|
const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
|
|
10949
11644
|
if (!open) void this.mounts.commit(at).catch(() => void 0);
|
|
10950
11645
|
}
|
|
@@ -11040,10 +11735,23 @@ var Launcher = class {
|
|
|
11040
11735
|
}
|
|
11041
11736
|
};
|
|
11042
11737
|
}
|
|
11043
|
-
mount(spec) {
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11738
|
+
async mount(spec) {
|
|
11739
|
+
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
11740
|
+
const entry = await this.mounts.mount(
|
|
11741
|
+
fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
|
|
11742
|
+
this.fs
|
|
11743
|
+
);
|
|
11744
|
+
void this.unlinked.sweep(entry.target);
|
|
11745
|
+
return entry;
|
|
11746
|
+
}
|
|
11747
|
+
mountRoot(path) {
|
|
11748
|
+
let root = "/";
|
|
11749
|
+
for (const { target } of this.mounts.list()) {
|
|
11750
|
+
if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
|
|
11751
|
+
root = target;
|
|
11752
|
+
}
|
|
11753
|
+
}
|
|
11754
|
+
return root;
|
|
11047
11755
|
}
|
|
11048
11756
|
async insert(target, handle, source) {
|
|
11049
11757
|
const at = normalizePath(target);
|
|
@@ -11057,6 +11765,7 @@ var Launcher = class {
|
|
|
11057
11765
|
await this.media.put(slot.id, handle);
|
|
11058
11766
|
slot.medium.insert(handle);
|
|
11059
11767
|
slot.served.invalidate(true);
|
|
11768
|
+
void this.unlinked.sweep(at);
|
|
11060
11769
|
}
|
|
11061
11770
|
umount(target, detach = false) {
|
|
11062
11771
|
const at = normalizePath(target);
|