Files
dashboards/src/lib/api.ts
T

120 lines
2.9 KiB
TypeScript

import { getAccessToken, getRefreshToken, setSession, clearSession } from './auth'
const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3100/api/v1'
export class ApiError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
type RequestOptions = {
method?: string
body?: unknown
auth?: boolean
/** Skip refresh retry (used by refresh itself). */
skipRefresh?: boolean
}
let refreshInFlight: Promise<boolean> | null = null
function parseErrorMessage(payload: unknown, fallback: string) {
if (!payload || typeof payload !== 'object') return fallback
const message = (payload as { message?: unknown }).message
if (typeof message === 'string' && message.trim()) return message
if (Array.isArray(message)) {
const parts = message.filter((item) => typeof item === 'string')
if (parts.length > 0) return parts.join('، ')
}
return fallback
}
async function tryRefreshSession(): Promise<boolean> {
if (refreshInFlight) return refreshInFlight
refreshInFlight = (async () => {
const refreshToken = getRefreshToken()
if (!refreshToken) {
clearSession()
return false
}
try {
const response = await fetch(`${API_BASE_URL}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
})
const payload = await response.json().catch(() => null)
if (!response.ok) {
clearSession()
return false
}
setSession({
accessToken: payload.accessToken,
refreshToken: payload.refreshToken,
user: payload.user,
})
return true
} catch {
clearSession()
return false
} finally {
refreshInFlight = null
}
})()
return refreshInFlight
}
export async function apiRequest<T>(
path: string,
options: RequestOptions = {},
): Promise<T> {
const { method = 'GET', body, auth = true, skipRefresh = false } = options
const headers: Record<string, string> = {}
if (body !== undefined) {
headers['Content-Type'] = 'application/json'
}
if (auth) {
const token = getAccessToken()
if (token) headers.Authorization = `Bearer ${token}`
}
const response = await fetch(`${API_BASE_URL}${path}`, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
})
if (response.status === 401 && auth && !skipRefresh) {
const refreshed = await tryRefreshSession()
if (refreshed) {
return apiRequest<T>(path, { ...options, skipRefresh: true })
}
}
if (response.status === 204) {
return undefined as T
}
const payload = await response.json().catch(() => null)
if (!response.ok) {
throw new ApiError(
response.status,
parseErrorMessage(payload, 'خطایی رخ داد'),
)
}
return payload as T
}