@freqhole/reliquary 0.0.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/dist/automerge/acl-filtering.d.ts +87 -0
- package/dist/automerge/acl-filtering.d.ts.map +1 -0
- package/dist/automerge/acl-filtering.js +138 -0
- package/dist/automerge/acl-filtering.test.d.ts +2 -0
- package/dist/automerge/acl-filtering.test.d.ts.map +1 -0
- package/dist/automerge/acl-filtering.test.js +302 -0
- package/dist/automerge/index.d.ts +5 -0
- package/dist/automerge/index.d.ts.map +1 -0
- package/dist/automerge/index.js +7 -0
- package/dist/automerge/iroh-network-adapter.d.ts +178 -0
- package/dist/automerge/iroh-network-adapter.d.ts.map +1 -0
- package/dist/automerge/iroh-network-adapter.js +691 -0
- package/dist/automerge/iroh-network-adapter.test.d.ts +2 -0
- package/dist/automerge/iroh-network-adapter.test.d.ts.map +1 -0
- package/dist/automerge/iroh-network-adapter.test.js +818 -0
- package/dist/automerge/types.d.ts +66 -0
- package/dist/automerge/types.d.ts.map +1 -0
- package/dist/automerge/types.js +10 -0
- package/dist/blobs/bytes-backend.d.ts +41 -0
- package/dist/blobs/bytes-backend.d.ts.map +1 -0
- package/dist/blobs/bytes-backend.js +252 -0
- package/dist/blobs/bytes-backend.test.d.ts +2 -0
- package/dist/blobs/bytes-backend.test.d.ts.map +1 -0
- package/dist/blobs/bytes-backend.test.js +241 -0
- package/dist/blobs/db.d.ts +8 -0
- package/dist/blobs/db.d.ts.map +1 -0
- package/dist/blobs/db.js +141 -0
- package/dist/blobs/db.test.d.ts +2 -0
- package/dist/blobs/db.test.d.ts.map +1 -0
- package/dist/blobs/db.test.js +120 -0
- package/dist/blobs/index.d.ts +5 -0
- package/dist/blobs/index.d.ts.map +1 -0
- package/dist/blobs/index.js +2 -0
- package/dist/blobs/store.d.ts +47 -0
- package/dist/blobs/store.d.ts.map +1 -0
- package/dist/blobs/store.js +263 -0
- package/dist/blobs/store.test.d.ts +2 -0
- package/dist/blobs/store.test.d.ts.map +1 -0
- package/dist/blobs/store.test.js +377 -0
- package/dist/blobs/types.d.ts +55 -0
- package/dist/blobs/types.d.ts.map +1 -0
- package/dist/blobs/types.js +8 -0
- package/dist/ensure/client.d.ts +19 -0
- package/dist/ensure/client.d.ts.map +1 -0
- package/dist/ensure/client.js +74 -0
- package/dist/ensure/client.test.d.ts +2 -0
- package/dist/ensure/client.test.d.ts.map +1 -0
- package/dist/ensure/client.test.js +133 -0
- package/dist/ensure/index.d.ts +4 -0
- package/dist/ensure/index.d.ts.map +1 -0
- package/dist/ensure/index.js +4 -0
- package/dist/ensure/responder.d.ts +20 -0
- package/dist/ensure/responder.d.ts.map +1 -0
- package/dist/ensure/responder.js +100 -0
- package/dist/ensure/responder.test.d.ts +2 -0
- package/dist/ensure/responder.test.d.ts.map +1 -0
- package/dist/ensure/responder.test.js +191 -0
- package/dist/ensure/types.d.ts +19 -0
- package/dist/ensure/types.d.ts.map +1 -0
- package/dist/ensure/types.js +4 -0
- package/dist/ensure/types.test.d.ts +2 -0
- package/dist/ensure/types.test.d.ts.map +1 -0
- package/dist/ensure/types.test.js +81 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +7 -0
- package/dist/solid/blob-url.d.ts +10 -0
- package/dist/solid/blob-url.d.ts.map +1 -0
- package/dist/solid/blob-url.js +34 -0
- package/dist/solid/blob-url.test.d.ts +2 -0
- package/dist/solid/blob-url.test.d.ts.map +1 -0
- package/dist/solid/blob-url.test.js +62 -0
- package/dist/solid/connection-summary.d.ts +25 -0
- package/dist/solid/connection-summary.d.ts.map +1 -0
- package/dist/solid/connection-summary.js +29 -0
- package/dist/solid/connection-summary.test.d.ts +2 -0
- package/dist/solid/connection-summary.test.d.ts.map +1 -0
- package/dist/solid/connection-summary.test.js +57 -0
- package/dist/solid/doc-store.d.ts +29 -0
- package/dist/solid/doc-store.d.ts.map +1 -0
- package/dist/solid/doc-store.js +79 -0
- package/dist/solid/doc-store.test.d.ts +2 -0
- package/dist/solid/doc-store.test.d.ts.map +1 -0
- package/dist/solid/doc-store.test.js +234 -0
- package/dist/solid/index.d.ts +4 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.js +9 -0
- package/dist/solid/transfer-progress.d.ts +21 -0
- package/dist/solid/transfer-progress.d.ts.map +1 -0
- package/dist/solid/transfer-progress.js +32 -0
- package/dist/solid/transfer-progress.test.d.ts +2 -0
- package/dist/solid/transfer-progress.test.d.ts.map +1 -0
- package/dist/solid/transfer-progress.test.js +76 -0
- package/dist/testing/blob-fixtures.d.ts +19 -0
- package/dist/testing/blob-fixtures.d.ts.map +1 -0
- package/dist/testing/blob-fixtures.js +46 -0
- package/dist/testing/blob-fixtures.test.d.ts +2 -0
- package/dist/testing/blob-fixtures.test.d.ts.map +1 -0
- package/dist/testing/blob-fixtures.test.js +46 -0
- package/dist/testing/idb-harness.d.ts +10 -0
- package/dist/testing/idb-harness.d.ts.map +1 -0
- package/dist/testing/idb-harness.js +17 -0
- package/dist/testing/idb-harness.test.d.ts +2 -0
- package/dist/testing/idb-harness.test.d.ts.map +1 -0
- package/dist/testing/idb-harness.test.js +48 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/media.d.ts +15 -0
- package/dist/testing/media.d.ts.map +1 -0
- package/dist/testing/media.js +37 -0
- package/dist/testing/media.test.d.ts +2 -0
- package/dist/testing/media.test.d.ts.map +1 -0
- package/dist/testing/media.test.js +47 -0
- package/dist/testing/mock-blob-fetch.d.ts +33 -0
- package/dist/testing/mock-blob-fetch.d.ts.map +1 -0
- package/dist/testing/mock-blob-fetch.js +39 -0
- package/dist/testing/mock-blob-fetch.test.d.ts +2 -0
- package/dist/testing/mock-blob-fetch.test.d.ts.map +1 -0
- package/dist/testing/mock-blob-fetch.test.js +65 -0
- package/dist/testing/mock-node.d.ts +36 -0
- package/dist/testing/mock-node.d.ts.map +1 -0
- package/dist/testing/mock-node.js +97 -0
- package/dist/testing/mock-node.test.d.ts +2 -0
- package/dist/testing/mock-node.test.d.ts.map +1 -0
- package/dist/testing/mock-node.test.js +66 -0
- package/dist/testing/streams.d.ts +68 -0
- package/dist/testing/streams.d.ts.map +1 -0
- package/dist/testing/streams.js +99 -0
- package/dist/testing/streams.test.d.ts +2 -0
- package/dist/testing/streams.test.d.ts.map +1 -0
- package/dist/testing/streams.test.js +82 -0
- package/dist/transfer/cancellation.d.ts +8 -0
- package/dist/transfer/cancellation.d.ts.map +1 -0
- package/dist/transfer/cancellation.js +19 -0
- package/dist/transfer/index.d.ts +8 -0
- package/dist/transfer/index.d.ts.map +1 -0
- package/dist/transfer/index.js +4 -0
- package/dist/transfer/prefetch.d.ts +38 -0
- package/dist/transfer/prefetch.d.ts.map +1 -0
- package/dist/transfer/prefetch.js +61 -0
- package/dist/transfer/prefetch.test.d.ts +2 -0
- package/dist/transfer/prefetch.test.d.ts.map +1 -0
- package/dist/transfer/prefetch.test.js +136 -0
- package/dist/transfer/serve.d.ts +66 -0
- package/dist/transfer/serve.d.ts.map +1 -0
- package/dist/transfer/serve.js +126 -0
- package/dist/transfer/serve.test.d.ts +2 -0
- package/dist/transfer/serve.test.d.ts.map +1 -0
- package/dist/transfer/serve.test.js +101 -0
- package/dist/transfer/snatch.d.ts +39 -0
- package/dist/transfer/snatch.d.ts.map +1 -0
- package/dist/transfer/snatch.js +289 -0
- package/dist/transfer/snatch.test.d.ts +2 -0
- package/dist/transfer/snatch.test.d.ts.map +1 -0
- package/dist/transfer/snatch.test.js +337 -0
- package/dist/transfer/types.d.ts +85 -0
- package/dist/transfer/types.d.ts.map +1 -0
- package/dist/transfer/types.js +15 -0
- package/dist/utils/hash.d.ts +13 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +31 -0
- package/dist/utils/hash.test.d.ts +2 -0
- package/dist/utils/hash.test.d.ts.map +1 -0
- package/dist/utils/hash.test.js +56 -0
- package/dist/utils/image-utils.d.ts +28 -0
- package/dist/utils/image-utils.d.ts.map +1 -0
- package/dist/utils/image-utils.js +113 -0
- package/dist/utils/image-utils.test.d.ts +2 -0
- package/dist/utils/image-utils.test.d.ts.map +1 -0
- package/dist/utils/image-utils.test.js +181 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/log.d.ts +22 -0
- package/dist/utils/log.d.ts.map +1 -0
- package/dist/utils/log.js +100 -0
- package/dist/utils/log.test.d.ts +2 -0
- package/dist/utils/log.test.d.ts.map +1 -0
- package/dist/utils/log.test.js +113 -0
- package/dist/worker/blob-worker-client.d.ts +118 -0
- package/dist/worker/blob-worker-client.d.ts.map +1 -0
- package/dist/worker/blob-worker-client.js +361 -0
- package/dist/worker/blob-worker-client.test.d.ts +2 -0
- package/dist/worker/blob-worker-client.test.d.ts.map +1 -0
- package/dist/worker/blob-worker-client.test.js +137 -0
- package/dist/worker/blob-worker-logic.d.ts +120 -0
- package/dist/worker/blob-worker-logic.d.ts.map +1 -0
- package/dist/worker/blob-worker-logic.js +368 -0
- package/dist/worker/blob-worker-logic.test.d.ts +2 -0
- package/dist/worker/blob-worker-logic.test.d.ts.map +1 -0
- package/dist/worker/blob-worker-logic.test.js +191 -0
- package/dist/worker/blob-worker.d.ts +21 -0
- package/dist/worker/blob-worker.d.ts.map +1 -0
- package/dist/worker/blob-worker.js +44 -0
- package/dist/worker/index.d.ts +12 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +6 -0
- package/dist/worker/midden-blake3.d.ts +23 -0
- package/dist/worker/midden-blake3.d.ts.map +1 -0
- package/dist/worker/midden-blake3.js +47 -0
- package/dist/worker/midden-blake3.test.d.ts +2 -0
- package/dist/worker/midden-blake3.test.d.ts.map +1 -0
- package/dist/worker/midden-blake3.test.js +25 -0
- package/dist/worker/midden-not-bundled.stub.d.ts +1 -0
- package/dist/worker/midden-not-bundled.stub.d.ts.map +1 -0
- package/dist/worker/midden-not-bundled.stub.js +10 -0
- package/dist/worker/midden-stub.d.ts +10 -0
- package/dist/worker/midden-stub.d.ts.map +1 -0
- package/dist/worker/midden-stub.js +31 -0
- package/dist/worker/midden-stub.test.d.ts +2 -0
- package/dist/worker/midden-stub.test.d.ts.map +1 -0
- package/dist/worker/midden-stub.test.js +25 -0
- package/dist/worker/midden-worker-client.d.ts +103 -0
- package/dist/worker/midden-worker-client.d.ts.map +1 -0
- package/dist/worker/midden-worker-client.js +246 -0
- package/dist/worker/midden-worker-client.test.d.ts +2 -0
- package/dist/worker/midden-worker-client.test.d.ts.map +1 -0
- package/dist/worker/midden-worker-client.test.js +151 -0
- package/dist/worker/midden-worker-contract.d.ts +87 -0
- package/dist/worker/midden-worker-contract.d.ts.map +1 -0
- package/dist/worker/midden-worker-contract.js +30 -0
- package/package.json +90 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** structural transport surface this subpath needs from a node. */
|
|
2
|
+
export interface BlobCapableNode {
|
|
3
|
+
node_id(): string;
|
|
4
|
+
/** bulk verified download of a known blake3 hash, with progress. the
|
|
5
|
+
* peer is asked to ensure the blob is staged before the transfer
|
|
6
|
+
* starts. strategy 1 of `snatchBlob`/`snatchBlobToDisk`. */
|
|
7
|
+
download_verified_with_ensure_progress?(peerAddr: string, blake3Hash: string, totalSize: number, onProgress: (fraction: number) => void, downloadId?: string): Promise<Uint8Array>;
|
|
8
|
+
/** chunk-streamed verified download: each verified chunk arrives via
|
|
9
|
+
* `onChunk` as it is read out of the peer's store, at its explicit
|
|
10
|
+
* byte offset. strategy 2 of `snatchBlob` (chunks are accumulated into
|
|
11
|
+
* one buffer) and the primary path of `snatchBlobToDisk` (chunks are
|
|
12
|
+
* written straight to the caller's writable).
|
|
13
|
+
*
|
|
14
|
+
* the chunk is always a fresh, plain-ArrayBuffer-backed view (never a
|
|
15
|
+
* SharedArrayBuffer view) - the narrower type lets it be handed
|
|
16
|
+
* straight to a `FileSystemWritableFileStream.write()` call without a
|
|
17
|
+
* cast. */
|
|
18
|
+
download_verified_streaming_with_ensure?(peerAddr: string, blake3Hash: string, totalSize: number, onChunk: (chunk: Uint8Array<ArrayBuffer>, offset: number) => void, onProgress: (fraction: number) => void, downloadId?: string): Promise<number>;
|
|
19
|
+
/** unverified base64 json fallback for peers not reachable over the
|
|
20
|
+
* verified-transfer transport at all (e.g. a desktop peer whose native
|
|
21
|
+
* backend only accepts an app-level rpc alpn). strategy 3 - the
|
|
22
|
+
* response has no transport-level integrity checking, so its bytes are
|
|
23
|
+
* hash-checked explicitly against the requested blake3 before use. */
|
|
24
|
+
proxy_request?(peerAddr: string, method: string, path: string, body: string | null): Promise<{
|
|
25
|
+
status: number;
|
|
26
|
+
body: string;
|
|
27
|
+
}>;
|
|
28
|
+
/** pause/cancel an in-flight download by the id passed to it. returns
|
|
29
|
+
* false when the download already settled. */
|
|
30
|
+
download_cancel?(downloadId: string): Promise<boolean>;
|
|
31
|
+
/** release a gc pin held by a paused/cancelled download, letting the
|
|
32
|
+
* partial be reclaimed. */
|
|
33
|
+
unprotect_blob?(blake3Hash: string): Promise<void>;
|
|
34
|
+
/** import local bytes into the node's own store so a peer can download
|
|
35
|
+
* them by hash. used by `BlobServer`/`serveBlobRequest`. */
|
|
36
|
+
import_blob?(data: Uint8Array): Promise<string>;
|
|
37
|
+
/** release a previously-imported blob from the node's store. used by
|
|
38
|
+
* `BlobServer`'s release timer. */
|
|
39
|
+
release_blob?(blake3Hash: string): Promise<void> | void;
|
|
40
|
+
}
|
|
41
|
+
/** what a snatch needs to know about the blob being fetched. */
|
|
42
|
+
export interface SnatchInfo {
|
|
43
|
+
/** app-addressable id used to build the strategy-3 proxy path (see
|
|
44
|
+
* `SnatchOptions.proxyPath`). defaults to `blake3` when omitted - most
|
|
45
|
+
* callers address blobs by blake3 directly. */
|
|
46
|
+
id?: string;
|
|
47
|
+
blake3: string;
|
|
48
|
+
size: number;
|
|
49
|
+
mime?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface SnatchOptions {
|
|
52
|
+
onProgress?(fraction: number): void;
|
|
53
|
+
/** aborting rejects immediately with an AbortError - no next-peer retry,
|
|
54
|
+
* no disk truncation (a resumed snatch rewrites the same offsets). */
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
/** opaque id registered with the node for this download; pass the same
|
|
57
|
+
* id to `pauseSnatchDownload` to pause the in-flight transfer. */
|
|
58
|
+
downloadId?: string;
|
|
59
|
+
/** per-peer attempt timeout in ms. default 10 minutes - generous because
|
|
60
|
+
* the responding peer may need to stage the blob into its own store
|
|
61
|
+
* (bao tree computation) on first request. */
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
/** build the http path used by the base64 `proxy_request` fallback
|
|
64
|
+
* (strategy 3). omit to skip that strategy entirely. */
|
|
65
|
+
proxyPath?(id: string): string;
|
|
66
|
+
/** parse the strategy-3 proxy response body into its base64 payload +
|
|
67
|
+
* mime. defaults to a flat `{ data, mime }` json shape; supply this
|
|
68
|
+
* when the peer's rpc envelope nests differently. return null when the
|
|
69
|
+
* response indicates no blob. */
|
|
70
|
+
parseProxyResponse?(body: string): {
|
|
71
|
+
data: string;
|
|
72
|
+
mime?: string;
|
|
73
|
+
} | null;
|
|
74
|
+
}
|
|
75
|
+
export interface SnatchResult {
|
|
76
|
+
bytes: Uint8Array;
|
|
77
|
+
blake3: string;
|
|
78
|
+
mime?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface DiskSnatchResult {
|
|
81
|
+
size: number;
|
|
82
|
+
blake3: string;
|
|
83
|
+
mime?: string;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transfer/types.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,MAAM,CAAC;IAElB;;iEAE6D;IAC7D,sCAAsC,CAAC,CACrC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB;;;;;;;;;gBASY;IACZ,uCAAuC,CAAC,CACtC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,EACjE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB;;;;2EAIuE;IACvE,aAAa,CAAC,CACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,IAAI,GAClB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C;mDAC+C;IAC/C,eAAe,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;gCAC4B;IAC5B,cAAc,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;iEAC6D;IAC7D,WAAW,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhD;wCACoC;IACpC,YAAY,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACzD;AAED,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB;;oDAEgD;IAChD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;2EACuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;uEACmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;mDAE+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;6DACyD;IACzD,SAAS,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B;;;sCAGkC;IAClC,kBAAkB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// shared shapes for ./transfer: the structural transport surface this
|
|
2
|
+
// subpath needs from a midden-shaped node, plus the request/result types
|
|
3
|
+
// for snatch (peer -> local) and serve (local -> peer) flows.
|
|
4
|
+
//
|
|
5
|
+
// this subpath never imports midden at runtime - only structural types
|
|
6
|
+
// matching its snake_case surface, so any BlobCapableNode-shaped object
|
|
7
|
+
// (a real wasm node, a worker-backed facade, a test double) works the
|
|
8
|
+
// same way. every method is optional: a caller's node may implement only
|
|
9
|
+
// a subset (a lean transport, a peer reachable only via app-level rpc),
|
|
10
|
+
// and each snatch strategy degrades to the next when its required method
|
|
11
|
+
// is missing OR when it fails against a given peer (a peer whose backend
|
|
12
|
+
// only accepts an app-level rpc alpn makes the verified strategies fail
|
|
13
|
+
// even though the local node has the methods) - only a cancelled error
|
|
14
|
+
// stops the fallthrough.
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hash of an ArrayBuffer (or a view over one) as a lowercase hex string.
|
|
3
|
+
*/
|
|
4
|
+
export declare function sha256Hex(data: ArrayBuffer | ArrayBufferView): Promise<string>;
|
|
5
|
+
/**
|
|
6
|
+
* hash of a File/Blob's contents as a lowercase hex string.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sha256HexOfBlob(blob: Blob): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* true if `data` hashes to `expectedHex` (case-insensitive).
|
|
11
|
+
*/
|
|
12
|
+
export declare function verifySha256Hex(data: ArrayBuffer | ArrayBufferView, expectedHex: string): Promise<boolean>;
|
|
13
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/utils/hash.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpF;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjE;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,GAAG,eAAe,EACnC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAGlB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// sha256 hashing helpers built on the web crypto subtle api. used wherever a
|
|
2
|
+
// content hash is needed but the full blake3-canonical blob pipeline (./blobs,
|
|
3
|
+
// ./worker) is overkill - legacy sha256 references, quick integrity checks, etc.
|
|
4
|
+
/**
|
|
5
|
+
* hash of an ArrayBuffer (or a view over one) as a lowercase hex string.
|
|
6
|
+
*/
|
|
7
|
+
export async function sha256Hex(data) {
|
|
8
|
+
const buffer = ArrayBuffer.isView(data)
|
|
9
|
+
? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
|
|
10
|
+
: data;
|
|
11
|
+
const digest = await crypto.subtle.digest("SHA-256", buffer);
|
|
12
|
+
return bufferToHex(digest);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* hash of a File/Blob's contents as a lowercase hex string.
|
|
16
|
+
*/
|
|
17
|
+
export async function sha256HexOfBlob(blob) {
|
|
18
|
+
return sha256Hex(await blob.arrayBuffer());
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* true if `data` hashes to `expectedHex` (case-insensitive).
|
|
22
|
+
*/
|
|
23
|
+
export async function verifySha256Hex(data, expectedHex) {
|
|
24
|
+
const actual = await sha256Hex(data);
|
|
25
|
+
return actual === expectedHex.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function bufferToHex(buffer) {
|
|
28
|
+
return Array.from(new Uint8Array(buffer))
|
|
29
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
30
|
+
.join("");
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.test.d.ts","sourceRoot":"","sources":["../../src/utils/hash.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { sha256Hex, sha256HexOfBlob, verifySha256Hex } from "./hash.js";
|
|
3
|
+
// known-answer test vectors for sha-256
|
|
4
|
+
const EMPTY_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
5
|
+
const ABC_HASH = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
|
|
6
|
+
function textToBuffer(text) {
|
|
7
|
+
return new TextEncoder().encode(text).buffer;
|
|
8
|
+
}
|
|
9
|
+
describe("sha256Hex", () => {
|
|
10
|
+
it("hashes an empty buffer to the well-known sha-256 empty digest", async () => {
|
|
11
|
+
const hash = await sha256Hex(new ArrayBuffer(0));
|
|
12
|
+
expect(hash).toBe(EMPTY_HASH);
|
|
13
|
+
});
|
|
14
|
+
it("hashes 'abc' to its well-known sha-256 digest", async () => {
|
|
15
|
+
const hash = await sha256Hex(textToBuffer("abc"));
|
|
16
|
+
expect(hash).toBe(ABC_HASH);
|
|
17
|
+
});
|
|
18
|
+
it("accepts an ArrayBufferView (Uint8Array) as well as a raw ArrayBuffer", async () => {
|
|
19
|
+
const bytes = new TextEncoder().encode("abc");
|
|
20
|
+
const hash = await sha256Hex(bytes);
|
|
21
|
+
expect(hash).toBe(ABC_HASH);
|
|
22
|
+
});
|
|
23
|
+
it("only hashes the view's byte range, not the whole underlying buffer", async () => {
|
|
24
|
+
const backing = new Uint8Array(10);
|
|
25
|
+
backing.set(new TextEncoder().encode("abc"), 2);
|
|
26
|
+
const view = new Uint8Array(backing.buffer, 2, 3);
|
|
27
|
+
const hash = await sha256Hex(view);
|
|
28
|
+
expect(hash).toBe(ABC_HASH);
|
|
29
|
+
});
|
|
30
|
+
it("produces different hashes for different inputs", async () => {
|
|
31
|
+
const a = await sha256Hex(textToBuffer("hello"));
|
|
32
|
+
const b = await sha256Hex(textToBuffer("world"));
|
|
33
|
+
expect(a).not.toBe(b);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe("sha256HexOfBlob", () => {
|
|
37
|
+
it("hashes a Blob's contents the same as its raw bytes", async () => {
|
|
38
|
+
const blob = new Blob(["abc"]);
|
|
39
|
+
const hash = await sha256HexOfBlob(blob);
|
|
40
|
+
expect(hash).toBe(ABC_HASH);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("verifySha256Hex", () => {
|
|
44
|
+
it("returns true when the expected hash matches", async () => {
|
|
45
|
+
const ok = await verifySha256Hex(textToBuffer("abc"), ABC_HASH);
|
|
46
|
+
expect(ok).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it("returns false when the expected hash does not match", async () => {
|
|
49
|
+
const ok = await verifySha256Hex(textToBuffer("abc"), EMPTY_HASH);
|
|
50
|
+
expect(ok).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it("is case-insensitive when comparing the expected hash", async () => {
|
|
53
|
+
const ok = await verifySha256Hex(textToBuffer("abc"), ABC_HASH.toUpperCase());
|
|
54
|
+
expect(ok).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* options for picking and resizing an image file.
|
|
3
|
+
*/
|
|
4
|
+
export interface PickImageOptions {
|
|
5
|
+
/** maximum output width in pixels (default: 200) */
|
|
6
|
+
maxWidth?: number;
|
|
7
|
+
/** maximum output height in pixels (default: 200) */
|
|
8
|
+
maxHeight?: number;
|
|
9
|
+
/** output quality 0-1, for lossy formats like webp/jpeg (default: 0.8) */
|
|
10
|
+
quality?: number;
|
|
11
|
+
/** if true, center-crop to a square before resizing (default: false) */
|
|
12
|
+
cropSquare?: boolean;
|
|
13
|
+
/** output mime type (default: "image/webp") */
|
|
14
|
+
mime?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* open a native file picker for images, resize and encode as a data url.
|
|
18
|
+
* returns null if the user cancels, the input never resolves, or an error
|
|
19
|
+
* occurs.
|
|
20
|
+
*/
|
|
21
|
+
export declare function pickImageAsDataUrl(options?: PickImageOptions): Promise<string | null>;
|
|
22
|
+
/**
|
|
23
|
+
* resize an image File/Blob to a data url. useful when a file is already in
|
|
24
|
+
* hand (e.g. from drag-and-drop). returns null on error, or if the current
|
|
25
|
+
* environment has no OffscreenCanvas/createImageBitmap support.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resizeImageToDataUrl(blob: Blob, options?: PickImageOptions): Promise<string | null>;
|
|
28
|
+
//# sourceMappingURL=image-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-utils.d.ts","sourceRoot":"","sources":["../../src/utils/image-utils.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmC3F;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsDxB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// helpers for picking an image file from the user's device and converting it
|
|
2
|
+
// to a small webp data url. resizing runs on the main thread via
|
|
3
|
+
// OffscreenCanvas/createImageBitmap; a worker-backed variant that keeps the
|
|
4
|
+
// decode/encode work off the main thread can build on top of this once the
|
|
5
|
+
// ./worker subpath exists.
|
|
6
|
+
const DEFAULT_MAX_WIDTH = 200;
|
|
7
|
+
const DEFAULT_MAX_HEIGHT = 200;
|
|
8
|
+
const DEFAULT_QUALITY = 0.8;
|
|
9
|
+
const DEFAULT_MIME = "image/webp";
|
|
10
|
+
/**
|
|
11
|
+
* open a native file picker for images, resize and encode as a data url.
|
|
12
|
+
* returns null if the user cancels, the input never resolves, or an error
|
|
13
|
+
* occurs.
|
|
14
|
+
*/
|
|
15
|
+
export async function pickImageAsDataUrl(options) {
|
|
16
|
+
const input = document.createElement("input");
|
|
17
|
+
input.type = "file";
|
|
18
|
+
input.accept = "image/*";
|
|
19
|
+
input.style.display = "none";
|
|
20
|
+
document.body.appendChild(input);
|
|
21
|
+
try {
|
|
22
|
+
input.click();
|
|
23
|
+
const file = await new Promise((resolve) => {
|
|
24
|
+
input.addEventListener("change", () => {
|
|
25
|
+
resolve(input.files?.[0] ?? null);
|
|
26
|
+
});
|
|
27
|
+
// the `cancel` event is the standard way to detect picker dismissal.
|
|
28
|
+
// it is supported in chrome 113+ and firefox 113+. older browsers that
|
|
29
|
+
// don't fire `cancel` will leave this promise pending until the page
|
|
30
|
+
// navigates - acceptable because pickImageAsDataUrl is always called in
|
|
31
|
+
// a fire-and-forget context and callers re-enable the trigger on each
|
|
32
|
+
// interaction.
|
|
33
|
+
input.addEventListener("cancel", () => resolve(null));
|
|
34
|
+
});
|
|
35
|
+
if (!file) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return await resizeImageToDataUrl(file, options);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
input.remove();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* resize an image File/Blob to a data url. useful when a file is already in
|
|
49
|
+
* hand (e.g. from drag-and-drop). returns null on error, or if the current
|
|
50
|
+
* environment has no OffscreenCanvas/createImageBitmap support.
|
|
51
|
+
*/
|
|
52
|
+
export async function resizeImageToDataUrl(blob, options) {
|
|
53
|
+
if (typeof OffscreenCanvas === "undefined" || typeof createImageBitmap !== "function") {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const maxWidth = options?.maxWidth ?? DEFAULT_MAX_WIDTH;
|
|
57
|
+
const maxHeight = options?.maxHeight ?? DEFAULT_MAX_HEIGHT;
|
|
58
|
+
const quality = options?.quality ?? DEFAULT_QUALITY;
|
|
59
|
+
const cropSquare = options?.cropSquare ?? false;
|
|
60
|
+
const mime = options?.mime ?? DEFAULT_MIME;
|
|
61
|
+
let bitmap = null;
|
|
62
|
+
try {
|
|
63
|
+
bitmap = await createImageBitmap(blob);
|
|
64
|
+
let sx = 0;
|
|
65
|
+
let sy = 0;
|
|
66
|
+
let sw = bitmap.width;
|
|
67
|
+
let sh = bitmap.height;
|
|
68
|
+
if (cropSquare) {
|
|
69
|
+
const minDim = Math.min(bitmap.width, bitmap.height);
|
|
70
|
+
sx = (bitmap.width - minDim) / 2;
|
|
71
|
+
sy = (bitmap.height - minDim) / 2;
|
|
72
|
+
sw = minDim;
|
|
73
|
+
sh = minDim;
|
|
74
|
+
}
|
|
75
|
+
const aspect = sw / sh;
|
|
76
|
+
let outW = sw;
|
|
77
|
+
let outH = sh;
|
|
78
|
+
if (outW > maxWidth) {
|
|
79
|
+
outW = maxWidth;
|
|
80
|
+
outH = Math.round(outW / aspect);
|
|
81
|
+
}
|
|
82
|
+
if (outH > maxHeight) {
|
|
83
|
+
outH = maxHeight;
|
|
84
|
+
outW = Math.round(outH * aspect);
|
|
85
|
+
}
|
|
86
|
+
outW = Math.max(1, outW);
|
|
87
|
+
outH = Math.max(1, outH);
|
|
88
|
+
const canvas = new OffscreenCanvas(outW, outH);
|
|
89
|
+
const ctx = canvas.getContext("2d");
|
|
90
|
+
if (!ctx)
|
|
91
|
+
return null;
|
|
92
|
+
ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, outW, outH);
|
|
93
|
+
const out = await canvas.convertToBlob({ type: mime, quality });
|
|
94
|
+
const buffer = await out.arrayBuffer();
|
|
95
|
+
return `data:${mime};base64,${arrayBufferToBase64(buffer)}`;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
bitmap?.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function arrayBufferToBase64(buffer) {
|
|
105
|
+
const bytes = new Uint8Array(buffer);
|
|
106
|
+
const chunkSize = 0x8000;
|
|
107
|
+
let binary = "";
|
|
108
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
109
|
+
const chunk = bytes.subarray(i, i + chunkSize);
|
|
110
|
+
binary += String.fromCharCode(...chunk);
|
|
111
|
+
}
|
|
112
|
+
return btoa(binary);
|
|
113
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-utils.test.d.ts","sourceRoot":"","sources":["../../src/utils/image-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { pickImageAsDataUrl, resizeImageToDataUrl } from "./image-utils.js";
|
|
4
|
+
// happy-dom provides `document` and the File/Blob constructors, but not canvas
|
|
5
|
+
// rasterization (OffscreenCanvas, createImageBitmap) - no environment does that
|
|
6
|
+
// without native bindings. these fakes stand in for the browser's actual image
|
|
7
|
+
// decode/encode pipeline so the surrounding sizing/cropping/error-handling logic
|
|
8
|
+
// gets real coverage without a native canvas dependency.
|
|
9
|
+
class FakeImageBitmap {
|
|
10
|
+
width;
|
|
11
|
+
height;
|
|
12
|
+
constructor(width, height) {
|
|
13
|
+
this.width = width;
|
|
14
|
+
this.height = height;
|
|
15
|
+
}
|
|
16
|
+
close = vi.fn();
|
|
17
|
+
}
|
|
18
|
+
class FakeOffscreenCanvasContext {
|
|
19
|
+
drawImage = vi.fn();
|
|
20
|
+
}
|
|
21
|
+
class FakeOffscreenCanvas {
|
|
22
|
+
width;
|
|
23
|
+
height;
|
|
24
|
+
static instances = [];
|
|
25
|
+
context = new FakeOffscreenCanvasContext();
|
|
26
|
+
convertToBlobResult = new Blob(["fake-encoded-bytes"], { type: "image/webp" });
|
|
27
|
+
constructor(width, height) {
|
|
28
|
+
this.width = width;
|
|
29
|
+
this.height = height;
|
|
30
|
+
FakeOffscreenCanvas.instances.push(this);
|
|
31
|
+
}
|
|
32
|
+
getContext() {
|
|
33
|
+
return this.context;
|
|
34
|
+
}
|
|
35
|
+
async convertToBlob(options) {
|
|
36
|
+
if (!this.convertToBlobResult)
|
|
37
|
+
throw new Error("encode failed");
|
|
38
|
+
return new Blob(["fake-encoded-bytes"], { type: options?.type ?? "image/webp" });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function installCanvasFakes(bitmapSize) {
|
|
42
|
+
vi.stubGlobal("createImageBitmap", vi.fn(async () => new FakeImageBitmap(bitmapSize.width, bitmapSize.height)));
|
|
43
|
+
vi.stubGlobal("OffscreenCanvas", FakeOffscreenCanvas);
|
|
44
|
+
}
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.unstubAllGlobals();
|
|
47
|
+
FakeOffscreenCanvas.instances = [];
|
|
48
|
+
});
|
|
49
|
+
describe("resizeImageToDataUrl", () => {
|
|
50
|
+
it("returns null when OffscreenCanvas/createImageBitmap are unavailable", async () => {
|
|
51
|
+
const result = await resizeImageToDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
52
|
+
expect(result).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
it("returns a data url with the requested (default) mime type", async () => {
|
|
55
|
+
installCanvasFakes({ width: 400, height: 300 });
|
|
56
|
+
const result = await resizeImageToDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
57
|
+
expect(result).toMatch(/^data:image\/webp;base64,/);
|
|
58
|
+
});
|
|
59
|
+
it("downscales to fit within maxWidth/maxHeight while preserving aspect ratio", async () => {
|
|
60
|
+
installCanvasFakes({ width: 400, height: 200 });
|
|
61
|
+
await resizeImageToDataUrl(new Blob(["x"]), { maxWidth: 100, maxHeight: 100 });
|
|
62
|
+
const canvas = FakeOffscreenCanvas.instances[0];
|
|
63
|
+
expect(canvas.width).toBe(100);
|
|
64
|
+
expect(canvas.height).toBe(50);
|
|
65
|
+
});
|
|
66
|
+
it("does not upscale images smaller than the max bounds", async () => {
|
|
67
|
+
installCanvasFakes({ width: 50, height: 40 });
|
|
68
|
+
await resizeImageToDataUrl(new Blob(["x"]), { maxWidth: 200, maxHeight: 200 });
|
|
69
|
+
const canvas = FakeOffscreenCanvas.instances[0];
|
|
70
|
+
expect(canvas.width).toBe(50);
|
|
71
|
+
expect(canvas.height).toBe(40);
|
|
72
|
+
});
|
|
73
|
+
it("center-crops to a square when cropSquare is set", async () => {
|
|
74
|
+
installCanvasFakes({ width: 400, height: 200 });
|
|
75
|
+
await resizeImageToDataUrl(new Blob(["x"]), { cropSquare: true, maxWidth: 200, maxHeight: 200 });
|
|
76
|
+
const canvas = FakeOffscreenCanvas.instances[0];
|
|
77
|
+
expect(canvas.width).toBe(200);
|
|
78
|
+
expect(canvas.height).toBe(200);
|
|
79
|
+
expect(canvas.context.drawImage).toHaveBeenCalledWith(expect.anything(), 100, // sx: (400 - 200) / 2
|
|
80
|
+
0, // sy: (200 - 200) / 2
|
|
81
|
+
200, // sw
|
|
82
|
+
200, // sh
|
|
83
|
+
0, 0, 200, 200);
|
|
84
|
+
});
|
|
85
|
+
it("passes the requested mime type and quality through to the encoder", async () => {
|
|
86
|
+
installCanvasFakes({ width: 100, height: 100 });
|
|
87
|
+
const result = await resizeImageToDataUrl(new Blob(["x"]), {
|
|
88
|
+
mime: "image/jpeg",
|
|
89
|
+
quality: 0.5,
|
|
90
|
+
});
|
|
91
|
+
expect(result).toMatch(/^data:image\/jpeg;base64,/);
|
|
92
|
+
});
|
|
93
|
+
it("closes the decoded bitmap even when encoding succeeds", async () => {
|
|
94
|
+
installCanvasFakes({ width: 100, height: 100 });
|
|
95
|
+
const bitmaps = [];
|
|
96
|
+
vi.stubGlobal("createImageBitmap", vi.fn(async () => {
|
|
97
|
+
const bmp = new FakeImageBitmap(100, 100);
|
|
98
|
+
bitmaps.push(bmp);
|
|
99
|
+
return bmp;
|
|
100
|
+
}));
|
|
101
|
+
await resizeImageToDataUrl(new Blob(["x"]));
|
|
102
|
+
expect(bitmaps[0].close).toHaveBeenCalledTimes(1);
|
|
103
|
+
});
|
|
104
|
+
it("returns null and still closes the bitmap when getContext returns null", async () => {
|
|
105
|
+
installCanvasFakes({ width: 100, height: 100 });
|
|
106
|
+
const bitmaps = [];
|
|
107
|
+
vi.stubGlobal("createImageBitmap", vi.fn(async () => {
|
|
108
|
+
const bmp = new FakeImageBitmap(100, 100);
|
|
109
|
+
bitmaps.push(bmp);
|
|
110
|
+
return bmp;
|
|
111
|
+
}));
|
|
112
|
+
vi.spyOn(FakeOffscreenCanvas.prototype, "getContext").mockReturnValueOnce(null);
|
|
113
|
+
const result = await resizeImageToDataUrl(new Blob(["x"]));
|
|
114
|
+
expect(result).toBeNull();
|
|
115
|
+
expect(bitmaps[0].close).toHaveBeenCalledTimes(1);
|
|
116
|
+
});
|
|
117
|
+
it("returns null when createImageBitmap rejects (e.g. non-image input)", async () => {
|
|
118
|
+
vi.stubGlobal("createImageBitmap", vi.fn(async () => {
|
|
119
|
+
throw new Error("not an image");
|
|
120
|
+
}));
|
|
121
|
+
vi.stubGlobal("OffscreenCanvas", FakeOffscreenCanvas);
|
|
122
|
+
const result = await resizeImageToDataUrl(new Blob(["not an image"]));
|
|
123
|
+
expect(result).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe("pickImageAsDataUrl", () => {
|
|
127
|
+
function captureInput() {
|
|
128
|
+
let captured = null;
|
|
129
|
+
const realCreateElement = document.createElement.bind(document);
|
|
130
|
+
vi.spyOn(document, "createElement").mockImplementation((tag) => {
|
|
131
|
+
const el = realCreateElement(tag);
|
|
132
|
+
if (tag === "input")
|
|
133
|
+
captured = el;
|
|
134
|
+
return el;
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
getInput: () => {
|
|
138
|
+
if (!captured)
|
|
139
|
+
throw new Error("input was never created");
|
|
140
|
+
return captured;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
it("resolves with the resized data url when a file is chosen", async () => {
|
|
145
|
+
installCanvasFakes({ width: 100, height: 100 });
|
|
146
|
+
const { getInput } = captureInput();
|
|
147
|
+
const promise = pickImageAsDataUrl();
|
|
148
|
+
const input = getInput();
|
|
149
|
+
const file = new File(["x"], "photo.png", { type: "image/png" });
|
|
150
|
+
Object.defineProperty(input, "files", { value: [file], configurable: true });
|
|
151
|
+
input.dispatchEvent(new Event("change"));
|
|
152
|
+
const result = await promise;
|
|
153
|
+
expect(result).toMatch(/^data:image\/webp;base64,/);
|
|
154
|
+
});
|
|
155
|
+
it("resolves with null when the picker is cancelled", async () => {
|
|
156
|
+
const { getInput } = captureInput();
|
|
157
|
+
const promise = pickImageAsDataUrl();
|
|
158
|
+
const input = getInput();
|
|
159
|
+
input.dispatchEvent(new Event("cancel"));
|
|
160
|
+
const result = await promise;
|
|
161
|
+
expect(result).toBeNull();
|
|
162
|
+
});
|
|
163
|
+
it("resolves with null when the change event fires with no file selected", async () => {
|
|
164
|
+
const { getInput } = captureInput();
|
|
165
|
+
const promise = pickImageAsDataUrl();
|
|
166
|
+
const input = getInput();
|
|
167
|
+
Object.defineProperty(input, "files", { value: [], configurable: true });
|
|
168
|
+
input.dispatchEvent(new Event("change"));
|
|
169
|
+
const result = await promise;
|
|
170
|
+
expect(result).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
it("removes the temporary input element after resolving", async () => {
|
|
173
|
+
const { getInput } = captureInput();
|
|
174
|
+
const promise = pickImageAsDataUrl();
|
|
175
|
+
const input = getInput();
|
|
176
|
+
expect(document.body.contains(input)).toBe(true);
|
|
177
|
+
input.dispatchEvent(new Event("cancel"));
|
|
178
|
+
await promise;
|
|
179
|
+
expect(document.body.contains(input)).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
|
|
2
|
+
export interface LoggingConfig {
|
|
3
|
+
level?: LogLevel;
|
|
4
|
+
/** tag prefixes to allow; an empty array (or omitting this) allows every tag. */
|
|
5
|
+
filter?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* set the default log level and/or tag filter. intended to be called once at
|
|
9
|
+
* app startup; localStorage overrides (if present) still take priority so
|
|
10
|
+
* logging can be adjusted at runtime without a rebuild.
|
|
11
|
+
*/
|
|
12
|
+
export declare function configureLogging(config: LoggingConfig): void;
|
|
13
|
+
/** clear any configureLogging() overrides, restoring the built-in default. */
|
|
14
|
+
export declare function resetLoggingConfig(): void;
|
|
15
|
+
export declare const log: {
|
|
16
|
+
trace: (tag: string, msg: string, ...args: unknown[]) => void;
|
|
17
|
+
debug: (tag: string, msg: string, ...args: unknown[]) => void;
|
|
18
|
+
info: (tag: string, msg: string, ...args: unknown[]) => void;
|
|
19
|
+
warn: (tag: string, msg: string, ...args: unknown[]) => void;
|
|
20
|
+
error: (tag: string, msg: string, ...args: unknown[]) => void;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAeD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAG5D;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAyCD,eAAO,MAAM,GAAG;iBACD,MAAM,OAAO,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;iBAC9C,MAAM,OAAO,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;gBAC/C,MAAM,OAAO,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;gBAC9C,MAAM,OAAO,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;iBAC7C,MAAM,OAAO,MAAM,WAAW,OAAO,EAAE,KAAG,IAAI;CAC5D,CAAC"}
|