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