File size: 3,027 Bytes
373030b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Thin API client. All requests go through the Vite proxy at /api -> backend.
// A JWT access token (from login/register/google) is kept in localStorage and
// sent as `Authorization: Bearer <token>`. A 401 on an AUTHENTICATED request
// (one that carried a token) clears it and notifies the app with a reason; a 401
// on a login attempt (no token) just throws so the form shows the error inline.
const BASE = "/api";
const TOKEN_KEY = "ff_token";

export const tokenStore = {
  get: () => localStorage.getItem(TOKEN_KEY),
  set: (t) => localStorage.setItem(TOKEN_KEY, t),
  clear: () => localStorage.removeItem(TOKEN_KEY),
};

let onUnauthorized = null;
export function setOnUnauthorized(fn) {
  onUnauthorized = fn;
}

async function handle(res, hadToken) {
  if (res.status === 401 && hadToken) {
    tokenStore.clear();
    onUnauthorized?.("Your session expired — please sign in again.");
  }
  if (!res.ok) {
    let detail = res.statusText;
    try {
      const body = await res.json();
      detail = body.detail || detail;
    } catch (_) {
      /* ignore parse errors */
    }
    throw new Error(detail);
  }
  return res.json();
}

function request(path, { method = "GET", body, json } = {}) {
  const headers = {};
  const token = tokenStore.get();
  if (token) headers["Authorization"] = `Bearer ${token}`;
  let payload = body;
  if (json !== undefined) {
    headers["Content-Type"] = "application/json";
    payload = JSON.stringify(json);
  }
  return fetch(`${BASE}${path}`, { method, headers, body: payload }).then((res) =>
    handle(res, !!token)
  );
}

const storeToken = (r) => {
  tokenStore.set(r.access_token);
  return r;
};

export const api = {
  // ---- Auth ----
  register: (creds) => request("/auth/register", { method: "POST", json: creds }).then(storeToken),
  login: (creds) => request("/auth/login", { method: "POST", json: creds }).then(storeToken),
  googleLogin: (credential) =>
    request("/auth/google", { method: "POST", json: { credential } }).then(storeToken),
  me: () => request("/auth/me"),
  logout: () => tokenStore.clear(),

  // ---- App ----
  health: () => request("/health"),
  listDocuments: () => request("/documents"),
  uploadDocument: (file) => {
    const form = new FormData();
    form.append("file", file);
    return request("/documents/upload", { method: "POST", body: form });
  },
  deleteDocument: (id) => request(`/documents/${id}`, { method: "DELETE" }),
  ask: (document_id, question, mode = "fast") =>
    request("/ask", { method: "POST", json: { document_id, question, mode } }),
  audit: (document_id, audit_type = "general") =>
    request("/audit", { method: "POST", json: { document_id, audit_type } }),
  auditPacket: (document_ids) =>
    request("/audit/packet", { method: "POST", json: { document_ids } }),
  history: (document_id) =>
    request(`/history${document_id ? `?document_id=${document_id}` : ""}`),
  evaluate: () => request("/evaluate", { method: "POST" }),
  seedDemo: () => request("/demo/seed", { method: "POST" }),
};