diff --git a/.changeset/idb-storage-memory-fallback.md b/.changeset/idb-storage-memory-fallback.md new file mode 100644 index 00000000..0499203d --- /dev/null +++ b/.changeset/idb-storage-memory-fallback.md @@ -0,0 +1,5 @@ +--- +'accounts': patch +--- + +Fell back to in-memory storage in `Storage.idb()` when IndexedDB is unavailable (e.g. React Native/Expo, some SSR), instead of throwing `ReferenceError: indexedDB is not defined` on the first read or write. diff --git a/src/core/Storage.test.ts b/src/core/Storage.test.ts new file mode 100644 index 00000000..0d23532d --- /dev/null +++ b/src/core/Storage.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from 'vp/test' + +import * as Storage from './Storage.js' + +// In runtimes that expose `window` but not IndexedDB (React Native/Expo, some +// SSR), `idb()` is still selected as the default storage. It must degrade to a +// working in-memory adapter instead of throwing: idb-keyval treats an +// `undefined` store as "use the default store", which opens IndexedDB and +// throws `ReferenceError: indexedDB is not defined`. +test('idb: degrades to in-memory storage when IndexedDB is unavailable', async () => { + expect(typeof indexedDB).toBe('undefined') + + const storage = Storage.idb() + + await storage.setItem('accounts', ['0xabc']) + expect(await storage.getItem('accounts')).toMatchInlineSnapshot(` + [ + "0xabc", + ] + `) + + await storage.removeItem('accounts') + expect(await storage.getItem('accounts')).toMatchInlineSnapshot(`null`) +}) diff --git a/src/core/Storage.ts b/src/core/Storage.ts index dea85524..95cf7378 100644 --- a/src/core/Storage.ts +++ b/src/core/Storage.ts @@ -156,9 +156,13 @@ export declare namespace cookie { type Options = from.Options } -/** Creates an IndexedDB-backed storage adapter. Stores raw values (no JSON serialization). */ +/** Creates an IndexedDB-backed storage adapter. Stores raw values (no JSON serialization). Falls back to in-memory storage in runtimes without IndexedDB (e.g. React Native, some SSR). */ export function idb(options: idb.Options = {}): Storage { - const store = typeof indexedDB !== 'undefined' ? createStore('tempo', 'store') : undefined + // idb-keyval treats an `undefined` store as "use the default store", which + // opens IndexedDB and throws where it is unavailable — so degrade to memory + // rather than passing `undefined` through. + if (typeof indexedDB === 'undefined') return memory(options) + const store = createStore('tempo', 'store') const storage = from( { async getItem(name) {