chore: track version files, docs, web app split, scripts (missed commits from March work)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
window.INP2P = window.INP2P || {};
|
||||
|
||||
window.INP2P.createApi = function createApi(state) {
|
||||
const bearer = () => ({ Authorization: 'Bearer ' + (localStorage.getItem('t') || '') });
|
||||
const inFlight = new Map();
|
||||
|
||||
const withTimeout = async (fetcher, timeoutMs=8000) => {
|
||||
let timer = null;
|
||||
try {
|
||||
return await Promise.race([
|
||||
fetcher(),
|
||||
new Promise((_, rej) => { timer = setTimeout(() => rej(new Error('请求超时')), timeoutMs); })
|
||||
]);
|
||||
} finally {
|
||||
if (timer) clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
|
||||
const shouldRetry = (method, err) => {
|
||||
const m = String(method || 'GET').toUpperCase();
|
||||
if (m !== 'GET') return false;
|
||||
const msg = (err && err.message) ? String(err.message) : '';
|
||||
return msg.includes('请求超时') || msg.includes('Failed to fetch') || msg.includes('NetworkError');
|
||||
};
|
||||
|
||||
const doRequest = async (path, opt={}) => {
|
||||
const headers = { 'Content-Type': 'application/json', ...(opt.headers||{}), ...bearer() };
|
||||
const method = String(opt.method || 'GET').toUpperCase();
|
||||
const timeoutMs = Number(opt.timeoutMs || (method === 'GET' ? 8000 : 12000));
|
||||
|
||||
const runOnce = async () => {
|
||||
const r = await withTimeout(() => fetch(path, { ...opt, method, headers }), timeoutMs);
|
||||
let d = {};
|
||||
try { d = await r.json(); } catch(_) {}
|
||||
if (!r.ok) {
|
||||
if (r.status === 401) {
|
||||
state.loggedIn.value = false;
|
||||
state.logEvent && state.logEvent('api_error', { path, status:r.status, message:'401 登录已过期' });
|
||||
throw new Error('401 登录已过期');
|
||||
}
|
||||
const em = d.message || ('HTTP ' + r.status);
|
||||
state.logEvent && state.logEvent('api_error', { path, status:r.status, message: em });
|
||||
throw new Error(em);
|
||||
}
|
||||
state.logEvent && state.logEvent('api_ok', { path, status:r.status, method });
|
||||
return d;
|
||||
};
|
||||
|
||||
try {
|
||||
return await runOnce();
|
||||
} catch (e) {
|
||||
if (shouldRetry(method, e)) {
|
||||
state.logEvent && state.logEvent('api_retry', { path, method, reason: e.message || 'retry' });
|
||||
return await runOnce();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const api = async (path, opt={}) => {
|
||||
const method = String(opt.method || 'GET').toUpperCase();
|
||||
const dedupe = opt.dedupe !== false && method === 'GET';
|
||||
const key = dedupe ? `${method}:${path}` : '';
|
||||
|
||||
if (dedupe && inFlight.has(key)) {
|
||||
state.logEvent && state.logEvent('api_dedupe_hit', { path, method });
|
||||
return inFlight.get(key);
|
||||
}
|
||||
|
||||
const p = doRequest(path, opt).finally(() => {
|
||||
if (dedupe) inFlight.delete(key);
|
||||
});
|
||||
|
||||
if (dedupe) inFlight.set(key, p);
|
||||
return p;
|
||||
};
|
||||
|
||||
const loginByPassword = async (username, password) => {
|
||||
return withTimeout(() => fetch('/api/v1/auth/login', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ username, password })
|
||||
}), 10000).then(r=>r.json());
|
||||
};
|
||||
|
||||
return { api, loginByPassword };
|
||||
};
|
||||
Reference in New Issue
Block a user