refactor(game): 重构项目结构,优化链路, 移动端适配
- 移除 useGameBoardVm 数据层实施说明文档 - 移除核心玩法与前端规则摘要文档 - 移除游戏模块数据与界面分层第一阶段实施稿文档 - 清理与数据层重构相关的技术方案说明 - 删除关于 PC 和 Mobile 界面分离的设计规划 - 移除 view-model hooks 架构设计相关内容
This commit is contained in:
233
src/api/auth-api.ts
Normal file
233
src/api/auth-api.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import {
|
||||
API_SUCCESS_CODE,
|
||||
AUTH_ENDPOINTS,
|
||||
AUTH_SKIP_REFRESH_CONTEXT_KEY,
|
||||
SMS_SEND_EVENT_REGISTER,
|
||||
} from '@/constants'
|
||||
import { api } from '@/lib/api/api-client'
|
||||
import { ApiError } from '@/lib/api/api-error'
|
||||
import {
|
||||
mergeAuthUsers,
|
||||
normalizeAuthSession,
|
||||
normalizeAuthUserProfile,
|
||||
normalizeRefreshAuthSession,
|
||||
} from '@/lib/auth/auth-normalizers'
|
||||
import { getAuthDeviceId } from '@/store/auth'
|
||||
import type {
|
||||
ApiResponse,
|
||||
AuthSessionDto,
|
||||
AuthSessionInput,
|
||||
AuthUserProfileDto,
|
||||
LoginPayload,
|
||||
LoginRequestDto,
|
||||
LogoutPayload,
|
||||
LogoutRequestDto,
|
||||
RefreshTokenDto,
|
||||
RefreshTokenRequestDto,
|
||||
RegisterPayload,
|
||||
RegisterRequestDto,
|
||||
SendSmsCodeDto,
|
||||
SendSmsCodePayload,
|
||||
SendSmsCodeRequestDto,
|
||||
SendSmsCodeResult,
|
||||
} from '@/type'
|
||||
|
||||
const shouldLogAuthLifecycle =
|
||||
import.meta.env.VITE_ENABLE_REQUEST_LOG === 'true'
|
||||
|
||||
function unwrapEnvelope<T>(
|
||||
response: ApiResponse<T>,
|
||||
fallbackErrorKey = 'auth.errors.requestFailed',
|
||||
) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new ApiError({
|
||||
data: response,
|
||||
message:
|
||||
typeof response.msg === 'string' && response.msg.length > 0
|
||||
? response.msg
|
||||
: typeof response.message === 'string' && response.message.length > 0
|
||||
? response.message
|
||||
: fallbackErrorKey,
|
||||
})
|
||||
}
|
||||
|
||||
function logAuthSessionExpiry(action: string, session: AuthSessionInput) {
|
||||
if (!shouldLogAuthLifecycle || !session.accessTokenExpiresAt) {
|
||||
return
|
||||
}
|
||||
|
||||
console.info(
|
||||
`[auth] ${action} user-token expires at ${new Date(
|
||||
session.accessTokenExpiresAt,
|
||||
).toISOString()} (${session.accessTokenExpiresAt})`,
|
||||
)
|
||||
}
|
||||
|
||||
async function getCurrentUserProfileByToken(userToken: string) {
|
||||
const response = await api.post<AuthUserProfileDto>(AUTH_ENDPOINTS.profile, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${userToken}`,
|
||||
'user-token': userToken,
|
||||
},
|
||||
})
|
||||
|
||||
return normalizeAuthUserProfile(
|
||||
unwrapEnvelope(
|
||||
response as ApiResponse<AuthUserProfileDto>,
|
||||
'auth.errors.requestFailed',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async function buildEnrichedAuthSession(dto: AuthSessionDto) {
|
||||
const session = normalizeAuthSession(dto)
|
||||
|
||||
try {
|
||||
const profileUser = await getCurrentUserProfileByToken(session.accessToken)
|
||||
|
||||
return {
|
||||
...session,
|
||||
currentUser: mergeAuthUsers(session.currentUser, profileUser),
|
||||
} satisfies AuthSessionInput
|
||||
} catch {
|
||||
return session
|
||||
}
|
||||
}
|
||||
|
||||
export async function loginWithPassword(
|
||||
payload: LoginPayload,
|
||||
): Promise<AuthSessionInput> {
|
||||
const response = await api.post<AuthSessionDto, LoginRequestDto>(
|
||||
AUTH_ENDPOINTS.login,
|
||||
{
|
||||
json: {
|
||||
device_id: getAuthDeviceId(),
|
||||
password: payload.password,
|
||||
username: payload.username,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const session = await buildEnrichedAuthSession(
|
||||
unwrapEnvelope(
|
||||
response as ApiResponse<AuthSessionDto>,
|
||||
'auth.login.errors.submitFailed',
|
||||
),
|
||||
)
|
||||
|
||||
logAuthSessionExpiry('login', session)
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
export async function logoutWithPassword(
|
||||
payload: LogoutPayload,
|
||||
): Promise<void> {
|
||||
const response = await api.post<null, LogoutRequestDto>(
|
||||
AUTH_ENDPOINTS.logout,
|
||||
{
|
||||
json: {
|
||||
password: payload.password,
|
||||
username: payload.username,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
unwrapEnvelope(
|
||||
response as ApiResponse<null>,
|
||||
'auth.logout.errors.submitFailed',
|
||||
)
|
||||
}
|
||||
|
||||
export async function registerWithPassword(
|
||||
payload: RegisterPayload,
|
||||
): Promise<AuthSessionInput> {
|
||||
const response = await api.post<AuthSessionDto, RegisterRequestDto>(
|
||||
AUTH_ENDPOINTS.register,
|
||||
{
|
||||
json: {
|
||||
captcha: payload.captcha,
|
||||
device_id: getAuthDeviceId(),
|
||||
invite_code: payload.inviteCode,
|
||||
password: payload.password,
|
||||
username: payload.mobile,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const session = await buildEnrichedAuthSession(
|
||||
unwrapEnvelope(
|
||||
response as ApiResponse<AuthSessionDto>,
|
||||
'auth.register.errors.submitFailed',
|
||||
),
|
||||
)
|
||||
|
||||
logAuthSessionExpiry('register', session)
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
export async function sendSmsCode(
|
||||
payload: SendSmsCodePayload,
|
||||
): Promise<SendSmsCodeResult> {
|
||||
const response = await api.post<SendSmsCodeDto, SendSmsCodeRequestDto>(
|
||||
AUTH_ENDPOINTS.sendSmsCode,
|
||||
{
|
||||
json: {
|
||||
event: SMS_SEND_EVENT_REGISTER,
|
||||
mobile: payload.mobile,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const data = unwrapEnvelope(
|
||||
response as ApiResponse<SendSmsCodeDto>,
|
||||
'auth.register.sms.errors.submitFailed',
|
||||
)
|
||||
|
||||
return {
|
||||
expiresIn: data.expires_in,
|
||||
messageId: data.message_id,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentUserProfile() {
|
||||
const response = await api.post<AuthUserProfileDto>(AUTH_ENDPOINTS.profile)
|
||||
|
||||
return normalizeAuthUserProfile(
|
||||
unwrapEnvelope(
|
||||
response as ApiResponse<AuthUserProfileDto>,
|
||||
'auth.errors.requestFailed',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export async function refreshAuthSession(
|
||||
refreshToken: string,
|
||||
): Promise<AuthSessionInput | null> {
|
||||
const response = await api.post<RefreshTokenDto, RefreshTokenRequestDto>(
|
||||
AUTH_ENDPOINTS.refreshToken,
|
||||
{
|
||||
context: {
|
||||
[AUTH_SKIP_REFRESH_CONTEXT_KEY]: true,
|
||||
},
|
||||
json: {
|
||||
refresh_token: refreshToken,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const session = normalizeRefreshAuthSession(
|
||||
unwrapEnvelope(
|
||||
response as ApiResponse<RefreshTokenDto>,
|
||||
'auth.errors.requestFailed',
|
||||
),
|
||||
)
|
||||
|
||||
logAuthSessionExpiry('refresh', session)
|
||||
|
||||
return session
|
||||
}
|
||||
Reference in New Issue
Block a user