From 81cbbe6cca20c639188dc107ded462d3c6f3ac44 Mon Sep 17 00:00:00 2001 From: daixihegu Date: Thu, 25 Jun 2026 10:39:47 +0800 Subject: [PATCH] fix: guard wallet id storage access Signed-off-by: daixihegu --- .changeset/tidy-wallet-storage.md | 5 +++ .../src/wallets/latestWalletId.test.ts | 31 +++++++++++++++++++ .../rainbowkit/src/wallets/latestWalletId.ts | 18 +++++------ .../src/wallets/recentWalletIds.test.ts | 26 ++++++++++++++++ .../rainbowkit/src/wallets/recentWalletIds.ts | 10 +++--- .../src/wallets/safeLocalStorage.ts | 29 +++++++++++++++++ 6 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 .changeset/tidy-wallet-storage.md create mode 100644 packages/rainbowkit/src/wallets/latestWalletId.test.ts create mode 100644 packages/rainbowkit/src/wallets/recentWalletIds.test.ts create mode 100644 packages/rainbowkit/src/wallets/safeLocalStorage.ts diff --git a/.changeset/tidy-wallet-storage.md b/.changeset/tidy-wallet-storage.md new file mode 100644 index 0000000000..7b8d6ae3bf --- /dev/null +++ b/.changeset/tidy-wallet-storage.md @@ -0,0 +1,5 @@ +--- +'@rainbow-me/rainbowkit': patch +--- + +Handle unavailable localStorage when reading and writing recent wallet ids. diff --git a/packages/rainbowkit/src/wallets/latestWalletId.test.ts b/packages/rainbowkit/src/wallets/latestWalletId.test.ts new file mode 100644 index 0000000000..8645969c6d --- /dev/null +++ b/packages/rainbowkit/src/wallets/latestWalletId.test.ts @@ -0,0 +1,31 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { + addLatestWalletId, + clearLatestWalletId, + getLatestWalletId, +} from './latestWalletId'; + +function mockUnavailableLocalStorage() { + vi.spyOn(window, 'localStorage', 'get').mockImplementation(() => { + throw new Error('localStorage unavailable'); + }); +} + +describe('latestWalletId', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('returns an empty wallet id when localStorage is unavailable', () => { + mockUnavailableLocalStorage(); + + expect(getLatestWalletId()).toBe(''); + }); + + it('ignores latest wallet updates when localStorage is unavailable', () => { + mockUnavailableLocalStorage(); + + expect(() => addLatestWalletId('rainbow')).not.toThrow(); + expect(() => clearLatestWalletId()).not.toThrow(); + }); +}); diff --git a/packages/rainbowkit/src/wallets/latestWalletId.ts b/packages/rainbowkit/src/wallets/latestWalletId.ts index 380298908f..6115d4038e 100644 --- a/packages/rainbowkit/src/wallets/latestWalletId.ts +++ b/packages/rainbowkit/src/wallets/latestWalletId.ts @@ -1,19 +1,19 @@ +import { + getStorageItem, + removeStorageItem, + setStorageItem, +} from './safeLocalStorage'; + const storageKey = 'rk-latest-id'; export function getLatestWalletId(): string { - return typeof window !== 'undefined' - ? window.localStorage.getItem(storageKey) || '' - : ''; + return getStorageItem(storageKey) || ''; } export function addLatestWalletId(walletId: string): void { - if (typeof window !== 'undefined') { - window.localStorage.setItem(storageKey, walletId); - } + setStorageItem(storageKey, walletId); } export function clearLatestWalletId(): void { - if (typeof window !== 'undefined') { - window.localStorage.removeItem(storageKey); - } + removeStorageItem(storageKey); } diff --git a/packages/rainbowkit/src/wallets/recentWalletIds.test.ts b/packages/rainbowkit/src/wallets/recentWalletIds.test.ts new file mode 100644 index 0000000000..454b1071f9 --- /dev/null +++ b/packages/rainbowkit/src/wallets/recentWalletIds.test.ts @@ -0,0 +1,26 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { addRecentWalletId, getRecentWalletIds } from './recentWalletIds'; + +function mockUnavailableLocalStorage() { + vi.spyOn(window, 'localStorage', 'get').mockImplementation(() => { + throw new Error('localStorage unavailable'); + }); +} + +describe('recentWalletIds', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('returns no recent wallet ids when localStorage is unavailable', () => { + mockUnavailableLocalStorage(); + + expect(getRecentWalletIds()).toEqual([]); + }); + + it('ignores recent wallet updates when localStorage is unavailable', () => { + mockUnavailableLocalStorage(); + + expect(() => addRecentWalletId('rainbow')).not.toThrow(); + }); +}); diff --git a/packages/rainbowkit/src/wallets/recentWalletIds.ts b/packages/rainbowkit/src/wallets/recentWalletIds.ts index 4dcbb844be..250901d30d 100644 --- a/packages/rainbowkit/src/wallets/recentWalletIds.ts +++ b/packages/rainbowkit/src/wallets/recentWalletIds.ts @@ -1,3 +1,5 @@ +import { getStorageItem, setStorageItem } from './safeLocalStorage'; + const storageKey = 'rk-recent'; function safeParseJsonArray(string: string | null): T[] { @@ -10,9 +12,7 @@ function safeParseJsonArray(string: string | null): T[] { } export function getRecentWalletIds(): string[] { - return typeof window !== 'undefined' - ? safeParseJsonArray(window.localStorage.getItem(storageKey)) - : []; + return safeParseJsonArray(getStorageItem(storageKey)); } function dedupe(array: T[]): T[] { @@ -22,7 +22,5 @@ function dedupe(array: T[]): T[] { export function addRecentWalletId(walletId: string): void { const newValue = dedupe([walletId, ...getRecentWalletIds()]); - if (typeof window !== 'undefined') { - window.localStorage.setItem(storageKey, JSON.stringify(newValue)); - } + setStorageItem(storageKey, JSON.stringify(newValue)); } diff --git a/packages/rainbowkit/src/wallets/safeLocalStorage.ts b/packages/rainbowkit/src/wallets/safeLocalStorage.ts new file mode 100644 index 0000000000..4efdec4480 --- /dev/null +++ b/packages/rainbowkit/src/wallets/safeLocalStorage.ts @@ -0,0 +1,29 @@ +function getSafeLocalStorage(): Storage | undefined { + if (typeof window === 'undefined') return; + + try { + return window.localStorage; + } catch { + return; + } +} + +export function getStorageItem(key: string): string | null { + try { + return getSafeLocalStorage()?.getItem(key) ?? null; + } catch { + return null; + } +} + +export function setStorageItem(key: string, value: string): void { + try { + getSafeLocalStorage()?.setItem(key, value); + } catch {} +} + +export function removeStorageItem(key: string): void { + try { + getSafeLocalStorage()?.removeItem(key); + } catch {} +}