@seedprotocol/sdk 0.7.3-next.613.g79a8dfd → 0.7.3

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.
Files changed (239) hide show
  1. package/dist/{db-CQt-gcAl.js → BasePathResolver-Cm4apKRb.js} +2041 -127
  2. package/dist/BasePathResolver-Cm4apKRb.js.map +1 -0
  3. package/dist/{ClientManager-DDA9956Y.js → ClientManager-i3nJASkg.js} +664 -277
  4. package/dist/ClientManager-i3nJASkg.js.map +1 -0
  5. package/dist/{Item-D4FigtYS.js → Item-DBFchXpC.js} +495 -219
  6. package/dist/Item-DBFchXpC.js.map +1 -0
  7. package/dist/{ModelProperty-BDt6AU2h.js → ModelProperty-BS49g01Y.js} +167 -47
  8. package/dist/ModelProperty-BS49g01Y.js.map +1 -0
  9. package/dist/{Schema-B8xqLWkL.js → Schema-CpMNEnUO.js} +53 -42
  10. package/dist/Schema-CpMNEnUO.js.map +1 -0
  11. package/dist/{SchemaValidationService-BPxkfDLU.js → SchemaValidationService-DieV4KL-.js} +13 -14
  12. package/dist/{SchemaValidationService-BPxkfDLU.js.map → SchemaValidationService-DieV4KL-.js.map} +1 -1
  13. package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js → backfillMetadataPropertyIds-DSoOoTnX.js} +14 -7
  14. package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js.map → backfillMetadataPropertyIds-DSoOoTnX.js.map} +1 -1
  15. package/dist/{configurePlatform-YcOiDQBZ.js → configurePlatform-D0lQGAMQ.js} +8 -7
  16. package/dist/{configurePlatform-YcOiDQBZ.js.map → configurePlatform-D0lQGAMQ.js.map} +1 -1
  17. package/dist/detachRowsBeforeDelete-S0q-DJPT.js +50 -0
  18. package/dist/detachRowsBeforeDelete-S0q-DJPT.js.map +1 -0
  19. package/dist/{eas-CpOD74Ke.js → eas-6pjU-OuX.js} +9 -8
  20. package/dist/eas-6pjU-OuX.js.map +1 -0
  21. package/dist/{getItem-B_OoFkd7.js → getItem-CxBNZ-WL.js} +13 -11
  22. package/dist/{getItem-B_OoFkd7.js.map → getItem-CxBNZ-WL.js.map} +1 -1
  23. package/dist/{getPublishPayload-Xl3sRkR-.js → getPublishPayload-CTCvRUoU.js} +50 -18
  24. package/dist/getPublishPayload-CTCvRUoU.js.map +1 -0
  25. package/dist/getPublishUploads-SFHCbf_y.js +648 -0
  26. package/dist/getPublishUploads-SFHCbf_y.js.map +1 -0
  27. package/dist/{index-Xl1_4Lmb.js → index-8O3Uow-Y.js} +125 -57
  28. package/dist/index-8O3Uow-Y.js.map +1 -0
  29. package/dist/{index-CSOQFAOn.js → index-AVFTVp1m.js} +2 -2
  30. package/dist/index-AVFTVp1m.js.map +1 -0
  31. package/dist/index-Cvt5UBz4.js +30 -0
  32. package/dist/index-Cvt5UBz4.js.map +1 -0
  33. package/dist/{index.browser-CjTe0vt1.js → index.browser-DBhxRoe8.js} +554 -165
  34. package/dist/index.browser-DBhxRoe8.js.map +1 -0
  35. package/dist/{index.node-Cfa7npS6.js → index.node-CJhlcNHM.js} +25 -13
  36. package/dist/index.node-CJhlcNHM.js.map +1 -0
  37. package/dist/main.js +30 -47
  38. package/dist/main.js.map +1 -1
  39. package/dist/mimeTypes-DHAHC4eZ.js +204 -0
  40. package/dist/mimeTypes-DHAHC4eZ.js.map +1 -0
  41. package/dist/node.js +19 -21
  42. package/dist/node.js.map +1 -1
  43. package/dist/platform.browser.js +13 -11
  44. package/dist/platform.browser.js.map +1 -1
  45. package/dist/platform.node.js +18 -5
  46. package/dist/platform.node.js.map +1 -1
  47. package/dist/{property-BrXkY1Rg.js → property-DwPD283A.js} +14 -12
  48. package/dist/property-DwPD283A.js.map +1 -0
  49. package/dist/{publishDraftGraph-X0Wr9txE.js → publishDraftGraph-BOn3_Occ.js} +6 -9
  50. package/dist/{publishDraftGraph-X0Wr9txE.js.map → publishDraftGraph-BOn3_Occ.js.map} +1 -1
  51. package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js → resetAmbiguousLegacyItemData-BkYmBeJc.js} +14 -3
  52. package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js.map → resetAmbiguousLegacyItemData-BkYmBeJc.js.map} +1 -1
  53. package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js → resolveModelForSyncedSeed-BL24nLa7.js} +2 -2
  54. package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js.map → resolveModelForSyncedSeed-BL24nLa7.js.map} +1 -1
  55. package/dist/src/Item/Item.d.ts +21 -3
  56. package/dist/src/Item/Item.d.ts.map +1 -1
  57. package/dist/src/Item/service/actors/runPublish.d.ts +11 -2
  58. package/dist/src/Item/service/actors/runPublish.d.ts.map +1 -1
  59. package/dist/src/Item/service/itemMachineSingle.d.ts +12 -2
  60. package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
  61. package/dist/src/ItemProperty/ItemProperty.d.ts +5 -3
  62. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  63. package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts.map +1 -1
  64. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +1 -1
  65. package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
  66. package/dist/src/Model/Model.d.ts +10 -4
  67. package/dist/src/Model/Model.d.ts.map +1 -1
  68. package/dist/src/Model/index.d.ts +2 -2
  69. package/dist/src/ModelProperty/ModelProperty.d.ts +10 -2
  70. package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
  71. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +3 -0
  72. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
  73. package/dist/src/Schema/Schema.d.ts +2 -2
  74. package/dist/src/Schema/Schema.d.ts.map +1 -1
  75. package/dist/src/Schema/index.d.ts +0 -1
  76. package/dist/src/Schema/index.d.ts.map +1 -1
  77. package/dist/src/browser/helpers/FileManager.d.ts +4 -1
  78. package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
  79. package/dist/src/browser/helpers/tolerantWebAccess.d.ts +79 -4
  80. package/dist/src/browser/helpers/tolerantWebAccess.d.ts.map +1 -1
  81. package/dist/src/browser/workers/FileDownloader.d.ts +19 -4
  82. package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
  83. package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
  84. package/dist/src/client/ClientManager.d.ts +30 -11
  85. package/dist/src/client/ClientManager.d.ts.map +1 -1
  86. package/dist/src/client/actors/platformClassesInit.d.ts +6 -0
  87. package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
  88. package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
  89. package/dist/src/client/actors/saveConfig.d.ts +13 -6
  90. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  91. package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
  92. package/dist/src/db/migrateLegacyItemStorageRows.d.ts +57 -0
  93. package/dist/src/db/migrateLegacyItemStorageRows.d.ts.map +1 -0
  94. package/dist/src/db/read/getCompositeChildPaths.d.ts +13 -0
  95. package/dist/src/db/read/getCompositeChildPaths.d.ts.map +1 -0
  96. package/dist/src/db/read/getItems.d.ts.map +1 -1
  97. package/dist/src/db/read/getPublishPayload.d.ts +1 -1
  98. package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
  99. package/dist/src/db/read/getPublishUploads.d.ts +32 -3
  100. package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
  101. package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
  102. package/dist/src/db/write/claimUnpublishedDrafts.d.ts +2 -0
  103. package/dist/src/db/write/claimUnpublishedDrafts.d.ts.map +1 -1
  104. package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts +44 -0
  105. package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts.map +1 -0
  106. package/dist/src/db/write/createNewItem.d.ts.map +1 -1
  107. package/dist/src/eventBus.d.ts +5 -0
  108. package/dist/src/eventBus.d.ts.map +1 -1
  109. package/dist/src/events/files/download.d.ts.map +1 -1
  110. package/dist/src/helpers/FileManager/BaseFileManager.d.ts +3 -3
  111. package/dist/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
  112. package/dist/src/helpers/FileManager/IFileManager.d.ts +25 -1
  113. package/dist/src/helpers/FileManager/IFileManager.d.ts.map +1 -1
  114. package/dist/src/helpers/arweaveContentTags.d.ts +33 -0
  115. package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -0
  116. package/dist/src/helpers/cachedPropertyRefresh.d.ts +12 -0
  117. package/dist/src/helpers/cachedPropertyRefresh.d.ts.map +1 -0
  118. package/dist/src/helpers/compositeUpload.d.ts +28 -0
  119. package/dist/src/helpers/compositeUpload.d.ts.map +1 -0
  120. package/dist/src/helpers/db.d.ts +3 -0
  121. package/dist/src/helpers/db.d.ts.map +1 -1
  122. package/dist/src/helpers/easSdk.d.ts +16 -0
  123. package/dist/src/helpers/easSdk.d.ts.map +1 -0
  124. package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts +16 -0
  125. package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts.map +1 -0
  126. package/dist/src/helpers/entity/entityDestroy.d.ts +2 -0
  127. package/dist/src/helpers/entity/entityDestroy.d.ts.map +1 -1
  128. package/dist/src/helpers/itemStorageFile.d.ts +57 -0
  129. package/dist/src/helpers/itemStorageFile.d.ts.map +1 -0
  130. package/dist/src/helpers/mapWithConcurrency.d.ts +7 -0
  131. package/dist/src/helpers/mapWithConcurrency.d.ts.map +1 -0
  132. package/dist/src/helpers/mimeTypes.d.ts +43 -0
  133. package/dist/src/helpers/mimeTypes.d.ts.map +1 -0
  134. package/dist/src/helpers/ownership.d.ts +28 -4
  135. package/dist/src/helpers/ownership.d.ts.map +1 -1
  136. package/dist/src/helpers/property/index.d.ts +37 -51
  137. package/dist/src/helpers/property/index.d.ts.map +1 -1
  138. package/dist/src/helpers/publishConfig.d.ts +27 -8
  139. package/dist/src/helpers/publishConfig.d.ts.map +1 -1
  140. package/dist/src/helpers/storageDir.d.ts +16 -0
  141. package/dist/src/helpers/storageDir.d.ts.map +1 -0
  142. package/dist/src/helpers/tabCoordinator.d.ts +5 -1
  143. package/dist/src/helpers/tabCoordinator.d.ts.map +1 -1
  144. package/dist/src/helpers/tabEvents.d.ts +23 -2
  145. package/dist/src/helpers/tabEvents.d.ts.map +1 -1
  146. package/dist/src/helpers/updateSchema.d.ts +5 -0
  147. package/dist/src/helpers/updateSchema.d.ts.map +1 -1
  148. package/dist/src/helpers/waitForEntityIdle.d.ts +5 -0
  149. package/dist/src/helpers/waitForEntityIdle.d.ts.map +1 -1
  150. package/dist/src/imports/json.d.ts +9 -0
  151. package/dist/src/imports/json.d.ts.map +1 -1
  152. package/dist/src/index.d.ts +11 -7
  153. package/dist/src/index.d.ts.map +1 -1
  154. package/dist/src/interfaces/IItem.d.ts +7 -1
  155. package/dist/src/interfaces/IItem.d.ts.map +1 -1
  156. package/dist/src/node/db/Db.d.ts.map +1 -1
  157. package/dist/src/node/helpers/PathResolver.d.ts.map +1 -1
  158. package/dist/src/node/index.d.ts +8 -1
  159. package/dist/src/node/index.d.ts.map +1 -1
  160. package/dist/src/platform/index.browser.d.ts +2 -0
  161. package/dist/src/platform/index.browser.d.ts.map +1 -1
  162. package/dist/src/platform/index.node.d.ts +2 -0
  163. package/dist/src/platform/index.node.d.ts.map +1 -1
  164. package/dist/src/types/fileManager.d.ts +3 -14
  165. package/dist/src/types/fileManager.d.ts.map +1 -1
  166. package/dist/src/types/index.d.ts +12 -1
  167. package/dist/src/types/index.d.ts.map +1 -1
  168. package/dist/src/types/machines.d.ts +2 -0
  169. package/dist/src/types/machines.d.ts.map +1 -1
  170. package/dist/src/types/property.d.ts +8 -0
  171. package/dist/src/types/property.d.ts.map +1 -1
  172. package/dist/src/types/publish.d.ts +14 -0
  173. package/dist/src/types/publish.d.ts.map +1 -1
  174. package/dist/{syncDbWithEas-BU-jZ5bJ.js → syncDbWithEas-Bv2Fe38J.js} +17 -17
  175. package/dist/{syncDbWithEas-BU-jZ5bJ.js.map → syncDbWithEas-Bv2Fe38J.js.map} +1 -1
  176. package/dist/tabEvents-9s2WCYw-.js +335 -0
  177. package/dist/tabEvents-9s2WCYw-.js.map +1 -0
  178. package/dist/tolerantWebAccess-iUu80pAP.js +551 -0
  179. package/dist/tolerantWebAccess-iUu80pAP.js.map +1 -0
  180. package/dist/{updateSeedRevokedAt-iLpzae1i.js → updateSeedRevokedAt-zBfi931j.js} +3 -3
  181. package/dist/{updateSeedRevokedAt-iLpzae1i.js.map → updateSeedRevokedAt-zBfi931j.js.map} +1 -1
  182. package/package.json +11 -12
  183. package/src/node/easLodashRegister.js +4 -0
  184. package/dist/BasePathResolver-BdxCSIzn.js +0 -679
  185. package/dist/BasePathResolver-BdxCSIzn.js.map +0 -1
  186. package/dist/ClientManager-DDA9956Y.js.map +0 -1
  187. package/dist/Item-D4FigtYS.js.map +0 -1
  188. package/dist/ModelProperty-BDt6AU2h.js.map +0 -1
  189. package/dist/Schema-B8xqLWkL.js.map +0 -1
  190. package/dist/cjs/ModelProperty-B-cZm1yU.js +0 -1977
  191. package/dist/cjs/ModelProperty-B-cZm1yU.js.map +0 -1
  192. package/dist/cjs/Schema-STP972Xw.js +0 -5256
  193. package/dist/cjs/Schema-STP972Xw.js.map +0 -1
  194. package/dist/cjs/SchemaValidationService-CKNW1bKg.js +0 -778
  195. package/dist/cjs/SchemaValidationService-CKNW1bKg.js.map +0 -1
  196. package/dist/cjs/backfillMetadataPropertyIds-CP3y1skt.js +0 -89
  197. package/dist/cjs/backfillMetadataPropertyIds-CP3y1skt.js.map +0 -1
  198. package/dist/cjs/eas-CAnUC4wd.js +0 -138
  199. package/dist/cjs/eas-CAnUC4wd.js.map +0 -1
  200. package/dist/cjs/getItem-CWgb3e9G.js +0 -58
  201. package/dist/cjs/getItem-CWgb3e9G.js.map +0 -1
  202. package/dist/cjs/getPublishPayload-BsZWiJ03.js +0 -1603
  203. package/dist/cjs/getPublishPayload-BsZWiJ03.js.map +0 -1
  204. package/dist/cjs/getPublishUploads-BQr8aMVP.js +0 -495
  205. package/dist/cjs/getPublishUploads-BQr8aMVP.js.map +0 -1
  206. package/dist/cjs/index-nA5PxBSs.js +0 -70
  207. package/dist/cjs/index-nA5PxBSs.js.map +0 -1
  208. package/dist/cjs/index-usKaANAq.js +0 -22171
  209. package/dist/cjs/index-usKaANAq.js.map +0 -1
  210. package/dist/cjs/index.browser-BSf8HpnU.js +0 -22
  211. package/dist/cjs/index.browser-BSf8HpnU.js.map +0 -1
  212. package/dist/cjs/property-xMwPhQ5N.js +0 -327
  213. package/dist/cjs/property-xMwPhQ5N.js.map +0 -1
  214. package/dist/cjs/publishDraftGraph-BRWlR1KO.js +0 -403
  215. package/dist/cjs/publishDraftGraph-BRWlR1KO.js.map +0 -1
  216. package/dist/cjs/resetAmbiguousLegacyItemData-8nfkj9rz.js +0 -116
  217. package/dist/cjs/resetAmbiguousLegacyItemData-8nfkj9rz.js.map +0 -1
  218. package/dist/cjs/resolveModelForSyncedSeed-kYRuDWYw.js +0 -49
  219. package/dist/cjs/resolveModelForSyncedSeed-kYRuDWYw.js.map +0 -1
  220. package/dist/cjs/syncDbWithEas-DqRhV8o2.js +0 -886
  221. package/dist/cjs/syncDbWithEas-DqRhV8o2.js.map +0 -1
  222. package/dist/db-CQt-gcAl.js.map +0 -1
  223. package/dist/eas-CpOD74Ke.js.map +0 -1
  224. package/dist/getPublishPayload-Xl3sRkR-.js.map +0 -1
  225. package/dist/getPublishUploads-DHP01KgU.js +0 -484
  226. package/dist/getPublishUploads-DHP01KgU.js.map +0 -1
  227. package/dist/index-BGt4_oPO.js +0 -31
  228. package/dist/index-BGt4_oPO.js.map +0 -1
  229. package/dist/index-CSOQFAOn.js.map +0 -1
  230. package/dist/index-Xl1_4Lmb.js.map +0 -1
  231. package/dist/index.browser-CjTe0vt1.js.map +0 -1
  232. package/dist/index.node-Cfa7npS6.js.map +0 -1
  233. package/dist/main.cjs +0 -84
  234. package/dist/main.cjs.map +0 -1
  235. package/dist/property-BrXkY1Rg.js.map +0 -1
  236. package/dist/tabEvents-BZJMY5uK.js +0 -1344
  237. package/dist/tabEvents-BZJMY5uK.js.map +0 -1
  238. package/dist/tolerantWebAccess-BZ2RA_mb.js +0 -160
  239. package/dist/tolerantWebAccess-BZ2RA_mb.js.map +0 -1
@@ -1,12 +1,13 @@
1
- import { t as BasePathResolver, a as BaseDb, b as appState, B as BaseFileManager } from './BasePathResolver-BdxCSIzn.js';
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 { q as withDbTabLock, u as notifyFilesWrittenOutsideCache, v as withSeedDbLock } from './tabEvents-BZJMY5uK.js';
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-CSOQFAOn.js';
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
- const arrayBuffer = await response.arrayBuffer();
256
- if (arrayBuffer.byteLength > 0) {
257
- return { arrayBuffer, networkFailedEverywhere: false };
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 { networkFailedEverywhere: !answered };
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
- const { arrayBuffer, networkFailedEverywhere } = await fetchRaw(baseUrls, transactionId);
277
- if (!arrayBuffer) {
278
- if (networkFailedEverywhere) {
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
- let dataString;
287
- const isBinaryData = isBinary(arrayBuffer);
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
- if (dataString && dataString.startsWith('===FILE_SEPARATOR===')) {
294
- const dataStringParts = dataString
295
- .split('===FILE_SEPARATOR===')
296
- .slice(1);
297
- if (dataStringParts.length % 2 !== 0) {
298
- throw new Error('Input array must have an even number of elements.');
299
- }
300
- for (let i = 0; i < dataStringParts.length; i += 2) {
301
- const contentType = dataStringParts[i];
302
- const content = dataStringParts[i + 1];
303
- const encoder = new TextEncoder();
304
- if (contentType === 'html') {
305
- const fileName = `${transactionId}.html`;
306
- const buffer = encoder.encode(content);
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 (!dataString && arrayBuffer) {
318
- await saveBufferToOPFS(getFilesPath(filesRoot, 'images', transactionId), new Uint8Array(arrayBuffer));
319
- continue;
493
+ else if (fetched.networkFailedEverywhere) {
494
+ globalThis.postMessage({
495
+ message: 'excludeTransaction',
496
+ transactionId,
497
+ });
320
498
  }
321
- if (!dataString) {
322
- continue;
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
- let contentType = identifyString(dataString);
325
- if (contentType !== 'json' &&
326
- contentType !== 'base64' &&
327
- contentType !== 'html') {
328
- const possibleImageType = getDataTypeFromString(dataString);
329
- if (!possibleImageType) {
330
- continue;
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
- contentType = possibleImageType;
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
- catch (error) {
345
- globalThis.postMessage({
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
- const bufferUint8Array = new Uint8Array(buffer);
352
- // Extract the file extension from the URL
353
- const extensionMatch = url.match(/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i);
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
- const mimeType = getMimeType(dataString);
364
- let fileExtension = mimeType;
365
- if (fileExtension && fileExtension?.startsWith('image')) {
366
- fileExtension = fileExtension.replace('image/', '');
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
- let fileName = transactionId;
369
- if (contentType === 'base64') {
370
- if (fileExtension) {
371
- fileName += `.${fileExtension}`;
372
- }
373
- // Remove the Base64 header if it exists (e.g., "data:image/png;base64,")
374
- const base64Data = dataString.split(',').pop() || '';
375
- // Decode the Base64 string to binary
376
- const binaryString = atob(base64Data);
377
- const length = binaryString.length;
378
- const binaryData = new Uint8Array(length);
379
- for (let i = 0; i < length; i++) {
380
- binaryData[i] = binaryString.charCodeAt(i);
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 saveBufferToOPFS(getFilesPath(filesRoot, 'images', fileName), binaryData);
593
+ buffer = await response.arrayBuffer();
383
594
  }
384
- if (contentType === 'html') {
385
- fileName += '.html';
386
- const encoder = new TextEncoder();
387
- const buffer = encoder.encode(dataString);
388
- await saveBufferToOPFS(getFilesPath(filesRoot, 'html', fileName), buffer);
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
- if (contentType === 'json') {
391
- fileName += '.json';
392
- const encoder = new TextEncoder();
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 { debug, filesRoot } = e.data;
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
- await downloadFiles({ ...e.data, filesRoot });
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.workersArchive = [];
416
- this.downloadAll = async ({ transactionIds, arweaveHost, arweaveBaseUrls, excludedTransactions }) => {
417
- if (this.workersArchive.length > 0) {
418
- for (let i = 0; i < this.workersArchive.length; i++) {
419
- this.workersArchive[i].terminate();
420
- delete this.workersArchive[i];
421
- }
422
- this.workersArchive = [];
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
- const worker = new Worker(this.workerBlobUrl);
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
- logger$6('filesDownload main thread onmessage', e.data);
431
- if (e.data.message === 'excludeTransaction') {
432
- localExcludedTransactions.add(e.data.transactionId);
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 (e.data.message === 'fileSaved') {
435
- savedPaths.push(e.data.filePath);
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 (e.data.done) {
438
- addExcludedTransactions(localExcludedTransactions)
439
- // The worker wrote to OPFS directly; refresh caches here and in other tabs.
440
- .then(() => notifyFilesWrittenOutsideCache(savedPaths))
441
- .then(() => {
442
- resolve(e.data);
443
- })
444
- .catch((error) => {
445
- reject(error);
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 (e.data.error) {
449
- reject(e.data.error);
803
+ if (data.done) {
804
+ succeed();
450
805
  }
451
806
  };
452
- worker.postMessage({
453
- transactionIds,
454
- arweaveHost,
455
- arweaveBaseUrls,
456
- debug: logger$6.enabled,
457
- filesRoot: BaseFileManager.getWorkingDir(),
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-BZ2RA_mb.js');
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({ transactionIds, arweaveHost, arweaveBaseUrls, excludedTransactions, }) {
1489
+ async downloadAllFiles(params) {
1108
1490
  const fileDownloader = new FileDownloader();
1109
- await fileDownloader.downloadAll({ transactionIds, arweaveHost, arweaveBaseUrls, excludedTransactions });
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-BZ2RA_mb.js');
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
- await zenfs.writeFile(filePath, writeContent);
1354
- await this.waitForFileWithContent(filePath, 100, 5000);
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-DuTPcv8t.js');
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-Ci6PM_vC.js');
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-CjTe0vt1.js.map
4528
+ //# sourceMappingURL=index.browser-DBhxRoe8.js.map