@ai-ecoverse/slicc-kernel 1.26.6 → 1.28.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 +62 -4
- package/dist/driver-worker.js +1 -1
- package/dist/driver-worker.js.map +1 -1
- package/dist/index.js +221 -12
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +634 -51
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +1 -1
- package/dist/node-driver-worker.js.map +1 -1
- package/dist/node-process-worker.js +89 -20
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +547 -49
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +89 -20
- package/dist/process-worker.js.map +4 -4
- package/dist/testing.js +223 -0
- package/dist/testing.js.map +7 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -282,6 +282,204 @@ async function loopbackFetch(dial, input, options) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
// src/kernel/net/loopback-names.ts
|
|
286
|
+
var HOST_LOOPBACK = "host.slicc.internal";
|
|
287
|
+
var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
|
|
288
|
+
var PAGE_LOOPBACK = "kernel.localhost";
|
|
289
|
+
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
290
|
+
|
|
291
|
+
// src/kernel/net/routes.ts
|
|
292
|
+
var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
|
|
293
|
+
function ipv4Bytes(text) {
|
|
294
|
+
const parts = text.split(".");
|
|
295
|
+
if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
|
|
296
|
+
const bytes = parts.map(Number);
|
|
297
|
+
return bytes.every((b) => b <= 255) ? bytes : void 0;
|
|
298
|
+
}
|
|
299
|
+
function ipv6Groups(text) {
|
|
300
|
+
if (!text.includes(":")) return void 0;
|
|
301
|
+
let canonical;
|
|
302
|
+
try {
|
|
303
|
+
canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);
|
|
304
|
+
} catch {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
const [head, tail] = canonical.split("::");
|
|
308
|
+
const part = (s) => s ? s.split(":").map((h) => Number.parseInt(h, 16)) : [];
|
|
309
|
+
const front = part(head);
|
|
310
|
+
const back = part(tail);
|
|
311
|
+
return tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
|
|
312
|
+
}
|
|
313
|
+
function toBits(values, width) {
|
|
314
|
+
return values.reduce((acc, v) => acc << BigInt(width) | BigInt(v), 0n);
|
|
315
|
+
}
|
|
316
|
+
function addressBits(host) {
|
|
317
|
+
const v4 = ipv4Bytes(host);
|
|
318
|
+
if (v4) return { v6: false, bits: toBits(v4, 8) };
|
|
319
|
+
const v6 = ipv6Groups(host);
|
|
320
|
+
return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
|
|
321
|
+
}
|
|
322
|
+
function parsePrefix(text) {
|
|
323
|
+
const parts = text.trim().split("/");
|
|
324
|
+
if (parts.length > 2) return void 0;
|
|
325
|
+
const [address = "", size] = parts;
|
|
326
|
+
const parsed = addressBits(address);
|
|
327
|
+
if (!parsed) return void 0;
|
|
328
|
+
const max = parsed.v6 ? 128 : 32;
|
|
329
|
+
const length = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
|
|
330
|
+
if (length < 0 || length > max) return void 0;
|
|
331
|
+
return { ...parsed, length };
|
|
332
|
+
}
|
|
333
|
+
function within(prefix, addr) {
|
|
334
|
+
if (prefix.v6 !== addr.v6) return false;
|
|
335
|
+
const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
|
|
336
|
+
return addr.bits >> shift === prefix.bits >> shift;
|
|
337
|
+
}
|
|
338
|
+
var RESERVED = [
|
|
339
|
+
"0.0.0.0/8",
|
|
340
|
+
"127.0.0.0/8",
|
|
341
|
+
"169.254.0.0/16",
|
|
342
|
+
"224.0.0.0/4",
|
|
343
|
+
"240.0.0.0/4",
|
|
344
|
+
"::/96",
|
|
345
|
+
"::ffff:0:0/96",
|
|
346
|
+
"fe80::/10",
|
|
347
|
+
"ff00::/8"
|
|
348
|
+
].map((p) => parsePrefix(p));
|
|
349
|
+
var Routes = class {
|
|
350
|
+
table = [];
|
|
351
|
+
exit = false;
|
|
352
|
+
ipv6;
|
|
353
|
+
constructor(ipv6 = false) {
|
|
354
|
+
this.ipv6 = ipv6;
|
|
355
|
+
}
|
|
356
|
+
set(table) {
|
|
357
|
+
const parsed = (table.prefixes ?? []).map(parsePrefix);
|
|
358
|
+
const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
|
|
359
|
+
if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
|
|
360
|
+
this.table = parsed;
|
|
361
|
+
this.exit = table.exit === true;
|
|
362
|
+
}
|
|
363
|
+
classify(host) {
|
|
364
|
+
const name = host.toLowerCase().replace(/\.$/, "");
|
|
365
|
+
if (name === "localhost" || name.endsWith(".localhost") || name === "0.0.0.0") {
|
|
366
|
+
return "loopback";
|
|
367
|
+
}
|
|
368
|
+
if (name === HOST_LOOPBACK || name === HOST_ADDRESS) return "host";
|
|
369
|
+
const addr = addressBits(name);
|
|
370
|
+
if (!addr) return "unreachable";
|
|
371
|
+
if (addr.v6 && addr.bits === 1n) return "loopback";
|
|
372
|
+
if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
|
|
373
|
+
if (RESERVED.some((p) => within(p, addr))) return "reserved";
|
|
374
|
+
if (addr.v6 && !this.ipv6) return "unreachable";
|
|
375
|
+
if (this.exit || this.table.some((p) => within(p, addr))) return "uplink";
|
|
376
|
+
return "unreachable";
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/kernel/net/uplink.ts
|
|
381
|
+
function answerOf(result) {
|
|
382
|
+
const answer = Array.isArray(result) ? { addresses: result } : result;
|
|
383
|
+
const addresses = Array.isArray(answer?.addresses) ? answer.addresses.filter((a) => typeof a === "string") : [];
|
|
384
|
+
return {
|
|
385
|
+
addresses,
|
|
386
|
+
...typeof answer?.ttl === "number" && answer.ttl >= 0 ? { ttl: answer.ttl } : {}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function failure(uid, e) {
|
|
390
|
+
const code = e?.code;
|
|
391
|
+
return {
|
|
392
|
+
uplink: "error",
|
|
393
|
+
uid,
|
|
394
|
+
message: e instanceof Error ? e.message : String(e),
|
|
395
|
+
...typeof code === "string" ? { code } : {}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
var NEVER = new Routes(true);
|
|
399
|
+
function serveUplink(peer, uplink) {
|
|
400
|
+
const conns = /* @__PURE__ */ new Map();
|
|
401
|
+
const pending = /* @__PURE__ */ new Map();
|
|
402
|
+
let nextCid = 0;
|
|
403
|
+
let closed = false;
|
|
404
|
+
const run = (uid, work) => {
|
|
405
|
+
const abort = new AbortController();
|
|
406
|
+
pending.set(uid, abort);
|
|
407
|
+
void work(abort.signal).then(
|
|
408
|
+
(reply) => reply && peer.postMessage(reply, transferOf(reply)),
|
|
409
|
+
(e) => peer.postMessage(failure(uid, e))
|
|
410
|
+
).finally(() => pending.delete(uid));
|
|
411
|
+
};
|
|
412
|
+
const conn = (cid) => {
|
|
413
|
+
const found = conns.get(cid);
|
|
414
|
+
if (!found) throw Object.assign(new Error("no such uplink connection"), { code: "EBADF" });
|
|
415
|
+
return found;
|
|
416
|
+
};
|
|
417
|
+
const dial = async (call, signal) => {
|
|
418
|
+
const kind = NEVER.classify(call.host);
|
|
419
|
+
if (kind === "loopback" || kind === "host" || kind === "reserved") {
|
|
420
|
+
throw Object.assign(new Error(`the uplink does not carry ${call.host}`), {
|
|
421
|
+
code: "ENETUNREACH"
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
const { host, port } = call;
|
|
425
|
+
const opened = await uplink.dial({ network: "tcp", host, port, signal });
|
|
426
|
+
if (signal.aborted || closed) {
|
|
427
|
+
opened.close();
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
const cid = ++nextCid;
|
|
431
|
+
conns.set(cid, opened);
|
|
432
|
+
const { localAddr, remoteAddr } = opened;
|
|
433
|
+
return { uplink: "dialled", uid: call.uid, cid, localAddr, remoteAddr };
|
|
434
|
+
};
|
|
435
|
+
const drop = (cid) => {
|
|
436
|
+
const found = conns.get(cid);
|
|
437
|
+
conns.delete(cid);
|
|
438
|
+
found?.close();
|
|
439
|
+
};
|
|
440
|
+
return {
|
|
441
|
+
answer(call) {
|
|
442
|
+
switch (call.uplink) {
|
|
443
|
+
case "resolve":
|
|
444
|
+
return run(call.uid, async (signal) => ({
|
|
445
|
+
uplink: "resolved",
|
|
446
|
+
uid: call.uid,
|
|
447
|
+
...answerOf(await uplink.resolve(call.name, call.family, signal))
|
|
448
|
+
}));
|
|
449
|
+
case "dial":
|
|
450
|
+
return run(call.uid, (signal) => dial(call, signal));
|
|
451
|
+
case "cancel":
|
|
452
|
+
return pending.get(call.uid)?.abort();
|
|
453
|
+
case "read":
|
|
454
|
+
return run(call.uid, async () => ({
|
|
455
|
+
uplink: "data",
|
|
456
|
+
uid: call.uid,
|
|
457
|
+
bytes: (await conn(call.cid).read())?.slice() ?? null
|
|
458
|
+
}));
|
|
459
|
+
case "write":
|
|
460
|
+
return run(call.uid, async () => ({
|
|
461
|
+
uplink: "written",
|
|
462
|
+
uid: call.uid,
|
|
463
|
+
n: await conn(call.cid).write(call.bytes)
|
|
464
|
+
}));
|
|
465
|
+
case "close-write":
|
|
466
|
+
return conns.get(call.cid)?.closeWrite();
|
|
467
|
+
default:
|
|
468
|
+
return drop(call.cid);
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
close() {
|
|
472
|
+
closed = true;
|
|
473
|
+
for (const abort of pending.values()) abort.abort();
|
|
474
|
+
for (const cid of [...conns.keys()]) drop(cid);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function transferOf(reply) {
|
|
479
|
+
if (reply.uplink !== "data" || !reply.bytes) return void 0;
|
|
480
|
+
return [reply.bytes.buffer];
|
|
481
|
+
}
|
|
482
|
+
|
|
285
483
|
// src/body-idle.ts
|
|
286
484
|
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
287
485
|
function idleFailure(ms) {
|
|
@@ -561,7 +759,7 @@ var RemoteTransport = class {
|
|
|
561
759
|
};
|
|
562
760
|
|
|
563
761
|
// src/client/protocol.ts
|
|
564
|
-
var PROTOCOL = [1,
|
|
762
|
+
var PROTOCOL = [1, 7];
|
|
565
763
|
function locksOf(scope = globalThis) {
|
|
566
764
|
const locks = scope.navigator?.locks;
|
|
567
765
|
return typeof locks?.request === "function" ? locks : void 0;
|
|
@@ -657,8 +855,8 @@ var Channel = class {
|
|
|
657
855
|
}
|
|
658
856
|
request(call, handlers = {}) {
|
|
659
857
|
const id = ++this.nextId;
|
|
660
|
-
const { failure:
|
|
661
|
-
if (
|
|
858
|
+
const { failure: failure3 } = this;
|
|
859
|
+
if (failure3) return { id, done: Promise.reject(failure3) };
|
|
662
860
|
const done = new Promise((resolve, reject) => {
|
|
663
861
|
this.pending.set(id, { resolve, reject, ...handlers });
|
|
664
862
|
this.port.postMessage({ ...call, id });
|
|
@@ -938,7 +1136,7 @@ var MAX_HEAD = 1024 * 1024;
|
|
|
938
1136
|
var MAX_REQUEST_BODY2 = 64 * 1024 * 1024;
|
|
939
1137
|
var PERMISSIONS = ["loopback-network", "local-network-access"];
|
|
940
1138
|
var LOOPBACK_PAGE = /^(127\.\d+\.\d+\.\d+|localhost|\[::1\])$/;
|
|
941
|
-
var
|
|
1139
|
+
var failure2 = (message, status = 502) => Object.assign(new Error(`local proxy: ${message}`), { status });
|
|
942
1140
|
function encodeHead(url, method, headers) {
|
|
943
1141
|
return JSON.stringify({ url, method, headers }).replace(
|
|
944
1142
|
/[\u007f-]/g,
|
|
@@ -961,7 +1159,7 @@ async function refusalMessage(response) {
|
|
|
961
1159
|
return text || response.statusText || `answered ${response.status}`;
|
|
962
1160
|
}
|
|
963
1161
|
async function refusal(response) {
|
|
964
|
-
return
|
|
1162
|
+
return failure2(await refusalMessage(response), response.ok ? 502 : response.status);
|
|
965
1163
|
}
|
|
966
1164
|
async function permission(permissions) {
|
|
967
1165
|
if (!permissions) return "unknown";
|
|
@@ -990,19 +1188,19 @@ async function readHead(reader, idleMs) {
|
|
|
990
1188
|
for (; ; ) {
|
|
991
1189
|
if (buffer.byteLength >= 4) {
|
|
992
1190
|
const length = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(0);
|
|
993
|
-
if (length > MAX_HEAD) throw
|
|
1191
|
+
if (length > MAX_HEAD) throw failure2(`response head of ${length} bytes`);
|
|
994
1192
|
if (buffer.byteLength >= 4 + length) {
|
|
995
1193
|
let head;
|
|
996
1194
|
try {
|
|
997
1195
|
head = JSON.parse(new TextDecoder().decode(buffer.subarray(4, 4 + length)));
|
|
998
1196
|
} catch {
|
|
999
1197
|
}
|
|
1000
|
-
if (!isHead(head)) throw
|
|
1198
|
+
if (!isHead(head)) throw failure2("malformed response head");
|
|
1001
1199
|
return { head, rest: buffer.subarray(4 + length) };
|
|
1002
1200
|
}
|
|
1003
1201
|
}
|
|
1004
1202
|
const next = await readWithin(reader, idleMs);
|
|
1005
|
-
if (next.done) throw
|
|
1203
|
+
if (next.done) throw failure2("closed before the response head");
|
|
1006
1204
|
buffer = concat2(buffer, next.value);
|
|
1007
1205
|
}
|
|
1008
1206
|
}
|
|
@@ -1082,7 +1280,7 @@ function localProxyTransport(options) {
|
|
|
1082
1280
|
});
|
|
1083
1281
|
} catch (e) {
|
|
1084
1282
|
if (request.signal.aborted) throw e;
|
|
1085
|
-
throw
|
|
1283
|
+
throw failure2(`${endpoint.origin} is unreachable or refuses this origin`);
|
|
1086
1284
|
}
|
|
1087
1285
|
const type = response.headers.get("content-type") ?? "";
|
|
1088
1286
|
if (response.status !== 200 || !type.startsWith(RAW_CONTENT_TYPE) || !response.body) {
|
|
@@ -1136,6 +1334,11 @@ function dialer(call) {
|
|
|
1136
1334
|
};
|
|
1137
1335
|
return { dial, reset };
|
|
1138
1336
|
}
|
|
1337
|
+
function uplinkInit(uplink) {
|
|
1338
|
+
if (!uplink) return {};
|
|
1339
|
+
const traits = { tcp: true, udp: false, ipv6: uplink.traits.ipv6 === true };
|
|
1340
|
+
return { uplink: { traits, ...uplink.routes ? { routes: uplink.routes } : {} } };
|
|
1341
|
+
}
|
|
1139
1342
|
function initCall(options, transport) {
|
|
1140
1343
|
return {
|
|
1141
1344
|
op: "init",
|
|
@@ -1146,6 +1349,7 @@ function initCall(options, transport) {
|
|
|
1146
1349
|
...options.media !== void 0 ? { media: options.media } : {},
|
|
1147
1350
|
...options.ca !== void 0 ? { ca: options.ca } : {},
|
|
1148
1351
|
...transport ? { transport: transport.traits } : {},
|
|
1352
|
+
...uplinkInit(options.network?.uplink),
|
|
1149
1353
|
...options.hostfs ? { hostfs: true } : {},
|
|
1150
1354
|
...options.cdp ? { cdp: true } : {},
|
|
1151
1355
|
...options.processMounts !== void 0 ? {
|
|
@@ -1177,19 +1381,22 @@ async function createKernel(options = {}) {
|
|
|
1177
1381
|
const worker = new Worker(url, { type: "module", name: "slicc-kernel" });
|
|
1178
1382
|
const pending = /* @__PURE__ */ new Map();
|
|
1179
1383
|
let nextId = 0;
|
|
1180
|
-
let
|
|
1384
|
+
let failure3;
|
|
1181
1385
|
const dials = dialer((req) => call(req));
|
|
1182
1386
|
const cdp = cdpBridge(options.cdp);
|
|
1183
1387
|
const fail = (error) => {
|
|
1184
1388
|
dials.reset();
|
|
1185
1389
|
cdp.close();
|
|
1186
|
-
|
|
1390
|
+
failure3 = error;
|
|
1187
1391
|
bridge?.close();
|
|
1392
|
+
uplinks?.close();
|
|
1188
1393
|
for (const call2 of pending.values()) call2.reject(error);
|
|
1189
1394
|
pending.clear();
|
|
1190
1395
|
};
|
|
1191
1396
|
const transport = options.network?.transport;
|
|
1192
1397
|
const bridge = transport ? serveTransport(worker, transport) : void 0;
|
|
1398
|
+
const uplink = options.network?.uplink;
|
|
1399
|
+
const uplinks = uplink ? serveUplink(worker, uplink) : void 0;
|
|
1193
1400
|
async function chosen(handle) {
|
|
1194
1401
|
if (handle) {
|
|
1195
1402
|
const state = await handle.requestPermission?.({ mode: "readwrite" });
|
|
@@ -1228,6 +1435,7 @@ async function createKernel(options = {}) {
|
|
|
1228
1435
|
data
|
|
1229
1436
|
}) => {
|
|
1230
1437
|
if ("net" in data) return bridge?.answer(data);
|
|
1438
|
+
if ("uplink" in data) return uplinks?.answer(data);
|
|
1231
1439
|
if ("cdpOpen" in data) return cdp.open(data);
|
|
1232
1440
|
if ("mountPolicy" in data) return void policy(data.mountPolicy);
|
|
1233
1441
|
if ("medium" in data) return pendingMedium(data.medium);
|
|
@@ -1247,7 +1455,7 @@ async function createKernel(options = {}) {
|
|
|
1247
1455
|
});
|
|
1248
1456
|
function request(req, handlers = {}) {
|
|
1249
1457
|
const id = ++nextId;
|
|
1250
|
-
if (
|
|
1458
|
+
if (failure3) return { id, done: Promise.reject(failure3) };
|
|
1251
1459
|
const done = new Promise((resolve, reject) => {
|
|
1252
1460
|
pending.set(id, { resolve, reject, ...handlers });
|
|
1253
1461
|
worker.postMessage({ ...req, id });
|
|
@@ -1309,6 +1517,7 @@ async function createKernel(options = {}) {
|
|
|
1309
1517
|
},
|
|
1310
1518
|
openTerminal,
|
|
1311
1519
|
connect: async () => await call({ op: "connect" }),
|
|
1520
|
+
setRoutes: async (routes) => void await call({ op: "routes", routes }),
|
|
1312
1521
|
dial,
|
|
1313
1522
|
loopbackFetch: (input, fetchOptions) => loopbackFetch(dial, input, fetchOptions),
|
|
1314
1523
|
mount: async (spec) => await call({ op: "mount", spec }),
|