@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,368 @@
|
|
|
1
|
+
// blob worker logic - the CPU-bound blob work (blake3 hashing, sha256
|
|
2
|
+
// hashing, base64 encode/decode, OPFS writes, thumbnail generation, chunked
|
|
3
|
+
// upload sessions) that `blob-worker.ts` exposes over comlink.
|
|
4
|
+
//
|
|
5
|
+
// kept free of comlink/postMessage side effects on purpose: this module is
|
|
6
|
+
// what the test suite imports directly, and what `blob-worker.ts` (the
|
|
7
|
+
// thin worker entry) and `blob-worker-client.ts` (the main-thread fallback
|
|
8
|
+
// path) both build on.
|
|
9
|
+
//
|
|
10
|
+
// browser-only. blake3 hashing needs a midden-shaped module bundled by the
|
|
11
|
+
// embedding app (see `midden-blake3.ts`) - without one, `hashBlake3`
|
|
12
|
+
// degrades to an empty string and the streaming upload/selftest functions
|
|
13
|
+
// throw a clear error, matching the graceful-degradation contract of the
|
|
14
|
+
// rest of this file.
|
|
15
|
+
import { sha256Hex } from "../utils/hash.js";
|
|
16
|
+
import { loadMiddenBlake3 } from "./midden-blake3.js";
|
|
17
|
+
import { log } from "../utils/log.js";
|
|
18
|
+
const TAG = "blob.worker.logic";
|
|
19
|
+
/** message the worker entry posts once comlink has registered its message
|
|
20
|
+
* listener - see `blob-worker.ts` for why the ordering matters. */
|
|
21
|
+
export const BLOB_WORKER_READY_MESSAGE = "blob-worker-ready";
|
|
22
|
+
/** directory name for the on-disk (OPFS) blob store. exported so callers
|
|
23
|
+
* reading bytes back directly (bypassing the worker's write path, e.g.
|
|
24
|
+
* `./blobs`'s main-thread OPFS reads) always agree with the worker on
|
|
25
|
+
* where the bytes actually live. */
|
|
26
|
+
export const OPFS_DIR = "reliquary-blobs";
|
|
27
|
+
/**
|
|
28
|
+
* compute blake3 hash of a Uint8Array via the embedding app's midden
|
|
29
|
+
* module. returns an empty string when no such module is bundled, or when
|
|
30
|
+
* it does not export `hash_blake3`.
|
|
31
|
+
*/
|
|
32
|
+
export async function hashBlake3(data) {
|
|
33
|
+
try {
|
|
34
|
+
const midden = await loadMiddenBlake3();
|
|
35
|
+
if (!midden || typeof midden.hash_blake3 !== "function") {
|
|
36
|
+
log.warn(TAG, "no midden module with hash_blake3 available, blake3 hashing degraded to empty string");
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return midden.hash_blake3(data);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
log.warn(TAG, "blake3 hashing threw, degrading to empty string:", err);
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* compute sha256 hash via SubtleCrypto. SubtleCrypto.digest is already
|
|
48
|
+
* async/non-blocking on the main thread, but this is exposed here too so
|
|
49
|
+
* callers can do sha256 + blake3 in a single round-trip.
|
|
50
|
+
*/
|
|
51
|
+
export async function hashSha256(data) {
|
|
52
|
+
return sha256Hex(data);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* base64-encode an ArrayBuffer. uses chunked btoa to avoid stack overflow
|
|
56
|
+
* on large buffers (`String.fromCharCode(...veryLargeArray)` blows up).
|
|
57
|
+
*/
|
|
58
|
+
export async function base64Encode(buffer) {
|
|
59
|
+
const bytes = new Uint8Array(buffer);
|
|
60
|
+
const chunkSize = 0x8000; // 32 KiB at a time
|
|
61
|
+
let binary = "";
|
|
62
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
63
|
+
const chunk = bytes.subarray(i, i + chunkSize);
|
|
64
|
+
binary += String.fromCharCode.apply(null, chunk);
|
|
65
|
+
}
|
|
66
|
+
return btoa(binary);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* decode a base64 string into a Uint8Array.
|
|
70
|
+
*/
|
|
71
|
+
export async function base64Decode(b64) {
|
|
72
|
+
const binary = atob(b64);
|
|
73
|
+
const out = new Uint8Array(binary.length);
|
|
74
|
+
for (let i = 0; i < binary.length; i++) {
|
|
75
|
+
out[i] = binary.charCodeAt(i);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
// ---- OPFS write path ------------------------------------------------------
|
|
80
|
+
async function getOpfsDir(create = false) {
|
|
81
|
+
try {
|
|
82
|
+
const root = await navigator.storage.getDirectory();
|
|
83
|
+
return await root.getDirectoryHandle(OPFS_DIR, { create });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* write blob bytes to OPFS. prefers `FileSystemSyncAccessHandle` (worker-only,
|
|
91
|
+
* fastest path on chromium/safari/firefox) and falls back to the async
|
|
92
|
+
* writable-stream API. silently no-ops if OPFS is unavailable.
|
|
93
|
+
*
|
|
94
|
+
* `data` should be transferred across postMessage to avoid a copy.
|
|
95
|
+
*/
|
|
96
|
+
export async function writeBlobToOpfs(blobId, data) {
|
|
97
|
+
const dir = await getOpfsDir(true);
|
|
98
|
+
if (!dir)
|
|
99
|
+
return;
|
|
100
|
+
const fileHandle = await dir.getFileHandle(blobId, { create: true });
|
|
101
|
+
// sync access handle: only available in workers, much faster than
|
|
102
|
+
// createWritable(). uses synchronous I/O on a dedicated I/O thread.
|
|
103
|
+
const createSync = fileHandle.createSyncAccessHandle;
|
|
104
|
+
if (typeof createSync === "function") {
|
|
105
|
+
let handle = null;
|
|
106
|
+
try {
|
|
107
|
+
handle = await createSync.call(fileHandle);
|
|
108
|
+
handle.truncate(0);
|
|
109
|
+
handle.write(data, { at: 0 });
|
|
110
|
+
handle.flush();
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
handle?.close();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// fallback: async writable stream (works on main thread too)
|
|
118
|
+
const createWritable = fileHandle.createWritable;
|
|
119
|
+
if (typeof createWritable !== "function")
|
|
120
|
+
return;
|
|
121
|
+
const writable = await createWritable.call(fileHandle);
|
|
122
|
+
await writable.write(data);
|
|
123
|
+
await writable.close();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* read blob bytes from OPFS into a transferable ArrayBuffer. returns null
|
|
127
|
+
* if the file doesn't exist or OPFS is unavailable.
|
|
128
|
+
*/
|
|
129
|
+
export async function readBlobFromOpfs(blobId) {
|
|
130
|
+
const dir = await getOpfsDir(false);
|
|
131
|
+
if (!dir)
|
|
132
|
+
return null;
|
|
133
|
+
try {
|
|
134
|
+
const fileHandle = await dir.getFileHandle(blobId, { create: false });
|
|
135
|
+
const file = await fileHandle.getFile();
|
|
136
|
+
return await file.arrayBuffer();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* one-shot: hash bytes (sha256 + blake3), write to OPFS, return metadata.
|
|
144
|
+
* lets callers avoid three round-trips across the worker boundary for an
|
|
145
|
+
* upload. `data` should be transferred.
|
|
146
|
+
*
|
|
147
|
+
* blake3 is the canonical blob id (matches iroh-blobs / a native rust
|
|
148
|
+
* store); sha256 is still computed so legacy sha256-keyed records and old
|
|
149
|
+
* doc references keep resolving via the sha256 index.
|
|
150
|
+
*/
|
|
151
|
+
export async function processBlobBytes(data, filename, mime) {
|
|
152
|
+
// run sha256 and blake3 concurrently. SubtleCrypto.digest does its own
|
|
153
|
+
// copy of the bytes, so we can't transfer-and-reuse - do them in parallel
|
|
154
|
+
// and let the runtime overlap them.
|
|
155
|
+
const [sha256, blake3] = await Promise.all([hashSha256(data), hashBlake3(new Uint8Array(data))]);
|
|
156
|
+
await writeBlobToOpfs(blake3, data);
|
|
157
|
+
return {
|
|
158
|
+
blob_id: blake3,
|
|
159
|
+
sha256,
|
|
160
|
+
blake3,
|
|
161
|
+
size: data.byteLength,
|
|
162
|
+
mime,
|
|
163
|
+
filename,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* resize an image Blob to a WebP data URL via OffscreenCanvas. all heavy
|
|
168
|
+
* work (image decode, resize, WebP encode, base64 encode) happens here.
|
|
169
|
+
* returns null on any failure.
|
|
170
|
+
*
|
|
171
|
+
* the input Blob is structured-cloned across postMessage by reference
|
|
172
|
+
* (the underlying bytes aren't copied), so this is cheap to call.
|
|
173
|
+
*/
|
|
174
|
+
export async function resizeImageToWebpDataUrl(blob, options) {
|
|
175
|
+
const maxWidth = options?.maxWidth ?? 200;
|
|
176
|
+
const maxHeight = options?.maxHeight ?? 200;
|
|
177
|
+
const quality = options?.quality ?? 0.8;
|
|
178
|
+
const cropSquare = options?.cropSquare ?? false;
|
|
179
|
+
const mime = options?.mime ?? "image/webp";
|
|
180
|
+
let bitmap = null;
|
|
181
|
+
try {
|
|
182
|
+
bitmap = await createImageBitmap(blob);
|
|
183
|
+
let sx = 0;
|
|
184
|
+
let sy = 0;
|
|
185
|
+
let sw = bitmap.width;
|
|
186
|
+
let sh = bitmap.height;
|
|
187
|
+
if (cropSquare) {
|
|
188
|
+
const minDim = Math.min(bitmap.width, bitmap.height);
|
|
189
|
+
sx = (bitmap.width - minDim) / 2;
|
|
190
|
+
sy = (bitmap.height - minDim) / 2;
|
|
191
|
+
sw = minDim;
|
|
192
|
+
sh = minDim;
|
|
193
|
+
}
|
|
194
|
+
const sourceAspect = sw / sh;
|
|
195
|
+
let outW = sw;
|
|
196
|
+
let outH = sh;
|
|
197
|
+
if (outW > maxWidth) {
|
|
198
|
+
outW = maxWidth;
|
|
199
|
+
outH = Math.round(outW / sourceAspect);
|
|
200
|
+
}
|
|
201
|
+
if (outH > maxHeight) {
|
|
202
|
+
outH = maxHeight;
|
|
203
|
+
outW = Math.round(outH * sourceAspect);
|
|
204
|
+
}
|
|
205
|
+
outW = Math.max(1, outW);
|
|
206
|
+
outH = Math.max(1, outH);
|
|
207
|
+
const canvas = new OffscreenCanvas(outW, outH);
|
|
208
|
+
const ctx = canvas.getContext("2d");
|
|
209
|
+
if (!ctx)
|
|
210
|
+
return null;
|
|
211
|
+
ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, outW, outH);
|
|
212
|
+
const out = await canvas.convertToBlob({ type: mime, quality });
|
|
213
|
+
const buf = await out.arrayBuffer();
|
|
214
|
+
const b64 = await base64Encode(buf);
|
|
215
|
+
return `data:${mime};base64,${b64}`;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
bitmap?.close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* convenience wrapper that fits an image inside `maxSize` x `maxSize`,
|
|
226
|
+
* encoding WebP at 0.75 quality. skips non-image blobs.
|
|
227
|
+
*/
|
|
228
|
+
export async function generateThumbnailDataUrl(blob, maxSize = 200) {
|
|
229
|
+
if (!blob.type.startsWith("image/"))
|
|
230
|
+
return null;
|
|
231
|
+
return resizeImageToWebpDataUrl(blob, {
|
|
232
|
+
maxWidth: maxSize,
|
|
233
|
+
maxHeight: maxSize,
|
|
234
|
+
quality: 0.75,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const uploadSessions = new Map();
|
|
238
|
+
let nextUploadSessionId = 1;
|
|
239
|
+
/**
|
|
240
|
+
* begin a streaming upload session. throws when the environment can't
|
|
241
|
+
* support it (no OPFS sync access handles, or no midden module with a
|
|
242
|
+
* Blake3Hasher bundled) - callers fall back to the one-shot
|
|
243
|
+
* processBlobBytes path.
|
|
244
|
+
*/
|
|
245
|
+
export async function uploadBegin() {
|
|
246
|
+
const midden = await loadMiddenBlake3();
|
|
247
|
+
const Blake3HasherCtor = midden?.Blake3Hasher;
|
|
248
|
+
if (typeof Blake3HasherCtor !== "function") {
|
|
249
|
+
throw new Error("streaming upload unavailable: midden Blake3Hasher missing");
|
|
250
|
+
}
|
|
251
|
+
const dir = await getOpfsDir(true);
|
|
252
|
+
if (!dir)
|
|
253
|
+
throw new Error("streaming upload unavailable: OPFS inaccessible");
|
|
254
|
+
const id = nextUploadSessionId++;
|
|
255
|
+
const tmpName = `.upload-${id}-${Date.now()}`;
|
|
256
|
+
const fileHandle = await dir.getFileHandle(tmpName, { create: true });
|
|
257
|
+
const createSync = fileHandle.createSyncAccessHandle;
|
|
258
|
+
if (typeof createSync !== "function") {
|
|
259
|
+
await dir.removeEntry(tmpName).catch(() => { });
|
|
260
|
+
throw new Error("streaming upload unavailable: no sync access handle support");
|
|
261
|
+
}
|
|
262
|
+
const handle = await createSync.call(fileHandle);
|
|
263
|
+
handle.truncate(0);
|
|
264
|
+
uploadSessions.set(id, { handle, tmpName, hasher: new Blake3HasherCtor(), size: 0 });
|
|
265
|
+
return id;
|
|
266
|
+
}
|
|
267
|
+
/** append the next chunk (transferred buffer) to a session. */
|
|
268
|
+
export async function uploadPush(id, buffer) {
|
|
269
|
+
const session = uploadSessions.get(id);
|
|
270
|
+
if (!session)
|
|
271
|
+
throw new Error(`unknown upload session ${id}`);
|
|
272
|
+
const bytes = new Uint8Array(buffer);
|
|
273
|
+
session.hasher.update(bytes);
|
|
274
|
+
session.handle.write(bytes, { at: session.size });
|
|
275
|
+
session.size += bytes.byteLength;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* finish a session: flush + close the temp file, then rename it to the
|
|
279
|
+
* final blake3 content address. returns { blake3, size }.
|
|
280
|
+
*/
|
|
281
|
+
export async function uploadFinish(id) {
|
|
282
|
+
const session = uploadSessions.get(id);
|
|
283
|
+
if (!session)
|
|
284
|
+
throw new Error(`unknown upload session ${id}`);
|
|
285
|
+
uploadSessions.delete(id);
|
|
286
|
+
let blake3 = "";
|
|
287
|
+
try {
|
|
288
|
+
session.handle.flush();
|
|
289
|
+
session.handle.close();
|
|
290
|
+
blake3 = session.hasher.finalize();
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
session.hasher.free();
|
|
294
|
+
}
|
|
295
|
+
const dir = await getOpfsDir(true);
|
|
296
|
+
if (!dir)
|
|
297
|
+
throw new Error("OPFS inaccessible at upload finish");
|
|
298
|
+
const tmpHandle = await dir.getFileHandle(session.tmpName);
|
|
299
|
+
// dedup: if the content-addressed file already exists, drop the temp copy
|
|
300
|
+
let exists = false;
|
|
301
|
+
try {
|
|
302
|
+
await dir.getFileHandle(blake3, { create: false });
|
|
303
|
+
exists = true;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// target doesn't exist - rename below
|
|
307
|
+
}
|
|
308
|
+
if (exists) {
|
|
309
|
+
await dir.removeEntry(session.tmpName).catch(() => { });
|
|
310
|
+
return { blake3, size: session.size };
|
|
311
|
+
}
|
|
312
|
+
const move = tmpHandle.move;
|
|
313
|
+
if (typeof move === "function") {
|
|
314
|
+
await move.call(tmpHandle, blake3);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// rare fallback (no FileSystemFileHandle.move): copy then remove.
|
|
318
|
+
// buffers once here - still better than failing the upload.
|
|
319
|
+
const file = await tmpHandle.getFile();
|
|
320
|
+
await writeBlobToOpfs(blake3, await file.arrayBuffer());
|
|
321
|
+
await dir.removeEntry(session.tmpName).catch(() => { });
|
|
322
|
+
}
|
|
323
|
+
return { blake3, size: session.size };
|
|
324
|
+
}
|
|
325
|
+
/** abort a session: close and delete the temp file. */
|
|
326
|
+
export async function uploadAbort(id) {
|
|
327
|
+
const session = uploadSessions.get(id);
|
|
328
|
+
if (!session)
|
|
329
|
+
return;
|
|
330
|
+
uploadSessions.delete(id);
|
|
331
|
+
try {
|
|
332
|
+
session.handle.close();
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// already closed
|
|
336
|
+
}
|
|
337
|
+
session.hasher.free();
|
|
338
|
+
const dir = await getOpfsDir(false);
|
|
339
|
+
await dir?.removeEntry(session.tmpName).catch(() => { });
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* opfs-store selftest - runs the embedding app's midden module's
|
|
343
|
+
* out-of-crate iroh-blobs store round trip against real OPFS. must run in
|
|
344
|
+
* a dedicated worker: sync access handles don't exist on the main thread.
|
|
345
|
+
* throws when no midden module (or no `opfs_store_selftest` export) is
|
|
346
|
+
* bundled.
|
|
347
|
+
*/
|
|
348
|
+
export async function opfsStoreSelftest() {
|
|
349
|
+
const midden = await loadMiddenBlake3();
|
|
350
|
+
const fn = midden?.opfs_store_selftest;
|
|
351
|
+
if (typeof fn !== "function") {
|
|
352
|
+
throw new Error("opfs_store_selftest missing from the bundled midden module");
|
|
353
|
+
}
|
|
354
|
+
return fn();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* persistence selftest - blobs + tags must survive a store shutdown and
|
|
358
|
+
* reopen over the same OPFS directory (the whole point of the store:
|
|
359
|
+
* cross-reload resume without re-import). worker context required.
|
|
360
|
+
*/
|
|
361
|
+
export async function opfsStoreSelftestPersistence() {
|
|
362
|
+
const midden = await loadMiddenBlake3();
|
|
363
|
+
const fn = midden?.opfs_store_selftest_persistence;
|
|
364
|
+
if (typeof fn !== "function") {
|
|
365
|
+
throw new Error("opfs_store_selftest_persistence missing from the bundled midden module");
|
|
366
|
+
}
|
|
367
|
+
return fn();
|
|
368
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-worker-logic.test.d.ts","sourceRoot":"","sources":["../../src/worker/blob-worker-logic.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { base64Decode, base64Encode, generateThumbnailDataUrl, hashBlake3, hashSha256, opfsStoreSelftest, opfsStoreSelftestPersistence, processBlobBytes, readBlobFromOpfs, resizeImageToWebpDataUrl, uploadBegin, writeBlobToOpfs, } from "./blob-worker-logic.js";
|
|
4
|
+
import { resetMiddenBlake3Cache } from "./midden-blake3.js";
|
|
5
|
+
// ---- fake OPFS (async writable-stream fallback path only - sync access
|
|
6
|
+
// handles are worker/chromium-specific and not worth faking here; the
|
|
7
|
+
// fallback path exercises the same directory/file-handle logic). ----
|
|
8
|
+
class FakeWritable {
|
|
9
|
+
file;
|
|
10
|
+
constructor(file) {
|
|
11
|
+
this.file = file;
|
|
12
|
+
}
|
|
13
|
+
async write(data) {
|
|
14
|
+
this.file.bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer);
|
|
15
|
+
}
|
|
16
|
+
async close() { }
|
|
17
|
+
}
|
|
18
|
+
class FakeFileHandle {
|
|
19
|
+
bytes = new Uint8Array(0);
|
|
20
|
+
async getFile() {
|
|
21
|
+
const bytes = this.bytes;
|
|
22
|
+
return { arrayBuffer: async () => bytes.buffer };
|
|
23
|
+
}
|
|
24
|
+
async createWritable() {
|
|
25
|
+
return new FakeWritable(this);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class FakeDirHandle {
|
|
29
|
+
files = new Map();
|
|
30
|
+
async getFileHandle(name, opts) {
|
|
31
|
+
let handle = this.files.get(name);
|
|
32
|
+
if (!handle) {
|
|
33
|
+
if (!opts?.create)
|
|
34
|
+
throw new Error(`not found: ${name}`);
|
|
35
|
+
handle = new FakeFileHandle();
|
|
36
|
+
this.files.set(name, handle);
|
|
37
|
+
}
|
|
38
|
+
return handle;
|
|
39
|
+
}
|
|
40
|
+
async getDirectoryHandle() {
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function installFakeOpfs() {
|
|
45
|
+
const root = new FakeDirHandle();
|
|
46
|
+
vi.stubGlobal("navigator", {
|
|
47
|
+
storage: {
|
|
48
|
+
getDirectory: async () => root,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
// ---- fake canvas pipeline (same approach as utils/image-utils.test.ts) ----
|
|
54
|
+
class FakeImageBitmap {
|
|
55
|
+
width;
|
|
56
|
+
height;
|
|
57
|
+
constructor(width, height) {
|
|
58
|
+
this.width = width;
|
|
59
|
+
this.height = height;
|
|
60
|
+
}
|
|
61
|
+
close = vi.fn();
|
|
62
|
+
}
|
|
63
|
+
class FakeOffscreenCanvasContext {
|
|
64
|
+
drawImage = vi.fn();
|
|
65
|
+
}
|
|
66
|
+
class FakeOffscreenCanvas {
|
|
67
|
+
width;
|
|
68
|
+
height;
|
|
69
|
+
context = new FakeOffscreenCanvasContext();
|
|
70
|
+
constructor(width, height) {
|
|
71
|
+
this.width = width;
|
|
72
|
+
this.height = height;
|
|
73
|
+
}
|
|
74
|
+
getContext() {
|
|
75
|
+
return this.context;
|
|
76
|
+
}
|
|
77
|
+
async convertToBlob(options) {
|
|
78
|
+
return new Blob(["fake-encoded-bytes"], { type: options?.type ?? "image/webp" });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function installCanvasFakes(size = { width: 100, height: 100 }) {
|
|
82
|
+
vi.stubGlobal("createImageBitmap", vi.fn(async () => new FakeImageBitmap(size.width, size.height)));
|
|
83
|
+
vi.stubGlobal("OffscreenCanvas", FakeOffscreenCanvas);
|
|
84
|
+
}
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
vi.unstubAllGlobals();
|
|
87
|
+
resetMiddenBlake3Cache();
|
|
88
|
+
});
|
|
89
|
+
describe("hashSha256", () => {
|
|
90
|
+
it("hashes a known buffer to its well-known sha-256 digest", async () => {
|
|
91
|
+
const bytes = new TextEncoder().encode("abc");
|
|
92
|
+
const hash = await hashSha256(bytes.buffer);
|
|
93
|
+
expect(hash).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe("hashBlake3", () => {
|
|
97
|
+
it("degrades to an empty string when no midden module is bundled", async () => {
|
|
98
|
+
const hash = await hashBlake3(new Uint8Array([1, 2, 3]));
|
|
99
|
+
expect(hash).toBe("");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("base64Encode / base64Decode", () => {
|
|
103
|
+
it("round-trips arbitrary bytes", async () => {
|
|
104
|
+
const original = new Uint8Array([0, 1, 2, 253, 254, 255, 42]);
|
|
105
|
+
const encoded = await base64Encode(original.buffer);
|
|
106
|
+
const decoded = await base64Decode(encoded);
|
|
107
|
+
expect(Array.from(decoded)).toEqual(Array.from(original));
|
|
108
|
+
});
|
|
109
|
+
it("handles buffers larger than the chunk size", async () => {
|
|
110
|
+
const original = new Uint8Array(0x8000 * 3 + 17).map((_, i) => i % 256);
|
|
111
|
+
const encoded = await base64Encode(original.buffer);
|
|
112
|
+
const decoded = await base64Decode(encoded);
|
|
113
|
+
expect(Array.from(decoded)).toEqual(Array.from(original));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe("writeBlobToOpfs / readBlobFromOpfs", () => {
|
|
117
|
+
it("round-trips bytes through the fake opfs directory", async () => {
|
|
118
|
+
installFakeOpfs();
|
|
119
|
+
const data = new Uint8Array([9, 8, 7, 6]).buffer;
|
|
120
|
+
await writeBlobToOpfs("blake3-hex", data);
|
|
121
|
+
const readBack = await readBlobFromOpfs("blake3-hex");
|
|
122
|
+
expect(readBack).not.toBeNull();
|
|
123
|
+
expect(Array.from(new Uint8Array(readBack))).toEqual([9, 8, 7, 6]);
|
|
124
|
+
});
|
|
125
|
+
it("readBlobFromOpfs returns null for a file that was never written", async () => {
|
|
126
|
+
installFakeOpfs();
|
|
127
|
+
const result = await readBlobFromOpfs("never-written");
|
|
128
|
+
expect(result).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
it("readBlobFromOpfs returns null when OPFS is unavailable", async () => {
|
|
131
|
+
vi.stubGlobal("navigator", {});
|
|
132
|
+
const result = await readBlobFromOpfs("anything");
|
|
133
|
+
expect(result).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it("writeBlobToOpfs silently no-ops when OPFS is unavailable", async () => {
|
|
136
|
+
vi.stubGlobal("navigator", {});
|
|
137
|
+
await expect(writeBlobToOpfs("anything", new ArrayBuffer(4))).resolves.toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe("processBlobBytes", () => {
|
|
141
|
+
it("hashes and writes to opfs, returning blake3 as the canonical blob_id", async () => {
|
|
142
|
+
installFakeOpfs();
|
|
143
|
+
const bytes = new TextEncoder().encode("hello world");
|
|
144
|
+
const result = await processBlobBytes(bytes.buffer, "hello.txt", "text/plain");
|
|
145
|
+
expect(result.filename).toBe("hello.txt");
|
|
146
|
+
expect(result.mime).toBe("text/plain");
|
|
147
|
+
expect(result.size).toBe(bytes.byteLength);
|
|
148
|
+
// no midden module bundled in this test environment - blake3 degrades
|
|
149
|
+
// to an empty string, and blob_id mirrors it (documented behavior).
|
|
150
|
+
expect(result.blake3).toBe("");
|
|
151
|
+
expect(result.blob_id).toBe(result.blake3);
|
|
152
|
+
expect(result.sha256).toBe("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe("uploadBegin", () => {
|
|
156
|
+
it("throws a clear error when no midden Blake3Hasher is bundled", async () => {
|
|
157
|
+
installFakeOpfs();
|
|
158
|
+
await expect(uploadBegin()).rejects.toThrow(/Blake3Hasher/);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe("opfsStoreSelftest / opfsStoreSelftestPersistence", () => {
|
|
162
|
+
it("opfsStoreSelftest throws when no midden module is bundled", async () => {
|
|
163
|
+
await expect(opfsStoreSelftest()).rejects.toThrow(/opfs_store_selftest/);
|
|
164
|
+
});
|
|
165
|
+
it("opfsStoreSelftestPersistence throws when no midden module is bundled", async () => {
|
|
166
|
+
await expect(opfsStoreSelftestPersistence()).rejects.toThrow(/opfs_store_selftest_persistence/);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("resizeImageToWebpDataUrl", () => {
|
|
170
|
+
it("returns a data url with the requested mime type", async () => {
|
|
171
|
+
installCanvasFakes();
|
|
172
|
+
const result = await resizeImageToWebpDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
173
|
+
expect(result).toMatch(/^data:image\/webp;base64,/);
|
|
174
|
+
});
|
|
175
|
+
it("returns null when OffscreenCanvas/createImageBitmap are unavailable", async () => {
|
|
176
|
+
const result = await resizeImageToWebpDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
177
|
+
expect(result).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe("generateThumbnailDataUrl", () => {
|
|
181
|
+
it("skips non-image blobs", async () => {
|
|
182
|
+
installCanvasFakes();
|
|
183
|
+
const result = await generateThumbnailDataUrl(new Blob(["x"], { type: "text/plain" }));
|
|
184
|
+
expect(result).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
it("resizes image blobs to a webp data url", async () => {
|
|
187
|
+
installCanvasFakes();
|
|
188
|
+
const result = await generateThumbnailDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
189
|
+
expect(result).toMatch(/^data:image\/webp;base64,/);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { base64Decode, base64Encode, generateThumbnailDataUrl, hashBlake3, hashSha256, opfsStoreSelftest, opfsStoreSelftestPersistence, processBlobBytes, readBlobFromOpfs, resizeImageToWebpDataUrl, uploadAbort, uploadBegin, uploadFinish, uploadPush, writeBlobToOpfs } from "./blob-worker-logic.js";
|
|
2
|
+
declare const api: {
|
|
3
|
+
hashBlake3: typeof hashBlake3;
|
|
4
|
+
hashSha256: typeof hashSha256;
|
|
5
|
+
base64Encode: typeof base64Encode;
|
|
6
|
+
base64Decode: typeof base64Decode;
|
|
7
|
+
writeBlobToOpfs: typeof writeBlobToOpfs;
|
|
8
|
+
readBlobFromOpfs: typeof readBlobFromOpfs;
|
|
9
|
+
processBlobBytes: typeof processBlobBytes;
|
|
10
|
+
resizeImageToWebpDataUrl: typeof resizeImageToWebpDataUrl;
|
|
11
|
+
generateThumbnailDataUrl: typeof generateThumbnailDataUrl;
|
|
12
|
+
uploadBegin: typeof uploadBegin;
|
|
13
|
+
uploadPush: typeof uploadPush;
|
|
14
|
+
uploadFinish: typeof uploadFinish;
|
|
15
|
+
uploadAbort: typeof uploadAbort;
|
|
16
|
+
opfsStoreSelftest: typeof opfsStoreSelftest;
|
|
17
|
+
opfsStoreSelftestPersistence: typeof opfsStoreSelftestPersistence;
|
|
18
|
+
};
|
|
19
|
+
export type BlobWorkerApi = typeof api;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=blob-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blob-worker.d.ts","sourceRoot":"","sources":["../../src/worker/blob-worker.ts"],"names":[],"mappings":"AAeA,OAAO,EAEL,YAAY,EACZ,YAAY,EACZ,wBAAwB,EACxB,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,4BAA4B,EAC5B,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAEhC,QAAA,MAAM,GAAG;;;;;;;;;;;;;;;;CAgBR,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// blob worker entry - moves CPU-bound blob work (blake3 hashing, sha256
|
|
2
|
+
// hashing, base64 encode/decode, OPFS writes) off the main thread.
|
|
3
|
+
//
|
|
4
|
+
// architecture: comlink-exposed module. spun up lazily by
|
|
5
|
+
// `blob-worker-client.ts` on first use. shares no state with the main
|
|
6
|
+
// thread other than what's passed across postMessage (with transfer
|
|
7
|
+
// ownership for ArrayBuffers).
|
|
8
|
+
//
|
|
9
|
+
// kept deliberately thin: all the actual logic lives in
|
|
10
|
+
// `blob-worker-logic.ts`, which has no comlink/postMessage side effects
|
|
11
|
+
// and is what the test suite imports directly. this file is the
|
|
12
|
+
// worker-only glue - importing it anywhere other than as a worker script
|
|
13
|
+
// runs `Comlink.expose()` and posts a ready message for no reason.
|
|
14
|
+
import * as Comlink from "comlink";
|
|
15
|
+
import { BLOB_WORKER_READY_MESSAGE, base64Decode, base64Encode, generateThumbnailDataUrl, hashBlake3, hashSha256, opfsStoreSelftest, opfsStoreSelftestPersistence, processBlobBytes, readBlobFromOpfs, resizeImageToWebpDataUrl, uploadAbort, uploadBegin, uploadFinish, uploadPush, writeBlobToOpfs, } from "./blob-worker-logic.js";
|
|
16
|
+
const api = {
|
|
17
|
+
hashBlake3,
|
|
18
|
+
hashSha256,
|
|
19
|
+
base64Encode,
|
|
20
|
+
base64Decode,
|
|
21
|
+
writeBlobToOpfs,
|
|
22
|
+
readBlobFromOpfs,
|
|
23
|
+
processBlobBytes,
|
|
24
|
+
resizeImageToWebpDataUrl,
|
|
25
|
+
generateThumbnailDataUrl,
|
|
26
|
+
uploadBegin,
|
|
27
|
+
uploadPush,
|
|
28
|
+
uploadFinish,
|
|
29
|
+
uploadAbort,
|
|
30
|
+
opfsStoreSelftest,
|
|
31
|
+
opfsStoreSelftestPersistence,
|
|
32
|
+
};
|
|
33
|
+
Comlink.expose(api);
|
|
34
|
+
// signal readiness *after* Comlink has registered its message listener.
|
|
35
|
+
//
|
|
36
|
+
// without this, a Comlink RPC call sent right after `new Worker()` can race
|
|
37
|
+
// the worker's own startup (fetching + instantiating a wasm-backed midden
|
|
38
|
+
// module, when one is bundled, can take a couple of seconds). if the
|
|
39
|
+
// browser dispatches the postMessage'd call before Comlink.expose() above
|
|
40
|
+
// has added its "message" listener, the event fires with no listener
|
|
41
|
+
// attached and is silently dropped forever - the caller's promise never
|
|
42
|
+
// resolves. see `getBlobWorker()` in blob-worker-client.ts, which waits
|
|
43
|
+
// for this signal before handing out the worker proxy.
|
|
44
|
+
postMessage(BLOB_WORKER_READY_MESSAGE);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { ProcessedBlob, ResizeImageOptions } from "./blob-worker-logic.js";
|
|
2
|
+
export { BLOB_WORKER_READY_MESSAGE, OPFS_DIR as BLOB_OPFS_DIR } from "./blob-worker-logic.js";
|
|
3
|
+
export type { BlobWorkerApi } from "./blob-worker.js";
|
|
4
|
+
export { getBlobWorker, shutdownBlobWorker, hashBlake3, hashSha256, base64Encode, base64Decode, processBlobBytes, writeBlobToOpfs, streamFileToOpfs, resizeImageToWebpDataUrl, generateThumbnailDataUrl, } from "./blob-worker-client.js";
|
|
5
|
+
export type { MiddenWorkerApi, MiddenWorkerIdentity, StreamInfo } from "./midden-worker-contract.js";
|
|
6
|
+
export { MIDDEN_WORKER_READY_MESSAGE, MIDDEN_WORKER_READY_TIMEOUT_MS, } from "./midden-worker-contract.js";
|
|
7
|
+
export type { CreateMiddenWorker } from "./midden-worker-client.js";
|
|
8
|
+
export { WorkerBiStream, WorkerImportSession, WorkerMiddenNode } from "./midden-worker-client.js";
|
|
9
|
+
export { MiddenNode } from "./midden-stub.js";
|
|
10
|
+
export type { Blake3HasherLike, MiddenBlake3Module } from "./midden-blake3.js";
|
|
11
|
+
export { loadMiddenBlake3, resetMiddenBlake3Cache } from "./midden-blake3.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE9F,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACrG,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,6BAA6B,CAAC;AAErC,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElG,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { BLOB_WORKER_READY_MESSAGE, OPFS_DIR as BLOB_OPFS_DIR } from "./blob-worker-logic.js";
|
|
2
|
+
export { getBlobWorker, shutdownBlobWorker, hashBlake3, hashSha256, base64Encode, base64Decode, processBlobBytes, writeBlobToOpfs, streamFileToOpfs, resizeImageToWebpDataUrl, generateThumbnailDataUrl, } from "./blob-worker-client.js";
|
|
3
|
+
export { MIDDEN_WORKER_READY_MESSAGE, MIDDEN_WORKER_READY_TIMEOUT_MS, } from "./midden-worker-contract.js";
|
|
4
|
+
export { WorkerBiStream, WorkerImportSession, WorkerMiddenNode } from "./midden-worker-client.js";
|
|
5
|
+
export { MiddenNode } from "./midden-stub.js";
|
|
6
|
+
export { loadMiddenBlake3, resetMiddenBlake3Cache } from "./midden-blake3.js";
|