@ai-ecoverse/slicc-kernel 1.171.0 → 1.172.2
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 +25 -8
- package/dist/driver-worker.js +4 -2
- package/dist/driver-worker.js.map +2 -2
- package/dist/index.js +260 -42
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +602 -230
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +4 -2
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.cjs +1 -1
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +462 -233
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +1 -1
- package/dist/testing.js +498 -3
- package/dist/testing.js.map +4 -4
- package/dist/types/fs/attest.d.ts +12 -2
- package/dist/types/fs/portable.d.ts +11 -0
- package/dist/types/fs/roots.d.ts +1 -1
- package/dist/types/index.d.ts +12 -4
- package/dist/types/kernel/net/http-syscalls.d.ts +3 -1
- package/dist/types/kernel/net/loopback-transport.d.ts +1 -1
- package/dist/types/kernel/net/placeholder-floor.d.ts +9 -3
- package/dist/types/kernel/net/remote-transport.d.ts +4 -1
- package/dist/types/kernel/net/sealed-fetch.d.ts +27 -0
- package/dist/types/kernel/net/transport.d.ts +2 -1
- package/dist/types/kernel/socket.d.ts +4 -1
- package/dist/types/launcher.d.ts +12 -2
- package/dist/types/mount/media.d.ts +3 -1
- package/dist/types/node.d.ts +12 -4
- package/dist/types/sealed-relay.d.ts +12 -2
- package/dist/types/serve.d.ts +14 -5
- package/dist/types/testing.d.ts +3 -0
- package/dist/types/transport.d.ts +2 -1
- package/package.json +2 -2
package/dist/process-worker.js
CHANGED
|
@@ -3118,7 +3118,7 @@ var init_version = __esm({
|
|
|
3118
3118
|
"src/version.ts"() {
|
|
3119
3119
|
"use strict";
|
|
3120
3120
|
init_captured_globals();
|
|
3121
|
-
KERNEL_VERSION = true ? "1.
|
|
3121
|
+
KERNEL_VERSION = true ? "1.172.2" : "0.0.0-dev";
|
|
3122
3122
|
OS_RELEASE = "6.0.0-slicc";
|
|
3123
3123
|
}
|
|
3124
3124
|
});
|
package/dist/testing.js
CHANGED
|
@@ -126,11 +126,11 @@ function fakeCronClock(start = Date.UTC(2026, 0, 1), utc = true) {
|
|
|
126
126
|
get sleepers() {
|
|
127
127
|
return sleepers.size;
|
|
128
128
|
},
|
|
129
|
-
sleep(
|
|
129
|
+
sleep(until2, signal) {
|
|
130
130
|
if (signal.aborted) return Promise.resolve();
|
|
131
131
|
return new Promise((resolve) => {
|
|
132
132
|
const sleeper = {
|
|
133
|
-
until,
|
|
133
|
+
until: until2,
|
|
134
134
|
wake: () => {
|
|
135
135
|
sleepers.delete(sleeper);
|
|
136
136
|
signal.removeEventListener("abort", sleeper.wake);
|
|
@@ -145,7 +145,7 @@ function fakeCronClock(start = Date.UTC(2026, 0, 1), utc = true) {
|
|
|
145
145
|
awake = Math.max(0, awake - 1);
|
|
146
146
|
signal.addEventListener("abort", sleeper.wake, { once: true });
|
|
147
147
|
sleepers.add(sleeper);
|
|
148
|
-
if (
|
|
148
|
+
if (until2 <= now) queueMicrotask(sleeper.ring);
|
|
149
149
|
else check();
|
|
150
150
|
});
|
|
151
151
|
},
|
|
@@ -166,6 +166,223 @@ function fakeCronClock(start = Date.UTC(2026, 0, 1), utc = true) {
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// src/kernel/net/placeholder-floor.ts
|
|
170
|
+
var PLACEHOLDER = /fnox_proxy_[0-9a-f]{24}/;
|
|
171
|
+
var ESCAPE = /%([0-9a-f]{2})/gi;
|
|
172
|
+
var BASIC = /\bbasic\b(.*)/is;
|
|
173
|
+
var PERCENT_LAYERS = 8;
|
|
174
|
+
var BASE64_GROUP = 4;
|
|
175
|
+
var PLACEHOLDER_REFUSED = "refused: this request has a sealed placeholder in its URL, a header or a Basic credential, and nothing fills it, so it was not sent";
|
|
176
|
+
var PlaceholderRefused = class extends Error {
|
|
177
|
+
status = 403;
|
|
178
|
+
code = "EACCES";
|
|
179
|
+
constructor(reason = PLACEHOLDER_REFUSED) {
|
|
180
|
+
super(reason);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
function holds(text, known) {
|
|
184
|
+
return PLACEHOLDER.test(text) || known.some((placeholder) => text.includes(placeholder));
|
|
185
|
+
}
|
|
186
|
+
function carries(text, known) {
|
|
187
|
+
let layer = text;
|
|
188
|
+
for (let i = 0; i < PERCENT_LAYERS; i++) {
|
|
189
|
+
if (holds(layer, known)) return true;
|
|
190
|
+
const next = layer.replace(
|
|
191
|
+
ESCAPE,
|
|
192
|
+
(_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
|
|
193
|
+
);
|
|
194
|
+
if (next === layer) return false;
|
|
195
|
+
layer = next;
|
|
196
|
+
}
|
|
197
|
+
return holds(layer, known);
|
|
198
|
+
}
|
|
199
|
+
function basicCarries(value, known) {
|
|
200
|
+
const credential = BASIC.exec(value)?.[1];
|
|
201
|
+
if (credential === void 0) return false;
|
|
202
|
+
const letters = credential.replace(/[^A-Za-z0-9+/_-]/g, "").replaceAll("-", "+").replaceAll("_", "/");
|
|
203
|
+
for (let shift = 0; shift < BASE64_GROUP; shift++) {
|
|
204
|
+
const part = letters.slice(shift);
|
|
205
|
+
const whole = part.length % BASE64_GROUP === 1 ? part.slice(0, -1) : part;
|
|
206
|
+
if (carries(atob(whole), known)) return true;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
function headersCarry(headers, known) {
|
|
211
|
+
return headers.some(
|
|
212
|
+
([name, value]) => carries(name, known) || carries(name.toLowerCase(), known) || carries(value, known) || basicCarries(value, known)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
function urlCarries(url, known) {
|
|
216
|
+
return carries(url, known) || URL.canParse(url) && carries(new URL(url).href, known);
|
|
217
|
+
}
|
|
218
|
+
function detected(request, known) {
|
|
219
|
+
return urlCarries(request.url, known) || headersCarry(request.headers, known);
|
|
220
|
+
}
|
|
221
|
+
function refuseSocket(request, known) {
|
|
222
|
+
if (detected(request, known) || request.protocols.some((p) => carries(p, known))) {
|
|
223
|
+
throw new PlaceholderRefused();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function placeholderFloor(inner, { known = () => [], fill } = {}) {
|
|
227
|
+
const open = inner.websocket?.bind(inner);
|
|
228
|
+
return {
|
|
229
|
+
get traits() {
|
|
230
|
+
return inner.traits;
|
|
231
|
+
},
|
|
232
|
+
...open ? {
|
|
233
|
+
websocket: async (request) => {
|
|
234
|
+
refuseSocket(request, known());
|
|
235
|
+
return open(request);
|
|
236
|
+
}
|
|
237
|
+
} : {},
|
|
238
|
+
async fetch(request, owner, stamp) {
|
|
239
|
+
if (!detected(request, known())) return inner.fetch(request, owner, stamp);
|
|
240
|
+
if (!fill || !stamp) throw new PlaceholderRefused();
|
|
241
|
+
return fill(request, stamp);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/body-idle.ts
|
|
247
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
248
|
+
|
|
249
|
+
// src/kernel/net/sealed-fetch.ts
|
|
250
|
+
var UNSAFE_FIELD = /[\r\n\0]/;
|
|
251
|
+
function headerList(headers) {
|
|
252
|
+
return Array.isArray(headers) && headers.every(
|
|
253
|
+
(field) => Array.isArray(field) && field.length === 2 && field.every((text) => typeof text === "string" && !UNSAFE_FIELD.test(text))
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
function sealedReplyValid(reply) {
|
|
257
|
+
if (typeof reply !== "object" || reply === null) return false;
|
|
258
|
+
const { redirects, redirected, status, statusText, headers, url, body, cancel } = reply;
|
|
259
|
+
return redirects === void 0 && redirected !== true && Number.isInteger(status) && status >= 100 && status <= 599 && typeof statusText === "string" && !UNSAFE_FIELD.test(statusText) && headerList(headers) && (url === void 0 || typeof url === "string") && typeof body?.[Symbol.asyncIterator] === "function" && typeof cancel === "function";
|
|
260
|
+
}
|
|
261
|
+
function dropSealedReply(reply) {
|
|
262
|
+
const { cancel } = reply ?? {};
|
|
263
|
+
if (typeof cancel !== "function") return;
|
|
264
|
+
void Promise.resolve().then(() => cancel.call(reply)).catch(() => void 0);
|
|
265
|
+
}
|
|
266
|
+
function failed(reason) {
|
|
267
|
+
const text = reason instanceof Error ? reason.message : String(reason);
|
|
268
|
+
return new PlaceholderRefused(
|
|
269
|
+
`refused: the request with a sealed placeholder was not completed: ${text}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
var Stall = class {
|
|
273
|
+
idle = new AbortController();
|
|
274
|
+
ms;
|
|
275
|
+
timer;
|
|
276
|
+
signal;
|
|
277
|
+
constructor(signals, ms) {
|
|
278
|
+
this.ms = ms;
|
|
279
|
+
this.signal = AbortSignal.any([...signals, this.idle.signal]);
|
|
280
|
+
}
|
|
281
|
+
arm() {
|
|
282
|
+
clearTimeout(this.timer);
|
|
283
|
+
this.timer = setTimeout(() => this.idle.abort("timeout"), this.ms);
|
|
284
|
+
this.timer.unref?.();
|
|
285
|
+
}
|
|
286
|
+
touch() {
|
|
287
|
+
if (this.timer !== void 0) this.arm();
|
|
288
|
+
}
|
|
289
|
+
rest() {
|
|
290
|
+
clearTimeout(this.timer);
|
|
291
|
+
this.timer = void 0;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
async function until(work, signal) {
|
|
295
|
+
const stopped = Promise.withResolvers();
|
|
296
|
+
const stop = () => stopped.reject(signal.reason);
|
|
297
|
+
if (signal.aborted) stop();
|
|
298
|
+
else signal.addEventListener("abort", stop, { once: true });
|
|
299
|
+
try {
|
|
300
|
+
return await Promise.race([work, stopped.promise]);
|
|
301
|
+
} finally {
|
|
302
|
+
signal.removeEventListener("abort", stop);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async function* ticking(stream, stall) {
|
|
306
|
+
for await (const chunk of stream) {
|
|
307
|
+
stall.touch();
|
|
308
|
+
yield chunk;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function sealedRequest(request, stamp, stall) {
|
|
312
|
+
const { method, url, headers, body, bodyStream } = request;
|
|
313
|
+
return {
|
|
314
|
+
v: 1,
|
|
315
|
+
caller: structuredClone(stamp),
|
|
316
|
+
method,
|
|
317
|
+
url,
|
|
318
|
+
headers: headers.map(([name, value]) => [name, value]),
|
|
319
|
+
redirect: "manual",
|
|
320
|
+
...bodyStream ? { bodyStream: ticking(bodyStream, stall) } : body ? { body } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function guarded(reply, stall) {
|
|
324
|
+
let finished = false;
|
|
325
|
+
const cancel = async () => {
|
|
326
|
+
stall.signal.removeEventListener("abort", dropped);
|
|
327
|
+
stall.rest();
|
|
328
|
+
if (finished) return;
|
|
329
|
+
finished = true;
|
|
330
|
+
await reply.cancel().catch(() => void 0);
|
|
331
|
+
};
|
|
332
|
+
const dropped = () => void cancel();
|
|
333
|
+
stall.signal.addEventListener("abort", dropped, { once: true });
|
|
334
|
+
const source = reply.body[Symbol.asyncIterator]();
|
|
335
|
+
const body = (async function* () {
|
|
336
|
+
try {
|
|
337
|
+
for (; ; ) {
|
|
338
|
+
stall.arm();
|
|
339
|
+
const next = await until(source.next(), stall.signal);
|
|
340
|
+
stall.rest();
|
|
341
|
+
if (next.done) {
|
|
342
|
+
finished = true;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!(next.value instanceof Uint8Array)) throw failed("a body chunk is not bytes");
|
|
346
|
+
if (next.value.byteLength > 0) yield next.value;
|
|
347
|
+
}
|
|
348
|
+
} finally {
|
|
349
|
+
await cancel();
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
return {
|
|
353
|
+
...reply.url !== void 0 ? { url: reply.url } : {},
|
|
354
|
+
status: reply.status,
|
|
355
|
+
statusText: reply.statusText,
|
|
356
|
+
headers: reply.headers,
|
|
357
|
+
body,
|
|
358
|
+
cancel
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function sealedFetcher(hook, stop, idleMs = BODY_IDLE_MS) {
|
|
362
|
+
return async (request, stamp) => {
|
|
363
|
+
const stall = new Stall([request.signal, stop], idleMs);
|
|
364
|
+
const { signal } = stall;
|
|
365
|
+
if (signal.aborted) throw failed(signal.reason);
|
|
366
|
+
stall.arm();
|
|
367
|
+
const asked = Promise.resolve().then(() => {
|
|
368
|
+
signal.throwIfAborted();
|
|
369
|
+
return hook(sealedRequest(request, stamp, stall), { signal });
|
|
370
|
+
});
|
|
371
|
+
let reply;
|
|
372
|
+
try {
|
|
373
|
+
reply = await until(asked, signal);
|
|
374
|
+
} catch (e) {
|
|
375
|
+
void asked.then(dropSealedReply, () => void 0);
|
|
376
|
+
throw failed(e);
|
|
377
|
+
} finally {
|
|
378
|
+
stall.rest();
|
|
379
|
+
}
|
|
380
|
+
if (sealedReplyValid(reply)) return guarded(reply, stall);
|
|
381
|
+
dropSealedReply(reply);
|
|
382
|
+
throw failed("the reply is not a scrubbed response");
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
169
386
|
// src/kernel/test-hooks.ts
|
|
170
387
|
var SYSCALL_HOOK = /* @__PURE__ */ Symbol.for("slicc-kernel.test.syscall");
|
|
171
388
|
function setTestSyscallHook(hook) {
|
|
@@ -174,6 +391,280 @@ function setTestSyscallHook(hook) {
|
|
|
174
391
|
else delete holder[SYSCALL_HOOK];
|
|
175
392
|
}
|
|
176
393
|
|
|
394
|
+
// src/bytes.ts
|
|
395
|
+
function fresh(bytes, start = 0, end = bytes.length) {
|
|
396
|
+
return new Uint8Array(bytes.subarray(start, end));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/sealed-relay.ts
|
|
400
|
+
function textOf(err) {
|
|
401
|
+
return err instanceof Error ? err.message : String(err);
|
|
402
|
+
}
|
|
403
|
+
function sendBytes(port, bytes) {
|
|
404
|
+
const copy = fresh(bytes);
|
|
405
|
+
port.postMessage({ t: "chunk", bytes: copy }, [copy.buffer]);
|
|
406
|
+
}
|
|
407
|
+
function askFetch(port, req, signal) {
|
|
408
|
+
signal.throwIfAborted();
|
|
409
|
+
const { bodyStream, ...wire } = req;
|
|
410
|
+
const { port1: near, port2: far } = new MessageChannel();
|
|
411
|
+
const upload = bodyStream?.[Symbol.asyncIterator]();
|
|
412
|
+
const head = Promise.withResolvers();
|
|
413
|
+
let reading;
|
|
414
|
+
let ended = false;
|
|
415
|
+
const end = (failure2) => {
|
|
416
|
+
if (ended) return;
|
|
417
|
+
ended = true;
|
|
418
|
+
signal.removeEventListener("abort", abort);
|
|
419
|
+
near.close();
|
|
420
|
+
void upload?.return?.().catch(() => void 0);
|
|
421
|
+
head.reject(failure2);
|
|
422
|
+
reading?.reject(failure2);
|
|
423
|
+
reading = void 0;
|
|
424
|
+
};
|
|
425
|
+
const abort = () => {
|
|
426
|
+
near.postMessage({ t: "cancel", reason: String(signal.reason) });
|
|
427
|
+
end(new Error(String(signal.reason)));
|
|
428
|
+
};
|
|
429
|
+
const pull = async () => {
|
|
430
|
+
try {
|
|
431
|
+
const next = await upload.next();
|
|
432
|
+
if (ended) return;
|
|
433
|
+
if (next.done) near.postMessage({ t: "end" });
|
|
434
|
+
else sendBytes(near, next.value);
|
|
435
|
+
} catch (err) {
|
|
436
|
+
if (!ended) near.postMessage({ t: "error", message: textOf(err) });
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const respond = (message) => {
|
|
440
|
+
const { status, statusText, headers, url } = message;
|
|
441
|
+
const body = {
|
|
442
|
+
[Symbol.asyncIterator]: () => ({
|
|
443
|
+
next: () => {
|
|
444
|
+
if (ended) return Promise.resolve({ value: void 0, done: true });
|
|
445
|
+
reading = Promise.withResolvers();
|
|
446
|
+
near.postMessage({ t: "read" });
|
|
447
|
+
return reading.promise;
|
|
448
|
+
},
|
|
449
|
+
return: async () => {
|
|
450
|
+
await cancel();
|
|
451
|
+
return { value: void 0, done: true };
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
};
|
|
455
|
+
const cancel = async () => {
|
|
456
|
+
if (ended) return;
|
|
457
|
+
near.postMessage({ t: "cancel", reason: "cancelled" });
|
|
458
|
+
end(new Error("the request was cancelled"));
|
|
459
|
+
};
|
|
460
|
+
head.resolve({
|
|
461
|
+
...url !== void 0 ? { url } : {},
|
|
462
|
+
status,
|
|
463
|
+
statusText,
|
|
464
|
+
headers,
|
|
465
|
+
body,
|
|
466
|
+
cancel
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
near.addEventListener("message", ({ data }) => {
|
|
470
|
+
if (data.t === "pull") void pull();
|
|
471
|
+
else if (data.t === "stop") void upload?.return?.().catch(() => void 0);
|
|
472
|
+
else if (data.t === "head") respond(data);
|
|
473
|
+
else if (data.t === "chunk") {
|
|
474
|
+
reading?.resolve({ value: data.bytes, done: false });
|
|
475
|
+
reading = void 0;
|
|
476
|
+
} else if (data.t === "end") {
|
|
477
|
+
reading?.resolve({ value: void 0, done: true });
|
|
478
|
+
reading = void 0;
|
|
479
|
+
end(new Error("the response ended"));
|
|
480
|
+
} else end(new Error(data.message));
|
|
481
|
+
});
|
|
482
|
+
near.addEventListener("close", () => end(new Error("the sealed fetch relay closed")));
|
|
483
|
+
near.start();
|
|
484
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
485
|
+
const ask = {
|
|
486
|
+
req: { ...wire, ...upload ? { streamed: true } : {} },
|
|
487
|
+
port: far
|
|
488
|
+
};
|
|
489
|
+
port.postMessage({ sealedFetch: ask }, [far]);
|
|
490
|
+
return head.promise;
|
|
491
|
+
}
|
|
492
|
+
function uploadOf(far) {
|
|
493
|
+
let waiting;
|
|
494
|
+
let failure2;
|
|
495
|
+
const next = () => {
|
|
496
|
+
if (failure2) return Promise.reject(failure2);
|
|
497
|
+
waiting = Promise.withResolvers();
|
|
498
|
+
far.postMessage({ t: "pull" });
|
|
499
|
+
return waiting.promise;
|
|
500
|
+
};
|
|
501
|
+
return {
|
|
502
|
+
stream: {
|
|
503
|
+
[Symbol.asyncIterator]: () => ({
|
|
504
|
+
next,
|
|
505
|
+
return: async () => {
|
|
506
|
+
far.postMessage({ t: "stop" });
|
|
507
|
+
return { value: void 0, done: true };
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
},
|
|
511
|
+
answer(message) {
|
|
512
|
+
if (message.t === "chunk") waiting?.resolve({ value: message.bytes, done: false });
|
|
513
|
+
else if (message.t === "end") waiting?.resolve({ value: void 0, done: true });
|
|
514
|
+
else {
|
|
515
|
+
failure2 = new Error(message.message);
|
|
516
|
+
waiting?.reject(failure2);
|
|
517
|
+
}
|
|
518
|
+
waiting = void 0;
|
|
519
|
+
},
|
|
520
|
+
fail(reason) {
|
|
521
|
+
failure2 = reason;
|
|
522
|
+
waiting?.reject(reason);
|
|
523
|
+
waiting = void 0;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function answerFetch(hook, { req, port: far }, live) {
|
|
528
|
+
const control = new AbortController();
|
|
529
|
+
const { streamed, ...fields } = req;
|
|
530
|
+
const upload = uploadOf(far);
|
|
531
|
+
let source;
|
|
532
|
+
let reply;
|
|
533
|
+
let closed = false;
|
|
534
|
+
const settle = () => {
|
|
535
|
+
closed = true;
|
|
536
|
+
live.delete(answer);
|
|
537
|
+
upload.fail(new Error("the request ended"));
|
|
538
|
+
};
|
|
539
|
+
const answer = {
|
|
540
|
+
close(reason) {
|
|
541
|
+
if (closed) return;
|
|
542
|
+
settle();
|
|
543
|
+
control.abort(reason);
|
|
544
|
+
dropSealedReply(reply);
|
|
545
|
+
far.close();
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const finish = (message) => {
|
|
549
|
+
if (closed) return;
|
|
550
|
+
settle();
|
|
551
|
+
far.postMessage(message);
|
|
552
|
+
};
|
|
553
|
+
const failWith = (err) => {
|
|
554
|
+
dropSealedReply(reply);
|
|
555
|
+
finish({ t: "error", message: textOf(err) });
|
|
556
|
+
};
|
|
557
|
+
const read = async () => {
|
|
558
|
+
try {
|
|
559
|
+
const next = await source.next();
|
|
560
|
+
if (closed) return;
|
|
561
|
+
if (next.done) finish({ t: "end" });
|
|
562
|
+
else if (next.value instanceof Uint8Array) sendBytes(far, next.value);
|
|
563
|
+
else failWith("a body chunk is not bytes");
|
|
564
|
+
} catch (err) {
|
|
565
|
+
failWith(err);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
const ask = async () => {
|
|
569
|
+
try {
|
|
570
|
+
if (!hook) throw new Error("no vault is connected");
|
|
571
|
+
const request = {
|
|
572
|
+
...fields,
|
|
573
|
+
...streamed ? { bodyStream: upload.stream } : {}
|
|
574
|
+
};
|
|
575
|
+
const answered = await hook(request, { signal: control.signal });
|
|
576
|
+
if (!sealedReplyValid(answered)) {
|
|
577
|
+
dropSealedReply(answered);
|
|
578
|
+
throw new Error("the reply is not a scrubbed response");
|
|
579
|
+
}
|
|
580
|
+
if (closed) return dropSealedReply(answered);
|
|
581
|
+
reply = answered;
|
|
582
|
+
source = answered.body[Symbol.asyncIterator]();
|
|
583
|
+
const { status, statusText, headers, url } = answered;
|
|
584
|
+
far.postMessage({
|
|
585
|
+
t: "head",
|
|
586
|
+
status,
|
|
587
|
+
statusText,
|
|
588
|
+
headers,
|
|
589
|
+
...url !== void 0 ? { url } : {}
|
|
590
|
+
});
|
|
591
|
+
} catch (err) {
|
|
592
|
+
failWith(err);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
far.addEventListener("message", ({ data }) => {
|
|
596
|
+
if (data.t === "read") void read();
|
|
597
|
+
else if (data.t === "cancel") answer.close(data.reason);
|
|
598
|
+
else upload.answer(data);
|
|
599
|
+
});
|
|
600
|
+
far.addEventListener("close", () => answer.close("withdrawn"));
|
|
601
|
+
far.start();
|
|
602
|
+
live.add(answer);
|
|
603
|
+
void ask();
|
|
604
|
+
}
|
|
605
|
+
function sealedAsker(port) {
|
|
606
|
+
const asks = /* @__PURE__ */ new Map();
|
|
607
|
+
let next = 0;
|
|
608
|
+
const hook = (req, { signal }) => {
|
|
609
|
+
const id = ++next;
|
|
610
|
+
const waiting = Promise.withResolvers();
|
|
611
|
+
asks.set(id, waiting);
|
|
612
|
+
const abort = () => {
|
|
613
|
+
if (!asks.delete(id)) return;
|
|
614
|
+
port.postMessage({ sealedAbort: { id, reason: signal.reason } });
|
|
615
|
+
waiting.reject(new Error(String(signal.reason)));
|
|
616
|
+
};
|
|
617
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
618
|
+
port.postMessage({ sealedAsk: { id, req } });
|
|
619
|
+
return waiting.promise.finally(() => signal.removeEventListener("abort", abort));
|
|
620
|
+
};
|
|
621
|
+
const answer = ({ id, reply, error }) => {
|
|
622
|
+
const waiting = asks.get(id);
|
|
623
|
+
if (!waiting) return;
|
|
624
|
+
asks.delete(id);
|
|
625
|
+
if (reply) waiting.resolve(reply);
|
|
626
|
+
else waiting.reject(new Error(error ?? "no reply"));
|
|
627
|
+
};
|
|
628
|
+
return { hook, fetch: (req, { signal }) => askFetch(port, req, signal), answer };
|
|
629
|
+
}
|
|
630
|
+
function sealedAnswerer(port, hook, fetchHook) {
|
|
631
|
+
const asked = /* @__PURE__ */ new Map();
|
|
632
|
+
const fetches = /* @__PURE__ */ new Set();
|
|
633
|
+
const ask = async ({ id, req }) => {
|
|
634
|
+
const control = new AbortController();
|
|
635
|
+
asked.set(id, control);
|
|
636
|
+
try {
|
|
637
|
+
if (!hook) throw new Error("no vault is connected");
|
|
638
|
+
const reply = await hook(req, { signal: control.signal });
|
|
639
|
+
port.postMessage({ sealedReply: { id, reply } });
|
|
640
|
+
} catch (err) {
|
|
641
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
642
|
+
port.postMessage({ sealedReply: { id, error } });
|
|
643
|
+
} finally {
|
|
644
|
+
asked.delete(id);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
const abort = ({ id, reason }) => {
|
|
648
|
+
asked.get(id)?.abort(reason);
|
|
649
|
+
asked.delete(id);
|
|
650
|
+
};
|
|
651
|
+
return {
|
|
652
|
+
handle(data) {
|
|
653
|
+
const message = data;
|
|
654
|
+
if (message?.sealedAsk) void ask(message.sealedAsk);
|
|
655
|
+
else if (message?.sealedAbort) abort(message.sealedAbort);
|
|
656
|
+
else if (message?.sealedFetch) answerFetch(fetchHook, message.sealedFetch, fetches);
|
|
657
|
+
else return false;
|
|
658
|
+
return true;
|
|
659
|
+
},
|
|
660
|
+
close() {
|
|
661
|
+
for (const control of asked.values()) control.abort("withdrawn");
|
|
662
|
+
asked.clear();
|
|
663
|
+
for (const answer of [...fetches]) answer.close("withdrawn");
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
177
668
|
// src/testing.ts
|
|
178
669
|
var failure = (code, message) => Object.assign(new Error(message), { code });
|
|
179
670
|
var Stream = class {
|
|
@@ -336,6 +827,10 @@ function fakeUplink(options = {}) {
|
|
|
336
827
|
export {
|
|
337
828
|
fakeCronClock,
|
|
338
829
|
fakeUplink,
|
|
830
|
+
placeholderFloor,
|
|
831
|
+
sealedAnswerer,
|
|
832
|
+
sealedAsker,
|
|
833
|
+
sealedFetcher,
|
|
339
834
|
setTestSyscallHook
|
|
340
835
|
};
|
|
341
836
|
//# sourceMappingURL=testing.js.map
|