From 5a9cf49d6b39cb2fb9e9cb5465b117fdab7bf954 Mon Sep 17 00:00:00 2001 From: Nelito Junior Date: Wed, 25 Dec 2024 18:45:54 -0300 Subject: [PATCH] feat(local-storage): enhance local storage availability checks FE-1221 (#132) --- .changeset/curvy-news-work.md | 5 ++ .changeset/flat-keys-melt.md | 5 ++ .github/workflows/pr.yaml | 1 - packages/local-storage/src/index.ts | 46 +++++++++++++++---- .../react-xstore/src/ReactFactory.test.tsx | 12 ++++- 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 .changeset/curvy-news-work.md create mode 100644 .changeset/flat-keys-melt.md diff --git a/.changeset/curvy-news-work.md b/.changeset/curvy-news-work.md new file mode 100644 index 0000000..230fd8b --- /dev/null +++ b/.changeset/curvy-news-work.md @@ -0,0 +1,5 @@ +--- +'@fuels/react-xstore': minor +--- + +Update tests to handle the new local-storage checks. diff --git a/.changeset/flat-keys-melt.md b/.changeset/flat-keys-melt.md new file mode 100644 index 0000000..3282921 --- /dev/null +++ b/.changeset/flat-keys-melt.md @@ -0,0 +1,5 @@ +--- +'@fuels/local-storage': minor +--- + +Check for localStorage availability diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 6a7d5c3..cf05c5d 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -42,7 +42,6 @@ jobs: steps: - uses: actions/checkout@v3 with: - # need this to get full git-history/clone in order to build changelogs and check changesets fetch-depth: 0 - name: CI Setup diff --git a/packages/local-storage/src/index.ts b/packages/local-storage/src/index.ts index 2dd15de..7b59f87 100644 --- a/packages/local-storage/src/index.ts +++ b/packages/local-storage/src/index.ts @@ -3,10 +3,24 @@ import { EventEmitter } from 'events'; export class LocalStorage { private prefix!: string; private emitter!: EventEmitter; + private isAvailable: boolean; constructor(prefix: string, emitter?: EventEmitter) { this.prefix = prefix; this.emitter = emitter ?? new EventEmitter(); + this.isAvailable = this.checkLocalStorageAvailability(); + } + + private checkLocalStorageAvailability(): boolean { + try { + if (typeof window === 'undefined' || !window.localStorage) return false; + const testKey = '__storage_test__'; + localStorage.setItem(testKey, testKey); + localStorage.removeItem(testKey); + return true; + } catch { + return false; + } } subscribe = (listener: (...args: T) => void) => { @@ -18,11 +32,17 @@ export class LocalStorage { }; setItem = (key: string, value: T) => { - localStorage.setItem(this.createKey(key), JSON.stringify(value)); - this.dispatchChange(key, value); + if (!this.isAvailable) return; + try { + localStorage.setItem(this.createKey(key), JSON.stringify(value)); + this.dispatchChange(key, value); + } catch (error) { + // Silently fail if localStorage is not available or quota is exceeded + } }; getItem = (key: string): T | null => { + if (!this.isAvailable) return null; try { const data = localStorage.getItem(this.createKey(key)); return data ? JSON.parse(data) : null; @@ -32,15 +52,25 @@ export class LocalStorage { }; clear = () => { - Object.keys(localStorage) - .filter((key) => key.startsWith(this.prefix)) - .forEach((key) => localStorage.removeItem(key)); - this.dispatchChange(); + if (!this.isAvailable) return; + try { + Object.keys(localStorage) + .filter((key) => key.startsWith(this.prefix)) + .forEach((key) => localStorage.removeItem(key)); + this.dispatchChange(); + } catch (error) { + // Silently fail if localStorage operations fail + } }; removeItem = (key: string) => { - localStorage.removeItem(this.createKey(key)); - this.dispatchChange(); + if (!this.isAvailable) return; + try { + localStorage.removeItem(this.createKey(key)); + this.dispatchChange(); + } catch (error) { + // Silently fail if localStorage operations fail + } }; // --------------------------------------------------------------------------- diff --git a/packages/react-xstore/src/ReactFactory.test.tsx b/packages/react-xstore/src/ReactFactory.test.tsx index 3f4ae81..f9b6a36 100644 --- a/packages/react-xstore/src/ReactFactory.test.tsx +++ b/packages/react-xstore/src/ReactFactory.test.tsx @@ -32,7 +32,17 @@ describe('ReactFactory', () => { }, opts); expect(result.current).toEqual([payload]); - expect(spy).toBeCalledTimes(1); + expect( + spy.mock.calls.filter((call) => !call[0].includes('__storage_test__')) + .length, + ).toBe(1); + expect( + spy.mock.calls.some( + (call) => + call[0] === '@xstate/store_todos' && + JSON.parse(call[1]).context.todos[0].id === payload.id, + ), + ).toBe(true); let storageState: ReturnType; expect(() => {