export interface ApiResponse {
|
code: number
|
msg: string
|
[key: string]: unknown
|
}
|
|
export class ApiError extends Error {
|
readonly code: number
|
|
constructor(message: string, code = 500) {
|
super(message)
|
this.name = 'ApiError'
|
this.code = code
|
}
|
}
|
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/dev-api'
|
const AUTH_TOKEN_KEY = 'battery_detection_auth_token'
|
const AUTH_USER_KEY = 'battery_detection_auth_user'
|
|
type UnauthorizedHandler = () => void | Promise<void>
|
|
let unauthorizedHandler: UnauthorizedHandler | undefined
|
let unauthorizedHandling: Promise<void> | null = null
|
|
export function setUnauthorizedHandler(handler: UnauthorizedHandler) {
|
unauthorizedHandler = handler
|
}
|
|
function clearInvalidSession() {
|
localStorage.removeItem(AUTH_TOKEN_KEY)
|
localStorage.removeItem(AUTH_USER_KEY)
|
}
|
|
async function handleUnauthorized() {
|
clearInvalidSession()
|
if (!unauthorizedHandler) return
|
|
if (!unauthorizedHandling) {
|
unauthorizedHandling = Promise.resolve(unauthorizedHandler())
|
.finally(() => {
|
unauthorizedHandling = null
|
})
|
}
|
|
await unauthorizedHandling
|
}
|
|
export async function request<T extends ApiResponse>(path: string, init: RequestInit = {}): Promise<T> {
|
const headers = new Headers(init.headers)
|
const token = localStorage.getItem(AUTH_TOKEN_KEY)
|
|
if (init.body && !headers.has('Content-Type')) {
|
headers.set('Content-Type', 'application/json')
|
}
|
if (token) {
|
headers.set('Authorization', `Bearer ${token}`)
|
}
|
|
let response: Response
|
try {
|
response = await fetch(`${API_BASE_URL}${path}`, { ...init, headers })
|
} catch {
|
throw new ApiError('无法连接服务器,请检查后端服务是否已启动', 0)
|
}
|
|
let result: T
|
try {
|
result = await response.json() as T
|
} catch {
|
throw new ApiError(`服务器响应异常(HTTP ${response.status})`, response.status)
|
}
|
|
if (response.status === 401 || result.code === 401) {
|
await handleUnauthorized()
|
}
|
|
if (!response.ok || result.code !== 200) {
|
throw new ApiError(result.msg || `请求失败(HTTP ${response.status})`, result.code || response.status)
|
}
|
|
return result
|
}
|