fix(guests): expire stale tokens, sync tabs, survive unwritable storage
Codex review round 1 on #1268. All three findings are consequences of the storage move itself. Expired tokens now read as absent. GUEST_TOKEN_TTL is 30 days and sessionStorage almost never survived that long, so 'stored but expired' was unreachable before; persisting the token makes it routine. Nothing else clears it -- the 401 handler in config/api.ts only drops gallery_event_<slug> -- so the visitor was shown as signed in while every like 401'd, and ensureIdentity() short-circuited so recovery was never offered. The signature is still the server's business; an unparseable token is left alone. Tabs now stay in step. localStorage is shared where sessionStorage gave each tab its own copy, so 'Not you?' or a registration in one tab silently changed the token every other tab sends while they still displayed the old name -- their likes would land on the new guest, the exact misattribution this branch set out to stop. A storage listener rehydrates the others. Storage is probed for writability, not just readability. A store that reads but throws on setItem (quota, private mode) sailed past the read-only guard, and storeGuestIdentity threw after the server had created the guest: failed registration, retry, duplicate row. Writes are also wrapped so a storage failure degrades to a per-session identity instead of rejecting registration.
This commit is contained in:
@@ -67,6 +67,27 @@ export const GuestIdentityProvider: React.FC<GuestIdentityProviderProps> = ({
|
||||
setIdentity(getGuestIdentity(slug));
|
||||
}, [slug]);
|
||||
|
||||
// Keep tabs in step. The identity now lives in localStorage, which is shared
|
||||
// across tabs — where sessionStorage gave each tab its own copy. So "Not
|
||||
// you?" or a fresh registration in one tab silently changes the token the
|
||||
// axios interceptor sends from every other tab, while those tabs still show
|
||||
// the old name. Their likes would then be recorded against the new guest:
|
||||
// the same misattribution this change set out to stop.
|
||||
//
|
||||
// `storage` fires only in the OTHER tabs, which is exactly the audience that
|
||||
// needs to catch up. A null key means the whole store was cleared.
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const onStorage = (event: StorageEvent) => {
|
||||
if (event.key && event.key !== `guest_token_${slug}` && event.key !== `guest_identity_${slug}`) {
|
||||
return;
|
||||
}
|
||||
setIdentity(getGuestIdentity(slug));
|
||||
};
|
||||
window.addEventListener('storage', onStorage);
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
}, [slug]);
|
||||
|
||||
// When an invite token is present on the URL (?invite=xxx), redeem it once
|
||||
// on mount. The server returns a guest token we can persist.
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user