const TOKEN_KEY = 'ft_access_token' export function getApiBaseUrl(): string { return (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, '') || 'https://parche.montech.ir/server/api' } export function getAccessToken(): string | null { return localStorage.getItem(TOKEN_KEY) } export function setAccessToken(token: string | null): void { if (token) localStorage.setItem(TOKEN_KEY, token) else localStorage.removeItem(TOKEN_KEY) } 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) { if ('message' in data) { const message = (data as { message: unknown }).message if (Array.isArray(message)) return message.join('، ') if (message != null && String(message).trim()) return String(message) } if ('error' in data) { const error = (data as { error: unknown }).error if (typeof error === 'string' && error.trim()) return `${error} (${status})` } } if (typeof data === 'string') { if (/upstream error/i.test(data)) { return 'سرور API در دسترس نیست. کمی بعد دوباره تلاش کنید.' } if (data.trim() && !data.trimStart().startsWith('<')) { return data.trim().slice(0, 200) } } if (status === 400) return 'درخواست نامعتبر است. اطلاعات سفارش را بررسی کنید.' if (status === 401) return 'نشست شما منقضی شده است. دوباره وارد شوید.' 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 }