Initial clean project import

This commit is contained in:
cat-shark
2026-06-19 18:41:41 +08:00
commit a13b804c7a
1306 changed files with 220568 additions and 0 deletions
+421
View File
@@ -0,0 +1,421 @@
/**
* 前端认证服务
* 负责与后端 IPC 通信,处理用户注册、登录、Token 管理等
*/
const AUTH_TOKEN_KEY = 'auth_token';
const AUTH_USER_KEY = 'auth_user';
const AUTH_SUBSCRIPTION_KEY = 'auth_subscription';
const AUTH_SESSION_FLAG = 'auth_session_active';
const AUTH_REMEMBER_ME_KEY = 'auth_remember_me';
// 🔧 应用启动时清除登录状态(确保每次启动都需要重新登录)
// 使用 sessionStorage 标记当前会话,如果没有标记说明是新启动
// 💡 新增:如果用户选择了"记住我",则不清除登录状态
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
const isSessionActive = sessionStorage.getItem(AUTH_SESSION_FLAG);
const rememberMe = localStorage.getItem(AUTH_REMEMBER_ME_KEY);
if (!isSessionActive) {
// 检查是否勾选了"记住我"
if (rememberMe !== 'true') {
// 新会话,且用户未选择记住我,清除之前的登录状态
console.log('[Auth] 新会话启动,清除之前的登录状态');
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(AUTH_USER_KEY);
localStorage.removeItem(AUTH_SUBSCRIPTION_KEY);
} else {
console.log('[Auth] 新会话启动,但用户选择了记住我,保留登录状态');
}
// 标记当前会话
sessionStorage.setItem(AUTH_SESSION_FLAG, 'true');
}
}
interface AuthResult {
success: boolean;
token?: string;
user?: any;
subscription?: any;
message?: string;
}
interface VerifyResult {
valid: boolean;
payload?: any;
kicked?: boolean;
message?: string;
apiUnavailable?: boolean;
}
interface RegisterData {
username: string;
email: string;
password: string;
invite_code?: string;
}
interface LoginData {
email: string;
password: string;
rememberMe?: boolean;
}
/**
* 检查 window.$mapi 是否可用
*/
const ensureApiAvailable = async (): Promise<boolean> => {
for (let i = 0; i < 10; i++) {
if (typeof window !== 'undefined' && window['$mapi'] && window['$mapi'].auth) {
return true;
}
if (i === 0) {
const keys = typeof window !== 'undefined' ? Object.keys(window).filter(k => k.startsWith('$') || k.startsWith('__')) : [];
console.warn(`[Auth.ensureApiAvailable] window.$mapi 不存在, 等待重试 (${i + 1}/10), window特殊键:`, keys);
}
await new Promise(r => setTimeout(r, 500));
}
console.error('[Auth.ensureApiAvailable] 等待超时,window.$mapi 仍未就绪');
return false;
};
const clearAuthStorage = () => {
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(AUTH_USER_KEY);
localStorage.removeItem(AUTH_SUBSCRIPTION_KEY);
};
const refreshRemoteConfigAfterAuth = async () => {
try {
const systemConfig = await import('./systemConfig');
systemConfig.clearConfigCache();
const config = await systemConfig.fetchSystemConfig();
(window as any).__systemConfig = config;
} catch (error) {
console.warn('[Auth] 刷新渲染进程系统配置失败:', error);
}
try {
const result = await window['$mapi']?.runninghub?.refreshConfig?.();
if (result && !result.success) {
console.warn('[Auth] 刷新主进程 RunningHub 配置失败:', result.error);
}
} catch (error) {
console.warn('[Auth] 刷新主进程 RunningHub 配置异常:', error);
}
};
/**
* 用户注册
*/
const register = async (data: RegisterData): Promise<AuthResult> => {
try {
if (!await ensureApiAvailable()) {
return {
success: false,
message: '认证 API 不可用'
};
}
const result = await window['$mapi'].auth.register(data);
if (result.success && result.token) {
// 保存 Token 和用户信息
localStorage.setItem(AUTH_TOKEN_KEY, result.token);
localStorage.setItem(AUTH_USER_KEY, JSON.stringify(result.user));
localStorage.setItem(AUTH_SUBSCRIPTION_KEY, JSON.stringify(result.subscription));
await refreshRemoteConfigAfterAuth();
}
return result;
} catch (error: any) {
return {
success: false,
message: error.message || '注册失败'
};
}
};
/**
* 用户登录
*/
const login = async (data: LoginData): Promise<AuthResult> => {
try {
if (!await ensureApiAvailable()) {
return {
success: false,
message: '认证 API 不可用'
};
}
const result = await window['$mapi'].auth.login(data);
if (result.success && result.token) {
// 保存 Token 和用户信息
localStorage.setItem(AUTH_TOKEN_KEY, result.token);
localStorage.setItem(AUTH_USER_KEY, JSON.stringify(result.user));
localStorage.setItem(AUTH_SUBSCRIPTION_KEY, JSON.stringify(result.subscription));
// 💡 保存"记住我"的选择
if (data.rememberMe) {
localStorage.setItem(AUTH_REMEMBER_ME_KEY, 'true');
localStorage.setItem('auth_saved_email', data.email);
localStorage.setItem('auth_saved_password', data.password);
console.log('[Auth] 用户选择了记住我,已保存登录状态');
} else {
localStorage.removeItem(AUTH_REMEMBER_ME_KEY);
localStorage.removeItem('auth_saved_email');
localStorage.removeItem('auth_saved_password');
console.log('[Auth] 用户未选择记住我,登录状态仅保留到应用关闭');
}
await refreshRemoteConfigAfterAuth();
}
return result;
} catch (error: any) {
return {
success: false,
message: error.message || '登录失败'
};
}
};
/**
* 用户登出
*/
const logout = async () => {
try {
clearAuthStorage();
localStorage.removeItem(AUTH_REMEMBER_ME_KEY);
localStorage.removeItem('auth_saved_email');
localStorage.removeItem('auth_saved_password');
return {
success: true,
message: '已登出'
};
} catch (error: any) {
return {
success: false,
message: error.message || '登出失败'
};
}
};
/**
* 获取存储的 Token
*/
const getToken = (): string | null => {
try {
return localStorage.getItem(AUTH_TOKEN_KEY);
} catch (error) {
return null;
}
};
/**
* 获取存储的用户信息
*/
const getUser = (): any | null => {
try {
const userStr = localStorage.getItem(AUTH_USER_KEY);
return userStr ? JSON.parse(userStr) : null;
} catch (error) {
return null;
}
};
/**
* 获取存储的订阅信息
*/
const getSubscription = (): any | null => {
try {
const subStr = localStorage.getItem(AUTH_SUBSCRIPTION_KEY);
return subStr ? JSON.parse(subStr) : null;
} catch (error) {
return null;
}
};
/**
* 验证 Token 有效性
*/
const verifyToken = async (token?: string): Promise<VerifyResult> => {
try {
if (!await ensureApiAvailable()) {
return {
valid: false,
message: '认证 API 不可用',
apiUnavailable: true,
};
}
const tokenToVerify = token || getToken();
if (!tokenToVerify) {
return {
valid: false,
message: 'Token 不存在'
};
}
const result = await window['$mapi'].auth.verifyToken(tokenToVerify);
if (!result.valid) {
clearAuthStorage();
}
return result;
} catch (error: any) {
return {
valid: false,
message: error.message || 'Token 验证失败'
};
}
};
/**
* 获取当前用户信息(从后端)
*/
const getCurrentUser = async (token?: string) => {
try {
if (!await ensureApiAvailable()) {
return {
success: false,
message: '认证 API 不可用'
};
}
const tokenToUse = token || getToken();
if (!tokenToUse) {
return {
success: false,
message: 'Token 不存在'
};
}
const result = await window['$mapi'].auth.getCurrentUser(tokenToUse);
if (result.success) {
// 更新本地缓存
localStorage.setItem(AUTH_USER_KEY, JSON.stringify(result.user));
localStorage.setItem(AUTH_SUBSCRIPTION_KEY, JSON.stringify(result.subscription));
}
return result;
} catch (error: any) {
return {
success: false,
message: error.message || '获取用户信息失败'
};
}
};
/**
* 检查订阅状态
*/
const checkSubscriptionStatus = async (userId?: number) => {
try {
if (!await ensureApiAvailable()) {
return {
hasAccess: false,
isExpired: true
};
}
// 如果没有提供 userId,从本地用户信息获取
const user = userId ? { id: userId } : getUser();
if (!user || !user.id) {
return {
hasAccess: false,
isExpired: true,
message: '用户不存在'
};
}
const result = await window['$mapi'].auth.checkSubscriptionStatus(user.id);
if (result.subscription) {
localStorage.setItem(AUTH_SUBSCRIPTION_KEY, JSON.stringify(result.subscription));
}
return result;
} catch (error: any) {
return {
hasAccess: false,
isExpired: true,
message: error.message || '检查订阅状态失败'
};
}
};
/**
* 检查用户是否已登录
*/
const isAuthenticated = (): boolean => {
return !!getToken() && !!getUser();
};
/**
* 检查用户是否为管理员
*/
const isAdmin = (): boolean => {
const user = getUser();
return user?.role === 'admin';
};
/**
* 检查是否可以访问应用(已登录 + 订阅未过期)
*/
const canAccess = async (): Promise<boolean> => {
const token = getToken();
if (!token) {
return false;
}
const verifyResult = await verifyToken(token);
if (!verifyResult.valid) {
return false;
}
const result = await getCurrentUser(token);
if (!result.success || !result.subscription) {
return false;
}
const expiresAt = new Date(result.subscription.expires_at);
const now = new Date();
return now <= expiresAt;
};
/**
* 获取剩余天数
*/
const getRemainingDays = (): number => {
const subscription = getSubscription();
if (!subscription) {
return 0;
}
const expiresAt = new Date(subscription.expires_at);
const now = new Date();
const diffTime = expiresAt.getTime() - now.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays > 0 ? diffDays : 0;
};
export default {
register,
login,
logout,
getToken,
getUser,
getSubscription,
verifyToken,
getCurrentUser,
checkSubscriptionStatus,
isAuthenticated,
isAdmin,
canAccess,
getRemainingDays
};