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( response: ApiResponse, 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.get(AUTH_ENDPOINTS.profile, { headers: { Authorization: `Bearer ${userToken}`, 'user-token': userToken, }, }) return normalizeAuthUserProfile( unwrapEnvelope( response as ApiResponse, '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 { const response = await api.post( AUTH_ENDPOINTS.login, { json: { device_id: getAuthDeviceId(), password: payload.password, username: payload.username, }, }, ) const session = await buildEnrichedAuthSession( unwrapEnvelope( response as ApiResponse, 'auth.login.errors.submitFailed', ), ) logAuthSessionExpiry('login', session) return session } export async function logoutWithPassword( payload: LogoutPayload, ): Promise { const response = await api.post( AUTH_ENDPOINTS.logout, { json: { password: payload.password, username: payload.username, }, }, ) unwrapEnvelope( response as ApiResponse, 'auth.logout.errors.submitFailed', ) } export async function registerWithPassword( payload: RegisterPayload, ): Promise { const response = await api.post( 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, 'auth.register.errors.submitFailed', ), ) logAuthSessionExpiry('register', session) return session } export async function sendSmsCode( payload: SendSmsCodePayload, ): Promise { const response = await api.post( AUTH_ENDPOINTS.sendSmsCode, { json: { event: SMS_SEND_EVENT_REGISTER, mobile: payload.mobile, }, }, ) const data = unwrapEnvelope( response as ApiResponse, 'auth.register.sms.errors.submitFailed', ) return { expiresIn: data.expires_in, messageId: data.message_id, } } export async function getCurrentUserProfile() { const response = await api.get(AUTH_ENDPOINTS.profile) return normalizeAuthUserProfile( unwrapEnvelope( response as ApiResponse, 'auth.errors.requestFailed', ), ) } export async function refreshAuthSession( refreshToken: string, ): Promise { const response = await api.post( AUTH_ENDPOINTS.refreshToken, { context: { [AUTH_SKIP_REFRESH_CONTEXT_KEY]: true, }, json: { refresh_token: refreshToken, }, }, ) const session = normalizeRefreshAuthSession( unwrapEnvelope( response as ApiResponse, 'auth.errors.requestFailed', ), ) logAuthSessionExpiry('refresh', session) return session }