const DEFAULT_RECONNECT_MS = 1800; export const DEFAULT_AGENT_URL = "ws://127.0.0.1:8765/ws/gestures"; export function createMotionAgentProvider(options = {}) { const { url = DEFAULT_AGENT_URL, reconnectMs = DEFAULT_RECONNECT_MS, WebSocketCtor = typeof WebSocket !== "undefined" ? WebSocket : null, onMessage = () => {}, onState = () => {}, onStatus = () => {}, } = options; let socket = null; let reconnectTimer = null; let disposed = false; let connected = false; function emitState(detail = {}) { onState({ provider: "motion_agent", connected, url, ...detail, }); } function clearReconnect() { if (!reconnectTimer) return; clearTimeout(reconnectTimer); reconnectTimer = null; } function scheduleReconnect() { if (disposed || reconnectTimer) return; reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, reconnectMs); } function closeSocket() { if (!socket) return; const current = socket; socket = null; current.onopen = null; current.onmessage = null; current.onerror = null; current.onclose = null; try { current.close(); } catch (_error) { // Browser WebSocket close can throw during teardown in older engines. } } function connect() { if (disposed || socket) return; if (!WebSocketCtor) { emitState({ error: "websocket_unavailable", message: "当前浏览器不支持 WebSocket" }); return; } try { socket = new WebSocketCtor(url); } catch (_error) { emitState({ error: "socket_create_failed", message: "无法创建 Motion Agent 连接" }); scheduleReconnect(); return; } socket.onopen = () => { connected = true; emitState({ connected: true }); onStatus("动捕 Agent 已连接", "info"); }; socket.onmessage = (rawMessage) => onMessage(rawMessage?.data ?? rawMessage); socket.onerror = () => { emitState({ connected: false, error: "socket_error", message: "Motion Agent 连接异常" }); }; socket.onclose = () => { connected = false; socket = null; emitState({ connected: false, message: "Motion Agent 未连接" }); scheduleReconnect(); }; } return { provider: "motion_agent", start() { if (disposed) return false; connect(); return true; }, stop() { disposed = true; clearReconnect(); closeSocket(); connected = false; emitState({ connected: false }); }, isConnected() { return connected; }, }; }