Files
IOT_SIM/web/src/api.ts
T
xuyueandClaude Opus 5.5 5bb2940969 登录改用访问令牌 + 刷新令牌,不用 cookie
- 访问令牌:JWT(HS256),15 分钟,Authorization: Bearer;WebSocket 放在网址参数里
- 刷新令牌:30 天,每次刷新换新的,数据库只存哈希;退出登录即作废
- 重置密码、删除学生:令牌版本加一并删除刷新令牌,所有设备立即退出
- 签名密钥首次使用时生成,存在数据库 settings 表
- 浏览器:令牌存 localStorage;快过期先刷新,401 时刷新一次再重试;多个标签页同时刷新不冲突

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:20:41 +08:00

44 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 调用服务器接口。出错时抛出 ApiError,message 是服务器给的中文说明;
// 请求带上访问令牌(快过期时先刷新);登录过期(刷新也失败)时回到登录页。
import { refreshTokens, validAccessToken } from './tokens'
export class ApiError extends Error {
constructor(
message: string,
readonly status: number,
) {
super(message)
}
}
/** 登录过期时的处理,由 router.ts 设置(避免这里依赖路由) */
export const apiHooks = { onUnauthorized: () => {} }
export async function api<T>(method: string, path: string, json?: unknown, init?: RequestInit): Promise<T> {
const response = await send(method, path, json, init)
const data = (await response.json().catch(() => ({}))) as T & { message?: string }
if (response.status === 401 && path !== '/api/login') apiHooks.onUnauthorized()
if (!response.ok) throw new ApiError(data.message ?? `请求失败(${response.status})`, response.status)
return data
}
/** 发请求并带上访问令牌;令牌被拒绝(401)时刷新一次再重发 */
async function send(method: string, path: string, json: unknown, init: RequestInit | undefined, retried = false): Promise<Response> {
const headers: Record<string, string> = {}
if (json !== undefined) headers['Content-Type'] = 'application/json'
if (path !== '/api/login') {
const token = await validAccessToken()
if (token) headers.Authorization = `Bearer ${token}`
}
let response: Response
try {
response = await fetch(path, { method, headers, body: json === undefined ? undefined : JSON.stringify(json), ...init })
} catch {
throw new ApiError('连不上服务器,请检查网络', 0)
}
if (response.status === 401 && path !== '/api/login' && !retried && (await refreshTokens())) {
return send(method, path, json, init, true)
}
return response
}