-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsession.ts
More file actions
49 lines (43 loc) · 1.57 KB
/
Copy pathsession.ts
File metadata and controls
49 lines (43 loc) · 1.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Where tokens live after sign-in completes — `localStorage`, unlike the ephemeral PKCE state
* in `auth.ts`'s `sessionStorage`: a signed-in session should survive a page reload and work
* across tabs, the standard trade-off for a public SPA client with no server-side session.
*
* `expiresAt` is computed from `expires_in` at storage time so `isExpired` never needs the
* original request's timestamp again.
*/
import type { TokenResponse } from './auth';
export interface StoredSession {
readonly accessToken: string;
readonly idToken: string;
readonly expiresAt: number; // epoch ms
}
const SESSION_KEY = 'ojr.session';
export function storeSession(tokens: TokenResponse, now: number = Date.now()): StoredSession {
const session: StoredSession = {
accessToken: tokens.access_token,
idToken: tokens.id_token,
expiresAt: now + tokens.expires_in * 1000,
};
localStorage.setItem(SESSION_KEY, JSON.stringify(session));
return session;
}
export function loadSession(): StoredSession | null {
const raw = localStorage.getItem(SESSION_KEY);
if (!raw) return null;
try {
const parsed = JSON.parse(raw) as Partial<StoredSession>;
if (typeof parsed.accessToken !== 'string' || typeof parsed.idToken !== 'string' || typeof parsed.expiresAt !== 'number') {
return null;
}
return parsed as StoredSession;
} catch {
return null;
}
}
export function clearSession(): void {
localStorage.removeItem(SESSION_KEY);
}
export function isExpired(session: StoredSession, now: number = Date.now()): boolean {
return now >= session.expiresAt;
}