108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
import { clearSession, readSession, writeSession, type AuthSession } from "@/lib/auth"
|
|
|
|
const API_BASE_URL =
|
|
process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || ""
|
|
|
|
type JsonResult<T> = {
|
|
errorCode: number
|
|
message: string
|
|
data: T
|
|
success: boolean
|
|
}
|
|
|
|
type RequestOptions = RequestInit & {
|
|
skipAuth?: boolean
|
|
retryOnAuthError?: boolean
|
|
baseUrl?: string
|
|
}
|
|
|
|
async function parseResult<T>(response: Response) {
|
|
const payload = (await response.json()) as JsonResult<T>
|
|
if (!response.ok || !payload.success) {
|
|
const error = new Error(payload.message || "请求失败")
|
|
;(error as Error & { errorCode?: number }).errorCode = payload.errorCode
|
|
throw error
|
|
}
|
|
return payload.data
|
|
}
|
|
|
|
async function refreshAccessToken() {
|
|
const session = readSession()
|
|
if (!session?.refreshToken) {
|
|
clearSession()
|
|
return null
|
|
}
|
|
|
|
const data = await request<AuthSession>(
|
|
"/api/auth/refresh_token",
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ refreshToken: session.refreshToken }),
|
|
skipAuth: true,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
},
|
|
false
|
|
)
|
|
const merged = {
|
|
...data,
|
|
refreshToken: data.refreshToken || session.refreshToken,
|
|
}
|
|
writeSession(merged)
|
|
return merged
|
|
}
|
|
|
|
export async function request<T>(
|
|
path: string,
|
|
options: RequestOptions = {},
|
|
retryOnAuthError = true
|
|
): Promise<T> {
|
|
const { headers, skipAuth, baseUrl, ...rest } = options
|
|
delete (rest as RequestOptions).retryOnAuthError
|
|
delete (rest as RequestOptions).baseUrl
|
|
const session = readSession()
|
|
const authHeaders = new Headers(headers)
|
|
|
|
if (!skipAuth && session?.accessToken) {
|
|
authHeaders.set("Authorization", `Bearer ${session.accessToken}`)
|
|
}
|
|
if (
|
|
!authHeaders.has("Content-Type") &&
|
|
rest.body &&
|
|
!(typeof FormData !== "undefined" && rest.body instanceof FormData)
|
|
) {
|
|
authHeaders.set("Content-Type", "application/json")
|
|
}
|
|
|
|
const requestBaseUrl = baseUrl !== undefined ? baseUrl : API_BASE_URL
|
|
const response = await fetch(`${requestBaseUrl}${path}`, {
|
|
...rest,
|
|
headers: authHeaders,
|
|
cache: "no-store",
|
|
})
|
|
|
|
try {
|
|
return await parseResult<T>(response)
|
|
} catch (error) {
|
|
const errorCode = (error as Error & { errorCode?: number }).errorCode
|
|
if (
|
|
!skipAuth &&
|
|
retryOnAuthError &&
|
|
(errorCode === 3000 || errorCode === 3002) &&
|
|
session?.refreshToken
|
|
) {
|
|
const refreshed = await refreshAccessToken()
|
|
if (!refreshed) {
|
|
throw error
|
|
}
|
|
return request<T>(path, options, false)
|
|
}
|
|
|
|
if (errorCode === 3000 || errorCode === 3002) {
|
|
clearSession()
|
|
}
|
|
throw error
|
|
}
|
|
}
|