import axios from 'axios' const http = axios.create({ baseURL: '/api', timeout: 10000 }) export const getVersion = () => http.get('/version').then((r) => r.data) export const getStatus = () => http.get('/status').then((r) => r.data) export const getMultiStatus = () => http.get('/bot/status').then((r) => r.data) export const getLiveCheck = () => http.get('/live-check').then((r) => r.data) export const startMany = (payload) => http.post('/bot/start-many', payload).then((r) => r.data) export const getStrategies = () => http.get('/strategies').then((r) => r.data) export const getPositions = () => http.get('/positions').then((r) => r.data) export const getOrders = () => http.get('/orders').then((r) => r.data) export const getEvents = () => http.get('/events').then((r) => r.data) export const getEquity = () => http.get('/equity').then((r) => r.data) export const getLogs = (limit = 100) => http.get('/logs', { params: { limit } }).then((r) => r.data) export const startBot = (payload) => http.post('/bot/start', payload).then((r) => r.data) export const stopBot = () => http.post('/bot/stop').then((r) => r.data) export const killBot = (reason = 'manual kill switch') => http.post('/bot/kill', { reason }).then((r) => r.data) export const closePosition = (id, reason = 'manual') => http.post(`/positions/${id}/close`, { reason }).then((r) => r.data) export const placeOrder = (payload) => http.post('/orders', payload).then((r) => r.data) export const runBacktest = (payload) => http.post('/backtest', payload, { timeout: 60000 }).then((r) => r.data) export const runSweep = (payload) => http.post('/sweep', payload, { timeout: 120000 }).then((r) => r.data) export const getPresets = () => http.get('/presets').then((r) => r.data) export const createPreset = (payload) => http.post('/presets', payload).then((r) => r.data) export const deletePreset = (id) => http.delete(`/presets/${id}`).then((r) => r.data) export function openSocket({ onMessage, onOpen, onClose, onError }) { const proto = window.location.protocol === 'https:' ? 'wss' : 'ws' const url = `${proto}://${window.location.host}/api/ws` let socket let retryTimer let closed = false let attempt = 0 const schedule = () => { if (closed) return // Retry at 1s, then back off up to 30s so a persistently failing socket // does not hammer the server while a brief blip recovers immediately. const delay = Math.min(1000 * 2 ** attempt, 30000) attempt += 1 retryTimer = setTimeout(connect, delay) } const connect = () => { if (closed) return let opened = false let reported = false console.debug('[ws] connecting to', url) socket = new WebSocket(url) // Report at most one failure per attempt (Safari often fires error *and* // close for the same bad handshake). const reportFailure = (code, detail) => { if (reported) return reported = true onError?.(code, detail) } // Some mobile browsers leave a blocked upgrade hanging without firing // onclose/onerror. Treat a slow handshake as a failure so the caller can // fall back to polling instead of waiting forever. const openTimeout = setTimeout(() => { if (!opened) { reportFailure(0, 'handshake timed out') socket.close() } }, 5000) socket.onopen = () => { opened = true clearTimeout(openTimeout) attempt = 0 console.debug('[ws] open') onOpen?.() } socket.onmessage = (event) => onMessage?.(JSON.parse(event.data)) socket.onclose = (event) => { clearTimeout(openTimeout) console.debug('[ws] close', event.code, event.reason) onClose?.() if (!opened) { // 1008 = policy violation (token rejected); 1006 = abnormal close. reportFailure(event.code, event.reason) } schedule() } socket.onerror = (event) => { clearTimeout(openTimeout) if (!opened) reportFailure(0, event?.message || 'connection error') socket.close() } } connect() return { close() { closed = true clearTimeout(retryTimer) socket?.close() }, } }