syncorejs 0.2.3 → 0.2.4

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 (69) hide show
  1. package/dist/_vendor/core/cli.d.mts.map +1 -1
  2. package/dist/_vendor/core/cli.mjs +272 -7
  3. package/dist/_vendor/core/cli.mjs.map +1 -1
  4. package/dist/_vendor/core/index.d.mts +3 -3
  5. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  6. package/dist/_vendor/core/runtime/devtools.mjs +131 -0
  7. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  8. package/dist/_vendor/core/runtime/functions.d.mts +3 -3
  9. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  10. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +1 -1
  11. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
  12. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +1 -1
  13. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
  14. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +5 -1
  15. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
  16. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +99 -13
  17. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
  18. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +38 -4
  19. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
  20. package/dist/_vendor/core/runtime/runtime.d.mts +65 -8
  21. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  22. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  23. package/dist/_vendor/core/transport.d.mts.map +1 -1
  24. package/dist/_vendor/core/transport.mjs +30 -5
  25. package/dist/_vendor/core/transport.mjs.map +1 -1
  26. package/dist/_vendor/devtools-protocol/index.d.ts +75 -1
  27. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  28. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  29. package/dist/_vendor/next/index.js +9 -1
  30. package/dist/_vendor/next/index.js.map +1 -1
  31. package/dist/_vendor/platform-expo/index.d.ts +1 -1
  32. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  33. package/dist/_vendor/platform-expo/index.js +6 -1
  34. package/dist/_vendor/platform-expo/index.js.map +1 -1
  35. package/dist/_vendor/platform-node/index.d.mts +2 -1
  36. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  37. package/dist/_vendor/platform-node/index.mjs +27 -2
  38. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  39. package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
  40. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  41. package/dist/_vendor/platform-web/external-change.d.ts +2 -2
  42. package/dist/_vendor/platform-web/external-change.js +2 -2
  43. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  44. package/dist/_vendor/platform-web/index.d.ts +13 -10
  45. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  46. package/dist/_vendor/platform-web/index.js +66 -10
  47. package/dist/_vendor/platform-web/index.js.map +1 -1
  48. package/dist/_vendor/platform-web/indexeddb.d.ts +3 -3
  49. package/dist/_vendor/platform-web/indexeddb.js +3 -3
  50. package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
  51. package/dist/_vendor/platform-web/opfs.d.ts +3 -1
  52. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
  53. package/dist/_vendor/platform-web/opfs.js +29 -3
  54. package/dist/_vendor/platform-web/opfs.js.map +1 -1
  55. package/dist/_vendor/platform-web/persistence.d.ts +31 -1
  56. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
  57. package/dist/_vendor/platform-web/persistence.js.map +1 -1
  58. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  59. package/dist/_vendor/platform-web/react.js +9 -1
  60. package/dist/_vendor/platform-web/react.js.map +1 -1
  61. package/dist/_vendor/react/index.d.ts +6 -5
  62. package/dist/_vendor/react/index.d.ts.map +1 -1
  63. package/dist/_vendor/react/index.js +6 -5
  64. package/dist/_vendor/react/index.js.map +1 -1
  65. package/dist/_vendor/svelte/index.d.ts +8 -6
  66. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  67. package/dist/_vendor/svelte/index.js +7 -5
  68. package/dist/_vendor/svelte/index.js.map +1 -1
  69. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";;;;UAkEiB,0BAAA;EACf,YAAA;EACA,gBAAgB;AAAA;AAAA,UAGD,aAAA;EACf,aAAA,GAAgB,0BAA0B;EAC1C,YAAA;EACA,gBAAA;AAAA;AAAA,UAGe,kBAAA;EACf,YAAA;EACA,yBAAA;EACA,WAAA;EACA,KAAA;AAAA;AAAA,KAGU,mBAAA;AAAA,iBASI,4BAAA,CACd,QAA6B,EAAnB,mBAAmB;AAAA,UA0Bd,sBAAA;EACf,QAAA,EAAU,mBAAmB;EAC7B,KAAA;AAAA;AAAA,UAGe,qBAAA;EACf,QAAA,EAAU,mBAAmB;EAC7B,OAAA;EACA,OAAA;EACA,OAAA;AAAA;AAAA,UAGQ,gBAAA;EACR,IAAA;EACA,IAAA;EACA,OAAA,GAAU,MAAA;EACV,YAAA,GAAe,MAAA;EACf,eAAA,GAAkB,MAAA;AAAA;AAAA,cAYP,oCAAA;AAAA,cACA,uBAAA,EAAyB,mBAAmB;AAAA,iBAuMnC,aAAA,CAAc,IAAA,cAAsB,OAAO;AAAA,iBAQ3C,UAAA,CAAW,GAAA,WAAc,OAAO;AAAA,iBAmShC,eAAA,CACpB,GAAA,UACA,OAAA,EAAS,sBAAA,GACR,OAAA,CAAQ,qBAAA;AAAA,iBA+RK,iBAAA,CACd,MAAA,EAAQ,qBAAqB,EAC7B,OAAA;AAAA,iBA6BoB,iBAAA,CAAkB,GAAA,WAAc,OAAO;AAAA,iBAQvC,wBAAA,CACpB,GAAA,UACA,iBAAA,WACC,OAAO,CAAC,mBAAA;AAAA,iBAgBW,qBAAA,CACpB,GAAA,WACC,OAAO,CAAC,mBAAA;AAAA,iBAwCW,eAAA,CACpB,GAAA,WACC,OAAO,CAAC,gBAAA;AAAA,iBAkKW,sBAAA,CACpB,GAAA,UACA,SAAA,UACA,UAAA,WACC,OAAO;AAAA,iBAmEY,sBAAA,CACpB,GAAA,UACA,SAAA,WACC,OAAO;AAAA,iBA2WM,0BAAA,CACd,MAAA,EAAQ,aAAA,GACP,0BAA0B;AAAA,iBAuBP,iBAAA,CAAkB,GAAA,WAAc,OAAO,CAAC,aAAA;AAAA,iBAwBxC,qBAAA,CACpB,GAAA,WACC,OAAA,CAAQ,eAAA,CAAA,aAAA,CAAc,MAAA,SAAe,eAAA,CAAA,kBAAA;AAAA,iBAgBlB,6BAAA,CACpB,GAAA,WACC,OAAO,CAAC,MAAA;AAAA,iBAeW,iBAAA,CACpB,GAAA,WACC,OAAA,CAAQ,eAAA,CAAA,aAAA,CAAc,MAAA,SAAe,eAAA,CAAA,kBAAA;AAAA,iBAmGlB,oBAAA,CACpB,GAAA,WACC,OAAO,CAAC,uBAAA;AAAA,iBAiBW,6BAAA,CACpB,GAAA,WACC,OAAO,CAAC,yBAAA;AAAA,iBAyBW,kBAAA,CAAmB,GAAA,WAAc,OAAA;EACrD,MAAA,EAAQ,eAAA,CAAA,aAAA,CAAc,MAAA,SAAe,eAAA,CAAA,kBAAA;EACrC,SAAA,EAAW,uBAAA;EACX,UAAA,EAAY,yBAAA;AAAA;AAAA,iBA0aQ,kBAAA,CACpB,GAAA,WACC,OAAO,CAAC,eAAA,CAAA,cAAA;AAAA,iBAaW,mBAAA,CACpB,GAAA,UACA,QAAA,EAAU,eAAA,CAAA,cAAA,GACT,OAAO;AAAA,iBASY,sBAAA,CACpB,SAAA,WACC,OAAO;AAAA,iBAeY,sBAAA,CACpB,GAAA,WACC,OAAO;AAAA,iBAoHY,UAAA,CAAW,QAAA,WAAmB,OAAO;AAAA,iBA+C3C,WAAA,CAAY,KAAc;AAAA,iBAOpB,gBAAA,CAAiB,IAAA,WAAe,OAAO;AAAA,iBAuB7C,OAAA,CAAQ,KAAa;AAAA,iBAkCf,WAAA,CAAY,OAAA;EAChC,GAAA;EACA,QAAA,EAAU,mBAAA;AAAA,IACR,OAAA,CAAQ,kBAAA;AAAA,iBAwnBU,sBAAA,CACpB,GAAA,UACA,QAAA,EAAU,mBAAA,GACT,OAAO;AAAA,iBAyKM,kBAAA,CACd,mBAAA,UACA,QAAgB"}
1
+ {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":"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"}
@@ -4,7 +4,8 @@ import { SyncoreRuntime } from "./runtime/runtime.mjs";
4
4
  import { createDevtoolsCommandHandler, createDevtoolsSubscriptionHost } from "./runtime/devtools.mjs";
5
5
  import { src_exports } from "./index.mjs";
6
6
  import { generateDevtoolsToken, isAllowedDashboardOrigin, isAuthorizedDashboardRequest, sanitizeDevtoolsToken } from "./devtools-auth.mjs";
7
- import { appendFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
+ import { appendFile, mkdir, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
8
+ import { randomUUID } from "node:crypto";
8
9
  import { createServer } from "node:http";
9
10
  import { connect } from "node:net";
10
11
  import path from "node:path";
@@ -36,6 +37,9 @@ const VALID_SYNCORE_TEMPLATES = [
36
37
  let pendingDevBootstrap;
37
38
  let devBootstrapInFlight = false;
38
39
  const PROJECT_TARGET_RUNTIME_ID = "syncore-project-target";
40
+ const STORAGE_ACCESS_TICKET_TTL_MS = 300 * 1e3;
41
+ const STORAGE_ACCESS_CHUNK_BYTES = 1024 * 1024;
42
+ const STORAGE_ACCESS_MAX_PREVIEW_BYTES = 8e4;
39
43
  program.name("syncorejs").description("Syncore local-first toolkit CLI").version("0.1.0");
40
44
  program.command("init").description("Scaffold Syncore in the current directory").option("--template <template>", `Template to scaffold (${VALID_SYNCORE_TEMPLATES.join(", ")}, or auto)`, "auto").option("--force", "Overwrite Syncore-managed files when they already exist").action(async (options) => {
41
45
  const cwd = process.cwd();
@@ -1161,6 +1165,19 @@ var HubFileStorageAdapter = class {
1161
1165
  return null;
1162
1166
  }
1163
1167
  }
1168
+ async readRange(id, offset, length) {
1169
+ let handle;
1170
+ try {
1171
+ handle = await open(this.filePath(id), "r");
1172
+ const buffer = Buffer.alloc(Math.max(length, 0));
1173
+ const result = await handle.read(buffer, 0, buffer.byteLength, Math.max(offset, 0));
1174
+ return buffer.subarray(0, result.bytesRead);
1175
+ } catch {
1176
+ return null;
1177
+ } finally {
1178
+ await handle?.close();
1179
+ }
1180
+ }
1164
1181
  async delete(id) {
1165
1182
  await rm(this.filePath(id), { force: true });
1166
1183
  }
@@ -1252,6 +1269,11 @@ async function createProjectTargetBackend(cwd, externalChangeSignal) {
1252
1269
  driver,
1253
1270
  storage: new HubFileStorageAdapter(storageDirectory),
1254
1271
  platform: "project",
1272
+ runtimeCapabilities: { storage: {
1273
+ available: true,
1274
+ protocol: "file",
1275
+ supportsRange: true
1276
+ } },
1255
1277
  ...externalChangeSignal ? { externalChangeSignal } : {}
1256
1278
  });
1257
1279
  await runtime.start();
@@ -1310,6 +1332,13 @@ function createProjectDevtoolsCapabilities() {
1310
1332
  mutate: true,
1311
1333
  importExport: true
1312
1334
  },
1335
+ storage: {
1336
+ browse: true,
1337
+ download: true,
1338
+ readRange: true,
1339
+ delete: true,
1340
+ maxPreviewBytes: 8e4
1341
+ },
1313
1342
  scheduler: {
1314
1343
  read: true,
1315
1344
  edit: true
@@ -1497,14 +1526,15 @@ async function startDevHub(options) {
1497
1526
  return await readDevtoolsSessionState(options.cwd) ?? sessionState;
1498
1527
  }
1499
1528
  await writeDevtoolsSessionState(options.cwd, sessionState);
1500
- const httpServer = createServer((_request, response) => {
1501
- response.writeHead(200, { "content-type": "application/json" });
1502
- response.end(JSON.stringify({
1503
- ok: true,
1504
- wsPort: devtoolsPort
1505
- }));
1529
+ const httpServer = createServer((request, response) => {
1530
+ handleStorageAccessHttpRequest(request, response).catch((error) => {
1531
+ if (!response.headersSent) writeJsonResponse(response, 500, { error: formatError(error) });
1532
+ else response.destroy(error instanceof Error ? error : void 0);
1533
+ });
1506
1534
  });
1507
1535
  const websocketServer = new WebSocketServer({ server: httpServer });
1536
+ const storageAccessTickets = /* @__PURE__ */ new Map();
1537
+ const pendingHubCommands = /* @__PURE__ */ new Map();
1508
1538
  const runtimeSockets = /* @__PURE__ */ new Map();
1509
1539
  const runtimeHellos = /* @__PURE__ */ new Map();
1510
1540
  const runtimeEvents = /* @__PURE__ */ new Map();
@@ -1571,6 +1601,135 @@ async function startDevHub(options) {
1571
1601
  event
1572
1602
  })}\n`);
1573
1603
  };
1604
+ const requestRuntimeCommand = async (targetRuntimeId, payload, timeoutMs = 3e4) => {
1605
+ if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) return projectTargetBackend.handleCommand(payload);
1606
+ const target = runtimeSockets.get(targetRuntimeId);
1607
+ if (!target || target.readyState !== WebSocket.OPEN) throw new Error(`Runtime ${targetRuntimeId} is not connected.`);
1608
+ const commandId = `hub:${randomUUID()}`;
1609
+ return await new Promise((resolve, reject) => {
1610
+ const timeout = setTimeout(() => {
1611
+ pendingHubCommands.delete(commandId);
1612
+ reject(/* @__PURE__ */ new Error(`Runtime command ${payload.kind} timed out.`));
1613
+ }, timeoutMs);
1614
+ pendingHubCommands.set(commandId, {
1615
+ resolve,
1616
+ reject,
1617
+ timeout
1618
+ });
1619
+ target.send(JSON.stringify({
1620
+ type: "command",
1621
+ commandId,
1622
+ targetRuntimeId,
1623
+ payload
1624
+ }));
1625
+ });
1626
+ };
1627
+ const createStorageAccessTicket = async (targetRuntimeId, id, purpose) => {
1628
+ const metadata = await requestRuntimeCommand(targetRuntimeId, {
1629
+ kind: "storage.readRange",
1630
+ id,
1631
+ offset: 0,
1632
+ length: 0
1633
+ });
1634
+ if (metadata.kind !== "storage.readRange.result") return {
1635
+ kind: "storage.access.create.result",
1636
+ error: "Runtime returned an unexpected storage access response."
1637
+ };
1638
+ if (metadata.error || !metadata.entry) return {
1639
+ kind: "storage.access.create.result",
1640
+ error: metadata.error ?? "Storage object could not be accessed."
1641
+ };
1642
+ const ticket = randomUUID();
1643
+ const expiresAt = Date.now() + STORAGE_ACCESS_TICKET_TTL_MS;
1644
+ storageAccessTickets.set(ticket, {
1645
+ id: ticket,
1646
+ runtimeId: targetRuntimeId,
1647
+ storageId: id,
1648
+ purpose,
1649
+ entry: metadata.entry,
1650
+ supportsRange: metadata.supportsRange,
1651
+ expiresAt
1652
+ });
1653
+ cleanupExpiredStorageTickets();
1654
+ return {
1655
+ kind: "storage.access.create.result",
1656
+ entry: metadata.entry,
1657
+ url: `http://127.0.0.1:${devtoolsPort}/storage/access/${ticket}`,
1658
+ expiresAt,
1659
+ supportsRange: metadata.supportsRange,
1660
+ maxPreviewBytes: STORAGE_ACCESS_MAX_PREVIEW_BYTES
1661
+ };
1662
+ };
1663
+ const cleanupExpiredStorageTickets = () => {
1664
+ const now = Date.now();
1665
+ for (const [ticket, access] of storageAccessTickets) if (access.expiresAt <= now) storageAccessTickets.delete(ticket);
1666
+ };
1667
+ const handleStorageAccessHttpRequest = async (request, response) => {
1668
+ setStorageCorsHeaders(response);
1669
+ if (request.method === "OPTIONS") {
1670
+ response.writeHead(204);
1671
+ response.end();
1672
+ return;
1673
+ }
1674
+ const requestUrl = new URL(request.url ?? "/", `http://127.0.0.1:${devtoolsPort}`);
1675
+ const match = /^\/storage\/access\/([^/]+)$/.exec(requestUrl.pathname);
1676
+ if (!match) {
1677
+ writeJsonResponse(response, 200, {
1678
+ ok: true,
1679
+ wsPort: devtoolsPort
1680
+ });
1681
+ return;
1682
+ }
1683
+ if (request.method !== "GET" && request.method !== "HEAD") {
1684
+ writeTextResponse(response, 405, "Method not allowed.");
1685
+ return;
1686
+ }
1687
+ cleanupExpiredStorageTickets();
1688
+ const ticket = storageAccessTickets.get(match[1]);
1689
+ if (!ticket || ticket.expiresAt <= Date.now()) {
1690
+ writeTextResponse(response, 401, "Storage access ticket is invalid or expired.");
1691
+ return;
1692
+ }
1693
+ const range = parseStorageRangeHeader(request.headers.range, ticket.entry.size);
1694
+ if ("error" in range) {
1695
+ writeTextResponse(response, range.status, range.error);
1696
+ return;
1697
+ }
1698
+ const start = range.start;
1699
+ const end = range.end;
1700
+ const byteLength = end >= start ? end - start + 1 : 0;
1701
+ if (!ticket.supportsRange && ticket.entry.size > STORAGE_ACCESS_CHUNK_BYTES) {
1702
+ writeTextResponse(response, 409, "This storage backend does not support streaming large files.");
1703
+ return;
1704
+ }
1705
+ writeStorageAccessHeaders(response, ticket, {
1706
+ status: range.partial ? 206 : 200,
1707
+ start,
1708
+ end,
1709
+ byteLength
1710
+ });
1711
+ if (request.method === "HEAD") {
1712
+ response.end();
1713
+ return;
1714
+ }
1715
+ let offset = start;
1716
+ while (offset <= end) {
1717
+ const chunkLength = Math.min(STORAGE_ACCESS_CHUNK_BYTES, end - offset + 1);
1718
+ const chunk = await requestRuntimeCommand(ticket.runtimeId, {
1719
+ kind: "storage.readRange",
1720
+ id: ticket.storageId,
1721
+ offset,
1722
+ length: chunkLength
1723
+ }, 6e4);
1724
+ if (chunk.kind !== "storage.readRange.result") throw new Error("Runtime returned an unexpected storage chunk response.");
1725
+ if (chunk.error || !chunk.base64) throw new Error(chunk.error ?? "Storage chunk could not be read.");
1726
+ const bytes = Buffer.from(chunk.base64, "base64");
1727
+ if (bytes.byteLength === 0) break;
1728
+ await writeResponseChunk(response, bytes);
1729
+ offset += bytes.byteLength;
1730
+ }
1731
+ response.end();
1732
+ };
1574
1733
  websocketServer.on("connection", (socket, request) => {
1575
1734
  const isBrowserDashboardClient = isAllowedDashboardOrigin(request.headers.origin, dashboardPort);
1576
1735
  const isAuthorizedDashboardClient = !isBrowserDashboardClient || isAuthorizedDashboardRequest({
@@ -1616,6 +1775,29 @@ async function startDevHub(options) {
1616
1775
  if (!isAuthorizedDashboardClient) return;
1617
1776
  const targetRuntimeId = message.targetRuntimeId;
1618
1777
  if (!targetRuntimeId) return;
1778
+ if (message.payload.kind === "storage.access.create") {
1779
+ createStorageAccessTicket(targetRuntimeId, message.payload.id, message.payload.purpose).then((payload) => {
1780
+ if (socket.readyState !== WebSocket.OPEN) return;
1781
+ socket.send(JSON.stringify({
1782
+ type: "command.result",
1783
+ commandId: message.commandId,
1784
+ runtimeId: targetRuntimeId,
1785
+ payload
1786
+ }));
1787
+ }).catch((error) => {
1788
+ if (socket.readyState !== WebSocket.OPEN) return;
1789
+ socket.send(JSON.stringify({
1790
+ type: "command.result",
1791
+ commandId: message.commandId,
1792
+ runtimeId: targetRuntimeId,
1793
+ payload: {
1794
+ kind: "storage.access.create.result",
1795
+ error: formatError(error)
1796
+ }
1797
+ }));
1798
+ });
1799
+ return;
1800
+ }
1619
1801
  if (targetRuntimeId === PROJECT_TARGET_RUNTIME_ID && projectTargetBackend) {
1620
1802
  (async () => {
1621
1803
  const payload = await projectTargetBackend.handleCommand(message.payload);
@@ -1719,6 +1901,15 @@ async function startDevHub(options) {
1719
1901
  appendHubLog(message.event);
1720
1902
  } else if (message.type === "event") appendHubLog(message.event);
1721
1903
  if (message.type === "command.result" || message.type === "subscription.data" || message.type === "subscription.error") {
1904
+ if (message.type === "command.result") {
1905
+ const pending = pendingHubCommands.get(message.commandId);
1906
+ if (pending) {
1907
+ clearTimeout(pending.timeout);
1908
+ pendingHubCommands.delete(message.commandId);
1909
+ pending.resolve(message.payload);
1910
+ return;
1911
+ }
1912
+ }
1722
1913
  for (const client of dashboardSockets) if (client.readyState === WebSocket.OPEN) client.send(encoded);
1723
1914
  return;
1724
1915
  }
@@ -1808,6 +1999,80 @@ async function startDevHub(options) {
1808
1999
  process.on("SIGTERM", close);
1809
2000
  return sessionState;
1810
2001
  }
2002
+ function setStorageCorsHeaders(response) {
2003
+ response.setHeader("Access-Control-Allow-Origin", "*");
2004
+ response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
2005
+ response.setHeader("Access-Control-Allow-Headers", "Range");
2006
+ response.setHeader("Access-Control-Expose-Headers", "Accept-Ranges, Content-Disposition, Content-Length, Content-Range, Content-Type");
2007
+ }
2008
+ function writeJsonResponse(response, status, payload) {
2009
+ response.writeHead(status, { "content-type": "application/json" });
2010
+ response.end(JSON.stringify(payload));
2011
+ }
2012
+ function writeTextResponse(response, status, message) {
2013
+ response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
2014
+ response.end(message);
2015
+ }
2016
+ function parseStorageRangeHeader(header, size) {
2017
+ if (size === 0) {
2018
+ if (!header) return {
2019
+ start: 0,
2020
+ end: -1,
2021
+ partial: false
2022
+ };
2023
+ return {
2024
+ error: "Requested range is not satisfiable.",
2025
+ status: 416
2026
+ };
2027
+ }
2028
+ if (!header) return {
2029
+ start: 0,
2030
+ end: size - 1,
2031
+ partial: false
2032
+ };
2033
+ if (header.includes(",")) return {
2034
+ error: "Multi-range requests are not supported.",
2035
+ status: 416
2036
+ };
2037
+ const match = /^bytes=(\d+)-(\d*)$/.exec(header);
2038
+ if (!match) return {
2039
+ error: "Only simple byte ranges are supported.",
2040
+ status: 416
2041
+ };
2042
+ const start = Number(match[1]);
2043
+ const end = match[2] ? Number(match[2]) : size - 1;
2044
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || start >= size) return {
2045
+ error: "Requested range is not satisfiable.",
2046
+ status: 416
2047
+ };
2048
+ return {
2049
+ start,
2050
+ end: Math.min(end, size - 1),
2051
+ partial: true
2052
+ };
2053
+ }
2054
+ function writeStorageAccessHeaders(response, ticket, range) {
2055
+ const headers = {
2056
+ "content-type": ticket.entry.contentType ?? "application/octet-stream",
2057
+ "content-length": range.byteLength,
2058
+ "accept-ranges": ticket.supportsRange ? "bytes" : "none",
2059
+ "content-disposition": renderStorageContentDisposition(ticket)
2060
+ };
2061
+ if (range.status === 206) headers["content-range"] = `bytes ${range.start}-${range.end}/${ticket.entry.size}`;
2062
+ response.writeHead(range.status, headers);
2063
+ }
2064
+ function renderStorageContentDisposition(ticket) {
2065
+ return `${ticket.purpose === "download" ? "attachment" : "inline"}; filename="${sanitizeHeaderFilename(ticket.entry.fileName ?? ticket.entry.id)}"; filename*=UTF-8''${encodeURIComponent(ticket.entry.fileName ?? `${ticket.entry.id}.bin`)}`;
2066
+ }
2067
+ function sanitizeHeaderFilename(value) {
2068
+ return value.replaceAll(/["\\\r\n]/g, "_");
2069
+ }
2070
+ async function writeResponseChunk(response, chunk) {
2071
+ if (response.write(chunk)) return;
2072
+ await new Promise((resolve) => {
2073
+ response.once("drain", resolve);
2074
+ });
2075
+ }
1811
2076
  async function writeDevtoolsSessionState(cwd, state) {
1812
2077
  const sessionPath = path.join(cwd, DEVTOOLS_SESSION_FILE);
1813
2078
  await mkdir(path.dirname(sessionPath), { recursive: true });