mxivideo/src/services/nakamaAuth.ts

330 lines
8.6 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.

/**
* 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<void> {
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<UserProfile> {
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<UserProfile> {
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<void> {
try {
if (this.session) {
await this.client.sessionLogout(this.session);
}
} catch (error) {
console.error('Logout error:', error);
} finally {
this.clearStoredSession();
}
}
/**
* 获取当前用户信息
*/
async getCurrentUser(): Promise<UserProfile | null> {
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<Pick<UserProfile, 'displayName' | 'avatarUrl'>>): Promise<void> {
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<void> {
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;