Caching and browser persistence
Why this page exists
State survives page reloads in six different places, written by reducers, services and interceptors. "Why does it still show the old value" and "why did my override disappear" are almost always answered here.
localStorage
| Key | Written by | Contents and lifetime |
|---|---|---|
|
|
The merged user record including the JWT. Its presence is what makes the app consider
you logged in — |
|
|
The last supplier-list query string. When absent the default is
|
|
|
Persisted layout preferences — CRM/supplier-info panel states per route pattern. |
|
|
Reference data — timezones, funding periods, US states, countries. 24-hour TTL. |
|
|
Cached |
|
|
sessionStorage
Per tab, so two tabs can legitimately disagree.
| Key | Written by | Contents |
|---|---|---|
|
|
The URL to return to after login, with |
|
|
The resolved flag set for this application as |
|
Same |
Timestamp of the last override change. Compared against the global setting’s
|
|
|
EVA’s resolved flag set — deliberately separate keys so the two applications' overrides never collide. |
|
Same |
EVA’s override timestamp. |
Flag keys are cleared by clearAvailableFlagsExecStatus(), which LoginComponent calls
on entry. See feature flags.
Cookies
One cookie, written through shared/services/cookie-service/cookie-service.ts (a thin
wrapper over ngx-cookie-service):
| Cookie | Detail |
|---|---|
|
The JWT, set by |
CookieService also exposes getAllCookies() and deleteAllCookies(); neither is used in
the current code.
The HTTP response cache
HttpCacheAbstract → HttpCacheLocalStorage, bound in AppModule:
private readonly localStorageCacheKeyPrefix = 'ng-cache-';
private readonly validCacheTimespan = 5 * 60 * 1000; // 5 minutes
get(req) → JSON.parse(localStorage[key]); if older than 5 min, removeItem and return null
put(req, resp) → localStorage[key] = { data: resp, timestamp: Date.now() }
key → 'ng-cache-' + Md5.hashAsciiStr(JSON.stringify(req))
Every method is wrapped in try/catch returning null or doing nothing, so a quota error
or corrupt entry degrades silently.
CachingInterceptor implements read-through caching for GET`s against this cache — and
is not registered in `AppModule’s `HTTP_INTERCEPTORS. It is dormant.
|
If you enable
|
Domain caching in the store
The application’s real caching strategy is in-state, not HTTP-level.
| Cache | Behaviour |
|---|---|
|
Every supplier visited this session. |
|
Per-target CRM timeline state, so switching between suppliers does not re-fetch. |
|
Reference data, additionally persisted with a 24-hour TTL. |
|
The settings collection; |
|
The gateway list. |
Effect-level |
Nearly every fetch effect checks state first and returns an empty observable on a hit. |
|
Several supplier reducer cases reset the slice, which silently drops these caches:
|
In-memory service caches
Easy to miss because they are neither store nor storage.
| Cache | Behaviour and hazard |
|---|---|
|
|
|
Caches |
|
|
|
A |
|
Memoises the pricing-defaults fixture. See advances. |
|
The shared SSE stream — never reset, so a dead stream is never replaced. See real-time streams. |
|
Backed by the |
What clears what
| Action | Clears |
|---|---|
Logout ( |
All of |
Visiting |
|
Editing flags in DEV Admin → save to global settings |
Effectively invalidates every user’s session overrides, because the new |
Page reload |
All in-memory service caches and the whole store, except what is rehydrated from storage
( |
New tab |
|
24 hours |
|
5 minutes |
|
Security notes
| Item | Consideration |
|---|---|
JWT in |
Readable by any script in the page. Accepted for an internal tool on a controlled network; do not extend the pattern. |
JWT in a cookie on the parent domain |
Necessary for the EVA seam. |
JWT in the iframe URL on localhost |
Appears in browser history. Local development only. |
Request ring buffer in state |
The |
Supplier PII in state |
|
Practical guidance
-
Reading persisted state: go through the store, not
localStorage, except where existing code already does otherwise (EvaComponent,UsersService). -
Adding a storage key: define the key as a
constnext to its writer, and add it to this page. -
Needing fresh domain data: dispatch the fetch action with
refresh: truerather than clearing storage. -
Debugging "stale data": check the store cache first (
supplier.suppliers[key]), then the service-level `BehaviorSubject`s, then storage. -
Do not rely on
localStorage.clear()semantics — if you add a key that must survive logout, logout will delete it.