const TOKEN_KEY = 'fabric-admin-token' export function getApiBaseUrl(): string { return (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, '') || 'https://parche.montest.ir/server/api' } export function getAccessToken(): string | null { return localStorage.getItem(TOKEN_KEY) ?? sessionStorage.getItem(TOKEN_KEY) } export function setAccessToken(token: string | null, remember = true): void { localStorage.removeItem(TOKEN_KEY) sessionStorage.removeItem(TOKEN_KEY) if (!token) return if (remember) localStorage.setItem(TOKEN_KEY, token) else sessionStorage.setItem(TOKEN_KEY, token) } export class ApiError extends Error { status: number constructor(status: number, message: string) { super(message) this.status = status } } type RequestOptions = { method?: string body?: unknown auth?: boolean query?: Record } function buildUrl(path: string, query?: RequestOptions['query']): string { const base = getApiBaseUrl() const joined = path.startsWith('http') ? path : `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}` const url = joined.startsWith('http') ? new URL(joined) : new URL(joined, typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1') if (query) { for (const [key, value] of Object.entries(query)) { if (value === undefined || value === null || value === '') continue url.searchParams.set(key, String(value)) } } return url.toString() } function errorMessageFromBody(data: unknown, status: number): string { if (typeof data === 'object' && data && 'message' in data) { const message = (data as { message: unknown }).message if (Array.isArray(message)) return message.join('، ') return String(message) } if (typeof data === 'string') { if (/upstream error/i.test(data)) { return 'سرور API در دسترس نیست. کمی بعد دوباره تلاش کنید.' } if (data.trim() && !data.trimStart().startsWith('<')) { return data.trim().slice(0, 200) } } return `خطای سرور (${status})` } export async function apiRequest(path: string, options: RequestOptions = {}): Promise { const headers: Record = { Accept: 'application/json', } if (options.body !== undefined) { headers['Content-Type'] = 'application/json' } if (options.auth !== false) { const token = getAccessToken() if (token) headers.Authorization = `Bearer ${token}` } let res: Response try { res = await fetch(buildUrl(path, options.query), { method: options.method ?? (options.body !== undefined ? 'POST' : 'GET'), headers, body: options.body !== undefined ? JSON.stringify(options.body) : undefined, signal: AbortSignal.timeout(20_000), }) } catch (err) { const timedOut = (err instanceof DOMException && err.name === 'TimeoutError') || (err instanceof Error && err.name === 'TimeoutError') throw new ApiError( 0, timedOut ? 'پاسخ سرور طول کشید. دوباره تلاش کنید.' : 'اتصال به سرور برقرار نشد. اینترنت یا آدرس API را بررسی کنید.', ) } const text = await res.text() let data: unknown = null if (text) { try { data = JSON.parse(text) } catch { data = text } } if (!res.ok) { throw new ApiError(res.status, errorMessageFromBody(data, res.status)) } return data as T }