import { useTorchStore } from '../stores/torch' import { getWebMCP, autoConnect } from './webmcp' import { endpoints } from '../config/endpoints' const API_BASE = endpoints.api let pollingInterval: number | null = null let clientId: string | null = null /** * Register this browser as a torch client */ export async function registerClient(): Promise { try { const res = await fetch(`${API_BASE}/torch/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userAgent: navigator.userAgent, hostname: window.location.hostname }) }) if (!res.ok) { console.error('[Torch] Failed to register:', res.status) return null } const data = await res.json() clientId = data.id const torchStore = useTorchStore() torchStore.setClientId(data.id) console.log(`[Torch] Registered as ${data.id}, hasTorch: ${data.hasTorch}`) // If we have the torch, connect to MCP if (data.hasTorch) { torchStore.setTorchState(data.id) await connectToMCP() } return data.id } catch (e) { console.error('[Torch] Error registering:', e) return null } } /** * Unregister this browser */ export async function unregisterClient(): Promise { if (!clientId) return try { await fetch(`${API_BASE}/torch/client/${clientId}`, { method: 'DELETE' }) console.log('[Torch] Unregistered') } catch (e) { console.error('[Torch] Error unregistering:', e) } clientId = null } /** * Request the torch */ export async function requestTorch(): Promise { if (!clientId) { console.error('[Torch] Not registered') return false } const torchStore = useTorchStore() torchStore.setRequesting(true) try { const res = await fetch(`${API_BASE}/torch/request`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientId }) }) if (!res.ok) { console.error('[Torch] Failed to request:', res.status) torchStore.setRequesting(false) return false } const data = await res.json() console.log('[Torch] Torch granted!') // Update state and connect to MCP torchStore.setTorchState(clientId) torchStore.setRequesting(false) await connectToMCP() return true } catch (e) { console.error('[Torch] Error requesting:', e) torchStore.setRequesting(false) return false } } /** * Release the torch */ export async function releaseTorch(): Promise { if (!clientId) { console.error('[Torch] Not registered') return false } const torchStore = useTorchStore() if (!torchStore.hasTorch) { console.warn('[Torch] Cannot release - do not have torch') return false } try { const res = await fetch(`${API_BASE}/torch/release`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientId }) }) if (!res.ok) { console.error('[Torch] Failed to release:', res.status) return false } console.log('[Torch] Torch released') // Update state and disconnect from MCP torchStore.setTorchState(null) disconnectFromMCP() return true } catch (e) { console.error('[Torch] Error releasing:', e) return false } } /** * Fetch current torch state from server */ export async function fetchTorchState(): Promise { try { const res = await fetch(`${API_BASE}/torch`) if (!res.ok) return const data = await res.json() const torchStore = useTorchStore() torchStore.setClients(data.clients) // Check if torch holder changed const hadTorch = torchStore.hasTorch const hasTorchNow = data.holderId === clientId if (hadTorch && !hasTorchNow) { // We lost the torch - disconnect console.log('[Torch] Lost torch, disconnecting from MCP') torchStore.setTorchState(data.holderId) disconnectFromMCP() } else if (!hadTorch && hasTorchNow) { // We got the torch - connect console.log('[Torch] Got torch, connecting to MCP') torchStore.setTorchState(data.holderId) await connectToMCP() } else { torchStore.setTorchState(data.holderId) } } catch (e) { // Silently ignore polling errors } } /** * Start polling for torch state changes */ export function startTorchPolling(intervalMs: number = 2000) { if (pollingInterval) return console.log('[Torch] Starting polling...') pollingInterval = window.setInterval(fetchTorchState, intervalMs) } /** * Stop polling */ export function stopTorchPolling() { if (pollingInterval) { window.clearInterval(pollingInterval) pollingInterval = null console.log('[Torch] Polling stopped') } } /** * Connect to MCP (when we have the torch) */ async function connectToMCP(): Promise { const webmcp = getWebMCP() if (!webmcp) { console.error('[Torch] WebMCP not initialized') return } if (webmcp.isConnected) { console.log('[Torch] Already connected to MCP') return } console.log('[Torch] Connecting to MCP...') const success = await autoConnect() if (success) { console.log('[Torch] Connected to MCP') } else { console.error('[Torch] Failed to connect to MCP') } } /** * Disconnect from MCP (when we lose the torch) */ function disconnectFromMCP(): void { const webmcp = getWebMCP() if (!webmcp) return if (webmcp.isConnected && typeof webmcp.disconnect === 'function') { console.log('[Torch] Disconnecting from MCP...') webmcp.disconnect() } } /** * Initialize torch system */ export async function initTorch(): Promise { // Register this client await registerClient() // Start polling for state changes startTorchPolling() // Cleanup on page unload window.addEventListener('beforeunload', () => { stopTorchPolling() // Note: unregisterClient is async, may not complete before unload // Server should handle stale clients via timeout }) } /** * Cleanup torch system */ export async function destroyTorch(): Promise { stopTorchPolling() await unregisterClient() const torchStore = useTorchStore() torchStore.reset() }