@ai-ecoverse/slicc-kernel 1.37.1 → 1.38.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 +2 -1
- package/dist/driver-worker.js +123 -0
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +309 -6
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +597 -198
- 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.js +528 -50
- package/dist/node.js.map +4 -4
- package/package.json +1 -1
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
|
|
1396
|
+
const headers2 = [];
|
|
1247
1397
|
response.headers.forEach((value, name2) => {
|
|
1248
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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,
|
|
5760
|
+
function timedSource(conn, idle, stop) {
|
|
5476
5761
|
return {
|
|
5477
|
-
read: (max) =>
|
|
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
|
|
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
|
|
5663
|
-
if (
|
|
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
|
|
@@ -11429,12 +11852,15 @@ var RemoteTransport = class {
|
|
|
11429
11852
|
port;
|
|
11430
11853
|
waiting = /* @__PURE__ */ new Map();
|
|
11431
11854
|
nextId = 0;
|
|
11855
|
+
sockets = /* @__PURE__ */ new Map();
|
|
11432
11856
|
failure;
|
|
11433
11857
|
constructor(port, traits) {
|
|
11434
11858
|
this.port = port;
|
|
11435
11859
|
this.traits = traits;
|
|
11436
11860
|
}
|
|
11437
11861
|
receive(reply) {
|
|
11862
|
+
const socket = this.sockets.get(reply.nid);
|
|
11863
|
+
if (socket && this.toSocket(reply, socket)) return;
|
|
11438
11864
|
const waiter = this.waiting.get(reply.nid);
|
|
11439
11865
|
if (!waiter) return;
|
|
11440
11866
|
this.waiting.delete(reply.nid);
|
|
@@ -11442,10 +11868,22 @@ var RemoteTransport = class {
|
|
|
11442
11868
|
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
11443
11869
|
} else waiter.resolve(reply);
|
|
11444
11870
|
}
|
|
11871
|
+
toSocket(reply, socket) {
|
|
11872
|
+
if (reply.net === "ws-message") socket.queue.push(reply.data);
|
|
11873
|
+
else if (reply.net === "ws-acked") socket.acked(reply.n);
|
|
11874
|
+
else if (reply.net === "ws-closed") {
|
|
11875
|
+
this.sockets.delete(reply.nid);
|
|
11876
|
+
socket.queue.end({ code: reply.code, reason: reply.reason });
|
|
11877
|
+
} else return false;
|
|
11878
|
+
return true;
|
|
11879
|
+
}
|
|
11445
11880
|
fail(error) {
|
|
11446
11881
|
this.failure = error;
|
|
11447
11882
|
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
11448
11883
|
this.waiting.clear();
|
|
11884
|
+
for (const socket of this.sockets.values())
|
|
11885
|
+
socket.queue.end({ code: 1011, reason: error.message });
|
|
11886
|
+
this.sockets.clear();
|
|
11449
11887
|
}
|
|
11450
11888
|
ask(call) {
|
|
11451
11889
|
if (this.failure) return Promise.reject(this.failure);
|
|
@@ -11524,6 +11962,46 @@ var RemoteTransport = class {
|
|
|
11524
11962
|
}
|
|
11525
11963
|
};
|
|
11526
11964
|
}
|
|
11965
|
+
async websocket(request) {
|
|
11966
|
+
const nid = ++this.nextId;
|
|
11967
|
+
const { signal } = request;
|
|
11968
|
+
signal.throwIfAborted();
|
|
11969
|
+
const abort = () => this.cancel(nid);
|
|
11970
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
11971
|
+
let opened2;
|
|
11972
|
+
try {
|
|
11973
|
+
opened2 = await this.ask({
|
|
11974
|
+
net: "ws-open",
|
|
11975
|
+
nid,
|
|
11976
|
+
url: request.url,
|
|
11977
|
+
protocols: request.protocols,
|
|
11978
|
+
headers: request.headers
|
|
11979
|
+
});
|
|
11980
|
+
} finally {
|
|
11981
|
+
signal.removeEventListener("abort", abort);
|
|
11982
|
+
}
|
|
11983
|
+
const close = (code, reason) => this.port.postMessage({ net: "ws-close", nid, code, reason });
|
|
11984
|
+
const queue = new MessageQueue(
|
|
11985
|
+
() => close(1e3, "a message is too big"),
|
|
11986
|
+
Number.POSITIVE_INFINITY,
|
|
11987
|
+
(n) => this.port.postMessage({ net: "ws-consumed", nid, n })
|
|
11988
|
+
);
|
|
11989
|
+
let unacked = 0;
|
|
11990
|
+
this.sockets.set(nid, { queue, acked: (n) => void (unacked -= n) });
|
|
11991
|
+
return {
|
|
11992
|
+
protocol: opened2.protocol,
|
|
11993
|
+
get buffered() {
|
|
11994
|
+
return unacked;
|
|
11995
|
+
},
|
|
11996
|
+
send: (data) => {
|
|
11997
|
+
unacked += payloadSize(data);
|
|
11998
|
+
this.port.postMessage({ net: "ws-send", nid, data });
|
|
11999
|
+
},
|
|
12000
|
+
messages: queue,
|
|
12001
|
+
close,
|
|
12002
|
+
closed: queue.closed
|
|
12003
|
+
};
|
|
12004
|
+
}
|
|
11527
12005
|
};
|
|
11528
12006
|
|
|
11529
12007
|
// src/serve.ts
|
|
@@ -12145,7 +12623,7 @@ function hostfsFetchOf(options) {
|
|
|
12145
12623
|
};
|
|
12146
12624
|
}
|
|
12147
12625
|
function nodeTransport() {
|
|
12148
|
-
const transport = fetchTransport();
|
|
12626
|
+
const transport = fetchTransport({ webSocket: true, webSocketHeaders: true });
|
|
12149
12627
|
return { ...transport, traits: { ...transport.traits, crossOrigin: "any" } };
|
|
12150
12628
|
}
|
|
12151
12629
|
var TERMINATED = "the kernel is terminated";
|