88c6ce8ccc
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.4 KiB
JavaScript
108 lines
3.4 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var refreshPromise = null;
|
|
|
|
function getAccessToken() {
|
|
try { return localStorage.getItem('soon_access') || ''; } catch (e) { return ''; }
|
|
}
|
|
|
|
function getRefreshToken() {
|
|
try { return localStorage.getItem('soon_refresh') || ''; } catch (e) { return ''; }
|
|
}
|
|
|
|
function clearSession() {
|
|
try {
|
|
localStorage.removeItem('soon_access');
|
|
localStorage.removeItem('soon_refresh');
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
function getApiBase() {
|
|
var cfg = (typeof window !== 'undefined' && window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base);
|
|
if (cfg) return String(cfg).replace(/\/+$/, '');
|
|
if (typeof window !== 'undefined' && window.location) {
|
|
return window.location.origin + '/api/v1';
|
|
}
|
|
return '/api/v1';
|
|
}
|
|
|
|
function redirectToLogin() {
|
|
var path = 'login.web.html';
|
|
try {
|
|
var current = window.location.pathname.replace(/^.*\/pages\//, '').replace(/^\//, '');
|
|
if (current && current.indexOf('login.web.html') !== 0) {
|
|
var redirect = typeof soonSafeRedirect === 'function'
|
|
? soonSafeRedirect(current + window.location.search, 'index.web.html')
|
|
: 'index.web.html';
|
|
path = 'login.web.html?redirect=' + encodeURIComponent(redirect);
|
|
}
|
|
} catch (e) { /* use default */ }
|
|
window.location.href = path;
|
|
}
|
|
|
|
function refreshAccessToken() {
|
|
if (refreshPromise) return refreshPromise;
|
|
var refresh = getRefreshToken();
|
|
if (!refresh) {
|
|
return Promise.reject(new Error('no_refresh_token'));
|
|
}
|
|
refreshPromise = fetch(getApiBase() + '/auth/refresh', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ refresh_token: refresh }),
|
|
}).then(function (r) {
|
|
return r.json().then(function (j) {
|
|
if (!r.ok || !j.ok || !j.data || !j.data.access_token) {
|
|
throw new Error(j.message || 'refresh_failed');
|
|
}
|
|
localStorage.setItem('soon_access', j.data.access_token);
|
|
if (j.data.refresh_token) {
|
|
localStorage.setItem('soon_refresh', j.data.refresh_token);
|
|
}
|
|
return j.data.access_token;
|
|
});
|
|
}).finally(function () {
|
|
refreshPromise = null;
|
|
});
|
|
return refreshPromise;
|
|
}
|
|
|
|
function soonAuthedFetch(url, opts) {
|
|
opts = opts || {};
|
|
var headers = Object.assign({}, opts.headers || {});
|
|
var token = getAccessToken();
|
|
if (token) {
|
|
headers['Authorization'] = 'Bearer ' + token;
|
|
} else {
|
|
delete headers['Authorization'];
|
|
delete headers['authorization'];
|
|
}
|
|
opts.headers = headers;
|
|
|
|
var fullUrl = url;
|
|
if (url.indexOf('http') !== 0) {
|
|
fullUrl = getApiBase() + '/' + String(url).replace(/^\/+/, '');
|
|
}
|
|
|
|
return fetch(fullUrl, opts).then(function (response) {
|
|
if (response.status !== 401) return response;
|
|
return refreshAccessToken().then(function (newToken) {
|
|
var retryHeaders = Object.assign({}, opts.headers || {});
|
|
retryHeaders['Authorization'] = 'Bearer ' + newToken;
|
|
var retryOpts = Object.assign({}, opts, { headers: retryHeaders });
|
|
return fetch(fullUrl, retryOpts);
|
|
}).catch(function () {
|
|
clearSession();
|
|
redirectToLogin();
|
|
return response;
|
|
});
|
|
});
|
|
}
|
|
|
|
window.soonAuthedFetch = soonAuthedFetch;
|
|
window.soonClearAuthSession = clearSession;
|
|
window.soonGetAccessToken = getAccessToken;
|
|
window.soonRedirectToLogin = redirectToLogin;
|
|
})();
|