Files
carry-your-live/carry-your-live/src/services/auth.ts
T
2026-08-25 16:26:10 +02:00

185 lines
5.2 KiB
TypeScript

import AsyncStorage from '@react-native-async-storage/async-storage';
export const DEFAULT_API_BASE_URL = process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:3000/api';
const TOKEN_KEY = 'auth:token';
const USER_KEY = 'auth:user';
export const API_URL_KEY = 'settings:apiUrl';
function normalizeApiUrl(url: string): string {
return url.trim().replace(/\/+$/, '');
}
export async function getApiBaseUrl(): Promise<string> {
try {
const stored = await AsyncStorage.getItem(API_URL_KEY);
if (!stored) return DEFAULT_API_BASE_URL;
try {
const parsed = JSON.parse(stored) as unknown;
if (typeof parsed === 'string' && parsed.trim()) {
return normalizeApiUrl(parsed);
}
} catch {
// fall through to raw value
}
if (stored.trim()) {
return normalizeApiUrl(stored);
}
} catch {
// ignore
}
return DEFAULT_API_BASE_URL;
}
export async function setApiBaseUrl(url: string): Promise<void> {
const normalized = normalizeApiUrl(url);
try {
if (normalized) {
await AsyncStorage.setItem(API_URL_KEY, normalized);
} else {
await AsyncStorage.removeItem(API_URL_KEY);
}
} catch {
// ignore
}
}
export interface AuthUser {
id: string;
username: string;
}
export async function getAuthToken(): Promise<string | null> {
try {
return await AsyncStorage.getItem(TOKEN_KEY);
} catch {
return null;
}
}
export async function getAuthUser(): Promise<AuthUser | null> {
try {
const raw = await AsyncStorage.getItem(USER_KEY);
return raw ? (JSON.parse(raw) as AuthUser) : null;
} catch {
return null;
}
}
export async function setAuth(token: string, user: AuthUser): Promise<void> {
try {
await AsyncStorage.setItem(TOKEN_KEY, token);
await AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
} catch {
// ignore
}
}
export async function signOutAuth(): Promise<void> {
try {
await AsyncStorage.removeItem(TOKEN_KEY);
await AsyncStorage.removeItem(USER_KEY);
} catch {
// ignore
}
}
export async function apiFetch(path: string, options: RequestInit = {}): Promise<Response> {
const token = await getAuthToken();
const apiBaseUrl = await getApiBaseUrl();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string> | undefined),
};
if (token) {
headers.Authorization = `Bearer ${token}`;
}
const response = await fetch(`${apiBaseUrl}${path}`, { ...options, headers });
if (!response.ok) {
const text = await response.text();
// If it's a 401 or network error, try to get a fresh token and retry once
if (response.status === 404 || response.status === 0) {
// Not found or network error - don't throw, return the response for caller to handle
const error = new Error(text || `Request failed (${response.status})`);
(error as any).status = response.status;
(error as any).offline = true;
throw error;
}
let message = `Request failed (${response.status})`;
let details: any = null;
try {
const body = JSON.parse(text);
if (body && typeof body.error?.message === 'string') {
message = body.error.message;
} else if (body && typeof body.message === 'string') {
message = body.message;
}
if (body && body.error && body.error.details) {
details = body.error.details;
}
} catch {
// ignore
}
const error = new Error(message);
(error as any).details = details;
(error as any).status = response.status;
throw error;
}
return response;
}
export async function login(username: string, password: string): Promise<AuthUser> {
const response = await apiFetch('/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
const data = await response.json();
await setAuth(data.token, data.user);
return data.user as AuthUser;
}
export async function register(username: string, password: string): Promise<AuthUser> {
const response = await apiFetch('/auth/register', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
const data = await response.json();
await setAuth(data.token, data.user);
return data.user as AuthUser;
}
export async function forgotPassword(username: string): Promise<{ success: boolean; resetToken?: string }> {
const response = await apiFetch('/auth/forgot-password', {
method: 'POST',
body: JSON.stringify({ username }),
});
const data = await response.json();
return data;
}
export async function resetPassword(token: string, password: string): Promise<{ success: boolean }> {
const response = await apiFetch('/auth/reset-password', {
method: 'POST',
body: JSON.stringify({ token, password }),
});
const data = await response.json();
return data;
}
export async function registerPushToken(pushToken: string): Promise<{ success: boolean }> {
const response = await apiFetch('/push/push-token', {
method: 'POST',
body: JSON.stringify({ pushToken }),
});
const data = await response.json();
return data;
}
export async function unregisterPushToken(): Promise<{ success: boolean }> {
const response = await apiFetch('/push/push-token', {
method: 'DELETE',
});
const data = await response.json();
return data;
}