import axios from 'axios' import axiosRetry from 'axios-retry' import router from '@/router' import { authStorageKeys } from '@/authStorage' export const api = axios.create({ baseURL: '/', timeout: 30000, }) axiosRetry(api, { retries: 3, retryDelay: axiosRetry.exponentialDelay, retryCondition: (err) => { // Retry on network errors or 5xx, but never on 401/403/404/4xx if (!err.response) return true return err.response.status >= 500 && err.response.status < 600 }, onRetry: (_retryCount, _err, _requestConfig) => { // no-op — could log in dev }, }) api.interceptors.response.use( (r) => r, (err) => { if (err.response?.status === 401) { localStorage.removeItem(authStorageKeys.token) localStorage.removeItem(authStorageKeys.email) router.push('/login') } return Promise.reject(err) } ) // ——— Auth ——— export const authApi = { login: (email: string, password: string) => api.post<{ access_token: string }>('/api/auth/login', { email, password }), me: () => api.get<{ email: string }>('/api/auth/me'), } // ——— Upstreams ——— export interface UpstreamData { id: number name: string base_url: string api_prefix: string auth_type: string auth_config_masked: Record rate_endpoint: string groups_endpoint: string enabled: boolean check_interval_seconds: number timeout_seconds: number last_status: string last_checked_at: string | null last_error: string | null created_at: string updated_at: string } export interface UpstreamForm { name: string base_url: string api_prefix: string auth_type: string auth_config: Record rate_endpoint: string groups_endpoint: string enabled: boolean check_interval_seconds: number timeout_seconds: number } export const upstreamsApi = { list: () => api.get('/api/upstreams'), create: (data: UpstreamForm) => api.post('/api/upstreams', data), update: (id: number, data: Partial) => api.put(`/api/upstreams/${id}`, data), delete: (id: number) => api.delete(`/api/upstreams/${id}`), test: (id: number) => api.post<{ success: boolean; message: string; detail?: string }>(`/api/upstreams/${id}/test`), checkNow: (id: number) => api.post<{ success: boolean; message: string }>(`/api/upstreams/${id}/check-now`), latestSnapshot: (id: number) => api.get(`/api/upstreams/${id}/snapshots/latest`), listSnapshots: (id: number, limit = 20, offset = 0) => api.get(`/api/upstreams/${id}/snapshots`, { params: { limit, offset } }), } // ——— Websites ——— export interface WebsiteData { id: number name: string site_type: string base_url: string api_prefix: string auth_type: string auth_config_masked: Record groups_endpoint: string group_update_endpoint: string enabled: boolean auto_sync_enabled: boolean timeout_seconds: number last_status: string last_checked_at: string | null last_error: string | null created_at: string updated_at: string } export interface WebsiteForm { name: string site_type: string base_url: string api_prefix: string auth_type: string auth_config: Record groups_endpoint: string group_update_endpoint: string enabled: boolean auto_sync_enabled: boolean timeout_seconds: number } export interface WebsiteGroup { id: string name: string rate_multiplier: string | null raw: Record } export interface BindingSourceGroup { upstream_id: number group_id: string upstream_name: string group_name: string } export interface GroupBindingData { id: number website_id: number website_name: string target_group_id: string target_group_name: string source_groups: BindingSourceGroup[] percent: number algorithm: string enabled: boolean created_at: string updated_at: string } export interface GroupBindingForm { website_id: number target_group_id: string target_group_name: string source_groups: BindingSourceGroup[] percent: number algorithm: string enabled: boolean } export interface WebsiteSyncLog { id: number website_id: number binding_id: number | null target_group_id: string target_group_name: string algorithm: string percent: number source_rates: Array> old_rate: string | null new_rate: string | null status: string message: string created_at: string } export const websitesApi = { list: () => api.get('/api/websites'), create: (data: WebsiteForm) => api.post('/api/websites', data), update: (id: number, data: Partial) => api.put(`/api/websites/${id}`, data), delete: (id: number) => api.delete(`/api/websites/${id}`), test: (id: number) => api.post<{ success: boolean; message: string; detail?: string }>(`/api/websites/${id}/test`), groups: (id: number) => api.get(`/api/websites/${id}/groups`), listBindings: () => api.get('/api/group-bindings'), createBinding: (data: GroupBindingForm) => api.post('/api/group-bindings', data), updateBinding: (id: number, data: Partial) => api.put(`/api/group-bindings/${id}`, data), deleteBinding: (id: number) => api.delete(`/api/group-bindings/${id}`), syncNow: (id: number) => api.post(`/api/group-bindings/${id}/sync-now`), logs: (params?: { website_id?: number; binding_id?: number; limit?: number; offset?: number }) => api.get('/api/website-sync-logs', { params }), } // ——— Webhooks ——— export interface WebhookData { id: number name: string type: string url: string secret_masked: string enabled: boolean events: string[] created_at: string updated_at: string } export interface WebhookForm { name: string type: string url: string secret: string enabled: boolean events: string[] } export const webhooksApi = { list: () => api.get('/api/webhooks'), create: (data: WebhookForm) => api.post('/api/webhooks', data), update: (id: number, data: Partial) => api.put(`/api/webhooks/${id}`, data), delete: (id: number) => api.delete(`/api/webhooks/${id}`), test: (id: number) => api.post<{ success: boolean; message: string }>(`/api/webhooks/${id}/test`), } // ——— Logs ——— export interface LogData { id: number webhook_config_id: number webhook_name: string event_type: string payload: Record status: string response_text: string | null created_at: string } export const logsApi = { list: (params?: { status?: string; event_type?: string; limit?: number; offset?: number }) => api.get('/api/notification-logs', { params }), } // ——— Custom Pages ——— export type CustomPageAccessMode = 'direct' | 'proxy' | 'remote_browser' export interface CustomPageData { id: number name: string url: string icon: string sort_order: number enabled: boolean use_proxy: boolean access_mode: CustomPageAccessMode description: string | null login_username: string | null login_username_selector: string | null login_password_selector: string | null login_submit_selector: string | null login_autofill_enabled: boolean login_password_configured: boolean created_at: string updated_at: string } export interface CustomPageForm { name: string url: string icon: string sort_order: number enabled: boolean use_proxy: boolean access_mode: CustomPageAccessMode description?: string login_username?: string login_password?: string login_username_selector?: string login_password_selector?: string login_submit_selector?: string login_autofill_enabled?: boolean login_password_clear?: boolean } export const customPagesApi = { list: () => api.get('/api/custom-pages'), listPublic: () => axios.get('/api/custom-pages/public'), create: (data: CustomPageForm) => api.post('/api/custom-pages', data), update: (id: number, data: Partial) => api.put(`/api/custom-pages/${id}`, data), delete: (id: number) => api.delete(`/api/custom-pages/${id}`), } // ——— Remote browser sessions ——— export interface BrowserSessionData { id: string custom_page_id: number url: string title: string } export type BrowserEventPayload = | { type: 'click' | 'dblclick' | 'mousemove' | 'mousedown' | 'mouseup'; x: number; y: number; button?: 'left' | 'right' | 'middle' } | { type: 'type'; text: string } | { type: 'key'; key: string } | { type: 'scroll'; delta_x: number; delta_y: number; x?: number; y?: number } | { type: 'reload' | 'back' | 'forward' } | { type: 'resize'; width: number; height: number } export const browserSessionsApi = { create: (data: { custom_page_id: number; width: number; height: number }) => api.post('/api/browser-sessions', data), get: (id: string) => api.get(`/api/browser-sessions/${id}`), event: (id: string, data: BrowserEventPayload) => api.post(`/api/browser-sessions/${id}/events`, data), selection: (id: string) => api.get<{ text: string }>(`/api/browser-sessions/${id}/selection`), close: (id: string) => api.delete(`/api/browser-sessions/${id}`), screenshotUrl: (id: string, token?: string) => { const params = new URLSearchParams({ t: String(Date.now()) }) if (token) params.set('token', token) return `/api/browser-sessions/${id}/screenshot?${params.toString()}` }, /** Build a WebSocket URL for the streaming endpoint. */ wsUrl: (id: string, token?: string) => { const proto = location.protocol === 'https:' ? 'wss:' : 'ws:' const params = new URLSearchParams() if (token) params.set('token', token) return `${proto}//${location.host}/api/browser-sessions/${id}/ws?${params.toString()}` }, }