Hotfix: Fixed mobile session persistance
This commit is contained in:
+80
-7
@@ -1,4 +1,5 @@
|
|||||||
const AUTH_TOKEN_KEY = "auth_token";
|
const AUTH_TOKEN_KEY = "auth_token";
|
||||||
|
const REFRESH_TOKEN_KEY = "refresh_token";
|
||||||
|
|
||||||
// Mood Mix Types (Legacy - for old presets endpoint)
|
// Mood Mix Types (Legacy - for old presets endpoint)
|
||||||
export interface MoodPreset {
|
export interface MoodPreset {
|
||||||
@@ -113,6 +114,7 @@ class ApiClient {
|
|||||||
if (this.token) {
|
if (this.token) {
|
||||||
this.tokenInitialized = true;
|
this.tokenInitialized = true;
|
||||||
}
|
}
|
||||||
|
// Note: Refresh token is loaded on-demand via getRefreshToken()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,19 +155,31 @@ class ApiClient {
|
|||||||
this.baseUrl = "";
|
this.baseUrl = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store JWT token
|
// Store JWT token and optionally refresh token
|
||||||
setToken(token: string) {
|
setToken(token: string, refreshToken?: string) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.setItem(AUTH_TOKEN_KEY, token);
|
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() {
|
clearToken() {
|
||||||
this.token = null;
|
this.token = null;
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.removeItem(AUTH_TOKEN_KEY);
|
localStorage.removeItem(AUTH_TOKEN_KEY);
|
||||||
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,15 +191,56 @@ class ApiClient {
|
|||||||
return getApiBaseUrl();
|
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
|
* Make an authenticated API request
|
||||||
* Public method for components that need custom API calls
|
* Public method for components that need custom API calls
|
||||||
*/
|
*/
|
||||||
async request<T>(
|
async request<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
options: RequestInit & { silent404?: boolean } = {}
|
options: RequestInit & { silent404?: boolean; _retryCount?: number } = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const { silent404, ...fetchOptions } = options;
|
const { silent404, _retryCount = 0, ...fetchOptions } = options;
|
||||||
const headers: HeadersInit = {
|
const headers: HeadersInit = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...fetchOptions.headers,
|
...fetchOptions.headers,
|
||||||
@@ -217,6 +272,23 @@ class ApiClient {
|
|||||||
console.error(`[API] Request failed: ${url}`, error);
|
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) {
|
if (response.status === 401) {
|
||||||
const err = new Error("Not authenticated");
|
const err = new Error("Not authenticated");
|
||||||
(err as any).status = response.status;
|
(err as any).status = response.status;
|
||||||
@@ -260,6 +332,7 @@ class ApiClient {
|
|||||||
async login(username: string, password: string, token?: string) {
|
async login(username: string, password: string, token?: string) {
|
||||||
const data = await this.request<{
|
const data = await this.request<{
|
||||||
token?: string;
|
token?: string;
|
||||||
|
refreshToken?: string;
|
||||||
user?: {
|
user?: {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
@@ -274,9 +347,9 @@ class ApiClient {
|
|||||||
body: JSON.stringify({ username, password, token }),
|
body: JSON.stringify({ username, password, token }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// If login returned a JWT token, store it
|
// If login returned JWT tokens, store them
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
this.setToken(data.token);
|
this.setToken(data.token, data.refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return user data in consistent format
|
// Return user data in consistent format
|
||||||
|
|||||||
Reference in New Issue
Block a user