relic-mcp 0.14.4 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +67 -1
  4. package/dist/cover/cover.js +211 -40
  5. package/dist/cover/frame.html +104 -6
  6. package/dist/cover/pdf.worker.mjs +73 -3
  7. package/dist/cover/styles.css +698 -27
  8. package/dist/native-secure-open.d.ts +1 -1
  9. package/dist/native-secure-open.js +8 -9
  10. package/dist/program/index.html +4 -0
  11. package/dist/program/index.js +35403 -0
  12. package/dist/relic-mcp.js +7538 -1919
  13. package/mcp-servers.json +2 -2
  14. package/native/build.mjs +164 -0
  15. package/native/secure-enclave/relic-se.swift +243 -0
  16. package/native/secure-open.c +39 -39
  17. package/native/secure-open.d.ts +1 -1
  18. package/package.json +2 -2
  19. package/prebuilds/darwin-arm64/build-info.json +2 -2
  20. package/prebuilds/darwin-arm64/relic-se +0 -0
  21. package/prebuilds/darwin-arm64/relic-se-build-info.json +44 -0
  22. package/prebuilds/darwin-arm64/secure_open.node +0 -0
  23. package/prebuilds/darwin-x64/build-info.json +2 -2
  24. package/prebuilds/darwin-x64/relic-se +0 -0
  25. package/prebuilds/darwin-x64/relic-se-build-info.json +44 -0
  26. package/prebuilds/darwin-x64/secure_open.node +0 -0
  27. package/prebuilds/linux-arm64-gnu/build-info.json +2 -2
  28. package/prebuilds/linux-arm64-gnu/secure_open.node +0 -0
  29. package/prebuilds/linux-x64-gnu/build-info.json +2 -2
  30. package/prebuilds/linux-x64-gnu/secure_open.node +0 -0
  31. package/scripts/prove-packed-native.mjs +42 -3
  32. package/skills/relic/SKILL.md +39 -0
  33. package/src/comments.ts +138 -24
  34. package/src/cover/chrome.ts +46 -18
  35. package/src/cover/inputs.ts +1 -1
  36. package/src/cover/render.ts +9 -0
  37. package/src/decisions.ts +13 -7
  38. package/src/device.ts +1196 -0
  39. package/src/files.ts +4 -3
  40. package/src/index.ts +27 -0
  41. package/src/inventory.ts +1 -1
  42. package/src/native-secure-open.ts +13 -13
  43. package/src/otio-bundle.ts +433 -152
  44. package/src/private.ts +1322 -0
  45. package/src/program/browser.ts +124 -0
  46. package/src/program/build.ts +1339 -0
  47. package/src/program/confine.ts +162 -0
  48. package/src/program/jail-probe.ts +192 -0
  49. package/src/program/policy.ts +128 -0
  50. package/src/program/ramdisk.ts +371 -0
  51. package/src/program/render.ts +793 -0
  52. package/src/program/report.ts +92 -0
  53. package/src/publish.ts +202 -33
  54. package/src/republish.ts +69 -6
  55. package/src/resolve-anchor.ts +9 -6
  56. package/src/secure-enclave.ts +228 -0
  57. package/src/server.ts +588 -12
  58. package/src/share.ts +35 -1
  59. package/src/state.ts +201 -47
  60. package/src/upload.ts +140 -0
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "relic",
12
12
  "description": "Publish a file from your machine as an encrypted, shareable link. The agent encrypts locally, uploads only ciphertext, and hands back a URL whose fragment holds the key, so the service stores something it cannot read.",
13
- "version": "0.14.4",
13
+ "version": "0.16.0",
14
14
  "source": "./",
15
15
  "author": {
16
16
  "name": "The Bushido Collective",
@@ -21,6 +21,6 @@
21
21
  }
22
22
  ],
23
23
  "metadata": {
24
- "version": "0.14.4"
24
+ "version": "0.16.0"
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relic",
3
- "version": "0.14.4",
3
+ "version": "0.16.0",
4
4
  "description": "Publish a file from your machine as an encrypted, shareable link. The agent encrypts locally, uploads only ciphertext, and hands back a URL whose fragment holds the key, so the service stores something it cannot read.",
5
5
  "mcpServers": "./mcp-servers.json",
6
6
  "author": {
package/README.md CHANGED
@@ -100,7 +100,10 @@ created `0600` inside a `0700` directory, and redirectable with
100
100
  Copying the state file to another machine moves the ability with it.
101
101
  - **The file holds the key and the token.** They are never printed, logged,
102
102
  or returned by any tool result. The service stores only a SHA-256 of the
103
- token, so it cannot reconstruct either one.
103
+ token, so it cannot reconstruct either one. On this machine the version keys
104
+ are plaintext in the file. Other users cannot read it (`0600`), but any
105
+ process running as this user can, so the file is protected only from other
106
+ users and from the service.
104
107
  - **Losing the file changes nothing for existing links**; it only ends that
105
108
  machine's ability to update those relics.
106
109
  - **A takedown is permanent.** A removed relic answers `relic_removed`
@@ -210,6 +213,66 @@ flags (`--host-resolver-rules=MAP * ~NOTFOUND`,
210
213
  `--webrtc-ip-handling-policy=disable_non_proxied_udp`, `--disable-quic`) so no
211
214
  relic plaintext leaves the machine during rendering.
212
215
 
216
+ ### OTIO programs
217
+
218
+ Publishing or republishing an `.otioz` archive, or a `.otiod` directory packaged
219
+ into one, also renders each OpenTimelineIO timeline to an H.264/AAC MP4 in the
220
+ same local headless browser shell. The render runs in memory: the browser context is
221
+ off-the-record, the HTTP cache is disabled, and the page can reach only the
222
+ archive's own media through the virtual origin
223
+ (`https://program.relic.invalid`). The MP4s and a `relic/program.json` manifest
224
+ go into the archive before encryption, so the program is encrypted like the
225
+ source. `content.otio` and every media entry keep their bytes.
226
+
227
+ A timeline that cannot be rendered is published focus-only, and the publish
228
+ result says why for each one: no browser, no H.264/AAC encoder, an unsupported
229
+ effect, codec, transition, schema, or timing, missing media, or the size cap.
230
+ The result's `program` field reports the status and every timeline. A bare
231
+ `.otio` file has no archive to carry a program and publishes as given. No
232
+ browser or encoder is installed or downloaded, and nothing is sent to a remote
233
+ renderer.
234
+
235
+ Programs render only on macOS in this version. On Linux no browser starts, and
236
+ every OTIO timeline is published focus-only with the reason "program rendering is
237
+ confined only on macOS in this version, so this timeline has no program".
238
+
239
+ On macOS each publish creates a private RAM volume and runs the headless shell
240
+ under one deny-default macOS sandbox profile that the kernel applies to every
241
+ process the shell starts. Chromium's own sandbox is off, since it cannot start
242
+ inside that profile, so the profile is the containment. The profile allows
243
+ writes only to the RAM volume; reads only of the system libraries, fonts, and
244
+ the shell's own directory; no network; no home directory; and executing only the
245
+ shell. GPU and hardware video decoding are disabled. Before every launch a probe
246
+ runs unconfined,
247
+ which must succeed, and then confined, where each outside write, read, exec and
248
+ connection must be refused and the inside write must succeed. Any deviation, or
249
+ a volume that cannot be created, means no browser starts and every timeline is
250
+ focus-only with that reason. The browser gets a minimal environment: `HOME`,
251
+ temporary and cache directories inside the volume, and a fixed `PATH`. Nothing
252
+ else is inherited.
253
+
254
+ After rendering, the process unmounts and detaches its volume and removes only
255
+ the device and directory it created. SIGINT and SIGTERM abort active renders and
256
+ wait for browser and volume cleanup before exit. SIGKILL, power loss, and other
257
+ forced termination cannot run cleanup; the RAM mount may persist until system
258
+ cleanup or reboot. If storage this process made cannot be released, the publish
259
+ is refused. Reasons in the archive are fixed sentences,
260
+ never local paths or environment values. The archive always carries its
261
+ manifest, so a reader never falls back to a full composite the publisher did not
262
+ select. An archive that already contains `relic/` entries, a bad
263
+ `RELIC_PROGRAM_MAX_BYTES`, an unsafe entry name, or a manifest that cannot fit
264
+ the cap is refused with an exact reason rather than published without one.
265
+
266
+ What the sandbox does not cover: with Chromium's sandbox off, one profile covers
267
+ every process the shell starts, so an exploit in a media decoder reaches
268
+ everything that profile allows, though nothing beyond it. Seatbelt does not filter
269
+ every system call, and its profile language is private and deprecated, so a
270
+ future macOS release may change it; the per-launch probe then fails closed. The
271
+ macOS crash reporter sits outside the profile and can write crash details,
272
+ including strings a compromised process chose, to `~/Library/Logs/DiagnosticReports`.
273
+ No claim is made about swap. Anything readable inside the volume, such as the
274
+ host name, can appear in the published MP4.
275
+
213
276
  ## Tools
214
277
 
215
278
  | Tool | Input | Notes |
@@ -238,6 +301,9 @@ relic plaintext leaves the machine during rendering.
238
301
  | `RELIC_MCP_PORT`, `RELIC_MCP_HOST` | HTTP bind. Defaults to `127.0.0.1:7333`. |
239
302
  | `RELIC_MCP_ALLOWED_ORIGINS` | Comma-separated `Origin` allowlist for HTTP. |
240
303
  | `RELIC_CHROME_PATH` | Explicit path to Chrome, Chromium, or Edge binary for cover rendering. Set to `none` or `disabled` to skip covers. |
304
+ | `RELIC_OTIO_CHROME_PATH` | Path to a `chrome-headless-shell` build whose file layout matches the r1228 headless shell, for OTIO program rendering on macOS. Unset, the newest one under `~/.omp/owned-shells` is used. Any other file or layout is refused. Full Chrome is never used for programs. Set to `none` or `disabled` to publish OTIO without programs. |
305
+ | `RELIC_PROGRAM_MAX_BYTES` | Lowers the program budget for the whole archive, never raises it. Must be a positive integer. A timeline that does not fit is focus-only with the reason `size-cap-exceeded`. |
306
+ | `RELIC_PROGRAM_ENCODER_OFF` | `1` makes the page report its H.264/AAC encoder as absent. Used by proofs, to exercise the focus-only path. |
241
307
  | `RELIC_FFMPEG_PATH` | Path to ffmpeg binary for generating video frame and audio waveform/artwork cover images. |
242
308
 
243
309
  ## Protocol
@@ -1202,6 +1202,68 @@ var require_build = __commonJS(function(exports) {
1202
1202
  exports.default = LinesAndColumns;
1203
1203
  });
1204
1204
 
1205
+ // ../relic-viewer/src/content.ts
1206
+ var CONTENT_BATCH_BYTES = 8 * 1024 * 1024;
1207
+ var CONTENT_HEAD_BYTES = 65536;
1208
+ var CONTENT_TAIL_BYTES = 65557;
1209
+ var MAX_CONTENT_READ_BYTES = 16 * 1024 * 1024;
1210
+ var DOCUMENT_RENDER_LIMIT_BYTES = 100 * 1024 * 1024;
1211
+
1212
+ class ContentStoreError extends Error {
1213
+ constructor(cause) {
1214
+ super("The browser could not hold the decrypted content.", { cause });
1215
+ this.name = "ContentStoreError";
1216
+ }
1217
+ }
1218
+ function sliceBlob(blob, start, end, type) {
1219
+ try {
1220
+ return blob.slice(start, end, type);
1221
+ } catch (cause) {
1222
+ throw new ContentStoreError(cause);
1223
+ }
1224
+ }
1225
+ async function readBlob(blob) {
1226
+ try {
1227
+ const bytes = new Uint8Array(await blob.arrayBuffer());
1228
+ if (bytes.length !== blob.size)
1229
+ throw new Error("short Blob read");
1230
+ return bytes;
1231
+ } catch (cause) {
1232
+ throw new ContentStoreError(cause);
1233
+ }
1234
+ }
1235
+ function contentFromParts(base, head, tail) {
1236
+ return {
1237
+ size: base.size,
1238
+ head,
1239
+ tail,
1240
+ blob(type) {
1241
+ return sliceBlob(base, 0, base.size, type);
1242
+ },
1243
+ async read(start, end) {
1244
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > base.size || end - start > MAX_CONTENT_READ_BYTES) {
1245
+ throw new RangeError("Invalid or oversized content read");
1246
+ }
1247
+ return readBlob(sliceBlob(base, start, end));
1248
+ },
1249
+ async readWhole(limit) {
1250
+ if (!Number.isSafeInteger(limit) || limit < 0 || limit > DOCUMENT_RENDER_LIMIT_BYTES) {
1251
+ throw new RangeError("Invalid whole-content read limit");
1252
+ }
1253
+ if (base.size > limit)
1254
+ return;
1255
+ return readBlob(base);
1256
+ }
1257
+ };
1258
+ }
1259
+ async function contentFromBlob(blob) {
1260
+ const [head, tail] = await Promise.all([
1261
+ readBlob(sliceBlob(blob, 0, Math.min(blob.size, CONTENT_HEAD_BYTES))),
1262
+ readBlob(sliceBlob(blob, Math.max(0, blob.size - CONTENT_TAIL_BYTES), blob.size))
1263
+ ]);
1264
+ return contentFromParts(blob, head, tail);
1265
+ }
1266
+
1205
1267
  // ../relic-viewer/src/markdown.ts
1206
1268
  function escapeHtml(value) {
1207
1269
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1240,7 +1302,7 @@ function inline(source) {
1240
1302
  });
1241
1303
  return out.split(SENTINEL).join("");
1242
1304
  }
1243
- function renderMarkdown(source) {
1305
+ function parseMarkdown(source) {
1244
1306
  const lines = source.replace(/\r\n?/g, `
1245
1307
  `).split(`
1246
1308
  `);
@@ -1250,7 +1312,7 @@ function renderMarkdown(source) {
1250
1312
  const flushParagraph = () => {
1251
1313
  if (paragraph.length === 0)
1252
1314
  return;
1253
- out.push(`<p>${inline(paragraph.join(" "))}</p>`);
1315
+ out.push({ kind: "paragraph", text: paragraph.join(" ") });
1254
1316
  paragraph = [];
1255
1317
  };
1256
1318
  while (index < lines.length) {
@@ -1266,22 +1328,24 @@ function renderMarkdown(source) {
1266
1328
  index++;
1267
1329
  }
1268
1330
  index++;
1269
- const attribute = language.length > 0 ? ` data-language="${escapeHtml(language)}"` : "";
1270
- out.push(`<pre${attribute}><code>${escapeHtml(body.join(`
1271
- `))}</code></pre>`);
1331
+ out.push({ kind: "code", language, text: body.join(`
1332
+ `) });
1272
1333
  continue;
1273
1334
  }
1274
1335
  const heading = /^(#{1,6})\s+(.*)$/.exec(line);
1275
1336
  if (heading !== null) {
1276
1337
  flushParagraph();
1277
- const level = heading[1].length;
1278
- out.push(`<h${level}>${inline(heading[2])}</h${level}>`);
1338
+ out.push({
1339
+ kind: "heading",
1340
+ level: heading[1].length,
1341
+ text: heading[2]
1342
+ });
1279
1343
  index++;
1280
1344
  continue;
1281
1345
  }
1282
1346
  if (/^\s*(-\s*-\s*-|\*\s*\*\s*\*|_\s*_\s*_)[-*_\s]*$/.test(line)) {
1283
1347
  flushParagraph();
1284
- out.push("<hr>");
1348
+ out.push({ kind: "rule" });
1285
1349
  index++;
1286
1350
  continue;
1287
1351
  }
@@ -1292,8 +1356,8 @@ function renderMarkdown(source) {
1292
1356
  quoted.push(lines[index].replace(/^\s*>\s?/, ""));
1293
1357
  index++;
1294
1358
  }
1295
- out.push(`<blockquote>${renderMarkdown(quoted.join(`
1296
- `))}</blockquote>`);
1359
+ out.push({ kind: "quote", blocks: parseMarkdown(quoted.join(`
1360
+ `)) });
1297
1361
  continue;
1298
1362
  }
1299
1363
  if (/^\s*([-*+]|\d+\.)\s+/.test(line)) {
@@ -1311,9 +1375,7 @@ function renderMarkdown(source) {
1311
1375
  }
1312
1376
  index++;
1313
1377
  }
1314
- const tag = ordered ? "ol" : "ul";
1315
- const body = items.map((item) => `<li>${inline(item)}</li>`).join("");
1316
- out.push(`<${tag}>${body}</${tag}>`);
1378
+ out.push({ kind: "list", ordered, items });
1317
1379
  continue;
1318
1380
  }
1319
1381
  if (line.includes("|") && isTableSeparator(lines[index + 1])) {
@@ -1325,7 +1387,7 @@ function renderMarkdown(source) {
1325
1387
  rows.push(splitRow(lines[index]));
1326
1388
  index++;
1327
1389
  }
1328
- out.push('<div class="md-scroll"><table><thead><tr>' + header.map((cell) => `<th>${inline(cell)}</th>`).join("") + "</tr></thead><tbody>" + rows.map((row) => `<tr>${row.map((cell) => `<td>${inline(cell)}</td>`).join("")}</tr>`).join("") + "</tbody></table></div>");
1390
+ out.push({ kind: "table", header, rows });
1329
1391
  continue;
1330
1392
  }
1331
1393
  if (line.trim().length === 0) {
@@ -1337,7 +1399,34 @@ function renderMarkdown(source) {
1337
1399
  index++;
1338
1400
  }
1339
1401
  flushParagraph();
1340
- return out.join(`
1402
+ return out;
1403
+ }
1404
+ function renderBlock(block) {
1405
+ switch (block.kind) {
1406
+ case "heading":
1407
+ return `<h${block.level}>${inline(block.text)}</h${block.level}>`;
1408
+ case "paragraph":
1409
+ return `<p>${inline(block.text)}</p>`;
1410
+ case "code": {
1411
+ const attribute = block.language.length > 0 ? ` data-language="${escapeHtml(block.language)}"` : "";
1412
+ return `<pre${attribute}><code>${escapeHtml(block.text)}</code></pre>`;
1413
+ }
1414
+ case "rule":
1415
+ return "<hr>";
1416
+ case "quote":
1417
+ return `<blockquote>${block.blocks.map(renderBlock).join(`
1418
+ `)}</blockquote>`;
1419
+ case "list": {
1420
+ const tag = block.ordered ? "ol" : "ul";
1421
+ const body = block.items.map((item) => `<li>${inline(item)}</li>`);
1422
+ return `<${tag}>${body.join("")}</${tag}>`;
1423
+ }
1424
+ case "table":
1425
+ return '<div class="md-scroll"><table><thead><tr>' + block.header.map((cell) => `<th>${inline(cell)}</th>`).join("") + "</tr></thead><tbody>" + block.rows.map((row) => `<tr>${row.map((cell) => `<td>${inline(cell)}</td>`).join("")}</tr>`).join("") + "</tbody></table></div>";
1426
+ }
1427
+ }
1428
+ function renderMarkdown(source) {
1429
+ return parseMarkdown(source).map(renderBlock).join(`
1341
1430
  `);
1342
1431
  }
1343
1432
  function isTableSeparator(line) {
@@ -44726,6 +44815,81 @@ globalThis.pdfjsLib = {
44726
44815
  XfaLayer
44727
44816
  };
44728
44817
 
44818
+ // ../relic-viewer/src/pdf.ts
44819
+ var PDF_INITIAL_BYTES = 1024 * 1024;
44820
+ var PDF_RANGE_CHUNK_SIZE = 65536;
44821
+
44822
+ class RelicPdfRangeTransport extends PDFDataRangeTransport {
44823
+ content;
44824
+ onError;
44825
+ aborted = false;
44826
+ failed = false;
44827
+ constructor(content, initialData, onError) {
44828
+ super(content.size, initialData);
44829
+ this.content = content;
44830
+ this.onError = onError;
44831
+ }
44832
+ requestDataRange(begin, end) {
44833
+ if (this.aborted || this.failed)
44834
+ return;
44835
+ this.readRange(begin, end).then((bytes) => {
44836
+ if (!this.aborted && !this.failed)
44837
+ this.onDataRange(begin, bytes);
44838
+ }).catch((error) => {
44839
+ if (this.aborted || this.failed)
44840
+ return;
44841
+ this.failed = true;
44842
+ this.onError(error);
44843
+ });
44844
+ }
44845
+ abort() {
44846
+ this.aborted = true;
44847
+ }
44848
+ async readRange(begin, end) {
44849
+ if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || end > this.content.size) {
44850
+ throw new RangeError("Invalid PDF range");
44851
+ }
44852
+ const length = end - begin;
44853
+ if (length <= MAX_CONTENT_READ_BYTES) {
44854
+ const bytes = await this.content.read(begin, end);
44855
+ if (bytes.byteLength !== length)
44856
+ throw new Error("Short PDF range read");
44857
+ return bytes;
44858
+ }
44859
+ const bytes = new Uint8Array(length);
44860
+ for (let start = begin;start < end; start += MAX_CONTENT_READ_BYTES) {
44861
+ if (this.aborted)
44862
+ return new Uint8Array(0);
44863
+ const stop = Math.min(start + MAX_CONTENT_READ_BYTES, end);
44864
+ const chunk = await this.content.read(start, stop);
44865
+ if (chunk.byteLength !== stop - start)
44866
+ throw new Error("Short PDF range read");
44867
+ bytes.set(chunk, start - begin);
44868
+ }
44869
+ return bytes;
44870
+ }
44871
+ }
44872
+ async function createPdfLoadingTask(content, signal) {
44873
+ const initialData = await content.read(0, Math.min(content.size, PDF_INITIAL_BYTES));
44874
+ if (signal?.aborted)
44875
+ throw new Error("PDF loading cancelled");
44876
+ if (initialData.byteLength !== Math.min(content.size, PDF_INITIAL_BYTES))
44877
+ throw new Error("Short PDF initial read");
44878
+ const { promise: failure, reject: rejectFailure } = Promise.withResolvers();
44879
+ failure.catch(() => {
44880
+ return;
44881
+ });
44882
+ const range = new RelicPdfRangeTransport(content, initialData, rejectFailure);
44883
+ const task = getDocument({
44884
+ range,
44885
+ length: content.size,
44886
+ rangeChunkSize: PDF_RANGE_CHUNK_SIZE,
44887
+ disableAutoFetch: true,
44888
+ disableStream: true
44889
+ });
44890
+ return { task, failure };
44891
+ }
44892
+
44729
44893
  // ../relic-viewer/src/cover/pdf-trim.ts
44730
44894
  var PDF_GUTTER_PX = 20;
44731
44895
  var PDF_CANVAS_WIDTH = 600;
@@ -44778,21 +44942,21 @@ function trimPdfPage(ink, pageWidth) {
44778
44942
  // ../relic-viewer/src/cover/pdf.ts
44779
44943
  var PDF_CANVAS_HEIGHT = 315;
44780
44944
  var PDF_DEVICE_SCALE = 2;
44781
- var workerInitialized = false;
44782
44945
  function ensurePdfWorker() {
44783
- if (!workerInitialized) {
44784
- GlobalWorkerOptions.workerPort = new Worker("/pdf.worker.mjs", {
44785
- type: "module"
44786
- });
44787
- workerInitialized = true;
44788
- }
44946
+ GlobalWorkerOptions.workerSrc = "/pdf.worker.mjs";
44789
44947
  }
44790
- async function renderPdfCover(bytes, root) {
44948
+ async function renderPdfCover(content, root) {
44791
44949
  ensurePdfWorker();
44792
- let pdfDoc;
44793
44950
  try {
44794
- const loadingTask = getDocument({ data: bytes });
44795
- pdfDoc = await loadingTask.promise;
44951
+ const { task, failure } = await createPdfLoadingTask(content);
44952
+ try {
44953
+ const pdfDoc = await Promise.race([task.promise, failure]);
44954
+ return await renderPdfCoverDocument(pdfDoc, root, failure);
44955
+ } finally {
44956
+ await task.destroy().catch(() => {
44957
+ return;
44958
+ });
44959
+ }
44796
44960
  } catch (err) {
44797
44961
  return {
44798
44962
  ok: false,
@@ -44800,9 +44964,11 @@ async function renderPdfCover(bytes, root) {
44800
44964
  detail: err instanceof Error ? err.message : String(err)
44801
44965
  };
44802
44966
  }
44967
+ }
44968
+ async function renderPdfCoverDocument(pdfDoc, root, failure) {
44803
44969
  let page;
44804
44970
  try {
44805
- page = await pdfDoc.getPage(1);
44971
+ page = await Promise.race([pdfDoc.getPage(1), failure]);
44806
44972
  } catch (err) {
44807
44973
  return {
44808
44974
  ok: false,
@@ -44827,11 +44993,14 @@ async function renderPdfCover(bytes, root) {
44827
44993
  };
44828
44994
  }
44829
44995
  try {
44830
- await page.render({
44831
- canvasContext: measureCtx,
44832
- viewport: refViewport,
44833
- canvas: measureCanvas
44834
- }).promise;
44996
+ await Promise.race([
44997
+ page.render({
44998
+ canvasContext: measureCtx,
44999
+ viewport: refViewport,
45000
+ canvas: measureCanvas
45001
+ }).promise,
45002
+ failure
45003
+ ]);
44835
45004
  } catch (err) {
44836
45005
  return {
44837
45006
  ok: false,
@@ -44864,11 +45033,14 @@ async function renderPdfCover(bytes, root) {
44864
45033
  };
44865
45034
  }
44866
45035
  try {
44867
- await page.render({
44868
- canvasContext: renderCtx,
44869
- viewport: finalViewport,
44870
- canvas: renderCanvas
44871
- }).promise;
45036
+ await Promise.race([
45037
+ page.render({
45038
+ canvasContext: renderCtx,
45039
+ viewport: finalViewport,
45040
+ canvas: renderCanvas
45041
+ }).promise,
45042
+ failure
45043
+ ]);
44872
45044
  } catch (err) {
44873
45045
  return {
44874
45046
  ok: false,
@@ -44896,7 +45068,7 @@ async function fetchInputBlob(input) {
44896
45068
  if (!res.ok) {
44897
45069
  throw new Error(`Failed to fetch input part ${i}: ${res.status}`);
44898
45070
  }
44899
- parts.push(await res.arrayBuffer());
45071
+ parts.push(await res.blob());
44900
45072
  }
44901
45073
  return new Blob(parts, { type: input.mimetype });
44902
45074
  }
@@ -45084,8 +45256,7 @@ async function renderKind(request, root) {
45084
45256
  }
45085
45257
  }
45086
45258
  case "pdf": {
45087
- const arrayBuffer = await blob.arrayBuffer();
45088
- return renderPdfCover(new Uint8Array(arrayBuffer), root);
45259
+ return renderPdfCover(await contentFromBlob(blob), root);
45089
45260
  }
45090
45261
  }
45091
45262
  }
@@ -11390,15 +11390,91 @@ var MAX_HEADER_BYTES = 2 + (2 + MAX_FILENAME_BYTES + 2 + MAX_MIMETYPE_BYTES + 8
11390
11390
  var encoder = new TextEncoder;
11391
11391
  var decoder = new TextDecoder("utf-8", { fatal: false });
11392
11392
 
11393
+ // ../relic-format/src/limits.ts
11394
+ var PLAINTEXT_CAP_BYTES = 500 * 1024 * 1024;
11395
+
11393
11396
  // ../relic-format/src/rfc8188.ts
11394
11397
  var TAG_BYTES = 16;
11395
11398
  var DELIMITER_BYTES = 1;
11396
11399
  var RECORD_OVERHEAD = TAG_BYTES + DELIMITER_BYTES;
11397
11400
  var CEK_INFO = new TextEncoder().encode("Content-Encoding: aes128gcm\x00");
11398
11401
  var NONCE_INFO = new TextEncoder().encode("Content-Encoding: nonce\x00");
11399
-
11400
- // ../relic-format/src/container.ts
11401
- var PLAINTEXT_CAP_BYTES = 100 * 1024 * 1024;
11402
+ // ../relic-format/src/crc32c.ts
11403
+ var CRC32C_TABLE = new Uint32Array(256);
11404
+ for (let index = 0;index < 256; index++) {
11405
+ let value = index;
11406
+ for (let bit = 0;bit < 8; bit++) {
11407
+ value = value & 1 ? value >>> 1 ^ 2197175160 : value >>> 1;
11408
+ }
11409
+ CRC32C_TABLE[index] = value >>> 0;
11410
+ }
11411
+ // ../relic-format/src/sha256.ts
11412
+ var K = new Uint32Array([
11413
+ 1116352408,
11414
+ 1899447441,
11415
+ 3049323471,
11416
+ 3921009573,
11417
+ 961987163,
11418
+ 1508970993,
11419
+ 2453635748,
11420
+ 2870763221,
11421
+ 3624381080,
11422
+ 310598401,
11423
+ 607225278,
11424
+ 1426881987,
11425
+ 1925078388,
11426
+ 2162078206,
11427
+ 2614888103,
11428
+ 3248222580,
11429
+ 3835390401,
11430
+ 4022224774,
11431
+ 264347078,
11432
+ 604807628,
11433
+ 770255983,
11434
+ 1249150122,
11435
+ 1555081692,
11436
+ 1996064986,
11437
+ 2554220882,
11438
+ 2821834349,
11439
+ 2952996808,
11440
+ 3210313671,
11441
+ 3336571891,
11442
+ 3584528711,
11443
+ 113926993,
11444
+ 338241895,
11445
+ 666307205,
11446
+ 773529912,
11447
+ 1294757372,
11448
+ 1396182291,
11449
+ 1695183700,
11450
+ 1986661051,
11451
+ 2177026350,
11452
+ 2456956037,
11453
+ 2730485921,
11454
+ 2820302411,
11455
+ 3259730800,
11456
+ 3345764771,
11457
+ 3516065817,
11458
+ 3600352804,
11459
+ 4094571909,
11460
+ 275423344,
11461
+ 430227734,
11462
+ 506948616,
11463
+ 659060556,
11464
+ 883997877,
11465
+ 958139571,
11466
+ 1322822218,
11467
+ 1537002063,
11468
+ 1747873779,
11469
+ 1955562222,
11470
+ 2024104815,
11471
+ 2227730452,
11472
+ 2361852424,
11473
+ 2428436474,
11474
+ 2756734187,
11475
+ 3204031479,
11476
+ 3329325298
11477
+ ]);
11402
11478
  // ../relic-format/src/id.ts
11403
11479
  var RESERVED_SEGMENTS = [
11404
11480
  "abuse",
@@ -11431,7 +11507,10 @@ function foldAliases(lowered) {
11431
11507
  return out;
11432
11508
  }
11433
11509
  // ../relic-format/src/otio-zip.ts
11434
- var MAX_EXPANDED_ZIP_BYTES = 100 * 1024 * 1024;
11510
+ var MAX_INFLATED_ZIP_BYTES = 100 * 1024 * 1024;
11511
+ var OTIO_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
11512
+ var MAX_ZIP_DIRECTORY_BYTES = 16 * 1024 * 1024;
11513
+ var ZIP_READ_CHUNK_BYTES = 8 * 1024 * 1024;
11435
11514
  var MAX_OTIO_BUNDLE_MEDIA_ENTRIES = 65535 - 3;
11436
11515
  var CRC_TABLE = new Uint32Array(256);
11437
11516
  for (let i = 0;i < 256; i++) {
@@ -11453,6 +11532,12 @@ var documentSequences = new WeakMap;
11453
11532
 
11454
11533
  // ../relic-format/src/otio-identity.ts
11455
11534
  var mediaWitnessCache = new WeakMap;
11535
+ // ../relic-format/src/otio-program.ts
11536
+ var MAX_OTIO_PROGRAM_MANIFEST_BYTES = 16 * 1024 * 1024;
11537
+ var OTIO_PROGRAM_MAX_BYTES = 100 * 1024 * 1024;
11538
+ var UTF8 = new TextEncoder;
11539
+ var MAX_FRAMES = Number.MAX_SAFE_INTEGER;
11540
+ var MAX_AUDIO_SAMPLES = Number.MAX_SAFE_INTEGER;
11456
11541
  // ../relic-format/src/share.ts
11457
11542
  var SHARE_PROOF_INFO = new TextEncoder().encode("relic/share-proof/v1");
11458
11543
  // ../relic-viewer/src/sandbox.ts
@@ -11802,12 +11887,25 @@ function createScrollportWatch(onChange) {
11802
11887
  }
11803
11888
  function concealMarks(marks, watch, live = true) {
11804
11889
  watch.sync(marks.map((mark) => mark.node));
11805
- const merged = new Map;
11890
+ if (live) {
11891
+ for (const { node, subject } of marks) {
11892
+ if (subject === node && node.getAttribute(CONCEALED_ATTRIBUTE) === "hidden")
11893
+ node.removeAttribute(CONCEALED_ATTRIBUTE);
11894
+ }
11895
+ }
11896
+ const measured = [];
11806
11897
  for (const { node, subject } of marks) {
11807
11898
  const id = node.getAttribute("data-comment-id");
11808
11899
  if (id === null || id === "")
11809
11900
  continue;
11810
- const concealment = !live || !isRendered(subject) ? "hidden" : !watch.inView(node) ? "offscreen" : "shown";
11901
+ measured.push({
11902
+ node,
11903
+ id,
11904
+ concealment: !live || !isRendered(subject) ? "hidden" : !watch.inView(node) ? "offscreen" : "shown"
11905
+ });
11906
+ }
11907
+ const merged = new Map;
11908
+ for (const { node, id, concealment } of measured) {
11811
11909
  setConcealed(node, concealment === "shown" ? undefined : concealment);
11812
11910
  const previous = merged.get(id);
11813
11911
  merged.set(id, mergeReason(previous, concealment));