/** * voko-server 网页接口的唯一调用点。access token 存在 sessionStorage(Alpha 内部使用)。 */ import { reactive } from 'vue' const TOKEN_KEY = 'voko.access_token' /** 登录状态(给顶栏用)。 */ export const session = reactive({ loggedIn: sessionStorage.getItem(TOKEN_KEY) !== null }) export function getToken() { return sessionStorage.getItem(TOKEN_KEY) } /** @param {string|null} token */ export function setToken(token) { if (token === null) sessionStorage.removeItem(TOKEN_KEY) else sessionStorage.setItem(TOKEN_KEY, token) session.loggedIn = token !== null } /** * @param {string} method * @param {string} path * @param {unknown} [body] * @param {string} [idempotencyKey] 写接口的幂等键:同一次提交重试不会重复扣积分 */ export async function api(method, path, body, idempotencyKey) { const token = getToken() const response = await fetch(path, { method, headers: { ...(body === undefined ? {} : { 'content-type': 'application/json' }), ...(token === null ? {} : { authorization: `Bearer ${token}` }), ...(idempotencyKey === undefined ? {} : { 'idempotency-key': idempotencyKey }), }, body: body === undefined ? undefined : JSON.stringify(body), }) // 登录已过期:清掉 token 回登录页,登录后回到当前页(否则页面只会空白)。 if (response.status === 401 && token !== null) { setToken(null) window.location.assign(`/login?next=${encodeURIComponent(window.location.pathname)}`) } const json = await response.json() if (!response.ok) throw new Error(json.error.message) return json } /** * 带登录态下载一个文件并交给浏览器保存(交付文件需要鉴权,不能直接用链接)。 * @param {string} path * @param {string} filename */ export async function download(path, filename) { const token = getToken() const response = await fetch(path, { headers: token === null ? {} : { authorization: `Bearer ${token}` } }) if (!response.ok) throw new Error((await response.json()).error.message) const url = URL.createObjectURL(await response.blob()) const link = document.createElement('a') link.href = url link.download = filename link.click() URL.revokeObjectURL(url) }