diff --git a/ui/env.js b/ui/env.js index 152bd41..7aa8411 100644 --- a/ui/env.js +++ b/ui/env.js @@ -10,16 +10,8 @@ export default async ($mod) => { console.error('Failed to load langs.json', e) } if (!$mod.$auth) { - // Initialize VBase Service $mod.users = {} - const vbase = new VBase('vb', $mod.scoped, null, $mod.users); // Relative path - $mod.$auth = vbase; + const vbase = new VBase($mod.scoped, null, $mod.users) + $mod.$auth = vbase } - $mod.$axios.interceptors.response.use(function(response) { - return response?.data - }, function(error) { - let data = error.response ? error.response.data : error.response - return Promise.reject(data?.message || data); - }); - } diff --git a/ui/vbase.js b/ui/vbase.js index a132285..b7fafcf 100644 --- a/ui/vbase.js +++ b/ui/vbase.js @@ -16,18 +16,18 @@ export const Level = { Admin: 7, // 111 管理员 (完全控制) }; +const REFRESH_INTERVAL = 12 * 60 * 1000; // 12 分钟(access_token 15min 过期) + class VBase { - constructor(scope, baseURL, login_page, users = {}) { - if (!scope) throw new Error('VBase: scope is required'); + constructor(baseURL, login_page, users = {}) { if (!baseURL) baseURL = window.location.origin; if (baseURL === '' || baseURL === '/') baseURL = window.location.origin; this.baseURL = baseURL; - this.scope = scope; this.login_page = login_page || (baseURL + '/login'); this.userKey = 'vbase_user'; + this.refreshTimer = null; this.users = users; - this._user = null; try { @@ -36,15 +36,58 @@ class VBase { this._user = cached; this._cachePublicUser(cached); } - } catch (e) {} + } catch (e) { } this._pendingUserIDs = new Set(); this._loadingUserIDs = new Set(); this._resolvedUserIDs = new Set(); this._pendingUserFlush = null; - // 验证登录状态 - this.fetchUser().catch(() => {}); + // 初始化鉴权:有用户缓存就验证 + 定时刷新 + if (this._user) { + this._ensureAuth(); + } + } + + async _ensureAuth() { + const now = Date.now(); + const lastRefresh = parseInt(localStorage.getItem('vbase_last_refresh'), 10) || 0; + + // 超过 12 分钟没刷新,先刷新再拉用户 + if (now - lastRefresh > REFRESH_INTERVAL) { + const ok = await this.refresh(); + if (!ok) { + this.clear(); + return; + } + } + + // 无用户信息或刷新后重新拉 + try { + await this.fetchUser(); + } catch (e) { + this.clear(); + return; + } + + // 启动定时刷新 + this._startRefreshTimer(); + } + + _startRefreshTimer() { + this._stopRefreshTimer(); + this.refreshTimer = setInterval(() => { + this.refresh().then(ok => { + if (!ok) this.clear(); + }); + }, REFRESH_INTERVAL); + } + + _stopRefreshTimer() { + if (this.refreshTimer) { + clearInterval(this.refreshTimer); + this.refreshTimer = null; + } } // ========== Getters / Setters ========== @@ -57,10 +100,15 @@ class VBase { localStorage.setItem(this.userKey, JSON.stringify(val)); } else { localStorage.removeItem(this.userKey); + localStorage.removeItem('vbase_last_refresh'); } this._cachePublicUser(val); } + _touchRefresh() { + localStorage.setItem('vbase_last_refresh', Date.now().toString()); + } + // ========== API 请求 ========== async request(method, path, data = null, headers = {}) { @@ -94,12 +142,19 @@ class VBase { // ========== 认证 ========== + /** 登录成功后初始化鉴权状态(密码登录之外的方式:验证码、OAuth、注册后自动登录等) */ + async onAuthSuccess(user) { + this._touchRefresh(); + this.user = user; + await this.fetchUser(); + this._startRefreshTimer(); + } + /** 用户名密码登录 */ async login(username, password) { const data = await this.request('POST', '/api/auth/login', { username, password }); if (data.user) { - this.user = data.user; - await this.fetchUser(); + await this.onAuthSuccess(data.user); return true; } return false; @@ -108,38 +163,25 @@ class VBase { /** OAuth 回调 */ async oauthCallback(provider, code, state) { const data = await this.request('GET', `/api/auth/callback/${provider}?code=${code}&state=${state}`); - if (data.user) { - this.user = data.user; - await this.fetchUser(); - } + if (data.user) await this.onAuthSuccess(data.user); return data; } /** 绑定已有账号 */ async bindAccount(tempToken, username, password) { const data = await this.request('POST', '/api/auth/bind', { - temp_token: tempToken, - username, - password, + temp_token: tempToken, username, password, }); - if (data.user) { - this.user = data.user; - await this.fetchUser(); - } + if (data.user) await this.onAuthSuccess(data.user); return data; } /** 绑定并注册 */ async bindRegister(tempToken, username, email) { const data = await this.request('POST', '/api/auth/bind-register', { - temp_token: tempToken, - username, - email, + temp_token: tempToken, username, email, }); - if (data.user) { - this.user = data.user; - await this.fetchUser(); - } + if (data.user) await this.onAuthSuccess(data.user); return data; } @@ -156,10 +198,11 @@ class VBase { } } - /** Token 刷新(后端自动处理,前端可主动调用) */ + /** Token 刷新 */ async refresh() { try { await this.request('POST', '/api/auth/refresh', {}); + this._touchRefresh(); return true; } catch (e) { return false; @@ -180,6 +223,7 @@ class VBase { /** 清除登录状态 */ clear() { + this._stopRefreshTimer(); this.user = null; for (const id of Object.keys(this.users)) { delete this.users[id];