| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- const API_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:3001'
- export interface AuthUser {
- id: string
- email: string
- name: string
- avatarUrl: string | null
- }
- export interface AuthTokens {
- accessToken: string
- refreshToken: string
- expiresIn: number
- user: AuthUser
- }
- const ACCESS_TOKEN_KEY = 'timelapse_access_token'
- const REFRESH_TOKEN_KEY = 'timelapse_refresh_token'
- const USER_KEY = 'timelapse_user'
- // ── Token storage ────────────────────────────────────────────────
- export function getAccessToken(): string | null {
- if (typeof window === 'undefined') return null
- return localStorage.getItem(ACCESS_TOKEN_KEY)
- }
- export function getRefreshToken(): string | null {
- if (typeof window === 'undefined') return null
- return localStorage.getItem(REFRESH_TOKEN_KEY)
- }
- export function getStoredUser(): AuthUser | null {
- if (typeof window === 'undefined') return null
- const raw = localStorage.getItem(USER_KEY)
- if (!raw) return null
- try { return JSON.parse(raw) } catch { return null }
- }
- export function storeTokens(tokens: AuthTokens): void {
- localStorage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken)
- localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken)
- localStorage.setItem(USER_KEY, JSON.stringify(tokens.user))
- }
- export function clearAuth(): void {
- localStorage.removeItem(ACCESS_TOKEN_KEY)
- localStorage.removeItem(REFRESH_TOKEN_KEY)
- localStorage.removeItem(USER_KEY)
- }
- // ── API helpers ──────────────────────────────────────────────────
- async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
- const token = getAccessToken()
- const headers: Record<string, string> = {
- 'Content-Type': 'application/json',
- ...(options.headers as Record<string, string>),
- }
- if (token) headers['Authorization'] = `Bearer ${token}`
- const res = await fetch(`${API_URL}${path}`, { ...options, headers })
- if (res.status === 401) {
- // Try refresh
- const refreshed = await tryRefresh()
- if (refreshed) {
- const retryHeaders = { ...headers, Authorization: `Bearer ${getAccessToken()}` }
- const retry = await fetch(`${API_URL}${path}`, { ...options, headers: retryHeaders })
- if (!retry.ok) throw new Error(`API error ${retry.status}`)
- return retry.json() as Promise<T>
- }
- clearAuth()
- if (typeof window !== 'undefined') window.location.href = '/login'
- throw new Error('Unauthorized')
- }
- if (!res.ok) {
- const body = await res.json().catch(() => ({ message: 'Unknown error' }))
- throw new Error(body.message || `API error ${res.status}`)
- }
- return res.json() as Promise<T>
- }
- export async function tryRefresh(): Promise<boolean> {
- const refreshToken = getRefreshToken()
- if (!refreshToken) return false
- try {
- const res = await fetch(`${API_URL}/v1/auth/refresh`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ refreshToken }),
- })
- if (!res.ok) return false
- const tokens: AuthTokens = await res.json()
- storeTokens(tokens)
- return true
- } catch {
- return false
- }
- }
- // ── Auth API calls ──────────────────────────────────────────────
- export const authApi = {
- login: (email: string, password: string) =>
- apiFetch<AuthTokens>('/v1/auth/login', {
- method: 'POST',
- body: JSON.stringify({ email, password }),
- }),
- register: (email: string, password: string, name: string) =>
- apiFetch<AuthTokens>('/v1/auth/register', {
- method: 'POST',
- body: JSON.stringify({ email, password, name }),
- }),
- logout: () =>
- apiFetch('/v1/auth/logout', {
- method: 'POST',
- body: JSON.stringify({ refreshToken: getRefreshToken() }),
- }).finally(() => clearAuth()),
- me: () => apiFetch<AuthUser>('/v1/auth/me'),
- }
- // ── Devices API (JWT-authenticated) ─────────────────────────────
- export const devicesApi = {
- list: (projectId?: string) => {
- const qs = projectId ? `?projectId=${projectId}` : ''
- return apiFetch<any[]>(`/v1/devices${qs}`)
- },
- get: (id: string) => apiFetch<any>(`/v1/devices/${id}`),
- stats: () => apiFetch<{ devicesOnline: number; devicesOffline: number; capturesToday: number; devicesTotal: number }>('/v1/devices/stats'),
- heartbeats: (id: string, limit?: number) => {
- const qs = limit ? `?limit=${limit}` : ''
- return apiFetch<any[]>(`/v1/devices/${id}/heartbeats${qs}`)
- },
- }
|