Files
mengpost/mengpost-frontend/src/api/client.js

112 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }),
}