Files
chat-one-web/src/api/auth.ts
alboped b3fdb0ad4b
All checks were successful
CI / build (push) Successful in 1m59s
feat: 接入会话列表并支持路由记忆当前会话
新增会话与消息查询 API,并将首页改为真实会话驱动;当前选中会话会同步到 URL 参数,刷新或直达链接可恢复上下文。

Made-with: Cursor
2026-04-22 23:32:07 +08:00

142 lines
4.4 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.
/**
* 鉴权相关:登录、刷新令牌、本地会话读写。
*
* 会话默认存 `localStorage`,键名见下方常量(与 `http.ts` 里读取的 `accessToken` 一致)。
*/
import { postJson } from "./http";
import { ApiPath } from "./paths";
/** localStorage访问令牌 */
export const ACCESS_TOKEN_KEY = "accessToken";
/** localStorage刷新令牌 */
export const REFRESH_TOKEN_KEY = "refreshToken";
/** localStorage用户信息 JSON 字符串 */
export const USER_KEY = "user";
export type AuthUser = Record<string, unknown>;
/** `ClientAuthUserDto`(与 OpenAPI 一致) */
export type ClientAuthUserDto = {
id: string;
phone: string;
nickname: string;
avatarUrl: string;
};
/** 发送短信验证码时 `scene` 取值(与后端约定一致) */
export const AUTH_SMS_SCENE_LOGIN = "login";
/** `ClientSendSmsResponseDto` */
export type SmsSendResponse = {
requestId: string;
phone: string;
scene: string;
provider: string;
expireIn: number;
testCode?: string;
};
/** `ClientLoginResponseDto` */
export type SmsLoginResponse = {
accessToken: string;
refreshToken: string;
user: ClientAuthUserDto;
};
/** `POST .../auth/refresh` 成功响应 */
export type RefreshTokenResponse = {
accessToken: string;
refreshToken: string;
};
/** 写入一对令牌,并清理历史 mock 键 `token` */
export function persistTokens(accessToken: string, refreshToken: string): void {
window.localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
window.localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
window.localStorage.removeItem("token");
}
/** 写入用户信息(可选) */
export function persistUser(user: AuthUser | undefined): void {
if (!user) return;
window.localStorage.setItem(USER_KEY, JSON.stringify(user));
window.dispatchEvent(new CustomEvent("chatone-user-changed"));
}
/** 清除本地会话(含兼容旧键) */
export function clearSession(): void {
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
window.localStorage.removeItem(REFRESH_TOKEN_KEY);
window.localStorage.removeItem(USER_KEY);
window.localStorage.removeItem("token");
window.dispatchEvent(new CustomEvent("chatone-user-changed"));
}
/**
* 续签失败、双 token 均失效等场景:清会话并回到登录页(整页跳转,避免残留状态)。
*/
export class SessionExpiredError extends Error {
override readonly name = "SessionExpiredError";
constructor(message = "登录已过期,请重新登录") {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
export function invalidateSessionAndGoLogin(): void {
clearSession();
const base = import.meta.env.BASE_URL || "/";
const loginPath = base === "/" ? "/login" : `${String(base).replace(/\/$/, "")}/login`;
window.location.replace(loginPath);
}
/**
* 退出登录:尽量通知后端吊销 refresh失败则忽略并清除本地会话。
*/
export async function logout(): Promise<void> {
const refreshToken = window.localStorage.getItem(REFRESH_TOKEN_KEY);
if (refreshToken) {
try {
await postJson<unknown>(ApiPath.authLogout, { refreshToken });
} catch {
// 无网或接口未实现时仍允许用户本地退出
}
}
clearSession();
}
/**
* 发送短信验证码(如登录前获取验证码)。
* @param phone 国际格式,如 `+86153xxxxxxxx`
* @param scene 业务场景,默认 `AUTH_SMS_SCENE_LOGIN`
* @returns `testCode` 仅部分环境返回,用于联调自动填码
*/
export async function sendAuthSmsCode(
phone: string,
scene: string = AUTH_SMS_SCENE_LOGIN,
): Promise<{ testCode?: string }> {
const raw = await postJson<SmsSendResponse>(ApiPath.authSmsSend, { phone, scene });
const testCode = raw.testCode;
if (typeof testCode === "string" && testCode.trim()) {
return { testCode: testCode.trim() };
}
return {};
}
/**
* 短信验证码登录。
* @param phone 国际格式,如 `+86153xxxxxxxx`
* @param code 短信验证码
*/
export async function smsLogin(phone: string, code: string): Promise<SmsLoginResponse> {
return postJson<SmsLoginResponse>(ApiPath.authSmsLogin, { phone, code });
}
/**
* 使用 refreshToken 换取新的访问令牌与刷新令牌。
* 该请求在 `http` 层不会附带旧的 `Authorization`。
*/
export async function refreshTokens(refreshToken: string): Promise<RefreshTokenResponse> {
return postJson<RefreshTokenResponse>(ApiPath.authRefresh, { refreshToken });
}