Safe storage - ram290476/investor-dashboard GitHub Wiki

Safe storage (private-mode-safe browser storage)

Introduced in PR #215 (issue #214). Code: apps/web/safe-storage.js, tests: apps/web/safe-storage.test.js.

What it is

A small wrapper around the browser's Web Storage (sessionStorage and localStorage) that never throws. It gives the dashboard one storage object with the usual getItem, setItem and removeItem calls, and quietly falls back to another place to keep data when Web Storage isn't usable.

The problem it fixes

Some browsers throw an error when a page touches Web Storage:

  • Older Safari in private mode throws QuotaExceededError on setItem.
  • Chrome or Firefox with site data blocked throw as soon as the page reads window.sessionStorage or window.localStorage.

Before this change, app.js used Web Storage directly, so in those browsers:

  • Sign-in broke. The Cognito sign-in (OAuth with PKCE) saves a verifier and a state value before redirecting to Cognito and reads them back on return. If saving threw, or the values were lost across the redirect, sign-in failed.
  • The saved theme and preferences didn't load, and an uncaught error could stop the page from rendering.

How it works

createSafeStorage(kind, { cookieFallback }) picks a backend once, in this order:

  1. Web Storage. It tests the store by writing and removing a probe key (__invdash_probe__). If that works, it is used.
  2. First-party cookie (only when cookieFallback: true). Each key becomes a cookie named invdash_s_<key> (session) or invdash_l_<key> (local), with Max-Age=600 (10 minutes), Path=/, SameSite=Lax and Secure. A probe cookie checks that cookies work first. This is what keeps the PKCE state alive across the sign-in redirect.
  3. In-memory map. Always works, but doesn't survive a reload or redirect.

Every call is also wrapped in try/catch. If the chosen backend throws later, the value goes to, or is read from, the in-memory copy instead, so no call ever throws. The backend property reports "web", "cookie" or "memory".

Where the app uses it

In apps/web/app.js:

  • authStorage = createSafeStorage("sessionStorage", { cookieFallback: true }) holds the sign-in values: the PKCE verifier, state, the page path and hash to return to, and the dashboard ticker. If the backend ends up being memory and sign-in can't complete, the app shows a clear error saying so instead of failing silently.
  • localPrefs = createSafeStorage("localStorage") holds preferences such as the metric-review filter (no cookie fallback).
  • The default theme is applied on first paint, so the page looks right even when the saved theme can't be read.

Tests

safe-storage.test.js covers:

  • Web Storage is used when it works.
  • Private mode where the storage getter throws: the cookie fallback keeps the PKCE state across the redirect.
  • Safari private mode where setItem throws QuotaExceededError: nothing throws, and it falls back to memory.
  • Everything blocked: no call throws.
  • app.js never touches Web Storage directly, and the default theme is set on first paint.

Limits and risks

  • With the memory backend, nothing survives a reload, so sign-in can't complete and preferences reset. The user sees an error instead of a broken page.
  • Cookie values are capped at about 4 KB, and the 10-minute lifetime is only meant to cover the sign-in round trip.
  • The cookies are first-party, Secure and SameSite=Lax, and only hold short-lived sign-in values, never tokens.
⚠️ **GitHub.com Fallback** ⚠️