@seedprotocol/sdk 0.2.46 → 0.2.48

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 (66) hide show
  1. package/dist/{ArweaveClient-CoFomTCN.js → ArweaveClient-Dcqcpsq4.js} +2 -2
  2. package/dist/{ArweaveClient-DQrXvrNN.js.map → ArweaveClient-Dcqcpsq4.js.map} +1 -1
  3. package/dist/{ArweaveClient-DQrXvrNN.js → ArweaveClient-Dh7LRIqD.js} +2 -2
  4. package/dist/{ArweaveClient-CoFomTCN.js.map → ArweaveClient-Dh7LRIqD.js.map} +1 -1
  5. package/dist/{Db-5C5VqRWb.js → Db-BhLWMUOe.js} +7 -7
  6. package/dist/Db-BhLWMUOe.js.map +1 -0
  7. package/dist/{Db-Dv890NJZ.js → Db-DrcygP77.js} +3 -4
  8. package/dist/{Db-Dv890NJZ.js.map → Db-DrcygP77.js.map} +1 -1
  9. package/dist/{EasClient-CBu9ENAi.js → EasClient-A1xC7Gm-.js} +2 -2
  10. package/dist/{EasClient-sCbTHcO8.js.map → EasClient-A1xC7Gm-.js.map} +1 -1
  11. package/dist/{EasClient-sCbTHcO8.js → EasClient-m2mXad59.js} +2 -2
  12. package/dist/{EasClient-CBu9ENAi.js.map → EasClient-m2mXad59.js.map} +1 -1
  13. package/dist/{FileManager-COOp89Mj.js → FileManager-Dv2zn1RW.js} +12 -2
  14. package/dist/FileManager-Dv2zn1RW.js.map +1 -0
  15. package/dist/{Item-BmPvgaG4.js → Item-D0rGmrZg.js} +3 -4
  16. package/dist/{Item-BmPvgaG4.js.map → Item-D0rGmrZg.js.map} +1 -1
  17. package/dist/{ItemProperty-BW4k1h0o.js → ItemProperty-D0tkeKx1.js} +3 -4
  18. package/dist/{ItemProperty-BW4k1h0o.js.map → ItemProperty-D0tkeKx1.js.map} +1 -1
  19. package/dist/{QueryClient-BBaE-LKI.js → QueryClient-C-hFMF2j.js} +2 -2
  20. package/dist/{QueryClient-BBaE-LKI.js.map → QueryClient-C-hFMF2j.js.map} +1 -1
  21. package/dist/{QueryClient-C-ZzUnWq.js → QueryClient-C02sPZ-K.js} +2 -2
  22. package/dist/{QueryClient-C-ZzUnWq.js.map → QueryClient-C02sPZ-K.js.map} +1 -1
  23. package/dist/bin.js +5 -5
  24. package/dist/{constants-Dgv-tSO3.js → constants-C03RQQht.js} +12 -5
  25. package/dist/constants-C03RQQht.js.map +1 -0
  26. package/dist/{index-CGeSKilQ.js → index-B6FQruEq.js} +1069 -134
  27. package/dist/index-B6FQruEq.js.map +1 -0
  28. package/dist/{index-CPxn1SR4.js → index-C93o7-zP.js} +3 -4
  29. package/dist/index-C93o7-zP.js.map +1 -0
  30. package/dist/main.js +2 -3
  31. package/dist/main.js.map +1 -1
  32. package/dist/{seed.schema.config-BXWvwsZQ.js → seed.schema.config-ClOsMOKS.js} +3 -4
  33. package/dist/seed.schema.config-ClOsMOKS.js.map +1 -0
  34. package/dist/src/BaseFileManager.ts +14 -2
  35. package/dist/src/FileManager.ts +14 -1
  36. package/dist/src/ImageResizer.ts +2 -5
  37. package/dist/src/actors.ts +34 -272
  38. package/dist/src/client.ts +19 -0
  39. package/dist/src/download.ts +5 -15
  40. package/dist/src/helpers.ts +200 -83
  41. package/dist/src/migrate.ts +2 -2
  42. package/dist/types/src/browser/db/Db.d.ts +1 -1
  43. package/dist/types/src/browser/db/Db.d.ts.map +1 -1
  44. package/dist/types/src/browser/helpers/FileManager.d.ts +10 -0
  45. package/dist/types/src/browser/helpers/FileManager.d.ts.map +1 -1
  46. package/dist/types/src/browser/helpers/index.d.ts +1 -2
  47. package/dist/types/src/browser/helpers/index.d.ts.map +1 -1
  48. package/dist/types/src/browser/workers/ImageResizer.d.ts.map +1 -1
  49. package/dist/types/src/client.d.ts +1 -0
  50. package/dist/types/src/client.d.ts.map +1 -1
  51. package/dist/types/src/events/files/download.d.ts.map +1 -1
  52. package/dist/types/src/helpers/FileManager/BaseFileManager.d.ts +5 -2
  53. package/dist/types/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
  54. package/dist/types/src/node/helpers/FileManager.d.ts +2 -0
  55. package/dist/types/src/node/helpers/FileManager.d.ts.map +1 -1
  56. package/dist/types/src/services/internal/helpers.d.ts +0 -2
  57. package/dist/types/src/services/internal/helpers.d.ts.map +1 -1
  58. package/package.json +3 -3
  59. package/dist/Db-5C5VqRWb.js.map +0 -1
  60. package/dist/FileManager-AhAsy_F_.js +0 -794
  61. package/dist/FileManager-AhAsy_F_.js.map +0 -1
  62. package/dist/FileManager-COOp89Mj.js.map +0 -1
  63. package/dist/constants-Dgv-tSO3.js.map +0 -1
  64. package/dist/index-CGeSKilQ.js.map +0 -1
  65. package/dist/index-CPxn1SR4.js.map +0 -1
  66. package/dist/seed.schema.config-BXWvwsZQ.js.map +0 -1
@@ -2,7 +2,7 @@ import { immerable, produce, enableMapSet } from 'immer';
2
2
  import 'reflect-metadata';
3
3
  import { Type } from '@sinclair/typebox';
4
4
  import { fromCallback, setup, assign, waitFor, createActor, raise } from 'xstate';
5
- import { b as BaseQueryClient, a as BaseEasClient, G as GLOBAL_INITIALIZING_INTERNAL_SERVICE_READY, f as GLOBAL_INITIALIZING_SEND_CONFIG, g as GLOBAL_INITIALIZING_CREATE_ALL_ITEMS_SERVICES, h as GLOBAL_ADDING_MODELS_TO_DB_SUCCESS, D as DB_CHECK_STATUS_UPDATE_PATHS, i as DB_CHECK_STATUS_EXISTS, d as BROWSER_FS_TOP_DIR, j as DB_CREATING_SUCCESS, k as DB_VALIDATING_SUCCESS, l as DB_VALIDATING_WAIT, m as DB_MIGRATING_SUCCESS, n as DB_WAITING_FOR_FILES_RECEIVED, M as MachineIds, o as DB_MIGRATING_WAIT, p as DbState, I as INTERNAL_VALIDATING_INPUT_SUCCESS, q as ARWEAVE_HOST, B as BaseArweaveClient, c as BaseFileManager, r as INTERNAL_CONFIGURING_FS_SUCCESS, s as INTERNAL_SAVING_CONFIG_SUCCESS, t as INTERNAL_LOADING_APP_DB_SUCCESS, u as DB_ON_SNAPSHOT, v as DB_NAME_APP, w as InternalState, P as PublishMachineStates, x as GlobalState, y as INTERNAL_DATA_TYPES, z as ImageSize, C as internalPropertyNames, V as VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA, F as defaultAttestationData } from './constants-Dgv-tSO3.js';
5
+ import { b as BaseQueryClient, a as BaseEasClient, G as GLOBAL_INITIALIZING_INTERNAL_SERVICE_READY, f as GLOBAL_INITIALIZING_SEND_CONFIG, g as GLOBAL_INITIALIZING_CREATE_ALL_ITEMS_SERVICES, h as GLOBAL_ADDING_MODELS_TO_DB_SUCCESS, D as DB_CHECK_STATUS_UPDATE_PATHS, i as DB_CHECK_STATUS_EXISTS, d as BROWSER_FS_TOP_DIR, j as DB_CREATING_SUCCESS, k as DB_VALIDATING_SUCCESS, l as DB_VALIDATING_WAIT, c as BaseFileManager, m as DB_MIGRATING_SUCCESS, n as DB_WAITING_FOR_FILES_RECEIVED, M as MachineIds, o as DB_MIGRATING_WAIT, p as DbState, I as INTERNAL_VALIDATING_INPUT_SUCCESS, q as ARWEAVE_HOST, B as BaseArweaveClient, r as INTERNAL_CONFIGURING_FS_SUCCESS, s as INTERNAL_SAVING_CONFIG_SUCCESS, t as INTERNAL_LOADING_APP_DB_SUCCESS, u as DB_ON_SNAPSHOT, v as DB_NAME_APP, w as InternalState, P as PublishMachineStates, x as GlobalState, y as INTERNAL_DATA_TYPES, z as ImageSize, C as internalPropertyNames, V as VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA, F as defaultAttestationData } from './constants-C03RQQht.js';
6
6
  import debug from 'debug';
7
7
  import pluralize from 'pluralize';
8
8
  import { sqliteTable, text, int, unique } from 'drizzle-orm/sqlite-core';
@@ -13,7 +13,6 @@ import * as nanoIdDictionary from 'nanoid-dictionary';
13
13
  import fs from '@zenfs/core';
14
14
  import { throttle, camelCase, startCase, orderBy } from 'lodash-es';
15
15
  import { createBrowserInspector } from '@statelyai/inspect';
16
- import * as fsNode from 'node:fs';
17
16
  import path from 'path';
18
17
  import Arweave from 'arweave';
19
18
  import { BehaviorSubject } from 'rxjs';
@@ -1345,7 +1344,7 @@ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
1345
1344
  if (!models$1) {
1346
1345
  return;
1347
1346
  }
1348
- const { models: SeedModels } = await import('./seed.schema.config-BXWvwsZQ.js');
1347
+ const { models: SeedModels } = await import('./seed.schema.config-ClOsMOKS.js');
1349
1348
  const allModels = {
1350
1349
  ...SeedModels,
1351
1350
  ...models$1,
@@ -1522,57 +1521,962 @@ const validate = fromCallback(({ sendBack, input: { context } }) => {
1522
1521
  });
1523
1522
  });
1524
1523
 
1525
- const logger$k = debug('app:helpers:files');
1526
- /**
1527
- * Waits for a file to exist at the specified path.
1528
- * @param {string} filePath - The path of the file to check.
1529
- * @param {number} interval - The interval in milliseconds between checks (default: 500ms).
1530
- * @param {number} timeout - The timeout in milliseconds to wait for the file to exist (default: 10s).
1531
- * @returns {Promise<boolean>} - Resolves to true if the file exists within the timeout period, otherwise false.
1532
- */
1533
- const waitForFile = (filePath, interval = 1000, timeout = 60000) => {
1534
- return new Promise((resolve, reject) => {
1535
- const startTime = Date.now();
1536
- let isBusy = false;
1537
- const _interval = setInterval(async () => {
1538
- logger$k('waitForFile', filePath);
1539
- if (isBusy) {
1540
- return;
1524
+ const saveAppState = async (key, value) => {
1525
+ const appDb = BaseDb.getAppDb();
1526
+ await appDb
1527
+ .insert(appState)
1528
+ .values({
1529
+ key,
1530
+ value,
1531
+ })
1532
+ .onConflictDoUpdate({
1533
+ target: appState.key,
1534
+ set: {
1535
+ value,
1536
+ },
1537
+ });
1538
+ };
1539
+
1540
+ var filesDownload = `(
1541
+ ${function () {
1542
+ const identifyString = (str) => {
1543
+ try {
1544
+ JSON.parse(str);
1545
+ return 'json';
1546
+ }
1547
+ catch (e) {
1548
+ // Not JSON
1549
+ }
1550
+ if (!str) {
1551
+ return;
1552
+ }
1553
+ if (str.trim().startsWith('<') && str.trim().endsWith('>')) {
1554
+ return 'html';
1555
+ }
1556
+ // Simple markdown checks (very naive)
1557
+ if (/^#{1,6}\s|^-{3,}|\*{3,}|^-{1,2}\s|\*\s/.test(str)) {
1558
+ return 'markdown';
1559
+ }
1560
+ if (/^data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/]+={0,2}$/.test(str)) {
1561
+ return 'base64';
1562
+ }
1563
+ // Default to plain text if unsure
1564
+ return 'text';
1565
+ };
1566
+ const getMimeType = (base64) => {
1567
+ if (!base64) {
1568
+ return null;
1569
+ }
1570
+ const result = base64.match(/^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,/);
1571
+ if (result && result.length > 1) {
1572
+ return result[1];
1573
+ }
1574
+ else {
1575
+ return null; // MIME type could not be determined
1576
+ }
1577
+ };
1578
+ const getDataTypeFromString = (data) => {
1579
+ const nonImageBase64Regex = /^(?!data:image\/(?:jpeg|png|gif|bmp|webp);base64,)[A-Za-z0-9+/=]+$/;
1580
+ if (nonImageBase64Regex.test(data)) {
1581
+ return 'base64';
1582
+ }
1583
+ // Regular expression for base64 (simple version, checking for base64 format)
1584
+ const imageBase64Regex = /^data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/]+={0,2}$/;
1585
+ if (imageBase64Regex.test(data)) {
1586
+ return 'imageBase64';
1587
+ }
1588
+ // Regular expression for URL (simple version, checking for common URL format)
1589
+ const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
1590
+ if (urlRegex.test(data)) {
1591
+ return 'url';
1592
+ }
1593
+ return null;
1594
+ };
1595
+ const isBinary = (arrayBuffer) => {
1596
+ const view = new Uint8Array(arrayBuffer);
1597
+ let nonTextCount = 0;
1598
+ const threshold = 0.2; // Adjust as needed (e.g., 20% non-text implies binary)
1599
+ for (let i = 0; i < view.length; i++) {
1600
+ const byte = view[i];
1601
+ // ASCII printable characters (32-126) and common whitespace (9, 10, 13)
1602
+ if ((byte >= 32 && byte <= 126) || // Printable ASCII
1603
+ byte === 9 || byte === 10 || byte === 13 // Tab, LF, CR
1604
+ ) {
1605
+ continue;
1541
1606
  }
1542
- isBusy = true;
1543
- if (fs.existsSync(filePath) && fsNode.existsSync(filePath)) {
1544
- clearInterval(_interval);
1545
- resolve(true);
1607
+ nonTextCount++;
1608
+ if (nonTextCount / view.length > threshold) {
1609
+ return true; // More than threshold are non-text bytes
1546
1610
  }
1547
- if (Date.now() - startTime >= timeout) {
1548
- clearInterval(_interval);
1549
- reject(new Error('Timeout exceeded while waiting for file'));
1611
+ }
1612
+ return false; // Fewer than threshold are non-text bytes
1613
+ };
1614
+ const saveBufferToOPFS = async (filePath, buffer) => {
1615
+ // Access the OPFS root directory
1616
+ const rootHandle = await navigator.storage.getDirectory();
1617
+ // Split the filePath into directory segments and file name
1618
+ const segments = filePath.split('/').filter(Boolean);
1619
+ const fileName = segments.pop(); // Extract the file name
1620
+ if (!fileName) {
1621
+ throw new Error('Invalid file path: No file name provided.');
1622
+ }
1623
+ // Traverse or create directories as needed
1624
+ let currentDirHandle = rootHandle;
1625
+ for (const segment of segments) {
1626
+ currentDirHandle = await currentDirHandle.getDirectoryHandle(segment, { create: true });
1627
+ }
1628
+ // Create or open the file in OPFS
1629
+ const fileHandleAsync = await currentDirHandle.getFileHandle(fileName, { create: true });
1630
+ const fileHandle = await fileHandleAsync.createSyncAccessHandle();
1631
+ // Write the buffer to the file
1632
+ fileHandle.write(buffer);
1633
+ fileHandle.flush();
1634
+ fileHandle.close();
1635
+ };
1636
+ const downloadFiles = async ({ transactionIds, arweaveHost, }) => {
1637
+ let arrayBuffer;
1638
+ for (const transactionId of transactionIds) {
1639
+ try {
1640
+ const response = await fetch(`https://${arweaveHost}/raw/${transactionId}`);
1641
+ arrayBuffer = await response.arrayBuffer();
1550
1642
  }
1551
- isBusy = false;
1552
- }, interval);
1553
- // retry(
1554
- // {
1555
- // times: Math.ceil(timeout / interval),
1556
- // interval: interval,
1557
- // },
1558
- // (callback: Function) => {
1559
- // if (fs.existsSync(filePath) && fsNode.existsSync(filePath)) {
1560
- // return callback(null, true) // File exists, finish with success
1561
- // }
1562
- // if (Date.now() - startTime >= timeout) {
1563
- // return callback(new Error('Timeout exceeded while waiting for file'))
1564
- // }
1565
- // callback(new Error('File does not exist yet')) // Retry with this error
1566
- // },
1567
- // (err: Error, result: boolean) => {
1568
- // if (err) {
1569
- // return resolve(false) // Resolve as false if timeout or error occurs
1570
- // }
1571
- // resolve(result) // Resolve as true if file exists
1643
+ catch (error) {
1644
+ console.log(`[filesDownload] transaction ${transactionId} data not found`, error);
1645
+ globalThis.postMessage({
1646
+ message: 'excludeTransaction',
1647
+ transactionId,
1648
+ });
1649
+ continue;
1650
+ }
1651
+ let dataString;
1652
+ const isBinaryData = isBinary(arrayBuffer);
1653
+ if (!isBinaryData) {
1654
+ const decoder = new TextDecoder('utf-8');
1655
+ const text = decoder.decode(arrayBuffer);
1656
+ dataString = text;
1657
+ }
1658
+ if (!dataString && !arrayBuffer) {
1659
+ console.log(`[filesDownload] transaction ${transactionId} data not found`);
1660
+ }
1661
+ if (dataString && dataString.startsWith('===FILE_SEPARATOR===')) {
1662
+ const dataStringParts = dataString
1663
+ .split('===FILE_SEPARATOR===')
1664
+ .slice(1);
1665
+ if (dataStringParts.length % 2 !== 0) {
1666
+ throw new Error('Input array must have an even number of elements.');
1667
+ }
1668
+ for (let i = 0; i < dataStringParts.length; i += 2) {
1669
+ const contentType = dataStringParts[i];
1670
+ const content = dataStringParts[i + 1];
1671
+ const encoder = new TextEncoder();
1672
+ if (contentType === 'html') {
1673
+ const fileName = `${transactionId}.html`;
1674
+ const buffer = encoder.encode(content);
1675
+ saveBufferToOPFS(`/files/html/${fileName}`, buffer);
1676
+ }
1677
+ if (contentType === 'json') {
1678
+ const fileName = `${transactionId}.json`;
1679
+ const buffer = encoder.encode(content);
1680
+ saveBufferToOPFS(`/files/json/${fileName}`, buffer);
1681
+ }
1682
+ }
1683
+ continue;
1684
+ }
1685
+ if (!dataString && arrayBuffer) {
1686
+ saveBufferToOPFS(`/files/images/${transactionId}`, new Uint8Array(arrayBuffer));
1687
+ continue;
1688
+ }
1689
+ if (!dataString) {
1690
+ continue;
1691
+ }
1692
+ let contentType = identifyString(dataString);
1693
+ if (contentType !== 'json' &&
1694
+ contentType !== 'base64' &&
1695
+ contentType !== 'html') {
1696
+ const possibleImageType = getDataTypeFromString(dataString);
1697
+ if (!possibleImageType) {
1698
+ console.log(`[filesDownload] transaction ${transactionId} data not in expected format: ${possibleImageType}`);
1699
+ continue;
1700
+ }
1701
+ contentType = possibleImageType;
1702
+ }
1703
+ if (contentType === 'url') {
1704
+ const url = dataString;
1705
+ let buffer;
1706
+ try {
1707
+ const response = await fetch(url);
1708
+ buffer = await response.arrayBuffer();
1709
+ }
1710
+ catch (error) {
1711
+ console.log(`[filesDownload] transaction ${transactionId} value was url: ${dataString} but failed to fetch`, error);
1712
+ globalThis.postMessage({
1713
+ message: 'excludeTransaction',
1714
+ transactionId,
1715
+ });
1716
+ continue;
1717
+ }
1718
+ const bufferUint8Array = new Uint8Array(buffer);
1719
+ // Extract the file extension from the URL
1720
+ const extensionMatch = url.match(/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i);
1721
+ if (!extensionMatch) {
1722
+ throw new Error('Unable to determine the file extension from the URL.');
1723
+ }
1724
+ extensionMatch[0]; // e.g., ".jpg"
1725
+ // Set the file name (you can customize this)
1726
+ // const fileNameFromUrl = `${transactionId}${fileExtension}`
1727
+ await saveBufferToOPFS(`/files/images/${transactionId}`, bufferUint8Array);
1728
+ continue;
1729
+ }
1730
+ const mimeType = getMimeType(dataString);
1731
+ let fileExtension = mimeType;
1732
+ if (fileExtension && fileExtension?.startsWith('image')) {
1733
+ fileExtension = fileExtension.replace('image/', '');
1734
+ }
1735
+ let fileName = transactionId;
1736
+ if (contentType === 'base64') {
1737
+ if (fileExtension) {
1738
+ fileName += `.${fileExtension}`;
1739
+ }
1740
+ // Remove the Base64 header if it exists (e.g., "data:image/png;base64,")
1741
+ const base64Data = dataString.split(',').pop() || '';
1742
+ // Decode the Base64 string to binary
1743
+ const binaryString = atob(base64Data);
1744
+ const length = binaryString.length;
1745
+ const binaryData = new Uint8Array(length);
1746
+ for (let i = 0; i < length; i++) {
1747
+ binaryData[i] = binaryString.charCodeAt(i);
1748
+ }
1749
+ await saveBufferToOPFS(`/files/images/${fileName}`, binaryData);
1750
+ }
1751
+ if (contentType === 'html') {
1752
+ fileName += '.html';
1753
+ const encoder = new TextEncoder();
1754
+ const buffer = encoder.encode(dataString);
1755
+ await saveBufferToOPFS(`/files/html/${fileName}`, buffer);
1756
+ }
1757
+ if (contentType === 'json') {
1758
+ fileName += '.json';
1759
+ const encoder = new TextEncoder();
1760
+ const buffer = encoder.encode(dataString);
1761
+ await saveBufferToOPFS(`/files/json/${fileName}`, buffer);
1762
+ }
1763
+ }
1764
+ };
1765
+ onmessage = async (e) => {
1766
+ console.log({
1767
+ message: 'filesDownload onmessage',
1768
+ data: e.data,
1769
+ });
1770
+ await downloadFiles(e.data);
1771
+ globalThis.postMessage({
1772
+ message: 'filesDownload onmessage done',
1773
+ done: true,
1774
+ });
1775
+ };
1776
+ }.toString()}
1777
+ )()`;
1778
+
1779
+ class FileDownloader {
1780
+ constructor() {
1781
+ this.workersArchive = [];
1782
+ this.downloadAll = async ({ transactionIds, arweaveHost, excludedTransactions }) => {
1783
+ if (this.workersArchive.length > 0) {
1784
+ for (let i = 0; i < this.workersArchive.length; i++) {
1785
+ this.workersArchive[i].terminate();
1786
+ delete this.workersArchive[i];
1787
+ }
1788
+ this.workersArchive = [];
1789
+ }
1790
+ const worker = new Worker(this.workerBlobUrl);
1791
+ this.workersArchive.push(worker);
1792
+ const localExcludedTransactions = new Set(excludedTransactions);
1793
+ return new Promise((resolve, reject) => {
1794
+ worker.onmessage = (e) => {
1795
+ console.log('filesDownload main thread onmessage', e.data);
1796
+ if (e.data.message === 'excludeTransaction') {
1797
+ localExcludedTransactions.add(e.data.transactionId);
1798
+ }
1799
+ if (e.data.done) {
1800
+ saveAppState('excludedTransactions', JSON.stringify(Array.from(localExcludedTransactions)))
1801
+ .then(() => {
1802
+ resolve(e.data);
1803
+ })
1804
+ .catch((error) => {
1805
+ reject(error);
1806
+ });
1807
+ }
1808
+ if (e.data.error) {
1809
+ reject(e.data.error);
1810
+ }
1811
+ };
1812
+ worker.postMessage({
1813
+ transactionIds,
1814
+ arweaveHost,
1815
+ });
1816
+ });
1817
+ };
1818
+ this.cores = Math.min(navigator.hardwareConcurrency || 4, 4);
1819
+ this.workerBlobUrl = globalThis.URL.createObjectURL(new Blob([filesDownload], { type: 'application/javascript' }));
1820
+ }
1821
+ }
1822
+
1823
+ var imageResize = `(
1824
+ ${function () {
1825
+ async function listFilesInDirectory(directoryHandle) {
1826
+ const entries = [];
1827
+ for await (const [name, handle] of directoryHandle.entries()) {
1828
+ entries.push({
1829
+ name,
1830
+ kind: handle.kind,
1831
+ });
1832
+ }
1833
+ return entries;
1834
+ }
1835
+ const getFileHandle = async (path, rootHandle = null) => {
1836
+ // Split the path into segments
1837
+ const segments = path.split('/').filter(Boolean);
1838
+ // Start from the root directory if not provided
1839
+ if (!rootHandle) {
1840
+ rootHandle = await navigator.storage.getDirectory();
1841
+ }
1842
+ let currentHandle = rootHandle;
1843
+ // Traverse the path segments
1844
+ for (let i = 0; i < segments.length; i++) {
1845
+ const segment = segments[i];
1846
+ const isLastSegment = i === segments.length - 1;
1847
+ try {
1848
+ for await (const [name, handle] of currentHandle.entries()) {
1849
+ if (name !== segment) {
1850
+ continue;
1851
+ }
1852
+ if (isLastSegment) {
1853
+ if (handle.kind === 'file') {
1854
+ return handle; // Return the file handle if found
1855
+ }
1856
+ else {
1857
+ throw new Error(`Path '${path}' refers to a directory, not a file.`);
1858
+ }
1859
+ }
1860
+ else if (handle.kind === 'directory') {
1861
+ currentHandle = handle; // Traverse into the directory
1862
+ }
1863
+ else {
1864
+ throw new Error(`Invalid path segment '${segment}'`);
1865
+ }
1866
+ }
1867
+ }
1868
+ catch (err) {
1869
+ if (err instanceof Error && err.name === 'NotFoundError') {
1870
+ throw new Error(`Path '${path}' does not exist.`);
1871
+ }
1872
+ else {
1873
+ throw err;
1874
+ }
1875
+ }
1876
+ }
1877
+ throw new Error(`Path '${path}' could not be resolved.`);
1878
+ };
1879
+ async function getFileFromOPFS(path) {
1880
+ const fileHandleAsync = await getFileHandle(path);
1881
+ const file = await fileHandleAsync.getFile();
1882
+ return file;
1883
+ }
1884
+ const DEFAULT_CONFIG = {
1885
+ argorithm: 'null',
1886
+ processByHalf: true,
1887
+ quality: 0.5,
1888
+ maxWidth: 800,
1889
+ maxHeight: 600,
1890
+ debug: false,
1891
+ mimeType: 'image/jpeg',
1892
+ };
1893
+ function isIos() {
1894
+ if (typeof navigator === 'undefined')
1895
+ return false;
1896
+ if (!navigator.userAgent)
1897
+ return false;
1898
+ return /iPad|iPhone|iPod/.test(navigator.userAgent);
1899
+ }
1900
+ const getTargetHeight = (srcHeight, scale, config) => {
1901
+ return Math.min(Math.floor(srcHeight * scale), config.maxHeight);
1902
+ };
1903
+ const findMaxWidth = (config, canvas) => {
1904
+ //Let's find the max available width for scaled image
1905
+ const ratio = canvas.width / canvas.height;
1906
+ let mWidth = Math.min(canvas.width, config.maxWidth, ratio * config.maxHeight);
1907
+ if (config.maxSize &&
1908
+ config.maxSize > 0 &&
1909
+ config.maxSize < (canvas.width * canvas.height) / 1000)
1910
+ mWidth = Math.min(mWidth, Math.floor((config.maxSize * 1000) / canvas.height));
1911
+ if (!!config.scaleRatio)
1912
+ mWidth = Math.min(mWidth, Math.floor(config.scaleRatio * canvas.width));
1913
+ const rHeight = getTargetHeight(canvas.height, mWidth / canvas.width, config);
1914
+ // console.log(
1915
+ // 'browser-image-resizer: original image size = ' +
1916
+ // canvas.width +
1917
+ // ' px (width) X ' +
1918
+ // canvas.height +
1919
+ // ' px (height)'
1920
+ // );
1921
+ // console.log(
1922
+ // 'browser-image-resizer: scaled image size = ' +
1923
+ // mWidth +
1924
+ // ' px (width) X ' +
1925
+ // rHeight +
1926
+ // ' px (height)'
1927
+ // );
1928
+ if (mWidth <= 0) {
1929
+ mWidth = 1;
1930
+ console.warn("browser-image-resizer: image size is too small");
1931
+ }
1932
+ if (isIos() && mWidth * rHeight > 167777216) {
1933
+ console.error("browser-image-resizer: image size is too large for iOS WebKit.", mWidth, rHeight);
1934
+ throw new Error("browser-image-resizer: image size is too large for iOS WebKit.");
1935
+ }
1936
+ return mWidth;
1937
+ };
1938
+ /**
1939
+ * Hermite resize, multicore version - fast image resize/resample using Hermite filter.
1940
+ */
1941
+ const resample = (srcCanvas, destCanvas, config) => {
1942
+ return new Promise((resolve, reject) => {
1943
+ const ratio_h = srcCanvas.height / destCanvas.height;
1944
+ const cores = Math.min(navigator.hardwareConcurrency || 4, 4);
1945
+ //prepare source and target data for workers
1946
+ const ctx = srcCanvas.getContext('2d');
1947
+ if (!ctx)
1948
+ return reject('Canvas is empty (resample)');
1949
+ const data_part = [];
1950
+ const block_height = Math.ceil(srcCanvas.height / cores / 2) * 2;
1951
+ let end_y = -1;
1952
+ for (let c = 0; c < cores; c++) {
1953
+ //source
1954
+ const offset_y = end_y + 1;
1955
+ if (offset_y >= srcCanvas.height) {
1956
+ //size too small, nothing left for this core
1957
+ continue;
1958
+ }
1959
+ end_y = Math.min(offset_y + block_height - 1, srcCanvas.height - 1);
1960
+ const current_block_height = Math.min(block_height, srcCanvas.height - offset_y);
1961
+ console.log('browser-image-resizer: source split: ', '#' + c, offset_y, end_y, 'height: ' + current_block_height);
1962
+ data_part.push({
1963
+ source: ctx.getImageData(0, offset_y, srcCanvas.width, block_height),
1964
+ startY: Math.ceil(offset_y / ratio_h),
1965
+ height: current_block_height
1966
+ });
1967
+ }
1968
+ //start
1969
+ const destCtx = destCanvas.getContext('2d');
1970
+ if (!destCtx)
1971
+ return reject('Canvas is empty (resample dest)');
1972
+ for (let c = 0; c < data_part.length; c++) {
1973
+ //draw
1974
+ const height_part = Math.ceil(data_part[c].height / ratio_h);
1975
+ const target = destCtx.createImageData(destCanvas.width, height_part);
1976
+ // target.data.set(event.data.target);
1977
+ destCtx.putImageData(target, 0, data_part[c].startY);
1978
+ }
1979
+ });
1980
+ };
1981
+ /**
1982
+ * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
1983
+ */
1984
+ const resampleSingle = (srcCanvasData, destCanvasData) => {
1985
+ const ratio_w = srcCanvasData.width / destCanvasData.width;
1986
+ const ratio_h = srcCanvasData.height / destCanvasData.height;
1987
+ const ratio_w_half = Math.ceil(ratio_w / 2);
1988
+ const ratio_h_half = Math.ceil(ratio_h / 2);
1989
+ const data = srcCanvasData.data;
1990
+ const data2 = destCanvasData.data;
1991
+ for (let j = 0; j < destCanvasData.height; j++) {
1992
+ for (let i = 0; i < destCanvasData.width; i++) {
1993
+ const x2 = (i + j * destCanvasData.width) * 4;
1994
+ let weight = 0;
1995
+ let weights = 0;
1996
+ let weights_alpha = 0;
1997
+ let gx_r = 0;
1998
+ let gx_g = 0;
1999
+ let gx_b = 0;
2000
+ let gx_a = 0;
2001
+ const center_y = j * ratio_h;
2002
+ const xx_start = Math.floor(i * ratio_w);
2003
+ const xx_stop = Math.min(Math.ceil((i + 1) * ratio_w), srcCanvasData.width);
2004
+ const yy_start = Math.floor(j * ratio_h);
2005
+ const yy_stop = Math.min(Math.ceil((j + 1) * ratio_h), srcCanvasData.height);
2006
+ for (let yy = yy_start; yy < yy_stop; yy++) {
2007
+ let dy = Math.abs(center_y - yy) / ratio_h_half;
2008
+ let center_x = i * ratio_w;
2009
+ let w0 = dy * dy; //pre-calc part of w
2010
+ for (let xx = xx_start; xx < xx_stop; xx++) {
2011
+ let dx = Math.abs(center_x - xx) / ratio_w_half;
2012
+ let w = Math.sqrt(w0 + dx * dx);
2013
+ if (w >= 1) {
2014
+ //pixel too far
2015
+ continue;
2016
+ }
2017
+ //hermite filter
2018
+ weight = 2 * w * w * w - 3 * w * w + 1;
2019
+ let pos_x = 4 * (xx + yy * srcCanvasData.width);
2020
+ //alpha
2021
+ gx_a += weight * data[pos_x + 3];
2022
+ weights_alpha += weight;
2023
+ //colors
2024
+ if (data[pos_x + 3] < 255)
2025
+ weight = weight * data[pos_x + 3] / 250;
2026
+ gx_r += weight * data[pos_x];
2027
+ gx_g += weight * data[pos_x + 1];
2028
+ gx_b += weight * data[pos_x + 2];
2029
+ weights += weight;
2030
+ }
2031
+ }
2032
+ data2[x2] = gx_r / weights;
2033
+ data2[x2 + 1] = gx_g / weights;
2034
+ data2[x2 + 2] = gx_b / weights;
2035
+ data2[x2 + 3] = gx_a / weights_alpha;
2036
+ }
2037
+ }
2038
+ };
2039
+ /**
2040
+ * Hermite resize. Detect cpu count and use best option for user.
2041
+ */
2042
+ const resampleAuto = (srcCanvas, destCanvas, config) => {
2043
+ if (!!globalThis.Worker && navigator.hardwareConcurrency > 1 && config?.argorithm !== 'hermite_single') {
2044
+ //workers supported and we have at least 2 cpu cores - using multithreading
2045
+ return resample(srcCanvas, destCanvas);
2046
+ }
2047
+ else {
2048
+ //1 cpu version
2049
+ const { srcImgData, destImgData } = getImageData(srcCanvas, destCanvas);
2050
+ resampleSingle(srcImgData, destImgData);
2051
+ destCanvas.getContext('2d').putImageData(destImgData, 0, 0);
2052
+ return;
2053
+ }
2054
+ };
2055
+ async function scaleCanvasWithAlgorithm(canvas, config) {
2056
+ const scale = config.outputWidth / canvas.width;
2057
+ const scaled = new OffscreenCanvas(Math.floor(config.outputWidth), getTargetHeight(canvas.height, scale, config));
2058
+ switch (config.algorithm) {
2059
+ case 'hermite': {
2060
+ await resampleAuto(canvas, scaled, config);
2061
+ break;
2062
+ }
2063
+ case 'hermite_single': {
2064
+ const { srcImgData, destImgData } = getImageData(canvas, scaled);
2065
+ resampleSingle(srcImgData, destImgData);
2066
+ scaled?.getContext('2d')?.putImageData(destImgData, 0, 0);
2067
+ break;
2068
+ }
2069
+ case 'bilinear': {
2070
+ // const { srcImgData, destImgData } = getImageData(canvas, scaled);
2071
+ // bilinear(srcImgData, destImgData, scale);
2072
+ // scaled?.getContext('2d')?.putImageData(destImgData, 0, 0);
2073
+ break;
2074
+ }
2075
+ default: {
2076
+ scaled.getContext('2d')?.drawImage(canvas, 0, 0, scaled.width, scaled.height);
2077
+ break;
2078
+ }
2079
+ }
2080
+ return scaled;
2081
+ }
2082
+ const getHalfScaleCanvas = (src) => {
2083
+ const half = new OffscreenCanvas(src.width / 2, src.height / 2);
2084
+ half
2085
+ ?.getContext('2d')
2086
+ ?.drawImage(src, 0, 0, half.width, half.height);
2087
+ return half;
2088
+ };
2089
+ const getImageData = (canvas, scaled) => {
2090
+ const srcImgData = canvas
2091
+ ?.getContext('2d')
2092
+ ?.getImageData(0, 0, canvas.width, canvas.height);
2093
+ const destImgData = scaled
2094
+ ?.getContext('2d')
2095
+ ?.createImageData(scaled.width, scaled.height);
2096
+ if (!srcImgData || !destImgData)
2097
+ throw Error('Canvas is empty (scaleCanvasWithAlgorithm). You should run this script after the document is ready.');
2098
+ return { srcImgData, destImgData };
2099
+ };
2100
+ async function saveBlobToOPFS(filePath, blob) {
2101
+ // Access the OPFS root directory
2102
+ const rootHandle = await navigator.storage.getDirectory();
2103
+ // Split the filePath into directory segments and file name
2104
+ const segments = filePath.split('/').filter(Boolean);
2105
+ const fileName = segments.pop(); // Extract the file name
2106
+ if (!fileName) {
2107
+ throw new Error('Invalid file path: No file name provided.');
2108
+ }
2109
+ // Traverse or create directories as needed
2110
+ let currentDirHandle = rootHandle;
2111
+ for (const segment of segments) {
2112
+ currentDirHandle = await currentDirHandle.getDirectoryHandle(segment, { create: true });
2113
+ }
2114
+ // Create or open the file in OPFS
2115
+ const fileHandle = await currentDirHandle.getFileHandle(fileName, { create: true });
2116
+ // Write the Blob to the file
2117
+ const writableStream = await fileHandle.createWritable();
2118
+ await writableStream.write(blob);
2119
+ await writableStream.close();
2120
+ }
2121
+ const imageResize = async (filePath, width, height) => {
2122
+ console.log({ filePath, width, height });
2123
+ const config = {
2124
+ ...DEFAULT_CONFIG,
2125
+ algorithm: 'hermite_single',
2126
+ mimeType: 'image/webp',
2127
+ maxWidth: width,
2128
+ maxHeight: height,
2129
+ };
2130
+ const rootHandle = await navigator.storage.getDirectory();
2131
+ // List files in the root directory
2132
+ const files = await listFilesInDirectory(rootHandle);
2133
+ console.log({
2134
+ message: 'listFilesInDirectory',
2135
+ files
2136
+ });
2137
+ const file = await getFileFromOPFS(filePath);
2138
+ const imageBitmap = await createImageBitmap(file);
2139
+ let converting;
2140
+ if (isIos() && imageBitmap.width * imageBitmap.height > 16777216) {
2141
+ const scale = Math.sqrt(16777216 / (imageBitmap.width * imageBitmap.height));
2142
+ console.log(`browser-image-resizer: scale: Image is too large in iOS WebKit`);
2143
+ converting = new OffscreenCanvas(Math.floor(imageBitmap.width * scale), Math.floor(imageBitmap.height * scale));
2144
+ converting.getContext('2d')?.drawImage(imageBitmap, 0, 0, converting.width, converting.height);
2145
+ }
2146
+ else {
2147
+ converting = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
2148
+ converting.getContext('2d')?.drawImage(imageBitmap, 0, 0);
2149
+ }
2150
+ if (!converting?.getContext('2d')) {
2151
+ console.log('browser-image-resizer: Canvas Context is empty.');
2152
+ }
2153
+ const maxWidth = findMaxWidth(config, converting);
2154
+ if (!maxWidth) {
2155
+ throw Error(`browser-image-resizer: maxWidth is ${maxWidth}!!`);
2156
+ }
2157
+ while (config.processByHalf && converting.width >= 2 * maxWidth) {
2158
+ if (config.debug)
2159
+ console.log(`browser-image-resizer: scale: Scaling canvas by half from ${converting.width}`);
2160
+ converting = getHalfScaleCanvas(converting);
2161
+ }
2162
+ if (converting.width > maxWidth) {
2163
+ if (config.debug)
2164
+ console.log(`browser-image-resizer: scale: Scaling canvas by ${config.argorithm} from ${converting.width} to ${maxWidth}`);
2165
+ converting = await scaleCanvasWithAlgorithm(converting, Object.assign(config, { outputWidth: maxWidth }));
2166
+ }
2167
+ if (config.mimeType === null) {
2168
+ return converting;
2169
+ }
2170
+ const resizedBlob = await converting.convertToBlob({ type: config.mimeType, quality: config.quality });
2171
+ const pathSegments = filePath.split('/');
2172
+ const fileName = pathSegments.pop();
2173
+ if (!fileName) {
2174
+ throw Error('Invalid file path: No file name provided.');
2175
+ }
2176
+ const newSegments = [
2177
+ ...pathSegments,
2178
+ width,
2179
+ ];
2180
+ const fileNameParts = fileName.split('.');
2181
+ const newFileName = `${fileNameParts[0]}.webp`;
2182
+ const newDirPath = newSegments.join('/');
2183
+ const newFilePath = `${newDirPath}/${newFileName}`;
2184
+ // Save resized image to OPFS with new name
2185
+ await saveBlobToOPFS(newFilePath, resizedBlob);
2186
+ globalThis.postMessage({
2187
+ done: true,
2188
+ filePath: newFilePath,
2189
+ });
2190
+ };
2191
+ onmessage = async (e) => {
2192
+ console.log('[imageResize] onmessage', e.data);
2193
+ const { filePath, width, height } = e.data;
2194
+ await imageResize(filePath, width, height);
2195
+ console.log(`[imageResize] Done`, filePath);
2196
+ };
2197
+ }.toString()}
2198
+ )()`;
2199
+
2200
+ class ImageResizer {
2201
+ constructor() {
2202
+ this.workersArchive = new Map();
2203
+ this.cores = Math.min(navigator.hardwareConcurrency || 4, 4);
2204
+ this.workerBlobUrl = globalThis.URL.createObjectURL(new Blob([imageResize], { type: 'application/javascript' }));
2205
+ }
2206
+ async resize({ filePath, width, height }) {
2207
+ if (this.workersArchive.has(filePath)) {
2208
+ const savedWorker = this.workersArchive.get(filePath);
2209
+ savedWorker?.terminate();
2210
+ console.log('[ImageResizer.resize] Terminated worker for filePath due to incoming request', filePath);
2211
+ this.workersArchive.delete(filePath);
2212
+ }
2213
+ const worker = new Worker(this.workerBlobUrl);
2214
+ this.workersArchive.set(filePath, worker);
2215
+ return new Promise((resolve, reject) => {
2216
+ worker.onmessage = (e) => {
2217
+ console.log('[ImageResizer.resize] main thread onmessage', e.data);
2218
+ if (e.data.done) {
2219
+ const savedWorker = this.workersArchive.get(filePath);
2220
+ savedWorker?.terminate();
2221
+ console.log('[ImageResizer.resize] Terminated worker for filePath due to done', filePath);
2222
+ this.workersArchive.delete(filePath);
2223
+ resolve(e.data);
2224
+ }
2225
+ if (e.data.error) {
2226
+ reject(e.data.error);
2227
+ }
2228
+ };
2229
+ worker.postMessage({
2230
+ filePath,
2231
+ width,
2232
+ height,
2233
+ });
2234
+ });
2235
+ }
2236
+ async resizeAll({ width, height }) {
2237
+ const imageDir = '/files/images';
2238
+ let imageFilesStats = await fs.promises.readdir(imageDir, {
2239
+ withFileTypes: true
2240
+ });
2241
+ imageFilesStats = imageFilesStats.filter(file => file.isFile());
2242
+ const imageFiles = imageFilesStats.map(file => file.path);
2243
+ const widthDir = `${imageDir}/${width}`;
2244
+ await FileManager.createDirIfNotExists(widthDir);
2245
+ for (const imageFile of imageFiles) {
2246
+ const resizedImageExists = await fs.promises.exists(`${widthDir}/${imageFile}`);
2247
+ if (!resizedImageExists) {
2248
+ await this.resize({ filePath: `${imageDir}/${imageFile}`, width, height });
2249
+ }
2250
+ }
2251
+ }
2252
+ }
2253
+
2254
+ const logger$k = debug('app:browser:helpers:FileManager');
2255
+ class FileManager extends BaseFileManager {
2256
+ static async readFileAsBuffer(filePath) {
2257
+ return new Promise((resolve, reject) => {
2258
+ reject(new Error('Not implemented'));
2259
+ });
2260
+ }
2261
+ static async getContentUrlFromPath(path) {
2262
+ const fs = await import('@zenfs/core');
2263
+ const fileExists = await fs.promises.exists(path);
2264
+ if (fileExists) {
2265
+ const fileContents = await fs.promises.readFile(path);
2266
+ const fileHandler = new File([fileContents], path);
2267
+ return URL.createObjectURL(fileHandler);
2268
+ }
2269
+ }
2270
+ static async initializeFileSystem() {
2271
+ const { WebAccess } = await import('@zenfs/dom');
2272
+ const { configureSingle } = await import('@zenfs/core');
2273
+ const handle = await navigator.storage.getDirectory();
2274
+ // await configure({
2275
+ // mounts: {
2276
+ // '/': {
2277
+ // backend: WebAccess,
2278
+ // handle,
2279
+ // },
1572
2280
  // },
1573
- // )
1574
- });
1575
- };
2281
+ // disableUpdateOnRead: true,
2282
+ // onlySyncOnClose: true,
2283
+ // })
2284
+ await configureSingle({
2285
+ backend: WebAccess,
2286
+ handle,
2287
+ });
2288
+ }
2289
+ static async downloadAllFiles({ transactionIds, arweaveHost, excludedTransactions, }) {
2290
+ const fileDownloader = new FileDownloader();
2291
+ await fileDownloader.downloadAll({ transactionIds, arweaveHost, excludedTransactions });
2292
+ }
2293
+ static async resizeImage({ filePath, width, height }) {
2294
+ const imageResizer = new ImageResizer();
2295
+ await imageResizer.resize({ filePath, width, height });
2296
+ }
2297
+ static async resizeAllImages({ width, height }) {
2298
+ const imageResizer = new ImageResizer();
2299
+ await imageResizer.resizeAll({ width, height });
2300
+ }
2301
+ static async pathExists(filePath) {
2302
+ try {
2303
+ // Access the root directory of OPFS
2304
+ const root = await navigator.storage.getDirectory();
2305
+ // Split the path into segments
2306
+ const parts = filePath.split('/').filter(Boolean);
2307
+ let currentDir = root;
2308
+ // Traverse each part of the path
2309
+ for (let i = 0; i < parts.length; i++) {
2310
+ const part = parts[i];
2311
+ try {
2312
+ const handle = await currentDir.getDirectoryHandle(part, { create: false });
2313
+ currentDir = handle; // Move into the directory
2314
+ }
2315
+ catch (error) {
2316
+ try {
2317
+ // If it's not a directory, check if it's a file
2318
+ await currentDir.getFileHandle(part, { create: false });
2319
+ // If we successfully got a file handle and it's the last part, return true
2320
+ return i === parts.length - 1;
2321
+ }
2322
+ catch {
2323
+ // Neither a directory nor a file exists
2324
+ return false;
2325
+ }
2326
+ }
2327
+ }
2328
+ return true; // Directory exists
2329
+ }
2330
+ catch (error) {
2331
+ return false; // Any error means the path does not exist
2332
+ }
2333
+ }
2334
+ static async createDirIfNotExists(filePath) {
2335
+ if (!(await FileManager.pathExists(filePath))) {
2336
+ try {
2337
+ const fs = await import('@zenfs/core');
2338
+ await fs.promises.mkdir(filePath);
2339
+ }
2340
+ catch (error) {
2341
+ // This is a no-op. We tried to create a directory that already exists.
2342
+ console.log('Attempted to create a directory that already exists');
2343
+ }
2344
+ }
2345
+ }
2346
+ /**
2347
+ * Waits for a file to exist at the specified path.
2348
+ * @param {string} filePath - The path of the file to check.
2349
+ * @param {number} interval - The interval in milliseconds between checks (default: 500ms).
2350
+ * @param {number} timeout - The timeout in milliseconds to wait for the file to exist (default: 10s).
2351
+ * @returns {Promise<boolean>} - Resolves to true if the file exists within the timeout period, otherwise false.
2352
+ */
2353
+ static async waitForFile(filePath, interval = 1000, timeout = 60000) {
2354
+ const fs = await import('@zenfs/core');
2355
+ const fsNode = await import('node:fs');
2356
+ return new Promise((resolve, reject) => {
2357
+ const startTime = Date.now();
2358
+ let isBusy = false;
2359
+ const cancelableInterval = new CancelableInterval(async (stop) => {
2360
+ logger$k('waitForFile', filePath);
2361
+ if (isBusy) {
2362
+ return;
2363
+ }
2364
+ isBusy = true;
2365
+ // TODO: Needs to read from OPFS
2366
+ if (fs.existsSync(filePath) && fsNode.existsSync(filePath)) {
2367
+ stop();
2368
+ resolve(true);
2369
+ }
2370
+ const pathExists = await this.pathExists(filePath);
2371
+ if (pathExists) {
2372
+ stop();
2373
+ resolve(true);
2374
+ }
2375
+ if (Date.now() - startTime >= timeout) {
2376
+ stop();
2377
+ reject(new Error('Timeout exceeded while waiting for file'));
2378
+ }
2379
+ isBusy = false;
2380
+ }, interval);
2381
+ cancelableInterval.start();
2382
+ // const _interval = setInterval(async () => {
2383
+ // logger('waitForFile', filePath)
2384
+ // if (isBusy) {
2385
+ // return
2386
+ // }
2387
+ // isBusy = true
2388
+ // // TODO: Needs to read from OPFS
2389
+ // if (fs.existsSync(filePath) && fsNode.existsSync(filePath)) {
2390
+ // clearInterval(_interval)
2391
+ // resolve(true)
2392
+ // }
2393
+ // const pathExists = await this.pathExists(filePath)
2394
+ // if (pathExists) {
2395
+ // clearInterval(_interval)
2396
+ // resolve(true)
2397
+ // }
2398
+ // if (Date.now() - startTime >= timeout) {
2399
+ // clearInterval(_interval)
2400
+ // reject(new Error('Timeout exceeded while waiting for file'))
2401
+ // }
2402
+ // isBusy = false
2403
+ // }, interval)
2404
+ // retry(
2405
+ // {
2406
+ // times: Math.ceil(timeout / interval),
2407
+ // interval: interval,
2408
+ // },
2409
+ // (callback: Function) => {
2410
+ // if (fs.existsSync(filePath) && fsNode.existsSync(filePath)) {
2411
+ // return callback(null, true) // File exists, finish with success
2412
+ // }
2413
+ // if (Date.now() - startTime >= timeout) {
2414
+ // return callback(new Error('Timeout exceeded while waiting for file'))
2415
+ // }
2416
+ // callback(new Error('File does not exist yet')) // Retry with this error
2417
+ // },
2418
+ // (err: Error, result: boolean) => {
2419
+ // if (err) {
2420
+ // return resolve(false) // Resolve as false if timeout or error occurs
2421
+ // }
2422
+ // resolve(result) // Resolve as true if file exists
2423
+ // },
2424
+ // )
2425
+ });
2426
+ }
2427
+ }
2428
+ BaseFileManager.setPlatformClass(FileManager);
2429
+ class CancelableInterval {
2430
+ constructor(task, interval) {
2431
+ this.task = task;
2432
+ this.interval = interval;
2433
+ this.intervalId = null;
2434
+ this.currentTaskAbortController = null;
2435
+ }
2436
+ start() {
2437
+ this.intervalId = window.setInterval(async () => {
2438
+ if (this.currentTaskAbortController) {
2439
+ // Cancel the previous running task
2440
+ this.currentTaskAbortController.abort();
2441
+ }
2442
+ // Create a new abort controller for the current task
2443
+ this.currentTaskAbortController = new AbortController();
2444
+ const signal = this.currentTaskAbortController.signal;
2445
+ try {
2446
+ await this.taskWithCancellation(signal);
2447
+ }
2448
+ catch (error) {
2449
+ if (error instanceof DOMException && error.name === 'AbortError') {
2450
+ console.log('Previous task was canceled.');
2451
+ }
2452
+ else {
2453
+ console.error('Task error:', error);
2454
+ }
2455
+ this.stop();
2456
+ }
2457
+ }, this.interval);
2458
+ }
2459
+ async taskWithCancellation(signal) {
2460
+ await this.task(() => this.stop());
2461
+ if (signal.aborted) {
2462
+ throw new DOMException('Task was aborted', 'AbortError');
2463
+ }
2464
+ }
2465
+ stop() {
2466
+ if (this.intervalId !== null) {
2467
+ clearInterval(this.intervalId);
2468
+ this.intervalId = null;
2469
+ }
2470
+ if (this.currentTaskAbortController) {
2471
+ this.currentTaskAbortController.abort();
2472
+ }
2473
+ }
2474
+ }
2475
+
2476
+ var FileManager$1 = /*#__PURE__*/Object.freeze({
2477
+ __proto__: null,
2478
+ FileManager: FileManager
2479
+ });
1576
2480
 
1577
2481
  const logger$j = debug('app:services:db:actors:migrate');
1578
2482
  const migrate = fromCallback(({ sendBack, input: { context } }) => {
@@ -1583,7 +2487,7 @@ const migrate = fromCallback(({ sendBack, input: { context } }) => {
1583
2487
  const journalPath = `/${pathToDbDir}/meta/_journal.json`;
1584
2488
  journalExists = await fs.promises.exists(journalPath);
1585
2489
  if (!journalExists) {
1586
- await waitForFile(journalPath, 500, 60000);
2490
+ await FileManager.waitForFile(journalPath, 500, 60000);
1587
2491
  }
1588
2492
  };
1589
2493
  const _migrate = async () => {
@@ -1831,26 +2735,22 @@ const createDirectories = async (dirPath) => {
1831
2735
  return;
1832
2736
  }
1833
2737
  const parentDir = path.dirname(dirPath);
1834
- let parentDirExists = await fs.promises.exists(parentDir);
2738
+ let parentDirExists = await BaseFileManager.pathExists(parentDir);
1835
2739
  if (!parentDirExists) {
1836
2740
  await createDirectories(parentDir);
1837
2741
  }
1838
- parentDirExists = await fs.promises.exists(parentDir);
2742
+ parentDirExists = await BaseFileManager.pathExists(parentDir);
1839
2743
  if (parentDirExists) {
1840
- try {
1841
- await fs.promises.mkdir(dirPath);
1842
- }
1843
- catch (error) {
1844
- logger$h(`[Error] Failed to create directories for ${dirPath}:`, error);
1845
- }
1846
- }
1847
- if (!parentDirExists) {
1848
- console.log(fs);
2744
+ await BaseFileManager.createDirIfNotExists(dirPath);
1849
2745
  }
1850
2746
  };
1851
- let busy = false;
1852
- const downloadFile = async (url, localFilePath) => {
1853
- try {
2747
+ class FileDownloadManager {
2748
+ constructor(fileUrls, maxRetries) {
2749
+ this.isDownloading = false;
2750
+ this.filesToDownload = new Map(fileUrls.map(url => [url, 0]));
2751
+ this.maxRetries = maxRetries;
2752
+ }
2753
+ async downloadFile(url, localFilePath) {
1854
2754
  const response = await fetch(url);
1855
2755
  const fileData = await response.text().catch((error) => {
1856
2756
  console.error(`Failed to parse text from ${url}:`, error);
@@ -1860,10 +2760,6 @@ const downloadFile = async (url, localFilePath) => {
1860
2760
  return;
1861
2761
  }
1862
2762
  const localDirPath = path.dirname(localFilePath);
1863
- if (busy) {
1864
- return;
1865
- }
1866
- busy = true;
1867
2763
  await createDirectories(localDirPath);
1868
2764
  const filename = path.basename(localFilePath);
1869
2765
  const regex = /(\d+)[\w_]+\.(sql|json)$/;
@@ -1899,34 +2795,77 @@ const downloadFile = async (url, localFilePath) => {
1899
2795
  logger$h(`[downloadFile] Wrote file sync to ${localFilePath}`);
1900
2796
  }
1901
2797
  }
1902
- catch (error) {
1903
- logger$h(`[Error] Failed to download file from ${url}:`, error);
2798
+ async start() {
2799
+ if (this.isDownloading) {
2800
+ console.warn("Download process is already running.");
2801
+ return;
2802
+ }
2803
+ this.isDownloading = true;
2804
+ for (const [fileUrl, attempts] of this.filesToDownload.entries()) {
2805
+ let success = false;
2806
+ while (attempts < this.maxRetries) {
2807
+ try {
2808
+ console.log(`Starting download: ${fileUrl}`);
2809
+ await this.downloadFile(fileUrl, fileUrl);
2810
+ console.log(`Download successful: ${fileUrl}`);
2811
+ this.filesToDownload.delete(fileUrl);
2812
+ success = true;
2813
+ break; // Move to next file
2814
+ }
2815
+ catch (error) {
2816
+ console.error(`Error downloading ${fileUrl}:`, error);
2817
+ this.filesToDownload.set(fileUrl, attempts + 1);
2818
+ }
2819
+ }
2820
+ if (!success) {
2821
+ console.error(`Failed to download after ${this.maxRetries} attempts: ${fileUrl}`);
2822
+ }
2823
+ }
2824
+ this.isDownloading = false;
2825
+ console.log("All downloads completed.");
1904
2826
  }
1905
- busy = false;
1906
- };
1907
- const fetchDirectory = async (url) => {
1908
- const response = await fetch(url);
1909
- return response.json();
1910
- };
1911
- const fetchFilesRecursively = async (url, localPath, fileList) => {
1912
- for (const file of fileList) {
1913
- try {
1914
- const fileUrl = `${url}/${file}`;
1915
- const fileLocalPath = path.join(localPath, file);
1916
- // logger(`[fetchFilesRecursively] fileUrl: ${fileUrl}`)
1917
- // logger(`[fetchFilesRecursively] fileLocalPath: ${fileLocalPath}`)
1918
- await downloadFile(fileUrl, fileLocalPath);
2827
+ addFile(fileUrl) {
2828
+ if (!this.filesToDownload.has(fileUrl)) {
2829
+ this.filesToDownload.set(fileUrl, 0);
2830
+ console.log(`Added file to download queue: ${fileUrl}`);
1919
2831
  }
1920
- catch (error) {
1921
- console.error(`Failed to fetch files from ${url}:`, error);
2832
+ else {
2833
+ console.warn(`File already in queue: ${fileUrl}`);
1922
2834
  }
1923
2835
  }
2836
+ getPendingFiles() {
2837
+ return Array.from(this.filesToDownload.keys());
2838
+ }
2839
+ clear() {
2840
+ this.filesToDownload.clear();
2841
+ console.log("Cleared all files from the download queue.");
2842
+ }
2843
+ }
2844
+ const fetchDirectory = async (url) => {
2845
+ const response = await fetch(url);
2846
+ return response.json();
1924
2847
  };
2848
+ // export const fetchFilesRecursively = async (
2849
+ // url: string,
2850
+ // localPath: string,
2851
+ // fileList: string[],
2852
+ // ) => {
2853
+ // for (const file of fileList) {
2854
+ // try {
2855
+ // const fileUrl = `${url}/${file}`
2856
+ // const fileLocalPath = path.join(localPath, file)
2857
+ // // logger(`[fetchFilesRecursively] fileUrl: ${fileUrl}`)
2858
+ // // logger(`[fetchFilesRecursively] fileLocalPath: ${fileLocalPath}`)
2859
+ // await downloadFile(fileUrl, fileLocalPath)
2860
+ // } catch (error) {
2861
+ // console.error(`Failed to fetch files from ${url}:`, error)
2862
+ // }
2863
+ // }
2864
+ // }
1925
2865
  const confirmFilesExist = async (filePaths) => {
1926
2866
  let everythingDownloaded = false;
1927
2867
  for (const filePath of filePaths) {
1928
- const fullPath = path.join(BROWSER_FS_TOP_DIR, filePath);
1929
- everythingDownloaded = await fs.promises.exists(fullPath);
2868
+ everythingDownloaded = await fs.promises.exists(filePath);
1930
2869
  }
1931
2870
  if (!everythingDownloaded) {
1932
2871
  setTimeout(async () => {
@@ -1938,7 +2877,10 @@ const filesToExclude = ['.DS_Store'];
1938
2877
  const syncDbFiles = async ({ filePaths, files }) => {
1939
2878
  let fileList = await fetchDirectory(filePaths);
1940
2879
  fileList = fileList.filter((file) => !filesToExclude.includes(file));
1941
- await fetchFilesRecursively(files, BROWSER_FS_TOP_DIR, fileList);
2880
+ fileList = fileList.map((file) => `${files}/${file}`);
2881
+ const downloadManager = new FileDownloadManager(fileList, 5);
2882
+ await downloadManager.start();
2883
+ // await fetchFilesRecursively(files, BROWSER_FS_TOP_DIR, fileList)
1942
2884
  await confirmFilesExist(fileList);
1943
2885
  logger$h('[syncDbFiles] Files synced!');
1944
2886
  };
@@ -2031,28 +2973,12 @@ const GET_TRANSACTION_TAGS = graphql(/* GraphQL */ `
2031
2973
  }
2032
2974
  `);
2033
2975
 
2034
- const saveAppState = async (key, value) => {
2035
- const appDb = BaseDb.getAppDb();
2036
- await appDb
2037
- .insert(appState)
2038
- .values({
2039
- key,
2040
- value,
2041
- })
2042
- .onConflictDoUpdate({
2043
- target: appState.key,
2044
- set: {
2045
- value,
2046
- },
2047
- });
2048
- };
2049
-
2050
2976
  const initArweaveClient = async () => {
2051
2977
  if (isBrowser()) {
2052
- (await import('./ArweaveClient-DQrXvrNN.js')).ArweaveClient;
2978
+ (await import('./ArweaveClient-Dcqcpsq4.js')).ArweaveClient;
2053
2979
  }
2054
2980
  if (!isBrowser()) {
2055
- (await import('./ArweaveClient-CoFomTCN.js')).ArweaveClient;
2981
+ (await import('./ArweaveClient-Dh7LRIqD.js')).ArweaveClient;
2056
2982
  }
2057
2983
  };
2058
2984
  let domain = 'arweave.net';
@@ -2138,18 +3064,10 @@ const downloadAllFilesBinaryRequestHandler = async () => {
2138
3064
  },
2139
3065
  }),
2140
3066
  });
2141
- if (!(await fs.promises.exists('/files'))) {
2142
- await fs.promises.mkdir('/files', { recursive: true });
2143
- }
2144
- if (!(await fs.promises.exists('/files/html'))) {
2145
- await fs.promises.mkdir('/files/html', { recursive: true });
2146
- }
2147
- if (!(await fs.promises.exists('/files/json'))) {
2148
- await fs.promises.mkdir('/files/json', { recursive: true });
2149
- }
2150
- if (!(await fs.promises.exists('/files/images'))) {
2151
- await fs.promises.mkdir('/files/images', { recursive: true });
2152
- }
3067
+ await BaseFileManager.createDirIfNotExists('/files');
3068
+ await BaseFileManager.createDirIfNotExists('/files/html');
3069
+ await BaseFileManager.createDirIfNotExists('/files/json');
3070
+ await BaseFileManager.createDirIfNotExists('/files/images');
2153
3071
  const appDb = BaseDb.getAppDb();
2154
3072
  if (!appDb) {
2155
3073
  console.warn('[fetchAll/actors] [fetchAllBinaryData] seedDb not available');
@@ -4932,7 +5850,7 @@ const hydrateFromDb = fromCallback(({ sendBack, input: { context } }) => {
4932
5850
  if (propertyRecordSchema &&
4933
5851
  propertyRecordSchema.storageType &&
4934
5852
  propertyRecordSchema.storageType === 'ItemStorage') {
4935
- const { Item } = await import('./index-CPxn1SR4.js');
5853
+ const { Item } = await import('./index-C93o7-zP.js');
4936
5854
  const item = await Item.find({
4937
5855
  seedLocalId,
4938
5856
  modelName,
@@ -6865,7 +7783,7 @@ const initItem = async () => {
6865
7783
  Item$2 = (await Promise.resolve().then(function () { return Item$1; })).Item;
6866
7784
  }
6867
7785
  if (!isBrowser()) {
6868
- Item$2 = (await import('./Item-BmPvgaG4.js')).Item;
7786
+ Item$2 = (await import('./Item-D0rGmrZg.js')).Item;
6869
7787
  }
6870
7788
  };
6871
7789
 
@@ -6875,17 +7793,17 @@ const initItemProperty = async () => {
6875
7793
  ItemProperty$2 = (await Promise.resolve().then(function () { return ItemProperty$1; })).ItemProperty;
6876
7794
  }
6877
7795
  if (!isBrowser()) {
6878
- ItemProperty$2 = (await import('./ItemProperty-BW4k1h0o.js')).ItemProperty;
7796
+ ItemProperty$2 = (await import('./ItemProperty-D0tkeKx1.js')).ItemProperty;
6879
7797
  }
6880
7798
  };
6881
7799
 
6882
7800
  let Db;
6883
7801
  const initDb = async () => {
6884
7802
  if (isBrowser()) {
6885
- Db = (await import('./Db-5C5VqRWb.js')).Db;
7803
+ Db = (await import('./Db-BhLWMUOe.js')).Db;
6886
7804
  }
6887
7805
  if (!isBrowser()) {
6888
- Db = (await import('./Db-Dv890NJZ.js')).Db;
7806
+ Db = (await import('./Db-DrcygP77.js')).Db;
6889
7807
  }
6890
7808
  // TODO: Add config for React Native
6891
7809
  };
@@ -7669,28 +8587,28 @@ const setupServiceHandlers = () => {
7669
8587
 
7670
8588
  const initEasClient = async () => {
7671
8589
  if (isBrowser()) {
7672
- (await import('./EasClient-sCbTHcO8.js')).EasClient;
8590
+ (await import('./EasClient-A1xC7Gm-.js')).EasClient;
7673
8591
  }
7674
8592
  if (!isBrowser()) {
7675
- (await import('./EasClient-CBu9ENAi.js')).EasClient;
8593
+ (await import('./EasClient-m2mXad59.js')).EasClient;
7676
8594
  }
7677
8595
  };
7678
8596
 
7679
8597
  const initQueryClient = async () => {
7680
8598
  if (isBrowser()) {
7681
- (await import('./QueryClient-BBaE-LKI.js')).QueryClient;
8599
+ (await import('./QueryClient-C-hFMF2j.js')).QueryClient;
7682
8600
  }
7683
8601
  if (!isBrowser()) {
7684
- (await import('./QueryClient-C-ZzUnWq.js')).QueryClient;
8602
+ (await import('./QueryClient-C02sPZ-K.js')).QueryClient;
7685
8603
  }
7686
8604
  };
7687
8605
 
7688
8606
  const initFileManager = async () => {
7689
8607
  if (isBrowser()) {
7690
- (await import('./FileManager-AhAsy_F_.js')).FileManager;
8608
+ (await Promise.resolve().then(function () { return FileManager$1; })).FileManager;
7691
8609
  }
7692
8610
  if (!isBrowser()) {
7693
- (await import('./FileManager-COOp89Mj.js')).FileManager;
8611
+ (await import('./FileManager-Dv2zn1RW.js')).FileManager;
7694
8612
  }
7695
8613
  };
7696
8614
 
@@ -7736,11 +8654,28 @@ const client = {
7736
8654
  arweaveDomain,
7737
8655
  filesDir,
7738
8656
  });
7739
- const { models: internalModels } = await import('./seed.schema.config-BXWvwsZQ.js');
8657
+ const { models: internalModels } = await import('./seed.schema.config-ClOsMOKS.js');
7740
8658
  for (const [key, value] of Object.entries(internalModels)) {
7741
8659
  setModel(key, value);
7742
8660
  }
7743
8661
  },
8662
+ setAddresses: (addresses) => {
8663
+ const appDb = BaseDb.getAppDb();
8664
+ if (!appDb) {
8665
+ throw new Error('App DB not found');
8666
+ }
8667
+ appDb.insert(appState)
8668
+ .values({
8669
+ key: 'addresses',
8670
+ value: JSON.stringify(addresses),
8671
+ })
8672
+ .onConflictDoUpdate({
8673
+ target: appState.key,
8674
+ set: {
8675
+ value: JSON.stringify(addresses),
8676
+ },
8677
+ });
8678
+ },
7744
8679
  subscribe: (callback) => {
7745
8680
  const subscription = globalService.subscribe(callback);
7746
8681
  eventEmitter.addListener('internal.globalService', callback);
@@ -7783,5 +8718,5 @@ const client = {
7783
8718
 
7784
8719
  enableMapSet();
7785
8720
 
7786
- export { BaseItem as B, Db as D, Item as I, Json as J, List as L, Model as M, Property as P, Relation as R, Text as T, BaseItemProperty as a, BaseDb as b, ImageSrc as c, Item$2 as d, ItemProperty$2 as e, useItem as f, useItemProperties as g, useCreateItem as h, useItemProperty as i, useDeleteItem as j, useGlobalServiceStatus as k, usePublishItem as l, usePersistedSnapshots as m, useServices as n, useService as o, getCorrectId as p, getGlobalService as q, eventEmitter as r, saveAppState as s, withSeed as t, useItems as u, client as v, waitForFile as w };
7787
- //# sourceMappingURL=index-CGeSKilQ.js.map
8721
+ export { BaseItem as B, Db as D, FileManager as F, Item as I, Json as J, List as L, Model as M, Property as P, Relation as R, Text as T, BaseItemProperty as a, BaseDb as b, ImageSrc as c, Item$2 as d, ItemProperty$2 as e, useItem as f, useItemProperties as g, useCreateItem as h, useItemProperty as i, useDeleteItem as j, useGlobalServiceStatus as k, usePublishItem as l, usePersistedSnapshots as m, useServices as n, useService as o, getCorrectId as p, getGlobalService as q, eventEmitter as r, client as s, useItems as u, withSeed as w };
8722
+ //# sourceMappingURL=index-B6FQruEq.js.map