@seedprotocol/sdk 0.7.3-next.613.g79a8dfd → 0.7.3-next.654.g52217f6
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/{BasePathResolver-BdxCSIzn.js → BasePathResolver-CfSfMW5_.js} +7 -10
- package/dist/BasePathResolver-CfSfMW5_.js.map +1 -0
- package/dist/{ClientManager-DDA9956Y.js → ClientManager-C-QXqUK9.js} +438 -153
- package/dist/ClientManager-C-QXqUK9.js.map +1 -0
- package/dist/{Item-D4FigtYS.js → Item-C7sUUu2B.js} +291 -105
- package/dist/Item-C7sUUu2B.js.map +1 -0
- package/dist/{ModelProperty-BDt6AU2h.js → ModelProperty-DJxeOOrc.js} +28 -27
- package/dist/ModelProperty-DJxeOOrc.js.map +1 -0
- package/dist/{Schema-B8xqLWkL.js → Schema-fXtvd-yL.js} +30 -30
- package/dist/{Schema-B8xqLWkL.js.map → Schema-fXtvd-yL.js.map} +1 -1
- package/dist/{SchemaValidationService-BPxkfDLU.js → SchemaValidationService-CvRS2HSO.js} +10 -9
- package/dist/{SchemaValidationService-BPxkfDLU.js.map → SchemaValidationService-CvRS2HSO.js.map} +1 -1
- package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js → backfillMetadataPropertyIds-SXW-8C64.js} +4 -4
- package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js.map → backfillMetadataPropertyIds-SXW-8C64.js.map} +1 -1
- package/dist/cjs/{ModelProperty-B-cZm1yU.js → ModelProperty-DXZ39Wyv.js} +24 -23
- package/dist/cjs/{ModelProperty-B-cZm1yU.js.map → ModelProperty-DXZ39Wyv.js.map} +1 -1
- package/dist/cjs/{Schema-STP972Xw.js → Schema-DdP109QD.js} +26 -25
- package/dist/cjs/{Schema-STP972Xw.js.map → Schema-DdP109QD.js.map} +1 -1
- package/dist/cjs/{SchemaValidationService-CKNW1bKg.js → SchemaValidationService-CSXxx7sQ.js} +5 -4
- package/dist/cjs/{SchemaValidationService-CKNW1bKg.js.map → SchemaValidationService-CSXxx7sQ.js.map} +1 -1
- package/dist/cjs/{backfillMetadataPropertyIds-CP3y1skt.js → backfillMetadataPropertyIds-CPYoaMl4.js} +6 -5
- package/dist/cjs/{backfillMetadataPropertyIds-CP3y1skt.js.map → backfillMetadataPropertyIds-CPYoaMl4.js.map} +1 -1
- package/dist/cjs/{eas-CAnUC4wd.js → eas-B2YDQssV.js} +6 -5
- package/dist/cjs/{eas-CAnUC4wd.js.map → eas-B2YDQssV.js.map} +1 -1
- package/dist/cjs/{getItem-CWgb3e9G.js → getItem-DgnaQzbE.js} +5 -4
- package/dist/cjs/{getItem-CWgb3e9G.js.map → getItem-DgnaQzbE.js.map} +1 -1
- package/dist/cjs/{getPublishPayload-BsZWiJ03.js → getPublishPayload-CqlqRdot.js} +12 -11
- package/dist/cjs/{getPublishPayload-BsZWiJ03.js.map → getPublishPayload-CqlqRdot.js.map} +1 -1
- package/dist/cjs/getPublishUploads-DsXY9S4U.js +872 -0
- package/dist/cjs/getPublishUploads-DsXY9S4U.js.map +1 -0
- package/dist/cjs/{index-nA5PxBSs.js → index-BHaWOwNF.js} +6 -5
- package/dist/cjs/index-BHaWOwNF.js.map +1 -0
- package/dist/cjs/{index-usKaANAq.js → index-C4XlgCJ4.js} +1156 -526
- package/dist/cjs/index-C4XlgCJ4.js.map +1 -0
- package/dist/cjs/{property-xMwPhQ5N.js → property-fa0iju0J.js} +8 -7
- package/dist/cjs/{property-xMwPhQ5N.js.map → property-fa0iju0J.js.map} +1 -1
- package/dist/cjs/{publishDraftGraph-BRWlR1KO.js → publishDraftGraph-3ASwAlD7.js} +4 -4
- package/dist/cjs/{publishDraftGraph-BRWlR1KO.js.map → publishDraftGraph-3ASwAlD7.js.map} +1 -1
- package/dist/cjs/{resetAmbiguousLegacyItemData-8nfkj9rz.js → resetAmbiguousLegacyItemData-Bu_MBBYt.js} +5 -4
- package/dist/cjs/{resetAmbiguousLegacyItemData-8nfkj9rz.js.map → resetAmbiguousLegacyItemData-Bu_MBBYt.js.map} +1 -1
- package/dist/cjs/{resolveModelForSyncedSeed-kYRuDWYw.js → resolveModelForSyncedSeed-BANIiuFU.js} +2 -2
- package/dist/cjs/{resolveModelForSyncedSeed-kYRuDWYw.js.map → resolveModelForSyncedSeed-BANIiuFU.js.map} +1 -1
- package/dist/cjs/{syncDbWithEas-DqRhV8o2.js → syncDbWithEas-CrmxxmUW.js} +10 -9
- package/dist/cjs/{syncDbWithEas-DqRhV8o2.js.map → syncDbWithEas-CrmxxmUW.js.map} +1 -1
- package/dist/{configurePlatform-YcOiDQBZ.js → configurePlatform-BRw2j0ZN.js} +7 -4
- package/dist/{configurePlatform-YcOiDQBZ.js.map → configurePlatform-BRw2j0ZN.js.map} +1 -1
- package/dist/{db-CQt-gcAl.js → db-D9K_f5cF.js} +14 -14
- package/dist/{db-CQt-gcAl.js.map → db-D9K_f5cF.js.map} +1 -1
- package/dist/{eas-CpOD74Ke.js → eas-DHxfswCt.js} +8 -5
- package/dist/eas-DHxfswCt.js.map +1 -0
- package/dist/{getItem-B_OoFkd7.js → getItem-d6N_aM6S.js} +8 -5
- package/dist/{getItem-B_OoFkd7.js.map → getItem-d6N_aM6S.js.map} +1 -1
- package/dist/{getPublishPayload-Xl3sRkR-.js → getPublishPayload-BHrxVtzY.js} +16 -16
- package/dist/{getPublishPayload-Xl3sRkR-.js.map → getPublishPayload-BHrxVtzY.js.map} +1 -1
- package/dist/getPublishUploads-BEqRiKIS.js +657 -0
- package/dist/getPublishUploads-BEqRiKIS.js.map +1 -0
- package/dist/{index-BGt4_oPO.js → index-CiMEoGu3.js} +12 -11
- package/dist/index-CiMEoGu3.js.map +1 -0
- package/dist/{index-CSOQFAOn.js → index-DglvthOk.js} +2 -2
- package/dist/index-DglvthOk.js.map +1 -0
- package/dist/{index-Xl1_4Lmb.js → index-s15q14rm.js} +29 -29
- package/dist/{index-Xl1_4Lmb.js.map → index-s15q14rm.js.map} +1 -1
- package/dist/{index.browser-CjTe0vt1.js → index.browser-Cm06tP4X.js} +553 -165
- package/dist/index.browser-Cm06tP4X.js.map +1 -0
- package/dist/{index.node-Cfa7npS6.js → index.node-C30dmFgn.js} +5 -5
- package/dist/{index.node-Cfa7npS6.js.map → index.node-C30dmFgn.js.map} +1 -1
- package/dist/main.cjs +4 -3
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +32 -44
- package/dist/main.js.map +1 -1
- package/dist/mimeTypes-DHAHC4eZ.js +204 -0
- package/dist/mimeTypes-DHAHC4eZ.js.map +1 -0
- package/dist/node.js +13 -12
- package/dist/node.js.map +1 -1
- package/dist/platform.browser.js +9 -7
- package/dist/platform.browser.js.map +1 -1
- package/dist/platform.node.js +3 -3
- package/dist/{property-BrXkY1Rg.js → property-DhmmYAvQ.js} +12 -9
- package/dist/property-DhmmYAvQ.js.map +1 -0
- package/dist/{publishDraftGraph-X0Wr9txE.js → publishDraftGraph-yYP74WRt.js} +8 -8
- package/dist/{publishDraftGraph-X0Wr9txE.js.map → publishDraftGraph-yYP74WRt.js.map} +1 -1
- package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js → resetAmbiguousLegacyItemData-Bk0QyDW8.js} +2 -2
- package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js.map → resetAmbiguousLegacyItemData-Bk0QyDW8.js.map} +1 -1
- package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js → resolveModelForSyncedSeed-8cAzvUFd.js} +2 -2
- package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js.map → resolveModelForSyncedSeed-8cAzvUFd.js.map} +1 -1
- package/dist/src/Item/Item.d.ts +1 -1
- package/dist/src/Item/Item.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +1 -1
- package/dist/src/browser/helpers/FileManager.d.ts +4 -1
- package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
- package/dist/src/browser/helpers/tolerantWebAccess.d.ts +79 -4
- package/dist/src/browser/helpers/tolerantWebAccess.d.ts.map +1 -1
- package/dist/src/browser/workers/FileDownloader.d.ts +19 -4
- package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
- package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
- package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
- package/dist/src/db/migrateLegacyItemStorageRows.d.ts +57 -0
- package/dist/src/db/migrateLegacyItemStorageRows.d.ts.map +1 -0
- package/dist/src/db/read/getCompositeChildPaths.d.ts +13 -0
- package/dist/src/db/read/getCompositeChildPaths.d.ts.map +1 -0
- package/dist/src/db/read/getItems.d.ts.map +1 -1
- package/dist/src/db/read/getPublishUploads.d.ts +32 -3
- package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
- package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
- package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts +44 -0
- package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts.map +1 -0
- package/dist/src/db/write/createNewItem.d.ts.map +1 -1
- package/dist/src/events/files/download.d.ts.map +1 -1
- package/dist/src/helpers/FileManager/BaseFileManager.d.ts +3 -3
- package/dist/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
- package/dist/src/helpers/FileManager/IFileManager.d.ts +25 -1
- package/dist/src/helpers/FileManager/IFileManager.d.ts.map +1 -1
- package/dist/src/helpers/arweaveContentTags.d.ts +33 -0
- package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -0
- package/dist/src/helpers/compositeUpload.d.ts +28 -0
- package/dist/src/helpers/compositeUpload.d.ts.map +1 -0
- package/dist/src/helpers/itemStorageFile.d.ts +57 -0
- package/dist/src/helpers/itemStorageFile.d.ts.map +1 -0
- package/dist/src/helpers/mapWithConcurrency.d.ts +7 -0
- package/dist/src/helpers/mapWithConcurrency.d.ts.map +1 -0
- package/dist/src/helpers/mimeTypes.d.ts +43 -0
- package/dist/src/helpers/mimeTypes.d.ts.map +1 -0
- package/dist/src/helpers/ownership.d.ts +19 -3
- package/dist/src/helpers/ownership.d.ts.map +1 -1
- package/dist/src/helpers/storageDir.d.ts +16 -0
- package/dist/src/helpers/storageDir.d.ts.map +1 -0
- package/dist/src/helpers/tabCoordinator.d.ts +5 -1
- package/dist/src/helpers/tabCoordinator.d.ts.map +1 -1
- package/dist/src/helpers/tabEvents.d.ts +23 -2
- package/dist/src/helpers/tabEvents.d.ts.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/types/fileManager.d.ts +3 -14
- package/dist/src/types/fileManager.d.ts.map +1 -1
- package/dist/src/types/publish.d.ts +14 -0
- package/dist/src/types/publish.d.ts.map +1 -1
- package/dist/{syncDbWithEas-BU-jZ5bJ.js → syncDbWithEas--HUA9WNb.js} +15 -14
- package/dist/syncDbWithEas--HUA9WNb.js.map +1 -0
- package/dist/{tabEvents-BZJMY5uK.js → tabEvents-DguZpCCv.js} +294 -58
- package/dist/tabEvents-DguZpCCv.js.map +1 -0
- package/dist/tolerantWebAccess-BZYWt09Y.js +551 -0
- package/dist/tolerantWebAccess-BZYWt09Y.js.map +1 -0
- package/dist/{updateSeedRevokedAt-iLpzae1i.js → updateSeedRevokedAt-D_EG-q3b.js} +3 -3
- package/dist/{updateSeedRevokedAt-iLpzae1i.js.map → updateSeedRevokedAt-D_EG-q3b.js.map} +1 -1
- package/package.json +5 -5
- package/dist/BasePathResolver-BdxCSIzn.js.map +0 -1
- package/dist/ClientManager-DDA9956Y.js.map +0 -1
- package/dist/Item-D4FigtYS.js.map +0 -1
- package/dist/ModelProperty-BDt6AU2h.js.map +0 -1
- package/dist/cjs/getPublishUploads-BQr8aMVP.js +0 -495
- package/dist/cjs/getPublishUploads-BQr8aMVP.js.map +0 -1
- package/dist/cjs/index-nA5PxBSs.js.map +0 -1
- package/dist/cjs/index-usKaANAq.js.map +0 -1
- package/dist/eas-CpOD74Ke.js.map +0 -1
- package/dist/getPublishUploads-DHP01KgU.js +0 -484
- package/dist/getPublishUploads-DHP01KgU.js.map +0 -1
- package/dist/index-BGt4_oPO.js.map +0 -1
- package/dist/index-CSOQFAOn.js.map +0 -1
- package/dist/index.browser-CjTe0vt1.js.map +0 -1
- package/dist/property-BrXkY1Rg.js.map +0 -1
- package/dist/syncDbWithEas-BU-jZ5bJ.js.map +0 -1
- package/dist/tabEvents-BZJMY5uK.js.map +0 -1
- package/dist/tolerantWebAccess-BZ2RA_mb.js +0 -160
- package/dist/tolerantWebAccess-BZ2RA_mb.js.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as BasePathResolver, B as BaseDb, a as appState, t as BaseFileManager } from './BasePathResolver-CfSfMW5_.js';
|
|
2
2
|
import { eq, sql } from 'drizzle-orm';
|
|
3
|
-
import {
|
|
3
|
+
import { D as withDbTabLock, p as contentSha256TagMatches, E as notifyFilesWrittenOutsideCache, G as withSeedDbLock } from './tabEvents-DguZpCCv.js';
|
|
4
|
+
import { M as MIME_TO_EXTENSION } from './mimeTypes-DHAHC4eZ.js';
|
|
4
5
|
import debug from 'debug';
|
|
5
6
|
import { F as FileSystemLockedError } from './errors-CrBxGWah.js';
|
|
6
7
|
import { drizzle } from 'drizzle-orm/sqlite-proxy';
|
|
7
8
|
import { sha256 } from '@noble/hashes/sha256';
|
|
8
9
|
import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
|
|
9
|
-
import { s as schema } from './index-
|
|
10
|
+
import { s as schema } from './index-DglvthOk.js';
|
|
10
11
|
import { SQLocalDrizzle } from 'sqlocal/drizzle';
|
|
11
12
|
import { Observable, distinctUntilChanged } from 'rxjs';
|
|
12
13
|
import { BaseArweaveClient, getPreferredArweaveReadBaseUrls, fetchArweaveRawFromBaseUrl, GET_TRANSACTION_TAGS } from '@seedprotocol/arweave';
|
|
@@ -111,7 +112,8 @@ async function addExcludedTransactions(transactionIds) {
|
|
|
111
112
|
});
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
var filesDownload = `(
|
|
115
|
+
var filesDownload = `const SEED_MIME_TO_EXTENSION = ${JSON.stringify(MIME_TO_EXTENSION)};
|
|
116
|
+
(
|
|
115
117
|
${function () {
|
|
116
118
|
const identifyString = (str) => {
|
|
117
119
|
try {
|
|
@@ -229,6 +231,15 @@ var filesDownload = `(
|
|
|
229
231
|
const root = filesRoot.replace(/\/$/, '');
|
|
230
232
|
return [root, ...parts].filter(Boolean).join('/').replace(/\/+/g, '/');
|
|
231
233
|
};
|
|
234
|
+
/** Per-gateway limit for one `/raw/{id}` request, body included. */
|
|
235
|
+
const RAW_FETCH_TIMEOUT_MS = 60000;
|
|
236
|
+
const errorInfo = (error) => {
|
|
237
|
+
const e = (error ?? {});
|
|
238
|
+
return {
|
|
239
|
+
name: typeof e.name === 'string' && e.name ? e.name : 'Error',
|
|
240
|
+
message: typeof e.message === 'string' ? e.message : String(error),
|
|
241
|
+
};
|
|
242
|
+
};
|
|
232
243
|
const toBaseUrl = (hostOrUrl) => {
|
|
233
244
|
const h = (hostOrUrl || '').trim().replace(/\/$/, '');
|
|
234
245
|
if (!h)
|
|
@@ -240,30 +251,213 @@ var filesDownload = `(
|
|
|
240
251
|
: 'https';
|
|
241
252
|
return `${protocol}://${h}`;
|
|
242
253
|
};
|
|
254
|
+
const toHex = (buffer) => {
|
|
255
|
+
const view = new Uint8Array(buffer);
|
|
256
|
+
let hex = '';
|
|
257
|
+
for (let i = 0; i < view.length; i++)
|
|
258
|
+
hex += view[i].toString(16).padStart(2, '0');
|
|
259
|
+
return hex;
|
|
260
|
+
};
|
|
261
|
+
/** Verification requests waiting for the page's answer, by request id. */
|
|
262
|
+
const pendingVerifications = new Map();
|
|
263
|
+
let lastVerificationId = 0;
|
|
264
|
+
/**
|
|
265
|
+
* Ask the page whether `bytes` match a Content-SHA-256 tag. The page runs `contentSha256TagMatches`,
|
|
266
|
+
* which also accepts the SHA3-256 digests older SDKs wrote under that tag (no SHA3 in WebCrypto, and
|
|
267
|
+
* this worker cannot import a hash library). The bytes are copied, not transferred.
|
|
268
|
+
*/
|
|
269
|
+
const verifyOnPage = (transactionId, bytes, expected) => new Promise((resolve) => {
|
|
270
|
+
const requestId = ++lastVerificationId;
|
|
271
|
+
pendingVerifications.set(requestId, resolve);
|
|
272
|
+
globalThis.postMessage({ message: 'verifyContent', requestId, transactionId, expected, bytes });
|
|
273
|
+
});
|
|
274
|
+
/**
|
|
275
|
+
* True when the downloaded bytes match the upload's Content-SHA-256 tag. SHA-256 is checked here
|
|
276
|
+
* with WebCrypto when available (secure contexts); anything else goes to the page.
|
|
277
|
+
*/
|
|
278
|
+
const contentMatchesTag = async (transactionId, bytes, expected) => {
|
|
279
|
+
const want = expected.trim().toLowerCase();
|
|
280
|
+
try {
|
|
281
|
+
const subtle = globalThis.crypto && globalThis.crypto.subtle;
|
|
282
|
+
if (subtle && toHex(await subtle.digest('SHA-256', bytes)) === want)
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
/* fall back to the page */
|
|
287
|
+
}
|
|
288
|
+
return verifyOnPage(transactionId, bytes, want);
|
|
289
|
+
};
|
|
243
290
|
/**
|
|
244
291
|
* GET /raw/{id} from each gateway in order. `networkFailedEverywhere` is true only when no gateway
|
|
245
292
|
* answered at all; a non-2xx (e.g. not yet propagated) is not a reason to exclude the transaction.
|
|
293
|
+
* `contentType` is the response's Content-Type (gateways serve the upload's Content-Type tag).
|
|
294
|
+
* With `expectedHash` (the upload's Content-SHA-256 tag), a body that does not match it is skipped
|
|
295
|
+
* and the next gateway tried; `hashMismatch` says that happened and no gateway served matching bytes.
|
|
246
296
|
*/
|
|
247
|
-
const fetchRaw = async (baseUrls, transactionId) => {
|
|
297
|
+
const fetchRaw = async (baseUrls, transactionId, expectedHash) => {
|
|
248
298
|
let answered = false;
|
|
299
|
+
let hashMismatch = false;
|
|
249
300
|
for (const base of baseUrls) {
|
|
301
|
+
let arrayBuffer;
|
|
302
|
+
let contentType = '';
|
|
303
|
+
// A hung gateway must not stall the batch: abort the request (body included) after a while.
|
|
304
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;
|
|
305
|
+
const timer = controller ? setTimeout(() => controller.abort(), RAW_FETCH_TIMEOUT_MS) : undefined;
|
|
250
306
|
try {
|
|
251
|
-
const response = await fetch(`${base}/raw/${transactionId}
|
|
307
|
+
const response = await fetch(`${base}/raw/${transactionId}`, controller ? { signal: controller.signal } : undefined);
|
|
252
308
|
answered = true;
|
|
253
309
|
if (!response.ok)
|
|
254
310
|
continue;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
311
|
+
arrayBuffer = await response.arrayBuffer();
|
|
312
|
+
contentType = (response.headers?.get('content-type') || '')
|
|
313
|
+
.split(';')[0]
|
|
314
|
+
.trim()
|
|
315
|
+
.toLowerCase();
|
|
259
316
|
}
|
|
260
317
|
catch (error) {
|
|
261
318
|
/* next gateway */
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
if (timer)
|
|
323
|
+
clearTimeout(timer);
|
|
324
|
+
}
|
|
325
|
+
if (!arrayBuffer || arrayBuffer.byteLength === 0)
|
|
326
|
+
continue;
|
|
327
|
+
if (expectedHash) {
|
|
328
|
+
if (!(await contentMatchesTag(transactionId, arrayBuffer, expectedHash))) {
|
|
329
|
+
// E.g. a gateway's error page served with 200: don't trust it, try the next gateway.
|
|
330
|
+
hashMismatch = true;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
return { arrayBuffer, contentType, networkFailedEverywhere: false, verified: true };
|
|
334
|
+
}
|
|
335
|
+
return { arrayBuffer, contentType, networkFailedEverywhere: false };
|
|
336
|
+
}
|
|
337
|
+
return { networkFailedEverywhere: !answered, hashMismatch };
|
|
338
|
+
};
|
|
339
|
+
/**
|
|
340
|
+
* Downloaded bytes on their way to OPFS. The one place a read-side transform (e.g. decrypting a
|
|
341
|
+
* future private upload) would go for downloads: this worker runs from a string, so it cannot
|
|
342
|
+
* import the main thread's `transformArweaveContent`; keys or config would arrive in the message.
|
|
343
|
+
* Identity for now.
|
|
344
|
+
*/
|
|
345
|
+
const transformDownloadedContent = async (bytes, _context) => bytes;
|
|
346
|
+
/** True when the bytes start with the signature of an image format the SDK stores in `images/`. */
|
|
347
|
+
const hasImageSignature = (bytes) => {
|
|
348
|
+
const at = (offset, ...sig) => sig.every((b, i) => bytes[offset + i] === b);
|
|
349
|
+
const ascii = (offset, s) => at(offset, ...Array.from(s).map((c) => c.charCodeAt(0)));
|
|
350
|
+
if (at(0, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a))
|
|
351
|
+
return true; // PNG
|
|
352
|
+
if (at(0, 0xff, 0xd8, 0xff))
|
|
353
|
+
return true; // JPEG
|
|
354
|
+
if (ascii(0, 'GIF87a') || ascii(0, 'GIF89a'))
|
|
355
|
+
return true;
|
|
356
|
+
if (ascii(0, 'RIFF') && ascii(8, 'WEBP'))
|
|
357
|
+
return true;
|
|
358
|
+
if (ascii(0, 'BM'))
|
|
359
|
+
return true; // BMP
|
|
360
|
+
if (at(0, 0x00, 0x00, 0x01, 0x00))
|
|
361
|
+
return true; // ICO
|
|
362
|
+
if (at(0, 0x49, 0x49, 0x2a, 0x00) || at(0, 0x4d, 0x4d, 0x00, 0x2a))
|
|
363
|
+
return true; // TIFF
|
|
364
|
+
if (ascii(4, 'ftyp')) {
|
|
365
|
+
// ISO-BMFF images (AVIF / HEIC); ftyp is also MP4, so check the brand
|
|
366
|
+
const brand = String.fromCharCode(...Array.from(bytes.slice(8, 12)));
|
|
367
|
+
return ['avif', 'avis', 'heic', 'heix', 'heif', 'mif1', 'msf1'].includes(brand);
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* Content-Types that say nothing about the content: these are sniffed instead. `text/plain` is
|
|
373
|
+
* here because some gateways send it for anything textual.
|
|
374
|
+
*/
|
|
375
|
+
const GENERIC_CONTENT_TYPES = ['', 'application/octet-stream', 'binary/octet-stream', 'text/plain'];
|
|
376
|
+
/** Where a declared Content-Type goes; undefined means sniff the bytes. */
|
|
377
|
+
const directoryForContentType = (contentType) => {
|
|
378
|
+
if (GENERIC_CONTENT_TYPES.indexOf(contentType) !== -1)
|
|
379
|
+
return undefined;
|
|
380
|
+
if (contentType.startsWith('image/'))
|
|
381
|
+
return 'images';
|
|
382
|
+
if (contentType === 'text/html' || contentType === 'application/xhtml+xml')
|
|
383
|
+
return 'html';
|
|
384
|
+
if (contentType === 'application/json' || contentType.endsWith('+json'))
|
|
385
|
+
return 'json';
|
|
386
|
+
return 'files';
|
|
387
|
+
};
|
|
388
|
+
/** Shared with the upload side (`helpers/mimeTypes.ts`), injected into this worker's source. */
|
|
389
|
+
const FILE_EXTENSIONS = SEED_MIME_TO_EXTENSION;
|
|
390
|
+
/** `files/{txId}` plus an extension when the Content-Type has a well-known one. */
|
|
391
|
+
const fileNameForFiles = (transactionId, contentType) => {
|
|
392
|
+
const ext = FILE_EXTENSIONS[contentType];
|
|
393
|
+
return ext ? `${transactionId}.${ext}` : transactionId;
|
|
394
|
+
};
|
|
395
|
+
const decodeBase64 = (base64) => {
|
|
396
|
+
try {
|
|
397
|
+
const binaryString = atob(base64);
|
|
398
|
+
const binaryData = new Uint8Array(binaryString.length);
|
|
399
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
400
|
+
binaryData[i] = binaryString.charCodeAt(i);
|
|
401
|
+
}
|
|
402
|
+
return binaryData;
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
/**
|
|
409
|
+
* Composite format: a copy of `helpers/compositeUpload.ts` (this worker cannot import; a test
|
|
410
|
+
* decodes the SDK encoder's output through this worker). Children are
|
|
411
|
+
* `===FILE_SEPARATOR===<name>===FILE_SEPARATOR===<text>`; a binary child's name ends in `;base64`
|
|
412
|
+
* and its content is base64.
|
|
413
|
+
*/
|
|
414
|
+
const COMPOSITE_SEPARATOR = '===FILE_SEPARATOR===';
|
|
415
|
+
const COMPOSITE_BASE64_SUFFIX = ';base64';
|
|
416
|
+
const startsWithCompositeSeparator = (bytes) => {
|
|
417
|
+
if (bytes.length < COMPOSITE_SEPARATOR.length)
|
|
418
|
+
return false;
|
|
419
|
+
for (let i = 0; i < COMPOSITE_SEPARATOR.length; i++) {
|
|
420
|
+
if (bytes[i] !== COMPOSITE_SEPARATOR.charCodeAt(i))
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
424
|
+
};
|
|
425
|
+
const decodeComposite = (bytes) => {
|
|
426
|
+
const parts = new TextDecoder('utf-8').decode(bytes).split(COMPOSITE_SEPARATOR).slice(1);
|
|
427
|
+
if (parts.length % 2 !== 0)
|
|
428
|
+
return undefined;
|
|
429
|
+
const encoder = new TextEncoder();
|
|
430
|
+
const children = [];
|
|
431
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
432
|
+
const rawName = parts[i];
|
|
433
|
+
const content = parts[i + 1];
|
|
434
|
+
if (rawName.endsWith(COMPOSITE_BASE64_SUFFIX)) {
|
|
435
|
+
const decoded = decodeBase64(content.replace(/\s/g, ''));
|
|
436
|
+
if (!decoded)
|
|
437
|
+
return undefined;
|
|
438
|
+
children.push({ name: rawName.slice(0, -COMPOSITE_BASE64_SUFFIX.length), bytes: decoded });
|
|
262
439
|
}
|
|
440
|
+
else {
|
|
441
|
+
children.push({ name: rawName, bytes: encoder.encode(content) });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return children;
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* A composite child's path from the page, relative to the files root. Only plain relative paths
|
|
448
|
+
* are taken: nothing absolute, no `.` / `..` segments, no backslashes.
|
|
449
|
+
*/
|
|
450
|
+
const safeRelativePath = (path) => {
|
|
451
|
+
if (typeof path !== 'string' || !path || path.startsWith('/') || path.indexOf('\\') !== -1)
|
|
452
|
+
return undefined;
|
|
453
|
+
const segments = path.split('/');
|
|
454
|
+
for (const segment of segments) {
|
|
455
|
+
if (!segment || segment === '.' || segment === '..')
|
|
456
|
+
return undefined;
|
|
263
457
|
}
|
|
264
|
-
return
|
|
458
|
+
return path;
|
|
265
459
|
};
|
|
266
|
-
const downloadFiles = async ({ transactionIds, arweaveHost, arweaveBaseUrls, filesRoot = '/files', }) => {
|
|
460
|
+
const downloadFiles = async ({ transactionIds, arweaveHost, arweaveBaseUrls, filesRoot = '/files', expectedContentHashes, compositeChildPaths, }) => {
|
|
267
461
|
// Preferred / resolved gateways first, then the configured host; deduped.
|
|
268
462
|
const baseUrls = [];
|
|
269
463
|
for (const candidate of (arweaveBaseUrls || []).concat([arweaveHost])) {
|
|
@@ -273,134 +467,189 @@ var filesDownload = `(
|
|
|
273
467
|
}
|
|
274
468
|
}
|
|
275
469
|
for (const transactionId of transactionIds) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
globalThis.postMessage({
|
|
280
|
-
message: 'excludeTransaction',
|
|
281
|
-
transactionId,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
continue;
|
|
470
|
+
// One bad file (failed OPFS write, quota, malformed content) must not stall or abort the batch.
|
|
471
|
+
try {
|
|
472
|
+
await downloadFile(transactionId, baseUrls, filesRoot, expectedContentHashes?.[transactionId], compositeChildPaths?.[transactionId]);
|
|
285
473
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (!isBinaryData) {
|
|
289
|
-
const decoder = new TextDecoder('utf-8');
|
|
290
|
-
const text = decoder.decode(arrayBuffer);
|
|
291
|
-
dataString = text;
|
|
474
|
+
catch (error) {
|
|
475
|
+
globalThis.postMessage({ message: 'fileError', transactionId, error: errorInfo(error) });
|
|
292
476
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
await saveBufferToOPFS(getFilesPath(filesRoot, 'html', fileName), buffer);
|
|
308
|
-
}
|
|
309
|
-
if (contentType === 'json') {
|
|
310
|
-
const fileName = `${transactionId}.json`;
|
|
311
|
-
const buffer = encoder.encode(content);
|
|
312
|
-
await saveBufferToOPFS(getFilesPath(filesRoot, 'json', fileName), buffer);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
continue;
|
|
477
|
+
// Liveness signal for the page's inactivity watchdog.
|
|
478
|
+
globalThis.postMessage({ message: 'fileProcessed', transactionId });
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const downloadFile = async (transactionId, baseUrls, filesRoot, expectedHash, childPaths) => {
|
|
482
|
+
const encoder = new TextEncoder();
|
|
483
|
+
const fetched = await fetchRaw(baseUrls, transactionId, expectedHash);
|
|
484
|
+
if (fetched.verified) {
|
|
485
|
+
globalThis.postMessage({ message: 'contentVerified', transactionId });
|
|
486
|
+
}
|
|
487
|
+
if (!fetched.arrayBuffer) {
|
|
488
|
+
if (fetched.hashMismatch) {
|
|
489
|
+
// Nothing saved: the bytes are not what was uploaded. Not excluded, a later run may get them.
|
|
490
|
+
globalThis.postMessage({ message: 'contentHashMismatch', transactionId });
|
|
316
491
|
}
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
492
|
+
else if (fetched.networkFailedEverywhere) {
|
|
493
|
+
globalThis.postMessage({
|
|
494
|
+
message: 'excludeTransaction',
|
|
495
|
+
transactionId,
|
|
496
|
+
});
|
|
320
497
|
}
|
|
321
|
-
|
|
322
|
-
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const contentType = fetched.contentType || '';
|
|
501
|
+
const bytes = await transformDownloadedContent(new Uint8Array(fetched.arrayBuffer));
|
|
502
|
+
const saveToFiles = (data) => saveBufferToOPFS(getFilesPath(filesRoot, 'files', fileNameForFiles(transactionId, contentType)), data);
|
|
503
|
+
// Composite upload (several ItemStorage files in one transaction, uploaded as
|
|
504
|
+
// application/octet-stream). Detected on the bytes: a composite of mostly non-ASCII text (e.g.
|
|
505
|
+
// CJK html) would fail the isBinary heuristic below.
|
|
506
|
+
if (startsWithCompositeSeparator(bytes)) {
|
|
507
|
+
const children = decodeComposite(bytes);
|
|
508
|
+
if (!children) {
|
|
509
|
+
// Not a composite after all: keep the bytes rather than fail the whole download.
|
|
510
|
+
await saveToFiles(bytes);
|
|
511
|
+
return;
|
|
323
512
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
513
|
+
for (const child of children) {
|
|
514
|
+
// Children are routed by property name: to the path the page gave for it (the model's
|
|
515
|
+
// ItemStorage directory and extension), else the html / json layout older SDKs assumed.
|
|
516
|
+
const routed = childPaths && Object.prototype.hasOwnProperty.call(childPaths, child.name)
|
|
517
|
+
? safeRelativePath(childPaths[child.name])
|
|
518
|
+
: undefined;
|
|
519
|
+
if (routed) {
|
|
520
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, routed), child.bytes);
|
|
331
521
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (contentType === 'url') {
|
|
335
|
-
const url = dataString;
|
|
336
|
-
let buffer;
|
|
337
|
-
try {
|
|
338
|
-
const response = await fetch(url);
|
|
339
|
-
if (!response.ok) {
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
buffer = await response.arrayBuffer();
|
|
522
|
+
else if (child.name === 'html') {
|
|
523
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'html', `${transactionId}.html`), child.bytes);
|
|
343
524
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
message: 'excludeTransaction',
|
|
347
|
-
transactionId,
|
|
348
|
-
});
|
|
349
|
-
continue;
|
|
525
|
+
else if (child.name === 'json') {
|
|
526
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'json', `${transactionId}.json`), child.bytes);
|
|
350
527
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (!extensionMatch) {
|
|
355
|
-
throw new Error('Unable to determine the file extension from the URL.');
|
|
528
|
+
else {
|
|
529
|
+
// No local item knows this child's property yet; a later download (after sync) places it.
|
|
530
|
+
globalThis.postMessage({ message: 'compositeChildUnrouted', transactionId, name: child.name });
|
|
356
531
|
}
|
|
357
|
-
extensionMatch[0]; // e.g., ".jpg"
|
|
358
|
-
// Set the file name (you can customize this)
|
|
359
|
-
// const fileNameFromUrl = `${transactionId}${fileExtension}`
|
|
360
|
-
await saveBufferToOPFS(getFilesPath(filesRoot, 'images', transactionId), bufferUint8Array);
|
|
361
|
-
continue;
|
|
362
532
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
let dataString;
|
|
536
|
+
if (!isBinary(bytes)) {
|
|
537
|
+
dataString = new TextDecoder('utf-8').decode(bytes);
|
|
538
|
+
}
|
|
539
|
+
// The upload said what it is (Content-Type tag, served as the response header).
|
|
540
|
+
const declaredDir = directoryForContentType(contentType);
|
|
541
|
+
const isLegacyDataUri = !!dataString && dataString.startsWith('data:');
|
|
542
|
+
if (declaredDir && !isLegacyDataUri) {
|
|
543
|
+
if (declaredDir === 'images') {
|
|
544
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'images', transactionId), bytes);
|
|
367
545
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
546
|
+
else if (declaredDir === 'html') {
|
|
547
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'html', `${transactionId}.html`), bytes);
|
|
548
|
+
}
|
|
549
|
+
else if (declaredDir === 'json') {
|
|
550
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'json', `${transactionId}.json`), bytes);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
await saveToFiles(bytes);
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
// No useful Content-Type: sniff. Binary is an image only when it has an image signature.
|
|
558
|
+
if (!dataString) {
|
|
559
|
+
if (hasImageSignature(bytes)) {
|
|
560
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'images', transactionId), bytes);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
await saveToFiles(bytes);
|
|
564
|
+
}
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
let textType = identifyString(dataString);
|
|
568
|
+
if (textType !== 'json' &&
|
|
569
|
+
textType !== 'base64' &&
|
|
570
|
+
textType !== 'html') {
|
|
571
|
+
const possibleImageType = getDataTypeFromString(dataString);
|
|
572
|
+
if (!possibleImageType) {
|
|
573
|
+
// Plain text / markdown / anything else textual.
|
|
574
|
+
await saveToFiles(bytes);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
textType = possibleImageType;
|
|
578
|
+
}
|
|
579
|
+
if (textType === 'url') {
|
|
580
|
+
const url = dataString;
|
|
581
|
+
// Legacy: an upload whose content is the URL of an image.
|
|
582
|
+
if (!/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(url)) {
|
|
583
|
+
await saveToFiles(bytes);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
let buffer;
|
|
587
|
+
try {
|
|
588
|
+
const response = await fetch(url);
|
|
589
|
+
if (!response.ok) {
|
|
590
|
+
return;
|
|
381
591
|
}
|
|
382
|
-
await
|
|
592
|
+
buffer = await response.arrayBuffer();
|
|
383
593
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
594
|
+
catch (error) {
|
|
595
|
+
globalThis.postMessage({
|
|
596
|
+
message: 'excludeTransaction',
|
|
597
|
+
transactionId,
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'images', transactionId), new Uint8Array(buffer));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
let fileName = transactionId;
|
|
605
|
+
if (textType === 'base64') {
|
|
606
|
+
const mimeType = getMimeType(dataString);
|
|
607
|
+
const isDataUri = dataString.startsWith('data:');
|
|
608
|
+
// Remove the Base64 header if it exists (e.g., "data:image/png;base64,")
|
|
609
|
+
const binaryData = decodeBase64(dataString.split(',').pop() || '');
|
|
610
|
+
// A data:image URI, or bare base64 that decodes to an image. Anything else is kept as text.
|
|
611
|
+
const isImage = !!binaryData &&
|
|
612
|
+
((isDataUri && !!mimeType && mimeType.startsWith('image/')) || hasImageSignature(binaryData));
|
|
613
|
+
if (!binaryData || !isImage) {
|
|
614
|
+
await saveToFiles(bytes);
|
|
615
|
+
return;
|
|
389
616
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const buffer = encoder.encode(dataString);
|
|
394
|
-
await saveBufferToOPFS(getFilesPath(filesRoot, 'json', fileName), buffer);
|
|
617
|
+
const fileExtension = mimeType && mimeType.startsWith('image/') ? mimeType.replace('image/', '') : mimeType;
|
|
618
|
+
if (fileExtension) {
|
|
619
|
+
fileName += `.${fileExtension}`;
|
|
395
620
|
}
|
|
621
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'images', fileName), binaryData);
|
|
622
|
+
}
|
|
623
|
+
if (textType === 'html') {
|
|
624
|
+
fileName += '.html';
|
|
625
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'html', fileName), encoder.encode(dataString));
|
|
626
|
+
}
|
|
627
|
+
if (textType === 'json') {
|
|
628
|
+
fileName += '.json';
|
|
629
|
+
await saveBufferToOPFS(getFilesPath(filesRoot, 'json', fileName), encoder.encode(dataString));
|
|
396
630
|
}
|
|
397
631
|
};
|
|
398
632
|
onmessage = async (e) => {
|
|
399
|
-
const
|
|
633
|
+
const data = (e && e.data) || {};
|
|
634
|
+
if (data.message === 'verifyContentResult') {
|
|
635
|
+
const resolve = pendingVerifications.get(data.requestId);
|
|
636
|
+
pendingVerifications.delete(data.requestId);
|
|
637
|
+
if (resolve)
|
|
638
|
+
resolve(!!data.ok);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const { debug, filesRoot } = data;
|
|
400
642
|
if (!debug) {
|
|
401
643
|
console.log = () => { };
|
|
402
644
|
}
|
|
403
|
-
|
|
645
|
+
try {
|
|
646
|
+
await downloadFiles({ ...data, filesRoot });
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
// Anything not caught per file: tell the page instead of leaving it waiting for `done`.
|
|
650
|
+
globalThis.postMessage({ message: 'filesDownload failed', error: errorInfo(error) });
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
404
653
|
globalThis.postMessage({
|
|
405
654
|
message: 'filesDownload onmessage done',
|
|
406
655
|
done: true,
|
|
@@ -410,56 +659,177 @@ var filesDownload = `(
|
|
|
410
659
|
)()`;
|
|
411
660
|
|
|
412
661
|
const logger$6 = debug('seedSdk:browser:workers:FileDownloader');
|
|
662
|
+
/**
|
|
663
|
+
* Longest the worker may go without posting anything before the download is failed. The worker
|
|
664
|
+
* posts after every transaction and each gateway request times out after 60s, so this only fires
|
|
665
|
+
* when the worker died without an error event (e.g. killed for memory) or is wedged.
|
|
666
|
+
*/
|
|
667
|
+
const FILE_DOWNLOAD_INACTIVITY_TIMEOUT_MS = 5 * 60000;
|
|
668
|
+
class FileDownloadWorkerError extends Error {
|
|
669
|
+
constructor(message, options) {
|
|
670
|
+
super(message);
|
|
671
|
+
this.name = 'FileDownloadWorkerError';
|
|
672
|
+
if (options && 'cause' in options)
|
|
673
|
+
this.cause = options.cause;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const toError = (raw, fallback) => {
|
|
677
|
+
if (raw instanceof Error)
|
|
678
|
+
return raw;
|
|
679
|
+
if (raw && typeof raw === 'object' && 'message' in raw) {
|
|
680
|
+
const error = new FileDownloadWorkerError(String(raw.message));
|
|
681
|
+
const name = raw.name;
|
|
682
|
+
if (typeof name === 'string' && name)
|
|
683
|
+
error.name = name;
|
|
684
|
+
return error;
|
|
685
|
+
}
|
|
686
|
+
return new FileDownloadWorkerError(raw == null ? fallback : String(raw));
|
|
687
|
+
};
|
|
413
688
|
class FileDownloader {
|
|
414
|
-
constructor() {
|
|
415
|
-
this.
|
|
416
|
-
this.downloadAll = async ({ transactionIds, arweaveHost, arweaveBaseUrls, excludedTransactions }) => {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
689
|
+
constructor(options = {}) {
|
|
690
|
+
this.options = options;
|
|
691
|
+
this.downloadAll = async ({ transactionIds, arweaveHost, arweaveBaseUrls, excludedTransactions, expectedContentHashes, compositeChildPaths, }) => {
|
|
692
|
+
// One download per instance at a time: a newer call replaces the older one, which rejects
|
|
693
|
+
// instead of waiting forever on a terminated worker.
|
|
694
|
+
this.abortCurrent?.(new FileDownloadWorkerError('Superseded by a newer download'));
|
|
695
|
+
const workerBlobUrl = globalThis.URL.createObjectURL(new Blob([filesDownload], { type: 'application/javascript' }));
|
|
696
|
+
let worker;
|
|
697
|
+
try {
|
|
698
|
+
worker = new Worker(workerBlobUrl);
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
globalThis.URL.revokeObjectURL(workerBlobUrl);
|
|
702
|
+
throw error;
|
|
423
703
|
}
|
|
424
|
-
|
|
425
|
-
this.workersArchive.push(worker);
|
|
704
|
+
this.worker = worker;
|
|
426
705
|
const localExcludedTransactions = new Set(excludedTransactions);
|
|
427
706
|
const savedPaths = [];
|
|
707
|
+
const verified = new Set();
|
|
708
|
+
const hashMismatch = new Set();
|
|
709
|
+
const failed = new Set();
|
|
710
|
+
const inactivityTimeoutMs = this.options.inactivityTimeoutMs ?? FILE_DOWNLOAD_INACTIVITY_TIMEOUT_MS;
|
|
428
711
|
return new Promise((resolve, reject) => {
|
|
712
|
+
let settled = false;
|
|
713
|
+
let watchdog;
|
|
714
|
+
const cleanup = () => {
|
|
715
|
+
if (watchdog)
|
|
716
|
+
clearTimeout(watchdog);
|
|
717
|
+
worker.onmessage = null;
|
|
718
|
+
worker.onerror = null;
|
|
719
|
+
worker.onmessageerror = null;
|
|
720
|
+
worker.terminate();
|
|
721
|
+
globalThis.URL.revokeObjectURL(workerBlobUrl);
|
|
722
|
+
if (this.worker === worker)
|
|
723
|
+
this.worker = undefined;
|
|
724
|
+
if (this.abortCurrent === fail)
|
|
725
|
+
this.abortCurrent = undefined;
|
|
726
|
+
};
|
|
727
|
+
const fail = (error) => {
|
|
728
|
+
if (settled)
|
|
729
|
+
return;
|
|
730
|
+
settled = true;
|
|
731
|
+
cleanup();
|
|
732
|
+
logger$6('file download failed', error);
|
|
733
|
+
// Files the worker already wrote are on disk: refresh caches for them anyway.
|
|
734
|
+
if (savedPaths.length) {
|
|
735
|
+
void notifyFilesWrittenOutsideCache(savedPaths).catch(() => { });
|
|
736
|
+
}
|
|
737
|
+
reject(error);
|
|
738
|
+
};
|
|
739
|
+
const succeed = () => {
|
|
740
|
+
if (settled)
|
|
741
|
+
return;
|
|
742
|
+
settled = true;
|
|
743
|
+
cleanup();
|
|
744
|
+
addExcludedTransactions(localExcludedTransactions)
|
|
745
|
+
// The worker wrote to OPFS directly; refresh caches here and in other tabs.
|
|
746
|
+
.then(() => notifyFilesWrittenOutsideCache(savedPaths))
|
|
747
|
+
.then(() => resolve({
|
|
748
|
+
verifiedTransactionIds: [...verified].filter((id) => !failed.has(id)),
|
|
749
|
+
hashMismatchTransactionIds: [...hashMismatch],
|
|
750
|
+
failedTransactionIds: [...failed],
|
|
751
|
+
}))
|
|
752
|
+
.catch(reject);
|
|
753
|
+
};
|
|
754
|
+
const armWatchdog = () => {
|
|
755
|
+
if (watchdog)
|
|
756
|
+
clearTimeout(watchdog);
|
|
757
|
+
watchdog = setTimeout(() => fail(new FileDownloadWorkerError(`File download worker sent nothing for ${inactivityTimeoutMs}ms; assuming it stopped`)), inactivityTimeoutMs);
|
|
758
|
+
};
|
|
759
|
+
this.abortCurrent = fail;
|
|
429
760
|
worker.onmessage = (e) => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
761
|
+
armWatchdog();
|
|
762
|
+
const data = e.data ?? {};
|
|
763
|
+
logger$6('filesDownload main thread onmessage', data);
|
|
764
|
+
if (data.message === 'excludeTransaction') {
|
|
765
|
+
localExcludedTransactions.add(data.transactionId);
|
|
766
|
+
}
|
|
767
|
+
if (data.message === 'fileSaved') {
|
|
768
|
+
savedPaths.push(data.filePath);
|
|
433
769
|
}
|
|
434
|
-
if (
|
|
435
|
-
|
|
770
|
+
if (data.message === 'verifyContent') {
|
|
771
|
+
// The worker could not confirm the bytes itself (legacy SHA3-256 tag, or no WebCrypto).
|
|
772
|
+
let ok = false;
|
|
773
|
+
try {
|
|
774
|
+
ok = contentSha256TagMatches(new Uint8Array(data.bytes), String(data.expected ?? ''));
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
logger$6('content verification failed', error);
|
|
778
|
+
}
|
|
779
|
+
worker.postMessage({ message: 'verifyContentResult', requestId: data.requestId, ok });
|
|
780
|
+
return;
|
|
436
781
|
}
|
|
437
|
-
if (
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
});
|
|
782
|
+
if (data.message === 'compositeChildUnrouted') {
|
|
783
|
+
// No local ItemStorage property for this child (e.g. its item is not synced yet).
|
|
784
|
+
logger$6(`composite ${data.transactionId}: no local path for child "${data.name}"; not saved`);
|
|
785
|
+
}
|
|
786
|
+
if (data.message === 'contentVerified') {
|
|
787
|
+
verified.add(data.transactionId);
|
|
788
|
+
}
|
|
789
|
+
if (data.message === 'contentHashMismatch') {
|
|
790
|
+
hashMismatch.add(data.transactionId);
|
|
791
|
+
console.warn(`[FileDownloader] ${data.transactionId}: no gateway served bytes matching its Content-SHA-256 tag; not saved`);
|
|
792
|
+
}
|
|
793
|
+
if (data.message === 'fileError') {
|
|
794
|
+
failed.add(data.transactionId);
|
|
795
|
+
// One file failed (e.g. OPFS write); the worker carries on with the rest.
|
|
796
|
+
console.warn(`[FileDownloader] failed to save ${data.transactionId}: ${data.error?.name ?? 'Error'}: ${data.error?.message ?? ''}`);
|
|
797
|
+
}
|
|
798
|
+
if (data.error && data.message !== 'fileError') {
|
|
799
|
+
fail(toError(data.error, 'File download worker failed'));
|
|
800
|
+
return;
|
|
447
801
|
}
|
|
448
|
-
if (
|
|
449
|
-
|
|
802
|
+
if (data.done) {
|
|
803
|
+
succeed();
|
|
450
804
|
}
|
|
451
805
|
};
|
|
452
|
-
worker.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
806
|
+
worker.onerror = (event) => {
|
|
807
|
+
// Keep it from also surfacing as an uncaught error; the promise carries it.
|
|
808
|
+
event.preventDefault?.();
|
|
809
|
+
const where = event.filename ? ` (${event.filename}:${event.lineno ?? 0})` : '';
|
|
810
|
+
fail(new FileDownloadWorkerError(`File download worker error: ${event.message || 'unknown error'}${where}`, { cause: event.error }));
|
|
811
|
+
};
|
|
812
|
+
worker.onmessageerror = () => {
|
|
813
|
+
fail(new FileDownloadWorkerError('File download worker sent a message that could not be deserialized'));
|
|
814
|
+
};
|
|
815
|
+
armWatchdog();
|
|
816
|
+
try {
|
|
817
|
+
worker.postMessage({
|
|
818
|
+
transactionIds,
|
|
819
|
+
arweaveHost,
|
|
820
|
+
arweaveBaseUrls,
|
|
821
|
+
expectedContentHashes,
|
|
822
|
+
compositeChildPaths,
|
|
823
|
+
debug: logger$6.enabled,
|
|
824
|
+
filesRoot: BaseFileManager.getWorkingDir(),
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
fail(toError(error, 'Could not start the file download worker'));
|
|
829
|
+
}
|
|
459
830
|
});
|
|
460
831
|
};
|
|
461
832
|
this.cores = Math.min(navigator.hardwareConcurrency || 4, 4);
|
|
462
|
-
this.workerBlobUrl = globalThis.URL.createObjectURL(new Blob([filesDownload], { type: 'application/javascript' }));
|
|
463
833
|
}
|
|
464
834
|
}
|
|
465
835
|
|
|
@@ -1053,6 +1423,8 @@ function sleep(ms) {
|
|
|
1053
1423
|
class BrowserFileManager {
|
|
1054
1424
|
constructor() {
|
|
1055
1425
|
this.zenfsCache = null;
|
|
1426
|
+
/** Invalidations run one at a time: two refreshing the same path at once could interleave. */
|
|
1427
|
+
this.invalidations = Promise.resolve();
|
|
1056
1428
|
}
|
|
1057
1429
|
async getFs() {
|
|
1058
1430
|
if (!this.zenfsCache) {
|
|
@@ -1075,7 +1447,10 @@ class BrowserFileManager {
|
|
|
1075
1447
|
}
|
|
1076
1448
|
async initializeFileSystem(_workingDir) {
|
|
1077
1449
|
const zenfs = await this.getFs();
|
|
1078
|
-
const { TolerantWebAccess } = await import('./tolerantWebAccess-
|
|
1450
|
+
const { TolerantWebAccess, TolerantWebAccessFS } = await import('./tolerantWebAccess-BZYWt09Y.js');
|
|
1451
|
+
const _mod_0 = await import('./tabEvents-DguZpCCv.js');
|
|
1452
|
+
const _ns_0 = _mod_0._;
|
|
1453
|
+
const { queueFilesChangedNotice } = _ns_0;
|
|
1079
1454
|
const { configureSingle } = zenfs;
|
|
1080
1455
|
const configureRoot = async () => {
|
|
1081
1456
|
const handle = await navigator.storage.getDirectory();
|
|
@@ -1090,6 +1465,12 @@ class BrowserFileManager {
|
|
|
1090
1465
|
for (let attempt = 0;; attempt++) {
|
|
1091
1466
|
try {
|
|
1092
1467
|
await configureRoot();
|
|
1468
|
+
// Every write through ZenFS (saveFile, saveFileSync, fs.promises / fs.*Sync used directly)
|
|
1469
|
+
// reaches OPFS through this mount: tell other tabs once each change lands, coalesced.
|
|
1470
|
+
const mounted = zenfs.mounts.get('/');
|
|
1471
|
+
if (mounted instanceof TolerantWebAccessFS) {
|
|
1472
|
+
mounted.onOpfsChange = (path) => queueFilesChangedNotice([path]);
|
|
1473
|
+
}
|
|
1093
1474
|
return;
|
|
1094
1475
|
}
|
|
1095
1476
|
catch (error) {
|
|
@@ -1104,9 +1485,9 @@ class BrowserFileManager {
|
|
|
1104
1485
|
}
|
|
1105
1486
|
}
|
|
1106
1487
|
}
|
|
1107
|
-
async downloadAllFiles(
|
|
1488
|
+
async downloadAllFiles(params) {
|
|
1108
1489
|
const fileDownloader = new FileDownloader();
|
|
1109
|
-
|
|
1490
|
+
return fileDownloader.downloadAll(params);
|
|
1110
1491
|
}
|
|
1111
1492
|
async resizeImage({ filePath, width, height }) {
|
|
1112
1493
|
const imageResizer = new ImageResizer();
|
|
@@ -1130,12 +1511,17 @@ class BrowserFileManager {
|
|
|
1130
1511
|
return entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
1131
1512
|
}
|
|
1132
1513
|
async invalidateCachedPaths(filePaths) {
|
|
1514
|
+
const run = this.invalidations.then(() => this.invalidateNow(filePaths));
|
|
1515
|
+
this.invalidations = run.catch(() => { });
|
|
1516
|
+
return run;
|
|
1517
|
+
}
|
|
1518
|
+
async invalidateNow(filePaths) {
|
|
1133
1519
|
const { mounts } = await this.getFs();
|
|
1134
|
-
const { TolerantWebAccessFS } = await import('./tolerantWebAccess-
|
|
1520
|
+
const { TolerantWebAccessFS } = await import('./tolerantWebAccess-BZYWt09Y.js');
|
|
1135
1521
|
const mounted = mounts.get('/');
|
|
1136
1522
|
if (!(mounted instanceof TolerantWebAccessFS))
|
|
1137
1523
|
return;
|
|
1138
|
-
for (const filePath of filePaths) {
|
|
1524
|
+
for (const filePath of new Set(filePaths)) {
|
|
1139
1525
|
const absolute = filePath.startsWith('/') ? filePath : `/${filePath}`;
|
|
1140
1526
|
try {
|
|
1141
1527
|
await mounted.invalidate(absolute);
|
|
@@ -1350,8 +1736,9 @@ class BrowserFileManager {
|
|
|
1350
1736
|
else {
|
|
1351
1737
|
throw new Error('Unsupported content type');
|
|
1352
1738
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1739
|
+
// The promise API: zenfs.writeFile is the Node callback API, which returns before the write
|
|
1740
|
+
// lands, so a read right after saveFile could see the previous content.
|
|
1741
|
+
await zenfs.promises.writeFile(filePath, writeContent);
|
|
1355
1742
|
// try {
|
|
1356
1743
|
// // Get a handle to the OPFS root directory
|
|
1357
1744
|
// const root = await navigator.storage.getDirectory();
|
|
@@ -1530,6 +1917,7 @@ class CancelableInterval {
|
|
|
1530
1917
|
}
|
|
1531
1918
|
}
|
|
1532
1919
|
BaseFileManager.configure(new BrowserFileManager());
|
|
1920
|
+
new BrowserFileManager();
|
|
1533
1921
|
|
|
1534
1922
|
// This file embeds the drizzle migration files as strings for browser runtime
|
|
1535
1923
|
// These files are copied from packages/sdk/src/db/drizzle at build time
|
|
@@ -3668,10 +4056,10 @@ class BrowserDb {
|
|
|
3668
4056
|
await raceSqlocalStartup((async () => {
|
|
3669
4057
|
await this.runMigrations(db);
|
|
3670
4058
|
this.appDb = db;
|
|
3671
|
-
const { backfillMetadataPropertyIds } = await import('./backfillMetadataPropertyIds-
|
|
4059
|
+
const { backfillMetadataPropertyIds } = await import('./backfillMetadataPropertyIds-SXW-8C64.js');
|
|
3672
4060
|
await backfillMetadataPropertyIds();
|
|
3673
4061
|
// One-time: clear item data whose model can't be determined (seeds.model_file_id, migration 0015)
|
|
3674
|
-
const { resetAmbiguousLegacyItemData } = await import('./resetAmbiguousLegacyItemData-
|
|
4062
|
+
const { resetAmbiguousLegacyItemData } = await import('./resetAmbiguousLegacyItemData-Bk0QyDW8.js');
|
|
3675
4063
|
await resetAmbiguousLegacyItemData();
|
|
3676
4064
|
})(), workerFailed);
|
|
3677
4065
|
return this.appDb;
|
|
@@ -4136,4 +4524,4 @@ function createPlatformServices() {
|
|
|
4136
4524
|
}
|
|
4137
4525
|
|
|
4138
4526
|
export { createPlatformServices as c, isSkippableOpfsEntryError as i };
|
|
4139
|
-
//# sourceMappingURL=index.browser-
|
|
4527
|
+
//# sourceMappingURL=index.browser-Cm06tP4X.js.map
|