export interface UserStatsSnapshot { access_key: string; bytes_up: number; bytes_down: number; connections: number; limit_bytes: number | null; online: boolean; name?: string | null; } export interface ServerStatus { version: string; uptime_seconds: number; active_users: number; total_users: number; } export interface ApiResponse { ok: boolean; data?: T; error?: string; } const API_TOKEN_KEY = 'ostp_api_token'; export function getApiSettings() { // Use relative path for embedded panel, fallback to localhost for dev const isDev = import.meta.env?.DEV || window.location.hostname === 'localhost' && window.location.port === '5173'; const url = isDev ? 'http://localhost:9090' : window.location.pathname.replace(/\/$/, ''); const token = localStorage.getItem(API_TOKEN_KEY) || ''; return { url, token }; } export function saveApiToken(token: string) { localStorage.setItem(API_TOKEN_KEY, token.trim()); } export function clearApiAuth() { localStorage.removeItem(API_TOKEN_KEY); } async function request(path: string, options: RequestInit = {}): Promise { const { url, token } = getApiSettings(); const headers = new Headers(options.headers || {}); if (token) { headers.set('Authorization', `Bearer ${token}`); } if (!(options.body instanceof FormData) && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json'); } const response = await fetch(`${url}${path}`, { ...options, headers, }); if (response.status === 401) { throw new Error('Unauthorized API Token'); } const json: ApiResponse = await response.json(); if (!json.ok) { throw new Error(json.error || 'API Request failed'); } return json.data!; } export const api = { login: async (username: string, password?: string): Promise => { const { url } = getApiSettings(); const response = await fetch(`${url}/api/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); if (response.status === 401) throw new Error('Invalid credentials'); const json = await response.json(); if (!json.ok) throw new Error(json.error || 'Login failed'); saveApiToken(json.data.token); return json.data.token; }, getServerStatus: () => request('/api/server/status'), getServerConfig: () => request('/api/server/config'), updateServerConfig: (config: any) => request('/api/server/config', { method: 'PUT', body: JSON.stringify(config), }), listUsers: () => request('/api/users'), createUser: (name: string | null, limitBytes: number | null, accessKey?: string) => request('/api/users', { method: 'POST', body: JSON.stringify({ name, limit_bytes: limitBytes, access_key: accessKey }), }), updateUser: (key: string, name: string | null, limitBytes: number | null) => request(`/api/users/${key}`, { method: 'PUT', body: JSON.stringify({ name, limit_bytes: limitBytes }), }), deleteUser: (key: string) => request(`/api/users/${key}`, { method: 'DELETE', }), resetUserStats: (key: string) => request(`/api/users/${key}/reset`, { method: 'POST', }), getSubscriptionLink: async (key: string): Promise => { const { url } = getApiSettings(); const response = await fetch(`${url}/api/subscribe/${key}`, { headers: { 'Accept': 'text/plain', } }); const json = await response.json(); if (json.ok && json.data) { return json.data; } throw new Error(json.error || 'Failed to fetch subscription link'); } };