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