199 lines
5.4 KiB
TypeScript
199 lines
5.4 KiB
TypeScript
/**
|
||
* Auth API
|
||
*
|
||
* 认证相关接口
|
||
*
|
||
*/
|
||
import { z } from "zod";
|
||
|
||
import {
|
||
FacebookIdentityRequest,
|
||
FacebookIdentityResponse,
|
||
FacebookIdentityResponseSchema,
|
||
FacebookLoginRequest,
|
||
FacebookPsidLoginRequest,
|
||
FacebookPsidLoginResponse,
|
||
FacebookPsidLoginResponseSchema,
|
||
FbIdLoginRequest,
|
||
GoogleLoginRequest,
|
||
GuestLoginRequest,
|
||
GuestLoginResponse,
|
||
GuestLoginResponseSchema,
|
||
LoginRequest,
|
||
LoginResponse,
|
||
LoginResponseSchema,
|
||
RefreshTokenRequest,
|
||
RefreshTokenResponse,
|
||
RefreshTokenResponseSchema,
|
||
RegisterRequest,
|
||
} from "@/data/schemas/auth";
|
||
import { User, UserSchema } from "@/data/schemas/user";
|
||
import type { UserData } from "@/data/schemas/user/user";
|
||
import { Logger } from "@/utils/logger";
|
||
import { ApiPath } from "./api_path";
|
||
import { httpClient } from "./http_client";
|
||
import { ApiEnvelope, unwrap } from "./response_helper";
|
||
|
||
const log = new Logger("DataServicesApiAuthApi");
|
||
|
||
export class AuthApi {
|
||
/**
|
||
* 邮箱密码登录
|
||
*/
|
||
async emailLogin(body: LoginRequest): Promise<LoginResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.emailLogin, {
|
||
method: "POST",
|
||
body,
|
||
});
|
||
return LoginResponseSchema.parse(unwrap(env) as Record<string, unknown>);
|
||
}
|
||
|
||
/**
|
||
* 注册
|
||
*/
|
||
async register(body: RegisterRequest): Promise<void> {
|
||
await httpClient<ApiEnvelope<unknown>>(ApiPath.register, {
|
||
method: "POST",
|
||
body,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Google 登录
|
||
*/
|
||
async googleLogin(body: GoogleLoginRequest): Promise<LoginResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.googleLogin, {
|
||
method: "POST",
|
||
body,
|
||
});
|
||
return LoginResponseSchema.parse(unwrap(env) as Record<string, unknown>);
|
||
}
|
||
|
||
/**
|
||
* Facebook 登录
|
||
*/
|
||
async facebookLogin(body: FacebookLoginRequest): Promise<LoginResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.facebookLogin, {
|
||
method: "POST",
|
||
body,
|
||
});
|
||
return LoginResponseSchema.parse(unwrap(env) as Record<string, unknown>);
|
||
}
|
||
|
||
/**
|
||
* Facebook ID 登录(v7.0 新增)
|
||
*/
|
||
async facebookIdLogin(body: FbIdLoginRequest): Promise<LoginResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(
|
||
ApiPath.facebookIdLogin,
|
||
{ method: "POST", body },
|
||
);
|
||
return LoginResponseSchema.parse(unwrap(env) as Record<string, unknown>);
|
||
}
|
||
|
||
/**
|
||
* Facebook PSID 登录
|
||
*/
|
||
async facebookPsidLogin(
|
||
body: FacebookPsidLoginRequest,
|
||
): Promise<FacebookPsidLoginResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(
|
||
ApiPath.facebookPsidLogin,
|
||
{ method: "POST", body },
|
||
);
|
||
return FacebookPsidLoginResponseSchema.parse(
|
||
unwrap(env) as Record<string, unknown>,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 绑定 Facebook ASID / PSID 到当前用户
|
||
*/
|
||
async bindFacebookIdentity(
|
||
body: FacebookIdentityRequest,
|
||
): Promise<FacebookIdentityResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(
|
||
ApiPath.userFacebookIdentity,
|
||
{ method: "POST", body },
|
||
);
|
||
return FacebookIdentityResponseSchema.parse(
|
||
unwrap(env) as Record<string, unknown>,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 退出登录(fire-and-forget)
|
||
*
|
||
* 后端 logout 端点的 envelope `data` 字段常为 `null`(登出端点没有业务数据),
|
||
* 不要再用 `unwrap` + `LogoutResponseSchema.parse` 解析 —— Zod schema 是 z.object,
|
||
* 对 null 会抛 ZodError "Invalid input: expected object, received null"。
|
||
*
|
||
* 对齐 `register` 的写法:调通就完事,不解析响应体。
|
||
*/
|
||
async logout(): Promise<void> {
|
||
await httpClient<ApiEnvelope<unknown>>(ApiPath.logout, {
|
||
method: "POST",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 游客登录
|
||
*/
|
||
async guestLogin(body: GuestLoginRequest): Promise<GuestLoginResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.guestLogin, {
|
||
method: "POST",
|
||
body,
|
||
});
|
||
log.debug("[AuthApi.guestLogin] raw envelope from backend", env);
|
||
const unwrapped = unwrap(env) as Record<string, unknown>;
|
||
const userObj = unwrapped.user as { lastMessageAt?: unknown } | undefined;
|
||
log.debug("[AuthApi.guestLogin] unwrapped data (pre-parse)", {
|
||
hasUser: "user" in unwrapped,
|
||
userKeys:
|
||
userObj && typeof userObj === "object" ? Object.keys(userObj) : null,
|
||
lastMessageAt: userObj?.lastMessageAt,
|
||
lastMessageAtType: typeof userObj?.lastMessageAt,
|
||
});
|
||
try {
|
||
return GuestLoginResponseSchema.parse(unwrapped);
|
||
} catch (e) {
|
||
if (e instanceof z.ZodError) {
|
||
log.error("[AuthApi.guestLogin] ZodError parsing response", {
|
||
issueCount: e.issues.length,
|
||
firstIssue: e.issues[0],
|
||
allPaths: e.issues.map((i) => i.path),
|
||
});
|
||
} else {
|
||
log.error("[AuthApi.guestLogin] unexpected error", e);
|
||
}
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刷新 Token
|
||
*/
|
||
async refreshToken(body: RefreshTokenRequest): Promise<RefreshTokenResponse> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.refresh, {
|
||
method: "POST",
|
||
body,
|
||
});
|
||
return RefreshTokenResponseSchema.parse(
|
||
unwrap(env) as Record<string, unknown>,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取当前用户信息
|
||
*/
|
||
async getCurrentUser(): Promise<User> {
|
||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.getCurrentUser);
|
||
return UserSchema.parse(unwrap(env) as UserData);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 全局单例
|
||
*/
|
||
export const authApi = new AuthApi();
|