@siafoundation/sia-storage 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.node.cjs +3 -0
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.d.ts +38 -3
- package/dist/next.js +1 -1
- package/dist/sia-storage-sw.js +2 -2
- package/package.json +6 -6
- package/wasm/sia_storage_wasm.d.ts +81 -4
- package/wasm/sia_storage_wasm.js +127 -12
- package/wasm/sia_storage_wasm_bg.wasm +0 -0
- package/wasm/sia_storage_wasm_bg.wasm.d.ts +9 -1
package/README.md
CHANGED
|
@@ -281,7 +281,7 @@ Returned from `Builder.register()`, `Builder.connected()`, or `Builder.connectPr
|
|
|
281
281
|
| `connected(appKey)` | Reconnect with a saved `AppKey` → `Sdk \| null`. |
|
|
282
282
|
| `reconnecting()` | Whether the approved connect key already has an account. |
|
|
283
283
|
| `matchesExistingAppKey(phrase)` | Whether a phrase derives an already-registered app key. |
|
|
284
|
-
| `connectPreAuthorized(
|
|
284
|
+
| `connectPreAuthorized(key, phrase)` | Skip the approval flow with a `PreAuthorizedKey` → `Sdk`. |
|
|
285
285
|
|
|
286
286
|
### `AppKey`
|
|
287
287
|
|
|
@@ -289,6 +289,13 @@ Returned from `Builder.register()`, `Builder.connected()`, or `Builder.connectPr
|
|
|
289
289
|
|
|
290
290
|
`publicKey()` · `sign(message)` · `verifySignature(message, signature)` · `export()`
|
|
291
291
|
|
|
292
|
+
### `PreAuthorizedKey`
|
|
293
|
+
|
|
294
|
+
`new PreAuthorizedKey()` generates one, or `PreAuthorizedKey.import(seed)` from a 32-byte
|
|
295
|
+
seed. Pass it to `Builder.connectPreAuthorized()` to skip the approval flow.
|
|
296
|
+
|
|
297
|
+
`publicKey()` · `sign(message)` · `verifySignature(message, signature)` · `export()` · `signRequest(request)` — signs a `PreAuthorizedKeyRequest` (`connectKey`, `expiration`, `totalUses`, optional `allowedAppID`) into a `SignedPreAuthorizedKeyRequest` for the indexer.
|
|
298
|
+
|
|
292
299
|
### `PinnedObject`
|
|
293
300
|
|
|
294
301
|
`new PinnedObject()` for new uploads, or `sdk.object(key)`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ShardProgress, AppMetadata, Sdk, SharedSdk, PinnedObject } from '../wasm/sia_storage_wasm.js';
|
|
2
|
-
export { Account, AppKey, AppMetadata, Builder, DownloadOptions, Host, HostQuery, KeyRecord, KeyStats, ObjectEvent, ObjectEventSummary, ObjectSummary, PackedUpload, PinnedObject, Sdk, SealedObject, Sector, ShardProgress, SharedSdk, SharingKey, Slab, UploadOptions, encodedSize, generateRecoveryPhrase, setLogger, validateRecoveryPhrase } from '../wasm/sia_storage_wasm.js';
|
|
2
|
+
export { Account, AppKey, AppMetadata, Builder, DownloadOptions, Host, HostQuery, KeyRecord, KeyStats, ObjectEvent, ObjectEventSummary, ObjectSummary, PackedUpload, PinnedObject, PreAuthorizedKey, PreAuthorizedKeyRequest, Sdk, SealedObject, Sector, ShardProgress, SharedSdk, SharingKey, SignedPreAuthorizedKeyRequest, Slab, UploadOptions, encodedSize, generateRecoveryPhrase, setLogger, validateRecoveryPhrase } from '../wasm/sia_storage_wasm.js';
|
|
3
3
|
|
|
4
4
|
declare function detectPrivateRelay(): Promise<boolean>;
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -433,6 +433,7 @@ import {
|
|
|
433
433
|
ObjectSummary,
|
|
434
434
|
PackedUpload,
|
|
435
435
|
PinnedObject,
|
|
436
|
+
PreAuthorizedKey,
|
|
436
437
|
Sdk,
|
|
437
438
|
SharedSdk,
|
|
438
439
|
SharingKey,
|
|
@@ -455,6 +456,7 @@ export {
|
|
|
455
456
|
ObjectSummary,
|
|
456
457
|
PackedUpload,
|
|
457
458
|
PinnedObject,
|
|
459
|
+
PreAuthorizedKey,
|
|
458
460
|
Sdk,
|
|
459
461
|
SharedSdk,
|
|
460
462
|
SharingKey,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 StreamLogLevel,\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 ObjectEventSummary,\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 isLogLevel,\n LOG_LEVELS,\n type LogLine,\n type PageMessage,\n requestReply,\n type SourceReply,\n type StreamEvent,\n type StreamLogLevel,\n streamPath,\n type WorkerMessage,\n} from './protocol'\n\nexport type { FileEvents } from './fallback'\nexport type { StreamLogLevel, 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 * Prints the streaming worker's log in this page's console, which otherwise\n * never shows it, prefixed `[Sia stream worker]`. That is the SDK's log at\n * this level and above, and at `error`, any SDK panic and any error the\n * worker did not catch. Off when unset. URL query strings and fragments are\n * cut from each line, since indexer requests are signed in theirs. The log\n * covers every page the worker serves, not only this one's streams.\n */\n logLevel?: StreamLogLevel\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// Well inside the 30 seconds Firefox lets a worker go without an event.\nconst KEEP_ALIVE_INTERVAL = 10_000\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\nlet keepAlive: ReturnType<typeof setInterval> | 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 logLevel,\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 = {\n type: 'sia-client',\n ...(logLevel && { logLevel }),\n } 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\n/**\n * Messages the worker while it is serving this page, which Firefox needs to\n * keep the worker running past 30 seconds. See `hold` in the worker.\n */\nfunction keepWorkerAlive(streaming: boolean) {\n clearInterval(keepAlive)\n keepAlive = undefined\n const controller = navigator.serviceWorker.controller\n if (!streaming || !controller) return\n const message = { type: 'sia-keep-alive' } satisfies PageMessage\n keepAlive = setInterval(() => {\n // Streams end with the worker serving them, so a new controller has none.\n if (navigator.serviceWorker.controller === controller) {\n controller.postMessage(message)\n } else {\n keepWorkerAlive(false)\n }\n }, KEEP_ALIVE_INTERVAL)\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-streaming') {\n keepWorkerAlive(data.active === true)\n } else 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-log') {\n if (Array.isArray(data.lines)) data.lines.forEach(printLogLine)\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 ...(initOptions.logLevel && { logLevel: initOptions.logLevel }),\n }\n : { type: 'error' }\n port.postMessage(reply)\n port.close()\n }\n}\n\n/**\n * Checked here too, since the worker can come from a different deploy, so a\n * page that set no `logLevel` never prints a line.\n */\nfunction printLogLine(line: LogLine) {\n const wanted = initOptions.logLevel\n if (\n !wanted ||\n !isLogLevel(line?.level) ||\n typeof line.message !== 'string' ||\n LOG_LEVELS.indexOf(line.level) > LOG_LEVELS.indexOf(wanted)\n ) {\n return\n }\n // console.trace would add this page's own stack to every line.\n const method = line.level === 'trace' ? 'debug' : line.level\n console[method](`[Sia stream worker] ${line.message}`)\n}\n"],"mappings":"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","names":["AppKey"]}
|
|
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 StreamLogLevel,\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 ObjectEventSummary,\n ObjectSummary,\n PackedUpload,\n PinnedObject,\n PreAuthorizedKey,\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 PreAuthorizedKeyRequest,\n Sector,\n SealedObject,\n ShardProgress,\n SignedPreAuthorizedKeyRequest,\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 isLogLevel,\n LOG_LEVELS,\n type LogLine,\n type PageMessage,\n requestReply,\n type SourceReply,\n type StreamEvent,\n type StreamLogLevel,\n streamPath,\n type WorkerMessage,\n} from './protocol'\n\nexport type { FileEvents } from './fallback'\nexport type { StreamLogLevel, 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 * Prints the streaming worker's log in this page's console, which otherwise\n * never shows it, prefixed `[Sia stream worker]`. That is the SDK's log at\n * this level and above, and at `error`, any SDK panic and any error the\n * worker did not catch. Off when unset. URL query strings and fragments are\n * cut from each line, since indexer requests are signed in theirs. The log\n * covers every page the worker serves, not only this one's streams.\n */\n logLevel?: StreamLogLevel\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// Well inside the 30 seconds Firefox lets a worker go without an event.\nconst KEEP_ALIVE_INTERVAL = 10_000\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\nlet keepAlive: ReturnType<typeof setInterval> | 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 logLevel,\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 = {\n type: 'sia-client',\n ...(logLevel && { logLevel }),\n } 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\n/**\n * Messages the worker while it is serving this page, which Firefox needs to\n * keep the worker running past 30 seconds. See `hold` in the worker.\n */\nfunction keepWorkerAlive(streaming: boolean) {\n clearInterval(keepAlive)\n keepAlive = undefined\n const controller = navigator.serviceWorker.controller\n if (!streaming || !controller) return\n const message = { type: 'sia-keep-alive' } satisfies PageMessage\n keepAlive = setInterval(() => {\n // Streams end with the worker serving them, so a new controller has none.\n if (navigator.serviceWorker.controller === controller) {\n controller.postMessage(message)\n } else {\n keepWorkerAlive(false)\n }\n }, KEEP_ALIVE_INTERVAL)\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-streaming') {\n keepWorkerAlive(data.active === true)\n } else 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-log') {\n if (Array.isArray(data.lines)) data.lines.forEach(printLogLine)\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 ...(initOptions.logLevel && { logLevel: initOptions.logLevel }),\n }\n : { type: 'error' }\n port.postMessage(reply)\n port.close()\n }\n}\n\n/**\n * Checked here too, since the worker can come from a different deploy, so a\n * page that set no `logLevel` never prints a line.\n */\nfunction printLogLine(line: LogLine) {\n const wanted = initOptions.logLevel\n if (\n !wanted ||\n !isLogLevel(line?.level) ||\n typeof line.message !== 'string' ||\n LOG_LEVELS.indexOf(line.level) > LOG_LEVELS.indexOf(wanted)\n ) {\n return\n }\n // console.trace would add this page's own stack to every line.\n const method = line.level === 'trace' ? 'debug' : line.level\n console[method](`[Sia stream worker] ${line.message}`)\n}\n"],"mappings":"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","names":["AppKey"]}
|
package/dist/index.node.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_node_exports, {
|
|
|
27
27
|
ObjectEventSummary: () => ObjectEventSummary,
|
|
28
28
|
PackedUpload: () => PackedUpload,
|
|
29
29
|
PinnedObject: () => PinnedObject,
|
|
30
|
+
PreAuthorizedKey: () => PreAuthorizedKey,
|
|
30
31
|
Sdk: () => Sdk,
|
|
31
32
|
SharedSdk: () => SharedSdk,
|
|
32
33
|
SharingKey: () => SharingKey,
|
|
@@ -101,6 +102,7 @@ var {
|
|
|
101
102
|
SharingKey,
|
|
102
103
|
KeyRecord,
|
|
103
104
|
SharedSdk,
|
|
105
|
+
PreAuthorizedKey,
|
|
104
106
|
generateRecoveryPhrase,
|
|
105
107
|
validateRecoveryPhrase,
|
|
106
108
|
setLogger,
|
|
@@ -133,6 +135,7 @@ function openStreams() {
|
|
|
133
135
|
ObjectEventSummary,
|
|
134
136
|
PackedUpload,
|
|
135
137
|
PinnedObject,
|
|
138
|
+
PreAuthorizedKey,
|
|
136
139
|
Sdk,
|
|
137
140
|
SharedSdk,
|
|
138
141
|
SharingKey,
|
package/dist/index.node.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.node.ts","../src/node/platforms.ts","../src/node/load.ts","../src/node/napi.ts"],"sourcesContent":["export * from './node/napi'\nexport type * from './node/napi'\n\n/** Node's traffic never goes through iCloud Private Relay, which is Safari's. */\nexport async function detectPrivateRelay(): Promise<boolean> {\n return false\n}\n\n// openStreams hands a browser page URLs that a service worker serves. Node\n// streams objects with sdk.download() directly, so these only let code shared\n// with the browser import them, and server rendering call them, without\n// crashing.\nexport async function enableStreaming(): Promise<boolean> {\n return false\n}\n\nexport function openStreams() {\n const unavailable = () =>\n Promise.reject(\n new Error('openStreams is for browser pages. In Node, use sdk.download().'),\n )\n return { url: unavailable, download: unavailable, close() {} }\n}\n","/**\n * The npm package that ships the native addon for each platform, keyed by\n * `${process.platform}-${process.arch}`. The Linux and Windows packages name\n * the C library or toolchain their binary was built against, so a package\n * name is not always the platform key with a prefix.\n *\n * scripts/publish.ts publishes these and lists them as the main package's\n * optionalDependencies.\n */\nexport const NATIVE_PACKAGES: Record<string, string> = {\n 'darwin-arm64': '@siafoundation/sia-storage-darwin-arm64',\n 'darwin-x64': '@siafoundation/sia-storage-darwin-x64',\n 'linux-x64': '@siafoundation/sia-storage-linux-x64-gnu',\n 'linux-arm64': '@siafoundation/sia-storage-linux-arm64-gnu',\n 'win32-x64': '@siafoundation/sia-storage-win32-x64-msvc',\n}\n","import type * as Addon from './napi.generated'\nimport { NATIVE_PACKAGES } from './platforms'\n\nlet addon: typeof Addon | null = null\n\nexport function loadNativeAddon(): typeof Addon {\n if (addon) return addon\n\n const target = `${process.platform}-${process.arch}`\n let cause: unknown\n\n // One literal require per package, rather than require(NATIVE_PACKAGES[...]),\n // so bundlers, including `bun build --compile`, can find and embed the addon.\n try {\n if (target === 'darwin-arm64') {\n addon = require('@siafoundation/sia-storage-darwin-arm64')\n } else if (target === 'darwin-x64') {\n addon = require('@siafoundation/sia-storage-darwin-x64')\n } else if (target === 'linux-x64') {\n addon = require('@siafoundation/sia-storage-linux-x64-gnu')\n } else if (target === 'linux-arm64') {\n addon = require('@siafoundation/sia-storage-linux-arm64-gnu')\n } else if (target === 'win32-x64') {\n addon = require('@siafoundation/sia-storage-win32-x64-msvc')\n }\n } catch (error) {\n cause = error\n }\n\n if (!addon) throw addonLoadError(target, cause)\n return addon\n}\n\n/**\n * Why the addon could not load. A missing package and a binary that fails to\n * load read differently, so the require error is kept as the cause and its\n * message is part of this one.\n */\nexport function addonLoadError(target: string, cause: unknown): Error {\n const packageName = NATIVE_PACKAGES[target]\n if (!packageName) {\n return new Error(\n `@siafoundation/sia-storage: no native addon is published for ${target}. ` +\n 'Use the browser/WASM build.',\n )\n }\n const reason = cause instanceof Error ? cause.message : String(cause)\n return new Error(\n `@siafoundation/sia-storage: could not load the native addon for ${target} ` +\n `from ${packageName}: ${reason}`,\n { cause },\n )\n}\n","import { loadNativeAddon } from './load'\n\nconst addon = loadNativeAddon()\n\nexport const {\n AppKey,\n Builder,\n Sdk,\n PinnedObject,\n PackedUpload,\n ObjectEvent,\n ObjectEventSummary,\n SharingKey,\n KeyRecord,\n SharedSdk,\n generateRecoveryPhrase,\n validateRecoveryPhrase,\n setLogger,\n encodedSize,\n} = addon\n\nexport type * from './napi.generated'\n\n/** Force-load the native addon to surface missing-binary errors at startup. */\nexport async function initSia(): Promise<void> {\n loadNativeAddon()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,kBAA0C;AAAA,EACrD,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,aAAa;AAAA,EACb,eAAe;AAAA,EACf,aAAa;AACf;;;ACZA,IAAI,QAA6B;AAE1B,SAAS,kBAAgC;AAC9C,MAAI,MAAO,QAAO;AAElB,QAAM,SAAS,GAAG,QAAQ,QAAQ,IAAI,QAAQ,IAAI;AAClD,MAAI;AAIJ,MAAI;AACF,QAAI,WAAW,gBAAgB;AAC7B,cAAQ,QAAQ,yCAAyC;AAAA,IAC3D,WAAW,WAAW,cAAc;AAClC,cAAQ,QAAQ,uCAAuC;AAAA,IACzD,WAAW,WAAW,aAAa;AACjC,cAAQ,QAAQ,0CAA0C;AAAA,IAC5D,WAAW,WAAW,eAAe;AACnC,cAAQ,QAAQ,4CAA4C;AAAA,IAC9D,WAAW,WAAW,aAAa;AACjC,cAAQ,QAAQ,2CAA2C;AAAA,IAC7D;AAAA,EACF,SAAS,OAAO;AACd,YAAQ;AAAA,EACV;AAEA,MAAI,CAAC,MAAO,OAAM,eAAe,QAAQ,KAAK;AAC9C,SAAO;AACT;AAOO,SAAS,eAAe,QAAgB,OAAuB;AACpE,QAAM,cAAc,gBAAgB,MAAM;AAC1C,MAAI,CAAC,aAAa;AAChB,WAAO,IAAI;AAAA,MACT,gEAAgE,MAAM;AAAA,IAExE;AAAA,EACF;AACA,QAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,SAAO,IAAI;AAAA,IACT,mEAAmE,MAAM,SAC/D,WAAW,KAAK,MAAM;AAAA,IAChC,EAAE,MAAM;AAAA,EACV;AACF;;;AClDA,IAAMA,SAAQ,gBAAgB;AAEvB,IAAM;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,IAAIA;AAKJ,eAAsB,UAAyB;AAC7C,kBAAgB;AAClB;;;
|
|
1
|
+
{"version":3,"sources":["../src/index.node.ts","../src/node/platforms.ts","../src/node/load.ts","../src/node/napi.ts"],"sourcesContent":["export * from './node/napi'\nexport type * from './node/napi'\n\n/** Node's traffic never goes through iCloud Private Relay, which is Safari's. */\nexport async function detectPrivateRelay(): Promise<boolean> {\n return false\n}\n\n// openStreams hands a browser page URLs that a service worker serves. Node\n// streams objects with sdk.download() directly, so these only let code shared\n// with the browser import them, and server rendering call them, without\n// crashing.\nexport async function enableStreaming(): Promise<boolean> {\n return false\n}\n\nexport function openStreams() {\n const unavailable = () =>\n Promise.reject(\n new Error('openStreams is for browser pages. In Node, use sdk.download().'),\n )\n return { url: unavailable, download: unavailable, close() {} }\n}\n","/**\n * The npm package that ships the native addon for each platform, keyed by\n * `${process.platform}-${process.arch}`. The Linux and Windows packages name\n * the C library or toolchain their binary was built against, so a package\n * name is not always the platform key with a prefix.\n *\n * scripts/publish.ts publishes these and lists them as the main package's\n * optionalDependencies.\n */\nexport const NATIVE_PACKAGES: Record<string, string> = {\n 'darwin-arm64': '@siafoundation/sia-storage-darwin-arm64',\n 'darwin-x64': '@siafoundation/sia-storage-darwin-x64',\n 'linux-x64': '@siafoundation/sia-storage-linux-x64-gnu',\n 'linux-arm64': '@siafoundation/sia-storage-linux-arm64-gnu',\n 'win32-x64': '@siafoundation/sia-storage-win32-x64-msvc',\n}\n","import type * as Addon from './napi.generated'\nimport { NATIVE_PACKAGES } from './platforms'\n\nlet addon: typeof Addon | null = null\n\nexport function loadNativeAddon(): typeof Addon {\n if (addon) return addon\n\n const target = `${process.platform}-${process.arch}`\n let cause: unknown\n\n // One literal require per package, rather than require(NATIVE_PACKAGES[...]),\n // so bundlers, including `bun build --compile`, can find and embed the addon.\n try {\n if (target === 'darwin-arm64') {\n addon = require('@siafoundation/sia-storage-darwin-arm64')\n } else if (target === 'darwin-x64') {\n addon = require('@siafoundation/sia-storage-darwin-x64')\n } else if (target === 'linux-x64') {\n addon = require('@siafoundation/sia-storage-linux-x64-gnu')\n } else if (target === 'linux-arm64') {\n addon = require('@siafoundation/sia-storage-linux-arm64-gnu')\n } else if (target === 'win32-x64') {\n addon = require('@siafoundation/sia-storage-win32-x64-msvc')\n }\n } catch (error) {\n cause = error\n }\n\n if (!addon) throw addonLoadError(target, cause)\n return addon\n}\n\n/**\n * Why the addon could not load. A missing package and a binary that fails to\n * load read differently, so the require error is kept as the cause and its\n * message is part of this one.\n */\nexport function addonLoadError(target: string, cause: unknown): Error {\n const packageName = NATIVE_PACKAGES[target]\n if (!packageName) {\n return new Error(\n `@siafoundation/sia-storage: no native addon is published for ${target}. ` +\n 'Use the browser/WASM build.',\n )\n }\n const reason = cause instanceof Error ? cause.message : String(cause)\n return new Error(\n `@siafoundation/sia-storage: could not load the native addon for ${target} ` +\n `from ${packageName}: ${reason}`,\n { cause },\n )\n}\n","import { loadNativeAddon } from './load'\n\nconst addon = loadNativeAddon()\n\nexport const {\n AppKey,\n Builder,\n Sdk,\n PinnedObject,\n PackedUpload,\n ObjectEvent,\n ObjectEventSummary,\n SharingKey,\n KeyRecord,\n SharedSdk,\n PreAuthorizedKey,\n generateRecoveryPhrase,\n validateRecoveryPhrase,\n setLogger,\n encodedSize,\n} = addon\n\nexport type * from './napi.generated'\n\n/** Force-load the native addon to surface missing-binary errors at startup. */\nexport async function initSia(): Promise<void> {\n loadNativeAddon()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,kBAA0C;AAAA,EACrD,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,aAAa;AAAA,EACb,eAAe;AAAA,EACf,aAAa;AACf;;;ACZA,IAAI,QAA6B;AAE1B,SAAS,kBAAgC;AAC9C,MAAI,MAAO,QAAO;AAElB,QAAM,SAAS,GAAG,QAAQ,QAAQ,IAAI,QAAQ,IAAI;AAClD,MAAI;AAIJ,MAAI;AACF,QAAI,WAAW,gBAAgB;AAC7B,cAAQ,QAAQ,yCAAyC;AAAA,IAC3D,WAAW,WAAW,cAAc;AAClC,cAAQ,QAAQ,uCAAuC;AAAA,IACzD,WAAW,WAAW,aAAa;AACjC,cAAQ,QAAQ,0CAA0C;AAAA,IAC5D,WAAW,WAAW,eAAe;AACnC,cAAQ,QAAQ,4CAA4C;AAAA,IAC9D,WAAW,WAAW,aAAa;AACjC,cAAQ,QAAQ,2CAA2C;AAAA,IAC7D;AAAA,EACF,SAAS,OAAO;AACd,YAAQ;AAAA,EACV;AAEA,MAAI,CAAC,MAAO,OAAM,eAAe,QAAQ,KAAK;AAC9C,SAAO;AACT;AAOO,SAAS,eAAe,QAAgB,OAAuB;AACpE,QAAM,cAAc,gBAAgB,MAAM;AAC1C,MAAI,CAAC,aAAa;AAChB,WAAO,IAAI;AAAA,MACT,gEAAgE,MAAM;AAAA,IAExE;AAAA,EACF;AACA,QAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,SAAO,IAAI;AAAA,IACT,mEAAmE,MAAM,SAC/D,WAAW,KAAK,MAAM;AAAA,IAChC,EAAE,MAAM;AAAA,EACV;AACF;;;AClDA,IAAMA,SAAQ,gBAAgB;AAEvB,IAAM;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,IAAIA;AAKJ,eAAsB,UAAyB;AAC7C,kBAAgB;AAClB;;;AHvBA,eAAsB,qBAAuC;AAC3D,SAAO;AACT;AAMA,eAAsB,kBAAoC;AACxD,SAAO;AACT;AAEO,SAAS,cAAc;AAC5B,QAAM,cAAc,MAClB,QAAQ;AAAA,IACN,IAAI,MAAM,gEAAgE;AAAA,EAC5E;AACF,SAAO,EAAE,KAAK,aAAa,UAAU,aAAa,QAAQ;AAAA,EAAC,EAAE;AAC/D;","names":["addon"]}
|
package/dist/index.node.d.ts
CHANGED
|
@@ -71,10 +71,9 @@ export declare class Builder {
|
|
|
71
71
|
register(mnemonic: string): Promise<Sdk>
|
|
72
72
|
/**
|
|
73
73
|
* Connects using a pre-authorized key, bypassing the interactive approval
|
|
74
|
-
* flow, and returns the SDK directly. `
|
|
75
|
-
* pre-authorized private key seed; `mnemonic` derives the application key.
|
|
74
|
+
* flow, and returns the SDK directly. `mnemonic` derives the application key.
|
|
76
75
|
*/
|
|
77
|
-
connectPreAuthorized(preAuthorizedKey:
|
|
76
|
+
connectPreAuthorized(preAuthorizedKey: PreAuthorizedKey, mnemonic: string): Promise<Sdk>
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
/**
|
|
@@ -196,6 +195,24 @@ export declare class PinnedObject {
|
|
|
196
195
|
updatedAt(): Date
|
|
197
196
|
}
|
|
198
197
|
|
|
198
|
+
/** A key used to authorize application connections with indexd. */
|
|
199
|
+
export declare class PreAuthorizedKey {
|
|
200
|
+
/** Generates a new pre-authorized key. */
|
|
201
|
+
constructor()
|
|
202
|
+
/** Imports a pre-authorized key from a 32-byte seed. */
|
|
203
|
+
static import(seed: Buffer): PreAuthorizedKey
|
|
204
|
+
/** Exports the key as a 32-byte seed. */
|
|
205
|
+
export(): Buffer
|
|
206
|
+
/** Returns the Ed25519 public key as a string. */
|
|
207
|
+
publicKey(): string
|
|
208
|
+
/** Signs a message and returns its 64-byte signature. */
|
|
209
|
+
sign(message: Buffer): Buffer
|
|
210
|
+
/** Verifies a message signature using this key. */
|
|
211
|
+
verifySignature(message: Buffer, signature: Buffer): boolean
|
|
212
|
+
/** Returns the signed request for creating a pre-authorized key in indexd. */
|
|
213
|
+
signRequest(request: PreAuthorizedKeyRequest): SignedPreAuthorizedKeyRequest
|
|
214
|
+
}
|
|
215
|
+
|
|
199
216
|
export declare class Sdk {
|
|
200
217
|
/** Creates a sharing key. Attach objects to it with `Sdk.shareObject`. */
|
|
201
218
|
createSharingKey(description: string, expiresAt?: Date | undefined | null): Promise<SharingKey>
|
|
@@ -455,6 +472,14 @@ export interface PinnedSector {
|
|
|
455
472
|
hostKey: string
|
|
456
473
|
}
|
|
457
474
|
|
|
475
|
+
/** An unsigned request to create a pre-authorized key in indexd. */
|
|
476
|
+
export interface PreAuthorizedKeyRequest {
|
|
477
|
+
connectKey: string
|
|
478
|
+
expiration: Date
|
|
479
|
+
totalUses: number
|
|
480
|
+
allowedAppID?: string | null
|
|
481
|
+
}
|
|
482
|
+
|
|
458
483
|
/** A sealed object for offline storage. */
|
|
459
484
|
export interface SealedObject {
|
|
460
485
|
id: string
|
|
@@ -485,6 +510,16 @@ export interface ShardProgress {
|
|
|
485
510
|
elapsedMs: number
|
|
486
511
|
}
|
|
487
512
|
|
|
513
|
+
/** A signed request to create a pre-authorized key in indexd. */
|
|
514
|
+
export interface SignedPreAuthorizedKeyRequest {
|
|
515
|
+
publicKey: string
|
|
516
|
+
signature: string
|
|
517
|
+
connectKey: string
|
|
518
|
+
expiration: string
|
|
519
|
+
totalUses: number
|
|
520
|
+
allowedAppID?: string
|
|
521
|
+
}
|
|
522
|
+
|
|
488
523
|
/** A slab representing a contiguous erasure-coded segment of a file. */
|
|
489
524
|
export interface Slab {
|
|
490
525
|
version: number
|