@daugasauron/dolly 0.1.5 → 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
|
@@ -0,0 +1,1544 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// host/abi.mjs
|
|
12
|
+
var DOLLY_HOST_MAX_RECORDS;
|
|
13
|
+
var init_abi = __esm({
|
|
14
|
+
"host/abi.mjs"() {
|
|
15
|
+
DOLLY_HOST_MAX_RECORDS = 64;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// page/image-names.mjs
|
|
20
|
+
function unretainedPath(value) {
|
|
21
|
+
return value === "/tmp" || value.startsWith("/tmp/");
|
|
22
|
+
}
|
|
23
|
+
var init_image_names = __esm({
|
|
24
|
+
"page/image-names.mjs"() {
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// host/runtime/abi.mjs
|
|
29
|
+
var DOLLY_SNAPSHOT_HEADER_BYTES, DOLLY_SNAPSHOT_MAX_PATHS, DOLLY_SNAPSHOT_MAX_MEBIBYTES, DOLLY_ENTRY_VERSION, DOLLY_ENTRY_HEADER_BYTES, DOLLY_ENTRY_MAX_ARGUMENTS, DOLLY_ENTRY_ARGUMENT_MAX_BYTES, DOLLY_ENTRY_MAX_BYTES, DOLLY_ERRNO;
|
|
30
|
+
var init_abi2 = __esm({
|
|
31
|
+
"host/runtime/abi.mjs"() {
|
|
32
|
+
DOLLY_SNAPSHOT_HEADER_BYTES = 16;
|
|
33
|
+
DOLLY_SNAPSHOT_MAX_PATHS = 1e5;
|
|
34
|
+
DOLLY_SNAPSHOT_MAX_MEBIBYTES = 2048;
|
|
35
|
+
DOLLY_ENTRY_VERSION = 1;
|
|
36
|
+
DOLLY_ENTRY_HEADER_BYTES = 16;
|
|
37
|
+
DOLLY_ENTRY_MAX_ARGUMENTS = 256;
|
|
38
|
+
DOLLY_ENTRY_ARGUMENT_MAX_BYTES = 4096;
|
|
39
|
+
DOLLY_ENTRY_MAX_BYTES = 65536;
|
|
40
|
+
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 });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// page/snapshot-records.mjs
|
|
45
|
+
function validateSnapshotPacks(metadata) {
|
|
46
|
+
if (!Array.isArray(metadata.packs) || metadata.packs.length === 0 || metadata.packs.length > MAX_SNAPSHOT_PACKS) throw new Error("invalid snapshot packs");
|
|
47
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
48
|
+
let size = DOLLY_SNAPSHOT_HEADER_BYTES;
|
|
49
|
+
for (const pack of metadata.packs) {
|
|
50
|
+
if (!/^[0-9a-f]{64}$/.test(pack.sha256) || hashes.has(pack.sha256) || !Number.isSafeInteger(pack.byteLength) || pack.byteLength < DOLLY_SNAPSHOT_HEADER_BYTES || pack.byteLength > MAX_SNAPSHOT_BYTES || !Number.isSafeInteger(pack.encodedByteLength) || pack.encodedByteLength <= 0 || pack.encodedByteLength > MAX_SNAPSHOT_BYTES) {
|
|
51
|
+
throw new Error("invalid snapshot pack descriptor");
|
|
52
|
+
}
|
|
53
|
+
hashes.add(pack.sha256);
|
|
54
|
+
size += pack.byteLength - DOLLY_SNAPSHOT_HEADER_BYTES;
|
|
55
|
+
if (size > MAX_SNAPSHOT_BYTES) throw new Error("packed snapshot exceeds size limit");
|
|
56
|
+
}
|
|
57
|
+
if (size !== metadata.byteLength) throw new Error("snapshot pack sizes do not match image");
|
|
58
|
+
return metadata.packs;
|
|
59
|
+
}
|
|
60
|
+
var decoder, encoder, MAX_SNAPSHOT_BYTES, MAX_SNAPSHOT_PACKS;
|
|
61
|
+
var init_snapshot_records = __esm({
|
|
62
|
+
"page/snapshot-records.mjs"() {
|
|
63
|
+
init_image_names();
|
|
64
|
+
init_abi2();
|
|
65
|
+
decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
66
|
+
encoder = new TextEncoder();
|
|
67
|
+
MAX_SNAPSHOT_BYTES = DOLLY_SNAPSHOT_MAX_MEBIBYTES * 1024 * 1024;
|
|
68
|
+
MAX_SNAPSHOT_PACKS = MAX_SNAPSHOT_BYTES / (1024 * 1024);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// page/version.mjs
|
|
73
|
+
var DOLLY_VERSION;
|
|
74
|
+
var init_version = __esm({
|
|
75
|
+
"page/version.mjs"() {
|
|
76
|
+
DOLLY_VERSION = "0.1.6";
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// page/static-asset.mjs
|
|
81
|
+
async function sha256(bytes) {
|
|
82
|
+
return hex(await crypto.subtle.digest("SHA-256", bytes));
|
|
83
|
+
}
|
|
84
|
+
async function boundedAssetBody(response, limit, signal) {
|
|
85
|
+
if (!response.ok || !response.body) throw new Error(`asset returned HTTP ${response.status}`);
|
|
86
|
+
const reader = response.body.getReader(), chunks = [];
|
|
87
|
+
let size = 0;
|
|
88
|
+
try {
|
|
89
|
+
for (; ; ) {
|
|
90
|
+
signal?.throwIfAborted();
|
|
91
|
+
const { done, value } = await reader.read();
|
|
92
|
+
if (done) break;
|
|
93
|
+
size += value.length;
|
|
94
|
+
if (size > limit) {
|
|
95
|
+
await reader.cancel();
|
|
96
|
+
throw new Error("asset exceeds declared size");
|
|
97
|
+
}
|
|
98
|
+
chunks.push(value);
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
reader.releaseLock();
|
|
102
|
+
}
|
|
103
|
+
const bytes = new Uint8Array(size);
|
|
104
|
+
let offset = 0;
|
|
105
|
+
for (const chunk of chunks) {
|
|
106
|
+
bytes.set(chunk, offset);
|
|
107
|
+
offset += chunk.length;
|
|
108
|
+
}
|
|
109
|
+
return bytes;
|
|
110
|
+
}
|
|
111
|
+
async function peeked(response, length) {
|
|
112
|
+
const reader = response.body.getReader(), chunks = [];
|
|
113
|
+
let size = 0, ended = false;
|
|
114
|
+
while (size < length) {
|
|
115
|
+
const next = await reader.read();
|
|
116
|
+
if (next.done) {
|
|
117
|
+
ended = true;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
chunks.push(next.value);
|
|
121
|
+
size += next.value.length;
|
|
122
|
+
}
|
|
123
|
+
const head = new Uint8Array(Math.min(size, length));
|
|
124
|
+
for (let offset = 0, index = 0; offset < head.length; ++index) {
|
|
125
|
+
const taken = chunks[index].subarray(0, head.length - offset);
|
|
126
|
+
head.set(taken, offset);
|
|
127
|
+
offset += taken.length;
|
|
128
|
+
}
|
|
129
|
+
const body = new ReadableStream({
|
|
130
|
+
start(controller) {
|
|
131
|
+
for (const chunk of chunks) controller.enqueue(chunk);
|
|
132
|
+
if (ended) controller.close();
|
|
133
|
+
},
|
|
134
|
+
async pull(controller) {
|
|
135
|
+
const next = await reader.read();
|
|
136
|
+
if (next.done) controller.close();
|
|
137
|
+
else controller.enqueue(next.value);
|
|
138
|
+
},
|
|
139
|
+
cancel: (reason) => reader.cancel(reason)
|
|
140
|
+
}, { highWaterMark: 0 });
|
|
141
|
+
const whole = new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
142
|
+
Object.defineProperty(whole, "url", { value: response.url });
|
|
143
|
+
return { head, whole };
|
|
144
|
+
}
|
|
145
|
+
async function decodeStaticAsset(given, target, init, maximumBytes, fetchRequest = globalThis.fetch.bind(globalThis)) {
|
|
146
|
+
if (given.status !== 200 || !given.body) return given;
|
|
147
|
+
const start = new TextEncoder().encode(PARTS_MANIFEST_START), { head, whole: response } = await peeked(given, start.length);
|
|
148
|
+
if (head.length !== start.length || head.some((byte, index2) => byte !== start[index2])) return response;
|
|
149
|
+
const url = new URL(target);
|
|
150
|
+
if ((init.method ?? "GET") !== "GET" || url.search || url.hash || url.username || url.password || !/^https?:$/.test(url.protocol)) throw new Error("invalid multipart asset request");
|
|
151
|
+
const manifest = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await boundedAssetBody(response, 65536, init.signal)));
|
|
152
|
+
if (!Number.isSafeInteger(manifest.byteLength) || manifest.byteLength <= 0 || manifest.byteLength > maximumBytes || manifest.byteLength > MAX_SNAPSHOT_BYTES || !Array.isArray(manifest.parts) || manifest.parts.length < 2 || manifest.parts.length > staticMaxParts() || manifest.parts.some((part) => !Number.isSafeInteger(part.byteLength) || part.byteLength <= 0 || part.byteLength > STATIC_PART_BYTES || !/^[0-9a-f]{64}$/.test(part.sha256)) || manifest.parts.reduce((size, part) => size + part.byteLength, 0) !== manifest.byteLength) {
|
|
153
|
+
throw new Error("invalid multipart asset manifest");
|
|
154
|
+
}
|
|
155
|
+
let index = 0;
|
|
156
|
+
const body = new ReadableStream({
|
|
157
|
+
async pull(controller) {
|
|
158
|
+
if (index === manifest.parts.length) {
|
|
159
|
+
controller.close();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const part = manifest.parts[index];
|
|
163
|
+
const partURL = new URL(url);
|
|
164
|
+
partURL.pathname += `.part-${index++}`;
|
|
165
|
+
const result = await fetchRequest(partURL, {
|
|
166
|
+
method: "GET",
|
|
167
|
+
credentials: "omit",
|
|
168
|
+
redirect: "error",
|
|
169
|
+
referrerPolicy: "no-referrer",
|
|
170
|
+
cache: "force-cache",
|
|
171
|
+
signal: init.signal
|
|
172
|
+
});
|
|
173
|
+
const chunk = await boundedAssetBody(result, part.byteLength, init.signal);
|
|
174
|
+
if (chunk.length !== part.byteLength || await sha256(chunk) !== part.sha256) throw new Error("asset part integrity mismatch");
|
|
175
|
+
controller.enqueue(chunk);
|
|
176
|
+
}
|
|
177
|
+
}, { highWaterMark: 0 });
|
|
178
|
+
const headers = new Headers(response.headers);
|
|
179
|
+
headers.delete("x-dolly-parts");
|
|
180
|
+
headers.delete("content-encoding");
|
|
181
|
+
headers.set("content-length", String(manifest.byteLength));
|
|
182
|
+
const decoded = new Response(body, { status: response.status, statusText: response.statusText, headers });
|
|
183
|
+
Object.defineProperty(decoded, "url", { value: response.url });
|
|
184
|
+
return decoded;
|
|
185
|
+
}
|
|
186
|
+
var STATIC_PART_BYTES, staticMaxParts, hex, sitePrefix, siteReference, PARTS_MANIFEST_START;
|
|
187
|
+
var init_static_asset = __esm({
|
|
188
|
+
"page/static-asset.mjs"() {
|
|
189
|
+
init_snapshot_records();
|
|
190
|
+
init_version();
|
|
191
|
+
STATIC_PART_BYTES = 20 * 1024 * 1024;
|
|
192
|
+
staticMaxParts = () => Math.ceil(MAX_SNAPSHOT_BYTES / STATIC_PART_BYTES);
|
|
193
|
+
hex = (bytes) => Array.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
194
|
+
sitePrefix = `/v${DOLLY_VERSION}`;
|
|
195
|
+
siteReference = (path) => `${sitePrefix}/${path}`;
|
|
196
|
+
PARTS_MANIFEST_START = '{"byteLength":';
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// host/requirements.mjs
|
|
201
|
+
function hostRequirement(value) {
|
|
202
|
+
const match = typeof value === "string" && pattern.exec(value);
|
|
203
|
+
if (!match || Number(match[2]) > 65535) throw new TypeError(`invalid host requirement: ${value}`);
|
|
204
|
+
return { name: match[1], version: Number(match[2]) };
|
|
205
|
+
}
|
|
206
|
+
function hostRequirements(values = []) {
|
|
207
|
+
if (!Array.isArray(values)) throw new TypeError("invalid host requirements");
|
|
208
|
+
const versions = /* @__PURE__ */ new Map();
|
|
209
|
+
for (const value of values) {
|
|
210
|
+
const { name, version } = hostRequirement(value);
|
|
211
|
+
if (versions.has(name) && versions.get(name) !== version) throw new Error(`conflicting host ABI requirements for ${name}`);
|
|
212
|
+
versions.set(name, version);
|
|
213
|
+
}
|
|
214
|
+
if (versions.size > DOLLY_HOST_MAX_RECORDS) throw new TypeError("too many host requirements");
|
|
215
|
+
return [...versions].sort(([a], [b]) => a.localeCompare(b)).map(([name, version]) => `${name}@${version}`);
|
|
216
|
+
}
|
|
217
|
+
var pattern;
|
|
218
|
+
var init_requirements = __esm({
|
|
219
|
+
"host/requirements.mjs"() {
|
|
220
|
+
init_abi();
|
|
221
|
+
init_static_asset();
|
|
222
|
+
pattern = /^([a-z][a-z0-9-]{0,30})@(0|[1-9][0-9]{0,4})$/;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// page/process-constants.mjs
|
|
227
|
+
var DOLLY_ERRNO2, DOLLY_PROCESS_SIZEOF, DOLLY_PROCESS_OFFSETOF;
|
|
228
|
+
var init_process_constants = __esm({
|
|
229
|
+
"page/process-constants.mjs"() {
|
|
230
|
+
DOLLY_ERRNO2 = Object.freeze({
|
|
231
|
+
E2BIG: 1,
|
|
232
|
+
EACCES: 2,
|
|
233
|
+
EADDRINUSE: 3,
|
|
234
|
+
EADDRNOTAVAIL: 4,
|
|
235
|
+
EAFNOSUPPORT: 5,
|
|
236
|
+
EAGAIN: 6,
|
|
237
|
+
EALREADY: 7,
|
|
238
|
+
EBADF: 8,
|
|
239
|
+
EBADMSG: 9,
|
|
240
|
+
EBUSY: 10,
|
|
241
|
+
ECANCELED: 11,
|
|
242
|
+
ECHILD: 12,
|
|
243
|
+
ECONNABORTED: 13,
|
|
244
|
+
ECONNREFUSED: 14,
|
|
245
|
+
ECONNRESET: 15,
|
|
246
|
+
EDEADLK: 16,
|
|
247
|
+
EDESTADDRREQ: 17,
|
|
248
|
+
EDOM: 18,
|
|
249
|
+
EDQUOT: 19,
|
|
250
|
+
EEXIST: 20,
|
|
251
|
+
EFAULT: 21,
|
|
252
|
+
EFBIG: 22,
|
|
253
|
+
EHOSTUNREACH: 23,
|
|
254
|
+
EIDRM: 24,
|
|
255
|
+
EILSEQ: 25,
|
|
256
|
+
EINPROGRESS: 26,
|
|
257
|
+
EINTR: 27,
|
|
258
|
+
EINVAL: 28,
|
|
259
|
+
EIO: 29,
|
|
260
|
+
EISCONN: 30,
|
|
261
|
+
EISDIR: 31,
|
|
262
|
+
ELOOP: 32,
|
|
263
|
+
EMFILE: 33,
|
|
264
|
+
EMLINK: 34,
|
|
265
|
+
EMSGSIZE: 35,
|
|
266
|
+
EMULTIHOP: 36,
|
|
267
|
+
ENAMETOOLONG: 37,
|
|
268
|
+
ENETDOWN: 38,
|
|
269
|
+
ENETRESET: 39,
|
|
270
|
+
ENETUNREACH: 40,
|
|
271
|
+
ENFILE: 41,
|
|
272
|
+
ENOBUFS: 42,
|
|
273
|
+
ENODEV: 43,
|
|
274
|
+
ENOENT: 44,
|
|
275
|
+
ENOEXEC: 45,
|
|
276
|
+
ENOLCK: 46,
|
|
277
|
+
ENOLINK: 47,
|
|
278
|
+
ENOMEM: 48,
|
|
279
|
+
ENOMSG: 49,
|
|
280
|
+
ENOPROTOOPT: 50,
|
|
281
|
+
ENOSPC: 51,
|
|
282
|
+
ENOSYS: 52,
|
|
283
|
+
ENOTCONN: 53,
|
|
284
|
+
ENOTDIR: 54,
|
|
285
|
+
ENOTEMPTY: 55,
|
|
286
|
+
ENOTRECOVERABLE: 56,
|
|
287
|
+
ENOTSOCK: 57,
|
|
288
|
+
ENOTTY: 59,
|
|
289
|
+
ENXIO: 60,
|
|
290
|
+
EOVERFLOW: 61,
|
|
291
|
+
EOWNERDEAD: 62,
|
|
292
|
+
EPERM: 63,
|
|
293
|
+
EPIPE: 64,
|
|
294
|
+
EPROTO: 65,
|
|
295
|
+
EPROTONOSUPPORT: 66,
|
|
296
|
+
EPROTOTYPE: 67,
|
|
297
|
+
ERANGE: 68,
|
|
298
|
+
EROFS: 69,
|
|
299
|
+
ESPIPE: 70,
|
|
300
|
+
ESRCH: 71,
|
|
301
|
+
ESTALE: 72,
|
|
302
|
+
ETIMEDOUT: 73,
|
|
303
|
+
ETXTBSY: 74,
|
|
304
|
+
EXDEV: 75,
|
|
305
|
+
ENODATA: 116,
|
|
306
|
+
ENOTSUP: 138
|
|
307
|
+
});
|
|
308
|
+
DOLLY_PROCESS_SIZEOF = Object.freeze({
|
|
309
|
+
dolly_process_vector_sizes: 16,
|
|
310
|
+
dolly_process_exit_request: 8,
|
|
311
|
+
dolly_process_fd_io_request: 16,
|
|
312
|
+
dolly_process_fd_pread_request: 24,
|
|
313
|
+
dolly_process_fd_truncate_request: 16,
|
|
314
|
+
dolly_process_fd_request: 8,
|
|
315
|
+
dolly_process_timestamp: 16,
|
|
316
|
+
dolly_process_fd_times_request: 40,
|
|
317
|
+
dolly_process_io_result: 8,
|
|
318
|
+
dolly_process_fd_dup_request: 16,
|
|
319
|
+
dolly_process_fd_dup_response: 8,
|
|
320
|
+
dolly_process_fd_flags: 8,
|
|
321
|
+
dolly_process_fd_lock_request: 32,
|
|
322
|
+
dolly_process_fd_lock_response: 24,
|
|
323
|
+
dolly_process_pipe_request: 8,
|
|
324
|
+
dolly_process_pipe_response: 8,
|
|
325
|
+
dolly_process_poll_request: 16,
|
|
326
|
+
dolly_process_poll_query: 8,
|
|
327
|
+
dolly_process_poll_response: 16,
|
|
328
|
+
dolly_process_poll_result: 8,
|
|
329
|
+
dolly_process_directory_request: 24,
|
|
330
|
+
dolly_process_directory_entry: 24,
|
|
331
|
+
dolly_process_fd_seek_request: 16,
|
|
332
|
+
dolly_process_fd_seek_response: 8,
|
|
333
|
+
dolly_process_clock_request: 16,
|
|
334
|
+
dolly_process_clock_response: 8,
|
|
335
|
+
dolly_process_clock_sleep_request: 16,
|
|
336
|
+
dolly_process_exec_state: 16,
|
|
337
|
+
dolly_process_fd_mapping: 8,
|
|
338
|
+
dolly_process_spawn_request: 72,
|
|
339
|
+
dolly_process_spawn_response: 8,
|
|
340
|
+
dolly_process_exec_request: 48,
|
|
341
|
+
dolly_process_wait_request: 8,
|
|
342
|
+
dolly_process_wait_response: 16,
|
|
343
|
+
dolly_process_info_response: 8,
|
|
344
|
+
dolly_process_signal_request: 8,
|
|
345
|
+
dolly_process_signal_dispositions: 8,
|
|
346
|
+
dolly_process_alarm: 16,
|
|
347
|
+
dolly_process_terminal_request: 24,
|
|
348
|
+
dolly_process_terminal_response: 16,
|
|
349
|
+
dolly_process_path_request: 16,
|
|
350
|
+
dolly_process_path_times_request: 48,
|
|
351
|
+
dolly_process_path_open_response: 8,
|
|
352
|
+
dolly_process_stat_response: 88,
|
|
353
|
+
dolly_process_filesystem_stat_response: 96,
|
|
354
|
+
dolly_process_two_path_request: 16
|
|
355
|
+
});
|
|
356
|
+
DOLLY_PROCESS_OFFSETOF = Object.freeze({
|
|
357
|
+
dolly_process_vector_sizes: Object.freeze({ "count": 0, "reserved": 4, "bytes": 8 }),
|
|
358
|
+
dolly_process_exit_request: Object.freeze({ "status": 0, "signal_number": 4 }),
|
|
359
|
+
dolly_process_fd_io_request: Object.freeze({ "descriptor": 0, "reserved": 4, "size": 8 }),
|
|
360
|
+
dolly_process_fd_pread_request: Object.freeze({ "descriptor": 0, "reserved": 4, "offset": 8, "size": 16 }),
|
|
361
|
+
dolly_process_fd_truncate_request: Object.freeze({ "descriptor": 0, "reserved": 4, "size": 8 }),
|
|
362
|
+
dolly_process_fd_request: Object.freeze({ "descriptor": 0, "reserved": 4 }),
|
|
363
|
+
dolly_process_timestamp: Object.freeze({ "seconds": 0, "nanoseconds": 8, "flags": 12 }),
|
|
364
|
+
dolly_process_fd_times_request: Object.freeze({ "descriptor": 0, "reserved": 4, "access": 8, "modification": 24 }),
|
|
365
|
+
dolly_process_io_result: Object.freeze({ "size": 0 }),
|
|
366
|
+
dolly_process_fd_dup_request: Object.freeze({ "source_descriptor": 0, "target_descriptor": 4, "flags": 8, "reserved": 12 }),
|
|
367
|
+
dolly_process_fd_dup_response: Object.freeze({ "descriptor": 0, "reserved": 4 }),
|
|
368
|
+
dolly_process_fd_flags: Object.freeze({ "descriptor": 0, "flags": 4 }),
|
|
369
|
+
dolly_process_fd_lock_request: Object.freeze({ "descriptor": 0, "flags": 4, "type": 8, "whence": 12, "start": 16, "length": 24 }),
|
|
370
|
+
dolly_process_fd_lock_response: Object.freeze({ "type": 0, "pid": 4, "start": 8, "length": 16 }),
|
|
371
|
+
dolly_process_pipe_request: Object.freeze({ "flags": 0, "reserved": 4 }),
|
|
372
|
+
dolly_process_pipe_response: Object.freeze({ "read_descriptor": 0, "write_descriptor": 4 }),
|
|
373
|
+
dolly_process_poll_request: Object.freeze({ "deadline_nanoseconds": 0, "count": 8, "reserved": 12 }),
|
|
374
|
+
dolly_process_poll_query: Object.freeze({ "descriptor": 0, "events": 4, "reserved": 6 }),
|
|
375
|
+
dolly_process_poll_response: Object.freeze({ "ready": 0, "count": 4, "reserved": 8 }),
|
|
376
|
+
dolly_process_poll_result: Object.freeze({ "events": 0, "reserved": 2, "reserved2": 4 }),
|
|
377
|
+
dolly_process_directory_request: Object.freeze({ "descriptor": 0, "maximum_entries": 4, "cookie": 8, "reserved": 16 }),
|
|
378
|
+
dolly_process_directory_entry: Object.freeze({ "inode": 0, "next_cookie": 8, "file_type": 16, "name_size": 20 }),
|
|
379
|
+
dolly_process_fd_seek_request: Object.freeze({ "descriptor": 0, "whence": 4, "offset": 8 }),
|
|
380
|
+
dolly_process_fd_seek_response: Object.freeze({ "offset": 0 }),
|
|
381
|
+
dolly_process_clock_request: Object.freeze({ "clock_id": 0, "reserved": 4, "precision_nanoseconds": 8 }),
|
|
382
|
+
dolly_process_clock_response: Object.freeze({ "nanoseconds": 0 }),
|
|
383
|
+
dolly_process_clock_sleep_request: Object.freeze({ "clock_id": 0, "flags": 4, "deadline_nanoseconds": 8 }),
|
|
384
|
+
dolly_process_exec_state: Object.freeze({ "blocked_signals": 0, "ignored_signals": 4, "pending_signals": 8, "reserved": 12 }),
|
|
385
|
+
dolly_process_fd_mapping: Object.freeze({ "source_descriptor": 0, "target_descriptor": 4 }),
|
|
386
|
+
dolly_process_spawn_request: Object.freeze({ "flags": 0, "argument_count": 4, "environment_count": 8, "cwd_size": 12, "mapping_count": 16, "descriptor_inheritance": 20, "reserved": 24, "path_size": 28, "argument_bytes": 32, "environment_bytes": 40, "deadline_nanoseconds": 48, "signals": 56 }),
|
|
387
|
+
dolly_process_spawn_response: Object.freeze({ "pid": 0, "reserved": 4 }),
|
|
388
|
+
dolly_process_exec_request: Object.freeze({ "signals": 0, "argument_count": 16, "environment_count": 20, "path_size": 24, "reserved": 28, "argument_bytes": 32, "environment_bytes": 40 }),
|
|
389
|
+
dolly_process_wait_request: Object.freeze({ "pid": 0, "flags": 4 }),
|
|
390
|
+
dolly_process_wait_response: Object.freeze({ "pid": 0, "status": 4, "signal_number": 8, "reserved": 12 }),
|
|
391
|
+
dolly_process_info_response: Object.freeze({ "pid": 0, "parent_pid": 4 }),
|
|
392
|
+
dolly_process_signal_request: Object.freeze({ "pid": 0, "signal_number": 4 }),
|
|
393
|
+
dolly_process_signal_dispositions: Object.freeze({ "ignored_signals": 0, "handled_signals": 4 }),
|
|
394
|
+
dolly_process_alarm: Object.freeze({ "value_nanoseconds": 0, "interval_nanoseconds": 8 }),
|
|
395
|
+
dolly_process_terminal_request: Object.freeze({ "operation": 0, "descriptor": 4, "flags": 8, "reserved": 12, "deadline_nanoseconds": 16 }),
|
|
396
|
+
dolly_process_terminal_response: Object.freeze({ "value": 0, "columns": 8, "rows": 12 }),
|
|
397
|
+
dolly_process_path_request: Object.freeze({ "directory_descriptor": 0, "flags": 4, "reserved": 8, "path_size": 12 }),
|
|
398
|
+
dolly_process_path_times_request: Object.freeze({ "directory_descriptor": 0, "flags": 4, "reserved": 8, "path_size": 12, "access": 16, "modification": 32 }),
|
|
399
|
+
dolly_process_path_open_response: Object.freeze({ "descriptor": 0, "reserved": 4 }),
|
|
400
|
+
dolly_process_stat_response: Object.freeze({ "device": 0, "inode": 8, "size": 16, "access_nanoseconds": 24, "modification_nanoseconds": 32, "change_nanoseconds": 40, "blocks": 48, "mode": 56, "link_count": 60, "user": 64, "group": 68, "block_size": 72, "file_type": 76, "reserved": 80 }),
|
|
401
|
+
dolly_process_filesystem_stat_response: Object.freeze({ "type": 0, "block_size": 8, "blocks": 16, "blocks_free": 24, "blocks_available": 32, "files": 40, "files_free": 48, "maximum_name_length": 56, "fragment_size": 64, "flags": 72, "filesystem_id": 80, "reserved": 88 }),
|
|
402
|
+
dolly_process_two_path_request: Object.freeze({ "old_directory_descriptor": 0, "new_directory_descriptor": 4, "old_path_size": 8, "new_path_size": 12 })
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// host/modules.mjs
|
|
408
|
+
var modules_exports = {};
|
|
409
|
+
__export(modules_exports, {
|
|
410
|
+
hostRegistry: () => hostRegistry
|
|
411
|
+
});
|
|
412
|
+
async function hostRegistry(urls) {
|
|
413
|
+
const definitions = await Promise.all(urls.map(async (url) => {
|
|
414
|
+
const { default: manifest } = await import(url, { with: { type: "json" } });
|
|
415
|
+
const contract = Object.freeze({ ...manifest, url });
|
|
416
|
+
let provider;
|
|
417
|
+
return { contract, provider: () => provider ??= import(new URL(contract.host, url).href) };
|
|
418
|
+
}));
|
|
419
|
+
const byName = /* @__PURE__ */ new Map(), owners = /* @__PURE__ */ new Map();
|
|
420
|
+
for (const module of definitions) {
|
|
421
|
+
if (byName.has(module.contract.name)) throw new Error(`duplicate host module: ${module.contract.name}`);
|
|
422
|
+
byName.set(module.contract.name, module);
|
|
423
|
+
for (const name of module.contract.imports) {
|
|
424
|
+
if (owners.has(name)) throw new Error(`duplicate host import owner: ${name}`);
|
|
425
|
+
owners.set(name, module.contract.name);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const contracts = Object.freeze(definitions.map((module) => module.contract));
|
|
429
|
+
const requirementsOf = (keep) => Object.freeze(contracts.filter(keep).map(({ name, version }) => `${name}@${version}`));
|
|
430
|
+
const runtimes = requirementsOf((contract) => contract.provides === "kernel");
|
|
431
|
+
const buildHost = requirementsOf((contract) => contract.build);
|
|
432
|
+
const registry2 = { byName, owners, contracts, runtimes };
|
|
433
|
+
return Object.freeze({
|
|
434
|
+
urls: Object.freeze([...urls]),
|
|
435
|
+
buildHostFor: (declared) => [...declared.filter((value) => runtimes.includes(value)), ...buildHost],
|
|
436
|
+
load: (values) => loadProviders(byName, values),
|
|
437
|
+
selectBoot: (route) => selectBoot(definitions, route),
|
|
438
|
+
createHost: (...values) => createHost(registry2, ...values)
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async function selectBoot(definitions, route) {
|
|
442
|
+
if (!route.loadSession) return null;
|
|
443
|
+
let selection = null;
|
|
444
|
+
for (const module of definitions) {
|
|
445
|
+
const selected = await (await module.provider()).boot?.(route);
|
|
446
|
+
if (!selected) continue;
|
|
447
|
+
if (selection) throw new Error("two host modules selected the boot");
|
|
448
|
+
const { name, version } = module.contract;
|
|
449
|
+
selection = { ...selected, module: `${name}@${version}`, configuration: { [name]: selected.configuration } };
|
|
450
|
+
}
|
|
451
|
+
return selection;
|
|
452
|
+
}
|
|
453
|
+
function loadProviders(byName, values, seen = /* @__PURE__ */ new Set()) {
|
|
454
|
+
for (const value of values) {
|
|
455
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
456
|
+
if (seen.has(name) || module?.contract.version !== version) continue;
|
|
457
|
+
seen.add(name);
|
|
458
|
+
module.provider().catch(() => {
|
|
459
|
+
});
|
|
460
|
+
loadProviders(byName, module.contract.dependencies, seen);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function createHost({ byName, owners, contracts, runtimes }, side, enabled, { send, resources = {}, configuration = {} } = {}) {
|
|
464
|
+
if (!["browser", "worker"].includes(side)) throw new TypeError("invalid host side");
|
|
465
|
+
const selected = hostRequirements(enabled), instances = /* @__PURE__ */ new Map(), reasons = /* @__PURE__ */ new Map(), messages = /* @__PURE__ */ new Map();
|
|
466
|
+
const kernels = selected.filter((value) => runtimes.includes(value));
|
|
467
|
+
const started = /* @__PURE__ */ new Set(), pending = /* @__PURE__ */ new Map(), transfers = [], config = {};
|
|
468
|
+
const switched = /* @__PURE__ */ new Map();
|
|
469
|
+
let switching = Promise.resolve(), startContext, entryContext;
|
|
470
|
+
const page = {}, builder = {}, inherited = {};
|
|
471
|
+
const admittedAbi = /* @__PURE__ */ new Map();
|
|
472
|
+
let disposed = false;
|
|
473
|
+
const answers = /* @__PURE__ */ new Map();
|
|
474
|
+
let asked = 0;
|
|
475
|
+
const ask = (message, transfers2 = []) => new Promise((resolve, reject) => {
|
|
476
|
+
if (disposed) return reject(new Error("host providers closed"));
|
|
477
|
+
answers.set(++asked, { resolve, reject });
|
|
478
|
+
send({ ...message, answer: asked }, transfers2);
|
|
479
|
+
});
|
|
480
|
+
function requireModules(requirements) {
|
|
481
|
+
for (const value of hostRequirements(requirements)) {
|
|
482
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
483
|
+
const reason = !module ? "unknown host module" : module.contract.version !== version ? `host provides ABI ${module.contract.version}` : reasons.get(name) ?? (!instances.has(name) ? "not enabled by the host" : null);
|
|
484
|
+
if (reason) throw new Error(`Required host module ${value} is unavailable: ${reason}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const get = (name) => instances.get(name);
|
|
488
|
+
function claimsKey(event) {
|
|
489
|
+
for (const instance of instances.values()) {
|
|
490
|
+
const claim = instance.claimsKey?.(event);
|
|
491
|
+
if (claim) return claim;
|
|
492
|
+
}
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
const surfaceSize = () => [...instances.values()].find((instance) => instance.surfaceSize)?.surfaceSize;
|
|
496
|
+
function framePoint({ clientX, clientY }) {
|
|
497
|
+
const bounds = resources.canvas.getBoundingClientRect(), { width, height } = surfaceSize() ?? resources.canvas;
|
|
498
|
+
const scale = Math.min(bounds.width / width, bounds.height / height);
|
|
499
|
+
if (!(scale > 0)) return { x: 0, y: 0 };
|
|
500
|
+
return {
|
|
501
|
+
x: (clientX - bounds.left - (bounds.width - width * scale) / 2) / scale,
|
|
502
|
+
y: (clientY - bounds.top - (bounds.height - height * scale) / 2) / scale
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
async function attach(value, visiting = /* @__PURE__ */ new Set(), checked = false) {
|
|
506
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
507
|
+
if (instances.has(name) || reasons.has(name)) return;
|
|
508
|
+
if (!module || version !== module.contract.version) {
|
|
509
|
+
reasons.set(name, !module ? "unknown host module" : `host provides ABI ${module.contract.version}`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (visiting.has(name)) throw new Error(`host module dependency cycle: ${name}`);
|
|
513
|
+
visiting.add(name);
|
|
514
|
+
for (const dependency2 of module.contract.dependencies) await attach(dependency2, visiting);
|
|
515
|
+
visiting.delete(name);
|
|
516
|
+
try {
|
|
517
|
+
requireModules(module.contract.dependencies);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
reasons.set(name, error.message);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const provider = await module.provider();
|
|
523
|
+
if (side === "browser" && !checked) {
|
|
524
|
+
const reason = await provider.check?.(resources);
|
|
525
|
+
if (reason) {
|
|
526
|
+
reasons.set(name, reason);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const dependency = (name2) => {
|
|
531
|
+
if (!module.contract.dependencies.some((value2) => hostRequirement(value2).name === name2)) {
|
|
532
|
+
throw new Error(`${module.contract.name} accessed undeclared dependency ${name2}`);
|
|
533
|
+
}
|
|
534
|
+
return get(name2);
|
|
535
|
+
};
|
|
536
|
+
const instance = provider[side]?.({
|
|
537
|
+
...resources,
|
|
538
|
+
switched: checked,
|
|
539
|
+
send,
|
|
540
|
+
ask,
|
|
541
|
+
get: dependency,
|
|
542
|
+
service: () => {
|
|
543
|
+
for (const instance2 of instances.values()) instance2.service?.();
|
|
544
|
+
},
|
|
545
|
+
claimsKey,
|
|
546
|
+
framePoint,
|
|
547
|
+
abi: admittedAbi,
|
|
548
|
+
modules: module.contract.switches && side === "browser" ? { list, enable, disable } : void 0,
|
|
549
|
+
configuration: configuration[name] ?? {}
|
|
550
|
+
}) ?? {};
|
|
551
|
+
instances.set(name, instance);
|
|
552
|
+
for (const [type, handler] of Object.entries(instance.messages ?? {})) {
|
|
553
|
+
if (messages.has(type)) throw new Error(`duplicate host message: ${type}`);
|
|
554
|
+
messages.set(type, handler);
|
|
555
|
+
}
|
|
556
|
+
config[name] = instance.configuration ?? {};
|
|
557
|
+
transfers.push(...instance.transfers ?? []);
|
|
558
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(instance.page ?? {}))) {
|
|
559
|
+
if (key in page) throw new Error(`duplicate page API member: ${key}`);
|
|
560
|
+
Object.defineProperty(page, key, descriptor);
|
|
561
|
+
}
|
|
562
|
+
if (instance.builder) builder[name] = instance.builder;
|
|
563
|
+
for (const [key, value2] of Object.entries(instance.inherited ?? {})) {
|
|
564
|
+
if (key in inherited) throw new Error(`duplicate inherited record: ${key}`);
|
|
565
|
+
inherited[key] = value2;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function detach(name) {
|
|
569
|
+
const instance = instances.get(name);
|
|
570
|
+
instance.dispose?.();
|
|
571
|
+
instance.disabled?.();
|
|
572
|
+
for (const type of Object.keys(instance.messages ?? {})) messages.delete(type);
|
|
573
|
+
for (const key of Object.keys(instance.page ?? {})) delete page[key];
|
|
574
|
+
for (const key of Object.keys(instance.inherited ?? {})) delete inherited[key];
|
|
575
|
+
delete builder[name];
|
|
576
|
+
delete config[name];
|
|
577
|
+
instances.delete(name);
|
|
578
|
+
started.delete(name);
|
|
579
|
+
admittedAbi.delete(`${name}@${byName.get(name).contract.version}`);
|
|
580
|
+
}
|
|
581
|
+
async function startProvider(name, instance, context) {
|
|
582
|
+
if (started.has(name)) throw new Error(`host provider ${name} initialized twice`);
|
|
583
|
+
started.add(name);
|
|
584
|
+
const descriptor = await instance.start(context);
|
|
585
|
+
if (descriptor === void 0) return;
|
|
586
|
+
await new Promise((resolve, reject) => {
|
|
587
|
+
const timer = setTimeout(() => {
|
|
588
|
+
pending.delete(name);
|
|
589
|
+
reject(new Error(`host provider ${name} did not acknowledge initialization`));
|
|
590
|
+
}, 1e4);
|
|
591
|
+
pending.set(name, { resolve: () => {
|
|
592
|
+
clearTimeout(timer);
|
|
593
|
+
resolve();
|
|
594
|
+
}, reject: (error) => {
|
|
595
|
+
clearTimeout(timer);
|
|
596
|
+
reject(error);
|
|
597
|
+
} });
|
|
598
|
+
send({ type: "host-ready", module: name, descriptor });
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const requirementOf = ({ name, version }) => `${name}@${version}`;
|
|
602
|
+
const list = () => contracts.map((contract) => ({
|
|
603
|
+
name: contract.name,
|
|
604
|
+
version: contract.version,
|
|
605
|
+
gives: contract.gives ?? "",
|
|
606
|
+
fixed: contract.fixed ?? "",
|
|
607
|
+
enabled: instances.has(contract.name),
|
|
608
|
+
declared: selected.includes(requirementOf(contract))
|
|
609
|
+
}));
|
|
610
|
+
const workerSwitch = (value, enabling, name = hostRequirement(value).name) => new Promise((resolve) => {
|
|
611
|
+
switched.set(value, resolve);
|
|
612
|
+
if (enabling) send({ type: "host-switch", module: value, enabling, configuration: config[name] }, instances.get(name).transfers ?? []);
|
|
613
|
+
else send({ type: "host-switch", module: value, enabling });
|
|
614
|
+
});
|
|
615
|
+
const serially = (task) => switching = switching.then(task, task);
|
|
616
|
+
const enable = (value) => serially(async () => {
|
|
617
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
618
|
+
if (!module || module.contract.version !== version) return "this page has no such host module";
|
|
619
|
+
if (instances.has(name)) return null;
|
|
620
|
+
if (module.contract.fixed) return `fixed: ${module.contract.fixed}`;
|
|
621
|
+
const missing = module.contract.dependencies.find((dependency) => !instances.has(hostRequirement(dependency).name));
|
|
622
|
+
if (missing) return `it needs ${missing}`;
|
|
623
|
+
reasons.delete(name);
|
|
624
|
+
try {
|
|
625
|
+
const abandoned = new AbortController(), timer = setTimeout(() => abandoned.abort(), 5e3);
|
|
626
|
+
const unavailable = await Promise.race([(await module.provider()).check?.({ ...resources, signal: abandoned.signal }), new Promise((resolve) => abandoned.signal.addEventListener("abort", () => resolve("the browser did not answer in time")))]);
|
|
627
|
+
clearTimeout(timer);
|
|
628
|
+
if (unavailable) return unavailable;
|
|
629
|
+
await attach(value, /* @__PURE__ */ new Set(), true);
|
|
630
|
+
if (!instances.has(name)) return reasons.get(name);
|
|
631
|
+
const refusal = await workerSwitch(value, true);
|
|
632
|
+
if (refusal) {
|
|
633
|
+
detach(name);
|
|
634
|
+
return refusal;
|
|
635
|
+
}
|
|
636
|
+
if (entryContext) await instances.get(name).entryStarted?.(entryContext);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (instances.has(name)) {
|
|
639
|
+
await workerSwitch(value, false);
|
|
640
|
+
detach(name);
|
|
641
|
+
}
|
|
642
|
+
return error.message;
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
});
|
|
646
|
+
const disable = (value) => serially(async () => {
|
|
647
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
648
|
+
if (!module || module.contract.version !== version) return "this page has no such host module";
|
|
649
|
+
if (!instances.has(name)) return null;
|
|
650
|
+
if (module.contract.fixed) return `fixed: ${module.contract.fixed}`;
|
|
651
|
+
const refusal = await workerSwitch(value, false);
|
|
652
|
+
if (refusal) return refusal;
|
|
653
|
+
detach(name);
|
|
654
|
+
return null;
|
|
655
|
+
});
|
|
656
|
+
function dispose() {
|
|
657
|
+
if (disposed) return;
|
|
658
|
+
disposed = true;
|
|
659
|
+
for (const wait of [...pending.values(), ...answers.values()]) wait.reject(new Error("host providers closed"));
|
|
660
|
+
pending.clear();
|
|
661
|
+
answers.clear();
|
|
662
|
+
for (const instance of [...instances.values()].reverse()) instance.dispose?.();
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
loadProviders(byName, selected);
|
|
666
|
+
for (const value of selected) await attach(value);
|
|
667
|
+
if (kernels.length !== 1) {
|
|
668
|
+
requireModules(selected);
|
|
669
|
+
throw new Error(kernels.length ? `${kernels.join(" and ")} both provide the kernel` : `no enabled host module provides the kernel: add REQUIRES HOST ${runtimes.join(" or ")}`);
|
|
670
|
+
}
|
|
671
|
+
requireModules(kernels);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
dispose();
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
const kernel = hostRequirement(kernels[0]).name;
|
|
677
|
+
return {
|
|
678
|
+
transfers,
|
|
679
|
+
configuration: config,
|
|
680
|
+
page,
|
|
681
|
+
builder,
|
|
682
|
+
inherited,
|
|
683
|
+
kernel: instances.get(kernel),
|
|
684
|
+
get enabled() {
|
|
685
|
+
return [...instances.keys()].map((name) => requirementOf(byName.get(name).contract));
|
|
686
|
+
},
|
|
687
|
+
require: requireModules,
|
|
688
|
+
dispose,
|
|
689
|
+
// Executables may use only the modules the image declares. A declared module
|
|
690
|
+
// this host does not enable (a build host) answers its calls with ENOSYS.
|
|
691
|
+
async admit(requirements) {
|
|
692
|
+
const admitted = /* @__PURE__ */ new Map();
|
|
693
|
+
for (const value of hostRequirements(requirements)) {
|
|
694
|
+
const { name, version } = hostRequirement(value), module = byName.get(name);
|
|
695
|
+
const digest = module?.contract.version === version ? (await module.provider()).digest : void 0;
|
|
696
|
+
if (digest) admitted.set(value, digest);
|
|
697
|
+
}
|
|
698
|
+
admittedAbi.clear();
|
|
699
|
+
for (const [value, digest] of admitted) admittedAbi.set(value, digest);
|
|
700
|
+
},
|
|
701
|
+
// The Worker asks this before it restores the image: a module that restores
|
|
702
|
+
// the whole filesystem itself (a saved session, host/snapshot) says so, and
|
|
703
|
+
// no image is loaded.
|
|
704
|
+
async restoreFilesystem(context) {
|
|
705
|
+
for (const instance of instances.values()) if (await instance.restoreFilesystem?.(context)) return true;
|
|
706
|
+
return false;
|
|
707
|
+
},
|
|
708
|
+
// Whether restoreFilesystem will restore one, known from the configuration,
|
|
709
|
+
// before a kernel exists.
|
|
710
|
+
restoresFilesystem() {
|
|
711
|
+
return [...instances.values()].some((instance) => instance.restoresFilesystem?.());
|
|
712
|
+
},
|
|
713
|
+
// The Worker calls this after restoring the system image and before starting
|
|
714
|
+
// image-phase modules, in registry order.
|
|
715
|
+
async imageRestored(context) {
|
|
716
|
+
for (const instance of instances.values()) await instance.imageRestored?.(context);
|
|
717
|
+
},
|
|
718
|
+
// The page calls this once the image ENTRY may run, right before it lets
|
|
719
|
+
// the Worker start it: modules may then show their UI and admit services.
|
|
720
|
+
entryStarted(context) {
|
|
721
|
+
entryContext = context;
|
|
722
|
+
return Promise.all([...instances.values()].map((instance) => instance.entryStarted?.(context)));
|
|
723
|
+
},
|
|
724
|
+
// The page asks this when the image has ended, before it lets go of the
|
|
725
|
+
// modules: the links ({ text, href }) each one offers beside "start again".
|
|
726
|
+
ended: () => [...instances.values()].flatMap((instance) => instance.ended?.() ?? []),
|
|
727
|
+
// The kernel's whole import object: each import is the binding of the
|
|
728
|
+
// module whose manifest owns it.
|
|
729
|
+
imports(module) {
|
|
730
|
+
const imports = {};
|
|
731
|
+
for (const entry of WebAssembly.Module.imports(module)) {
|
|
732
|
+
const name = `${entry.module}.${entry.name}`, owner = owners.get(name);
|
|
733
|
+
if (!owner) {
|
|
734
|
+
(imports[entry.module] ??= {})[entry.name] = () => -DOLLY_ERRNO2.ENOSYS;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const binding = instances.has(owner) ? instances.get(owner).bindings?.[name] : () => -DOLLY_ERRNO2.ENOSYS;
|
|
738
|
+
if (binding === void 0) throw new Error(`missing host binding: ${name}`);
|
|
739
|
+
(imports[entry.module] ??= {})[entry.name] = byName.get(owner).contract.fixed ? binding : (...values) => instances.has(owner) ? instances.get(owner).bindings[name](...values) : -DOLLY_ERRNO2.ENOSYS;
|
|
740
|
+
}
|
|
741
|
+
return imports;
|
|
742
|
+
},
|
|
743
|
+
async handle(message) {
|
|
744
|
+
if (disposed) return false;
|
|
745
|
+
if (message.type === "host-ready-ack" && side === "worker") {
|
|
746
|
+
const wait = pending.get(message.module);
|
|
747
|
+
if (!wait) throw new Error("unexpected host acknowledgement");
|
|
748
|
+
pending.delete(message.module);
|
|
749
|
+
if (message.error) wait.reject(new Error(message.error));
|
|
750
|
+
else wait.resolve();
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
if (message.type === "host-ready" && side === "browser") {
|
|
754
|
+
try {
|
|
755
|
+
const instance = get(message.module);
|
|
756
|
+
if (!instance?.start || started.has(message.module)) throw new Error("invalid host provider handshake");
|
|
757
|
+
started.add(message.module);
|
|
758
|
+
await instance.start(message.descriptor);
|
|
759
|
+
send({ type: "host-ready-ack", module: message.module });
|
|
760
|
+
} catch (error) {
|
|
761
|
+
send({ type: "host-ready-ack", module: message.module, error: error.message });
|
|
762
|
+
throw error;
|
|
763
|
+
}
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
if (message.type === "host-switch" && side === "worker") {
|
|
767
|
+
const { name } = hostRequirement(message.module);
|
|
768
|
+
let refusal = null;
|
|
769
|
+
try {
|
|
770
|
+
if (message.enabling) {
|
|
771
|
+
configuration[name] = message.configuration;
|
|
772
|
+
reasons.delete(name);
|
|
773
|
+
await attach(message.module);
|
|
774
|
+
const instance = instances.get(name);
|
|
775
|
+
if (!instance) throw new Error(reasons.get(name));
|
|
776
|
+
if (instance.start) await startProvider(name, instance, startContext);
|
|
777
|
+
const { digest } = await byName.get(name).provider();
|
|
778
|
+
if (digest) admittedAbi.set(message.module, digest);
|
|
779
|
+
} else if (instances.has(name)) detach(name);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
if (instances.has(name)) detach(name);
|
|
782
|
+
refusal = error.message;
|
|
783
|
+
}
|
|
784
|
+
send({ type: "host-switch-ack", module: message.module, refusal });
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
if (message.type === "host-switch-ack" && side === "browser") {
|
|
788
|
+
switched.get(message.module)?.(message.refusal);
|
|
789
|
+
switched.delete(message.module);
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
if (message.type === "host-answer") {
|
|
793
|
+
const waiting = answers.get(message.answer);
|
|
794
|
+
answers.delete(message.answer);
|
|
795
|
+
if (message.error === void 0) waiting?.resolve(message.result);
|
|
796
|
+
else waiting?.reject(new Error(message.error));
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
const handler = messages.get(message.type);
|
|
800
|
+
if (!handler) return false;
|
|
801
|
+
if (message.answer === void 0) {
|
|
802
|
+
await handler(message);
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
try {
|
|
806
|
+
send({ type: "host-answer", answer: message.answer, result: await handler(message) });
|
|
807
|
+
} catch (error) {
|
|
808
|
+
send({ type: "host-answer", answer: message.answer, error: error.message });
|
|
809
|
+
}
|
|
810
|
+
return true;
|
|
811
|
+
},
|
|
812
|
+
async start(phase, context) {
|
|
813
|
+
if (side !== "worker" || disposed) throw new Error("host cannot initialize providers");
|
|
814
|
+
startContext = context;
|
|
815
|
+
for (const [name, instance] of [...instances]) {
|
|
816
|
+
if (byName.get(name).contract.phase === phase && instance.start) await startProvider(name, instance, context);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
var init_modules = __esm({
|
|
822
|
+
"host/modules.mjs"() {
|
|
823
|
+
init_requirements();
|
|
824
|
+
init_process_constants();
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// page/images.mjs
|
|
829
|
+
var DOLLY_IMAGES = [];
|
|
830
|
+
var DOLLY_STATIC_SOURCES = [];
|
|
831
|
+
var imageBase;
|
|
832
|
+
async function loadImages(base) {
|
|
833
|
+
imageBase = new URL(base);
|
|
834
|
+
({ images: DOLLY_IMAGES, staticSources: DOLLY_STATIC_SOURCES } = (await import(new URL("dist/dolly-images.json", imageBase).href, { with: { type: "json" } })).default);
|
|
835
|
+
}
|
|
836
|
+
var imageDefinition = (name) => DOLLY_IMAGES.find((definition) => definition.image === name);
|
|
837
|
+
|
|
838
|
+
// page/generated.mjs
|
|
839
|
+
import { DOLLY_BUILD_ID } from "../dist/dolly-build-id.mjs";
|
|
840
|
+
import { DOLLY_IMAGE_BUILD_ID } from "../dist/dolly-image-build-id.mjs";
|
|
841
|
+
import { DOLLY_PROCESS_ABI_DIGEST } from "../dist/dolly-process-abi.mjs";
|
|
842
|
+
import { DOLLY_KERNEL_PLUGIN_ABI_DIGEST, DOLLY_KERNEL_PLUGIN_IMPORTS } from "../dist/dolly-kernel-plugin-abi.mjs";
|
|
843
|
+
|
|
844
|
+
// page/sha256.mjs
|
|
845
|
+
var K = Int32Array.of(
|
|
846
|
+
1116352408,
|
|
847
|
+
1899447441,
|
|
848
|
+
3049323471,
|
|
849
|
+
3921009573,
|
|
850
|
+
961987163,
|
|
851
|
+
1508970993,
|
|
852
|
+
2453635748,
|
|
853
|
+
2870763221,
|
|
854
|
+
3624381080,
|
|
855
|
+
310598401,
|
|
856
|
+
607225278,
|
|
857
|
+
1426881987,
|
|
858
|
+
1925078388,
|
|
859
|
+
2162078206,
|
|
860
|
+
2614888103,
|
|
861
|
+
3248222580,
|
|
862
|
+
3835390401,
|
|
863
|
+
4022224774,
|
|
864
|
+
264347078,
|
|
865
|
+
604807628,
|
|
866
|
+
770255983,
|
|
867
|
+
1249150122,
|
|
868
|
+
1555081692,
|
|
869
|
+
1996064986,
|
|
870
|
+
2554220882,
|
|
871
|
+
2821834349,
|
|
872
|
+
2952996808,
|
|
873
|
+
3210313671,
|
|
874
|
+
3336571891,
|
|
875
|
+
3584528711,
|
|
876
|
+
113926993,
|
|
877
|
+
338241895,
|
|
878
|
+
666307205,
|
|
879
|
+
773529912,
|
|
880
|
+
1294757372,
|
|
881
|
+
1396182291,
|
|
882
|
+
1695183700,
|
|
883
|
+
1986661051,
|
|
884
|
+
2177026350,
|
|
885
|
+
2456956037,
|
|
886
|
+
2730485921,
|
|
887
|
+
2820302411,
|
|
888
|
+
3259730800,
|
|
889
|
+
3345764771,
|
|
890
|
+
3516065817,
|
|
891
|
+
3600352804,
|
|
892
|
+
4094571909,
|
|
893
|
+
275423344,
|
|
894
|
+
430227734,
|
|
895
|
+
506948616,
|
|
896
|
+
659060556,
|
|
897
|
+
883997877,
|
|
898
|
+
958139571,
|
|
899
|
+
1322822218,
|
|
900
|
+
1537002063,
|
|
901
|
+
1747873779,
|
|
902
|
+
1955562222,
|
|
903
|
+
2024104815,
|
|
904
|
+
2227730452,
|
|
905
|
+
2361852424,
|
|
906
|
+
2428436474,
|
|
907
|
+
2756734187,
|
|
908
|
+
3204031479,
|
|
909
|
+
3329325298
|
|
910
|
+
);
|
|
911
|
+
var Sha256 = class {
|
|
912
|
+
state = Int32Array.of(1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225);
|
|
913
|
+
block = new Uint8Array(64);
|
|
914
|
+
used = 0;
|
|
915
|
+
length = 0;
|
|
916
|
+
words = new Int32Array(64);
|
|
917
|
+
compress(view, offset) {
|
|
918
|
+
const w = this.words, s = this.state;
|
|
919
|
+
for (let i = 0; i < 16; i++) w[i] = view.getInt32(offset + 4 * i);
|
|
920
|
+
for (let i = 16; i < 64; i++) {
|
|
921
|
+
const x = w[i - 15], y = w[i - 2];
|
|
922
|
+
w[i] = ((x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ x >>> 3) + w[i - 7] + ((y >>> 17 | y << 15) ^ (y >>> 19 | y << 13) ^ y >>> 10) + w[i - 16] | 0;
|
|
923
|
+
}
|
|
924
|
+
let a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7];
|
|
925
|
+
for (let i = 0; i < 64; i++) {
|
|
926
|
+
const t1 = h + ((e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7)) + (e & f ^ ~e & g) + K[i] + w[i] | 0;
|
|
927
|
+
const t2 = ((a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10)) + (a & b ^ a & c ^ b & c) | 0;
|
|
928
|
+
h = g;
|
|
929
|
+
g = f;
|
|
930
|
+
f = e;
|
|
931
|
+
e = d + t1 | 0;
|
|
932
|
+
d = c;
|
|
933
|
+
c = b;
|
|
934
|
+
b = a;
|
|
935
|
+
a = t1 + t2 | 0;
|
|
936
|
+
}
|
|
937
|
+
s[0] += a;
|
|
938
|
+
s[1] += b;
|
|
939
|
+
s[2] += c;
|
|
940
|
+
s[3] += d;
|
|
941
|
+
s[4] += e;
|
|
942
|
+
s[5] += f;
|
|
943
|
+
s[6] += g;
|
|
944
|
+
s[7] += h;
|
|
945
|
+
}
|
|
946
|
+
update(bytes) {
|
|
947
|
+
this.length += bytes.length;
|
|
948
|
+
let offset = 0;
|
|
949
|
+
if (this.used !== 0) {
|
|
950
|
+
offset = Math.min(64 - this.used, bytes.length);
|
|
951
|
+
this.block.set(bytes.subarray(0, offset), this.used);
|
|
952
|
+
this.used += offset;
|
|
953
|
+
if (this.used < 64) return;
|
|
954
|
+
this.compress(new DataView(this.block.buffer), 0);
|
|
955
|
+
this.used = 0;
|
|
956
|
+
}
|
|
957
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
958
|
+
for (; offset + 64 <= bytes.length; offset += 64) this.compress(view, offset);
|
|
959
|
+
this.block.set(bytes.subarray(offset));
|
|
960
|
+
this.used = bytes.length - offset;
|
|
961
|
+
}
|
|
962
|
+
// The lowercase hex digest; the hash takes no more bytes after it.
|
|
963
|
+
hex() {
|
|
964
|
+
const bits = this.length * 8;
|
|
965
|
+
this.update(Uint8Array.of(128));
|
|
966
|
+
this.update(new Uint8Array((120 - this.used) % 64));
|
|
967
|
+
const view = new DataView(this.block.buffer);
|
|
968
|
+
view.setUint32(56, Math.floor(bits / 2 ** 32));
|
|
969
|
+
view.setUint32(60, bits >>> 0);
|
|
970
|
+
this.compress(view, 0);
|
|
971
|
+
return [...this.state].map((word) => (word >>> 0).toString(16).padStart(8, "0")).join("");
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// page/image-inputs.mjs
|
|
976
|
+
function imageInputs(artifacts = []) {
|
|
977
|
+
if (!Array.isArray(artifacts) || artifacts.length > 256) throw new Error("invalid image inputs");
|
|
978
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
979
|
+
for (const artifact of artifacts) {
|
|
980
|
+
const { recipeSha256, sha256: sha2562 } = artifact ?? {};
|
|
981
|
+
if (!/^[0-9a-f]{64}$/.test(recipeSha256) || !/^[0-9a-f]{64}$/.test(sha2562)) {
|
|
982
|
+
throw new Error("invalid image input digest");
|
|
983
|
+
}
|
|
984
|
+
if (inputs.has(recipeSha256) && inputs.get(recipeSha256).sha256 !== sha2562) {
|
|
985
|
+
throw new Error("conflicting image inputs");
|
|
986
|
+
}
|
|
987
|
+
inputs.set(recipeSha256, { recipeSha256, sha256: sha2562 });
|
|
988
|
+
}
|
|
989
|
+
return [...inputs.values()].sort((a, b) => a.recipeSha256.localeCompare(b.recipeSha256));
|
|
990
|
+
}
|
|
991
|
+
function imageInputsMatch(actual, expected) {
|
|
992
|
+
return JSON.stringify(imageInputs(actual)) === JSON.stringify(imageInputs(expected));
|
|
993
|
+
}
|
|
994
|
+
function recipesDigest(recipes) {
|
|
995
|
+
const hash = new Sha256();
|
|
996
|
+
hash.update(new TextEncoder().encode(recipes.map(({ sha256: sha2562, byteLength, sourcePath }) => `${sha2562} ${byteLength} ${sourcePath}
|
|
997
|
+
`).join("")));
|
|
998
|
+
return hash.hex();
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// page/packaged-snapshot.mjs
|
|
1002
|
+
init_requirements();
|
|
1003
|
+
init_static_asset();
|
|
1004
|
+
init_snapshot_records();
|
|
1005
|
+
init_abi2();
|
|
1006
|
+
init_image_names();
|
|
1007
|
+
|
|
1008
|
+
// page/pack-store.mjs
|
|
1009
|
+
init_static_asset();
|
|
1010
|
+
var STORE = "dolly-packs";
|
|
1011
|
+
var LOCK = "dolly-packs";
|
|
1012
|
+
var encoder2 = new TextEncoder();
|
|
1013
|
+
async function storeDirectories() {
|
|
1014
|
+
const root = await (await navigator.storage.getDirectory()).getDirectoryHandle(STORE, { create: true });
|
|
1015
|
+
const [packs, records] = await Promise.all(["packs", "images"].map((name) => root.getDirectoryHandle(name, { create: true })));
|
|
1016
|
+
return { packs, records };
|
|
1017
|
+
}
|
|
1018
|
+
var hold = (mode) => new Promise((held, refused) => {
|
|
1019
|
+
const released = navigator.locks.request(LOCK, { mode }, () => new Promise((release) => held(() => {
|
|
1020
|
+
release();
|
|
1021
|
+
return released;
|
|
1022
|
+
})));
|
|
1023
|
+
released.catch(refused);
|
|
1024
|
+
});
|
|
1025
|
+
async function writeFile(directory, name, data) {
|
|
1026
|
+
const writable = await (await directory.getFileHandle(name, { create: true })).createWritable();
|
|
1027
|
+
await writable.write(data);
|
|
1028
|
+
await writable.close();
|
|
1029
|
+
}
|
|
1030
|
+
var noStore = {
|
|
1031
|
+
kept: async () => null,
|
|
1032
|
+
keeping: async (pack, body) => ({ body, keep() {
|
|
1033
|
+
}, discard() {
|
|
1034
|
+
} }),
|
|
1035
|
+
close() {
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
async function openPackStore(image, packs) {
|
|
1039
|
+
let store, release;
|
|
1040
|
+
try {
|
|
1041
|
+
store = await storeDirectories();
|
|
1042
|
+
release = await hold("shared");
|
|
1043
|
+
const record = { directory: imageBase.href, image, buildId: DOLLY_IMAGE_BUILD_ID, packs: packs.map((pack) => pack.sha256) };
|
|
1044
|
+
await writeFile(store.records, await sha256(encoder2.encode(`${record.directory}\0${image}`)), JSON.stringify(record));
|
|
1045
|
+
} catch {
|
|
1046
|
+
release?.();
|
|
1047
|
+
return noStore;
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
async kept(pack) {
|
|
1051
|
+
try {
|
|
1052
|
+
const file = await (await store.packs.getFileHandle(pack.sha256)).getFile();
|
|
1053
|
+
if (file.size !== pack.encodedByteLength) return null;
|
|
1054
|
+
return { body: file.stream(), keep() {
|
|
1055
|
+
}, discard: () => store.packs.removeEntry(pack.sha256).catch(() => {
|
|
1056
|
+
}) };
|
|
1057
|
+
} catch {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
async keeping(pack, body) {
|
|
1062
|
+
let writable = await store.packs.getFileHandle(pack.sha256, { create: true }).then((handle) => handle.createWritable()).catch(() => null);
|
|
1063
|
+
const discard = async () => {
|
|
1064
|
+
const unkept = writable;
|
|
1065
|
+
writable = null;
|
|
1066
|
+
await unkept?.abort().catch(() => {
|
|
1067
|
+
});
|
|
1068
|
+
};
|
|
1069
|
+
return {
|
|
1070
|
+
body: body.pipeThrough(new TransformStream({
|
|
1071
|
+
async transform(chunk, controller) {
|
|
1072
|
+
await writable?.write(chunk).catch(discard);
|
|
1073
|
+
controller.enqueue(chunk);
|
|
1074
|
+
}
|
|
1075
|
+
})),
|
|
1076
|
+
keep: () => writable?.close().catch(discard),
|
|
1077
|
+
discard
|
|
1078
|
+
};
|
|
1079
|
+
},
|
|
1080
|
+
close(complete) {
|
|
1081
|
+
void release().then(() => complete && navigator.locks.request(LOCK, { ifAvailable: true }, (lock) => lock && prune(store))).catch(() => {
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
async function prune({ packs, records }) {
|
|
1087
|
+
const used = /* @__PURE__ */ new Set(), stale = [], unused = [];
|
|
1088
|
+
for await (const [name, handle] of records) {
|
|
1089
|
+
try {
|
|
1090
|
+
const record = JSON.parse(await (await handle.getFile()).text());
|
|
1091
|
+
if (record.buildId === DOLLY_IMAGE_BUILD_ID && (record.directory !== imageBase.href || imageDefinition(record.image))) {
|
|
1092
|
+
for (const pack of record.packs) used.add(pack);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
stale.push(name);
|
|
1098
|
+
}
|
|
1099
|
+
for (const name of stale) await records.removeEntry(name).catch(() => {
|
|
1100
|
+
});
|
|
1101
|
+
for await (const name of packs.keys()) if (!used.has(name)) unused.push(name);
|
|
1102
|
+
for (const name of unused) await packs.removeEntry(name).catch(() => {
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// page/packaged-snapshot.mjs
|
|
1107
|
+
var definitionOf = (image) => DOLLY_IMAGES.find((definition) => definition.image === image);
|
|
1108
|
+
var encoder3 = new TextEncoder();
|
|
1109
|
+
function validSnapshotPath(path) {
|
|
1110
|
+
return typeof path === "string" && path.startsWith("/") && path.length > 1 && path.length <= 4096 && !path.includes("\\") && !path.includes("\0") && !path.includes("//") && !path.split("/").some((part) => part === "." || part === "..") && !unretainedPath(path);
|
|
1111
|
+
}
|
|
1112
|
+
var referenced = ({ location, sha256: sha2562 }) => DOLLY_IMAGES.find((candidate) => siteReference(candidate.dollyfile) === location && candidate.sha256 === sha2562);
|
|
1113
|
+
async function importMetadata(image) {
|
|
1114
|
+
try {
|
|
1115
|
+
return (await import(new URL(`dist/dolly-${image}-system-snapshot.json`, imageBase).href, { with: { type: "json" } })).default;
|
|
1116
|
+
} catch {
|
|
1117
|
+
throw new Error(`The packaged ${image} snapshot is missing. Run npm run image -- ${image} first.`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
async function loadImageMetadata(image) {
|
|
1121
|
+
return checkedMetadata(image, await importMetadata(image), null);
|
|
1122
|
+
}
|
|
1123
|
+
function checkedMetadata(image, metadata, checked) {
|
|
1124
|
+
if (metadata === null || typeof metadata !== "object" || metadata.image !== image || metadata.buildId !== DOLLY_IMAGE_BUILD_ID || metadata.formatVersion !== 2 || metadata.identityVersion !== 2 || !Array.isArray(metadata.recipes) || recipesDigest(metadata.recipes) !== definitionOf(image).recipesSha256 || JSON.stringify(hostRequirements(metadata.hostRequirements)) !== JSON.stringify(definitionOf(image).hostRequirements) || !Number.isSafeInteger(metadata.byteLength) || metadata.byteLength <= 0 || metadata.byteLength > MAX_SNAPSHOT_BYTES || metadata.encoding !== "packs" || !/^[0-9a-f]{64}$/.test(metadata.sha256) || !Array.isArray(metadata.manifest) || metadata.manifest.length === 0 || metadata.manifest.length > DOLLY_SNAPSHOT_MAX_PATHS) {
|
|
1125
|
+
throw new Error("The packaged system snapshot metadata does not match this Dolly build");
|
|
1126
|
+
}
|
|
1127
|
+
validateSnapshotPacks(metadata);
|
|
1128
|
+
const inputs = imageInputs(metadata.inputs);
|
|
1129
|
+
const references = [...new Set(definitionOf(image).artifacts.map((reference) => reference.sha256))].sort();
|
|
1130
|
+
if (JSON.stringify(inputs.map((input) => input.recipeSha256)) !== JSON.stringify(references)) {
|
|
1131
|
+
throw new Error("The packaged system snapshot has stale image inputs");
|
|
1132
|
+
}
|
|
1133
|
+
let manifestBytes = 0;
|
|
1134
|
+
for (const path of metadata.manifest) {
|
|
1135
|
+
if (!validSnapshotPath(path)) {
|
|
1136
|
+
throw new Error("The packaged system snapshot has an invalid retained-path manifest");
|
|
1137
|
+
}
|
|
1138
|
+
manifestBytes += encoder3.encode(path).byteLength + 1;
|
|
1139
|
+
}
|
|
1140
|
+
if (manifestBytes > 8 * 1024 * 1024) {
|
|
1141
|
+
throw new Error("The packaged system snapshot manifest is too large");
|
|
1142
|
+
}
|
|
1143
|
+
for (const required of [
|
|
1144
|
+
"/etc/dolly/Dollyfile",
|
|
1145
|
+
"/etc/dolly/environment",
|
|
1146
|
+
"/etc/dolly/image",
|
|
1147
|
+
"/etc/dolly/recipes.lock"
|
|
1148
|
+
]) {
|
|
1149
|
+
if (!metadata.manifest.includes(required)) {
|
|
1150
|
+
throw new Error(`The packaged system snapshot is missing ${required}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
if (checked === null) return metadata;
|
|
1154
|
+
const expectedInputs = definitionOf(image).artifacts.map((reference) => ({ recipeSha256: reference.sha256, sha256: checked.get(referenced(reference).image).sha256 }));
|
|
1155
|
+
if (!imageInputsMatch(inputs, expectedInputs)) throw new Error("The packaged system snapshot has stale image inputs");
|
|
1156
|
+
return metadata;
|
|
1157
|
+
}
|
|
1158
|
+
var PREFETCH_PACKS = 8;
|
|
1159
|
+
var PREFETCH_BYTES = 64 * 1024 * 1024;
|
|
1160
|
+
async function* packagedSnapshotParts(metadata, signal) {
|
|
1161
|
+
const packs = validateSnapshotPacks(metadata), store = await openPackStore(metadata.image, packs);
|
|
1162
|
+
const opened = async (pack) => {
|
|
1163
|
+
const kept = await store.kept(pack);
|
|
1164
|
+
return kept ? { part: kept, downloaded: false } : { part: await store.keeping(pack, await downloadedPack(pack, signal)), downloaded: true };
|
|
1165
|
+
};
|
|
1166
|
+
const ahead = [];
|
|
1167
|
+
let next = 0, aheadBytes = 0, complete = false;
|
|
1168
|
+
try {
|
|
1169
|
+
while (next < packs.length || ahead.length) {
|
|
1170
|
+
signal?.throwIfAborted();
|
|
1171
|
+
while (next < packs.length && (!ahead.length || ahead.length < PREFETCH_PACKS && aheadBytes + packs[next].encodedByteLength <= PREFETCH_BYTES)) {
|
|
1172
|
+
const pack2 = packs[next++], opening2 = opened(pack2);
|
|
1173
|
+
opening2.catch(() => {
|
|
1174
|
+
});
|
|
1175
|
+
ahead.push({ pack: pack2, opening: opening2 });
|
|
1176
|
+
aheadBytes += pack2.encodedByteLength;
|
|
1177
|
+
}
|
|
1178
|
+
const { pack, opening } = ahead.shift();
|
|
1179
|
+
aheadBytes -= pack.encodedByteLength;
|
|
1180
|
+
const { part } = await opening;
|
|
1181
|
+
let verified = false;
|
|
1182
|
+
try {
|
|
1183
|
+
yield { ...pack, body: part.body, verified: () => {
|
|
1184
|
+
verified = true;
|
|
1185
|
+
return part.keep();
|
|
1186
|
+
} };
|
|
1187
|
+
} finally {
|
|
1188
|
+
if (!verified) await part.discard();
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
complete = true;
|
|
1192
|
+
} finally {
|
|
1193
|
+
for (const { opening } of ahead) {
|
|
1194
|
+
void opening.then(({ part, downloaded }) => {
|
|
1195
|
+
void part.body.cancel().catch(() => {
|
|
1196
|
+
});
|
|
1197
|
+
if (downloaded) void part.discard();
|
|
1198
|
+
}, () => {
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
store.close(complete);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
async function downloadedPack(pack, signal) {
|
|
1205
|
+
const url = new URL(`dist/packs/${pack.sha256}.snapshot.gz`, imageBase);
|
|
1206
|
+
const init = { cache: "force-cache", credentials: "same-origin", redirect: "error", signal };
|
|
1207
|
+
const response = await decodeStaticAsset(await fetch(url, init), url, init, pack.encodedByteLength);
|
|
1208
|
+
if (!response.ok || !response.body) throw new Error(`snapshot returned HTTP ${response.status}`);
|
|
1209
|
+
const declared = response.headers.get("content-length");
|
|
1210
|
+
if (declared !== null && Number(declared) !== pack.encodedByteLength) throw new Error("snapshot HTTP content length mismatch");
|
|
1211
|
+
return response.body;
|
|
1212
|
+
}
|
|
1213
|
+
var gunzip = (body) => body.pipeThrough(new DecompressionStream("gzip"));
|
|
1214
|
+
async function* chunksOf(body, signal) {
|
|
1215
|
+
const reader = body.getReader();
|
|
1216
|
+
let finished = false;
|
|
1217
|
+
try {
|
|
1218
|
+
for (; ; ) {
|
|
1219
|
+
signal?.throwIfAborted();
|
|
1220
|
+
const { done, value } = await reader.read();
|
|
1221
|
+
if (done) break;
|
|
1222
|
+
yield value;
|
|
1223
|
+
}
|
|
1224
|
+
finished = true;
|
|
1225
|
+
} finally {
|
|
1226
|
+
if (!finished) await reader.cancel().catch(() => {
|
|
1227
|
+
});
|
|
1228
|
+
reader.releaseLock();
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async function streamPackagedSystemSnapshot(metadata, write, endPart, signal) {
|
|
1232
|
+
for await (const part of packagedSnapshotParts(metadata, signal)) {
|
|
1233
|
+
const bytes = new Uint8Array(part.byteLength);
|
|
1234
|
+
let size = 0;
|
|
1235
|
+
for await (const chunk of chunksOf(gunzip(part.body), signal)) {
|
|
1236
|
+
if (chunk.length > part.byteLength - size) throw new Error("snapshot exceeds declared size");
|
|
1237
|
+
bytes.set(chunk, size);
|
|
1238
|
+
await write(chunk);
|
|
1239
|
+
size += chunk.length;
|
|
1240
|
+
}
|
|
1241
|
+
if (size !== part.byteLength || hex(await crypto.subtle.digest("SHA-256", bytes)) !== part.sha256) {
|
|
1242
|
+
throw new Error("snapshot integrity mismatch");
|
|
1243
|
+
}
|
|
1244
|
+
await endPart();
|
|
1245
|
+
await part.verified();
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// page/runtime-worker.mjs
|
|
1250
|
+
init_static_asset();
|
|
1251
|
+
|
|
1252
|
+
// page/image-entry.mjs
|
|
1253
|
+
init_abi2();
|
|
1254
|
+
function decodeImageEntry(bytes) {
|
|
1255
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength < DOLLY_ENTRY_HEADER_BYTES || bytes.byteLength > DOLLY_ENTRY_MAX_BYTES) {
|
|
1256
|
+
throw new TypeError("invalid image ENTRY record");
|
|
1257
|
+
}
|
|
1258
|
+
const decoder4 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
1259
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1260
|
+
if (decoder4.decode(bytes.subarray(0, 8)) !== "DOLLYENT" || view.getUint32(8, true) !== DOLLY_ENTRY_VERSION) {
|
|
1261
|
+
throw new TypeError("invalid image ENTRY version");
|
|
1262
|
+
}
|
|
1263
|
+
const count = view.getUint32(12, true);
|
|
1264
|
+
if (count === 0 || count > DOLLY_ENTRY_MAX_ARGUMENTS) throw new TypeError("invalid image ENTRY count");
|
|
1265
|
+
let offset = DOLLY_ENTRY_HEADER_BYTES;
|
|
1266
|
+
const arguments_ = [];
|
|
1267
|
+
for (let index = 0; index < count; ++index) {
|
|
1268
|
+
if (offset > bytes.byteLength - 4) throw new TypeError("truncated image ENTRY");
|
|
1269
|
+
const size = view.getUint32(offset, true);
|
|
1270
|
+
offset += 4;
|
|
1271
|
+
if (size > DOLLY_ENTRY_ARGUMENT_MAX_BYTES || offset > bytes.byteLength - size) throw new TypeError("invalid image ENTRY argument");
|
|
1272
|
+
const value = decoder4.decode(bytes.subarray(offset, offset + size));
|
|
1273
|
+
if (value.includes("\0")) throw new TypeError("image ENTRY has a NUL argument");
|
|
1274
|
+
arguments_.push(value);
|
|
1275
|
+
offset += size;
|
|
1276
|
+
}
|
|
1277
|
+
if (offset !== bytes.byteLength || !arguments_[0].startsWith("/")) {
|
|
1278
|
+
throw new TypeError("invalid image ENTRY payload");
|
|
1279
|
+
}
|
|
1280
|
+
return arguments_;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// page/worker-image.mjs
|
|
1284
|
+
init_snapshot_records();
|
|
1285
|
+
|
|
1286
|
+
// page/process-supervisor.mjs
|
|
1287
|
+
init_process_constants();
|
|
1288
|
+
|
|
1289
|
+
// page/wasm-interface.mjs
|
|
1290
|
+
var decoder2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
1291
|
+
|
|
1292
|
+
// page/process-abi.mjs
|
|
1293
|
+
init_static_asset();
|
|
1294
|
+
|
|
1295
|
+
// page/process-supervisor.mjs
|
|
1296
|
+
init_requirements();
|
|
1297
|
+
init_process_constants();
|
|
1298
|
+
init_static_asset();
|
|
1299
|
+
init_abi2();
|
|
1300
|
+
var encoder4 = new TextEncoder();
|
|
1301
|
+
var spawnHeaderSize = DOLLY_PROCESS_SIZEOF.dolly_process_spawn_request;
|
|
1302
|
+
var spawnField = DOLLY_PROCESS_OFFSETOF.dolly_process_spawn_request;
|
|
1303
|
+
var mappingField = DOLLY_PROCESS_OFFSETOF.dolly_process_fd_mapping;
|
|
1304
|
+
var mappingSize = DOLLY_PROCESS_SIZEOF.dolly_process_fd_mapping;
|
|
1305
|
+
var deferredResult = -(1n << 63n);
|
|
1306
|
+
var interruptedSystemCall = -BigInt(DOLLY_ERRNO2.EINTR);
|
|
1307
|
+
var processMemoryBudgetBytes = 4 * 1024 * 1024 * 1024;
|
|
1308
|
+
var compiledModuleCacheBytes = 256 * 1024 * 1024;
|
|
1309
|
+
var heldModuleBytes = 16 * 1024 * 1024;
|
|
1310
|
+
var holderUrl = URL.createObjectURL(new Blob([`let module;
|
|
1311
|
+
onmessage = async ({ data: { bytes, port } }) => {
|
|
1312
|
+
if (port) return port.postMessage(module);
|
|
1313
|
+
try {
|
|
1314
|
+
module = await WebAssembly.compile(bytes);
|
|
1315
|
+
postMessage(null);
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
postMessage(String(error?.message ?? error));
|
|
1318
|
+
} finally {
|
|
1319
|
+
bytes.transfer(0);
|
|
1320
|
+
}
|
|
1321
|
+
};`], { type: "text/javascript" }));
|
|
1322
|
+
var largeInteractiveProcessBytes = 128 * 1024 * 1024;
|
|
1323
|
+
function terminalFailureReason(error) {
|
|
1324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1325
|
+
const firstLine = message.split(/[\r\n]/, 1)[0].replace(/[^\x20-\x7e]/g, "?").slice(0, 512);
|
|
1326
|
+
return firstLine || "unknown Worker failure";
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// host/runtime/boot-files.mjs
|
|
1330
|
+
init_abi2();
|
|
1331
|
+
var encoder5 = new TextEncoder();
|
|
1332
|
+
function bootFiles(kernel, memory) {
|
|
1333
|
+
const allocate = (size) => {
|
|
1334
|
+
const address = Number(kernel.malloc(BigInt(Math.max(1, size))));
|
|
1335
|
+
if (address === 0) throw new Error("Dolly boot allocation failed");
|
|
1336
|
+
return address;
|
|
1337
|
+
};
|
|
1338
|
+
const withBytes = (bytes, use) => {
|
|
1339
|
+
const address = allocate(bytes.length);
|
|
1340
|
+
try {
|
|
1341
|
+
new Uint8Array(memory.buffer, address, bytes.length).set(bytes);
|
|
1342
|
+
return use(BigInt(address));
|
|
1343
|
+
} finally {
|
|
1344
|
+
kernel.free(BigInt(address));
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
const withPath = (path, use) => withBytes(encoder5.encode(`${path}\0`), use);
|
|
1348
|
+
return {
|
|
1349
|
+
write(path, value) {
|
|
1350
|
+
const bytes = typeof value === "string" ? encoder5.encode(value) : value;
|
|
1351
|
+
if (!(bytes instanceof Uint8Array)) throw new TypeError("invalid Dolly boot file");
|
|
1352
|
+
const status = withPath(path, (pathAddress) => withBytes(bytes, (address) => kernel.dolly_write_file(pathAddress, address, BigInt(bytes.length))));
|
|
1353
|
+
if (status !== 0) throw new Error(`Dolly could not write ${path}: status ${status}`);
|
|
1354
|
+
},
|
|
1355
|
+
// The build's input INPUT (the runtime's setBuildInputs), SIZE bytes, as PATH.
|
|
1356
|
+
input(path, input, size) {
|
|
1357
|
+
const status = withPath(path, (pathAddress) => kernel.dolly_build_input(pathAddress, input, BigInt(size)));
|
|
1358
|
+
if (status !== 0) throw new Error(`Dolly could not place ${path}: status ${status}`);
|
|
1359
|
+
},
|
|
1360
|
+
// Guest-writable files are bounded before trusted code copies them.
|
|
1361
|
+
read(path, limit) {
|
|
1362
|
+
const address = allocate(limit);
|
|
1363
|
+
try {
|
|
1364
|
+
const size = withPath(path, (pathAddress) => kernel.dolly_read_file(pathAddress, BigInt(address), BigInt(limit)));
|
|
1365
|
+
if (size === -DOLLY_ERRNO.EFBIG || size > limit) throw new Error(`${path} is larger than ${limit} bytes`);
|
|
1366
|
+
if (size < 0) throw new Error(`Dolly could not read ${path}: status ${size}`);
|
|
1367
|
+
return new Uint8Array(memory.buffer, address, size).slice();
|
|
1368
|
+
} finally {
|
|
1369
|
+
kernel.free(BigInt(address));
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
remove(path) {
|
|
1373
|
+
const status = withPath(path, (pathAddress) => kernel.dolly_remove_file(pathAddress));
|
|
1374
|
+
if (status !== 0 && status !== -DOLLY_ERRNO.ENOENT) throw new Error(`Dolly could not remove ${path}: status ${status}`);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// page/worker-image.mjs
|
|
1380
|
+
var decoder3 = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
1381
|
+
var configured = new Promise((resolve) => {
|
|
1382
|
+
self.addEventListener("message", function configure(event) {
|
|
1383
|
+
if (event.data?.type !== "configure") return;
|
|
1384
|
+
self.removeEventListener("message", configure);
|
|
1385
|
+
resolve(event.data);
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
var registry = Promise.resolve().then(() => (init_modules(), modules_exports));
|
|
1389
|
+
self.addEventListener("message", function prepare(event) {
|
|
1390
|
+
if (event.data?.type !== "prepare") return;
|
|
1391
|
+
self.removeEventListener("message", prepare);
|
|
1392
|
+
const { images, modules, hostModules } = event.data;
|
|
1393
|
+
void (async () => {
|
|
1394
|
+
await loadImages(images);
|
|
1395
|
+
(await (await registry).hostRegistry(modules)).load(hostModules);
|
|
1396
|
+
})().catch(() => {
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
var { hostRegistry: hostRegistry2 } = await registry;
|
|
1400
|
+
var runtimeBase = new URL("../", import.meta.url);
|
|
1401
|
+
var kernelArtifact = (path) => {
|
|
1402
|
+
if (path !== "dolly.wasm" && path !== "dolly.data") throw new Error("unknown fixed kernel artifact");
|
|
1403
|
+
return new URL(`dist/${path}`, runtimeBase).href;
|
|
1404
|
+
};
|
|
1405
|
+
function checkedMemoryRange(memory, addressValue, sizeValue) {
|
|
1406
|
+
const address = Number(addressValue);
|
|
1407
|
+
const size = Number(sizeValue);
|
|
1408
|
+
if (!Number.isSafeInteger(address) || !Number.isSafeInteger(size) || address <= 0 || size <= 0 || size > MAX_SNAPSHOT_BYTES || address > memory.buffer.byteLength - size) {
|
|
1409
|
+
throw new Error("Wasm published an invalid system snapshot memory range");
|
|
1410
|
+
}
|
|
1411
|
+
return { address, size };
|
|
1412
|
+
}
|
|
1413
|
+
var stage = (text) => self.postMessage({ type: "bootstrap", text: `${text}
|
|
1414
|
+
` });
|
|
1415
|
+
function acknowledged(type, failure) {
|
|
1416
|
+
return new Promise((resolve, reject) => {
|
|
1417
|
+
const timeout = setTimeout(() => reject(new Error(failure)), 1e4);
|
|
1418
|
+
self.addEventListener("message", function acknowledge(event) {
|
|
1419
|
+
if (event.data?.type !== type) return;
|
|
1420
|
+
self.removeEventListener("message", acknowledge);
|
|
1421
|
+
clearTimeout(timeout);
|
|
1422
|
+
resolve();
|
|
1423
|
+
});
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
async function runImage(image) {
|
|
1427
|
+
let host;
|
|
1428
|
+
self.addEventListener("message", (event) => {
|
|
1429
|
+
void host?.handle(event.data).catch((error) => {
|
|
1430
|
+
host.dispose();
|
|
1431
|
+
self.postMessage({ type: "error", message: error.message });
|
|
1432
|
+
});
|
|
1433
|
+
});
|
|
1434
|
+
try {
|
|
1435
|
+
const config = await configured;
|
|
1436
|
+
if (!Array.isArray(config.modules) || config.modules.length > 64 || !config.modules.every((url) => typeof url === "string")) throw new Error("invalid host module list");
|
|
1437
|
+
if (typeof config.images !== "string") throw new Error("invalid image directory");
|
|
1438
|
+
await loadImages(config.images);
|
|
1439
|
+
host = await (await hostRegistry2(config.modules)).createHost("worker", config.hostModules, {
|
|
1440
|
+
send: (message, transfers = []) => self.postMessage(message, transfers),
|
|
1441
|
+
configuration: config.hostConfiguration,
|
|
1442
|
+
// The image directory reaches the modules here: one the bundle holds is not theirs.
|
|
1443
|
+
resources: { runtimeBase, images: new URL(config.images) }
|
|
1444
|
+
});
|
|
1445
|
+
const steps = await image(config, host);
|
|
1446
|
+
stage("creating wasm64 userspace kernel...");
|
|
1447
|
+
const memory = host.kernel.memory;
|
|
1448
|
+
await host.kernel.growing;
|
|
1449
|
+
const kernelModule = await WebAssembly.compileStreaming(fetch(kernelArtifact("dolly.wasm")));
|
|
1450
|
+
const kernel = new WebAssembly.Instance(kernelModule, host.imports(kernelModule)).exports;
|
|
1451
|
+
kernel._initialize();
|
|
1452
|
+
const files = bootFiles(kernel, memory);
|
|
1453
|
+
await steps.prepare({ kernel, memory, files, stage });
|
|
1454
|
+
stage("Dolly runtime loaded");
|
|
1455
|
+
await host.start("kernel", { kernel, memory });
|
|
1456
|
+
const restored = await steps.restore({ kernel, memory, files, stage });
|
|
1457
|
+
if (restored.status !== 0) throw new Error(`Dolly bootstrap failed with status ${restored.status}`);
|
|
1458
|
+
if (config.buildOnly) {
|
|
1459
|
+
self.close();
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const supervisor = restored.supervisor ?? await host.kernel.supervisor(kernel);
|
|
1463
|
+
await steps.finish?.({ kernel, stage });
|
|
1464
|
+
await host.imageRestored({ kernel, memory, supervisor, stage, files, image: steps.name });
|
|
1465
|
+
if (kernel.dolly_bootstrap_environment() !== 0) throw new Error("Dolly image environment is invalid");
|
|
1466
|
+
await host.start("image", { kernel, memory });
|
|
1467
|
+
const runtimeImage = decoder3.decode(files.read("/etc/dolly/image", 256));
|
|
1468
|
+
if (steps.name !== "custom" && runtimeImage !== steps.name) {
|
|
1469
|
+
throw new Error(`Dollyfile selected image ${runtimeImage}, expected ${steps.name}`);
|
|
1470
|
+
}
|
|
1471
|
+
stage("starting image entry...");
|
|
1472
|
+
const entryReady = acknowledged("entry-ready-ack", "browser did not acknowledge image startup");
|
|
1473
|
+
self.postMessage({ type: "ready", image: runtimeImage });
|
|
1474
|
+
await entryReady;
|
|
1475
|
+
const entry = async () => supervisor.spawn(decodeImageEntry(files.read("/etc/dolly/entry", 64 * 1024)), { foreground: true });
|
|
1476
|
+
const ending = await entry().catch((error) => {
|
|
1477
|
+
console.error(error);
|
|
1478
|
+
return { status: 126, signal: 0, failure: error.reason ?? terminalFailureReason(error) };
|
|
1479
|
+
});
|
|
1480
|
+
host.dispose();
|
|
1481
|
+
self.postMessage({ type: "exited", ...ending });
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
self.postMessage({
|
|
1484
|
+
type: "error",
|
|
1485
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1486
|
+
stack: error instanceof Error ? error.stack ?? "" : ""
|
|
1487
|
+
});
|
|
1488
|
+
} finally {
|
|
1489
|
+
host?.dispose();
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// page/runtime-worker.mjs
|
|
1494
|
+
await runImage(async ({ image, files: given = {} }, host) => {
|
|
1495
|
+
const definition = imageDefinition(image);
|
|
1496
|
+
if (!definition) throw new Error("invalid Dolly image selection");
|
|
1497
|
+
const loading = host.restoresFilesystem() ? null : loadImageMetadata(image);
|
|
1498
|
+
loading?.catch(() => {
|
|
1499
|
+
});
|
|
1500
|
+
host.require(definition.hostRequirements);
|
|
1501
|
+
await host.admit(definition.hostRequirements);
|
|
1502
|
+
return {
|
|
1503
|
+
name: image,
|
|
1504
|
+
prepare({ files }) {
|
|
1505
|
+
files.write("/etc/dolly/recipe.locator", siteReference(definition.dollyfile));
|
|
1506
|
+
},
|
|
1507
|
+
async restore({ kernel, memory, files, stage: stage2 }) {
|
|
1508
|
+
if (await host.restoreFilesystem({ kernel, memory, stage: stage2 })) return { status: 0 };
|
|
1509
|
+
const metadata = await (loading ?? loadImageMetadata(image));
|
|
1510
|
+
files.write("/etc/dolly/image.manifest", `${metadata.manifest.join("\n")}
|
|
1511
|
+
`);
|
|
1512
|
+
stage2("loading precompiled userspace snapshot...");
|
|
1513
|
+
const capacity = 1024 * 1024;
|
|
1514
|
+
const address = kernel.dolly_snapshot_restore_address(BigInt(capacity));
|
|
1515
|
+
const staging = () => {
|
|
1516
|
+
const range = checkedMemoryRange(memory, address, capacity);
|
|
1517
|
+
return new Uint8Array(memory.buffer, range.address, range.size);
|
|
1518
|
+
};
|
|
1519
|
+
if (kernel.dolly_bootstrap_snapshot_begin(BigInt(metadata.byteLength)) !== 0)
|
|
1520
|
+
throw new Error("Dolly snapshot stream initialization failed");
|
|
1521
|
+
let loaded = 0, reported = 0;
|
|
1522
|
+
await streamPackagedSystemSnapshot(metadata, (bytes) => {
|
|
1523
|
+
for (let offset = 0; offset < bytes.length; offset += capacity) {
|
|
1524
|
+
const chunk = bytes.subarray(offset, offset + capacity);
|
|
1525
|
+
staging().set(chunk);
|
|
1526
|
+
if (kernel.dolly_snapshot_stream_write(BigInt(chunk.length), 0) !== 0)
|
|
1527
|
+
throw new Error("Dolly rejected a snapshot stream record");
|
|
1528
|
+
}
|
|
1529
|
+
loaded += bytes.length;
|
|
1530
|
+
if (performance.now() - reported >= 1e3) {
|
|
1531
|
+
const mib = 1024 * 1024;
|
|
1532
|
+
stage2(`loading userspace: ${Math.min(Math.floor(loaded / mib), Math.ceil(metadata.byteLength / mib))} / ${Math.ceil(metadata.byteLength / mib)} MiB`);
|
|
1533
|
+
reported = performance.now();
|
|
1534
|
+
}
|
|
1535
|
+
}, () => {
|
|
1536
|
+
if (kernel.dolly_snapshot_stream_write(0n, 1) !== 0)
|
|
1537
|
+
throw new Error("Dolly rejected an incomplete snapshot part");
|
|
1538
|
+
});
|
|
1539
|
+
const status = kernel.dolly_bootstrap_snapshot_end();
|
|
1540
|
+
if (status === 0) for (const [path, contents] of Object.entries(given)) files.write(path, contents);
|
|
1541
|
+
return { status };
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
});
|