88 lines
2.9 KiB
JavaScript
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 };
|
|
};
|