@siafoundation/sia-storage 0.1.2 → 0.1.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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/relay.ts","../src/stream/fallback.ts","../src/stream/page.ts"],"sourcesContent":["// Browser entry; lazily initializes WASM on first use.\n\nimport wasmInit from '../wasm/sia_storage_wasm.js'\n\nexport { detectPrivateRelay } from './relay'\nexport { enableStreaming, openStreams } from './stream/page'\nexport type {\n AppCredentials,\n CredentialsFor,\n FileOptions,\n SharedCredentials,\n StreamedFile,\n Streams,\n StreamingOptions,\n} from './stream/page'\n\nlet initPromise: Promise<unknown> | null = null\n\n/** Initialize the WASM module. Safe to call multiple times. */\nexport async function initSia(): Promise<void> {\n if (!initPromise) initPromise = wasmInit()\n await initPromise\n}\n\nexport {\n AppKey,\n Builder,\n KeyRecord,\n ObjectEvent,\n PackedUpload,\n PinnedObject,\n Sdk,\n SharedSdk,\n SharingKey,\n encodedSize,\n generateRecoveryPhrase,\n setLogger,\n validateRecoveryPhrase,\n} from '../wasm/sia_storage_wasm.js'\nexport type {\n Account,\n AppMetadata,\n DownloadOptions,\n Host,\n HostQuery,\n KeyStats,\n PinnedSlab,\n Sector,\n SealedObject,\n ShardProgress,\n Slab,\n UploadOptions,\n} from '../wasm/sia_storage_wasm.js'\n","/**\n * Whether the visitor is on iCloud Private Relay, which the SDK does not work\n * through. Show them a way to turn it off in iCloud settings.\n *\n * Asks check-relay.sia.storage, a Sia Foundation service that compares the\n * caller's address with Apple's published list of Private Relay addresses.\n * Only Safari's traffic goes through Private Relay, so other browsers resolve\n * false without a request. The answer is kept for the page's lifetime, and a\n * failure to get one resolves false.\n */\nconst RELAY_CHECK_URL = 'https://check-relay.sia.storage'\nconst TIMEOUT = 5000\n\nlet answer: Promise<boolean> | undefined\n\nexport function detectPrivateRelay(): Promise<boolean> {\n answer ??=\n typeof navigator === 'undefined'\n ? Promise.resolve(false)\n : checkPrivateRelay(navigator.userAgent, fetch)\n return answer\n}\n\n/** `detectPrivateRelay` without the caching, for tests. */\nexport async function checkPrivateRelay(\n userAgent: string,\n request: typeof fetch,\n): Promise<boolean> {\n const safari =\n /Version\\/\\S+.*Safari\\//.test(userAgent) &&\n !/Chrome|Chromium|CriOS|FxiOS|Edg|OPR|OPiOS|Ddg\\/|Android/.test(userAgent)\n if (!safari) return false\n try {\n const response = await request(RELAY_CHECK_URL, {\n signal: AbortSignal.timeout(TIMEOUT),\n })\n const body = (await response.json()) as { relay?: unknown }\n return body.relay === true\n } catch {\n return false\n }\n}\n","/**\n * Previews and downloads for pages that cannot stream through the service\n * worker, where the page reads the object itself. A preview holds the whole\n * file in memory as a Blob. A download writes straight to disk through the\n * save picker where the browser has one, and buffers a Blob where it does not.\n */\nimport type { PinnedObject, Sdk, SharedSdk } from '../../wasm/sia_storage_wasm.js'\n\ntype AnySdk = Pick<Sdk | SharedSdk, 'download'>\n\ntype Chunk = Uint8Array<ArrayBuffer>\n\n// The File System Access save picker, in Chromium only and not yet in\n// TypeScript's DOM types.\ntype SavePicker = (options: {\n suggestedName: string\n}) => Promise<FileSystemFileHandle & { createWritable(): Promise<WritableStream<Chunk>> }>\n\nfunction chunks(stream: ReadableStream): ReadableStreamDefaultReader<Chunk> {\n return stream.getReader() as ReadableStreamDefaultReader<Chunk>\n}\n\n/** The whole object as a Blob, stopping early when `signal` aborts. */\nexport async function readBlob(\n sdk: AnySdk,\n object: PinnedObject,\n type: string | undefined,\n onProgress: (bytesDownloaded: number) => void,\n signal: AbortSignal,\n) {\n const reader = chunks(sdk.download(object))\n const onAbort = () => void reader.cancel()\n signal.addEventListener('abort', onAbort)\n const parts: Chunk[] = []\n let bytesDownloaded = 0\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n parts.push(value)\n bytesDownloaded += value.length\n onProgress(bytesDownloaded)\n }\n } finally {\n signal.removeEventListener('abort', onAbort)\n }\n signal.throwIfAborted()\n return new Blob(parts, type ? { type } : {})\n}\n\nexport function saveBlob(blob: Blob, name: string) {\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = name\n a.click()\n // Some browsers cancel the download if the URL goes away in the same task.\n setTimeout(() => URL.revokeObjectURL(url), 0)\n}\n\n/** False if the visitor closes the save picker. */\nexport async function saveToDisk(\n sdk: AnySdk,\n object: PinnedObject,\n name: string,\n type: string | undefined,\n onProgress: (bytesDownloaded: number) => void,\n) {\n const picker = (window as { showSaveFilePicker?: SavePicker })\n .showSaveFilePicker\n if (!picker) {\n const blob = await readBlob(\n sdk,\n object,\n type,\n onProgress,\n new AbortController().signal,\n )\n saveBlob(blob, name)\n return true\n }\n\n // The picker must open inside the click that asked for it, so nothing is\n // awaited before this call.\n let handle: Awaited<ReturnType<SavePicker>>\n try {\n handle = await picker.call(window, { suggestedName: name })\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') return false\n throw e\n }\n let bytesDownloaded = 0\n const progress = new TransformStream<Chunk, Chunk>({\n transform(chunk, controller) {\n bytesDownloaded += chunk.length\n onProgress(bytesDownloaded)\n controller.enqueue(chunk)\n },\n })\n const file = await handle.createWritable()\n await (sdk.download(object) as ReadableStream<Chunk>)\n .pipeThrough(progress)\n .pipeTo(file)\n return true\n}\n","/**\n * The page side of streaming.\n *\n * `openStreams` hands out same-origin URLs that the library's service worker\n * answers with ranged reads of an object, so a `<video>` seeks without loading\n * the whole file and a download goes to the browser's download manager instead\n * of the page's memory. The worker connects its own SDK of the same kind, with\n * credentials this page hands it over a message port and it keeps in memory.\n * Where the worker is unavailable, the same calls read the object in the page.\n */\nimport type {\n AppKey,\n AppMetadata,\n PinnedObject,\n SealedObject,\n Sdk,\n SharedSdk,\n} from '../../wasm/sia_storage_wasm.js'\nimport { reportStreamError, streamErrorMessage } from './errors'\nimport { readBlob, saveToDisk } from './fallback'\nimport {\n type ClientReply,\n type Connection,\n isAppMetadata,\n type PageMessage,\n requestReply,\n type SourceReply,\n streamPath,\n type WorkerMessage,\n} from './protocol'\n\nexport type StreamingOptions = {\n /** Where the site serves the worker file. Defaults to `/sia-storage-sw.js`. */\n workerUrl?: string\n /** The worker's scope. Defaults to the directory `workerUrl` is in. */\n scope?: string\n /** `'module'` when `workerUrl` is an ES module worker of your own. */\n type?: WorkerType\n}\n\n/** What a `SharedSdk` was connected with. */\nexport type SharedCredentials = { indexerUrl: string; seed: string }\n\n/** What an `Sdk`'s `Builder` was made with. The key comes from `sdk.appKey()`. */\nexport type AppCredentials = { indexerUrl: string; appMeta: AppMetadata }\n\n/** The credentials `openStreams` takes for each kind of SDK. */\nexport type CredentialsFor<S extends Sdk | SharedSdk> = S extends SharedSdk\n ? SharedCredentials\n : AppCredentials\n\nexport type FileOptions = {\n /** Used for downloads and for the browser's media handling. */\n name: string\n /** The MIME type. Defaults to `application/octet-stream`. */\n type?: string\n /** A failure after streaming started. Without it, failures are logged. */\n onError?: (message: string) => void\n /** Bytes read so far, when the page reads the object itself. */\n onProgress?: (bytesDownloaded: number) => void\n}\n\n/** A URL for an `<img>`, `<video>`, `<audio>`, `<iframe>` or `fetch`. */\nexport type StreamedFile = {\n url: string\n /** The whole file, when the page read it itself instead of streaming. */\n blob?: Blob\n /** Frees the URL. Call it when the element showing it goes away. */\n release(): void\n}\n\nexport type Streams = {\n /** A URL serving the object. Streams where it can, else reads it whole. */\n url(\n object: PinnedObject,\n options: FileOptions & { signal?: AbortSignal },\n ): Promise<StreamedFile>\n /**\n * Saves the object, from a click handler. Streams to the browser's download\n * manager where it can, else opens the save picker, else saves a Blob.\n * Awaits nothing before the picker opens, so the browser still counts it as\n * part of the click.\n */\n download(\n object: PinnedObject,\n options: FileOptions,\n ): Promise<'streaming' | 'saved' | 'cancelled'>\n /**\n * Cancels the streams this handle made. Other handles on the same SDK keep\n * theirs. Call it when you stop using the handle.\n */\n close(): void\n}\n\nconst DEFAULT_WORKER_URL = '/sia-storage-sw.js'\n// Covers registering the worker, its first activation, and the handshake.\nconst SETUP_TIMEOUT = 5000\n\n/** A stream URL this page handed out, and what the worker needs to serve it. */\ntype StreamEntry = {\n handle: Opened\n objectId: string\n sealed: SealedObject | undefined\n size: number\n name: string\n mime: string\n onError: ((message: string) => void) | undefined\n}\n\n/**\n * One `openStreams` call, and how the worker reconnects its SDK. Each call gets\n * its own session, so closing one handle leaves another on the same SDK open.\n */\ntype Opened = { session: string; connection: Connection; appKey?: AppKey }\n\nconst entries = new Map<string, StreamEntry>()\nconst opened = new Set<Opened>()\nlet worker: { clientId: string; scope: string } | undefined\nlet initialization: Promise<boolean> | undefined\nlet initOptions: StreamingOptions = {}\n// The page's controller when setup last failed. Undefined unless it failed.\nlet failedController: ServiceWorker | null | undefined\n\n/**\n * Registers the streaming worker and resolves whether this page can stream.\n * `openStreams` calls it with the defaults, so call it yourself only to pass\n * options, and before the first `openStreams`. Later calls return the first\n * call's answer, except when setup failed or timed out and a different worker\n * has since taken control of the page. Then the next call tries again, with\n * the first call's options.\n */\nexport function enableStreaming(\n options: StreamingOptions = {},\n): Promise<boolean> {\n if (!initialization) {\n initOptions = options\n } else if (failedController === undefined) {\n return initialization\n } else {\n // Only a new controller is worth a retry. Retrying against the same one,\n // or none, would wait out SETUP_TIMEOUT again on every call.\n const controller = navigator.serviceWorker.controller\n if (!controller || controller === failedController) return initialization\n }\n failedController = undefined\n initialization = initialize(initOptions).catch(() => {\n failedController = navigator.serviceWorker.controller\n return false\n })\n return initialization\n}\n\nasync function initialize({\n workerUrl = DEFAULT_WORKER_URL,\n scope,\n type,\n}: StreamingOptions) {\n if (\n typeof window === 'undefined' ||\n !('serviceWorker' in navigator) ||\n !window.isSecureContext\n ) {\n return false\n }\n const { serviceWorker } = navigator\n serviceWorker.addEventListener('message', onWorkerMessage)\n const deadline = AbortSignal.timeout(SETUP_TIMEOUT)\n // A hard reload (Shift+Reload) loads the page without its worker, and an\n // active worker only claims pages when it first activates. Waiting would\n // run out the whole deadline, so fall back at once.\n const existing = await within(serviceWorker.getRegistration(), deadline)\n if (existing?.active && !serviceWorker.controller) return false\n const registration = await within(\n serviceWorker\n .register(workerUrl, { ...(scope && { scope }), ...(type && { type }) })\n .then(() => serviceWorker.ready),\n deadline,\n )\n // On a first visit, the page is controlled once the new worker claims it.\n if (!serviceWorker.controller) {\n await within(\n new Promise((resolve) =>\n serviceWorker.addEventListener('controllerchange', resolve, {\n once: true,\n }),\n ),\n deadline,\n )\n }\n const controller = serviceWorker.controller\n if (!controller) return false\n const request = { type: 'sia-client' } satisfies PageMessage\n const reply = (await requestReply(controller, request, {\n signal: deadline,\n })) as ClientReply\n if (!reply.supported) return false\n worker = { clientId: reply.clientId, scope: registration.scope }\n return true\n}\n\n/** `promise`, or a rejection if `signal` aborts first. */\nfunction within<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n return new Promise((resolve, reject) => {\n signal.throwIfAborted()\n signal.addEventListener('abort', () => reject(signal.reason), {\n once: true,\n })\n promise.then(resolve, reject)\n })\n}\n\n/**\n * Previews and downloads for one SDK's objects, from an `Sdk` or a `SharedSdk`.\n *\n * The worker cannot use this page's SDK, so it connects its own with\n * `credentials`: the indexer URL and seed a `SharedSdk` was connected with, or\n * the indexer URL and app metadata an `Sdk`'s `Builder` was made with.\n */\nexport function openStreams<S extends Sdk | SharedSdk>(\n sdk: S,\n credentials: CredentialsFor<S>,\n): Streams {\n const handle = openedFor(sdk, credentials)\n opened.add(handle)\n // Started now, so it has usually finished by the first download click,\n // which cannot wait for it.\n void enableStreaming()\n return {\n async url(object, options) {\n await enableStreaming()\n options.signal?.throwIfAborted()\n const stream = streamUrl(handle, object, options)\n if (stream) return stream\n const blob = await readBlob(\n sdk,\n object,\n options.type,\n options.onProgress ?? (() => {}),\n options.signal ?? new AbortController().signal,\n )\n const url = URL.createObjectURL(blob)\n return { url, blob, release: () => URL.revokeObjectURL(url) }\n },\n async download(object, options) {\n const stream = streamUrl(handle, object, options)\n if (stream) {\n // A hidden frame, so a worker error loads there instead of replacing\n // the page. Nothing signals when the download has started, so it stays.\n const frame = document.createElement('iframe')\n frame.hidden = true\n frame.src = `${stream.url}?download=1`\n document.body.append(frame)\n // Never released here. The URL stays valid until `close()`, so\n // delayed starts and browser retries still work.\n return 'streaming'\n }\n const saved = await saveToDisk(\n sdk,\n object,\n options.name,\n options.type,\n options.onProgress ?? (() => {}),\n )\n return saved ? 'saved' : 'cancelled'\n },\n close() {\n releaseStreams(handle)\n },\n }\n}\n\n/**\n * Checked at runtime too, for callers without types. An `Sdk` is told apart\n * by its `appKey()`, which a `SharedSdk` does not have.\n */\nfunction openedFor(sdk: Sdk | SharedSdk, credentials: unknown): Opened {\n const { indexerUrl, seed, appMeta } = (credentials ?? {}) as {\n indexerUrl?: unknown\n seed?: unknown\n appMeta?: unknown\n }\n const session = crypto.randomUUID()\n if (typeof (sdk as Partial<Sdk>).appKey === 'function') {\n if (typeof indexerUrl !== 'string' || !isAppMetadata(appMeta)) {\n throw new TypeError('openStreams(Sdk) needs { indexerUrl, appMeta }.')\n }\n const appKey = (sdk as Sdk).appKey()\n const connection: Connection = {\n kind: 'app',\n indexerUrl,\n appKey: toHex(appKey.export()),\n appMeta,\n }\n return { session, connection, appKey }\n }\n if (typeof indexerUrl !== 'string' || typeof seed !== 'string') {\n throw new TypeError('openStreams(SharedSdk) needs { indexerUrl, seed }.')\n }\n return { session, connection: { kind: 'shared', indexerUrl, seed } }\n}\n\nfunction toHex(bytes: Uint8Array) {\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nfunction streamUrl(\n handle: Opened,\n object: PinnedObject,\n { name, type, onError }: FileOptions,\n): StreamedFile | undefined {\n if (!worker || !navigator.serviceWorker.controller || !opened.has(handle)) {\n return undefined\n }\n const token = crypto.randomUUID()\n entries.set(token, {\n handle,\n objectId: object.id(),\n sealed: handle.appKey && object.seal(handle.appKey),\n size: object.size(),\n name,\n mime: type || 'application/octet-stream',\n onError,\n })\n return {\n url: streamPath(worker.scope, worker.clientId, token),\n release() {\n entries.delete(token)\n const message = { type: 'sia-release', token } satisfies PageMessage\n navigator.serviceWorker.controller?.postMessage(message)\n },\n }\n}\n\nfunction releaseStreams(handle: Opened) {\n for (const [token, entry] of entries) {\n if (entry.handle === handle) entries.delete(token)\n }\n if (!opened.delete(handle)) return\n const message = {\n type: 'sia-release-share',\n session: handle.session,\n } satisfies PageMessage\n navigator.serviceWorker.controller?.postMessage(message)\n}\n\nfunction onWorkerMessage(event: MessageEvent) {\n if (event.source !== navigator.serviceWorker.controller) return\n const data = event.data as WorkerMessage | undefined\n if (data?.type === 'sia-stream-error') {\n const entry = entries.get(data.token)\n if (!entry) return\n const { offset, length } = data\n const range =\n offset !== undefined && length !== undefined\n ? { offset, length }\n : undefined\n if (entry.onError) entry.onError(streamErrorMessage(data.message))\n else reportStreamError(data.message, entry.name, range)\n } else if (data?.type === 'sia-source') {\n const port = event.ports[0]\n if (!port) return\n const entry = entries.get(data.token)\n const reply: SourceReply =\n entry\n ? {\n type: 'source',\n session: entry.handle.session,\n connection: entry.handle.connection,\n objectId: entry.objectId,\n ...(entry.sealed && { sealed: entry.sealed }),\n size: entry.size,\n mime: entry.mime,\n name: entry.name,\n }\n : { type: 'error' }\n port.postMessage(reply)\n port.close()\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/relay.ts","../src/wasm.ts","../src/stream/fallback.ts","../src/stream/page.ts"],"sourcesContent":["// Browser entry; lazily initializes WASM on first use.\n\nimport wasmInit from '../wasm/sia_storage_wasm.js'\n\nexport { detectPrivateRelay } from './relay'\nexport { enableStreaming, openStreams } from './stream/page'\nexport type {\n AppCredentials,\n CredentialsFor,\n FileEvents,\n FileOptions,\n SharedCredentials,\n StreamedFile,\n StreamStatus,\n Streams,\n StreamingOptions,\n} from './stream/page'\n\nlet initPromise: Promise<unknown> | null = null\n\n/** Initialize the WASM module. Safe to call multiple times. */\nexport async function initSia(): Promise<void> {\n if (!initPromise) initPromise = wasmInit()\n await initPromise\n}\n\nexport {\n AppKey,\n Builder,\n KeyRecord,\n ObjectEvent,\n ObjectSummary,\n PackedUpload,\n PinnedObject,\n Sdk,\n SharedSdk,\n SharingKey,\n encodedSize,\n generateRecoveryPhrase,\n setLogger,\n validateRecoveryPhrase,\n} from '../wasm/sia_storage_wasm.js'\nexport type {\n Account,\n AppMetadata,\n DownloadOptions,\n Host,\n HostQuery,\n KeyStats,\n Sector,\n SealedObject,\n ShardProgress,\n Slab,\n UploadOptions,\n} from '../wasm/sia_storage_wasm.js'\n","/**\n * Whether the visitor is on iCloud Private Relay, which the SDK does not work\n * through. Show them a way to turn it off in iCloud settings.\n *\n * Asks check-relay.sia.storage, a Sia Foundation service that compares the\n * caller's address with Apple's published list of Private Relay addresses.\n * Only Safari's traffic goes through Private Relay, so other browsers resolve\n * false without a request. The answer is kept for the page's lifetime, and a\n * failure to get one resolves false.\n */\nconst RELAY_CHECK_URL = 'https://check-relay.sia.storage'\nconst TIMEOUT = 5000\n\nlet answer: Promise<boolean> | undefined\n\nexport function detectPrivateRelay(): Promise<boolean> {\n answer ??=\n typeof navigator === 'undefined'\n ? Promise.resolve(false)\n : checkPrivateRelay(navigator.userAgent, fetch)\n return answer\n}\n\n/** `detectPrivateRelay` without the caching, for tests. */\nexport async function checkPrivateRelay(\n userAgent: string,\n request: typeof fetch,\n): Promise<boolean> {\n const safari =\n /Version\\/\\S+.*Safari\\//.test(userAgent) &&\n !/Chrome|Chromium|CriOS|FxiOS|Edg|OPR|OPiOS|Ddg\\/|Android/.test(userAgent)\n if (!safari) return false\n try {\n const response = await request(RELAY_CHECK_URL, {\n signal: AbortSignal.timeout(TIMEOUT),\n })\n const body = (await response.json()) as { relay?: unknown }\n return body.relay === true\n } catch {\n return false\n }\n}\n","// The build keeps imports of the WebAssembly glue as written and resolves\n// them from dist/, so they must come from a file directly in src/. Code in\n// subfolders imports the glue through here.\nexport { AppKey } from '../wasm/sia_storage_wasm.js'\n","/**\n * Previews and downloads for pages that cannot stream through the service\n * worker, where the page reads the object itself. A preview holds the whole\n * file in memory as a Blob. A download writes straight to disk through the\n * save picker where the browser has one, and buffers a Blob where it does not.\n */\nimport type {\n PinnedObject,\n Sdk,\n SharedSdk,\n ShardProgress,\n} from '../../wasm/sia_storage_wasm.js'\nimport type { StreamStatus } from './protocol'\n\n/** What a file reports while it downloads, on the worker or in the page. */\nexport type FileEvents = {\n /**\n * Total bytes this URL has received. It is a transfer count, not how much of\n * the file is held: a range read again after a seek counts again, so it can\n * pass the file size, and a new URL for the same file starts from zero. For\n * how much of a video is buffered, read the media element's `buffered`.\n */\n onProgress?: (bytesDownloaded: number) => void\n /**\n * One call per piece the SDK read from a host. A download reads each slab in\n * chunks, so the same slab and shard come again for every chunk, and only\n * the first `minShards` hosts to answer for a chunk are reported.\n */\n onShard?: (progress: ShardProgress) => void\n /** Each change of what the URL is doing. See `StreamStatus`. */\n onStatus?: (status: StreamStatus) => void\n}\n\ntype AnySdk = Pick<Sdk | SharedSdk, 'download'>\n\ntype Chunk = Uint8Array<ArrayBuffer>\n\n// The File System Access save picker, in Chromium only and not yet in\n// TypeScript's DOM types.\ntype SavePicker = (options: {\n suggestedName: string\n}) => Promise<FileSystemFileHandle & { createWritable(): Promise<WritableStream<Chunk>> }>\n\nfunction chunks(stream: ReadableStream): ReadableStreamDefaultReader<Chunk> {\n return stream.getReader() as ReadableStreamDefaultReader<Chunk>\n}\n\n/**\n * Calls an app's callback. One that throws is reported to the console rather\n * than ending the download or skipping the callbacks after it.\n */\nexport function notify<T>(callback: ((value: T) => void) | undefined, value: T) {\n if (!callback) return\n try {\n callback(value)\n } catch (error) {\n reportError(error)\n }\n}\n\n// Asks the SDK for shard reports only when someone listens for them.\nfunction download(sdk: AnySdk, object: PinnedObject, events: FileEvents) {\n const { onShard } = events\n return sdk.download(\n object,\n onShard\n ? { onShardDownloaded: (shard: ShardProgress) => notify(onShard, shard) }\n : {},\n )\n}\n\n// The status cycle the worker reports for a stream URL, for a read in the page.\nfunction track({ onProgress, onStatus }: FileEvents) {\n let received = 0\n let started = false\n notify(onStatus, 'connecting')\n return {\n chunk(length: number) {\n if (!started) notify(onStatus, 'downloading')\n started = true\n received += length\n notify(onProgress, received)\n },\n end() {\n notify(onStatus, 'idle')\n },\n }\n}\n\n/** The whole object as a Blob, stopping early when `signal` aborts. */\nexport async function readBlob(\n sdk: AnySdk,\n object: PinnedObject,\n type: string | undefined,\n events: FileEvents,\n signal: AbortSignal,\n) {\n const progress = track(events)\n let reader: ReadableStreamDefaultReader<Chunk> | undefined\n const onAbort = () => void reader?.cancel()\n signal.addEventListener('abort', onAbort)\n const parts: Chunk[] = []\n try {\n // Inside the try, so a download that fails to start still reports idle.\n reader = chunks(download(sdk, object, events))\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n parts.push(value)\n progress.chunk(value.length)\n }\n } finally {\n signal.removeEventListener('abort', onAbort)\n progress.end()\n }\n signal.throwIfAborted()\n return new Blob(parts, type ? { type } : {})\n}\n\nexport function saveBlob(blob: Blob, name: string) {\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = name\n a.click()\n // Some browsers cancel the download if the URL goes away in the same task.\n setTimeout(() => URL.revokeObjectURL(url), 0)\n}\n\n/** False if the visitor closes the save picker. */\nexport async function saveToDisk(\n sdk: AnySdk,\n object: PinnedObject,\n name: string,\n type: string | undefined,\n events: FileEvents,\n) {\n const picker = (window as { showSaveFilePicker?: SavePicker })\n .showSaveFilePicker\n if (!picker) {\n const blob = await readBlob(\n sdk,\n object,\n type,\n events,\n new AbortController().signal,\n )\n saveBlob(blob, name)\n return true\n }\n\n // The picker must open inside the click that asked for it, so nothing is\n // awaited before this call.\n let handle: Awaited<ReturnType<SavePicker>>\n try {\n handle = await picker.call(window, { suggestedName: name })\n } catch (e) {\n if (e instanceof DOMException && e.name === 'AbortError') return false\n throw e\n }\n const file = await handle.createWritable()\n const progress = track(events)\n const counter = new TransformStream<Chunk, Chunk>({\n transform(chunk, controller) {\n progress.chunk(chunk.length)\n controller.enqueue(chunk)\n },\n })\n try {\n await (download(sdk, object, events) as ReadableStream<Chunk>)\n .pipeThrough(counter)\n .pipeTo(file)\n } finally {\n progress.end()\n }\n return true\n}\n","/**\n * The page side of streaming.\n *\n * `openStreams` hands out same-origin URLs that the library's service worker\n * answers with ranged reads of an object, so a `<video>` seeks without loading\n * the whole file and a download goes to the browser's download manager instead\n * of the page's memory. The worker connects its own SDK of the same kind, with\n * credentials this page hands it over a message port and it keeps in memory.\n * Where the worker is unavailable, the same calls read the object in the page.\n */\nimport type {\n AppMetadata,\n PinnedObject,\n SealedObject,\n Sdk,\n SharedSdk,\n} from '../../wasm/sia_storage_wasm.js'\nimport { AppKey } from '../wasm'\nimport { reportStreamError, streamErrorMessage } from './errors'\nimport { type FileEvents, notify, readBlob, saveToDisk } from './fallback'\nimport {\n type ClientReply,\n type Connection,\n isAppMetadata,\n type PageMessage,\n requestReply,\n type SourceReply,\n type StreamEvent,\n streamPath,\n type WorkerMessage,\n} from './protocol'\n\nexport type { FileEvents } from './fallback'\nexport type { StreamStatus } from './protocol'\n\nexport type StreamingOptions = {\n /** Where the site serves the worker file. Defaults to `/sia-storage-sw.js`. */\n workerUrl?: string\n /** The worker's scope. Defaults to the directory `workerUrl` is in. */\n scope?: string\n /** `'module'` when `workerUrl` is an ES module worker of your own. */\n type?: WorkerType\n}\n\n/** What a `SharedSdk` was connected with. */\nexport type SharedCredentials = { indexerUrl: string; seed: string }\n\n/** What an `Sdk`'s `Builder` was made with. The key comes from `sdk.appKey()`. */\nexport type AppCredentials = { indexerUrl: string; appMeta: AppMetadata }\n\n/** The credentials `openStreams` takes for each kind of SDK. */\nexport type CredentialsFor<S extends Sdk | SharedSdk> = S extends SharedSdk\n ? SharedCredentials\n : AppCredentials\n\nexport type FileOptions = FileEvents & {\n /** Used for downloads and for the browser's media handling. */\n name: string\n /** The MIME type. Defaults to `application/octet-stream`. */\n type?: string\n /** A failure after streaming started. Without it, failures are logged. */\n onError?: (message: string) => void\n}\n\n/** A URL for an `<img>`, `<video>`, `<audio>`, `<iframe>` or `fetch`. */\nexport type StreamedFile = {\n url: string\n /** The whole file, when the page read it itself instead of streaming. */\n blob?: Blob\n /**\n * Opens connections to the hosts holding the start of the file, so a play\n * that follows gets its first bytes sooner. It reads one byte, which asks\n * each host of the first slab for a single 64-byte segment, and reports no\n * status or progress. Resolves when the byte arrives, or at once for a file\n * the page read itself. Never rejects, since a play still works without it.\n * Call it when a play is likely, such as when the pointer reaches a Play\n * button. The earlier before the play, the more it saves.\n */\n warm(): Promise<void>\n /** Frees the URL. Call it when the element showing it goes away. */\n release(): void\n}\n\nexport type Streams = {\n /** A URL serving the object. Streams where it can, else reads it whole. */\n url(\n object: PinnedObject,\n options: FileOptions & { signal?: AbortSignal },\n ): Promise<StreamedFile>\n /**\n * Saves the object, from a click handler. Streams to the browser's download\n * manager where it can, else opens the save picker, else saves a Blob.\n * Awaits nothing before the picker opens, so the browser still counts it as\n * part of the click.\n */\n download(\n object: PinnedObject,\n options: FileOptions,\n ): Promise<'streaming' | 'saved' | 'cancelled'>\n /**\n * Cancels the streams this handle made. Other handles on the same SDK keep\n * theirs. Call it when you stop using the handle.\n */\n close(): void\n}\n\nconst DEFAULT_WORKER_URL = '/sia-storage-sw.js'\n// Covers registering the worker, its first activation, and the handshake.\nconst SETUP_TIMEOUT = 5000\n\n/** A stream URL this page handed out, and what the worker needs to serve it. */\ntype StreamEntry = {\n handle: Opened\n objectId: string\n sealed: SealedObject | undefined\n size: number\n name: string\n mime: string\n onError: ((message: string) => void) | undefined\n events: FileEvents\n // Bytes reported for this URL so far, across all of its range requests.\n received: number\n}\n\n/**\n * One `openStreams` call, and how the worker reconnects its SDK. Each call gets\n * its own session, so closing one handle leaves another on the same SDK open.\n *\n * `sealKey` seals each object the page streams, so the worker opens it instead\n * of asking the indexer for it again. An `Sdk`'s is a copy of its app key. A\n * `SharedSdk` has none, so it gets a random key that only this page and its\n * worker see. Either is freed on close.\n */\ntype Opened = { session: string; connection: Connection; sealKey?: AppKey }\n\nconst entries = new Map<string, StreamEntry>()\nconst opened = new Set<Opened>()\nlet worker: { clientId: string; scope: string } | undefined\nlet initialization: Promise<boolean> | undefined\nlet initOptions: StreamingOptions = {}\n// The page's controller when setup last failed. Undefined unless it failed.\nlet failedController: ServiceWorker | null | undefined\n\n/**\n * Registers the streaming worker and resolves whether this page can stream.\n * `openStreams` calls it with the defaults, so call it yourself only to pass\n * options, and before the first `openStreams`. Later calls return the first\n * call's answer, except when setup failed or timed out and a different worker\n * has since taken control of the page. Then the next call tries again, with\n * the first call's options.\n */\nexport function enableStreaming(\n options: StreamingOptions = {},\n): Promise<boolean> {\n if (!initialization) {\n initOptions = options\n } else if (failedController === undefined) {\n return initialization\n } else {\n // Only a new controller is worth a retry. Retrying against the same one,\n // or none, would wait out SETUP_TIMEOUT again on every call.\n const controller = navigator.serviceWorker.controller\n if (!controller || controller === failedController) return initialization\n }\n failedController = undefined\n initialization = initialize(initOptions).catch(() => {\n failedController = navigator.serviceWorker.controller\n return false\n })\n return initialization\n}\n\nasync function initialize({\n workerUrl = DEFAULT_WORKER_URL,\n scope,\n type,\n}: StreamingOptions) {\n if (\n typeof window === 'undefined' ||\n !('serviceWorker' in navigator) ||\n !window.isSecureContext\n ) {\n return false\n }\n const { serviceWorker } = navigator\n serviceWorker.addEventListener('message', onWorkerMessage)\n const deadline = AbortSignal.timeout(SETUP_TIMEOUT)\n // A hard reload (Shift+Reload) loads the page without its worker, and an\n // active worker only claims pages when it first activates. Waiting would\n // run out the whole deadline, so fall back at once.\n const existing = await within(serviceWorker.getRegistration(), deadline)\n if (existing?.active && !serviceWorker.controller) return false\n const registration = await within(\n serviceWorker\n .register(workerUrl, { ...(scope && { scope }), ...(type && { type }) })\n .then(() => serviceWorker.ready),\n deadline,\n )\n // On a first visit, the page is controlled once the new worker claims it.\n if (!serviceWorker.controller) {\n await within(\n new Promise((resolve) =>\n serviceWorker.addEventListener('controllerchange', resolve, {\n once: true,\n }),\n ),\n deadline,\n )\n }\n const controller = serviceWorker.controller\n if (!controller) return false\n const request = { type: 'sia-client' } satisfies PageMessage\n const reply = (await requestReply(controller, request, {\n signal: deadline,\n })) as ClientReply\n if (!reply.supported) return false\n worker = { clientId: reply.clientId, scope: registration.scope }\n return true\n}\n\n/** `promise`, or a rejection if `signal` aborts first. */\nfunction within<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n return new Promise((resolve, reject) => {\n signal.throwIfAborted()\n signal.addEventListener('abort', () => reject(signal.reason), {\n once: true,\n })\n promise.then(resolve, reject)\n })\n}\n\n/**\n * Previews and downloads for one SDK's objects, from an `Sdk` or a `SharedSdk`.\n *\n * The worker cannot use this page's SDK, so it connects its own with\n * `credentials`: the indexer URL and seed a `SharedSdk` was connected with, or\n * the indexer URL and app metadata an `Sdk`'s `Builder` was made with.\n */\nexport function openStreams<S extends Sdk | SharedSdk>(\n sdk: S,\n credentials: CredentialsFor<S>,\n): Streams {\n const handle = openedFor(sdk, credentials)\n opened.add(handle)\n // Started now, so it has usually finished by the first download click,\n // which cannot wait for it.\n void enableStreaming()\n return {\n async url(object, options) {\n await enableStreaming()\n options.signal?.throwIfAborted()\n const stream = streamUrl(handle, object, options)\n if (stream) return stream\n const blob = await readBlob(\n sdk,\n object,\n options.type,\n options,\n options.signal ?? new AbortController().signal,\n )\n const url = URL.createObjectURL(blob)\n return {\n url,\n blob,\n warm: async () => {},\n release: () => URL.revokeObjectURL(url),\n }\n },\n async download(object, options) {\n const stream = streamUrl(handle, object, options)\n if (stream) {\n // A hidden frame, so a worker error loads there instead of replacing\n // the page. Nothing signals when the download has started, so it stays.\n const frame = document.createElement('iframe')\n frame.hidden = true\n frame.src = `${stream.url}?download=1`\n document.body.append(frame)\n // Never released here. The URL stays valid until `close()`, so\n // delayed starts and browser retries still work.\n return 'streaming'\n }\n const saved = await saveToDisk(\n sdk,\n object,\n options.name,\n options.type,\n options,\n )\n return saved ? 'saved' : 'cancelled'\n },\n close() {\n releaseStreams(handle)\n },\n }\n}\n\n/**\n * Checked at runtime too, for callers without types. An `Sdk` is told apart\n * by its `appKey()`, which a `SharedSdk` does not have.\n */\nfunction openedFor(sdk: Sdk | SharedSdk, credentials: unknown): Opened {\n const { indexerUrl, seed, appMeta } = (credentials ?? {}) as {\n indexerUrl?: unknown\n seed?: unknown\n appMeta?: unknown\n }\n const session = crypto.randomUUID()\n if (typeof (sdk as Partial<Sdk>).appKey === 'function') {\n if (typeof indexerUrl !== 'string' || !isAppMetadata(appMeta)) {\n throw new TypeError('openStreams(Sdk) needs { indexerUrl, appMeta }.')\n }\n const appKey = (sdk as Sdk).appKey()\n const connection: Connection = {\n kind: 'app',\n indexerUrl,\n appKey: toHex(appKey.export()),\n appMeta,\n }\n return { session, connection, sealKey: appKey }\n }\n if (typeof indexerUrl !== 'string' || typeof seed !== 'string') {\n throw new TypeError('openStreams(SharedSdk) needs { indexerUrl, seed }.')\n }\n const sealing = randomSealKey()\n return {\n session,\n connection: {\n kind: 'shared',\n indexerUrl,\n seed,\n ...(sealing && { sealKey: sealing.hex }),\n },\n ...(sealing && { sealKey: sealing.key }),\n }\n}\n\n/**\n * Undefined when the SDK's WebAssembly is not running, which a page holding a\n * connected `SharedSdk` has always started. The worker then looks objects up.\n */\nfunction randomSealKey() {\n const bytes = crypto.getRandomValues(new Uint8Array(32))\n try {\n return { key: new AppKey(bytes), hex: toHex(bytes) }\n } catch {\n return undefined\n }\n}\n\nfunction toHex(bytes: Uint8Array) {\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nfunction streamUrl(\n handle: Opened,\n object: PinnedObject,\n { name, type, onError, onProgress, onShard, onStatus }: FileOptions,\n): StreamedFile | undefined {\n if (!worker || !navigator.serviceWorker.controller || !opened.has(handle)) {\n return undefined\n }\n const token = crypto.randomUUID()\n entries.set(token, {\n handle,\n objectId: object.id(),\n sealed: handle.sealKey && object.seal(handle.sealKey),\n size: object.size(),\n name,\n mime: type || 'application/octet-stream',\n onError,\n events: { onProgress, onShard, onStatus },\n received: 0,\n })\n const url = streamPath(worker.scope, worker.clientId, token)\n return {\n url,\n async warm() {\n try {\n const response = await fetch(`${url}?warm=1`, {\n headers: { Range: 'bytes=0-0' },\n })\n await response.arrayBuffer()\n } catch {\n // A play after a failed warm-up connects as it would have anyway.\n }\n },\n release() {\n entries.delete(token)\n const message = { type: 'sia-release', token } satisfies PageMessage\n navigator.serviceWorker.controller?.postMessage(message)\n },\n }\n}\n\nfunction releaseStreams(handle: Opened) {\n for (const [token, entry] of entries) {\n if (entry.handle === handle) entries.delete(token)\n }\n if (!opened.delete(handle)) return\n handle.sealKey?.free()\n const message = {\n type: 'sia-release-share',\n session: handle.session,\n } satisfies PageMessage\n navigator.serviceWorker.controller?.postMessage(message)\n}\n\nfunction onWorkerMessage(event: MessageEvent) {\n if (event.source !== navigator.serviceWorker.controller) return\n const data = event.data as WorkerMessage | undefined\n if (data?.type === 'sia-stream-error') {\n const entry = entries.get(data.token)\n if (!entry) return\n const { offset, length } = data\n const range =\n offset !== undefined && length !== undefined\n ? { offset, length }\n : undefined\n if (entry.onError) entry.onError(streamErrorMessage(data.message))\n else reportStreamError(data.message, entry.name, range)\n } else if (data?.type === 'sia-stream-status') {\n notify(entries.get(data.token)?.events.onStatus, data.status)\n } else if (data?.type === 'sia-stream-progress') {\n const entry = entries.get(data.token)\n // The worker can come from a different deploy, so its fields are checked.\n if (!entry || !Array.isArray(data.shards)) return\n const { onProgress, onShard } = entry.events\n for (const shard of data.shards) notify(onShard, shard)\n if (Number.isSafeInteger(data.bytes) && data.bytes > 0) {\n entry.received += data.bytes\n notify(onProgress, entry.received)\n }\n } else if (data?.type === 'sia-source') {\n const port = event.ports[0]\n if (!port) return\n const entry = entries.get(data.token)\n const events: StreamEvent[] = []\n if (entry?.events.onStatus) events.push('status')\n if (entry?.events.onProgress) events.push('progress')\n if (entry?.events.onShard) events.push('shards')\n const reply: SourceReply =\n entry\n ? {\n type: 'source',\n session: entry.handle.session,\n connection: entry.handle.connection,\n objectId: entry.objectId,\n ...(entry.sealed && { sealed: entry.sealed }),\n size: entry.size,\n mime: entry.mime,\n name: entry.name,\n ...(events.length > 0 && { events }),\n }\n : { type: 'error' }\n port.postMessage(reply)\n port.close()\n }\n}\n"],"mappings":"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","names":["AppKey"]}
@@ -245,10 +245,13 @@ export declare class Sdk {
245
245
  deleteObject(key: string): Promise<void>
246
246
  /** Returns metadata about a specific object. */
247
247
  object(key: string): Promise<PinnedObject>
248
- /** Returns metadata about a slab. */
249
- slab(slabId: string): Promise<PinnedSlab>
250
- /** Unpins slabs not used by any object on the account. */
251
- pruneSlabs(): Promise<void>
248
+ /**
249
+ * Unpins slabs not used by any object on the account.
250
+ *
251
+ * `before` prunes only slabs pinned before that time. Omitting it leaves
252
+ * the cutoff to the indexer.
253
+ */
254
+ pruneSlabs(before?: Date | undefined | null): Promise<void>
252
255
  /** Returns the current account. */
253
256
  account(): Promise<Account>
254
257
  /** Creates a signed URL for sharing an object. */
@@ -277,6 +280,11 @@ export declare class SharedSdk {
277
280
  object(id: string): Promise<PinnedObject>
278
281
  /** Lists and decrypts a page of the objects the key grants access to. */
279
282
  objects(offset: number, limit: number): Promise<Array<PinnedObject>>
283
+ /**
284
+ * Lists a page of the objects the key grants access to without their
285
+ * slabs, which is much smaller and faster than `objects`.
286
+ */
287
+ objectSummaries(offset: number, limit: number): Promise<Array<ObjectSummary>>
280
288
  /**
281
289
  * Returns the hosts serving this key's objects, optionally filtered by a
282
290
  * query. Mirrors `Sdk.hosts()` but is scoped to the sharing key, so the
@@ -406,6 +414,20 @@ export interface ObjectsCursor {
406
414
  after: Date
407
415
  }
408
416
 
417
+ /**
418
+ * An object listed without its slabs, as `SharedSdk.objectSummaries`
419
+ * returns it. It cannot be downloaded. Fetch the full object with
420
+ * `SharedSdk.object(id)` to download it.
421
+ */
422
+ export interface ObjectSummary {
423
+ id: string
424
+ /** The object's size in bytes. */
425
+ size: bigint
426
+ metadata: Buffer
427
+ createdAt: Date
428
+ updatedAt: Date
429
+ }
430
+
409
431
  /** Packed upload options. */
410
432
  export interface PackedUploadOptions {
411
433
  maxBufferedSlabs?: number
@@ -420,15 +442,6 @@ export interface PinnedSector {
420
442
  hostKey: string
421
443
  }
422
444
 
423
- /** A pinned slab from the indexer. */
424
- export interface PinnedSlab {
425
- version: number
426
- id: string
427
- encryptionKey: Buffer
428
- minShards: number
429
- sectors: Array<PinnedSector>
430
- }
431
-
432
445
  /** A sealed object for offline storage. */
433
446
  export interface SealedObject {
434
447
  id: string
@@ -485,9 +498,21 @@ export declare function initSia(): Promise<void>
485
498
  export declare function detectPrivateRelay(): Promise<boolean>
486
499
  /** Always false. Service worker streaming is for browser pages. */
487
500
  export declare function enableStreaming(options?: { workerUrl?: string; scope?: string; type?: 'classic' | 'module' }): Promise<boolean>
501
+ export type StreamStatus = 'connecting' | 'downloading' | 'idle'
502
+ /** The same callbacks browser code passes, so code shared with the browser typechecks. */
503
+ export type FileEvents = {
504
+ onProgress?: (bytesDownloaded: number) => void
505
+ onShard?: (progress: ShardProgress) => void
506
+ onStatus?: (status: StreamStatus) => void
507
+ }
508
+ export type FileOptions = FileEvents & {
509
+ name: string
510
+ type?: string
511
+ onError?: (message: string) => void
512
+ }
488
513
  /** For browser pages. In Node, `url` and `download` reject, and `sdk.download()` streams objects directly. */
489
514
  export declare function openStreams(sdk: object, credentials: { indexerUrl: string; appMeta: AppMetadata } | { indexerUrl: string; seed: string }): {
490
- url(object: PinnedObject, options: { name: string; type?: string }): Promise<never>
491
- download(object: PinnedObject, options: { name: string; type?: string }): Promise<never>
515
+ url(object: PinnedObject, options: FileOptions & { signal?: AbortSignal }): Promise<never>
516
+ download(object: PinnedObject, options: FileOptions): Promise<never>
492
517
  close(): void
493
518
  }