Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | 40x 40x 40x 40x 40x 40x 40x 11x 3x 2x 5x 5x 2x 2x 2x 2x 2x 3x 40x 40x 20x 40x 40x 5x 5x 5x 5x 40x 40x 3x 3x 3x 3x 3x 40x 6x 5x 5x 5x 5x 5x 5x 3x 3x 3x 3x 3x 3x 3x 2x 2x 2x 5x 6x | import type { AuthSessionResponse, StoredAuthSession } from '@/models/auth';
import { API_BASE_URL } from '@/config/apiBaseUrl';
import {
clearStoredSession,
readStoredSession,
writeStoredSession,
} from '@/services/sessionStorage';
type SessionListener = (session: StoredAuthSession | null) => void;
let currentSession: StoredAuthSession | null = null;
let hydratePromise: Promise<StoredAuthSession | null> | null = null;
let refreshPromise: Promise<StoredAuthSession> | null = null;
let sessionVersion = 0;
const listeners = new Set<SessionListener>();
function notifyListeners() {
for (const listener of listeners) {
listener(currentSession);
}
}
function mapAuthSession(response: AuthSessionResponse): StoredAuthSession {
return {
access_token: response.access_token,
refresh_token: response.refresh_token,
user: response.user,
};
}
function isTerminalRefreshError(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'status' in error &&
(error as { status?: unknown }).status === 401
);
}
async function refreshSessionRequest(
refreshToken: string,
): Promise<AuthSessionResponse> {
const response = await fetch(`${API_BASE_URL}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken }),
});
if (!response.ok) {
const body = await response.json().catch(() => null);
const error = new Error(
body?.error?.message ?? 'Could not refresh the session.',
) as Error & { status?: number; code?: string };
error.status = response.status;
error.code = body?.error?.code;
throw error;
}
return response.json();
}
export async function hydrateSession(): Promise<StoredAuthSession | null> {
if (!hydratePromise) {
hydratePromise = (async () => {
currentSession = await readStoredSession();
notifyListeners();
return currentSession;
})();
}
return hydratePromise;
}
export function getCurrentSession(): StoredAuthSession | null {
return currentSession;
}
export function subscribeToSession(listener: SessionListener): () => void {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
export async function setSession(session: StoredAuthSession): Promise<void> {
await writeStoredSession(session);
sessionVersion += 1;
currentSession = session;
notifyListeners();
}
export async function setSessionFromAuthResponse(
response: AuthSessionResponse,
): Promise<void> {
await setSession(mapAuthSession(response));
}
export async function clearSession(): Promise<void> {
refreshPromise = null;
await clearStoredSession();
sessionVersion += 1;
currentSession = null;
notifyListeners();
}
export async function refreshSession(): Promise<StoredAuthSession> {
if (!refreshPromise) {
refreshPromise = (async () => {
const session = currentSession ?? (await hydrateSession());
Iif (!session?.refresh_token) {
throw new Error('No refresh token available');
}
const refreshVersion = sessionVersion;
try {
const refreshed = await refreshSessionRequest(session.refresh_token);
const nextSession = mapAuthSession(refreshed);
Iif (
currentSession === null ||
sessionVersion !== refreshVersion ||
currentSession.refresh_token !== session.refresh_token
) {
throw new Error('Session changed during refresh');
}
await writeStoredSession(nextSession);
sessionVersion += 1;
currentSession = nextSession;
notifyListeners();
return nextSession;
} catch (error) {
Eif (isTerminalRefreshError(error)) {
await clearSession();
}
throw error;
} finally {
refreshPromise = null;
}
})();
}
return refreshPromise;
}
|