SPB Git forge

spb/uqo-chat

Public
14commits 1branches 0releases
1.4 MBsize
maindefault branch
17 days agolast push
Python 64.6% TypeScript 33.7% CSS 0.8%
2.4 KB · 70 lines typescript
Raw Blame History
1/** Minimal API client: cookie session + bearer token (for SSE), French error messages. */23export const API_URL = import.meta.env.VITE_API_URL || '/api/v1';4const TOKEN_KEY = 'uqo.token';56export function getToken(): string | null {7  return localStorage.getItem(TOKEN_KEY);8}9export function setToken(token: string | null): void {10  if (token) localStorage.setItem(TOKEN_KEY, token);11  else localStorage.removeItem(TOKEN_KEY);12}1314export class ApiError extends Error {15  status: number;16  constructor(status: number, message: string) {17    super(message);18    this.status = status;19  }20}2122export function authHeaders(): Record<string, string> {23  const t = getToken();24  return t ? { Authorization: `Bearer ${t}` } : {};25}2627export async function api<T>(path: string, init: RequestInit = {}): Promise<T> {28  const headers: Record<string, string> = { ...authHeaders(), ...(init.headers as Record<string, string> | undefined) };29  if (init.body && !(init.body instanceof FormData)) headers['Content-Type'] = 'application/json';30  const res = await fetch(`${API_URL}${path}`, { ...init, headers, credentials: 'include' });31  if (res.status === 401) {32    setToken(null);33    window.dispatchEvent(new CustomEvent('uqo:unauthorized'));34  }35  if (!res.ok) {36    let detail = 'Une erreur est survenue.';37    try {38      const j = await res.json();39      detail = typeof j.detail === 'string' ? j.detail : JSON.stringify(j.detail);40    } catch {41      /* ignore */42    }43    throw new ApiError(res.status, detail);44  }45  if (res.status === 204) return undefined as T;46  return (await res.json()) as T;47}4849export const fileUrl = (fileId: string): string => `${API_URL}/files/${fileId}`;5051export async function downloadFile(fileId: string, filename: string): Promise<void> {52  const { url } = await api<{ url: string }>(`/files/${fileId}/link`);53  const a = document.createElement('a');54  a.href = url;55  a.download = filename;56  document.body.appendChild(a);57  a.click();58  a.remove();59}6061export async function shareFile(fileId: string, filename: string): Promise<boolean> {62  if (!navigator.share) return false;63  const res = await fetch(fileUrl(fileId), { headers: authHeaders(), credentials: 'include' });64  const blob = await res.blob();65  const file = new File([blob], filename, { type: blob.type });66  if (navigator.canShare && !navigator.canShare({ files: [file] })) return false;67  await navigator.share({ files: [file], title: filename });68  return true;69}70