@siafoundation/sia-storage 0.1.5 → 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 +28 -4
- package/dist/{chunk-WOQD3MPG.js → chunk-KE73LO5C.js} +11 -1
- package/dist/chunk-KE73LO5C.js.map +1 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/index.node.cjs +6 -0
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.d.ts +56 -7
- package/dist/next.js +1 -1
- package/dist/sia-storage-sw.js +2 -2
- package/dist/stream-worker.d.ts +7 -0
- package/dist/stream-worker.js +134 -4
- package/dist/stream-worker.js.map +1 -1
- package/package.json +6 -6
- package/wasm/sia_storage_wasm.d.ts +118 -10
- package/wasm/sia_storage_wasm.js +279 -38
- package/wasm/sia_storage_wasm_bg.wasm +0 -0
- package/wasm/sia_storage_wasm_bg.wasm.d.ts +16 -2
- package/dist/chunk-WOQD3MPG.js.map +0 -1
package/README.md
CHANGED
|
@@ -126,6 +126,16 @@ videoElement.src = video.url
|
|
|
126
126
|
playButton.onpointerenter = () => void video.warm()
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### See the worker's log
|
|
130
|
+
|
|
131
|
+
A service worker's console is separate from the page's, so an error inside the worker, such as an SDK panic that stops every stream it serves, shows nowhere you usually look. Pass `logLevel` to `enableStreaming`, before the first `openStreams`, to print the worker's log in the page's console:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
enableStreaming({ logLevel: 'warn' })
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`logLevel` is `error`, `warn`, `info`, `debug` or `trace`, and each level includes the ones before it. `error` covers SDK panics and errors the worker did not catch. `warn` adds failed shard downloads and host list refreshes, and `debug` adds each host connection and each shard read. Each line starts with `[Sia stream worker]` and prints with the matching console method. Lines arrive in batches every 250 ms, and errors at once. The worker cuts the query string and fragment from every URL in a line, since the SDK signs its indexer requests in the query string. The log is the worker's, so it also has lines for other tabs of the site that are streaming. Off by default.
|
|
138
|
+
|
|
129
139
|
### Serve the worker
|
|
130
140
|
|
|
131
141
|
The worker is one file, `sia-storage-sw.js`, served from your site's root. Use the line for your setup.
|
|
@@ -218,7 +228,7 @@ Near-identical surfaces. Real differences:
|
|
|
218
228
|
- App identifier: `appMeta.appId: string (hex)` on browser, `appMeta.id: Buffer(32)` on Node.
|
|
219
229
|
- Numeric sizes are `number` on browser, `bigint` on Node. Byte arrays are `Uint8Array` on browser, `Buffer` on Node (`Buffer` is a `Uint8Array` subclass; both accept either as input).
|
|
220
230
|
- Sharing key seeds are hex `string` on browser, `Buffer` on Node. This covers `SharingKey.seed()`, `SharingKey.fromSeed(seed)`, and `SharedSdk.connect(indexerUrl, seed)`.
|
|
221
|
-
- `sharedSdk.objectSummaries(offset, limit)` resolves `ObjectSummary` class instances on browser, which own WASM memory and are disposable like `PinnedObject`. On Node it resolves plain objects.
|
|
231
|
+
- `sharedSdk.objectSummaries(offset, limit)` resolves `ObjectSummary` class instances on browser, which own WASM memory and are disposable like `PinnedObject`. On Node it resolves plain objects. The `object` on an `ObjectEventSummary` from `sdk.objectEventSummaries(cursor?, limit)` differs the same way, and is `undefined` on browser where it is `null` on Node.
|
|
222
232
|
- `sdk.unshareObject(key, object)` takes a `PinnedObject` on browser and an object id `string` on Node.
|
|
223
233
|
- `sdk.hosts(query?)` and `sharedSdk.hosts(query?)` accept a `HostQuery` on browser; on Node they take no arguments.
|
|
224
234
|
- `openStreams` gives a browser page URLs that a service worker serves. Node reads objects with `sdk.download(object, { offset, length })`, which already streams a range, so it needs no worker. On Node, `enableStreaming()` resolves false, and `url` and `download` on an `openStreams` handle reject.
|
|
@@ -250,6 +260,7 @@ Returned from `Builder.register()`, `Builder.connected()`, or `Builder.connectPr
|
|
|
250
260
|
| `updateObjectMetadata(object)` | Push local metadata changes to the indexer. |
|
|
251
261
|
| `objectShareUrl(object, validUntil)` / `objectFromShareUrl(url)` | Create / consume share URLs. |
|
|
252
262
|
| `objectEvents(cursor?, limit)` | Paginated change feed. |
|
|
263
|
+
| `objectEventSummaries(cursor?, limit)` | The same change feed without the objects' slabs, which is much smaller and faster. Each event carries an `ObjectEventSummary`, whose object cannot be downloaded. Fetch the full object with `object(key)` for that. |
|
|
253
264
|
| `hosts(query?)` / `account()` / `pruneSlabs(before?)` | Indexer reads. `pruneSlabs` unpins slabs no object uses; `before` limits it to slabs pinned before that date, and omitting it leaves the cutoff to the indexer. |
|
|
254
265
|
| `createSharingKey(description, expiresAt?)` | Create a `SharingKey` for read-only sharing. |
|
|
255
266
|
| `sharingKeys(offset, limit)` / `sharingKey(key)` | List keys / fetch one key's `KeyRecord`. |
|
|
@@ -270,7 +281,7 @@ Returned from `Builder.register()`, `Builder.connected()`, or `Builder.connectPr
|
|
|
270
281
|
| `connected(appKey)` | Reconnect with a saved `AppKey` → `Sdk \| null`. |
|
|
271
282
|
| `reconnecting()` | Whether the approved connect key already has an account. |
|
|
272
283
|
| `matchesExistingAppKey(phrase)` | Whether a phrase derives an already-registered app key. |
|
|
273
|
-
| `connectPreAuthorized(
|
|
284
|
+
| `connectPreAuthorized(key, phrase)` | Skip the approval flow with a `PreAuthorizedKey` → `Sdk`. |
|
|
274
285
|
|
|
275
286
|
### `AppKey`
|
|
276
287
|
|
|
@@ -278,6 +289,13 @@ Returned from `Builder.register()`, `Builder.connected()`, or `Builder.connectPr
|
|
|
278
289
|
|
|
279
290
|
`publicKey()` · `sign(message)` · `verifySignature(message, signature)` · `export()`
|
|
280
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
|
+
|
|
281
299
|
### `PinnedObject`
|
|
282
300
|
|
|
283
301
|
`new PinnedObject()` for new uploads, or `sdk.object(key)`.
|
|
@@ -317,10 +335,16 @@ sharing key's seed. Downloads are paid for by the key's owner.
|
|
|
317
335
|
|
|
318
336
|
### `ObjectSummary`
|
|
319
337
|
|
|
320
|
-
From `sharedSdk.objectSummaries()`.
|
|
338
|
+
From `sharedSdk.objectSummaries()`, or an `ObjectEventSummary`'s `object`.
|
|
321
339
|
|
|
322
340
|
`id` · `size` · `metadata` · `createdAt` · `updatedAt`
|
|
323
341
|
|
|
342
|
+
### `ObjectEventSummary`
|
|
343
|
+
|
|
344
|
+
From `sdk.objectEventSummaries()`.
|
|
345
|
+
|
|
346
|
+
`id` · `deleted` · `updatedAt` · `object` — the event's `ObjectSummary`, absent when the object was deleted.
|
|
347
|
+
|
|
324
348
|
### Streaming
|
|
325
349
|
|
|
326
350
|
| | |
|
|
@@ -329,7 +353,7 @@ From `sharedSdk.objectSummaries()`.
|
|
|
329
353
|
| `streams.url(object, options)` | `{ url, blob?, warm(), release() }`. Streams where it can, else reads the whole object into `blob`. |
|
|
330
354
|
| `streams.download(object, options)` | Saves the object. Resolves `'streaming'`, `'saved'` or `'cancelled'`. Call it from a click handler. |
|
|
331
355
|
| `streams.close()` | Cancels this handle's streams. Other handles on the same SDK keep theirs. |
|
|
332
|
-
| `enableStreaming(options?)` | Registers the worker and resolves whether streaming is on. `openStreams` calls it for you, so call it only to pass `workerUrl`, `scope` or `
|
|
356
|
+
| `enableStreaming(options?)` | Registers the worker and resolves whether streaming is on. `openStreams` calls it for you, so call it only to pass `workerUrl`, `scope`, `type` or `logLevel`. |
|
|
333
357
|
|
|
334
358
|
`options` for `url` and `download`: `name`, `type` (MIME), `onError(message)` for failures after streaming starts (logged when omitted), `onStatus`, `onProgress` and `onShard` (see [Status and progress](#status-and-progress)), and `signal` for `url`.
|
|
335
359
|
|
|
@@ -14,6 +14,14 @@ function reportStreamError(error, name, range) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// src/stream/protocol.ts
|
|
17
|
+
var LOG_LEVELS = [
|
|
18
|
+
"error",
|
|
19
|
+
"warn",
|
|
20
|
+
"info",
|
|
21
|
+
"debug",
|
|
22
|
+
"trace"
|
|
23
|
+
];
|
|
24
|
+
var isLogLevel = (value) => LOG_LEVELS.includes(value);
|
|
17
25
|
var STREAM_EVENTS = ["status", "progress", "shards"];
|
|
18
26
|
var STREAM_PATH = /\/__sia_stream__\/([^/]+)\/([^/]+)\/[^/]+$/;
|
|
19
27
|
function streamPath(scope, clientId, token) {
|
|
@@ -69,10 +77,12 @@ function requestReply(target, message, { timeout, signal }) {
|
|
|
69
77
|
export {
|
|
70
78
|
streamErrorMessage,
|
|
71
79
|
reportStreamError,
|
|
80
|
+
LOG_LEVELS,
|
|
81
|
+
isLogLevel,
|
|
72
82
|
streamPath,
|
|
73
83
|
parseStreamPath,
|
|
74
84
|
isAppMetadata,
|
|
75
85
|
isSourceReply,
|
|
76
86
|
requestReply
|
|
77
87
|
};
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
88
|
+
//# sourceMappingURL=chunk-KE73LO5C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stream/errors.ts","../src/stream/protocol.ts"],"sourcesContent":["/** Only sanitized strings cross the diagnostic boundary, never raw SDK objects. */\nexport function streamErrorMessage(error: unknown): string {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Unknown streaming failure'\n return message\n .replace(/\\b(?:https?|wss?|sia):\\/\\/[^\\s\"'<>]+/gi, '[URL redacted]')\n .replace(/(?:bearer\\s+)[^\\s,;]+/gi, 'Bearer [redacted]')\n .replace(\n /((?:seed|share|token|key|authorization)\\s*[=:]\\s*)[^\\s,;]+/gi,\n '$1[redacted]',\n )\n .replace(/[a-f0-9]{64,}/gi, '[redacted]')\n .replace(/[\\r\\n\\t]/g, ' ')\n .slice(0, 1000)\n}\n\nexport function reportStreamError(\n error: unknown,\n name: string,\n range: { offset: number; length: number } | undefined,\n) {\n const message = streamErrorMessage(error)\n // One string rather than an object, so it reads the same in every console.\n const bytes = range\n ? ` bytes ${range.offset}-${range.offset + range.length - 1}`\n : ''\n console.error(`[Sia stream] ${streamErrorMessage(name)}${bytes}: ${message}`)\n return message\n}\n","/**\n * Messages between a page and its streaming service worker.\n *\n * Stream URLs carry only the page's client id and a random token. When the\n * worker meets a token it does not know, it asks the page that made it for the\n * file and the share's credentials. The page and a worker can come from\n * different deployed versions, so the worker checks the page's reply before\n * using it.\n */\nimport type {\n AppMetadata,\n SealedObject,\n ShardProgress,\n} from '../../wasm/sia_storage_wasm.js'\n\n/**\n * What a stream URL is doing. `connecting` is a request waiting for its first\n * bytes, which on the URL's first request includes the worker connecting its\n * SDK and finding the object. `downloading` means bytes are arriving. `idle`\n * means no request for the URL is in flight, after the last one finished, was\n * cancelled or failed.\n */\nexport type StreamStatus = 'connecting' | 'downloading' | 'idle'\n\n/**\n * How much of the worker's log a page asked for. Each level includes the ones\n * before it, so `warn` gets warnings and errors.\n */\nexport type StreamLogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'\nexport const LOG_LEVELS: readonly StreamLogLevel[] = [\n 'error',\n 'warn',\n 'info',\n 'debug',\n 'trace',\n]\n\nexport const isLogLevel = (value: unknown): value is StreamLogLevel =>\n LOG_LEVELS.includes(value as StreamLogLevel)\n\n/** One line of the worker's log, already redacted. */\nexport type LogLine = { level: StreamLogLevel; message: string }\n\n/** The reports a page can ask for, one per callback it was given. */\nexport type StreamEvent = 'status' | 'progress' | 'shards'\nconst STREAM_EVENTS: readonly unknown[] = ['status', 'progress', 'shards']\n\n/**\n * What the worker connects its own SDK with. A `SharedSdk` reconnects from its\n * sharing key's seed, and an `Sdk` from its app key, as hex, and app metadata.\n * A `SharedSdk`'s `sealKey` is the hex of the random key the page seals its\n * objects with. A page from before it existed sends none.\n */\nexport type Connection =\n | { kind: 'shared'; indexerUrl: string; seed: string; sealKey?: string }\n | { kind: 'app'; indexerUrl: string; appKey: string; appMeta: AppMetadata }\n\nexport type SourceDetails = {\n // One per opened SDK, so the worker can release it as a whole.\n session: string\n connection: Connection\n objectId: string\n // The object, sealed with its app key or the share's `sealKey`, so the\n // worker can open it without a lookup. Without it the worker looks the\n // object up by `objectId`.\n sealed?: SealedObject\n name: string\n mime: string\n size: number\n // The reports the page wants for this URL. The worker sends no others, and\n // asks the SDK for shard reports only when `shards` is here.\n events?: StreamEvent[]\n // The page's `logLevel`, sent again here because a worker the browser\n // stopped and started since the handshake has forgotten it.\n logLevel?: StreamLogLevel\n}\n\n/** Page to worker. */\nexport type PageMessage =\n // A page from before `logLevel` existed sends none, and gets no log lines.\n | { type: 'sia-client'; logLevel?: StreamLogLevel }\n | { type: 'sia-release'; token: string }\n | { type: 'sia-release-share'; session: string }\n // Sent while the worker reports `sia-streaming` active. The event it raises\n // in the worker is what keeps the worker running.\n | { type: 'sia-keep-alive' }\n\n/** Worker to page. */\nexport type WorkerMessage =\n | { type: 'sia-source'; token: string }\n | {\n type: 'sia-stream-error'\n token: string\n message: string\n offset?: number\n length?: number\n }\n | { type: 'sia-stream-status'; token: string; status: StreamStatus }\n // Whether any request from this page is still being served. Sent when that\n // changes, and in reply to a keep-alive that arrives when none is.\n | { type: 'sia-streaming'; active: boolean }\n | {\n type: 'sia-stream-progress'\n token: string\n // Bytes served since the previous progress message.\n bytes: number\n shards: ShardProgress[]\n }\n // Sent only to a page that asked for a `logLevel`, with only the lines at\n // that level or above.\n | { type: 'sia-log'; lines: LogLine[] }\n\n/** The worker's answer to `sia-client`. */\nexport type ClientReply = { clientId: string; supported: boolean }\n\n/** The page's answer to `sia-source`. */\nexport type SourceReply =\n | ({ type: 'source' } & SourceDetails)\n | { type: 'error' }\n\n// Stream URLs sit under the worker's scope, so a download's navigation, which\n// only a worker whose scope covers the URL can answer, reaches it on sites\n// served from a sub-path too.\nconst STREAM_PATH = /\\/__sia_stream__\\/([^/]+)\\/([^/]+)\\/[^/]+$/\n\n/** `scope` is the worker registration's scope URL, which ends in a slash. */\nexport function streamPath(scope: string, clientId: string, token: string) {\n return `${scope}__sia_stream__/${clientId}/${token}/file`\n}\n\nexport function parseStreamPath(pathname: string) {\n const match = STREAM_PATH.exec(pathname)\n return match ? { clientId: match[1]!, token: match[2]! } : undefined\n}\n\ntype Fields = Record<string, unknown> | null | undefined\n\nconst strings = (data: Fields, fields: string[]) =>\n fields.every((field) => typeof data?.[field] === 'string')\n\n/** Checked on both sides, so bad metadata fails in `openStreams`, not per request. */\nexport function isAppMetadata(value: unknown): value is AppMetadata {\n return strings(value as Fields, ['appId', 'name', 'description', 'serviceUrl'])\n}\n\n// 32 bytes as hex, which is what the worker builds an AppKey from.\nconst isKeyHex = (value: unknown) =>\n typeof value === 'string' && /^[0-9a-f]{64}$/i.test(value)\n\nfunction isConnection(value: unknown): value is Connection {\n const data = value as Fields\n if (data?.kind === 'shared') {\n return (\n strings(data, ['indexerUrl', 'seed']) &&\n (data.sealKey === undefined || isKeyHex(data.sealKey))\n )\n }\n return (\n data?.kind === 'app' &&\n strings(data, ['indexerUrl', 'appKey']) &&\n isAppMetadata(data.appMeta)\n )\n}\n\nexport function isSourceReply(\n reply: unknown,\n): reply is { type: 'source' } & SourceDetails {\n const data = reply as Fields\n return (\n data?.type === 'source' &&\n strings(data, ['session', 'objectId', 'name', 'mime']) &&\n isConnection(data.connection) &&\n (data.connection.kind === 'shared' ||\n (typeof data.sealed === 'object' && data.sealed !== null)) &&\n Number.isSafeInteger(data.size) &&\n (data.size as number) >= 0 &&\n (data.events === undefined ||\n (Array.isArray(data.events) &&\n data.events.every((event) => STREAM_EVENTS.includes(event))))\n )\n}\n\n/**\n * Posts `message` with a reply port and resolves with the first reply. Rejects\n * after `timeout` ms, or when `signal` aborts.\n */\nexport function requestReply(\n target: { postMessage(message: unknown, transfer: Transferable[]): void },\n message: unknown,\n { timeout, signal }: { timeout?: number; signal?: AbortSignal },\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const { port1, port2 } = new MessageChannel()\n const timer =\n timeout === undefined\n ? undefined\n : setTimeout(\n () => settle(() => reject(new Error('No reply in time.'))),\n timeout,\n )\n const abort = () => settle(() => reject(signal?.reason))\n function settle(outcome: () => void) {\n clearTimeout(timer)\n signal?.removeEventListener('abort', abort)\n port1.close()\n port2.close()\n outcome()\n }\n if (signal?.aborted) return abort()\n signal?.addEventListener('abort', abort, { once: true })\n port1.onmessage = ({ data }) => settle(() => resolve(data))\n port1.onmessageerror = () =>\n settle(() => reject(new Error('Unreadable reply.')))\n try {\n target.postMessage(message, [port2])\n } catch (error) {\n settle(() => reject(error))\n }\n })\n}\n"],"mappings":"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","names":[]}
|
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, 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
|
|
|
@@ -21,6 +21,11 @@ declare function detectPrivateRelay(): Promise<boolean>;
|
|
|
21
21
|
* cancelled or failed.
|
|
22
22
|
*/
|
|
23
23
|
type StreamStatus = 'connecting' | 'downloading' | 'idle';
|
|
24
|
+
/**
|
|
25
|
+
* How much of the worker's log a page asked for. Each level includes the ones
|
|
26
|
+
* before it, so `warn` gets warnings and errors.
|
|
27
|
+
*/
|
|
28
|
+
type StreamLogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* Previews and downloads for pages that cannot stream through the service
|
|
@@ -66,6 +71,15 @@ type StreamingOptions = {
|
|
|
66
71
|
scope?: string;
|
|
67
72
|
/** `'module'` when `workerUrl` is an ES module worker of your own. */
|
|
68
73
|
type?: WorkerType;
|
|
74
|
+
/**
|
|
75
|
+
* Prints the streaming worker's log in this page's console, which otherwise
|
|
76
|
+
* never shows it, prefixed `[Sia stream worker]`. That is the SDK's log at
|
|
77
|
+
* this level and above, and at `error`, any SDK panic and any error the
|
|
78
|
+
* worker did not catch. Off when unset. URL query strings and fragments are
|
|
79
|
+
* cut from each line, since indexer requests are signed in theirs. The log
|
|
80
|
+
* covers every page the worker serves, not only this one's streams.
|
|
81
|
+
*/
|
|
82
|
+
logLevel?: StreamLogLevel;
|
|
69
83
|
};
|
|
70
84
|
/** What a `SharedSdk` was connected with. */
|
|
71
85
|
type SharedCredentials = {
|
|
@@ -144,4 +158,4 @@ declare function openStreams<S extends Sdk | SharedSdk>(sdk: S, credentials: Cre
|
|
|
144
158
|
/** Initialize the WASM module. Safe to call multiple times. */
|
|
145
159
|
declare function initSia(): Promise<void>;
|
|
146
160
|
|
|
147
|
-
export { type AppCredentials, type CredentialsFor, type FileEvents, type FileOptions, type SharedCredentials, type StreamStatus, type StreamedFile, type StreamingOptions, type Streams, detectPrivateRelay, enableStreaming, initSia, openStreams };
|
|
161
|
+
export { type AppCredentials, type CredentialsFor, type FileEvents, type FileOptions, type SharedCredentials, type StreamLogLevel, type StreamStatus, type StreamedFile, type StreamingOptions, type Streams, detectPrivateRelay, enableStreaming, initSia, openStreams };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
LOG_LEVELS,
|
|
2
3
|
isAppMetadata,
|
|
4
|
+
isLogLevel,
|
|
3
5
|
reportStreamError,
|
|
4
6
|
requestReply,
|
|
5
7
|
streamErrorMessage,
|
|
6
8
|
streamPath
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KE73LO5C.js";
|
|
8
10
|
|
|
9
11
|
// src/index.ts
|
|
10
12
|
import wasmInit from "../wasm/sia_storage_wasm.js";
|
|
@@ -164,7 +166,8 @@ function enableStreaming(options = {}) {
|
|
|
164
166
|
async function initialize({
|
|
165
167
|
workerUrl = DEFAULT_WORKER_URL,
|
|
166
168
|
scope,
|
|
167
|
-
type
|
|
169
|
+
type,
|
|
170
|
+
logLevel
|
|
168
171
|
}) {
|
|
169
172
|
if (typeof window === "undefined" || !("serviceWorker" in navigator) || !window.isSecureContext) {
|
|
170
173
|
return false;
|
|
@@ -190,7 +193,10 @@ async function initialize({
|
|
|
190
193
|
}
|
|
191
194
|
const controller = serviceWorker.controller;
|
|
192
195
|
if (!controller) return false;
|
|
193
|
-
const request = {
|
|
196
|
+
const request = {
|
|
197
|
+
type: "sia-client",
|
|
198
|
+
...logLevel && { logLevel }
|
|
199
|
+
};
|
|
194
200
|
const reply = await requestReply(controller, request, {
|
|
195
201
|
signal: deadline
|
|
196
202
|
});
|
|
@@ -382,6 +388,8 @@ function onWorkerMessage(event) {
|
|
|
382
388
|
entry.received += data.bytes;
|
|
383
389
|
notify(onProgress, entry.received);
|
|
384
390
|
}
|
|
391
|
+
} else if (data?.type === "sia-log") {
|
|
392
|
+
if (Array.isArray(data.lines)) data.lines.forEach(printLogLine);
|
|
385
393
|
} else if (data?.type === "sia-source") {
|
|
386
394
|
const port = event.ports[0];
|
|
387
395
|
if (!port) return;
|
|
@@ -399,12 +407,21 @@ function onWorkerMessage(event) {
|
|
|
399
407
|
size: entry.size,
|
|
400
408
|
mime: entry.mime,
|
|
401
409
|
name: entry.name,
|
|
402
|
-
...events.length > 0 && { events }
|
|
410
|
+
...events.length > 0 && { events },
|
|
411
|
+
...initOptions.logLevel && { logLevel: initOptions.logLevel }
|
|
403
412
|
} : { type: "error" };
|
|
404
413
|
port.postMessage(reply);
|
|
405
414
|
port.close();
|
|
406
415
|
}
|
|
407
416
|
}
|
|
417
|
+
function printLogLine(line) {
|
|
418
|
+
const wanted = initOptions.logLevel;
|
|
419
|
+
if (!wanted || !isLogLevel(line?.level) || typeof line.message !== "string" || LOG_LEVELS.indexOf(line.level) > LOG_LEVELS.indexOf(wanted)) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const method = line.level === "trace" ? "debug" : line.level;
|
|
423
|
+
console[method](`[Sia stream worker] ${line.message}`);
|
|
424
|
+
}
|
|
408
425
|
|
|
409
426
|
// src/index.ts
|
|
410
427
|
import {
|
|
@@ -412,9 +429,11 @@ import {
|
|
|
412
429
|
Builder,
|
|
413
430
|
KeyRecord,
|
|
414
431
|
ObjectEvent,
|
|
432
|
+
ObjectEventSummary,
|
|
415
433
|
ObjectSummary,
|
|
416
434
|
PackedUpload,
|
|
417
435
|
PinnedObject,
|
|
436
|
+
PreAuthorizedKey,
|
|
418
437
|
Sdk,
|
|
419
438
|
SharedSdk,
|
|
420
439
|
SharingKey,
|
|
@@ -433,9 +452,11 @@ export {
|
|
|
433
452
|
Builder,
|
|
434
453
|
KeyRecord,
|
|
435
454
|
ObjectEvent,
|
|
455
|
+
ObjectEventSummary,
|
|
436
456
|
ObjectSummary,
|
|
437
457
|
PackedUpload,
|
|
438
458
|
PinnedObject,
|
|
459
|
+
PreAuthorizedKey,
|
|
439
460
|
Sdk,
|
|
440
461
|
SharedSdk,
|
|
441
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 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// 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}: 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\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-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"]}
|
|
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
|
@@ -24,8 +24,10 @@ __export(index_node_exports, {
|
|
|
24
24
|
Builder: () => Builder,
|
|
25
25
|
KeyRecord: () => KeyRecord,
|
|
26
26
|
ObjectEvent: () => ObjectEvent,
|
|
27
|
+
ObjectEventSummary: () => ObjectEventSummary,
|
|
27
28
|
PackedUpload: () => PackedUpload,
|
|
28
29
|
PinnedObject: () => PinnedObject,
|
|
30
|
+
PreAuthorizedKey: () => PreAuthorizedKey,
|
|
29
31
|
Sdk: () => Sdk,
|
|
30
32
|
SharedSdk: () => SharedSdk,
|
|
31
33
|
SharingKey: () => SharingKey,
|
|
@@ -96,9 +98,11 @@ var {
|
|
|
96
98
|
PinnedObject,
|
|
97
99
|
PackedUpload,
|
|
98
100
|
ObjectEvent,
|
|
101
|
+
ObjectEventSummary,
|
|
99
102
|
SharingKey,
|
|
100
103
|
KeyRecord,
|
|
101
104
|
SharedSdk,
|
|
105
|
+
PreAuthorizedKey,
|
|
102
106
|
generateRecoveryPhrase,
|
|
103
107
|
validateRecoveryPhrase,
|
|
104
108
|
setLogger,
|
|
@@ -128,8 +132,10 @@ function openStreams() {
|
|
|
128
132
|
Builder,
|
|
129
133
|
KeyRecord,
|
|
130
134
|
ObjectEvent,
|
|
135
|
+
ObjectEventSummary,
|
|
131
136
|
PackedUpload,
|
|
132
137
|
PinnedObject,
|
|
138
|
+
PreAuthorizedKey,
|
|
133
139
|
Sdk,
|
|
134
140
|
SharedSdk,
|
|
135
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 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;;;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;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
|
/**
|
|
@@ -100,6 +99,14 @@ export declare class ObjectEvent {
|
|
|
100
99
|
get object(): PinnedObject | null
|
|
101
100
|
}
|
|
102
101
|
|
|
102
|
+
/** An object event from the indexer, without the object's slabs. */
|
|
103
|
+
export declare class ObjectEventSummary {
|
|
104
|
+
get id(): string
|
|
105
|
+
get deleted(): boolean
|
|
106
|
+
get updatedAt(): Date
|
|
107
|
+
get object(): ObjectSummary | null
|
|
108
|
+
}
|
|
109
|
+
|
|
103
110
|
/**
|
|
104
111
|
* A packed upload allows multiple objects to be uploaded together in a single
|
|
105
112
|
* upload. This can be more efficient than individual uploads for many small
|
|
@@ -188,6 +195,24 @@ export declare class PinnedObject {
|
|
|
188
195
|
updatedAt(): Date
|
|
189
196
|
}
|
|
190
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
|
+
|
|
191
216
|
export declare class Sdk {
|
|
192
217
|
/** Creates a sharing key. Attach objects to it with `Sdk.shareObject`. */
|
|
193
218
|
createSharingKey(description: string, expiresAt?: Date | undefined | null): Promise<SharingKey>
|
|
@@ -239,6 +264,11 @@ export declare class Sdk {
|
|
|
239
264
|
hosts(query?: HostQuery | undefined | null): Promise<Array<Host>>
|
|
240
265
|
/** Returns object events from the indexer for syncing. */
|
|
241
266
|
objectEvents(cursor: ObjectsCursor | undefined | null, limit: number): Promise<Array<ObjectEvent>>
|
|
267
|
+
/**
|
|
268
|
+
* Returns object events from the indexer for syncing, without the
|
|
269
|
+
* objects' slabs.
|
|
270
|
+
*/
|
|
271
|
+
objectEventSummaries(cursor: ObjectsCursor | undefined | null, limit: number): Promise<Array<ObjectEventSummary>>
|
|
242
272
|
/** Updates the metadata of an object stored in the indexer. */
|
|
243
273
|
updateObjectMetadata(object: PinnedObject): Promise<void>
|
|
244
274
|
/** Deletes an object from the indexer. */
|
|
@@ -415,9 +445,9 @@ export interface ObjectsCursor {
|
|
|
415
445
|
}
|
|
416
446
|
|
|
417
447
|
/**
|
|
418
|
-
* An object listed without its slabs, as `
|
|
419
|
-
*
|
|
420
|
-
* `SharedSdk.object(id)` to download it.
|
|
448
|
+
* An object listed without its slabs, as `Sdk.objectEventSummaries` and
|
|
449
|
+
* `SharedSdk.objectSummaries` return it. It cannot be downloaded. Fetch the
|
|
450
|
+
* full object with `Sdk.object(id)` or `SharedSdk.object(id)` to download it.
|
|
421
451
|
*/
|
|
422
452
|
export interface ObjectSummary {
|
|
423
453
|
id: string
|
|
@@ -442,6 +472,14 @@ export interface PinnedSector {
|
|
|
442
472
|
hostKey: string
|
|
443
473
|
}
|
|
444
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
|
+
|
|
445
483
|
/** A sealed object for offline storage. */
|
|
446
484
|
export interface SealedObject {
|
|
447
485
|
id: string
|
|
@@ -472,6 +510,16 @@ export interface ShardProgress {
|
|
|
472
510
|
elapsedMs: number
|
|
473
511
|
}
|
|
474
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
|
+
|
|
475
523
|
/** A slab representing a contiguous erasure-coded segment of a file. */
|
|
476
524
|
export interface Slab {
|
|
477
525
|
version: number
|
|
@@ -497,7 +545,8 @@ export declare function validateRecoveryPhrase(phrase: string): void
|
|
|
497
545
|
export declare function initSia(): Promise<void>
|
|
498
546
|
export declare function detectPrivateRelay(): Promise<boolean>
|
|
499
547
|
/** Always false. Service worker streaming is for browser pages. */
|
|
500
|
-
export declare function enableStreaming(options?: { workerUrl?: string; scope?: string; type?: 'classic' | 'module' }): Promise<boolean>
|
|
548
|
+
export declare function enableStreaming(options?: { workerUrl?: string; scope?: string; type?: 'classic' | 'module'; logLevel?: StreamLogLevel }): Promise<boolean>
|
|
549
|
+
export type StreamLogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'
|
|
501
550
|
export type StreamStatus = 'connecting' | 'downloading' | 'idle'
|
|
502
551
|
/** The same callbacks browser code passes, so code shared with the browser typechecks. */
|
|
503
552
|
export type FileEvents = {
|