102 lines
2.0 KiB
TypeScript
102 lines
2.0 KiB
TypeScript
import { logger } from '../services/logging';
|
|
|
|
export interface AuthTokens {
|
|
accessToken: string;
|
|
refreshToken?: string | null;
|
|
}
|
|
|
|
const TOKEN_STORAGE_KEY = 'meds_auth_tokens';
|
|
|
|
let memoryTokens: AuthTokens | null = null;
|
|
|
|
function getStorage(): Storage | null {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return window.localStorage;
|
|
} catch (error) {
|
|
// LocalStorage may be unavailable (e.g. Safari private mode); gracefully degrade.
|
|
logger.warn('Token storage fallback to memory', 'AUTH_TOKENS', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function persist(tokens: AuthTokens | null): void {
|
|
const storage = getStorage();
|
|
|
|
if (!storage) {
|
|
memoryTokens = tokens;
|
|
return;
|
|
}
|
|
|
|
if (!tokens) {
|
|
storage.removeItem(TOKEN_STORAGE_KEY);
|
|
memoryTokens = null;
|
|
return;
|
|
}
|
|
|
|
const payload: AuthTokens = {
|
|
accessToken: tokens.accessToken,
|
|
refreshToken: tokens.refreshToken ?? null,
|
|
};
|
|
|
|
storage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(payload));
|
|
memoryTokens = payload;
|
|
}
|
|
|
|
function readTokens(): AuthTokens | null {
|
|
const storage = getStorage();
|
|
|
|
if (!storage) {
|
|
return memoryTokens;
|
|
}
|
|
|
|
try {
|
|
const raw = storage.getItem(TOKEN_STORAGE_KEY);
|
|
if (!raw) {
|
|
memoryTokens = null;
|
|
return null;
|
|
}
|
|
|
|
const parsed = JSON.parse(raw) as AuthTokens;
|
|
memoryTokens = parsed;
|
|
return parsed;
|
|
} catch (error) {
|
|
logger.warn(
|
|
'Failed to parse stored tokens, clearing cache',
|
|
'AUTH_TOKENS',
|
|
error
|
|
);
|
|
storage.removeItem(TOKEN_STORAGE_KEY);
|
|
memoryTokens = null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export const tokenStorage = {
|
|
save(tokens: AuthTokens): void {
|
|
if (!tokens || !tokens.accessToken) {
|
|
throw new Error('Token payload must include an access token');
|
|
}
|
|
|
|
persist(tokens);
|
|
},
|
|
|
|
getTokens(): AuthTokens | null {
|
|
return readTokens();
|
|
},
|
|
|
|
getAccessToken(): string | null {
|
|
const tokens = readTokens();
|
|
return tokens?.accessToken ?? null;
|
|
},
|
|
|
|
clear(): void {
|
|
persist(null);
|
|
},
|
|
};
|
|
|
|
export default tokenStorage;
|