Newer
Older
bugtrail / packages / extension / src / background / settings.ts
import { createHttpClient, type HttpClient } from "@ltt/shared";
import browser from "webextension-polyfill";

/** Server settings persisted in storage.local. */
export interface Settings {
  serverUrl: string;
  /** web panel base URL; falls back to serverUrl when unset (same origin in prod) */
  panelUrl: string | null;
  token: string | null;
  tokenExpiresAt: string | null;
  defaultProjectId: string | null;
  user: { id: string; nickname: string; email: string } | null;
}

const DEFAULTS: Settings = {
  serverUrl: "http://localhost:8001",
  panelUrl: null,
  token: null,
  tokenExpiresAt: null,
  defaultProjectId: null,
  user: null,
};

export async function getSettings(): Promise<Settings> {
  const stored = await browser.storage.local.get(Object.keys(DEFAULTS));
  return { ...DEFAULTS, ...stored } as Settings;
}

export async function saveSettings(patch: Partial<Settings>): Promise<void> {
  await browser.storage.local.set(patch);
}

export async function clearSession(): Promise<void> {
  await saveSettings({ token: null, tokenExpiresAt: null, user: null, defaultProjectId: null });
}

export async function login(email: string, password: string): Promise<{ nickname: string; email: string }> {
  const settings = await getSettings();
  const response = await fetch(`${settings.serverUrl}/api/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Client": "extension" },
    body: JSON.stringify({ email, password }),
  });
  if (!response.ok) {
    const detail = await response.json().catch(() => null);
    throw new Error(detail?.detail ?? `Login failed (${response.status})`);
  }
  const { token, expires_at } = (await response.json()) as { token: string; expires_at: string };

  // fetch the profile with the new token
  const meResponse = await fetch(`${settings.serverUrl}/api/auth/me`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!meResponse.ok) throw new Error("Login succeeded but profile fetch failed");
  const user = (await meResponse.json()) as { id: string; nickname: string; email: string };

  await saveSettings({ token, tokenExpiresAt: expires_at, user });
  return user;
}

export async function logout(): Promise<void> {
  const settings = await getSettings();
  if (settings.token) {
    await fetch(`${settings.serverUrl}/api/auth/logout`, {
      method: "POST",
      headers: { Authorization: `Bearer ${settings.token}` },
    }).catch(() => {});
  }
  await clearSession();
}

/** Client with the current stored token; refreshes settings each call. */
export async function getHttpClient(): Promise<HttpClient> {
  const settings = await getSettings();
  return createHttpClient({ baseUrl: settings.serverUrl, token: settings.token });
}

/** Base URL of the web panel for share links and the "open panel" shortcut. */
export function panelBase(settings: Settings): string {
  return (settings.panelUrl ?? settings.serverUrl).replace(/\/+$/, "");
}