api.js 1.61 KB
/**
 * 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
}