@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/dist/node.js CHANGED
@@ -1190,7 +1190,153 @@ async function readWithin(reader, ms) {
1190
1190
  }
1191
1191
  }
1192
1192
 
1193
+ // src/kernel/net/ws-queue.ts
1194
+ var INBOUND_LIMIT = 16 * 1024 * 1024;
1195
+ var MESSAGE_LIMIT = 64 * 1024 * 1024;
1196
+ var TOO_BIG = 1009;
1197
+ function payloadSize(message2) {
1198
+ return typeof message2 === "string" ? new TextEncoder().encode(message2).length : message2.length;
1199
+ }
1200
+ var MessageQueue = class {
1201
+ items = [];
1202
+ limit;
1203
+ waiter;
1204
+ size = 0;
1205
+ ended = false;
1206
+ overflow;
1207
+ taken;
1208
+ closed;
1209
+ settle;
1210
+ constructor(overflow, limit = INBOUND_LIMIT, taken) {
1211
+ this.overflow = overflow;
1212
+ this.limit = limit;
1213
+ this.taken = taken;
1214
+ this.closed = new Promise((resolve) => {
1215
+ this.settle = resolve;
1216
+ });
1217
+ }
1218
+ push(message2) {
1219
+ if (this.ended) return;
1220
+ const size = payloadSize(message2);
1221
+ const huge = size > MESSAGE_LIMIT;
1222
+ if (huge || this.items.length > 0 && this.size + size > this.limit) {
1223
+ const reason = huge ? `a message is over ${MESSAGE_LIMIT} bytes` : `over ${this.limit} bytes of messages are waiting`;
1224
+ this.end({ code: TOO_BIG, reason });
1225
+ this.overflow();
1226
+ return;
1227
+ }
1228
+ this.size += size;
1229
+ this.items.push(message2);
1230
+ this.wake();
1231
+ }
1232
+ end(close) {
1233
+ if (this.ended) return;
1234
+ this.ended = true;
1235
+ this.settle(close);
1236
+ this.wake();
1237
+ }
1238
+ wake() {
1239
+ const waiter = this.waiter;
1240
+ this.waiter = void 0;
1241
+ waiter?.();
1242
+ }
1243
+ async *[Symbol.asyncIterator]() {
1244
+ for (; ; ) {
1245
+ const next = this.items.shift();
1246
+ if (next !== void 0) {
1247
+ const n = payloadSize(next);
1248
+ this.size -= n;
1249
+ this.taken?.(n);
1250
+ yield next;
1251
+ continue;
1252
+ }
1253
+ if (this.ended) return;
1254
+ await new Promise((resolve) => {
1255
+ this.waiter = resolve;
1256
+ });
1257
+ }
1258
+ }
1259
+ };
1260
+
1261
+ // src/websocket-transport.ts
1262
+ var SENT_HEADERS_DROP = /* @__PURE__ */ new Set([
1263
+ "host",
1264
+ "connection",
1265
+ "upgrade",
1266
+ "sec-websocket-key",
1267
+ "sec-websocket-version",
1268
+ "sec-websocket-extensions",
1269
+ "sec-websocket-protocol"
1270
+ ]);
1271
+ function headerRecord(headers) {
1272
+ const out = {};
1273
+ for (const [name2, value] of headers) {
1274
+ if (!SENT_HEADERS_DROP.has(name2.toLowerCase())) out[name2] = value;
1275
+ }
1276
+ return out;
1277
+ }
1278
+ function sendableClose(code) {
1279
+ return code === 1e3 || code !== void 0 && code >= 3e3 && code <= 4999 ? code : 1e3;
1280
+ }
1281
+ function blockedReason(url, e) {
1282
+ const name2 = e?.name;
1283
+ if (name2 === "SecurityError" && url.startsWith("ws:")) {
1284
+ return `${url}: ws:// is blocked from an https page; use wss:// or slicc-node`;
1285
+ }
1286
+ return `${url}: ${e instanceof Error ? e.message : String(e)}`;
1287
+ }
1288
+ function openWebSocket(Socket, request, withHeaders) {
1289
+ const { url, protocols, signal } = request;
1290
+ signal.throwIfAborted();
1291
+ let socket;
1292
+ try {
1293
+ socket = withHeaders ? new Socket(url, { protocols, headers: headerRecord(request.headers) }) : new Socket(url, protocols);
1294
+ } catch (e) {
1295
+ return Promise.reject(Object.assign(new Error(blockedReason(url, e)), { status: 502 }));
1296
+ }
1297
+ socket.binaryType = "arraybuffer";
1298
+ const queue = new MessageQueue(() => socket.close(1e3, "too many waiting messages"));
1299
+ return new Promise((resolve, reject) => {
1300
+ let open = false;
1301
+ const abort = () => {
1302
+ socket.close(1e3, "aborted");
1303
+ reject(signal.reason);
1304
+ };
1305
+ signal.addEventListener("abort", abort, { once: true });
1306
+ socket.addEventListener("open", () => {
1307
+ open = true;
1308
+ signal.removeEventListener("abort", abort);
1309
+ resolve({
1310
+ protocol: socket.protocol,
1311
+ get buffered() {
1312
+ return socket.bufferedAmount;
1313
+ },
1314
+ send: (data) => socket.send(data),
1315
+ messages: queue,
1316
+ close: (code, reason) => socket.close(sendableClose(code), reason),
1317
+ closed: queue.closed
1318
+ });
1319
+ });
1320
+ socket.addEventListener("message", (event) => {
1321
+ queue.push(typeof event.data === "string" ? event.data : new Uint8Array(event.data));
1322
+ });
1323
+ socket.addEventListener("close", (event) => {
1324
+ queue.end({ code: event.code, reason: event.reason });
1325
+ if (open) return;
1326
+ signal.removeEventListener("abort", abort);
1327
+ reject(
1328
+ Object.assign(new Error(`${url}: the WebSocket closed (${event.code}) before it opened`), {
1329
+ status: 502
1330
+ })
1331
+ );
1332
+ });
1333
+ });
1334
+ }
1335
+
1193
1336
  // src/transport.ts
1337
+ var SEND_BUFFER = 1024 * 1024;
1338
+ var CLOSE_WAIT_MS = 5e3;
1339
+ var NO_WEBSOCKET = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
1194
1340
  var MAX_REQUEST_BODY = 64 * 1024 * 1024;
1195
1341
  function joinHeaders(headers) {
1196
1342
  const out = new Headers();
@@ -1215,13 +1361,17 @@ async function* chunks(body, idleMs) {
1215
1361
  }
1216
1362
  function fetchTransport(options = {}) {
1217
1363
  const send = options.fetch ?? globalThis.fetch.bind(globalThis);
1364
+ const Socket = options.webSocket === true ? globalThis.WebSocket : options.webSocket || void 0;
1365
+ const headers = options.webSocketHeaders === true;
1218
1366
  return {
1219
1367
  traits: {
1220
1368
  manualRedirects: false,
1221
1369
  encodedBodies: false,
1222
1370
  maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
1223
- crossOrigin: "cors"
1371
+ crossOrigin: "cors",
1372
+ ...Socket ? { websocket: true } : {}
1224
1373
  },
1374
+ ...Socket ? { websocket: (request) => openWebSocket(Socket, request, headers) } : {},
1225
1375
  async fetch(request) {
1226
1376
  let response;
1227
1377
  try {
@@ -1243,15 +1393,15 @@ function fetchTransport(options = {}) {
1243
1393
  { status: 502 }
1244
1394
  );
1245
1395
  }
1246
- const headers = [];
1396
+ const headers2 = [];
1247
1397
  response.headers.forEach((value, name2) => {
1248
- headers.push([name2, value]);
1398
+ headers2.push([name2, value]);
1249
1399
  });
1250
1400
  const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
1251
1401
  return {
1252
1402
  status: response.status,
1253
1403
  statusText: response.statusText,
1254
- headers,
1404
+ headers: headers2,
1255
1405
  body,
1256
1406
  cancel: async () => {
1257
1407
  await body.return(void 0);
@@ -1261,7 +1411,7 @@ function fetchTransport(options = {}) {
1261
1411
  }
1262
1412
  };
1263
1413
  }
1264
- function serveTransport(peer, transport) {
1414
+ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1265
1415
  const open = /* @__PURE__ */ new Map();
1266
1416
  const fail2 = (nid, e) => {
1267
1417
  open.delete(nid);
@@ -1302,6 +1452,96 @@ function serveTransport(peer, transport) {
1302
1452
  fail2(call.nid, e);
1303
1453
  }
1304
1454
  };
1455
+ const sockets = /* @__PURE__ */ new Map();
1456
+ const credit = /* @__PURE__ */ new Map();
1457
+ const refill = (nid, n, closing = false) => {
1458
+ const flow = credit.get(nid);
1459
+ if (!flow) return;
1460
+ flow.inflight -= n;
1461
+ if (closing && !flow.closing) {
1462
+ flow.closing = true;
1463
+ flow.deadline();
1464
+ }
1465
+ flow.wake?.();
1466
+ };
1467
+ const openSocket = async (call) => {
1468
+ const abort = new AbortController();
1469
+ open.set(call.nid, { abort });
1470
+ try {
1471
+ if (!transport.websocket) throw new Error(NO_WEBSOCKET);
1472
+ const socket = await transport.websocket({
1473
+ url: call.url,
1474
+ protocols: call.protocols,
1475
+ headers: call.headers,
1476
+ signal: abort.signal
1477
+ });
1478
+ if (!open.delete(call.nid)) {
1479
+ socket.close(1e3, "cancelled");
1480
+ return;
1481
+ }
1482
+ sockets.set(call.nid, socket);
1483
+ peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
1484
+ void relay(call.nid, socket);
1485
+ } catch (e) {
1486
+ fail2(call.nid, e);
1487
+ }
1488
+ };
1489
+ const relay = async (nid, socket) => {
1490
+ let ending;
1491
+ let late;
1492
+ const unanswered = new Promise((resolve) => {
1493
+ late = () => resolve("late");
1494
+ });
1495
+ const flow = {
1496
+ inflight: 0,
1497
+ closing: false,
1498
+ deadline: () => AbortSignal.timeout(closeWaitMs).addEventListener("abort", late, { once: true })
1499
+ };
1500
+ credit.set(nid, flow);
1501
+ const timedOut = { code: 1006, reason: "the far end did not answer the close" };
1502
+ let oversize = false;
1503
+ try {
1504
+ const messages = socket.messages[Symbol.asyncIterator]();
1505
+ for (; ; ) {
1506
+ const next = await Promise.race([messages.next(), unanswered]);
1507
+ if (next === "late" || next.done) break;
1508
+ const data = next.value;
1509
+ if (flow.closing) continue;
1510
+ if (payloadSize(data) > MESSAGE_LIMIT) {
1511
+ oversize = true;
1512
+ break;
1513
+ }
1514
+ if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
1515
+ else {
1516
+ const bytes = data.slice();
1517
+ peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
1518
+ }
1519
+ flow.inflight += payloadSize(data);
1520
+ while (flow.inflight > INBOUND_LIMIT && !flow.closing) {
1521
+ await new Promise((resolve) => {
1522
+ flow.wake = resolve;
1523
+ });
1524
+ }
1525
+ }
1526
+ if (oversize) socket.close(1e3, "a message is too big");
1527
+ ending = oversize ? { code: 1009, reason: `a message is over ${MESSAGE_LIMIT} bytes` } : await Promise.race([socket.closed, unanswered.then(() => timedOut)]);
1528
+ } catch (e) {
1529
+ socket.close(1e3, "the relay failed");
1530
+ ending = { code: 1011, reason: e instanceof Error ? e.message : String(e) };
1531
+ }
1532
+ sockets.delete(nid);
1533
+ credit.delete(nid);
1534
+ peer.postMessage({ net: "ws-closed", nid, ...ending });
1535
+ };
1536
+ const sendOn = async (call) => {
1537
+ const socket = sockets.get(call.nid);
1538
+ if (!socket) return;
1539
+ socket.send(call.data);
1540
+ while (socket.buffered > SEND_BUFFER && sockets.has(call.nid)) {
1541
+ await new Promise((resolve) => setTimeout(resolve, 10));
1542
+ }
1543
+ peer.postMessage({ net: "ws-acked", nid: call.nid, n: payloadSize(call.data) });
1544
+ };
1305
1545
  const read = async (nid) => {
1306
1546
  const body = open.get(nid)?.body;
1307
1547
  if (!body) return;
@@ -1319,6 +1559,8 @@ function serveTransport(peer, transport) {
1319
1559
  }
1320
1560
  };
1321
1561
  const drop = (nid) => {
1562
+ sockets.get(nid)?.close(1e3, "cancelled");
1563
+ refill(nid, 0, true);
1322
1564
  const entry = open.get(nid);
1323
1565
  open.delete(nid);
1324
1566
  entry?.abort.abort();
@@ -1328,10 +1570,16 @@ function serveTransport(peer, transport) {
1328
1570
  answer(call) {
1329
1571
  if (call.net === "fetch") void start(call);
1330
1572
  else if (call.net === "read") void read(call.nid);
1573
+ else if (call.net === "ws-open") void openSocket(call);
1574
+ else if (call.net === "ws-send") void sendOn(call);
1575
+ else if (call.net === "ws-close") {
1576
+ sockets.get(call.nid)?.close(call.code, call.reason);
1577
+ refill(call.nid, 0, true);
1578
+ } else if (call.net === "ws-consumed") refill(call.nid, call.n);
1331
1579
  else drop(call.nid);
1332
1580
  },
1333
1581
  close() {
1334
- for (const nid of [...open.keys()]) drop(nid);
1582
+ for (const nid of [...open.keys(), ...sockets.keys()]) drop(nid);
1335
1583
  }
1336
1584
  };
1337
1585
  }
@@ -2417,6 +2665,10 @@ var Incoming = class {
2417
2665
  this.buf = this.buf.subarray(n);
2418
2666
  return out;
2419
2667
  }
2668
+ async peek(signal) {
2669
+ if (this.buf.length === 0) await this.fill(signal);
2670
+ return this.buf[0];
2671
+ }
2420
2672
  async some(max, signal) {
2421
2673
  if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
2422
2674
  if (this.eof) return new Uint8Array(0);
@@ -2614,7 +2866,15 @@ var CLOSE = {
2614
2866
  error: 1011
2615
2867
  };
2616
2868
  var OP_TEXT = 1;
2617
- var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
2869
+ var OP_BINARY = 2;
2870
+ var OP = {
2871
+ continuation: 0,
2872
+ text: OP_TEXT,
2873
+ binary: OP_BINARY,
2874
+ close: 8,
2875
+ ping: 9,
2876
+ pong: 10
2877
+ };
2618
2878
  var WsError = class extends Error {
2619
2879
  code;
2620
2880
  constructor(code, message2) {
@@ -2622,6 +2882,10 @@ var WsError = class extends Error {
2622
2882
  this.code = code;
2623
2883
  }
2624
2884
  };
2885
+ function sendableCode(code) {
2886
+ if (code >= 3e3 && code <= 4999) return true;
2887
+ return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
2888
+ }
2625
2889
  function field(headers, name2) {
2626
2890
  return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
2627
2891
  }
@@ -2660,7 +2924,11 @@ function frame(opcode, payload) {
2660
2924
  }
2661
2925
  function closeFrame(code, reason = "") {
2662
2926
  let bytes = new TextEncoder().encode(reason);
2663
- if (bytes.length > 123) bytes = bytes.subarray(0, 123);
2927
+ if (bytes.length > 123) {
2928
+ let end = 123;
2929
+ while ((bytes[end] & 192) === 128) end--;
2930
+ bytes = bytes.subarray(0, end);
2931
+ }
2664
2932
  const payload = new Uint8Array(2 + bytes.length);
2665
2933
  new DataView(payload.buffer).setUint16(0, code);
2666
2934
  payload.set(bytes, 2);
@@ -2691,9 +2959,12 @@ var WsReader = class {
2691
2959
  parts = [];
2692
2960
  size = 0;
2693
2961
  continuing = false;
2694
- constructor(incoming, max = MAX_MESSAGE) {
2962
+ binaryMessage = false;
2963
+ binary;
2964
+ constructor(incoming, max = MAX_MESSAGE, binary) {
2695
2965
  this.incoming = incoming;
2696
2966
  this.max = max;
2967
+ this.binary = binary;
2697
2968
  }
2698
2969
  async next(signal) {
2699
2970
  for (; ; ) {
@@ -2723,6 +2994,20 @@ var WsReader = class {
2723
2994
  if (event) return event;
2724
2995
  }
2725
2996
  }
2997
+ data(opcode, fin, payload) {
2998
+ if (opcode === OP.continuation !== this.continuing) {
2999
+ throw new WsError(CLOSE.protocol, "unexpected continuation frame");
3000
+ }
3001
+ if (opcode !== OP.continuation) this.binaryMessage = opcode === OP.binary;
3002
+ this.parts.push(payload);
3003
+ this.size += payload.length;
3004
+ this.continuing = !fin;
3005
+ if (!fin) return void 0;
3006
+ const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
3007
+ this.parts = [];
3008
+ this.size = 0;
3009
+ return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
3010
+ }
2726
3011
  async length(short, signal) {
2727
3012
  if (short < 126) return short;
2728
3013
  const ext = new Uint8Array(short === 126 ? 2 : 8);
@@ -2741,36 +3026,31 @@ var WsReader = class {
2741
3026
  case OP.close: {
2742
3027
  if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
2743
3028
  const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
2744
- return { kind: "close", code, reason: decode(payload.subarray(2)) };
2745
- }
2746
- case OP.text:
2747
- case OP.continuation: {
2748
- if (opcode === OP.continuation !== this.continuing) {
2749
- throw new WsError(CLOSE.protocol, "unexpected continuation frame");
3029
+ if (payload.length >= 2 && !sendableCode(code)) {
3030
+ throw new WsError(CLOSE.protocol, `close code ${code} may not be sent`);
2750
3031
  }
2751
- this.parts.push(payload);
2752
- this.size += payload.length;
2753
- this.continuing = !fin;
2754
- if (!fin) return void 0;
2755
- const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
2756
- this.parts = [];
2757
- this.size = 0;
2758
- return { kind: "text", text: decode(whole) };
3032
+ return { kind: "close", code, reason: decode(payload.subarray(2)) };
2759
3033
  }
2760
3034
  case OP.binary:
2761
- throw new WsError(CLOSE.unsupported, "CDP messages are text");
3035
+ if (!this.binary) throw new WsError(CLOSE.unsupported, "CDP messages are text");
3036
+ return this.data(opcode, fin, payload);
3037
+ case OP.text:
3038
+ case OP.continuation:
3039
+ return this.data(opcode, fin, payload);
2762
3040
  default:
2763
3041
  throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
2764
3042
  }
2765
3043
  }
2766
3044
  };
2767
- function switchingHead(accept2) {
3045
+ function switchingHead(accept2, protocol = "") {
3046
+ const chosen = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
3047
+ ` : "";
2768
3048
  return latin1Bytes(
2769
3049
  `HTTP/1.1 101 Switching Protocols\r
2770
3050
  Upgrade: websocket\r
2771
3051
  Connection: Upgrade\r
2772
3052
  Sec-WebSocket-Accept: ${accept2}\r
2773
- \r
3053
+ ${chosen}\r
2774
3054
  `
2775
3055
  );
2776
3056
  }
@@ -2778,7 +3058,7 @@ Sec-WebSocket-Accept: ${accept2}\r
2778
3058
  // src/cdp/facade.ts
2779
3059
  var MAX_HEAD = 64 * 1024;
2780
3060
  var LIST_TIMEOUT = 1e4;
2781
- var CLOSE_WAIT_MS = 1e3;
3061
+ var CLOSE_WAIT_MS2 = 1e3;
2782
3062
  function respond(status, body, type = "text/plain; charset=utf-8") {
2783
3063
  const bytes = new TextEncoder().encode(body);
2784
3064
  const head = latin1Bytes(
@@ -2926,7 +3206,7 @@ var CdpFacade = class {
2926
3206
  const host = await this.host(runtime);
2927
3207
  const limits = {
2928
3208
  max: this.options.maxMessage ?? MAX_MESSAGE,
2929
- closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS
3209
+ closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS2
2930
3210
  };
2931
3211
  await new Relay(conn, incoming, host, limits, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
2932
3212
  }
@@ -5276,12 +5556,17 @@ function ownNames(hostname) {
5276
5556
  }
5277
5557
 
5278
5558
  // src/kernel/net/proxy-service.ts
5559
+ var SEND_BUFFER2 = 1024 * 1024;
5560
+ var TLS_HANDSHAKE = 22;
5561
+ var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
5562
+ var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
5279
5563
  var REALM_PROXY_PORT = 3128;
5280
5564
  var DEFAULT_LIMITS = {
5281
5565
  maxConnections: 64,
5282
5566
  maxHead: 64 * 1024,
5283
5567
  idleMs: 12e4,
5284
- bodyBudget: 128 * 1024 * 1024
5568
+ bodyBudget: 128 * 1024 * 1024,
5569
+ closeWaitMs: 5e3
5285
5570
  };
5286
5571
  var HOP_BY_HOP = /* @__PURE__ */ new Set([
5287
5572
  "connection",
@@ -5472,11 +5757,27 @@ function plainResponse(status, message2) {
5472
5757
  cancel: async () => void 0
5473
5758
  };
5474
5759
  }
5475
- function timedSource(conn, idleMs, stop) {
5760
+ function timedSource(conn, idle, stop) {
5476
5761
  return {
5477
- read: (max) => conn.read(max, AbortSignal.any([stop, AbortSignal.timeout(idleMs)]))
5762
+ read: (max, signal) => {
5763
+ const own = signal ? [stop, signal] : [stop];
5764
+ if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
5765
+ return conn.read(max, AbortSignal.any(own));
5766
+ }
5478
5767
  };
5479
5768
  }
5769
+ function webSocketTarget(req) {
5770
+ return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
5771
+ }
5772
+ function wireCode(code) {
5773
+ if (code === 1005) return 1e3;
5774
+ return sendableCode(code) ? code : 1011;
5775
+ }
5776
+ function quietly(ms) {
5777
+ return new Promise((resolve) => {
5778
+ AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
5779
+ });
5780
+ }
5480
5781
  var RealmProxy = class {
5481
5782
  options;
5482
5783
  listener;
@@ -5485,6 +5786,7 @@ var RealmProxy = class {
5485
5786
  slots;
5486
5787
  bodies;
5487
5788
  connections = /* @__PURE__ */ new Set();
5789
+ warnedWebSocket = false;
5488
5790
  closed;
5489
5791
  port;
5490
5792
  constructor(options) {
@@ -5533,9 +5835,10 @@ var RealmProxy = class {
5533
5835
  }
5534
5836
  async serve(conn) {
5535
5837
  this.connections.add(conn);
5536
- const incoming = new Incoming(timedSource(conn, this.limits.idleMs, this.stop.signal));
5838
+ const idle = { ms: this.limits.idleMs };
5839
+ const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
5537
5840
  try {
5538
- await this.requests({ sink: conn, incoming, conn, socket: conn });
5841
+ await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
5539
5842
  } finally {
5540
5843
  this.connections.delete(conn);
5541
5844
  conn.close();
@@ -5562,7 +5865,11 @@ var RealmProxy = class {
5562
5865
  const req = parseRequestHead(head);
5563
5866
  if (req.method === "CONNECT") {
5564
5867
  if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
5565
- await this.connect(ctx.conn, incoming, req);
5868
+ await this.connect(ctx.conn, incoming, req, ctx.idle);
5869
+ return false;
5870
+ }
5871
+ if (req.method === "GET" && isUpgrade(req.headers)) {
5872
+ await this.websocket(ctx, req);
5566
5873
  return false;
5567
5874
  }
5568
5875
  const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
@@ -5657,33 +5964,149 @@ var RealmProxy = class {
5657
5964
  if (chunked2) await conn.write(LAST_CHUNK, signal);
5658
5965
  return keep;
5659
5966
  }
5660
- async connect(conn, incoming, req) {
5967
+ async connect(conn, incoming, req, idle) {
5661
5968
  const target = tunnelTarget(req);
5662
- const tunnel = this.options.tunnel;
5663
- if (!tunnel) {
5664
- throw new HttpError(
5665
- 501,
5666
- `CONNECT ${target.host}:${target.port}: no tunnels through this proxy`
5667
- );
5668
- }
5669
- await this.options.tunnelReady?.().catch((e) => {
5670
- throw new HttpError(
5671
- 501,
5672
- `CONNECT ${target.host}:${target.port}: ${e instanceof Error ? e.message : String(e)}`
5673
- );
5674
- });
5969
+ const refused = target.port === 443 ? await this.tunnelReady(target) : void 0;
5970
+ if (refused) throw refused;
5675
5971
  await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
5676
5972
  try {
5973
+ const first = await incoming.peek(this.stop.signal);
5974
+ if (first === void 0) return;
5975
+ if (first !== TLS_HANDSHAKE) {
5976
+ const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
5977
+ await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
5978
+ return;
5979
+ }
5980
+ const tunnel = this.options.tunnel;
5981
+ if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
5677
5982
  await tunnel(
5678
5983
  conn,
5679
5984
  incoming,
5680
5985
  target,
5681
5986
  this.stop.signal,
5682
- (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn })
5987
+ (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
5683
5988
  );
5684
5989
  } catch {
5685
5990
  }
5686
5991
  }
5992
+ async tunnelReady(target) {
5993
+ const name2 = `CONNECT ${target.host}:${target.port}`;
5994
+ if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
5995
+ try {
5996
+ await this.options.tunnelReady?.();
5997
+ return void 0;
5998
+ } catch (e) {
5999
+ return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
6000
+ }
6001
+ }
6002
+ async websocket(ctx, req) {
6003
+ const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
6004
+ const { transport } = this.options;
6005
+ if (!transport.traits.websocket || !transport.websocket) {
6006
+ if (!this.warnedWebSocket) {
6007
+ this.warnedWebSocket = true;
6008
+ console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
6009
+ }
6010
+ throw new HttpError(501, NO_WEBSOCKET2);
6011
+ }
6012
+ const accept2 = await acceptKey(req.headers);
6013
+ const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
6014
+ const hangup = new AbortController();
6015
+ const waiting = new AbortController();
6016
+ void watchHangup(ctx.socket, hangup, waiting.signal);
6017
+ let socket;
6018
+ try {
6019
+ socket = await transport.websocket({
6020
+ url: toHostLoopback(href).replace(/^http/, "ws"),
6021
+ protocols,
6022
+ headers: forwardRequestHeaders(req.headers),
6023
+ signal: AbortSignal.any([this.stop.signal, hangup.signal])
6024
+ });
6025
+ } catch (e) {
6026
+ waiting.abort();
6027
+ throw new HttpError(502, e instanceof Error ? e.message : String(e));
6028
+ }
6029
+ ctx.idle.ms = Number.POSITIVE_INFINITY;
6030
+ try {
6031
+ if (hangup.signal.aborted) throw hangup.signal.reason;
6032
+ if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
6033
+ throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
6034
+ }
6035
+ await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
6036
+ await this.bridge(ctx, socket, hangup.signal);
6037
+ } catch (e) {
6038
+ socket.close(1001, "the client went away");
6039
+ throw e;
6040
+ } finally {
6041
+ waiting.abort();
6042
+ }
6043
+ }
6044
+ async bridge(ctx, socket, hangup) {
6045
+ const stop = this.stop.signal;
6046
+ let chain = Promise.resolve();
6047
+ const toGuest = (bytes) => {
6048
+ chain = chain.then(() => ctx.sink.write(bytes, stop));
6049
+ return chain;
6050
+ };
6051
+ const done4 = new AbortController();
6052
+ const reading = AbortSignal.any([stop, done4.signal, hangup]);
6053
+ const stopping = () => socket.close(1001, "the proxy is stopping");
6054
+ stop.addEventListener("abort", stopping, { once: true });
6055
+ let guestDone = false;
6056
+ let closeSent = false;
6057
+ const outbound2 = (async () => {
6058
+ for await (const data of socket.messages) {
6059
+ if (guestDone) continue;
6060
+ const text = typeof data === "string";
6061
+ await toGuest(
6062
+ frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
6063
+ );
6064
+ }
6065
+ const closed = await socket.closed;
6066
+ if (guestDone) return;
6067
+ closeSent = true;
6068
+ await toGuest(closeFrame(wireCode(closed.code), closed.reason));
6069
+ AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
6070
+ once: true
6071
+ });
6072
+ })().catch(() => done4.abort());
6073
+ const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
6074
+ try {
6075
+ for (; ; ) {
6076
+ const event = await reader.next(reading);
6077
+ if (!event) break;
6078
+ if (event.kind === "ping") {
6079
+ await toGuest(pongFrame(event.payload));
6080
+ continue;
6081
+ }
6082
+ if (event.kind === "close") {
6083
+ guestDone = true;
6084
+ if (closeSent) break;
6085
+ const code = wireCode(event.code);
6086
+ socket.close(code, event.reason);
6087
+ const reply = await Promise.race([
6088
+ socket.closed,
6089
+ quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
6090
+ ]);
6091
+ await toGuest(closeFrame(wireCode(reply.code), reply.reason));
6092
+ break;
6093
+ }
6094
+ while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
6095
+ socket.send(event.kind === "text" ? event.text : event.bytes);
6096
+ }
6097
+ } catch (e) {
6098
+ if (e instanceof WsError) {
6099
+ guestDone = true;
6100
+ socket.close(e.code, e.message);
6101
+ await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
6102
+ }
6103
+ } finally {
6104
+ guestDone = true;
6105
+ socket.close(1e3, "the client went away");
6106
+ stop.removeEventListener("abort", stopping);
6107
+ await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
6108
+ }
6109
+ }
5687
6110
  };
5688
6111
 
5689
6112
  // src/kernel/net/x509.ts
@@ -8778,7 +9201,42 @@ var Dying = class {
8778
9201
  return [typeof reported === "number" ? reported : CRASHED, void 0];
8779
9202
  }
8780
9203
  };
8781
- function processInit(opts, sab, umask) {
9204
+ function notifiers(header3) {
9205
+ return {
9206
+ onPending(sig) {
9207
+ Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
9208
+ Atomics.notify(header3, SAB_I_SIGNALS);
9209
+ },
9210
+ onAsync() {
9211
+ Atomics.add(header3, SAB_I_ASYNC, 1);
9212
+ Atomics.notify(header3, SAB_I_ASYNC);
9213
+ }
9214
+ };
9215
+ }
9216
+ function publishStops(process, header3) {
9217
+ process.onState((state) => {
9218
+ Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
9219
+ Atomics.notify(header3, SAB_I_STOP);
9220
+ });
9221
+ }
9222
+ function transferOf(opts, lane) {
9223
+ const transfer = opts.fork ? [opts.fork.memory.buffer] : [];
9224
+ return lane ? [...transfer, lane.port] : transfer;
9225
+ }
9226
+ function openLane(token, dispatch2) {
9227
+ const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
9228
+ const { port1, port2 } = new MessageChannel();
9229
+ const responder = attachSyncSabResponder(port1, sab, token, { dispatch: dispatch2 });
9230
+ port1.start();
9231
+ return {
9232
+ lane: { sab, port: port2 },
9233
+ dispose() {
9234
+ responder.dispose();
9235
+ port1.close();
9236
+ }
9237
+ };
9238
+ }
9239
+ function processInit(opts, sab, umask, lane) {
8782
9240
  const base = {
8783
9241
  type: WASM_PROCESS_INIT,
8784
9242
  pid: opts.shownPid ?? opts.pid,
@@ -8791,7 +9249,12 @@ function processInit(opts, sab, umask) {
8791
9249
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
8792
9250
  };
8793
9251
  if (opts.program.abi === "js") {
8794
- return { ...base, program: opts.program, fds: inheritedFds(opts.fds) };
9252
+ return {
9253
+ ...base,
9254
+ program: opts.program,
9255
+ fds: inheritedFds(opts.fds),
9256
+ lane
9257
+ };
8795
9258
  }
8796
9259
  return {
8797
9260
  ...base,
@@ -8843,23 +9306,13 @@ function spawnWasmProcess(opts) {
8843
9306
  locks: opts.locks,
8844
9307
  onReap: opts.onReap,
8845
9308
  resolver: opts.resolver,
8846
- onPending: (sig) => {
8847
- Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
8848
- Atomics.notify(header3, SAB_I_SIGNALS);
8849
- },
8850
- onAsync: () => {
8851
- Atomics.add(header3, SAB_I_ASYNC, 1);
8852
- Atomics.notify(header3, SAB_I_ASYNC);
8853
- },
9309
+ ...notifiers(header3),
8854
9310
  onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
8855
9311
  hasPending: () => Atomics.load(header3, SAB_I_SIGNALS) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
8856
9312
  raise: (sig) => signal(sig),
8857
9313
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
8858
9314
  });
8859
- process.onState((state) => {
8860
- Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
8861
- Atomics.notify(header3, SAB_I_STOP);
8862
- });
9315
+ publishStops(process, header3);
8863
9316
  const holds = new HeldPaths();
8864
9317
  opts.held?.add(holds);
8865
9318
  const token = holdingToken(opts, holds);
@@ -8868,6 +9321,7 @@ function spawnWasmProcess(opts) {
8868
9321
  const dying = new Dying();
8869
9322
  const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
8870
9323
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
9324
+ const lane = opts.program.abi === "js" ? openLane(token, dispatch2) : void 0;
8871
9325
  const { promise: exited, resolve: settle } = Promise.withResolvers();
8872
9326
  let done4 = false;
8873
9327
  let endedBy;
@@ -8880,6 +9334,7 @@ function spawnWasmProcess(opts) {
8880
9334
  worker.removeEventListener("error", onError);
8881
9335
  for (const tid of [...threads.keys()]) endThread(tid);
8882
9336
  responder.dispose();
9337
+ lane?.dispose();
8883
9338
  revokeSyncFsToken(token);
8884
9339
  opts.held?.delete(holds);
8885
9340
  worker.terminate();
@@ -8950,8 +9405,10 @@ function spawnWasmProcess(opts) {
8950
9405
  };
8951
9406
  tw.postMessage(tinit);
8952
9407
  };
8953
- const init = processInit(opts, sab, process.umask);
8954
- worker.postMessage(init, opts.fork ? [opts.fork.memory.buffer] : []);
9408
+ worker.postMessage(
9409
+ processInit(opts, sab, process.umask, lane?.lane),
9410
+ transferOf(opts, lane?.lane)
9411
+ );
8955
9412
  const signal = (sig) => {
8956
9413
  if (process.signal(sig) !== "terminate") return;
8957
9414
  const code = sig === SIG.KILL ? 137 : 128 + sig;
@@ -11429,12 +11886,15 @@ var RemoteTransport = class {
11429
11886
  port;
11430
11887
  waiting = /* @__PURE__ */ new Map();
11431
11888
  nextId = 0;
11889
+ sockets = /* @__PURE__ */ new Map();
11432
11890
  failure;
11433
11891
  constructor(port, traits) {
11434
11892
  this.port = port;
11435
11893
  this.traits = traits;
11436
11894
  }
11437
11895
  receive(reply) {
11896
+ const socket = this.sockets.get(reply.nid);
11897
+ if (socket && this.toSocket(reply, socket)) return;
11438
11898
  const waiter = this.waiting.get(reply.nid);
11439
11899
  if (!waiter) return;
11440
11900
  this.waiting.delete(reply.nid);
@@ -11442,10 +11902,22 @@ var RemoteTransport = class {
11442
11902
  waiter.reject(new TransportError(reply.message, reply.status, reply.code));
11443
11903
  } else waiter.resolve(reply);
11444
11904
  }
11905
+ toSocket(reply, socket) {
11906
+ if (reply.net === "ws-message") socket.queue.push(reply.data);
11907
+ else if (reply.net === "ws-acked") socket.acked(reply.n);
11908
+ else if (reply.net === "ws-closed") {
11909
+ this.sockets.delete(reply.nid);
11910
+ socket.queue.end({ code: reply.code, reason: reply.reason });
11911
+ } else return false;
11912
+ return true;
11913
+ }
11445
11914
  fail(error) {
11446
11915
  this.failure = error;
11447
11916
  for (const waiter of this.waiting.values()) waiter.reject(error);
11448
11917
  this.waiting.clear();
11918
+ for (const socket of this.sockets.values())
11919
+ socket.queue.end({ code: 1011, reason: error.message });
11920
+ this.sockets.clear();
11449
11921
  }
11450
11922
  ask(call) {
11451
11923
  if (this.failure) return Promise.reject(this.failure);
@@ -11524,6 +11996,46 @@ var RemoteTransport = class {
11524
11996
  }
11525
11997
  };
11526
11998
  }
11999
+ async websocket(request) {
12000
+ const nid = ++this.nextId;
12001
+ const { signal } = request;
12002
+ signal.throwIfAborted();
12003
+ const abort = () => this.cancel(nid);
12004
+ signal.addEventListener("abort", abort, { once: true });
12005
+ let opened2;
12006
+ try {
12007
+ opened2 = await this.ask({
12008
+ net: "ws-open",
12009
+ nid,
12010
+ url: request.url,
12011
+ protocols: request.protocols,
12012
+ headers: request.headers
12013
+ });
12014
+ } finally {
12015
+ signal.removeEventListener("abort", abort);
12016
+ }
12017
+ const close = (code, reason) => this.port.postMessage({ net: "ws-close", nid, code, reason });
12018
+ const queue = new MessageQueue(
12019
+ () => close(1e3, "a message is too big"),
12020
+ Number.POSITIVE_INFINITY,
12021
+ (n) => this.port.postMessage({ net: "ws-consumed", nid, n })
12022
+ );
12023
+ let unacked = 0;
12024
+ this.sockets.set(nid, { queue, acked: (n) => void (unacked -= n) });
12025
+ return {
12026
+ protocol: opened2.protocol,
12027
+ get buffered() {
12028
+ return unacked;
12029
+ },
12030
+ send: (data) => {
12031
+ unacked += payloadSize(data);
12032
+ this.port.postMessage({ net: "ws-send", nid, data });
12033
+ },
12034
+ messages: queue,
12035
+ close,
12036
+ closed: queue.closed
12037
+ };
12038
+ }
11527
12039
  };
11528
12040
 
11529
12041
  // src/serve.ts
@@ -12145,7 +12657,7 @@ function hostfsFetchOf(options) {
12145
12657
  };
12146
12658
  }
12147
12659
  function nodeTransport() {
12148
- const transport = fetchTransport();
12660
+ const transport = fetchTransport({ webSocket: true, webSocketHeaders: true });
12149
12661
  return { ...transport, traits: { ...transport.traits, crossOrigin: "any" } };
12150
12662
  }
12151
12663
  var TERMINATED = "the kernel is terminated";