const TOKEN_KEY = 'mengpost_token' const EVT = 'mengpost-token' /** 生产环境前后端分离时设 VITE_API_URL, 如 http://127.0.0.1:8787 */ const base = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '') export function apiUrl(path) { if (!path.startsWith('/')) path = '/' + path return base ? base + path : path } export const tokenStore = { get: () => localStorage.getItem(TOKEN_KEY) || '', set: (t) => { localStorage.setItem(TOKEN_KEY, t) window.dispatchEvent(new Event(EVT)) }, clear: () => { localStorage.removeItem(TOKEN_KEY) window.dispatchEvent(new Event(EVT)) }, subscribe: (fn) => { window.addEventListener(EVT, fn) return () => window.removeEventListener(EVT, fn) }, } function parseJsonSafe(txt) { if (!txt || !txt.trim()) return null try { return JSON.parse(txt) } catch { return { error: txt.length > 120 ? txt.slice(0, 120) + '…' : txt } } } async function request(method, path, body) { const headers = { 'Content-Type': 'application/json' } const t = tokenStore.get() if (t) headers['X-Auth-Token'] = t let res try { res = await fetch(apiUrl(path), { method, headers, body: body ? JSON.stringify(body) : undefined, }) } catch (e) { throw new Error( e.message?.includes('Failed to fetch') ? '无法连接后端:请确认已启动 mengpost-backend (默认 8787),且本页通过 Vite 开发服务器打开或已配置 VITE_API_URL' : e.message, ) } const txt = await res.text() const data = parseJsonSafe(txt) if (!res.ok) { const msg = (data && data.error) || res.statusText || '请求失败' throw new Error(msg) } return data } /** 列表接口在空 body / SW 缓存异常时可能得到 null,统一成数组 */ function asArray(v) { return Array.isArray(v) ? v : [] } /** 校验 token 时不带 X-Auth-Token,避免旧 token 干扰 */ export async function verifyToken(token) { let res try { res = await fetch(apiUrl('/api/auth/verify'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }), }) } catch (e) { throw new Error( e.message?.includes('Failed to fetch') ? '无法连接后端:请先运行 dev.bat / dev.sh 启动后端,或设置 VITE_API_URL' : e.message, ) } const txt = await res.text() const data = parseJsonSafe(txt) if (!res.ok) { if (data && data.ok === false) return { ok: false } throw new Error((data && data.error) || res.statusText || '验证失败') } return data } export const api = { listAccounts: () => request('GET', '/api/accounts').then(asArray), createAccount: (a) => request('POST', '/api/accounts', a), updateAccount: (id, a) => request('PUT', `/api/accounts/${id}`, a), deleteAccount: (id) => request('DELETE', `/api/accounts/${id}`), listMailboxes: (id) => request('GET', `/api/accounts/${id}/mailboxes`).then(asArray), listMessages: (id, mailbox = 'INBOX', limit = 30) => request('GET', `/api/accounts/${id}/messages?mailbox=${encodeURIComponent(mailbox)}&limit=${limit}`).then( asArray, ), getMessage: (id, uid, mailbox = 'INBOX') => request('GET', `/api/accounts/${id}/messages/${uid}?mailbox=${encodeURIComponent(mailbox)}`), send: (id, body) => request('POST', `/api/accounts/${id}/send`, body), listSent: (id, limit = 50) => request('GET', `/api/accounts/${id}/sent?limit=${limit}`).then(asArray), microsoftOAuthStart: () => request('GET', '/api/oauth/microsoft/start'), microsoftOAuthFinish: (state) => request('POST', '/api/oauth/microsoft/finish', { state }), }