diff --git a/components/offline/offline-host.tsx b/components/offline/offline-host.tsx index 5e4f7cffd..23208b1d8 100644 --- a/components/offline/offline-host.tsx +++ b/components/offline/offline-host.tsx @@ -119,7 +119,7 @@ export function OfflineHost() { return () => { removeGate(); stop(); - setOfflineNotifier(() => {}); + setOfflineNotifier(null); }; }, []); diff --git a/lib/offline/__tests__/storage-health.test.ts b/lib/offline/__tests__/storage-health.test.ts index 064542837..2a4b99dc2 100644 --- a/lib/offline/__tests__/storage-health.test.ts +++ b/lib/offline/__tests__/storage-health.test.ts @@ -185,6 +185,19 @@ describe('a store the browser cleared', () => { expect(useOfflineStore.getState().health.acc.reset).toBeUndefined(); }); + it('says so also when the store opened before the host component was there', async () => { + setOfflineNotifier(null); + await openAccountKey('acc'); + hadStore('acc'); + FakeWorker.created = true; + const worker = await openAsOwner('acc'); + await vi.waitFor(() => expect(worker.posted.some((m) => m.kind === 'open')).toBe(true)); + await vi.waitFor(() => expect(useOfflineStore.getState().storeLost).toEqual(['acc'])); + expect(notices).toEqual([]); + setOfflineNotifier((notice) => notices.push(notice)); + expect(notices).toContainEqual({ kind: 'store-reset', lost: 0 }); + }); + it('keeps only numbers about it outside the store', () => { writeHealth('acc', { pending: 2, reset: { at: 5, lost: 2 }, lastSync: 9 }); expect(JSON.parse(localStorage.getItem(OFFLINE_HEALTH_KEY)!)).toEqual({ acc: { pending: 2, reset: { at: 5, lost: 2 }, lastSync: 9 } }); diff --git a/lib/offline/offline-mode.ts b/lib/offline/offline-mode.ts index 453b54be8..8966c614b 100644 --- a/lib/offline/offline-mode.ts +++ b/lib/offline/offline-mode.ts @@ -107,11 +107,20 @@ export type OfflineNotice = | { kind: 'forbidden' } | { kind: 'back-online-reload' }; -let notify: (notice: OfflineNotice) => void = () => {}; +/** + * The auth store opens the local store at start, and that can be before the + * host component has mounted: what's said until then waits for it. + */ +const earlyNotices: OfflineNotice[] = []; +const holdNotice = (notice: OfflineNotice) => { + if (earlyNotices.length < 20) earlyNotices.push(notice); +}; +let notify: (notice: OfflineNotice) => void = holdNotice; -/** Where the notices go (the host component turns them into toasts). */ -export function setOfflineNotifier(fn: (notice: OfflineNotice) => void): void { - notify = fn; +/** Where the notices go (the host component turns them into toasts). Null: none is there at the moment. */ +export function setOfflineNotifier(fn: ((notice: OfflineNotice) => void) | null): void { + notify = fn ?? holdNotice; + if (fn) for (const notice of earlyNotices.splice(0)) fn(notice); } export function offlineNotify(notice: OfflineNotice): void {