Files
agent-ui/frontend/src/services/session-state-ws.ts
josedario87 e1aa8b1bdb feat: integrate Tauri v2 with Android widget and voice assistant
- Add Tauri v2 shell (Cargo, tauri.conf.json, capabilities, plugins)
- Migrate all fetch() calls to apiFetch() for Tauri-aware HTTP
- Migrate WebSocket endpoints to resolveEndpoints() for dynamic URLs
- Add ServerConfigDialog for remote server URL configuration
- Add tauri.ts lib with isTauri detection, apiFetch wrapper, plugin helpers
- Add server-config Pinia store with persistence via plugin-store
- Conditional PWA (disabled in Tauri builds)
- Android: home screen transcript widget (last 5 messages, 30s refresh)
- Android: voice command / share activity (SpeechRecognizer + WebSocket)
- Android: signed release APK with auto-copy to installers/
- Remove stale frontend/src-tauri directory
2026-02-23 15:33:43 -06:00

77 lines
2.0 KiB
TypeScript

import { resolveEndpoints } from '@/config/endpoints'
import { useSessionState } from '@/stores/session-state'
let ws: WebSocket | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
let reconnectDelay = 1000
const MAX_RECONNECT_DELAY = 15000
function connect() {
const store = useSessionState()
// Connect to terminal server (4103) — same base as terminal WS
const url = resolveEndpoints().terminal
console.log('[SessionStateWS] Connecting to', url)
ws = new WebSocket(url)
ws.onopen = () => {
console.log('[SessionStateWS] Connected')
store.setConnected(true)
reconnectDelay = 1000
}
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data)
if (msg.type === 'session-state-snapshot' || msg.type === 'session-state-patch' || msg.type === 'terminal-registry-change') {
store.handleMessage(msg)
}
// Ignore other message types (terminal output, legacy broadcasts, etc.)
} catch {
// Non-JSON or malformed — ignore
}
}
ws.onclose = () => {
console.log('[SessionStateWS] Disconnected, reconnecting in', reconnectDelay, 'ms')
store.setConnected(false)
ws = null
scheduleReconnect()
}
ws.onerror = () => {
// onclose will fire after onerror
}
}
function scheduleReconnect() {
if (reconnectTimer) clearTimeout(reconnectTimer)
reconnectTimer = setTimeout(() => {
reconnectTimer = null
connect()
// Exponential backoff
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT_DELAY)
}, reconnectDelay)
}
/** Initialize the session state WebSocket. Call once on app mount. */
export function initSessionStateWS() {
if (ws) return // Already connected
connect()
}
/** Disconnect and stop reconnecting. */
export function destroySessionStateWS() {
if (reconnectTimer) {
clearTimeout(reconnectTimer)
reconnectTimer = null
}
if (ws) {
ws.onclose = null // Prevent reconnect
ws.close()
ws = null
}
useSessionState().setConnected(false)
}