/** * Nakama认证服务 * 处理用户登录、注册、会话管理等功能 */ import { Client, Session } from '@heroiclabs/nakama-js'; export interface LoginCredentials { email?: string; username?: string; password: string; } export interface RegisterCredentials { email: string; username: string; password: string; displayName?: string; } export interface UserProfile { id: string; username: string; displayName: string; email?: string; avatarUrl?: string; createTime: string; updateTime: string; } export interface AuthState { isAuthenticated: boolean; user: UserProfile | null; session: Session | null; loading: boolean; error: string | null; } class NakamaAuthService { private client: Client; private session: Session | null = null; private readonly SERVER_KEY = 'defaultkey'; // 替换为实际的服务器密钥 private readonly HOST = '43.143.58.201'; // 替换为实际的Nakama服务器地址 private readonly PORT = '7350'; // 替换为实际的端口 private readonly USE_SSL = true; // 根据实际情况设置 constructor() { this.client = new Client(this.SERVER_KEY, this.HOST, this.PORT, this.USE_SSL); this.loadStoredSession(); } /** * 从本地存储加载会话 */ private loadStoredSession(): void { try { const storedSession = localStorage.getItem('nakama_session'); if (storedSession) { const sessionData = JSON.parse(storedSession); this.session = Session.restore( sessionData.token, sessionData.refresh_token, sessionData.username, sessionData.user_id, sessionData.created, sessionData.expires_at, sessionData.vars ); // 检查会话是否过期 if (this.session.isexpired(Date.now() / 1000)) { this.refreshSession(); } } } catch (error) { console.error('Failed to load stored session:', error); this.clearStoredSession(); } } /** * 保存会话到本地存储 */ private saveSession(session: Session): void { try { const sessionData = { token: session.token, refresh_token: session.refresh_token, username: session.username, user_id: session.user_id, created: session.created, expires_at: session.expires_at, vars: session.vars }; localStorage.setItem('nakama_session', JSON.stringify(sessionData)); this.session = session; } catch (error) { console.error('Failed to save session:', error); } } /** * 清除本地存储的会话 */ private clearStoredSession(): void { localStorage.removeItem('nakama_session'); this.session = null; } /** * 刷新会话 */ private async refreshSession(): Promise { if (!this.session?.refresh_token) { throw new Error('No refresh token available'); } try { const newSession = await this.client.sessionRefresh(this.session); this.saveSession(newSession); } catch (error) { console.error('Failed to refresh session:', error); this.clearStoredSession(); throw error; } } /** * 用户登录 */ async login(credentials: LoginCredentials): Promise { try { let session: Session; if (credentials.email) { // 邮箱登录 session = await this.client.authenticateEmail(credentials.email, credentials.password); } else if (credentials.username) { // 用户名登录 session = await this.client.authenticateUsername(credentials.username, credentials.password); } else { throw new Error('Email or username is required'); } this.saveSession(session); // 获取用户信息 const account = await this.client.getAccount(session); return { id: account.user?.id || '', username: account.user?.username || '', displayName: account.user?.display_name || account.user?.username || '', email: account.email, avatarUrl: account.user?.avatar_url, createTime: account.user?.create_time || '', updateTime: account.user?.update_time || '' }; } catch (error) { console.error('Login failed:', error); throw new Error(this.getErrorMessage(error)); } } /** * 用户注册 */ async register(credentials: RegisterCredentials): Promise { try { const session = await this.client.authenticateEmail( credentials.email, credentials.password, true, // create account if not exists credentials.username, credentials.displayName ); this.saveSession(session); // 获取用户信息 const account = await this.client.getAccount(session); return { id: account.user?.id || '', username: account.user?.username || '', displayName: account.user?.display_name || credentials.displayName || credentials.username, email: account.email, avatarUrl: account.user?.avatar_url, createTime: account.user?.create_time || '', updateTime: account.user?.update_time || '' }; } catch (error) { console.error('Registration failed:', error); throw new Error(this.getErrorMessage(error)); } } /** * 用户登出 */ async logout(): Promise { try { if (this.session) { await this.client.sessionLogout(this.session); } } catch (error) { console.error('Logout error:', error); } finally { this.clearStoredSession(); } } /** * 获取当前用户信息 */ async getCurrentUser(): Promise { if (!this.session) { return null; } try { // 检查会话是否过期 if (this.session.isexpired(Date.now() / 1000)) { await this.refreshSession(); } const account = await this.client.getAccount(this.session); return { id: account.user?.id || '', username: account.user?.username || '', displayName: account.user?.display_name || account.user?.username || '', email: account.email, avatarUrl: account.user?.avatar_url, createTime: account.user?.create_time || '', updateTime: account.user?.update_time || '' }; } catch (error) { console.error('Failed to get current user:', error); this.clearStoredSession(); return null; } } /** * 检查是否已登录 */ isAuthenticated(): boolean { return this.session !== null && !this.session.isexpired(Date.now() / 1000); } /** * 获取当前会话 */ getSession(): Session | null { return this.session; } /** * 更新用户资料 */ async updateProfile(updates: Partial>): Promise { if (!this.session) { throw new Error('Not authenticated'); } try { await this.client.updateAccount(this.session, { display_name: updates.displayName, avatar_url: updates.avatarUrl }); } catch (error) { console.error('Failed to update profile:', error); throw new Error(this.getErrorMessage(error)); } } /** * 修改密码 */ async changePassword(currentPassword: string, newPassword: string): Promise { if (!this.session) { throw new Error('Not authenticated'); } try { // 首先验证当前密码 const account = await this.client.getAccount(this.session); if (account.email) { await this.client.authenticateEmail(account.email, currentPassword); } // 更新密码(这里需要根据Nakama的实际API调整) // Nakama可能需要通过其他方式更新密码 console.warn('Password change not implemented - requires server-side function'); throw new Error('Password change requires server-side implementation'); } catch (error) { console.error('Failed to change password:', error); throw new Error(this.getErrorMessage(error)); } } /** * 获取错误消息 */ private getErrorMessage(error: any): string { if (error?.message) { return error.message; } // 根据Nakama错误码返回友好的错误消息 switch (error?.code) { case 3: // INVALID_ARGUMENT return '输入参数无效'; case 5: // NOT_FOUND return '用户不存在'; case 6: // ALREADY_EXISTS return '用户已存在'; case 16: // UNAUTHENTICATED return '用户名或密码错误'; default: return '操作失败,请稍后重试'; } } } // 导出单例实例 export const nakamaAuth = new NakamaAuthService(); export default nakamaAuth;