@siafoundation/sia-storage 0.1.3 → 0.1.5

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.
@@ -3,7 +3,7 @@ import {
3
3
  parseStreamPath,
4
4
  requestReply,
5
5
  streamErrorMessage
6
- } from "./chunk-QICJ5CIY.js";
6
+ } from "./chunk-WOQD3MPG.js";
7
7
 
8
8
  // src/stream-worker.ts
9
9
  import wasmInit, {
@@ -93,7 +93,31 @@ function serveStreams(scope, connect, supportsDownloads) {
93
93
  const sources = /* @__PURE__ */ new Map();
94
94
  const sessions = /* @__PURE__ */ new Map();
95
95
  const released = /* @__PURE__ */ new Set();
96
+ const active = /* @__PURE__ */ new Map();
96
97
  const key = (clientId, id) => JSON.stringify([clientId, id]);
98
+ function tellStreaming(client, streaming) {
99
+ try {
100
+ client.postMessage({
101
+ type: "sia-streaming",
102
+ active: streaming
103
+ });
104
+ } catch {
105
+ }
106
+ }
107
+ function hold(client, lifetime) {
108
+ const count = (active.get(client.id) ?? 0) + 1;
109
+ active.set(client.id, count);
110
+ if (count === 1) tellStreaming(client, true);
111
+ void lifetime.then(() => {
112
+ const left = active.get(client.id) - 1;
113
+ if (left > 0) {
114
+ active.set(client.id, left);
115
+ return;
116
+ }
117
+ active.delete(client.id);
118
+ tellStreaming(client, false);
119
+ });
120
+ }
97
121
  function idle(session) {
98
122
  clearTimeout(session.timer);
99
123
  if (!session.closed && session.jobs.size === 0) {
@@ -245,7 +269,7 @@ function serveStreams(scope, connect, supportsDownloads) {
245
269
  const message = streamErrorMessage(error);
246
270
  const aborted = error instanceof Error && error.name === "AbortError";
247
271
  const { source, range } = job;
248
- if (!aborted && source && !source.closed) {
272
+ if (!aborted && !job.quiet && source && !source.closed) {
249
273
  try {
250
274
  source.client.postMessage({
251
275
  type: "sia-stream-error",
@@ -262,15 +286,17 @@ function serveStreams(scope, connect, supportsDownloads) {
262
286
  };
263
287
  return job;
264
288
  }
265
- async function serve(request, clientId, token, download) {
289
+ async function serve(request, clientId, token, { download, warm }) {
266
290
  if (request.method !== "GET" && request.method !== "HEAD") {
267
291
  return errorResponse(405);
268
292
  }
269
293
  if (!supportsDownloads) return errorResponse(503);
270
294
  const job = startJob(request, token);
295
+ job.quiet = warm;
271
296
  try {
272
297
  const client = await scope.clients.get(clientId);
273
298
  if (!client || request.signal.aborted) return errorResponse(404);
299
+ hold(client, job.lifetime);
274
300
  const source = getSource(client, token);
275
301
  job.source = source;
276
302
  source.requests.add(job.abort);
@@ -291,9 +317,9 @@ function serveStreams(scope, connect, supportsDownloads) {
291
317
  job.finish();
292
318
  return new Response(null, { status: range.status, headers });
293
319
  }
294
- const { reporter } = source;
295
- job.reading = true;
296
- if (++source.reading === 1) reporter?.status("connecting");
320
+ const reporter = warm ? void 0 : source.reporter;
321
+ job.reading = !warm;
322
+ if (!warm && ++source.reading === 1) reporter?.status("connecting");
297
323
  if (range.length === 0) {
298
324
  job.finish();
299
325
  return new Response(null, { status: range.status, headers });
@@ -345,6 +371,8 @@ function serveStreams(scope, connect, supportsDownloads) {
345
371
  released.add(sessionKey);
346
372
  const session = sessions.get(sessionKey);
347
373
  if (session) event.waitUntil(disposeSession(session));
374
+ } else if (data?.type === "sia-keep-alive" && !active.has(clientId)) {
375
+ tellStreaming(source, false);
348
376
  }
349
377
  });
350
378
  scope.addEventListener("fetch", (event) => {
@@ -353,8 +381,9 @@ function serveStreams(scope, connect, supportsDownloads) {
353
381
  const stream = parseStreamPath(url.pathname);
354
382
  if (!stream) return;
355
383
  const download = url.searchParams.get("download") === "1";
384
+ const warm = url.searchParams.get("warm") === "1";
356
385
  event.respondWith(
357
- serve(event.request, stream.clientId, stream.token, download)
386
+ serve(event.request, stream.clientId, stream.token, { download, warm })
358
387
  );
359
388
  });
360
389
  }
@@ -384,7 +413,7 @@ function rangeBody(job, chunks, length) {
384
413
  throw new Error("Shared file stream exceeded the requested length.");
385
414
  }
386
415
  controller.enqueue(value);
387
- const reporter = job.source?.reporter;
416
+ const reporter = job.quiet ? void 0 : job.source?.reporter;
388
417
  reporter?.status("downloading");
389
418
  reporter?.bytes(value.byteLength);
390
419
  if (remaining === 0) {
@@ -478,13 +507,27 @@ function installStreams(scope, options = {}) {
478
507
  }
479
508
  async function connectShared({
480
509
  indexerUrl,
481
- seed
510
+ seed,
511
+ sealKey
482
512
  }) {
483
- const sdk = await SharedSdk.connect(indexerUrl, seed);
513
+ const key = sealKey === void 0 ? void 0 : new AppKey(fromHex(sealKey));
514
+ let sdk;
515
+ try {
516
+ sdk = await SharedSdk.connect(indexerUrl, seed);
517
+ } catch (error) {
518
+ key?.free();
519
+ throw error;
520
+ }
484
521
  return {
485
- object: ({ objectId }) => sdk.object(objectId),
522
+ // The page sealed the object it already had with the share's random key.
523
+ // Opening that saves a round trip to the indexer before every stream's
524
+ // first byte.
525
+ object: async ({ objectId, sealed }) => key && sealed ? PinnedObject.open(key, sealed) : sdk.object(objectId),
486
526
  download: (object, options) => sdk.download(object, options),
487
- free: () => sdk.free()
527
+ free: () => {
528
+ sdk.free();
529
+ key?.free();
530
+ }
488
531
  };
489
532
  }
490
533
  async function connectApp({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stream-worker.ts","../src/stream/response.ts","../src/stream/worker.ts"],"sourcesContent":["/**\n * The worker side of streaming, for sites that already run a service worker.\n *\n * A page has only one service worker, so a site with its own calls\n * `installStreams(self)` in it and passes that worker's URL to\n * `enableStreaming`. Every other site serves the prebuilt `sia-storage-sw.js`,\n * which is this function with the WebAssembly built in.\n */\nimport wasmInit, {\n AppKey,\n Builder,\n type InitInput,\n PinnedObject,\n SharedSdk,\n} from '../wasm/sia_storage_wasm.js'\nimport type { Connection } from './stream/protocol'\nimport { serveStreams, type StreamScope, type StreamSdk } from './stream/worker'\n\nexport type { StreamScope }\n\nexport type InstallStreamsOptions = {\n /**\n * The SDK's WebAssembly, as a URL, bytes or compiled module. Only needed\n * when the worker's bundler does not emit `sia_storage_wasm_bg.wasm`.\n */\n wasm?: InitInput\n}\n\nexport function installStreams(\n scope: StreamScope,\n options: InstallStreamsOptions = {},\n) {\n let ready: Promise<unknown> | undefined\n serveStreams(\n scope,\n async (connection) => {\n ready ??= wasmInit(\n options.wasm === undefined ? undefined : { module_or_path: options.wasm },\n )\n await ready\n return connection.kind === 'shared'\n ? connectShared(connection)\n : connectApp(connection)\n },\n // Downloads from hosts need WebTransport, which some browsers leave out\n // of service workers. The page then reads objects itself.\n typeof WebTransport === 'function',\n )\n}\n\nasync function connectShared({\n indexerUrl,\n seed,\n}: Extract<Connection, { kind: 'shared' }>): Promise<StreamSdk> {\n const sdk = await SharedSdk.connect(indexerUrl, seed)\n return {\n object: ({ objectId }) => sdk.object(objectId),\n download: (object, options) => sdk.download(object, options),\n free: () => sdk.free(),\n }\n}\n\nasync function connectApp({\n indexerUrl,\n appKey,\n appMeta,\n}: Extract<Connection, { kind: 'app' }>): Promise<StreamSdk> {\n const key = new AppKey(fromHex(appKey))\n const builder = new Builder(indexerUrl, appMeta)\n try {\n const sdk = await builder.connected(key)\n if (!sdk) throw new Error('The app key is not registered with the indexer.')\n return {\n // The page sent the object sealed with this key, so no lookup is needed.\n object: async ({ sealed }) => PinnedObject.open(key, sealed!),\n download: (object, options) => sdk.download(object, options),\n free: () => {\n sdk.free()\n key.free()\n },\n }\n } catch (error) {\n key.free()\n throw error\n } finally {\n builder.free()\n }\n}\n\n// Uint8Array.fromHex is too new for some browsers that run service workers.\nfunction fromHex(hex: string) {\n return Uint8Array.from(hex.match(/../g) ?? [], (byte) => parseInt(byte, 16))\n}\n","// File bytes come from whoever owns the share, so the browser must never cache\n// them or run them as this origin's content.\nconst PROTECTIVE_HEADERS = {\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'Content-Security-Policy': \"sandbox; default-src 'none'\",\n 'Referrer-Policy': 'no-referrer',\n}\n\nexport type ByteRange = {\n status: 200 | 206 | 416\n offset: number\n length: number\n}\n\nexport function parseRange(range: string | null, size: number): ByteRange {\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new RangeError('Invalid file size')\n }\n if (range === null) return { status: 200, offset: 0, length: size }\n\n const invalid: ByteRange = { status: 416, offset: 0, length: 0 }\n const match = /^bytes=(\\d*)-(\\d*)$/i.exec(range.trim())\n if (!match || (!match[1] && !match[2]) || size === 0) return invalid\n // Both groups always participate in a match, possibly as empty strings.\n const start = match[1]!\n const end = match[2]!\n\n // Range numerals are not limited to JS's safe integers. Compare before converting.\n const total = BigInt(size)\n let first: bigint\n let last: bigint\n if (!start) {\n const suffix = BigInt(end)\n if (suffix === 0n) return invalid\n first = suffix >= total ? 0n : total - suffix\n last = total - 1n\n } else {\n first = BigInt(start)\n last = end ? BigInt(end) : total - 1n\n if (first >= total || last < first) return invalid\n if (last >= total) last = total - 1n\n }\n return {\n status: 206,\n offset: Number(first),\n length: Number(last - first + 1n),\n }\n}\n\nexport function contentDisposition(name: string, download: boolean) {\n // TextEncoder replaces lone surrogates without requiring toWellFormed(),\n // which older browsers with module service workers do not implement.\n const wellFormed = new TextDecoder().decode(new TextEncoder().encode(name))\n const filename =\n wellFormed\n // Filenames must not contain header controls or path separators.\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f/\\\\]/g, '_')\n .trim() || 'download'\n const fallback = filename.replace(/[^\\u0020-\\u007e]|[\";]/g, '_')\n const encoded = encodeURIComponent(filename).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n )\n return `${download ? 'attachment' : 'inline'}; filename=\"${fallback}\"; filename*=UTF-8''${encoded}`\n}\n\nexport function responseHeaders(\n metadata: { name: string; mime: string; size: number },\n range: ByteRange,\n download: boolean,\n) {\n // Only use a media type, never arbitrary header syntax from shared metadata.\n const mime = (metadata.mime.split(';', 1)[0] ?? '').trim()\n const headers = new Headers({\n 'Accept-Ranges': 'bytes',\n 'Content-Length': String(range.length),\n 'Content-Type': /^[\\w!#$&^.+-]+\\/[\\w!#$&^.+-]+$/.test(mime)\n ? mime\n : 'application/octet-stream',\n 'Content-Disposition': contentDisposition(metadata.name, download),\n ...PROTECTIVE_HEADERS,\n })\n if (range.status === 206) {\n headers.set(\n 'Content-Range',\n `bytes ${range.offset}-${range.offset + range.length - 1}/${metadata.size}`,\n )\n } else if (range.status === 416) {\n headers.set('Content-Range', `bytes */${metadata.size}`)\n }\n return headers\n}\n\nexport function errorResponse(status: number) {\n const headers = new Headers(PROTECTIVE_HEADERS)\n if (status === 405) headers.set('Allow', 'GET, HEAD')\n return new Response(null, { status, headers })\n}\n","import type {\n PinnedObject,\n ShardProgress,\n} from '../../wasm/sia_storage_wasm.js'\nimport { streamErrorMessage } from './errors'\nimport {\n type ClientReply,\n isSourceReply,\n type PageMessage,\n parseStreamPath,\n requestReply,\n type Connection,\n type SourceDetails,\n type StreamEvent,\n type StreamStatus,\n type WorkerMessage,\n} from './protocol'\nimport {\n type ByteRange,\n errorResponse,\n parseRange,\n responseHeaders,\n} from './response'\n\nconst SOURCE_TIMEOUT = 5_000\nconst IDLE_TIMEOUT = 5 * 60_000\n// How long bytes and shard reports for one URL collect before they are sent,\n// so a stream costs a few messages a second rather than one per chunk.\nconst REPORT_INTERVAL = 100\n\n/** An object as the page describes it. See `SourceDetails`. */\nexport type ObjectRef = Pick<SourceDetails, 'objectId' | 'sealed'>\n\n/** A connected SDK of either kind, as the worker uses it. */\nexport type StreamSdk = {\n object(ref: ObjectRef): Promise<PinnedObject>\n download(\n object: PinnedObject,\n options: {\n offset: number\n length: number\n onShardDownloaded?: (progress: ShardProgress) => void\n },\n ): ReadableStream\n free(): void\n}\n\n// The parts of ServiceWorkerGlobalScope this module uses, written out so it\n// type-checks alongside page code, whose DOM types conflict with the service\n// worker ones. A real ServiceWorkerGlobalScope satisfies them.\ntype StreamClient = { id: string; postMessage(message: unknown, transfer?: Transferable[]): void }\ntype WaitUntil = { waitUntil(promise: Promise<unknown>): void }\ntype StreamFetchEvent = { request: Request; respondWith(response: Promise<Response>): void }\ntype StreamMessageEvent = WaitUntil & {\n data: unknown\n source: unknown\n ports: readonly MessagePort[]\n}\nexport type StreamScope = {\n location: { origin: string }\n skipWaiting(): Promise<void>\n clients: {\n get(id: string): Promise<StreamClient | undefined>\n claim(): Promise<void>\n }\n addEventListener(type: 'install' | 'activate', listener: (event: WaitUntil) => void): void\n addEventListener(type: 'message', listener: (event: StreamMessageEvent) => void): void\n addEventListener(type: 'fetch', listener: (event: StreamFetchEvent) => void): void\n}\ntype Metadata = Pick<\n SourceDetails,\n 'objectId' | 'sealed' | 'name' | 'mime' | 'size'\n>\ntype Chunks = ReadableStreamDefaultReader<Uint8Array>\n\n/** One page's open share, and the SDK the worker connects for it on first use. */\ntype Session = {\n key: string\n connection: Connection\n sources: Set<Source>\n objects: Map<string, Promise<PinnedObject>>\n // One `Job.lifetime` per request in flight.\n jobs: Set<Promise<void>>\n closed: boolean\n timer?: ReturnType<typeof setTimeout>\n sdk?: Promise<StreamSdk>\n}\n\n/** One stream token, resolved to its file by asking the page that made it. */\ntype Source = {\n key: string\n client: StreamClient\n closed: boolean\n // The `abort` of each job serving this token.\n requests: Set<() => void>\n ready: Promise<Metadata>\n setup: AbortController\n session?: Session\n // Set when the page asked for status and progress messages.\n reporter?: Reporter\n // Requests for this URL past their HEAD and range checks and not yet\n // settled. Its status is `idle` while this is zero.\n reading: number\n}\n\n/** One HTTP request for a stream URL, from the first await to its last byte. */\ntype Job = {\n closed: boolean\n lifetime: Promise<void>\n source?: Source\n session?: Session\n range?: ByteRange\n // Counted in `source.reading` until the job settles.\n reading?: boolean\n reader?: Chunks\n controller?: ReadableStreamDefaultController<Uint8Array>\n finish(cancel?: boolean): void\n abort(): void\n fail(error: unknown): void\n}\n\n// The control port carries credentials and metadata only. Bytes never leave\n// this worker, since each response consumes the SDK's own stream with native\n// backpressure.\nexport function serveStreams(\n scope: StreamScope,\n connect: (connection: Connection) => Promise<StreamSdk>,\n supportsDownloads: boolean,\n) {\n const sources = new Map<string, Source>()\n const sessions = new Map<string, Session>()\n // Released session keys. A page reply already in flight when its share is\n // released must not bring the session back.\n const released = new Set<string>()\n const key = (clientId: string, id: string) => JSON.stringify([clientId, id])\n\n function idle(session: Session) {\n clearTimeout(session.timer)\n if (!session.closed && session.jobs.size === 0) {\n session.timer = setTimeout(\n () => void disposeSession(session),\n IDLE_TIMEOUT,\n )\n }\n }\n\n function sessionFor(clientId: string, details: SourceDetails) {\n const sessionKey = key(clientId, details.session)\n let session = sessions.get(sessionKey)\n if (!session) {\n session = {\n key: sessionKey,\n connection: details.connection,\n sources: new Set(),\n objects: new Map(),\n jobs: new Set(),\n closed: false,\n }\n sessions.set(sessionKey, session)\n }\n return session\n }\n\n async function disposeSession(session: Session) {\n if (session.closed) return\n session.closed = true\n sessions.delete(session.key)\n clearTimeout(session.timer)\n for (const source of session.sources) dropSource(source)\n // Do not free wasm handles while a connect, lookup or cancellation uses them.\n await Promise.allSettled(session.jobs)\n await Promise.allSettled(\n [...session.objects.values()].map(async (object) =>\n (await object).free(),\n ),\n )\n try {\n ;(await session.sdk)?.free()\n } catch {\n // Connection failures are reported by the request, not during disposal.\n }\n }\n\n // A failed connect or lookup is forgotten, so the next request retries it\n // instead of failing with the cached error.\n function sdkFor(session: Session) {\n if (session.sdk) return session.sdk\n const { connection } = session\n const connecting = Promise.resolve().then(() => connect(connection))\n session.sdk = connecting\n connecting.catch(() => {\n if (session.sdk === connecting) delete session.sdk\n })\n return connecting\n }\n\n function objectFor(session: Session, sdk: StreamSdk, ref: ObjectRef) {\n const { objectId } = ref\n const cached = session.objects.get(objectId)\n if (cached) return cached\n const lookup = Promise.resolve().then(() => sdk.object(ref))\n session.objects.set(objectId, lookup)\n lookup.catch(() => {\n if (session.objects.get(objectId) === lookup) {\n session.objects.delete(objectId)\n }\n })\n return lookup\n }\n\n function getSource(client: StreamClient, token: string) {\n const sourceKey = key(client.id, token)\n const cached = sources.get(sourceKey)\n if (cached) return cached\n const setup = new AbortController()\n const request = { type: 'sia-source', token } satisfies WorkerMessage\n const ready = requestReply(client, request, {\n timeout: SOURCE_TIMEOUT,\n signal: setup.signal,\n }).then((reply) => {\n if (!isSourceReply(reply)) {\n throw new Error('Shared file source unavailable.')\n }\n if (source.closed || released.has(key(client.id, reply.session))) {\n throw new DOMException('Released', 'AbortError')\n }\n const session = sessionFor(client.id, reply)\n source.session = session\n session.sources.add(source)\n if (reply.events?.length) {\n source.reporter = createReporter(client, token, reply.events)\n }\n idle(session)\n // Keep credentials in the session record, never in response metadata.\n const { objectId, sealed, name, mime, size } = reply\n return { objectId, sealed, name, mime, size }\n })\n const source: Source = {\n key: sourceKey,\n client,\n closed: false,\n requests: new Set(),\n ready,\n setup,\n reading: 0,\n }\n sources.set(sourceKey, source)\n return source\n }\n\n function dropSource(source: Source) {\n if (source.closed) return\n source.closed = true\n sources.delete(source.key)\n source.reporter?.stop()\n source.setup.abort(new DOMException('Released', 'AbortError'))\n for (const cancel of source.requests) cancel()\n source.session?.sources.delete(source)\n }\n\n /**\n * Tracks one request until it settles, whichever way that happens: its last\n * byte, the page aborting it, its source being released, or an SDK failure.\n */\n function startJob(request: Request, token: string): Job {\n let complete = () => {}\n // Kept out of event.waitUntil. Chromium kills a worker whose event runs\n // past 5 minutes, which would end any download still running at that mark.\n const lifetime = new Promise<void>((resolve) => {\n complete = resolve\n })\n const job: Job = {\n closed: false,\n lifetime,\n finish(cancel = false) {\n if (job.closed) return\n job.closed = true\n request.signal.removeEventListener('abort', job.abort)\n const { reader } = job\n const cancellation =\n cancel && reader ? reader.cancel().catch(() => {}) : undefined\n void Promise.resolve(cancellation).finally(() => {\n reader?.releaseLock()\n const { source } = job\n source?.requests.delete(job.abort)\n if (source && job.reading && --source.reading === 0) {\n source.reporter?.status('idle')\n }\n job.session?.jobs.delete(lifetime)\n if (job.session) idle(job.session)\n complete()\n })\n },\n abort() {\n if (job.closed) return\n job.controller?.error(new DOMException('Aborted', 'AbortError'))\n job.finish(true)\n },\n fail(error) {\n if (job.closed) return\n const message = streamErrorMessage(error)\n const aborted = error instanceof Error && error.name === 'AbortError'\n const { source, range } = job\n if (!aborted && source && !source.closed) {\n // Diagnostics go only to the owning page. Never log SDK objects here.\n try {\n source.client.postMessage({\n type: 'sia-stream-error',\n token,\n message,\n ...(range && { offset: range.offset, length: range.length }),\n } satisfies WorkerMessage)\n } catch {\n // The owning tab may already be gone.\n }\n }\n job.controller?.error(new Error(message))\n job.finish(true)\n },\n }\n return job\n }\n\n async function serve(\n request: Request,\n clientId: string,\n token: string,\n download: boolean,\n ): Promise<Response> {\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n return errorResponse(405)\n }\n if (!supportsDownloads) return errorResponse(503)\n const job = startJob(request, token)\n try {\n const client = await scope.clients.get(clientId)\n if (!client || request.signal.aborted) return errorResponse(404)\n const source = getSource(client, token)\n job.source = source\n source.requests.add(job.abort)\n request.signal.addEventListener('abort', job.abort, { once: true })\n const metadata = await source.ready\n if (job.closed) return errorResponse(502)\n // Set by `ready` before it resolves.\n const session = source.session!\n job.session = session\n session.jobs.add(job.lifetime)\n clearTimeout(session.timer)\n const range = parseRange(\n request.method === 'HEAD' ? null : request.headers.get('range'),\n metadata.size,\n )\n job.range = range\n const headers = responseHeaders(metadata, range, download)\n if (request.method === 'HEAD' || range.status === 416) {\n job.finish()\n return new Response(null, { status: range.status, headers })\n }\n const { reporter } = source\n job.reading = true\n // A read that starts while another is already downloading leaves the\n // status at `downloading`.\n if (++source.reading === 1) reporter?.status('connecting')\n // A zero-byte file reports connecting then idle, as a read in the page\n // does, without starting an SDK download.\n if (range.length === 0) {\n job.finish()\n return new Response(null, { status: range.status, headers })\n }\n const sdk = await sdkFor(session)\n if (job.closed) return errorResponse(502)\n const object = await objectFor(session, sdk, metadata)\n if (job.closed) return errorResponse(502)\n const chunks: Chunks = sdk\n .download(object, {\n offset: range.offset,\n length: range.length,\n ...(reporter?.wants.shards && {\n onShardDownloaded: (progress) => reporter.shard(progress),\n }),\n })\n .getReader()\n job.reader = chunks\n return new Response(rangeBody(job, chunks, range.length), {\n status: range.status,\n headers,\n })\n } catch (error) {\n job.fail(error)\n if (job.source && !job.source.session) dropSource(job.source)\n return errorResponse(502)\n }\n }\n\n scope.addEventListener('install', (event) =>\n event.waitUntil(scope.skipWaiting()),\n )\n scope.addEventListener('activate', (event) =>\n event.waitUntil(scope.clients.claim()),\n )\n scope.addEventListener('message', (event) => {\n const source = event.source as Partial<StreamClient> | null\n const clientId = typeof source?.id === 'string' ? source.id : undefined\n if (!clientId) return\n const data = event.data as PageMessage | undefined\n if (data?.type === 'sia-client') {\n const reply: ClientReply = { clientId, supported: supportsDownloads }\n event.ports[0]?.postMessage(reply)\n event.ports[0]?.close()\n } else if (data?.type === 'sia-release' && typeof data.token === 'string') {\n // The session outlives its last source until IDLE_TIMEOUT, so moving to\n // the next file reuses the connected SDK.\n const source = sources.get(key(clientId, data.token))\n if (source) dropSource(source)\n } else if (\n data?.type === 'sia-release-share' &&\n typeof data.session === 'string'\n ) {\n const sessionKey = key(clientId, data.session)\n released.add(sessionKey)\n const session = sessions.get(sessionKey)\n if (session) event.waitUntil(disposeSession(session))\n }\n })\n scope.addEventListener('fetch', (event) => {\n const url = new URL(event.request.url)\n if (url.origin !== scope.location.origin) return\n const stream = parseStreamPath(url.pathname)\n if (!stream) return\n const download = url.searchParams.get('download') === '1'\n event.respondWith(\n serve(event.request, stream.clientId, stream.token, download),\n )\n })\n}\n\n/**\n * Exactly `length` bytes from `chunks`, or an error if the SDK stream ends\n * early or runs over. Each pull reads one chunk, so backpressure reaches the SDK.\n */\nfunction rangeBody(job: Job, chunks: Chunks, length: number) {\n let remaining = length\n return new ReadableStream<Uint8Array>(\n {\n start(controller) {\n job.controller = controller\n },\n async pull(controller) {\n try {\n const { done, value } = await chunks.read()\n if (job.closed) return\n if (done) {\n if (remaining !== 0) {\n throw new Error(\n 'Shared file stream ended before all bytes arrived.',\n )\n }\n controller.close()\n job.finish()\n return\n }\n remaining -= value.byteLength\n if (remaining < 0) {\n throw new Error('Shared file stream exceeded the requested length.')\n }\n controller.enqueue(value)\n const reporter = job.source?.reporter\n reporter?.status('downloading')\n reporter?.bytes(value.byteLength)\n if (remaining === 0) {\n controller.close()\n job.finish(true)\n }\n } catch (error) {\n job.fail(error)\n }\n },\n cancel() {\n job.finish(true)\n return job.lifetime\n },\n },\n { highWaterMark: 0 },\n )\n}\n\ntype Reporter = {\n wants: { shards: boolean }\n status(status: StreamStatus): void\n bytes(count: number): void\n shard(progress: ShardProgress): void\n stop(): void\n}\n\n/**\n * Status and progress messages for one stream URL, sent to the page that made\n * it. Statuses go at once and only when they change. Bytes and shards collect\n * for REPORT_INTERVAL. After `stop`, every call does nothing, since the SDK\n * can still report a shard from a download being cancelled.\n */\nfunction createReporter(\n client: StreamClient,\n token: string,\n events: StreamEvent[],\n): Reporter {\n const wants = {\n status: events.includes('status'),\n progress: events.includes('progress'),\n shards: events.includes('shards'),\n }\n let stopped = false\n let current: StreamStatus | undefined\n let bytes = 0\n let shards: ShardProgress[] = []\n let timer: ReturnType<typeof setTimeout> | undefined\n\n function post(message: WorkerMessage) {\n try {\n client.postMessage(message)\n } catch {\n // The owning tab may already be gone.\n }\n }\n function flush() {\n clearTimeout(timer)\n timer = undefined\n if (stopped || (bytes === 0 && shards.length === 0)) return\n post({ type: 'sia-stream-progress', token, bytes, shards })\n bytes = 0\n shards = []\n }\n function schedule() {\n timer ??= setTimeout(flush, REPORT_INTERVAL)\n }\n function status(next: StreamStatus) {\n if (stopped || next === current) return\n flush()\n current = next\n if (wants.status) post({ type: 'sia-stream-status', token, status: next })\n }\n\n return {\n wants,\n status,\n bytes(count) {\n if (stopped || !wants.progress) return\n bytes += count\n schedule()\n },\n shard({ hostKey, shardSize, shardIndex, slabIndex, elapsedMs }) {\n if (stopped) return\n // Copied field by field, so only plain data crosses to the page.\n shards.push({ hostKey, shardSize, shardIndex, slabIndex, elapsedMs })\n schedule()\n },\n stop() {\n stopped = true\n clearTimeout(timer)\n timer = undefined\n shards = []\n },\n }\n}\n"],"mappings":"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","names":["source"]}
1
+ {"version":3,"sources":["../src/stream-worker.ts","../src/stream/response.ts","../src/stream/worker.ts"],"sourcesContent":["/**\n * The worker side of streaming, for sites that already run a service worker.\n *\n * A page has only one service worker, so a site with its own calls\n * `installStreams(self)` in it and passes that worker's URL to\n * `enableStreaming`. Every other site serves the prebuilt `sia-storage-sw.js`,\n * which is this function with the WebAssembly built in.\n */\nimport wasmInit, {\n AppKey,\n Builder,\n type InitInput,\n PinnedObject,\n SharedSdk,\n} from '../wasm/sia_storage_wasm.js'\nimport type { Connection } from './stream/protocol'\nimport { serveStreams, type StreamScope, type StreamSdk } from './stream/worker'\n\nexport type { StreamScope }\n\nexport type InstallStreamsOptions = {\n /**\n * The SDK's WebAssembly, as a URL, bytes or compiled module. Only needed\n * when the worker's bundler does not emit `sia_storage_wasm_bg.wasm`.\n */\n wasm?: InitInput\n}\n\nexport function installStreams(\n scope: StreamScope,\n options: InstallStreamsOptions = {},\n) {\n let ready: Promise<unknown> | undefined\n serveStreams(\n scope,\n async (connection) => {\n ready ??= wasmInit(\n options.wasm === undefined ? undefined : { module_or_path: options.wasm },\n )\n await ready\n return connection.kind === 'shared'\n ? connectShared(connection)\n : connectApp(connection)\n },\n // Downloads from hosts need WebTransport, which some browsers leave out\n // of service workers. The page then reads objects itself.\n typeof WebTransport === 'function',\n )\n}\n\nasync function connectShared({\n indexerUrl,\n seed,\n sealKey,\n}: Extract<Connection, { kind: 'shared' }>): Promise<StreamSdk> {\n // Built before connecting, so a key that fails to load cannot leave a\n // connected SDK behind with nothing to free it.\n const key = sealKey === undefined ? undefined : new AppKey(fromHex(sealKey))\n let sdk: SharedSdk\n try {\n sdk = await SharedSdk.connect(indexerUrl, seed)\n } catch (error) {\n key?.free()\n throw error\n }\n return {\n // The page sealed the object it already had with the share's random key.\n // Opening that saves a round trip to the indexer before every stream's\n // first byte.\n object: async ({ objectId, sealed }) =>\n key && sealed ? PinnedObject.open(key, sealed) : sdk.object(objectId),\n download: (object, options) => sdk.download(object, options),\n free: () => {\n sdk.free()\n key?.free()\n },\n }\n}\n\nasync function connectApp({\n indexerUrl,\n appKey,\n appMeta,\n}: Extract<Connection, { kind: 'app' }>): Promise<StreamSdk> {\n const key = new AppKey(fromHex(appKey))\n const builder = new Builder(indexerUrl, appMeta)\n try {\n const sdk = await builder.connected(key)\n if (!sdk) throw new Error('The app key is not registered with the indexer.')\n return {\n // The page sent the object sealed with this key, so no lookup is needed.\n object: async ({ sealed }) => PinnedObject.open(key, sealed!),\n download: (object, options) => sdk.download(object, options),\n free: () => {\n sdk.free()\n key.free()\n },\n }\n } catch (error) {\n key.free()\n throw error\n } finally {\n builder.free()\n }\n}\n\n// Uint8Array.fromHex is too new for some browsers that run service workers.\nfunction fromHex(hex: string) {\n return Uint8Array.from(hex.match(/../g) ?? [], (byte) => parseInt(byte, 16))\n}\n","// File bytes come from whoever owns the share, so the browser must never cache\n// them or run them as this origin's content.\nconst PROTECTIVE_HEADERS = {\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'Content-Security-Policy': \"sandbox; default-src 'none'\",\n 'Referrer-Policy': 'no-referrer',\n}\n\nexport type ByteRange = {\n status: 200 | 206 | 416\n offset: number\n length: number\n}\n\nexport function parseRange(range: string | null, size: number): ByteRange {\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new RangeError('Invalid file size')\n }\n if (range === null) return { status: 200, offset: 0, length: size }\n\n const invalid: ByteRange = { status: 416, offset: 0, length: 0 }\n const match = /^bytes=(\\d*)-(\\d*)$/i.exec(range.trim())\n if (!match || (!match[1] && !match[2]) || size === 0) return invalid\n // Both groups always participate in a match, possibly as empty strings.\n const start = match[1]!\n const end = match[2]!\n\n // Range numerals are not limited to JS's safe integers. Compare before converting.\n const total = BigInt(size)\n let first: bigint\n let last: bigint\n if (!start) {\n const suffix = BigInt(end)\n if (suffix === 0n) return invalid\n first = suffix >= total ? 0n : total - suffix\n last = total - 1n\n } else {\n first = BigInt(start)\n last = end ? BigInt(end) : total - 1n\n if (first >= total || last < first) return invalid\n if (last >= total) last = total - 1n\n }\n return {\n status: 206,\n offset: Number(first),\n length: Number(last - first + 1n),\n }\n}\n\nexport function contentDisposition(name: string, download: boolean) {\n // TextEncoder replaces lone surrogates without requiring toWellFormed(),\n // which older browsers with module service workers do not implement.\n const wellFormed = new TextDecoder().decode(new TextEncoder().encode(name))\n const filename =\n wellFormed\n // Filenames must not contain header controls or path separators.\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001f\\u007f/\\\\]/g, '_')\n .trim() || 'download'\n const fallback = filename.replace(/[^\\u0020-\\u007e]|[\";]/g, '_')\n const encoded = encodeURIComponent(filename).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n )\n return `${download ? 'attachment' : 'inline'}; filename=\"${fallback}\"; filename*=UTF-8''${encoded}`\n}\n\nexport function responseHeaders(\n metadata: { name: string; mime: string; size: number },\n range: ByteRange,\n download: boolean,\n) {\n // Only use a media type, never arbitrary header syntax from shared metadata.\n const mime = (metadata.mime.split(';', 1)[0] ?? '').trim()\n const headers = new Headers({\n 'Accept-Ranges': 'bytes',\n 'Content-Length': String(range.length),\n 'Content-Type': /^[\\w!#$&^.+-]+\\/[\\w!#$&^.+-]+$/.test(mime)\n ? mime\n : 'application/octet-stream',\n 'Content-Disposition': contentDisposition(metadata.name, download),\n ...PROTECTIVE_HEADERS,\n })\n if (range.status === 206) {\n headers.set(\n 'Content-Range',\n `bytes ${range.offset}-${range.offset + range.length - 1}/${metadata.size}`,\n )\n } else if (range.status === 416) {\n headers.set('Content-Range', `bytes */${metadata.size}`)\n }\n return headers\n}\n\nexport function errorResponse(status: number) {\n const headers = new Headers(PROTECTIVE_HEADERS)\n if (status === 405) headers.set('Allow', 'GET, HEAD')\n return new Response(null, { status, headers })\n}\n","import type {\n PinnedObject,\n ShardProgress,\n} from '../../wasm/sia_storage_wasm.js'\nimport { streamErrorMessage } from './errors'\nimport {\n type ClientReply,\n isSourceReply,\n type PageMessage,\n parseStreamPath,\n requestReply,\n type Connection,\n type SourceDetails,\n type StreamEvent,\n type StreamStatus,\n type WorkerMessage,\n} from './protocol'\nimport {\n type ByteRange,\n errorResponse,\n parseRange,\n responseHeaders,\n} from './response'\n\nconst SOURCE_TIMEOUT = 5_000\nconst IDLE_TIMEOUT = 5 * 60_000\n// How long bytes and shard reports for one URL collect before they are sent,\n// so a stream costs a few messages a second rather than one per chunk.\nconst REPORT_INTERVAL = 100\n\n/** An object as the page describes it. See `SourceDetails`. */\nexport type ObjectRef = Pick<SourceDetails, 'objectId' | 'sealed'>\n\n/** A connected SDK of either kind, as the worker uses it. */\nexport type StreamSdk = {\n object(ref: ObjectRef): Promise<PinnedObject>\n download(\n object: PinnedObject,\n options: {\n offset: number\n length: number\n onShardDownloaded?: (progress: ShardProgress) => void\n },\n ): ReadableStream\n free(): void\n}\n\n// The parts of ServiceWorkerGlobalScope this module uses, written out so it\n// type-checks alongside page code, whose DOM types conflict with the service\n// worker ones. A real ServiceWorkerGlobalScope satisfies them.\ntype StreamClient = { id: string; postMessage(message: unknown, transfer?: Transferable[]): void }\ntype WaitUntil = { waitUntil(promise: Promise<unknown>): void }\ntype StreamFetchEvent = { request: Request; respondWith(response: Promise<Response>): void }\ntype StreamMessageEvent = WaitUntil & {\n data: unknown\n source: unknown\n ports: readonly MessagePort[]\n}\nexport type StreamScope = {\n location: { origin: string }\n skipWaiting(): Promise<void>\n clients: {\n get(id: string): Promise<StreamClient | undefined>\n claim(): Promise<void>\n }\n addEventListener(type: 'install' | 'activate', listener: (event: WaitUntil) => void): void\n addEventListener(type: 'message', listener: (event: StreamMessageEvent) => void): void\n addEventListener(type: 'fetch', listener: (event: StreamFetchEvent) => void): void\n}\ntype Metadata = Pick<\n SourceDetails,\n 'objectId' | 'sealed' | 'name' | 'mime' | 'size'\n>\ntype Chunks = ReadableStreamDefaultReader<Uint8Array>\n\n/** One page's open share, and the SDK the worker connects for it on first use. */\ntype Session = {\n key: string\n connection: Connection\n sources: Set<Source>\n objects: Map<string, Promise<PinnedObject>>\n // One `Job.lifetime` per request in flight.\n jobs: Set<Promise<void>>\n closed: boolean\n timer?: ReturnType<typeof setTimeout>\n sdk?: Promise<StreamSdk>\n}\n\n/** One stream token, resolved to its file by asking the page that made it. */\ntype Source = {\n key: string\n client: StreamClient\n closed: boolean\n // The `abort` of each job serving this token.\n requests: Set<() => void>\n ready: Promise<Metadata>\n setup: AbortController\n session?: Session\n // Set when the page asked for status and progress messages.\n reporter?: Reporter\n // Requests for this URL past their HEAD and range checks and not yet\n // settled. Its status is `idle` while this is zero.\n reading: number\n}\n\n/** One HTTP request for a stream URL, from the first await to its last byte. */\ntype Job = {\n closed: boolean\n lifetime: Promise<void>\n source?: Source\n session?: Session\n range?: ByteRange\n // Counted in `source.reading` until the job settles.\n reading?: boolean\n // A warm-up read, which reports nothing to the page.\n quiet?: boolean\n reader?: Chunks\n controller?: ReadableStreamDefaultController<Uint8Array>\n finish(cancel?: boolean): void\n abort(): void\n fail(error: unknown): void\n}\n\n// The control port carries credentials and metadata only. Bytes never leave\n// this worker, since each response consumes the SDK's own stream with native\n// backpressure.\nexport function serveStreams(\n scope: StreamScope,\n connect: (connection: Connection) => Promise<StreamSdk>,\n supportsDownloads: boolean,\n) {\n const sources = new Map<string, Source>()\n const sessions = new Map<string, Session>()\n // Released session keys. A page reply already in flight when its share is\n // released must not bring the session back.\n const released = new Set<string>()\n // Requests in flight per page, by client id. See `hold`.\n const active = new Map<string, number>()\n const key = (clientId: string, id: string) => JSON.stringify([clientId, id])\n\n function tellStreaming(client: StreamClient, streaming: boolean) {\n try {\n client.postMessage({\n type: 'sia-streaming',\n active: streaming,\n } satisfies WorkerMessage)\n } catch {\n // The owning tab may already be gone.\n }\n }\n\n /**\n * Counts a request against its page until `lifetime` settles. Firefox stops\n * a worker 30 seconds after the last event it received, even mid-response,\n * and saves a download cut off that way as if it were complete. While a page\n * has a request here it sends keep-alive messages, and each one restarts\n * that timer.\n */\n function hold(client: StreamClient, lifetime: Promise<void>) {\n const count = (active.get(client.id) ?? 0) + 1\n active.set(client.id, count)\n if (count === 1) tellStreaming(client, true)\n void lifetime.then(() => {\n const left = active.get(client.id)! - 1\n if (left > 0) {\n active.set(client.id, left)\n return\n }\n active.delete(client.id)\n tellStreaming(client, false)\n })\n }\n\n function idle(session: Session) {\n clearTimeout(session.timer)\n if (!session.closed && session.jobs.size === 0) {\n session.timer = setTimeout(\n () => void disposeSession(session),\n IDLE_TIMEOUT,\n )\n }\n }\n\n function sessionFor(clientId: string, details: SourceDetails) {\n const sessionKey = key(clientId, details.session)\n let session = sessions.get(sessionKey)\n if (!session) {\n session = {\n key: sessionKey,\n connection: details.connection,\n sources: new Set(),\n objects: new Map(),\n jobs: new Set(),\n closed: false,\n }\n sessions.set(sessionKey, session)\n }\n return session\n }\n\n async function disposeSession(session: Session) {\n if (session.closed) return\n session.closed = true\n sessions.delete(session.key)\n clearTimeout(session.timer)\n for (const source of session.sources) dropSource(source)\n // Do not free wasm handles while a connect, lookup or cancellation uses them.\n await Promise.allSettled(session.jobs)\n await Promise.allSettled(\n [...session.objects.values()].map(async (object) =>\n (await object).free(),\n ),\n )\n try {\n ;(await session.sdk)?.free()\n } catch {\n // Connection failures are reported by the request, not during disposal.\n }\n }\n\n // A failed connect or lookup is forgotten, so the next request retries it\n // instead of failing with the cached error.\n function sdkFor(session: Session) {\n if (session.sdk) return session.sdk\n const { connection } = session\n const connecting = Promise.resolve().then(() => connect(connection))\n session.sdk = connecting\n connecting.catch(() => {\n if (session.sdk === connecting) delete session.sdk\n })\n return connecting\n }\n\n function objectFor(session: Session, sdk: StreamSdk, ref: ObjectRef) {\n const { objectId } = ref\n const cached = session.objects.get(objectId)\n if (cached) return cached\n const lookup = Promise.resolve().then(() => sdk.object(ref))\n session.objects.set(objectId, lookup)\n lookup.catch(() => {\n if (session.objects.get(objectId) === lookup) {\n session.objects.delete(objectId)\n }\n })\n return lookup\n }\n\n function getSource(client: StreamClient, token: string) {\n const sourceKey = key(client.id, token)\n const cached = sources.get(sourceKey)\n if (cached) return cached\n const setup = new AbortController()\n const request = { type: 'sia-source', token } satisfies WorkerMessage\n const ready = requestReply(client, request, {\n timeout: SOURCE_TIMEOUT,\n signal: setup.signal,\n }).then((reply) => {\n if (!isSourceReply(reply)) {\n throw new Error('Shared file source unavailable.')\n }\n if (source.closed || released.has(key(client.id, reply.session))) {\n throw new DOMException('Released', 'AbortError')\n }\n const session = sessionFor(client.id, reply)\n source.session = session\n session.sources.add(source)\n if (reply.events?.length) {\n source.reporter = createReporter(client, token, reply.events)\n }\n idle(session)\n // Keep credentials in the session record, never in response metadata.\n const { objectId, sealed, name, mime, size } = reply\n return { objectId, sealed, name, mime, size }\n })\n const source: Source = {\n key: sourceKey,\n client,\n closed: false,\n requests: new Set(),\n ready,\n setup,\n reading: 0,\n }\n sources.set(sourceKey, source)\n return source\n }\n\n function dropSource(source: Source) {\n if (source.closed) return\n source.closed = true\n sources.delete(source.key)\n source.reporter?.stop()\n source.setup.abort(new DOMException('Released', 'AbortError'))\n for (const cancel of source.requests) cancel()\n source.session?.sources.delete(source)\n }\n\n /**\n * Tracks one request until it settles, whichever way that happens: its last\n * byte, the page aborting it, its source being released, or an SDK failure.\n */\n function startJob(request: Request, token: string): Job {\n let complete = () => {}\n // Kept out of event.waitUntil. Chromium kills a worker whose event runs\n // past 5 minutes, which would end any download still running at that mark.\n // The page's keep-alive messages, started by `hold`, keep Firefox's worker\n // running instead.\n const lifetime = new Promise<void>((resolve) => {\n complete = resolve\n })\n const job: Job = {\n closed: false,\n lifetime,\n finish(cancel = false) {\n if (job.closed) return\n job.closed = true\n request.signal.removeEventListener('abort', job.abort)\n const { reader } = job\n const cancellation =\n cancel && reader ? reader.cancel().catch(() => {}) : undefined\n void Promise.resolve(cancellation).finally(() => {\n reader?.releaseLock()\n const { source } = job\n source?.requests.delete(job.abort)\n if (source && job.reading && --source.reading === 0) {\n source.reporter?.status('idle')\n }\n job.session?.jobs.delete(lifetime)\n if (job.session) idle(job.session)\n complete()\n })\n },\n abort() {\n if (job.closed) return\n job.controller?.error(new DOMException('Aborted', 'AbortError'))\n job.finish(true)\n },\n fail(error) {\n if (job.closed) return\n const message = streamErrorMessage(error)\n const aborted = error instanceof Error && error.name === 'AbortError'\n const { source, range } = job\n // A warm-up is invisible to the page, failures included. The play\n // that follows connects again and reports its own failure.\n if (!aborted && !job.quiet && source && !source.closed) {\n // Diagnostics go only to the owning page. Never log SDK objects here.\n try {\n source.client.postMessage({\n type: 'sia-stream-error',\n token,\n message,\n ...(range && { offset: range.offset, length: range.length }),\n } satisfies WorkerMessage)\n } catch {\n // The owning tab may already be gone.\n }\n }\n job.controller?.error(new Error(message))\n job.finish(true)\n },\n }\n return job\n }\n\n async function serve(\n request: Request,\n clientId: string,\n token: string,\n { download, warm }: { download: boolean; warm: boolean },\n ): Promise<Response> {\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n return errorResponse(405)\n }\n if (!supportsDownloads) return errorResponse(503)\n const job = startJob(request, token)\n job.quiet = warm\n try {\n const client = await scope.clients.get(clientId)\n if (!client || request.signal.aborted) return errorResponse(404)\n hold(client, job.lifetime)\n const source = getSource(client, token)\n job.source = source\n source.requests.add(job.abort)\n request.signal.addEventListener('abort', job.abort, { once: true })\n const metadata = await source.ready\n if (job.closed) return errorResponse(502)\n // Set by `ready` before it resolves.\n const session = source.session!\n job.session = session\n session.jobs.add(job.lifetime)\n clearTimeout(session.timer)\n const range = parseRange(\n request.method === 'HEAD' ? null : request.headers.get('range'),\n metadata.size,\n )\n job.range = range\n const headers = responseHeaders(metadata, range, download)\n if (request.method === 'HEAD' || range.status === 416) {\n job.finish()\n return new Response(null, { status: range.status, headers })\n }\n // A warm-up is invisible to the page, so a player that warms a file\n // does not see it go connecting and downloading before Play.\n const reporter = warm ? undefined : source.reporter\n job.reading = !warm\n // A read that starts while another is already downloading leaves the\n // status at `downloading`.\n if (!warm && ++source.reading === 1) reporter?.status('connecting')\n // A zero-byte file reports connecting then idle, as a read in the page\n // does, without starting an SDK download.\n if (range.length === 0) {\n job.finish()\n return new Response(null, { status: range.status, headers })\n }\n const sdk = await sdkFor(session)\n if (job.closed) return errorResponse(502)\n const object = await objectFor(session, sdk, metadata)\n if (job.closed) return errorResponse(502)\n const chunks: Chunks = sdk\n .download(object, {\n offset: range.offset,\n length: range.length,\n ...(reporter?.wants.shards && {\n onShardDownloaded: (progress) => reporter.shard(progress),\n }),\n })\n .getReader()\n job.reader = chunks\n return new Response(rangeBody(job, chunks, range.length), {\n status: range.status,\n headers,\n })\n } catch (error) {\n job.fail(error)\n if (job.source && !job.source.session) dropSource(job.source)\n return errorResponse(502)\n }\n }\n\n scope.addEventListener('install', (event) =>\n event.waitUntil(scope.skipWaiting()),\n )\n scope.addEventListener('activate', (event) =>\n event.waitUntil(scope.clients.claim()),\n )\n scope.addEventListener('message', (event) => {\n const source = event.source as Partial<StreamClient> | null\n const clientId = typeof source?.id === 'string' ? source.id : undefined\n if (!clientId) return\n const data = event.data as PageMessage | undefined\n if (data?.type === 'sia-client') {\n const reply: ClientReply = { clientId, supported: supportsDownloads }\n event.ports[0]?.postMessage(reply)\n event.ports[0]?.close()\n } else if (data?.type === 'sia-release' && typeof data.token === 'string') {\n // The session outlives its last source until IDLE_TIMEOUT, so moving to\n // the next file reuses the connected SDK.\n const source = sources.get(key(clientId, data.token))\n if (source) dropSource(source)\n } else if (\n data?.type === 'sia-release-share' &&\n typeof data.session === 'string'\n ) {\n const sessionKey = key(clientId, data.session)\n released.add(sessionKey)\n const session = sessions.get(sessionKey)\n if (session) event.waitUntil(disposeSession(session))\n } else if (data?.type === 'sia-keep-alive' && !active.has(clientId)) {\n // A worker stopped mid-stream never sent the page `active: false`, so the\n // one that answers this keep-alive does.\n tellStreaming(source as StreamClient, false)\n }\n })\n scope.addEventListener('fetch', (event) => {\n const url = new URL(event.request.url)\n if (url.origin !== scope.location.origin) return\n const stream = parseStreamPath(url.pathname)\n if (!stream) return\n const download = url.searchParams.get('download') === '1'\n const warm = url.searchParams.get('warm') === '1'\n event.respondWith(\n serve(event.request, stream.clientId, stream.token, { download, warm }),\n )\n })\n}\n\n/**\n * Exactly `length` bytes from `chunks`, or an error if the SDK stream ends\n * early or runs over. Each pull reads one chunk, so backpressure reaches the SDK.\n */\nfunction rangeBody(job: Job, chunks: Chunks, length: number) {\n let remaining = length\n return new ReadableStream<Uint8Array>(\n {\n start(controller) {\n job.controller = controller\n },\n async pull(controller) {\n try {\n const { done, value } = await chunks.read()\n if (job.closed) return\n if (done) {\n if (remaining !== 0) {\n throw new Error(\n 'Shared file stream ended before all bytes arrived.',\n )\n }\n controller.close()\n job.finish()\n return\n }\n remaining -= value.byteLength\n if (remaining < 0) {\n throw new Error('Shared file stream exceeded the requested length.')\n }\n controller.enqueue(value)\n const reporter = job.quiet ? undefined : job.source?.reporter\n reporter?.status('downloading')\n reporter?.bytes(value.byteLength)\n if (remaining === 0) {\n controller.close()\n job.finish(true)\n }\n } catch (error) {\n job.fail(error)\n }\n },\n cancel() {\n job.finish(true)\n return job.lifetime\n },\n },\n { highWaterMark: 0 },\n )\n}\n\ntype Reporter = {\n wants: { shards: boolean }\n status(status: StreamStatus): void\n bytes(count: number): void\n shard(progress: ShardProgress): void\n stop(): void\n}\n\n/**\n * Status and progress messages for one stream URL, sent to the page that made\n * it. Statuses go at once and only when they change. Bytes and shards collect\n * for REPORT_INTERVAL. After `stop`, every call does nothing, since the SDK\n * can still report a shard from a download being cancelled.\n */\nfunction createReporter(\n client: StreamClient,\n token: string,\n events: StreamEvent[],\n): Reporter {\n const wants = {\n status: events.includes('status'),\n progress: events.includes('progress'),\n shards: events.includes('shards'),\n }\n let stopped = false\n let current: StreamStatus | undefined\n let bytes = 0\n let shards: ShardProgress[] = []\n let timer: ReturnType<typeof setTimeout> | undefined\n\n function post(message: WorkerMessage) {\n try {\n client.postMessage(message)\n } catch {\n // The owning tab may already be gone.\n }\n }\n function flush() {\n clearTimeout(timer)\n timer = undefined\n if (stopped || (bytes === 0 && shards.length === 0)) return\n post({ type: 'sia-stream-progress', token, bytes, shards })\n bytes = 0\n shards = []\n }\n function schedule() {\n timer ??= setTimeout(flush, REPORT_INTERVAL)\n }\n function status(next: StreamStatus) {\n if (stopped || next === current) return\n flush()\n current = next\n if (wants.status) post({ type: 'sia-stream-status', token, status: next })\n }\n\n return {\n wants,\n status,\n bytes(count) {\n if (stopped || !wants.progress) return\n bytes += count\n schedule()\n },\n shard({ hostKey, shardSize, shardIndex, slabIndex, elapsedMs }) {\n if (stopped) return\n // Copied field by field, so only plain data crosses to the page.\n shards.push({ hostKey, shardSize, shardIndex, slabIndex, elapsedMs })\n schedule()\n },\n stop() {\n stopped = true\n clearTimeout(timer)\n timer = undefined\n shards = []\n },\n }\n}\n"],"mappings":"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","names":["source"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siafoundation/sia-storage",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,10 +56,11 @@
56
56
  "build-wasm": "bun run scripts/build-wasm.ts",
57
57
  "setup": "bun run scripts/setup-rust.ts",
58
58
  "setup-napi-test": "bun run scripts/setup-napi-test.ts",
59
- "typecheck": "tsc --noEmit",
59
+ "typecheck": "tsc --noEmit && tsc -p bench",
60
60
  "test": "bun test src/tests/install-integration.test.ts",
61
61
  "test:browser": "bun test src/tests/vite-integration.test.ts src/tests/esbuild-integration.test.ts src/tests/webpack-integration.test.ts src/tests/rollup-integration.test.ts src/tests/nextjs-integration.test.ts src/tests/streaming.test.ts src/tests/stream-prebuilt-worker.test.ts",
62
62
  "test:all": "bun test",
63
+ "bench": "bun run bench/run.ts",
63
64
  "prepublishOnly": "bun run build && bun test",
64
65
  "publish-npm": "bun run scripts/publish.ts"
65
66
  },
@@ -71,10 +72,10 @@
71
72
  "typescript": "5.9.3"
72
73
  },
73
74
  "optionalDependencies": {
74
- "@siafoundation/sia-storage-darwin-arm64": "0.1.3",
75
- "@siafoundation/sia-storage-darwin-x64": "0.1.3",
76
- "@siafoundation/sia-storage-linux-x64-gnu": "0.1.3",
77
- "@siafoundation/sia-storage-linux-arm64-gnu": "0.1.3",
78
- "@siafoundation/sia-storage-win32-x64-msvc": "0.1.3"
75
+ "@siafoundation/sia-storage-darwin-arm64": "0.1.5",
76
+ "@siafoundation/sia-storage-darwin-x64": "0.1.5",
77
+ "@siafoundation/sia-storage-linux-x64-gnu": "0.1.5",
78
+ "@siafoundation/sia-storage-linux-arm64-gnu": "0.1.5",
79
+ "@siafoundation/sia-storage-win32-x64-msvc": "0.1.5"
79
80
  }
80
81
  }
@@ -31,14 +31,6 @@ export interface Slab {
31
31
  sectors: Sector[];
32
32
  }
33
33
 
34
- export interface PinnedSlab {
35
- version: number;
36
- id: string;
37
- encryptionKey: string;
38
- minShards: number;
39
- sectors: Sector[];
40
- }
41
-
42
34
  export interface Host {
43
35
  publicKey: string;
44
36
  addresses: { protocol: string; address: string }[];
@@ -66,20 +58,6 @@ export interface PackedUploadOptions {
66
58
  }
67
59
 
68
60
 
69
-
70
- interface Sdk {
71
- download(object: PinnedObject, options?: DownloadOptions): ReadableStream;
72
- upload(object: PinnedObject, source: ReadableStream, options?: UploadOptions): Promise<PinnedObject>;
73
- uploadPacked(options?: PackedUploadOptions): PackedUpload;
74
- }
75
-
76
-
77
-
78
- interface SharedSdk {
79
- download(object: PinnedObject, options?: DownloadOptions): ReadableStream;
80
- }
81
-
82
-
83
61
  /**
84
62
  * Application info registered with the indexer.
85
63
  */
@@ -273,7 +251,7 @@ export class Builder {
273
251
  waitForApproval(): Promise<void>;
274
252
  }
275
253
 
276
- export class IntoUnderlyingByteSource {
254
+ declare class IntoUnderlyingByteSource {
277
255
  private constructor();
278
256
  free(): void;
279
257
  [Symbol.dispose](): void;
@@ -283,8 +261,9 @@ export class IntoUnderlyingByteSource {
283
261
  readonly autoAllocateChunkSize: number;
284
262
  readonly type: ReadableStreamType;
285
263
  }
264
+ export type { IntoUnderlyingByteSource };
286
265
 
287
- export class IntoUnderlyingSink {
266
+ declare class IntoUnderlyingSink {
288
267
  private constructor();
289
268
  free(): void;
290
269
  [Symbol.dispose](): void;
@@ -292,14 +271,16 @@ export class IntoUnderlyingSink {
292
271
  close(): Promise<any>;
293
272
  write(chunk: any): Promise<any>;
294
273
  }
274
+ export type { IntoUnderlyingSink };
295
275
 
296
- export class IntoUnderlyingSource {
276
+ declare class IntoUnderlyingSource {
297
277
  private constructor();
298
278
  free(): void;
299
279
  [Symbol.dispose](): void;
300
280
  cancel(): void;
301
281
  pull(controller: ReadableStreamDefaultController): Promise<any>;
302
282
  }
283
+ export type { IntoUnderlyingSource };
303
284
 
304
285
  /**
305
286
  * The indexer's record for a sharing key, including how many objects it grants
@@ -341,6 +322,37 @@ export class ObjectEvent {
341
322
  readonly updatedAt: Date;
342
323
  }
343
324
 
325
+ /**
326
+ * An object listed without its slabs, as `SharedSdk.objectSummaries` returns
327
+ * it. It describes the object but cannot be downloaded. Fetch the full
328
+ * object with `SharedSdk.object(id)` to download it.
329
+ */
330
+ export class ObjectSummary {
331
+ private constructor();
332
+ free(): void;
333
+ [Symbol.dispose](): void;
334
+ /**
335
+ * The creation time.
336
+ */
337
+ readonly createdAt: Date;
338
+ /**
339
+ * The object's ID as a hex string.
340
+ */
341
+ readonly id: string;
342
+ /**
343
+ * The object's decrypted metadata.
344
+ */
345
+ readonly metadata: Uint8Array;
346
+ /**
347
+ * The object's size in bytes.
348
+ */
349
+ readonly size: number;
350
+ /**
351
+ * The last updated time.
352
+ */
353
+ readonly updatedAt: Date;
354
+ }
355
+
344
356
  /**
345
357
  * A packed upload handle for efficiently uploading multiple objects
346
358
  * together. Objects are packed into shared slabs to avoid wasting storage.
@@ -503,7 +515,7 @@ export class Sdk {
503
515
  * }
504
516
  * ```
505
517
  */
506
- download(object: PinnedObject, options?: any | null): ReadableStream;
518
+ download(object: PinnedObject, options?: DownloadOptions): ReadableStream;
507
519
  /**
508
520
  * Returns a list of usable hosts, optionally filtered by a HostQuery.
509
521
  */
@@ -534,8 +546,11 @@ export class Sdk {
534
546
  pinObject(object: PinnedObject): Promise<void>;
535
547
  /**
536
548
  * Prunes unused slabs from the indexer.
549
+ *
550
+ * `before` prunes only slabs pinned before that date. Omitting it leaves
551
+ * the cutoff to the indexer.
537
552
  */
538
- pruneSlabs(): Promise<void>;
553
+ pruneSlabs(before?: Date | null): Promise<void>;
539
554
  /**
540
555
  * Deletes a sharing key along with all of its attachments, revoking
541
556
  * recipients' access. Account tokens already issued stay valid until they
@@ -560,10 +575,6 @@ export class Sdk {
560
575
  * Lists the account's sharing keys, most recently created first.
561
576
  */
562
577
  sharingKeys(offset: number, limit: number): Promise<KeyRecord[]>;
563
- /**
564
- * Retrieves a pinned slab from the indexer by its hex ID.
565
- */
566
- slab(slab_id: string): Promise<PinnedSlab>;
567
578
  /**
568
579
  * Detaches an object from a sharing key, leaving the key and its other
569
580
  * attachments in place.
@@ -585,14 +596,14 @@ export class Sdk {
585
596
  * await sdk.pinObject(obj);
586
597
  * ```
587
598
  */
588
- upload(object: PinnedObject, source: ReadableStream, options?: any | null): Promise<PinnedObject>;
599
+ upload(object: PinnedObject, source: ReadableStream, options?: UploadOptions): Promise<PinnedObject>;
589
600
  /**
590
601
  * Starts a packed upload for efficiently uploading multiple small objects.
591
602
  * Objects smaller than the slab size (~40 MiB) are packed into shared slabs
592
603
  * to avoid wasting storage. Call `add(data)` for each object, then
593
604
  * `finalize()` to get the resulting `PinnedObject` handles.
594
605
  */
595
- uploadPacked(options?: any | null): PackedUpload;
606
+ uploadPacked(options?: PackedUploadOptions): PackedUpload;
596
607
  }
597
608
 
598
609
  /**
@@ -613,7 +624,7 @@ export class SharedSdk {
613
624
  /**
614
625
  * Streams a shared object's data.
615
626
  */
616
- download(object: PinnedObject, options?: any | null): ReadableStream;
627
+ download(object: PinnedObject, options?: DownloadOptions): ReadableStream;
617
628
  /**
618
629
  * Returns the hosts serving this key's objects, optionally filtered by a
619
630
  * HostQuery. Mirrors `Sdk.hosts()` but is scoped to the sharing key, so the
@@ -624,6 +635,12 @@ export class SharedSdk {
624
635
  * Fetches and decrypts one object the key grants access to.
625
636
  */
626
637
  object(id: string): Promise<PinnedObject>;
638
+ /**
639
+ * Lists a page of the objects the key grants access to without their
640
+ * slabs, which is much smaller and faster than `objects`. Each summary
641
+ * has the object's id, size, decrypted metadata and timestamps.
642
+ */
643
+ objectSummaries(offset: number, limit: number): Promise<ObjectSummary[]>;
627
644
  /**
628
645
  * Lists and decrypts a page of the objects the key grants access to.
629
646
  */
@@ -700,6 +717,7 @@ export interface InitOutput {
700
717
  readonly __wbg_builder_free: (a: number, b: number) => void;
701
718
  readonly __wbg_keyrecord_free: (a: number, b: number) => void;
702
719
  readonly __wbg_objectevent_free: (a: number, b: number) => void;
720
+ readonly __wbg_objectsummary_free: (a: number, b: number) => void;
703
721
  readonly __wbg_packedupload_free: (a: number, b: number) => void;
704
722
  readonly __wbg_pinnedobject_free: (a: number, b: number) => void;
705
723
  readonly __wbg_sdk_free: (a: number, b: number) => void;
@@ -718,7 +736,7 @@ export interface InitOutput {
718
736
  readonly builder_requestConnection: (a: number) => any;
719
737
  readonly builder_responseUrl: (a: number) => [number, number, number, number];
720
738
  readonly builder_waitForApproval: (a: number) => any;
721
- readonly encodedSize: (a: number, b: number, c: number) => number;
739
+ readonly encodedSize: (a: number, b: number, c: number) => [number, number, number];
722
740
  readonly generateRecoveryPhrase: () => [number, number];
723
741
  readonly init: () => void;
724
742
  readonly keyrecord_description: (a: number) => [number, number];
@@ -728,6 +746,11 @@ export interface InitOutput {
728
746
  readonly objectevent_id: (a: number) => [number, number];
729
747
  readonly objectevent_object: (a: number) => number;
730
748
  readonly objectevent_updatedAt: (a: number) => any;
749
+ readonly objectsummary_createdAt: (a: number) => any;
750
+ readonly objectsummary_id: (a: number) => [number, number];
751
+ readonly objectsummary_metadata: (a: number) => [number, number];
752
+ readonly objectsummary_size: (a: number) => number;
753
+ readonly objectsummary_updatedAt: (a: number) => any;
731
754
  readonly packedupload_add: (a: number, b: any) => any;
732
755
  readonly packedupload_cancel: (a: number) => void;
733
756
  readonly packedupload_finalize: (a: number) => any;
@@ -758,13 +781,12 @@ export interface InitOutput {
758
781
  readonly sdk_objectFromShareUrl: (a: number, b: number, c: number) => any;
759
782
  readonly sdk_objectShareUrl: (a: number, b: number, c: any) => [number, number, number, number];
760
783
  readonly sdk_pinObject: (a: number, b: number) => any;
761
- readonly sdk_pruneSlabs: (a: number) => any;
784
+ readonly sdk_pruneSlabs: (a: number, b: number) => any;
762
785
  readonly sdk_revokeSharingKey: (a: number, b: number) => any;
763
786
  readonly sdk_shareObject: (a: number, b: number, c: number) => any;
764
787
  readonly sdk_sharedObjects: (a: number, b: number, c: number, d: number) => any;
765
788
  readonly sdk_sharingKey: (a: number, b: number) => any;
766
789
  readonly sdk_sharingKeys: (a: number, b: number, c: number) => any;
767
- readonly sdk_slab: (a: number, b: number, c: number) => any;
768
790
  readonly sdk_unshareObject: (a: number, b: number, c: number, d: number) => any;
769
791
  readonly sdk_updateObjectMetadata: (a: number, b: number) => any;
770
792
  readonly sdk_upload: (a: number, b: number, c: any, d: number) => any;
@@ -774,6 +796,7 @@ export interface InitOutput {
774
796
  readonly sharedsdk_download: (a: number, b: number, c: number) => [number, number, number];
775
797
  readonly sharedsdk_hosts: (a: number, b: number) => any;
776
798
  readonly sharedsdk_object: (a: number, b: number, c: number) => any;
799
+ readonly sharedsdk_objectSummaries: (a: number, b: number, c: number) => any;
777
800
  readonly sharedsdk_objects: (a: number, b: number, c: number) => any;
778
801
  readonly sharedsdk_stats: (a: number) => any;
779
802
  readonly sharingkey_fromSeed: (a: number, b: number) => [number, number, number];
@@ -794,13 +817,13 @@ export interface InitOutput {
794
817
  readonly intounderlyingsink_write: (a: number, b: any) => any;
795
818
  readonly intounderlyingsource_cancel: (a: number) => void;
796
819
  readonly intounderlyingsource_pull: (a: number, b: any) => any;
797
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke___wasm_bindgen_a939ac12ad536d64___JsValue__core_608f92abc48d28da___result__Result_____wasm_bindgen_a939ac12ad536d64___JsError___true_: (a: number, b: number, c: any) => [number, number];
798
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke___wasm_bindgen_a939ac12ad536d64___sys__Undefined__core_608f92abc48d28da___result__Result_____wasm_bindgen_a939ac12ad536d64___JsError___true_: (a: number, b: number, c: any) => [number, number];
799
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke___wasm_bindgen_a939ac12ad536d64___sys__Undefined__core_608f92abc48d28da___result__Result_____wasm_bindgen_a939ac12ad536d64___JsError___true__3: (a: number, b: number, c: any) => [number, number];
800
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke___wasm_bindgen_a939ac12ad536d64___sys__Undefined__core_608f92abc48d28da___result__Result_____wasm_bindgen_a939ac12ad536d64___JsError___true__4: (a: number, b: number, c: any) => [number, number];
801
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke___js_sys_7efaea6b2290715d___Function_fn_wasm_bindgen_a939ac12ad536d64___JsValue_____wasm_bindgen_a939ac12ad536d64___sys__Undefined___js_sys_7efaea6b2290715d___Function_fn_wasm_bindgen_a939ac12ad536d64___JsValue_____wasm_bindgen_a939ac12ad536d64___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
802
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke___wasm_bindgen_a939ac12ad536d64___JsValue______true_: (a: number, b: number, c: any) => void;
803
- readonly wasm_bindgen_a939ac12ad536d64___convert__closures_____invoke_______true_: (a: number, b: number) => void;
820
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke___wasm_bindgen_63576b009277873___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_63576b009277873___JsError___true_: (a: number, b: number, c: any) => [number, number];
821
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke___wasm_bindgen_63576b009277873___sys__Undefined__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_63576b009277873___JsError___true_: (a: number, b: number, c: any) => [number, number];
822
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke___wasm_bindgen_63576b009277873___sys__Undefined__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_63576b009277873___JsError___true__3: (a: number, b: number, c: any) => [number, number];
823
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke___wasm_bindgen_63576b009277873___sys__Undefined__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_63576b009277873___JsError___true__4: (a: number, b: number, c: any) => [number, number];
824
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke___js_sys_eaf17bee0f0de45e___Function_fn_wasm_bindgen_63576b009277873___JsValue_____wasm_bindgen_63576b009277873___sys__Undefined___js_sys_eaf17bee0f0de45e___Function_fn_wasm_bindgen_63576b009277873___JsValue_____wasm_bindgen_63576b009277873___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
825
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke___wasm_bindgen_63576b009277873___JsValue______true_: (a: number, b: number, c: any) => void;
826
+ readonly wasm_bindgen_63576b009277873___convert__closures_____invoke_______true_: (a: number, b: number) => void;
804
827
  readonly __wbindgen_malloc: (a: number, b: number) => number;
805
828
  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
806
829
  readonly __wbindgen_exn_store: (a: number) => void;