Files
cozsweet-frontend-nextjs/src/data/services/api/auth_api.ts
T
Codex 019caae598
Docker Image / Build and Push Docker Image (push) Successful in 2m9s
feat(auth): add Messenger topup handoff entry
2026-07-27 17:25:31 +08:00

197 lines
5.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.
/**
* Auth API
*
* 认证相关接口
*
*/
import { z } from "zod";
import {
FacebookIdentityRequest,
FacebookIdentityResponse,
FacebookIdentityResponseSchema,
FacebookLoginRequest,
FbIdLoginRequest,
GoogleLoginRequest,
GuestLoginRequest,
GuestLoginResponse,
GuestLoginResponseSchema,
LoginRequest,
LoginResponse,
LoginResponseSchema,
RefreshTokenRequest,
RefreshTokenResponse,
RefreshTokenResponseSchema,
RegisterRequest,
TopUpHandoffRequest,
TopUpHandoffResponse,
TopUpHandoffResponseSchema,
} 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>);
}
/** 消费一次性充值登录凭证。当前正式登录 token 会由拦截器自动携带。 */
async consumeTopUpHandoff(
body: TopUpHandoffRequest,
): Promise<TopUpHandoffResponse> {
const env = await httpClient<ApiEnvelope<unknown>>(
ApiPath.topUpHandoffConsume,
{ method: "POST", body },
);
return TopUpHandoffResponseSchema.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();