Hotfix: Fixed mobile session persistance

This commit is contained in:
Your Name
2026-01-09 19:53:13 -06:00
parent 0ac805b6fc
commit bddea9ef36
+80 -7
View File
@@ -1,4 +1,5 @@
const AUTH_TOKEN_KEY = "auth_token";
const REFRESH_TOKEN_KEY = "refresh_token";
// Mood Mix Types (Legacy - for old presets endpoint)
export interface MoodPreset {
@@ -113,6 +114,7 @@ class ApiClient {
if (this.token) {
this.tokenInitialized = true;
}
// Note: Refresh token is loaded on-demand via getRefreshToken()
}
}
@@ -153,19 +155,31 @@ class ApiClient {
this.baseUrl = "";
}
// Store JWT token
setToken(token: string) {
// Store JWT token and optionally refresh token
setToken(token: string, refreshToken?: string) {
this.token = token;
if (typeof window !== "undefined") {
localStorage.setItem(AUTH_TOKEN_KEY, token);
if (refreshToken) {
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
}
}
}
// Clear JWT token
// Get refresh token from storage
getRefreshToken(): string | null {
if (typeof window === "undefined") {
return null;
}
return localStorage.getItem(REFRESH_TOKEN_KEY);
}
// Clear both JWT tokens
clearToken() {
this.token = null;
if (typeof window !== "undefined") {
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
}
}
@@ -177,15 +191,56 @@ class ApiClient {
return getApiBaseUrl();
}
/**
* Refresh the access token using the refresh token
* @returns true if refresh succeeded, false otherwise
*/
private async refreshAccessToken(): Promise<boolean> {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
return false;
}
try {
const response = await fetch(`${this.getBaseUrl()}/api/auth/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken }),
credentials: "include",
});
if (!response.ok) {
// Refresh token invalid or expired - clear tokens
this.clearToken();
return false;
}
const data = await response.json();
// Store new tokens
if (data.token) {
this.setToken(data.token, data.refreshToken);
return true;
}
this.clearToken();
return false;
} catch (error) {
console.error("[API] Token refresh failed:", error);
this.clearToken();
return false;
}
}
/**
* Make an authenticated API request
* Public method for components that need custom API calls
*/
async request<T>(
endpoint: string,
options: RequestInit & { silent404?: boolean } = {}
options: RequestInit & { silent404?: boolean; _retryCount?: number } = {}
): Promise<T> {
const { silent404, ...fetchOptions } = options;
const { silent404, _retryCount = 0, ...fetchOptions } = options;
const headers: HeadersInit = {
"Content-Type": "application/json",
...fetchOptions.headers,
@@ -217,6 +272,23 @@ class ApiClient {
console.error(`[API] Request failed: ${url}`, error);
}
// Handle 401 with token refresh (retry once)
if (response.status === 401 && _retryCount === 0 && endpoint !== "/auth/refresh") {
console.log("[API] 401 error - attempting token refresh");
const refreshed = await this.refreshAccessToken();
if (refreshed) {
console.log("[API] Token refreshed - retrying request");
// Retry the request with new token
return this.request<T>(endpoint, {
...options,
_retryCount: 1, // Prevent infinite loops
});
}
console.log("[API] Token refresh failed - user needs to re-login");
}
if (response.status === 401) {
const err = new Error("Not authenticated");
(err as any).status = response.status;
@@ -260,6 +332,7 @@ class ApiClient {
async login(username: string, password: string, token?: string) {
const data = await this.request<{
token?: string;
refreshToken?: string;
user?: {
id: string;
username: string;
@@ -274,9 +347,9 @@ class ApiClient {
body: JSON.stringify({ username, password, token }),
});
// If login returned a JWT token, store it
// If login returned JWT tokens, store them
if (data.token) {
this.setToken(data.token);
this.setToken(data.token, data.refreshToken);
}
// Return user data in consistent format