@daugasauron/dolly 0.1.5-rc.0 → 0.1.6
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 +51 -78
- package/coi-serviceworker.js +23 -8
- package/dist/dolly-build-id.mjs +1 -1
- package/dist/dolly-image-build-id.mjs +1 -1
- package/dist/dolly-process-0.wasm +0 -0
- package/dist/dolly-process-abi.mjs +1 -1
- package/dist/dolly-process-dso.mjs +19 -11
- package/dist/dolly-process-worker.mjs +88 -33
- package/dist/dolly-runtime-worker.mjs +1544 -0
- package/dist/dolly.wasm +0 -0
- package/host/dso/dso.mjs +2 -2
- package/host/dso/module.json +4 -1
- package/host/http/abi.mjs +3 -1
- package/host/http/broker.mjs +17 -7
- package/host/http/http.mjs +19 -5
- package/host/http/module.json +3 -1
- package/host/http/policy.mjs +13 -6
- package/host/http/refusals.mjs +77 -0
- package/host/input/abi.mjs +1 -1
- package/host/modules.mjs +64 -23
- package/host/nena/module.json +2 -1
- package/host/nena/nena.mjs +2 -0
- package/host/packages/abi.mjs +27 -0
- package/host/packages/module.json +6 -4
- package/host/packages/packages.mjs +23 -8
- package/host/packages/service.mjs +46 -20
- package/host/packages/transport.mjs +123 -0
- package/host/packages/worker.mjs +13 -9
- package/host/requirements.mjs +1 -1
- package/host/runtime/abi.mjs +32 -0
- package/host/runtime/lease-bridge.mjs +13 -8
- package/host/runtime/module.json +3 -3
- package/host/runtime/runtime.mjs +3 -3
- package/host/snapshot/abi.mjs +2 -1
- package/host/snapshot/module.json +1 -1
- package/host/snapshot/snapshot.mjs +80 -57
- package/host/snapshot/transport.mjs +10 -3
- package/host/snapshot/ui.mjs +64 -39
- package/host/stack/module.json +1 -0
- package/host/stack/stack.mjs +1 -1
- package/host/threads/module.json +6 -1
- package/host/threads/threads.mjs +32 -13
- package/package.json +2 -2
- package/{src → page}/browser.mjs +37 -23
- package/page/generated.mjs +8 -0
- package/{src → page}/image-entry.mjs +10 -6
- package/{src → page}/image-inputs.mjs +11 -0
- package/{src → page}/image-names.mjs +2 -2
- package/{src → page}/kernel-plugin.mjs +1 -1
- package/{src → page}/pack-store.mjs +1 -1
- package/{src → page}/packaged-snapshot.mjs +56 -46
- package/{src → page}/process-constants.mjs +72 -15
- package/{src → page}/process-supervisor.mjs +155 -227
- package/{src → page}/snapshot-records.mjs +63 -44
- package/{src → page}/static-asset.mjs +36 -20
- package/{src → page}/version.mjs +1 -1
- package/{src → page}/worker-image.mjs +20 -9
- package/{src → site}/build-worker.mjs +36 -25
- package/{src → site}/custom-image.mjs +2 -10
- package/{src → site}/dollyfile-graph.mjs +1 -1
- package/{src → site}/dollyfile-view.mjs +1 -1
- package/{src → site}/image-artifact.mjs +9 -8
- package/{src → site}/image-build.mjs +4 -4
- package/{src → site}/image-builder.mjs +5 -4
- package/site/offline.mjs +53 -0
- package/{src → site}/session-store.mjs +47 -38
- package/src/runtime-worker.mjs +0 -57
- /package/{src → page}/build-log.mjs +0 -0
- /package/{src → page}/images.mjs +0 -0
- /package/{src → page}/page-chords.mjs +0 -0
- /package/{src → page}/page-indicators.mjs +0 -0
- /package/{src → page}/process-abi.mjs +0 -0
- /package/{src → page}/sha256.mjs +0 -0
- /package/{src → page}/terminal-text.mjs +0 -0
- /package/{src → page}/wasm-interface.mjs +0 -0
- /package/{src → site}/alphabetical.mjs +0 -0
package/dist/dolly.wasm
CHANGED
|
Binary file
|
package/host/dso/dso.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseWasmInterface } from "../../
|
|
1
|
+
import { parseWasmInterface } from "../../page/wasm-interface.mjs";
|
|
2
2
|
export { DOLLY_DSO_ABI_DIGEST as digest } from "./abi.mjs";
|
|
3
3
|
|
|
4
4
|
// The loader and the FFI dispatcher (process.mjs) run inside the process Worker
|
|
@@ -10,7 +10,7 @@ export function worker({ get, abi, runtimeBase }) {
|
|
|
10
10
|
async start() {
|
|
11
11
|
const [contract, bundle] = await Promise.all(["dolly-dso-0.wasm", "dolly-process-dso.mjs"].map(async name => {
|
|
12
12
|
const response = await fetch(new URL(`dist/${name}`, runtimeBase), {
|
|
13
|
-
|
|
13
|
+
credentials: "same-origin", redirect: "error",
|
|
14
14
|
});
|
|
15
15
|
if (!response.ok) throw new Error(`Dolly ${name} returned HTTP ${response.status}`);
|
|
16
16
|
return response.arrayBuffer();
|
package/host/dso/module.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dso",
|
|
3
3
|
"version": 0,
|
|
4
|
+
"build": true,
|
|
4
5
|
"gives": "dlopen and FFI in the process's own Worker",
|
|
5
6
|
"dependencies": ["runtime@0"],
|
|
6
7
|
"phase": "kernel",
|
|
@@ -11,5 +12,7 @@
|
|
|
11
12
|
"process": ["dolly-dso-0.wat"],
|
|
12
13
|
"headers": ["dso.h", "dso-abi.h"],
|
|
13
14
|
"kernel": [],
|
|
14
|
-
"client": ["client.c", "ffi.c"]
|
|
15
|
+
"client": ["client.c", "ffi.c"],
|
|
16
|
+
"ccExports": { "rdynamic": ["__dolly_dso_allocate"] },
|
|
17
|
+
"fixtures": "../../test/build-dso-fixtures.mjs"
|
|
15
18
|
}
|
package/host/http/abi.mjs
CHANGED
|
@@ -24,5 +24,7 @@ export const DOLLY_HTTP_START = 80;
|
|
|
24
24
|
export const DOLLY_HTTP_POLL = 81;
|
|
25
25
|
export const DOLLY_HTTP_CANCEL = 82;
|
|
26
26
|
export const DOLLY_HTTP_BODY_WRITE = 83;
|
|
27
|
+
export const DOLLY_HTTP_FAIL_STATUS = 1;
|
|
28
|
+
export const DOLLY_HTTP_FOLLOW_REDIRECTS = 2;
|
|
27
29
|
export const DOLLY_ERRNO = Object.freeze({ E2BIG: 1, EACCES: 2, EADDRINUSE: 3, EADDRNOTAVAIL: 4, EAFNOSUPPORT: 5, EAGAIN: 6, EALREADY: 7, EBADF: 8, EBADMSG: 9, EBUSY: 10, ECANCELED: 11, ECHILD: 12, ECONNABORTED: 13, ECONNREFUSED: 14, ECONNRESET: 15, EDEADLK: 16, EDESTADDRREQ: 17, EDOM: 18, EDQUOT: 19, EEXIST: 20, EFAULT: 21, EFBIG: 22, EHOSTUNREACH: 23, EIDRM: 24, EILSEQ: 25, EINPROGRESS: 26, EINTR: 27, EINVAL: 28, EIO: 29, EISCONN: 30, EISDIR: 31, ELOOP: 32, EMFILE: 33, EMLINK: 34, EMSGSIZE: 35, EMULTIHOP: 36, ENAMETOOLONG: 37, ENETDOWN: 38, ENETRESET: 39, ENETUNREACH: 40, ENFILE: 41, ENOBUFS: 42, ENODEV: 43, ENOENT: 44, ENOEXEC: 45, ENOLCK: 46, ENOLINK: 47, ENOMEM: 48, ENOMSG: 49, ENOPROTOOPT: 50, ENOSPC: 51, ENOSYS: 52, ENOTCONN: 53, ENOTDIR: 54, ENOTEMPTY: 55, ENOTRECOVERABLE: 56, ENOTSOCK: 57, ENOTTY: 59, ENXIO: 60, EOVERFLOW: 61, EOWNERDEAD: 62, EPERM: 63, EPIPE: 64, EPROTO: 65, EPROTONOSUPPORT: 66, EPROTOTYPE: 67, ERANGE: 68, EROFS: 69, ESPIPE: 70, ESRCH: 71, ESTALE: 72, ETIMEDOUT: 73, ETXTBSY: 74, EXDEV: 75, ENODATA: 116, ENOTSUP: 138 });
|
|
28
|
-
export const DOLLY_HTTP_ABI_DIGEST = "
|
|
30
|
+
export const DOLLY_HTTP_ABI_DIGEST = "fc788cef38bb5cd423369a5c12c274fa6203ff14eef72540347e778236986aee";
|
package/host/http/broker.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { HttpError, isDollyCredentialHeader, stripDollyBrowserOwnedHeaders } from "./policy.mjs";
|
|
2
2
|
import { DOLLY_ERRNO as errno } from "./abi.mjs";
|
|
3
|
-
import { decodeStaticAsset, sitePath } from "../../
|
|
3
|
+
import { decodeStaticAsset, sitePath } from "../../page/static-asset.mjs";
|
|
4
4
|
import { DOLLY_HTTP_SLOT_COUNT, DOLLY_HTTP_CHUNK_CAPACITY, DOLLY_HTTP_MAX_METHOD, DOLLY_HTTP_MAX_URL,
|
|
5
5
|
DOLLY_HTTP_MAX_HEADERS, DOLLY_HTTP_MAX_BODY, DOLLY_HTTP_HEADER_SIZE, DOLLY_HTTP_WORD_SEQUENCE,
|
|
6
6
|
DOLLY_HTTP_WORD_STATUS, DOLLY_HTTP_WORD_LENGTH, DOLLY_HTTP_WORD_EOF, DOLLY_HTTP_WORD_ERROR,
|
|
7
7
|
DOLLY_HTTP_WORD_KIND, DOLLY_HTTP_STATE_WRITABLE, DOLLY_HTTP_STATE_READABLE, DOLLY_HTTP_STATE_FAILED,
|
|
8
|
-
DOLLY_HTTP_KIND_URL, DOLLY_HTTP_KIND_HEADER, DOLLY_HTTP_KIND_BODY
|
|
8
|
+
DOLLY_HTTP_KIND_URL, DOLLY_HTTP_KIND_HEADER, DOLLY_HTTP_KIND_BODY, DOLLY_HTTP_FAIL_STATUS,
|
|
9
|
+
DOLLY_HTTP_FOLLOW_REDIRECTS } from "./abi.mjs";
|
|
9
10
|
|
|
10
11
|
const encoder = new TextEncoder();
|
|
11
12
|
const now = () => performance.timeOrigin + performance.now();
|
|
@@ -54,7 +55,7 @@ export class HttpMailbox {
|
|
|
54
55
|
message.flags === 0 };
|
|
55
56
|
}
|
|
56
57
|
try {
|
|
57
|
-
if (!(memory instanceof SharedArrayBuffer) || (message.flags & ~
|
|
58
|
+
if (!(memory instanceof SharedArrayBuffer) || (message.flags & ~(DOLLY_HTTP_FAIL_STATUS | DOLLY_HTTP_FOLLOW_REDIRECTS)) !== 0)
|
|
58
59
|
throw new HttpError(errno.EINVAL, "invalid HTTP admission");
|
|
59
60
|
const spans = {};
|
|
60
61
|
for (const [name, maximum] of Object.entries(DOLLY_HTTP_LIMITS)) {
|
|
@@ -127,13 +128,15 @@ export class HttpMailbox {
|
|
|
127
128
|
// The page's broker: policy, Fetch and the slots' bookkeeping. It never reads
|
|
128
129
|
// kernel memory: `mailbox` (HttpMailbox, or messages to it) writes the slots.
|
|
129
130
|
export class NetworkTransport {
|
|
130
|
-
// `site` is the URL of the root of the site serving this release, if any
|
|
131
|
-
|
|
131
|
+
// `site` is the URL of the root of the site serving this release, if any;
|
|
132
|
+
// `refused({ method, target, reason })` hears of each request the policy refuses.
|
|
133
|
+
constructor(mailbox, policy, { fetchRequest = globalThis.fetch.bind(globalThis), site, refused } = {}) {
|
|
132
134
|
this.mailbox = mailbox;
|
|
133
135
|
this.capacity = DOLLY_HTTP_CHUNK_CAPACITY;
|
|
134
136
|
this.policy = policy;
|
|
135
137
|
this.fetchRequest = fetchRequest;
|
|
136
138
|
this.site = site;
|
|
139
|
+
this.refused = refused;
|
|
137
140
|
// Host bookkeeping, never derived from a guest's claimed active count.
|
|
138
141
|
this.slots = Array(DOLLY_HTTP_SLOT_COUNT).fill(null);
|
|
139
142
|
this.closed = false;
|
|
@@ -278,7 +281,14 @@ class HttpTransfer {
|
|
|
278
281
|
}
|
|
279
282
|
stripDollyBrowserOwnedHeaders(headers);
|
|
280
283
|
const upperMethod = method.toUpperCase();
|
|
281
|
-
|
|
284
|
+
let rule;
|
|
285
|
+
try { rule = this.broker.policy.authorize(target, upperMethod, headers, body?.byteLength ?? 0); }
|
|
286
|
+
catch (error) {
|
|
287
|
+
if (error instanceof HttpError && [errno.EACCES, errno.EDQUOT].includes(error.errno)) {
|
|
288
|
+
this.broker.refused?.({ method: upperMethod, target, reason: error.message });
|
|
289
|
+
}
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
282
292
|
this.timeout = rule.timeoutMilliseconds;
|
|
283
293
|
this.moved();
|
|
284
294
|
failure = errno.EIO;
|
|
@@ -286,7 +296,7 @@ class HttpTransfer {
|
|
|
286
296
|
method: upperMethod, headers,
|
|
287
297
|
body: body === null || upperMethod === "GET" || upperMethod === "HEAD" ? undefined : body,
|
|
288
298
|
credentials: "omit",
|
|
289
|
-
redirect: (flags &
|
|
299
|
+
redirect: (flags & DOLLY_HTTP_FOLLOW_REDIRECTS) && rule.followRedirects === true ? "follow" : "error",
|
|
290
300
|
referrerPolicy: "no-referrer",
|
|
291
301
|
signal: this.controller.signal,
|
|
292
302
|
};
|
package/host/http/http.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { createHttpAdmission, HttpMailbox, NetworkTransport } from "./broker.mjs";
|
|
2
2
|
import { DollyHttpPolicy, httpPolicyConfigurations, restrictDollyHttpPolicy } from "./policy.mjs";
|
|
3
3
|
import { localServicesTransport } from "./local-services.mjs";
|
|
4
|
+
import { refusalNotices } from "./refusals.mjs";
|
|
4
5
|
export { DOLLY_HTTP_ABI_DIGEST as digest } from "./abi.mjs";
|
|
5
6
|
|
|
6
7
|
// The page takes the embedding's policy once, with the image directory's
|
|
7
8
|
// trusted sources and, in a custom tab, the policies it inherited; a builder or
|
|
8
9
|
// headless host is given its network instead. Modules depending on http@0 add
|
|
9
|
-
// their local services to `services`.
|
|
10
|
-
|
|
10
|
+
// their local services to `services`. Each request the policy refuses is a line in the
|
|
11
|
+
// console and a notice on the page (refusals.mjs).
|
|
12
|
+
export function browser({ applicationBase, configuration, mount, keyboard, send, ask }) {
|
|
11
13
|
let transport, admission, network = configuration.network;
|
|
12
|
-
const services = {};
|
|
14
|
+
const services = {}, refusals = refusalNotices(mount, keyboard);
|
|
13
15
|
const instance = {
|
|
14
16
|
services,
|
|
15
17
|
page: {
|
|
@@ -25,7 +27,8 @@ export function browser({ applicationBase, configuration, send, ask }) {
|
|
|
25
27
|
// The Worker writes the records into the guest's slots.
|
|
26
28
|
const mailbox = { publish: record => ask({ type: "http-publish", ...record }, [record.bytes.buffer]),
|
|
27
29
|
fail: ending => send({ type: "http-fail", ...ending }) };
|
|
28
|
-
transport = new NetworkTransport(mailbox, network.policy, { fetchRequest: network.fetchRequest, site: network.site
|
|
30
|
+
transport = new NetworkTransport(mailbox, network.policy, { fetchRequest: network.fetchRequest, site: network.site,
|
|
31
|
+
refused: refusals.refused });
|
|
29
32
|
},
|
|
30
33
|
messages: {
|
|
31
34
|
async "http-request"(message) {
|
|
@@ -36,7 +39,18 @@ export function browser({ applicationBase, configuration, send, ask }) {
|
|
|
36
39
|
Atomics.notify(admission, 0);
|
|
37
40
|
},
|
|
38
41
|
},
|
|
39
|
-
|
|
42
|
+
// A GET a host module makes for the guest, judged by the page's policy as
|
|
43
|
+
// the guest's own requests are, and shown as theirs when refused.
|
|
44
|
+
admit(url) {
|
|
45
|
+
try {
|
|
46
|
+
network.policy.authorize(url, "GET", new Headers(), 0);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
refusals.refused({ method: "GET", target: url, reason: error.message });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
claimsKey: refusals.claimsKey,
|
|
53
|
+
dispose() { transport?.close(); refusals.dispose(); },
|
|
40
54
|
};
|
|
41
55
|
if (!network) {
|
|
42
56
|
// A custom tab intersects the policy of the page that built it with its
|
package/host/http/module.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "http",
|
|
3
3
|
"version": 0,
|
|
4
|
+
"build": true,
|
|
4
5
|
"gives": "HTTP(S) requests under the page's policy",
|
|
5
6
|
"fixed": "the page's HTTP policy is consumed once, at load",
|
|
6
7
|
"dependencies": ["runtime@0"],
|
|
@@ -11,5 +12,6 @@
|
|
|
11
12
|
"process": [],
|
|
12
13
|
"headers": ["http.h", "http-abi.h"],
|
|
13
14
|
"kernel": ["kernel.c"],
|
|
14
|
-
"client": ["client.c"]
|
|
15
|
+
"client": ["client.c"],
|
|
16
|
+
"tools": ["libcurl.c"]
|
|
15
17
|
}
|
package/host/http/policy.mjs
CHANGED
|
@@ -27,6 +27,13 @@ const browserOwnedTransportHeaderNames = new Set([
|
|
|
27
27
|
"user-agent",
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
|
+
// What an ordinary request carries: under a hardened rule these and the
|
|
31
|
+
// headers the rule names leave the sandbox, and nothing else does, a token in
|
|
32
|
+
// a header of any other name included.
|
|
33
|
+
const ordinaryHeaderNames = new Set(["accept", "accept-language", "content-encoding", "content-language", "content-type",
|
|
34
|
+
"range", "if-match", "if-none-match", "if-modified-since", "if-unmodified-since", "if-range",
|
|
35
|
+
"cache-control", "pragma"]);
|
|
36
|
+
|
|
30
37
|
export function isDollyCredentialHeader(name) {
|
|
31
38
|
return credentialHeaderNames.has(String(name).toLowerCase());
|
|
32
39
|
}
|
|
@@ -86,8 +93,8 @@ function normalizeRule(rule) {
|
|
|
86
93
|
}
|
|
87
94
|
const credentialHeaders = new Set((rule.credentialHeaders ?? []).map((value) => {
|
|
88
95
|
const name = String(value).toLowerCase();
|
|
89
|
-
if (
|
|
90
|
-
throw new TypeError(`invalid Dolly HTTP
|
|
96
|
+
if (!/^[!#$%&'*+.^_`|~0-9a-z-]+$/.test(name)) {
|
|
97
|
+
throw new TypeError(`invalid Dolly HTTP header name: ${name}`);
|
|
91
98
|
}
|
|
92
99
|
return name;
|
|
93
100
|
}));
|
|
@@ -243,11 +250,11 @@ export class DollyHttpPolicy {
|
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
// Credentials are ordinary sandbox state. Development mode preserves
|
|
246
|
-
// them. A hardened destination rule
|
|
247
|
-
//
|
|
253
|
+
// them. A hardened destination rule names every header beyond an
|
|
254
|
+
// ordinary request's that may leave for that exact destination.
|
|
248
255
|
if (rule.credentialHeaders !== null) {
|
|
249
|
-
for (const name of
|
|
250
|
-
if (!rule.credentialHeaders.has(name)) headers.delete(name);
|
|
256
|
+
for (const name of [...headers.keys()]) {
|
|
257
|
+
if (!ordinaryHeaderNames.has(name) && !rule.credentialHeaders.has(name)) headers.delete(name);
|
|
251
258
|
}
|
|
252
259
|
}
|
|
253
260
|
const relay = rule.bootstrap ? undefined : relayUrl(this.relays, target, headers);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// What the HTTP policy refuses, for whoever runs the page: each refusal is a line in the
|
|
2
|
+
// browser's console, and a notice in boot()'s element that stays until the user closes it.
|
|
3
|
+
// Refusals of one method and URL are one notice with a count; the newest few show, with a
|
|
4
|
+
// line for the rest and Close all. A URL is shown without its query, which may hold a key.
|
|
5
|
+
// The list lies over the display: its words let the pointer through to the program, and only
|
|
6
|
+
// its buttons take a click.
|
|
7
|
+
const shownAtOnce = 5;
|
|
8
|
+
|
|
9
|
+
const style = `
|
|
10
|
+
#http-refusals { position: absolute; left: 0.6rem; top: 0.6rem; z-index: 7; width: min(34rem, calc(100% - 1.2rem));
|
|
11
|
+
margin: 0; padding: 0.35rem 0.35rem 0; list-style: none; color: var(--dolly-ui-color); background: var(--dolly-ui-background); font-size: 14px; line-height: 1.4;
|
|
12
|
+
pointer-events: none; }
|
|
13
|
+
#http-refusals[hidden] { display: none; }
|
|
14
|
+
#http-refusals li { display: flex; gap: 0.5rem; align-items: baseline; margin-bottom: 0.35rem; padding: 0.4rem 0.5rem;
|
|
15
|
+
background: var(--dolly-ui-background); border: 1px solid var(--dolly-ui-alert); overflow-wrap: anywhere; }
|
|
16
|
+
#http-refusals li span { flex: 1; }
|
|
17
|
+
#http-refusals li.rest { border-color: var(--dolly-ui-quiet); }
|
|
18
|
+
#http-refusals button { color: inherit; background: var(--dolly-ui-raised); border: 1px solid var(--dolly-ui-quiet); font: inherit; cursor: pointer;
|
|
19
|
+
pointer-events: auto; }
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export function refusalNotices(mount, keyboard) {
|
|
23
|
+
const notices = new Map();
|
|
24
|
+
let list, look;
|
|
25
|
+
if (mount) {
|
|
26
|
+
look = new CSSStyleSheet();
|
|
27
|
+
look.replaceSync(style);
|
|
28
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, look];
|
|
29
|
+
list = mount.appendChild(Object.assign(document.createElement("ul"), { id: "http-refusals", hidden: true }));
|
|
30
|
+
list.setAttribute("role", "status");
|
|
31
|
+
list.setAttribute("aria-label", "Requests the HTTP policy refused");
|
|
32
|
+
}
|
|
33
|
+
const button = (label, name, press) => {
|
|
34
|
+
const element = Object.assign(document.createElement("button"), { type: "button", textContent: label });
|
|
35
|
+
element.setAttribute("aria-label", name);
|
|
36
|
+
element.addEventListener("click", () => {
|
|
37
|
+
press();
|
|
38
|
+
render();
|
|
39
|
+
keyboard?.focus({ preventScroll: true });
|
|
40
|
+
});
|
|
41
|
+
return element;
|
|
42
|
+
};
|
|
43
|
+
function render() {
|
|
44
|
+
const newest = [...notices].reverse(), shown = newest.slice(0, shownAtOnce);
|
|
45
|
+
list.replaceChildren(...shown.map(([key, { text, count }]) => {
|
|
46
|
+
const item = document.createElement("li"), words = document.createElement("span");
|
|
47
|
+
words.textContent = `The HTTP policy refused ${text}${count > 1 ? ` (${count} times)` : ""}`;
|
|
48
|
+
item.append(words, button("×", `Close: ${text}`, () => notices.delete(key)));
|
|
49
|
+
return item;
|
|
50
|
+
}));
|
|
51
|
+
if (notices.size > 1) {
|
|
52
|
+
const rest = document.createElement("li"), words = document.createElement("span");
|
|
53
|
+
rest.className = "rest";
|
|
54
|
+
const hidden = notices.size - shown.length;
|
|
55
|
+
words.textContent = hidden ? `${hidden} more refused (the console lists every one)` : "";
|
|
56
|
+
rest.append(words, button("Close all", "Close all refusals", () => notices.clear()));
|
|
57
|
+
list.append(rest);
|
|
58
|
+
}
|
|
59
|
+
list.hidden = notices.size === 0;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
refused({ method, target, reason }) {
|
|
63
|
+
const text = `${method} ${target.origin}${target.pathname}`;
|
|
64
|
+
console.warn(`${reason}: ${text}`);
|
|
65
|
+
if (!list) return;
|
|
66
|
+
const notice = notices.get(text);
|
|
67
|
+
notices.delete(text);
|
|
68
|
+
notices.set(text, { text, count: (notice?.count ?? 0) + 1 });
|
|
69
|
+
render();
|
|
70
|
+
},
|
|
71
|
+
claimsKey: event => list?.contains(event.target) ?? false,
|
|
72
|
+
dispose() {
|
|
73
|
+
list?.remove();
|
|
74
|
+
if (look) document.adoptedStyleSheets = document.adoptedStyleSheets.filter(sheet => sheet !== look);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
package/host/input/abi.mjs
CHANGED
|
@@ -44,4 +44,4 @@ export const DOLLY_INPUT_NEXT_EVENT = 89;
|
|
|
44
44
|
export const DOLLY_INPUT_SET_POINTER = 90;
|
|
45
45
|
export const DOLLY_INPUT_RELEASE = 91;
|
|
46
46
|
export const DOLLY_ERRNO = Object.freeze({ E2BIG: 1, EACCES: 2, EADDRINUSE: 3, EADDRNOTAVAIL: 4, EAFNOSUPPORT: 5, EAGAIN: 6, EALREADY: 7, EBADF: 8, EBADMSG: 9, EBUSY: 10, ECANCELED: 11, ECHILD: 12, ECONNABORTED: 13, ECONNREFUSED: 14, ECONNRESET: 15, EDEADLK: 16, EDESTADDRREQ: 17, EDOM: 18, EDQUOT: 19, EEXIST: 20, EFAULT: 21, EFBIG: 22, EHOSTUNREACH: 23, EIDRM: 24, EILSEQ: 25, EINPROGRESS: 26, EINTR: 27, EINVAL: 28, EIO: 29, EISCONN: 30, EISDIR: 31, ELOOP: 32, EMFILE: 33, EMLINK: 34, EMSGSIZE: 35, EMULTIHOP: 36, ENAMETOOLONG: 37, ENETDOWN: 38, ENETRESET: 39, ENETUNREACH: 40, ENFILE: 41, ENOBUFS: 42, ENODEV: 43, ENOENT: 44, ENOEXEC: 45, ENOLCK: 46, ENOLINK: 47, ENOMEM: 48, ENOMSG: 49, ENOPROTOOPT: 50, ENOSPC: 51, ENOSYS: 52, ENOTCONN: 53, ENOTDIR: 54, ENOTEMPTY: 55, ENOTRECOVERABLE: 56, ENOTSOCK: 57, ENOTTY: 59, ENXIO: 60, EOVERFLOW: 61, EOWNERDEAD: 62, EPERM: 63, EPIPE: 64, EPROTO: 65, EPROTONOSUPPORT: 66, EPROTOTYPE: 67, ERANGE: 68, EROFS: 69, ESPIPE: 70, ESRCH: 71, ESTALE: 72, ETIMEDOUT: 73, ETXTBSY: 74, EXDEV: 75, ENODATA: 116, ENOTSUP: 138 });
|
|
47
|
-
export const DOLLY_INPUT_ABI_DIGEST = "
|
|
47
|
+
export const DOLLY_INPUT_ABI_DIGEST = "6bdd3f083670e13711d45c6bb787d6a6545ccee2c60d6de2ba9111780fc6c174";
|
package/host/modules.mjs
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { hostRequirement, hostRequirements } from "./requirements.mjs";
|
|
2
|
-
import { DOLLY_ERRNO as E } from "../
|
|
3
|
-
|
|
4
|
-
// What a build enables beside the runtime its image declares: the broker the
|
|
5
|
-
// engine fetches sources through, the threads its toolchain uses, the
|
|
6
|
-
// modules its compilers and interpreters load and the fresh stacks of the
|
|
7
|
-
// tools stack-wrap rewrote.
|
|
8
|
-
export const buildHost = Object.freeze(["http@0", "threads@0", "dso@0", "stack@0"]);
|
|
2
|
+
import { DOLLY_ERRNO as E } from "../page/process-constants.mjs";
|
|
9
3
|
|
|
10
4
|
// A page's host modules, from the URLs of their manifests. The embedding, trusted
|
|
11
5
|
// code, names them; images select no JS or Worker URLs, and each module's provider
|
|
@@ -15,7 +9,10 @@ export async function hostRegistry(urls) {
|
|
|
15
9
|
const definitions = await Promise.all(urls.map(async url => {
|
|
16
10
|
const { default: manifest } = await import(url, { with: { type: "json" } });
|
|
17
11
|
const contract = Object.freeze({ ...manifest, url });
|
|
18
|
-
|
|
12
|
+
// A module's provider is imported when the page or a Worker first needs it:
|
|
13
|
+
// its module attached or admitted, or a session route's boot selected.
|
|
14
|
+
let provider;
|
|
15
|
+
return { contract, provider: () => provider ??= import(new URL(contract.host, url).href) };
|
|
19
16
|
}));
|
|
20
17
|
const byName = new Map(), owners = new Map();
|
|
21
18
|
for (const module of definitions) {
|
|
@@ -27,25 +24,30 @@ export async function hostRegistry(urls) {
|
|
|
27
24
|
}
|
|
28
25
|
}
|
|
29
26
|
const contracts = Object.freeze(definitions.map(module => module.contract));
|
|
30
|
-
const
|
|
31
|
-
|
|
27
|
+
const requirementsOf = keep => Object.freeze(contracts.filter(keep).map(({ name, version }) => `${name}@${version}`));
|
|
28
|
+
const runtimes = requirementsOf(contract => contract.provides === "kernel");
|
|
29
|
+
// What a build enables beside the runtime its image declares: the modules
|
|
30
|
+
// whose manifests say `build` (README.md).
|
|
31
|
+
const buildHost = requirementsOf(contract => contract.build);
|
|
32
32
|
const registry = { byName, owners, contracts, runtimes };
|
|
33
33
|
return Object.freeze({
|
|
34
34
|
urls: Object.freeze([...urls]),
|
|
35
35
|
buildHostFor: declared => [...declared.filter(value => runtimes.includes(value)), ...buildHost],
|
|
36
|
+
load: values => loadProviders(byName, values),
|
|
36
37
|
selectBoot: route => selectBoot(definitions, route),
|
|
37
38
|
createHost: (...values) => createHost(registry, ...values),
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
// A module may select what a route boots, as a saved session names its
|
|
42
|
-
// at most one does, and the page then requires it. The selection carries
|
|
43
|
-
// image, a bootstrap label, the custom image record it restores and the
|
|
42
|
+
// A module may select what a session route boots, as a saved session names its
|
|
43
|
+
// image: at most one does, and the page then requires it. The selection carries
|
|
44
|
+
// the image, a bootstrap label, the custom image record it restores and the
|
|
44
45
|
// module's configuration, keyed for createHost.
|
|
45
46
|
async function selectBoot(definitions, route) {
|
|
47
|
+
if (!route.loadSession) return null;
|
|
46
48
|
let selection = null;
|
|
47
49
|
for (const module of definitions) {
|
|
48
|
-
const selected = await module.boot?.(route);
|
|
50
|
+
const selected = await (await module.provider()).boot?.(route);
|
|
49
51
|
if (!selected) continue;
|
|
50
52
|
if (selection) throw new Error("two host modules selected the boot");
|
|
51
53
|
const { name, version } = module.contract;
|
|
@@ -54,6 +56,18 @@ async function selectBoot(definitions, route) {
|
|
|
54
56
|
return selection;
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
// The providers of these modules and their dependencies start loading together; createHost
|
|
60
|
+
// attaches them in order.
|
|
61
|
+
function loadProviders(byName, values, seen = new Set()) {
|
|
62
|
+
for (const value of values) {
|
|
63
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
64
|
+
if (seen.has(name) || module?.contract.version !== version) continue;
|
|
65
|
+
seen.add(name);
|
|
66
|
+
module.provider().catch(() => {});
|
|
67
|
+
loadProviders(byName, module.contract.dependencies, seen);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
async function createHost({ byName, owners, contracts, runtimes }, side, enabled,
|
|
58
72
|
{ send, resources = {}, configuration = {} } = {}) {
|
|
59
73
|
if (!["browser", "worker"].includes(side)) throw new TypeError("invalid host side");
|
|
@@ -96,7 +110,7 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
96
110
|
const get = name => instances.get(name);
|
|
97
111
|
// Page UI across modules: the first module that claims a key event takes it
|
|
98
112
|
// from the guest's input and may act on it; a module drawing over the
|
|
99
|
-
// display canvas reports the pixel size
|
|
113
|
+
// display canvas reports the pixel size of its frame.
|
|
100
114
|
function claimsKey(event) {
|
|
101
115
|
for (const instance of instances.values()) {
|
|
102
116
|
const claim = instance.claimsKey?.(event);
|
|
@@ -105,6 +119,15 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
105
119
|
return false;
|
|
106
120
|
}
|
|
107
121
|
const surfaceSize = () => [...instances.values()].find(instance => instance.surfaceSize)?.surfaceSize;
|
|
122
|
+
// A pointer's position in pixels of the frame shown, which the canvas fits
|
|
123
|
+
// within its box at the frame's own shape, centred (object-fit: contain).
|
|
124
|
+
function framePoint({ clientX, clientY }) {
|
|
125
|
+
const bounds = resources.canvas.getBoundingClientRect(), { width, height } = surfaceSize() ?? resources.canvas;
|
|
126
|
+
const scale = Math.min(bounds.width / width, bounds.height / height);
|
|
127
|
+
if (!(scale > 0)) return { x: 0, y: 0 };
|
|
128
|
+
return { x: (clientX - bounds.left - (bounds.width - width * scale) / 2) / scale,
|
|
129
|
+
y: (clientY - bounds.top - (bounds.height - height * scale) / 2) / scale };
|
|
130
|
+
}
|
|
108
131
|
async function attach(value, visiting = new Set(), checked = false) {
|
|
109
132
|
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
110
133
|
if (instances.has(name) || reasons.has(name)) return;
|
|
@@ -117,8 +140,9 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
117
140
|
visiting.delete(name);
|
|
118
141
|
try { requireModules(module.contract.dependencies); }
|
|
119
142
|
catch (error) { reasons.set(name, error.message); return; }
|
|
143
|
+
const provider = await module.provider();
|
|
120
144
|
if (side === "browser" && !checked) {
|
|
121
|
-
const reason = await
|
|
145
|
+
const reason = await provider.check?.(resources);
|
|
122
146
|
if (reason) { reasons.set(name, reason); return; }
|
|
123
147
|
}
|
|
124
148
|
const dependency = name => {
|
|
@@ -127,9 +151,10 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
127
151
|
}
|
|
128
152
|
return get(name);
|
|
129
153
|
};
|
|
130
|
-
|
|
154
|
+
// `switched`: the page enabled the module after boot (nena@0), not the image.
|
|
155
|
+
const instance = provider[side]?.({ ...resources, switched: checked, send, ask, get: dependency,
|
|
131
156
|
service: () => { for (const instance of instances.values()) instance.service?.(); },
|
|
132
|
-
claimsKey,
|
|
157
|
+
claimsKey, framePoint,
|
|
133
158
|
abi: admittedAbi, modules: module.contract.switches && side === "browser" ? { list, enable, disable } : undefined,
|
|
134
159
|
configuration: configuration[name] ?? {} }) ?? {};
|
|
135
160
|
instances.set(name, instance);
|
|
@@ -210,7 +235,7 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
210
235
|
// later switch: after five seconds the switch is refused and the check
|
|
211
236
|
// told so, that an answer coming later shows nothing on the page.
|
|
212
237
|
const abandoned = new AbortController(), timer = setTimeout(() => abandoned.abort(), 5000);
|
|
213
|
-
const unavailable = await Promise.race([module.check?.({ ...resources, signal: abandoned.signal }), new Promise(resolve =>
|
|
238
|
+
const unavailable = await Promise.race([(await module.provider()).check?.({ ...resources, signal: abandoned.signal }), new Promise(resolve =>
|
|
214
239
|
abandoned.signal.addEventListener("abort", () => resolve("the browser did not answer in time")))]);
|
|
215
240
|
clearTimeout(timer);
|
|
216
241
|
if (unavailable) return unavailable;
|
|
@@ -244,6 +269,7 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
244
269
|
for (const instance of [...instances.values()].reverse()) instance.dispose?.();
|
|
245
270
|
}
|
|
246
271
|
try {
|
|
272
|
+
loadProviders(byName, selected);
|
|
247
273
|
for (const value of selected) await attach(value);
|
|
248
274
|
if (kernels.length !== 1) {
|
|
249
275
|
// A requested module this page lacks may be the runtime the image declares.
|
|
@@ -261,12 +287,27 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
261
287
|
require: requireModules, dispose,
|
|
262
288
|
// Executables may use only the modules the image declares. A declared module
|
|
263
289
|
// this host does not enable (a build host) answers its calls with ENOSYS.
|
|
264
|
-
admit(requirements) {
|
|
265
|
-
|
|
290
|
+
async admit(requirements) {
|
|
291
|
+
const admitted = new Map();
|
|
266
292
|
for (const value of hostRequirements(requirements)) {
|
|
267
293
|
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
268
|
-
|
|
294
|
+
const digest = module?.contract.version === version ? (await module.provider()).digest : undefined;
|
|
295
|
+
if (digest) admitted.set(value, digest);
|
|
269
296
|
}
|
|
297
|
+
admittedAbi.clear();
|
|
298
|
+
for (const [value, digest] of admitted) admittedAbi.set(value, digest);
|
|
299
|
+
},
|
|
300
|
+
// The Worker asks this before it restores the image: a module that restores
|
|
301
|
+
// the whole filesystem itself (a saved session, host/snapshot) says so, and
|
|
302
|
+
// no image is loaded.
|
|
303
|
+
async restoreFilesystem(context) {
|
|
304
|
+
for (const instance of instances.values()) if (await instance.restoreFilesystem?.(context)) return true;
|
|
305
|
+
return false;
|
|
306
|
+
},
|
|
307
|
+
// Whether restoreFilesystem will restore one, known from the configuration,
|
|
308
|
+
// before a kernel exists.
|
|
309
|
+
restoresFilesystem() {
|
|
310
|
+
return [...instances.values()].some(instance => instance.restoresFilesystem?.());
|
|
270
311
|
},
|
|
271
312
|
// The Worker calls this after restoring the system image and before starting
|
|
272
313
|
// image-phase modules, in registry order.
|
|
@@ -332,7 +373,7 @@ async function createHost({ byName, owners, contracts, runtimes }, side, enabled
|
|
|
332
373
|
const instance = instances.get(name);
|
|
333
374
|
if (!instance) throw new Error(reasons.get(name));
|
|
334
375
|
if (instance.start) await startProvider(name, instance, startContext);
|
|
335
|
-
const { digest } = byName.get(name);
|
|
376
|
+
const { digest } = await byName.get(name).provider();
|
|
336
377
|
if (digest) admittedAbi.set(message.module, digest);
|
|
337
378
|
} else if (instances.has(name)) detach(name);
|
|
338
379
|
} catch (error) {
|
package/host/nena/module.json
CHANGED
package/host/nena/nena.mjs
CHANGED
|
@@ -66,6 +66,8 @@ export function browser({ send, modules, showStatus }) {
|
|
|
66
66
|
// travels through the provider that the switch takes away.
|
|
67
67
|
if (!enabling && name === "nena") return { bytes: moduleRecord({ ...found(), enabled: false }), after: () => modules.disable(value) };
|
|
68
68
|
const refusal = await (enabling ? modules.enable(value) : modules.disable(value));
|
|
69
|
+
// The page says every switch: what a session gives itself is seen.
|
|
70
|
+
if (!refusal) showStatus(`This session ${enabling ? "enabled" : "disabled"} ${value}.`);
|
|
69
71
|
return { bytes: moduleRecord(found(), refusal ?? undefined), enabled: enabledSince() };
|
|
70
72
|
}
|
|
71
73
|
return {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Generated from host/packages/dolly-packages-0.wat, host/packages/packages.h, include/dolly/process.h.
|
|
2
|
+
export const DOLLY_PACKAGES_HEADER_SIZE = 64;
|
|
3
|
+
export const DOLLY_PACKAGES_WORD_REQUEST = 0;
|
|
4
|
+
export const DOLLY_PACKAGES_WORD_CANCELLED = 1;
|
|
5
|
+
export const DOLLY_PACKAGES_WORD_COMPLETED = 2;
|
|
6
|
+
export const DOLLY_PACKAGES_WORD_CHUNK = 3;
|
|
7
|
+
export const DOLLY_PACKAGES_WORD_CONSUMED = 4;
|
|
8
|
+
export const DOLLY_PACKAGES_WORD_LENGTH = 5;
|
|
9
|
+
export const DOLLY_PACKAGES_WORD_ERROR = 6;
|
|
10
|
+
export const DOLLY_PACKAGES_WORD_EOF = 7;
|
|
11
|
+
export const DOLLY_PACKAGES_WORD_ENABLED = 8;
|
|
12
|
+
export const DOLLY_PACKAGES_WORD_SIZE = 9;
|
|
13
|
+
export const DOLLY_PACKAGES_WORD_KIND = 10;
|
|
14
|
+
export const DOLLY_PACKAGES_WORD_DIRECTORY_LENGTH = 11;
|
|
15
|
+
export const DOLLY_PACKAGES_DIRECTORY_OFFSET = 64;
|
|
16
|
+
export const DOLLY_PACKAGES_DIRECTORY_CAPACITY = 2048;
|
|
17
|
+
export const DOLLY_PACKAGES_PIN_OFFSET = 2112;
|
|
18
|
+
export const DOLLY_PACKAGES_PIN_BYTES = 64;
|
|
19
|
+
export const DOLLY_PACKAGES_CHUNK_OFFSET = 2176;
|
|
20
|
+
export const DOLLY_PACKAGES_CHUNK_CAPACITY = 1048576;
|
|
21
|
+
export const DOLLY_PACKAGES_MAX_SIZE = 2147483647;
|
|
22
|
+
export const DOLLY_PACKAGES_KIND_DIRECTORY = 1;
|
|
23
|
+
export const DOLLY_PACKAGES_KIND_INDEX = 2;
|
|
24
|
+
export const DOLLY_PACKAGES_KIND_SNAPSHOT = 3;
|
|
25
|
+
export const DOLLY_PACKAGES_FETCH = 134;
|
|
26
|
+
export const DOLLY_ERRNO = Object.freeze({ E2BIG: 1, EACCES: 2, EADDRINUSE: 3, EADDRNOTAVAIL: 4, EAFNOSUPPORT: 5, EAGAIN: 6, EALREADY: 7, EBADF: 8, EBADMSG: 9, EBUSY: 10, ECANCELED: 11, ECHILD: 12, ECONNABORTED: 13, ECONNREFUSED: 14, ECONNRESET: 15, EDEADLK: 16, EDESTADDRREQ: 17, EDOM: 18, EDQUOT: 19, EEXIST: 20, EFAULT: 21, EFBIG: 22, EHOSTUNREACH: 23, EIDRM: 24, EILSEQ: 25, EINPROGRESS: 26, EINTR: 27, EINVAL: 28, EIO: 29, EISCONN: 30, EISDIR: 31, ELOOP: 32, EMFILE: 33, EMLINK: 34, EMSGSIZE: 35, EMULTIHOP: 36, ENAMETOOLONG: 37, ENETDOWN: 38, ENETRESET: 39, ENETUNREACH: 40, ENFILE: 41, ENOBUFS: 42, ENODEV: 43, ENOENT: 44, ENOEXEC: 45, ENOLCK: 46, ENOLINK: 47, ENOMEM: 48, ENOMSG: 49, ENOPROTOOPT: 50, ENOSPC: 51, ENOSYS: 52, ENOTCONN: 53, ENOTDIR: 54, ENOTEMPTY: 55, ENOTRECOVERABLE: 56, ENOTSOCK: 57, ENOTTY: 59, ENXIO: 60, EOVERFLOW: 61, EOWNERDEAD: 62, EPERM: 63, EPIPE: 64, EPROTO: 65, EPROTONOSUPPORT: 66, EPROTOTYPE: 67, ERANGE: 68, EROFS: 69, ESPIPE: 70, ESRCH: 71, ESTALE: 72, ETIMEDOUT: 73, ETXTBSY: 74, EXDEV: 75, ENODATA: 116, ENOTSUP: 138 });
|
|
27
|
+
export const DOLLY_PACKAGES_ABI_DIGEST = "2e7905a73d0f041833b239074042ed9ca77e692c3caa0d9d147be98f94ea95b8";
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
"version": 0,
|
|
4
4
|
"gives": "the release's published packages, for amy",
|
|
5
5
|
"dependencies": ["http@0"],
|
|
6
|
+
"phase": "image",
|
|
6
7
|
"imports": [],
|
|
7
8
|
"host": "packages.mjs",
|
|
8
|
-
"contracts": [],
|
|
9
|
+
"contracts": ["dolly-packages-0.wat"],
|
|
9
10
|
"process": [],
|
|
10
|
-
"headers": [],
|
|
11
|
-
"kernel": [],
|
|
12
|
-
"client": []
|
|
11
|
+
"headers": ["packages.h", "packages-abi.h"],
|
|
12
|
+
"kernel": ["kernel.c"],
|
|
13
|
+
"client": ["client.c"],
|
|
14
|
+
"package": "libdolly-packages"
|
|
13
15
|
}
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
-
// Published package snapshots for a running image.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// Published package snapshots for a running image. The guest names a package
|
|
2
|
+
// by the recipe pin an image directory publishes, by default the page's own;
|
|
3
|
+
// the page materializes and verifies its snapshot exactly as it does a build
|
|
4
|
+
// input. From the page's own directory the index, amy-index.txt, and the
|
|
5
|
+
// snapshots come over this module's own mailbox (dolly-packages-0.wat,
|
|
6
|
+
// transport.mjs); another directory is read through http@0's broker, as
|
|
7
|
+
// https://packages.dolly.invalid/v1/… (host/http/local-services.mjs), only
|
|
8
|
+
// where the page's HTTP policy admits it, as a request of the guest's would be.
|
|
8
9
|
import { PackageService } from "./service.mjs";
|
|
10
|
+
import { PackagesTransport } from "./transport.mjs";
|
|
11
|
+
export { DOLLY_PACKAGES_ABI_DIGEST as digest } from "./abi.mjs";
|
|
9
12
|
|
|
10
13
|
export function browser({ get }) {
|
|
11
14
|
return {
|
|
12
|
-
entryStarted() { get("http").services.packages = new PackageService(); },
|
|
15
|
+
entryStarted() { const http = get("http"); http.services.packages = new PackageService(http.admit); },
|
|
13
16
|
disabled() { delete get("http").services.packages; },
|
|
14
17
|
};
|
|
15
18
|
}
|
|
19
|
+
|
|
20
|
+
// The kernel's mailbox reports each request at the service tick.
|
|
21
|
+
export function worker({ images }) {
|
|
22
|
+
let transport;
|
|
23
|
+
return {
|
|
24
|
+
start({ kernel, memory }) {
|
|
25
|
+
transport = new PackagesTransport(memory.buffer, Number(kernel.dolly_packages_mailbox_address()), images);
|
|
26
|
+
},
|
|
27
|
+
service() { transport?.service(); },
|
|
28
|
+
dispose() { transport?.close(); },
|
|
29
|
+
};
|
|
30
|
+
}
|