Files
inp2p/web/app/api.js
T

88 lines
2.9 KiB
JavaScript

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 };
};