Safe storage - ram290476/investor-dashboard GitHub Wiki
Introduced in PR #215 (issue #214). Code: apps/web/safe-storage.js, tests: apps/web/safe-storage.test.js.
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.
Some browsers throw an error when a page touches Web Storage:
- Older Safari in private mode throws
QuotaExceededErroronsetItem. - Chrome or Firefox with site data blocked throw as soon as the page reads
window.sessionStorageorwindow.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
statevalue 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.
createSafeStorage(kind, { cookieFallback }) picks a backend once, in this order:
-
Web Storage. It tests the store by writing and removing a probe key (
__invdash_probe__). If that works, it is used. -
First-party cookie (only when
cookieFallback: true). Each key becomes a cookie namedinvdash_s_<key>(session) orinvdash_l_<key>(local), withMax-Age=600(10 minutes),Path=/,SameSite=LaxandSecure. A probe cookie checks that cookies work first. This is what keeps the PKCE state alive across the sign-in redirect. - 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".
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.
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
setItemthrowsQuotaExceededError: nothing throws, and it falls back to memory. - Everything blocked: no call throws.
-
app.jsnever touches Web Storage directly, and the default theme is set on first paint.
- 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,
SecureandSameSite=Lax, and only hold short-lived sign-in values, never tokens.