import { FIELDLENS_SCHEMA_VERSION, type DeviceRegistration, type Station, type SyncResponse, } from '@fieldlens/contracts' import { FIELDLENS_SYNC_REQUEST_EVENT, acknowledgeOutboxEvent, failOutboxEvent, getInstallationId, getLocalMetadata, getPendingOutbox, setLocalMetadata, } from '../../../packages/fieldlens-core/src/lib/database' import { getAccessToken } from './auth' const apiBase = (import.meta.env.VITE_API_BASE_URL ?? '').replace(/\/$/, '') export type RegisteredDevice = { id: string station: Station } async function authenticatedFetch(path: string, init: RequestInit = {}) { const token = await getAccessToken() if (!token) throw new Error('Authentication unavailable') const headers = new Headers(init.headers) headers.set('Authorization', `Bearer ${token}`) if (init.body) headers.set('Content-Type', 'application/json') const response = await fetch(`${apiBase}${path}`, { ...init, headers }) if (!response.ok) throw new Error(`API ${response.status}`) return response } export async function ensureDeviceRegistered(): Promise { const existing = await getLocalMetadata('registeredDevice') const token = await getAccessToken() if (!token) return existing ?? null const installationId = await getInstallationId() const registration: DeviceRegistration = { installationId, appVersion: import.meta.env.VITE_APP_VERSION ?? 'dev', schemaVersion: FIELDLENS_SCHEMA_VERSION, } try { const response = await authenticatedFetch('/api/v1/devices/register', { method: 'POST', body: JSON.stringify(registration), }) const next = await response.json() as RegisteredDevice await setLocalMetadata('registeredDevice', next) return next } catch { return existing ?? null } } let syncInFlight: Promise | null = null let syncRequested = false export function syncNow() { syncRequested = true if (syncInFlight) return syncInFlight syncInFlight = (async () => { do { syncRequested = false await runSync() } while (syncRequested) })().finally(() => { syncInFlight = null }) return syncInFlight } async function runSync() { const device = await ensureDeviceRegistered() if (!device) return const events = await getPendingOutbox(50) if (events.length === 0) return try { const response = await authenticatedFetch('/api/v1/sync', { method: 'POST', body: JSON.stringify({ deviceId: device.id, schemaVersion: FIELDLENS_SCHEMA_VERSION, events: events.map(({ attempts: _attempts, nextAttemptAt: _next, syncedAt: _synced, lastError: _error, ...event }) => event), }), }) const body = await response.json() as SyncResponse const acknowledgements = new Map(body.acknowledgements.map((ack) => [ack.eventId, ack])) for (const event of events) { const acknowledgement = acknowledgements.get(event.id) if (acknowledgement?.status === 'accepted' || acknowledgement?.status === 'duplicate') { await acknowledgeOutboxEvent(event.id) } else { await failOutboxEvent(event.id, acknowledgement?.error ?? 'Event was not acknowledged') } } } catch (error) { const message = error instanceof Error ? error.message : 'Sync request failed' await Promise.all(events.map((event) => failOutboxEvent(event.id, message))) } } export function installSyncTriggers() { const attempt = () => { void syncNow() } const onVisible = () => { if (document.visibilityState === 'visible') attempt() } window.addEventListener('online', attempt) window.addEventListener(FIELDLENS_SYNC_REQUEST_EVENT, attempt) document.addEventListener('visibilitychange', onVisible) const interval = window.setInterval(attempt, 30_000) attempt() return () => { window.removeEventListener('online', attempt) window.removeEventListener(FIELDLENS_SYNC_REQUEST_EVENT, attempt) document.removeEventListener('visibilitychange', onVisible) window.clearInterval(interval) } } export { authenticatedFetch }