mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:41:09 +02:00
Users were getting signed out after ~15 minutes because API clients were reading tokens directly from localStorage without triggering the refresh logic. The tokenManager exists with proper refresh capability but was never being used. Changes: - Add getValidToken() method to auth stores in 9 web apps - Update API clients to use authStore.getValidToken() instead of localStorage - Token refresh now happens proactively before requests fail Apps updated: chat, calendar, picture, zitare, contacts, todo, clock, manacore, manadeck 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
159 lines
3.8 KiB
TypeScript
159 lines
3.8 KiB
TypeScript
/**
|
|
* API Client for Picture Backend
|
|
* Replaces direct Supabase calls with backend API calls.
|
|
*
|
|
* Token handling: Uses authStore.getValidToken() which automatically
|
|
* refreshes expired tokens before making requests.
|
|
*/
|
|
|
|
import { env } from '$env/dynamic/public';
|
|
import { authStore } from '$lib/stores/auth.svelte';
|
|
|
|
const API_BASE = env.PUBLIC_BACKEND_URL || 'http://localhost:3003';
|
|
|
|
type FetchOptions = {
|
|
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
body?: unknown;
|
|
token?: string;
|
|
isFormData?: boolean;
|
|
};
|
|
|
|
export async function fetchApi<T>(
|
|
endpoint: string,
|
|
options: FetchOptions = {}
|
|
): Promise<{ data: T | null; error: Error | null }> {
|
|
const { method = 'GET', body, token, isFormData = false } = options;
|
|
|
|
// Get a valid token (auto-refreshes if expired)
|
|
const authToken = token || (await authStore.getValidToken());
|
|
|
|
try {
|
|
const headers: Record<string, string> = {};
|
|
|
|
// Don't set Content-Type for FormData - browser sets it automatically with boundary
|
|
if (!isFormData) {
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
|
|
if (authToken) {
|
|
headers['Authorization'] = `Bearer ${authToken}`;
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/api/v1${endpoint}`, {
|
|
method,
|
|
headers,
|
|
body: isFormData ? (body as FormData) : body ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
return {
|
|
data: null,
|
|
error: new Error(errorData.message || `API error: ${response.status}`),
|
|
};
|
|
}
|
|
|
|
// Handle empty responses (204 No Content)
|
|
if (response.status === 204) {
|
|
return { data: null, error: null };
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { data, error: null };
|
|
} catch (error) {
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error : new Error('Unknown error'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload a file to the backend
|
|
*/
|
|
export async function uploadFile(
|
|
endpoint: string,
|
|
file: File,
|
|
token?: string
|
|
): Promise<{ data: any; error: Error | null }> {
|
|
// Get a valid token (auto-refreshes if expired)
|
|
const authToken = token || (await authStore.getValidToken());
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
const headers: Record<string, string> = {};
|
|
if (authToken) {
|
|
headers['Authorization'] = `Bearer ${authToken}`;
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/api/v1${endpoint}`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
return {
|
|
data: null,
|
|
error: new Error(errorData.message || `Upload error: ${response.status}`),
|
|
};
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { data, error: null };
|
|
} catch (error) {
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error : new Error('Upload failed'),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload multiple files to the backend
|
|
*/
|
|
export async function uploadFiles(
|
|
endpoint: string,
|
|
files: File[],
|
|
token?: string
|
|
): Promise<{ data: any; error: Error | null }> {
|
|
// Get a valid token (auto-refreshes if expired)
|
|
const authToken = token || (await authStore.getValidToken());
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
files.forEach((file) => {
|
|
formData.append('files', file);
|
|
});
|
|
|
|
const headers: Record<string, string> = {};
|
|
if (authToken) {
|
|
headers['Authorization'] = `Bearer ${authToken}`;
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/api/v1${endpoint}`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
return {
|
|
data: null,
|
|
error: new Error(errorData.message || `Upload error: ${response.status}`),
|
|
};
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { data, error: null };
|
|
} catch (error) {
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error : new Error('Upload failed'),
|
|
};
|
|
}
|
|
}
|