From 5e955d3d3b4182193f17f6c41c7d75c64bf01aff Mon Sep 17 00:00:00 2001 From: Ankita Sahu <71656941+SAHU-01@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:35:17 +0530 Subject: [PATCH 1/6] fix:redux-bundler local-storage migration --- src/bundles/gateway.js | 28 ++++++++--- src/bundles/ipfs-provider.js | 46 ++++++++++++------ src/bundles/ipns.js | 38 ++++++++++----- src/bundles/pinning.js | 13 ++++- src/contexts/local-storage-context.tsx | 67 ++++++++++++++++++++++++++ src/index.js | 3 ++ 6 files changed, 159 insertions(+), 36 deletions(-) create mode 100644 src/contexts/local-storage-context.tsx diff --git a/src/bundles/gateway.js b/src/bundles/gateway.js index 978437cbd..37f1212da 100644 --- a/src/bundles/gateway.js +++ b/src/bundles/gateway.js @@ -1,4 +1,14 @@ -import { readSetting, writeSetting } from './local-storage.js' +import { createContextSelector } from '../helpers/context-bridge.jsx' + +const selectLocalStorageFromContext = createContextSelector('localStorage') + +const getLocalStorageUtils = () => { + const localStorageContext = selectLocalStorageFromContext() + return { + readSetting: localStorageContext?.readSetting || (() => null), + writeSetting: localStorageContext?.writeSetting || (() => {}) + } +} // TODO: switch to dweb.link when https://github.com/ipfs/kubo/issues/7318 export const DEFAULT_PATH_GATEWAY = 'https://ipfs.io' @@ -11,11 +21,13 @@ const IMG_ARRAY = [ ] const readPublicGatewaySetting = () => { + const { readSetting } = getLocalStorageUtils() const setting = readSetting('ipfsPublicGateway') return setting || DEFAULT_PATH_GATEWAY } const readPublicSubdomainGatewaySetting = () => { + const { readSetting } = getLocalStorageUtils() const setting = readSetting('ipfsPublicSubdomainGateway') return setting || DEFAULT_SUBDOMAIN_GATEWAY } @@ -175,14 +187,16 @@ const bundle = { doSetAvailableGateway: url => ({ dispatch }) => dispatch({ type: 'SET_AVAILABLE_GATEWAY', payload: url }), doUpdatePublicGateway: (address) => async ({ dispatch }) => { - await writeSetting('ipfsPublicGateway', address) - dispatch({ type: 'SET_PUBLIC_GATEWAY', payload: address }) - }, + const { writeSetting } = getLocalStorageUtils() + await writeSetting('ipfsPublicGateway', address) + dispatch({ type: 'SET_PUBLIC_GATEWAY', payload: address }) +}, doUpdatePublicSubdomainGateway: (address) => async ({ dispatch }) => { - await writeSetting('ipfsPublicSubdomainGateway', address) - dispatch({ type: 'SET_PUBLIC_SUBDOMAIN_GATEWAY', payload: address }) - }, + const { writeSetting } = getLocalStorageUtils() + await writeSetting('ipfsPublicSubdomainGateway', address) + dispatch({ type: 'SET_PUBLIC_SUBDOMAIN_GATEWAY', payload: address }) +}, selectAvailableGateway: (state) => state?.gateway?.availableGateway, diff --git a/src/bundles/ipfs-provider.js b/src/bundles/ipfs-provider.js index 310f6a0a4..aa0488171 100644 --- a/src/bundles/ipfs-provider.js +++ b/src/bundles/ipfs-provider.js @@ -5,9 +5,19 @@ import first from 'it-first' import last from 'it-last' import * as Enum from '../lib/enum.js' import { perform } from './task.js' -import { readSetting, writeSetting } from './local-storage.js' import { contextBridge } from '../helpers/context-bridge' import { createSelector } from 'redux-bundler' +import { createContextSelector } from '../helpers/context-bridge.jsx' + +const selectLocalStorageFromContext = createContextSelector('localStorage') + +const getLocalStorageUtils = () => { + const localStorageContext = selectLocalStorageFromContext() + return { + readSetting: localStorageContext?.readSetting || (() => null), + writeSetting: localStorageContext?.writeSetting || (() => {}) + } +} /** * @typedef {import('ipfs').IPFSService} IPFSService @@ -154,6 +164,7 @@ const init = () => { * @returns {HTTPClientOptions|string|null} */ const readAPIAddressSetting = () => { + const { readSetting } = getLocalStorageUtils() const setting = readSetting('ipfsApi') return setting == null ? null : asAPIOptions(setting) } @@ -324,22 +335,24 @@ const selectors = { const actions = { doSetupLocalStorage: () => async () => { - /** For the Explore page (i.e. ipld-explorer-components) */ - const useRemoteGatewaysToExplore = localStorage.getItem('explore.ipld.gatewayEnabled') - if (useRemoteGatewaysToExplore === null) { - // by default, disable remote gateways for the Explore page (i.e. ipld-explorer-components) - await writeSetting('explore.ipld.gatewayEnabled', false) - } + const { readSetting, writeSetting } = getLocalStorageUtils() + + /** For the Explore page (i.e. ipld-explorer-components) */ + const useRemoteGatewaysToExplore = localStorage.getItem('explore.ipld.gatewayEnabled') + if (useRemoteGatewaysToExplore === null) { + // by default, disable remote gateways for the Explore page (i.e. ipld-explorer-components) + await writeSetting('explore.ipld.gatewayEnabled', false) + } - const kuboGateway = readSetting('kuboGateway') - if (kuboGateway === null || typeof kuboGateway === 'string' || typeof kuboGateway === 'boolean' || typeof kuboGateway === 'number') { - // empty or invalid, set defaults - await writeSetting('kuboGateway', { trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) - } else if (/** @type {Record} */(kuboGateway).trustlessBlockBrokerConfig == null) { - // missing trustlessBlockBrokerConfig, set defaults - await writeSetting('kuboGateway', { ...kuboGateway, trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) - } - }, + const kuboGateway = readSetting('kuboGateway') + if (kuboGateway === null || typeof kuboGateway === 'string' || typeof kuboGateway === 'boolean' || typeof kuboGateway === 'number') { + // empty or invalid, set defaults + await writeSetting('kuboGateway', { trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) + } else if (/** @type {Record} */(kuboGateway).trustlessBlockBrokerConfig == null) { + // missing trustlessBlockBrokerConfig, set defaults + await writeSetting('kuboGateway', { ...kuboGateway, trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) + } +}, /** * @returns {function(Context):Promise} @@ -439,6 +452,7 @@ const actions = { * @returns {function(Context):Promise} */ doUpdateIpfsApiAddress: (address) => async (context) => { + const { writeSetting } = getLocalStorageUtils() const apiAddress = asAPIOptions(address) if (apiAddress == null) { context.dispatch({ type: ACTIONS.IPFS_API_ADDRESS_INVALID }) diff --git a/src/bundles/ipns.js b/src/bundles/ipns.js index 86c3459b8..6cae3a993 100644 --- a/src/bundles/ipns.js +++ b/src/bundles/ipns.js @@ -1,11 +1,24 @@ import all from 'it-all' -import { readSetting, writeSetting } from './local-storage.js' import { dispatchAsyncProvide } from './files/utils.js' +import { createContextSelector } from '../helpers/context-bridge.jsx' -const init = () => ({ - keys: [], - expectedPublishTime: readSetting('expectedPublishTime') || 60 -}) +const selectLocalStorageFromContext = createContextSelector('localStorage') + +const getLocalStorageUtils = () => { + const localStorageContext = selectLocalStorageFromContext() + return { + readSetting: localStorageContext?.readSetting || (() => null), + writeSetting: localStorageContext?.writeSetting || (() => {}) + } +} + +const init = () => { + const { readSetting } = getLocalStorageUtils() + return { + keys: [], + expectedPublishTime: readSetting('expectedPublishTime') || 60 + } +} const ipnsBundle = { name: 'ipns', @@ -68,13 +81,14 @@ const ipnsBundle = { }, doUpdateExpectedPublishTime: (time) => async ({ store, dispatch }) => { - // moderate expectation: publishing should take no longer than average - // between old expectation and the length of the last publish + some buffer - const oldExpectedTime = store.selectExpectedPublishTime() - const avg = Math.floor((time * 1.5 + oldExpectedTime) / 2) - await writeSetting('expectedPublishTime', avg) - dispatch({ type: 'SET_EXPECTED_PUBLISH_TIME', payload: avg }) - } + const { writeSetting } = getLocalStorageUtils() + // moderate expectation: publishing should take no longer than average + // between old expectation and the length of the last publish + some buffer + const oldExpectedTime = store.selectExpectedPublishTime() + const avg = Math.floor((time * 1.5 + oldExpectedTime) / 2) + await writeSetting('expectedPublishTime', avg) + dispatch({ type: 'SET_EXPECTED_PUBLISH_TIME', payload: avg }) +} } export default ipnsBundle diff --git a/src/bundles/pinning.js b/src/bundles/pinning.js index 6f323f0f2..769188307 100644 --- a/src/bundles/pinning.js +++ b/src/bundles/pinning.js @@ -4,13 +4,23 @@ import memoize from 'p-memoize' import { CID } from 'multiformats/cid' import all from 'it-all' -import { readSetting, writeSetting } from './local-storage.js' import { dispatchAsyncProvide } from './files/utils.js' +import { createContextSelector } from '../helpers/context-bridge.jsx' // This bundle leverages createCacheBundle and persistActions for // the persistence layer that keeps pins in IndexDB store // to ensure they are around across restarts/reloads/refactors/releases. +const selectLocalStorageFromContext = createContextSelector('localStorage') + +const getLocalStorageUtils = () => { + const localStorageContext = selectLocalStorageFromContext() + return { + readSetting: localStorageContext?.readSetting || (() => null), + writeSetting: localStorageContext?.writeSetting || (() => {}) + } +} + const CID_PIN_CHECK_BATCH_SIZE = 10 // Pinata returns error when >10 const PIN_CHECK_INTERVAL = 30000 @@ -80,6 +90,7 @@ const uniqueCidBatches = (arrayOfCids, size) => { } const remotePinLs = (ipfs, params) => { + const { readSetting, writeSetting } = getLocalStorageUtils() const backoffs = readSetting('remotesServicesBackoffs') || {} const { service } = params diff --git a/src/contexts/local-storage-context.tsx b/src/contexts/local-storage-context.tsx new file mode 100644 index 000000000..a8ed6880e --- /dev/null +++ b/src/contexts/local-storage-context.tsx @@ -0,0 +1,67 @@ +import React, { createContext, useContext, useCallback } from 'react' +import { useBridgeContext } from '../helpers/context-bridge' + +interface LocalStorageContextValue { + readSetting: (id: string) => string | object | null + writeSetting: (id: string, value: string | number | boolean | object) => void +} + +const LocalStorageContext = createContext(undefined) + +export const LocalStorageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + + /** + * Reads setting from the `localStorage` with a given `id` as JSON. If JSON + * parse is failed setting is interpreted as a string value. + */ + const readSetting = useCallback((id: string): string | object | null => { + let setting: string | null = null + if (window.localStorage) { + try { + setting = window.localStorage.getItem(id) + } catch (error) { + console.error(`Error reading '${id}' value from localStorage`, error) + } + try { + return JSON.parse(setting || '') + } catch (_) { + // res was probably a string, so pass it on. + return setting + } + } + return setting + }, []) + + /** + * Writes setting to localStorage as JSON string + */ + const writeSetting = useCallback((id: string, value: string | number | boolean | object) => { + try { + window.localStorage.setItem(id, JSON.stringify(value)) + } catch (error) { + console.error(`Error writing '${id}' value to localStorage`, error) + } + }, []) + + const contextValue: LocalStorageContextValue = { + readSetting, + writeSetting + } + + // Bridge to redux bundles that still need localStorage utilities + useBridgeContext('localStorage', contextValue) + + return ( + + {children} + + ) +} + +export const useLocalStorage = (): LocalStorageContextValue => { + const context = useContext(LocalStorageContext) + if (!context) { + throw new Error('useLocalStorage must be used within LocalStorageProvider') + } + return context +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 5ac1181b2..e3854c945 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import { DndProvider } from 'react-dnd' import DndBackend from './lib/dnd-backend.js' import { HeliaProvider, ExploreProvider } from 'ipld-explorer-components/providers' import { ContextBridgeProvider } from './helpers/context-bridge.jsx' +import { LocalStorageProvider } from './contexts/local-storage-context' const appVersion = process.env.REACT_APP_VERSION const gitRevision = process.env.REACT_APP_GIT_REV @@ -35,6 +36,7 @@ async function render () { ReactDOM.render( + @@ -44,6 +46,7 @@ async function render () { + , document.getElementById('root') From d741fcb42e43bea59e5ff698873fab15971534e7 Mon Sep 17 00:00:00 2001 From: Ankita Sahu <71656941+SAHU-01@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:43:11 +0530 Subject: [PATCH 2/6] chore:removed redux-bundler local-storage.js --- src/bundles/local-storage.js | 38 ------------------------------------ 1 file changed, 38 deletions(-) delete mode 100644 src/bundles/local-storage.js diff --git a/src/bundles/local-storage.js b/src/bundles/local-storage.js deleted file mode 100644 index 51487e530..000000000 --- a/src/bundles/local-storage.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Reads setting from the `localStorage` with a given `id` as JSON. If JSON - * parse is failed setting is interpreted as a string value. - * @param {string} id - * @returns {string|object|null} - */ -export const readSetting = (id) => { - /** @type {string|null} */ - let setting = null - if (window.localStorage) { - try { - setting = window.localStorage.getItem(id) - } catch (error) { - console.error(`Error reading '${id}' value from localStorage`, error) - } - - try { - return JSON.parse(setting || '') - } catch (_) { - // res was probably a string, so pass it on. - return setting - } - } - - return setting -} - -/** - * @param {string} id - * @param {string|number|boolean|object} value - */ -export const writeSetting = (id, value) => { - try { - window.localStorage.setItem(id, JSON.stringify(value)) - } catch (error) { - console.error(`Error writing '${id}' value to localStorage`, error) - } -} From 0f6bdf4556c05ddb845f98dbf12df5af866f9ed4 Mon Sep 17 00:00:00 2001 From: Ankita Sahu <71656941+SAHU-01@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:57:03 +0530 Subject: [PATCH 3/6] fix: eslint error fix --- src/bundles/gateway.js | 16 +++++++-------- src/bundles/ipfs-provider.js | 28 +++++++++++++------------- src/bundles/ipns.js | 16 +++++++-------- src/contexts/local-storage-context.tsx | 3 +-- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/bundles/gateway.js b/src/bundles/gateway.js index 37f1212da..4cf2f5321 100644 --- a/src/bundles/gateway.js +++ b/src/bundles/gateway.js @@ -187,16 +187,16 @@ const bundle = { doSetAvailableGateway: url => ({ dispatch }) => dispatch({ type: 'SET_AVAILABLE_GATEWAY', payload: url }), doUpdatePublicGateway: (address) => async ({ dispatch }) => { - const { writeSetting } = getLocalStorageUtils() - await writeSetting('ipfsPublicGateway', address) - dispatch({ type: 'SET_PUBLIC_GATEWAY', payload: address }) -}, + const { writeSetting } = getLocalStorageUtils() + await writeSetting('ipfsPublicGateway', address) + dispatch({ type: 'SET_PUBLIC_GATEWAY', payload: address }) + }, doUpdatePublicSubdomainGateway: (address) => async ({ dispatch }) => { - const { writeSetting } = getLocalStorageUtils() - await writeSetting('ipfsPublicSubdomainGateway', address) - dispatch({ type: 'SET_PUBLIC_SUBDOMAIN_GATEWAY', payload: address }) -}, + const { writeSetting } = getLocalStorageUtils() + await writeSetting('ipfsPublicSubdomainGateway', address) + dispatch({ type: 'SET_PUBLIC_SUBDOMAIN_GATEWAY', payload: address }) + }, selectAvailableGateway: (state) => state?.gateway?.availableGateway, diff --git a/src/bundles/ipfs-provider.js b/src/bundles/ipfs-provider.js index aa0488171..6fc44d680 100644 --- a/src/bundles/ipfs-provider.js +++ b/src/bundles/ipfs-provider.js @@ -335,24 +335,24 @@ const selectors = { const actions = { doSetupLocalStorage: () => async () => { - const { readSetting, writeSetting } = getLocalStorageUtils() - - /** For the Explore page (i.e. ipld-explorer-components) */ - const useRemoteGatewaysToExplore = localStorage.getItem('explore.ipld.gatewayEnabled') - if (useRemoteGatewaysToExplore === null) { + const { readSetting, writeSetting } = getLocalStorageUtils() + + /** For the Explore page (i.e. ipld-explorer-components) */ + const useRemoteGatewaysToExplore = localStorage.getItem('explore.ipld.gatewayEnabled') + if (useRemoteGatewaysToExplore === null) { // by default, disable remote gateways for the Explore page (i.e. ipld-explorer-components) - await writeSetting('explore.ipld.gatewayEnabled', false) - } + await writeSetting('explore.ipld.gatewayEnabled', false) + } - const kuboGateway = readSetting('kuboGateway') - if (kuboGateway === null || typeof kuboGateway === 'string' || typeof kuboGateway === 'boolean' || typeof kuboGateway === 'number') { + const kuboGateway = readSetting('kuboGateway') + if (kuboGateway === null || typeof kuboGateway === 'string' || typeof kuboGateway === 'boolean' || typeof kuboGateway === 'number') { // empty or invalid, set defaults - await writeSetting('kuboGateway', { trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) - } else if (/** @type {Record} */(kuboGateway).trustlessBlockBrokerConfig == null) { + await writeSetting('kuboGateway', { trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) + } else if (/** @type {Record} */(kuboGateway).trustlessBlockBrokerConfig == null) { // missing trustlessBlockBrokerConfig, set defaults - await writeSetting('kuboGateway', { ...kuboGateway, trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) - } -}, + await writeSetting('kuboGateway', { ...kuboGateway, trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) + } + }, /** * @returns {function(Context):Promise} diff --git a/src/bundles/ipns.js b/src/bundles/ipns.js index 6cae3a993..9fae63ccd 100644 --- a/src/bundles/ipns.js +++ b/src/bundles/ipns.js @@ -81,14 +81,14 @@ const ipnsBundle = { }, doUpdateExpectedPublishTime: (time) => async ({ store, dispatch }) => { - const { writeSetting } = getLocalStorageUtils() - // moderate expectation: publishing should take no longer than average - // between old expectation and the length of the last publish + some buffer - const oldExpectedTime = store.selectExpectedPublishTime() - const avg = Math.floor((time * 1.5 + oldExpectedTime) / 2) - await writeSetting('expectedPublishTime', avg) - dispatch({ type: 'SET_EXPECTED_PUBLISH_TIME', payload: avg }) -} + const { writeSetting } = getLocalStorageUtils() + // moderate expectation: publishing should take no longer than average + // between old expectation and the length of the last publish + some buffer + const oldExpectedTime = store.selectExpectedPublishTime() + const avg = Math.floor((time * 1.5 + oldExpectedTime) / 2) + await writeSetting('expectedPublishTime', avg) + dispatch({ type: 'SET_EXPECTED_PUBLISH_TIME', payload: avg }) + } } export default ipnsBundle diff --git a/src/contexts/local-storage-context.tsx b/src/contexts/local-storage-context.tsx index a8ed6880e..83fcf74be 100644 --- a/src/contexts/local-storage-context.tsx +++ b/src/contexts/local-storage-context.tsx @@ -9,7 +9,6 @@ interface LocalStorageContextValue { const LocalStorageContext = createContext(undefined) export const LocalStorageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - /** * Reads setting from the `localStorage` with a given `id` as JSON. If JSON * parse is failed setting is interpreted as a string value. @@ -64,4 +63,4 @@ export const useLocalStorage = (): LocalStorageContextValue => { throw new Error('useLocalStorage must be used within LocalStorageProvider') } return context -} \ No newline at end of file +} From 4d37de8d78ce580673a2ba9483bbce09cfc29a54 Mon Sep 17 00:00:00 2001 From: Ankita Sahu <71656941+SAHU-01@users.noreply.github.com> Date: Sat, 30 Aug 2025 18:38:47 +0530 Subject: [PATCH 4/6] fix: Move localStorage utilities to lib directory --- src/bundles/gateway.js | 16 +------ src/bundles/ipfs-provider.js | 16 +------ src/bundles/ipns.js | 24 ++-------- src/bundles/pinning.js | 14 +----- src/contexts/local-storage-context.tsx | 66 -------------------------- src/index.js | 3 -- src/lib/local-storage.js | 38 +++++++++++++++ tsconfig.json | 2 +- 8 files changed, 47 insertions(+), 132 deletions(-) delete mode 100644 src/contexts/local-storage-context.tsx create mode 100644 src/lib/local-storage.js diff --git a/src/bundles/gateway.js b/src/bundles/gateway.js index 4cf2f5321..a074f0b15 100644 --- a/src/bundles/gateway.js +++ b/src/bundles/gateway.js @@ -1,14 +1,4 @@ -import { createContextSelector } from '../helpers/context-bridge.jsx' - -const selectLocalStorageFromContext = createContextSelector('localStorage') - -const getLocalStorageUtils = () => { - const localStorageContext = selectLocalStorageFromContext() - return { - readSetting: localStorageContext?.readSetting || (() => null), - writeSetting: localStorageContext?.writeSetting || (() => {}) - } -} +import { readSetting, writeSetting } from '../lib/local-storage.js' // TODO: switch to dweb.link when https://github.com/ipfs/kubo/issues/7318 export const DEFAULT_PATH_GATEWAY = 'https://ipfs.io' @@ -21,13 +11,11 @@ const IMG_ARRAY = [ ] const readPublicGatewaySetting = () => { - const { readSetting } = getLocalStorageUtils() const setting = readSetting('ipfsPublicGateway') return setting || DEFAULT_PATH_GATEWAY } const readPublicSubdomainGatewaySetting = () => { - const { readSetting } = getLocalStorageUtils() const setting = readSetting('ipfsPublicSubdomainGateway') return setting || DEFAULT_SUBDOMAIN_GATEWAY } @@ -187,13 +175,11 @@ const bundle = { doSetAvailableGateway: url => ({ dispatch }) => dispatch({ type: 'SET_AVAILABLE_GATEWAY', payload: url }), doUpdatePublicGateway: (address) => async ({ dispatch }) => { - const { writeSetting } = getLocalStorageUtils() await writeSetting('ipfsPublicGateway', address) dispatch({ type: 'SET_PUBLIC_GATEWAY', payload: address }) }, doUpdatePublicSubdomainGateway: (address) => async ({ dispatch }) => { - const { writeSetting } = getLocalStorageUtils() await writeSetting('ipfsPublicSubdomainGateway', address) dispatch({ type: 'SET_PUBLIC_SUBDOMAIN_GATEWAY', payload: address }) }, diff --git a/src/bundles/ipfs-provider.js b/src/bundles/ipfs-provider.js index 6fc44d680..1f3a0717a 100644 --- a/src/bundles/ipfs-provider.js +++ b/src/bundles/ipfs-provider.js @@ -7,17 +7,7 @@ import * as Enum from '../lib/enum.js' import { perform } from './task.js' import { contextBridge } from '../helpers/context-bridge' import { createSelector } from 'redux-bundler' -import { createContextSelector } from '../helpers/context-bridge.jsx' - -const selectLocalStorageFromContext = createContextSelector('localStorage') - -const getLocalStorageUtils = () => { - const localStorageContext = selectLocalStorageFromContext() - return { - readSetting: localStorageContext?.readSetting || (() => null), - writeSetting: localStorageContext?.writeSetting || (() => {}) - } -} +import { readSetting, writeSetting } from '../lib/local-storage.js' /** * @typedef {import('ipfs').IPFSService} IPFSService @@ -164,7 +154,6 @@ const init = () => { * @returns {HTTPClientOptions|string|null} */ const readAPIAddressSetting = () => { - const { readSetting } = getLocalStorageUtils() const setting = readSetting('ipfsApi') return setting == null ? null : asAPIOptions(setting) } @@ -335,8 +324,6 @@ const selectors = { const actions = { doSetupLocalStorage: () => async () => { - const { readSetting, writeSetting } = getLocalStorageUtils() - /** For the Explore page (i.e. ipld-explorer-components) */ const useRemoteGatewaysToExplore = localStorage.getItem('explore.ipld.gatewayEnabled') if (useRemoteGatewaysToExplore === null) { @@ -452,7 +439,6 @@ const actions = { * @returns {function(Context):Promise} */ doUpdateIpfsApiAddress: (address) => async (context) => { - const { writeSetting } = getLocalStorageUtils() const apiAddress = asAPIOptions(address) if (apiAddress == null) { context.dispatch({ type: ACTIONS.IPFS_API_ADDRESS_INVALID }) diff --git a/src/bundles/ipns.js b/src/bundles/ipns.js index 9fae63ccd..bcf852e10 100644 --- a/src/bundles/ipns.js +++ b/src/bundles/ipns.js @@ -1,24 +1,11 @@ import all from 'it-all' +import { readSetting, writeSetting } from '../lib/local-storage.js' import { dispatchAsyncProvide } from './files/utils.js' -import { createContextSelector } from '../helpers/context-bridge.jsx' -const selectLocalStorageFromContext = createContextSelector('localStorage') - -const getLocalStorageUtils = () => { - const localStorageContext = selectLocalStorageFromContext() - return { - readSetting: localStorageContext?.readSetting || (() => null), - writeSetting: localStorageContext?.writeSetting || (() => {}) - } -} - -const init = () => { - const { readSetting } = getLocalStorageUtils() - return { - keys: [], - expectedPublishTime: readSetting('expectedPublishTime') || 60 - } -} +const init = () => ({ + keys: [], + expectedPublishTime: readSetting('expectedPublishTime') || 60 +}) const ipnsBundle = { name: 'ipns', @@ -81,7 +68,6 @@ const ipnsBundle = { }, doUpdateExpectedPublishTime: (time) => async ({ store, dispatch }) => { - const { writeSetting } = getLocalStorageUtils() // moderate expectation: publishing should take no longer than average // between old expectation and the length of the last publish + some buffer const oldExpectedTime = store.selectExpectedPublishTime() diff --git a/src/bundles/pinning.js b/src/bundles/pinning.js index 769188307..15e62eba9 100644 --- a/src/bundles/pinning.js +++ b/src/bundles/pinning.js @@ -3,24 +3,13 @@ import { pinningServiceTemplates } from '../constants/pinning.js' import memoize from 'p-memoize' import { CID } from 'multiformats/cid' import all from 'it-all' - +import { readSetting, writeSetting } from '../lib/local-storage.js' import { dispatchAsyncProvide } from './files/utils.js' -import { createContextSelector } from '../helpers/context-bridge.jsx' // This bundle leverages createCacheBundle and persistActions for // the persistence layer that keeps pins in IndexDB store // to ensure they are around across restarts/reloads/refactors/releases. -const selectLocalStorageFromContext = createContextSelector('localStorage') - -const getLocalStorageUtils = () => { - const localStorageContext = selectLocalStorageFromContext() - return { - readSetting: localStorageContext?.readSetting || (() => null), - writeSetting: localStorageContext?.writeSetting || (() => {}) - } -} - const CID_PIN_CHECK_BATCH_SIZE = 10 // Pinata returns error when >10 const PIN_CHECK_INTERVAL = 30000 @@ -90,7 +79,6 @@ const uniqueCidBatches = (arrayOfCids, size) => { } const remotePinLs = (ipfs, params) => { - const { readSetting, writeSetting } = getLocalStorageUtils() const backoffs = readSetting('remotesServicesBackoffs') || {} const { service } = params diff --git a/src/contexts/local-storage-context.tsx b/src/contexts/local-storage-context.tsx deleted file mode 100644 index 83fcf74be..000000000 --- a/src/contexts/local-storage-context.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { createContext, useContext, useCallback } from 'react' -import { useBridgeContext } from '../helpers/context-bridge' - -interface LocalStorageContextValue { - readSetting: (id: string) => string | object | null - writeSetting: (id: string, value: string | number | boolean | object) => void -} - -const LocalStorageContext = createContext(undefined) - -export const LocalStorageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - /** - * Reads setting from the `localStorage` with a given `id` as JSON. If JSON - * parse is failed setting is interpreted as a string value. - */ - const readSetting = useCallback((id: string): string | object | null => { - let setting: string | null = null - if (window.localStorage) { - try { - setting = window.localStorage.getItem(id) - } catch (error) { - console.error(`Error reading '${id}' value from localStorage`, error) - } - try { - return JSON.parse(setting || '') - } catch (_) { - // res was probably a string, so pass it on. - return setting - } - } - return setting - }, []) - - /** - * Writes setting to localStorage as JSON string - */ - const writeSetting = useCallback((id: string, value: string | number | boolean | object) => { - try { - window.localStorage.setItem(id, JSON.stringify(value)) - } catch (error) { - console.error(`Error writing '${id}' value to localStorage`, error) - } - }, []) - - const contextValue: LocalStorageContextValue = { - readSetting, - writeSetting - } - - // Bridge to redux bundles that still need localStorage utilities - useBridgeContext('localStorage', contextValue) - - return ( - - {children} - - ) -} - -export const useLocalStorage = (): LocalStorageContextValue => { - const context = useContext(LocalStorageContext) - if (!context) { - throw new Error('useLocalStorage must be used within LocalStorageProvider') - } - return context -} diff --git a/src/index.js b/src/index.js index e3854c945..5ac1181b2 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,6 @@ import { DndProvider } from 'react-dnd' import DndBackend from './lib/dnd-backend.js' import { HeliaProvider, ExploreProvider } from 'ipld-explorer-components/providers' import { ContextBridgeProvider } from './helpers/context-bridge.jsx' -import { LocalStorageProvider } from './contexts/local-storage-context' const appVersion = process.env.REACT_APP_VERSION const gitRevision = process.env.REACT_APP_GIT_REV @@ -36,7 +35,6 @@ async function render () { ReactDOM.render( - @@ -46,7 +44,6 @@ async function render () { - , document.getElementById('root') diff --git a/src/lib/local-storage.js b/src/lib/local-storage.js new file mode 100644 index 000000000..51487e530 --- /dev/null +++ b/src/lib/local-storage.js @@ -0,0 +1,38 @@ +/** + * Reads setting from the `localStorage` with a given `id` as JSON. If JSON + * parse is failed setting is interpreted as a string value. + * @param {string} id + * @returns {string|object|null} + */ +export const readSetting = (id) => { + /** @type {string|null} */ + let setting = null + if (window.localStorage) { + try { + setting = window.localStorage.getItem(id) + } catch (error) { + console.error(`Error reading '${id}' value from localStorage`, error) + } + + try { + return JSON.parse(setting || '') + } catch (_) { + // res was probably a string, so pass it on. + return setting + } + } + + return setting +} + +/** + * @param {string} id + * @param {string|number|boolean|object} value + */ +export const writeSetting = (id, value) => { + try { + window.localStorage.setItem(id, JSON.stringify(value)) + } catch (error) { + console.error(`Error writing '${id}' value to localStorage`, error) + } +} diff --git a/tsconfig.json b/tsconfig.json index ab8da7f60..321c22314 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -67,7 +67,7 @@ "src/bundles/ipfs-provider.js", "src/bundles/retry-init.js", "src/bundles/retry-init.js", - "src/bundles/local-storage.js", + "src/lib/local-storage.js", "src/bundles/task.js", "src/lib/count-dirs.js", "src/lib/sort.js", From 6dc2b4d8391ca9e318292c2cc7327be60c47b517 Mon Sep 17 00:00:00 2001 From: Ankita Sahu <71656941+SAHU-01@users.noreply.github.com> Date: Mon, 29 Sep 2025 10:49:59 +0530 Subject: [PATCH 5/6] fix: indentation fix --- src/bundles/ipfs-provider.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bundles/ipfs-provider.js b/src/bundles/ipfs-provider.js index eff5ba917..e9db68cf0 100644 --- a/src/bundles/ipfs-provider.js +++ b/src/bundles/ipfs-provider.js @@ -327,16 +327,16 @@ const actions = { /** For the Explore page (i.e. ipld-explorer-components) */ const useRemoteGatewaysToExplore = localStorage.getItem('explore.ipld.gatewayEnabled') if (useRemoteGatewaysToExplore === null) { - // by default, disable remote gateways for the Explore page (i.e. ipld-explorer-components) + // by default, disable remote gateways for the Explore page (i.e. ipld-explorer-components) await writeSetting('explore.ipld.gatewayEnabled', false) } const kuboGateway = readSetting('kuboGateway') if (kuboGateway === null || typeof kuboGateway === 'string' || typeof kuboGateway === 'boolean' || typeof kuboGateway === 'number') { - // empty or invalid, set defaults + // empty or invalid, set defaults await writeSetting('kuboGateway', { trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) } else if (/** @type {Record} */(kuboGateway).trustlessBlockBrokerConfig == null) { - // missing trustlessBlockBrokerConfig, set defaults + // missing trustlessBlockBrokerConfig, set defaults await writeSetting('kuboGateway', { ...kuboGateway, trustlessBlockBrokerConfig: { init: { allowLocal: true, allowInsecure: false } } }) } }, From 20f1339977c7bb230885800ef71bef40e4e65612 Mon Sep 17 00:00:00 2001 From: Ankita Sahu <71656941+SAHU-01@users.noreply.github.com> Date: Mon, 29 Sep 2025 10:57:23 +0530 Subject: [PATCH 6/6] fix: import for bundles/local-storage.js fixed and build error resolved --- src/files/FilesPage.js | 2 +- tsconfig.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/files/FilesPage.js b/src/files/FilesPage.js index 64d70973c..8fd5f53e7 100644 --- a/src/files/FilesPage.js +++ b/src/files/FilesPage.js @@ -6,7 +6,7 @@ import { withTranslation, Trans } from 'react-i18next' import ReactJoyride from 'react-joyride' // Lib import { filesTour } from '../lib/tours.js' -import { readSetting, writeSetting } from '../bundles/local-storage.js' +import { readSetting, writeSetting } from '../lib/local-storage.js' // Components import ContextMenu from './context-menu/ContextMenu.js' import withTour from '../components/tour/withTour.js' diff --git a/tsconfig.json b/tsconfig.json index ffc158e5b..9aec196ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -68,7 +68,6 @@ "src/bundles/retry-init.js", "src/bundles/retry-init.js", "src/lib/local-storage.js", - "src/bundles/local-storage.js", "src/bundles/task.js", "src/bundles/gateway.js", "src/lib/count-dirs.js",