import { OwlClient, ValidationError, ApiError } from './owlClient'; import type { ApiStationsNearestStations } from '@owlboard/owlboard-ts'; import { prefs } from './preferences.svelte'; class NearestStationsState { list = $state([]); currentHash = $state(''); loading = $state(false); error = $state(null); private watcherId: number | null = null; private initGeoConfig: PositionOptions = { enableHighAccuracy: false, timeout: 500, maximumAge: Infinity }; private geoConfig: PositionOptions = { enableHighAccuracy: false, timeout: 20000, maximumAge: 30000 }; constructor() { if (typeof window === 'undefined' || !('geolocation' in navigator)) return; $effect.root(() => { $effect(() => { if (prefs.current.NearToMe) { this.startTracking(); } else { this.stopTracking(); } }); }); } private startTracking() { if (this.watcherId !== null) return; // Already tracking this.loading = true; this.jumpstart(); this.initWatcher(); } private stopTracking() { if (this.watcherId !== null) { navigator.geolocation.clearWatch(this.watcherId); this.watcherId = null; } this.list = []; this.currentHash = ''; this.loading = false; this.error = null; } private jumpstart() { navigator.geolocation.getCurrentPosition( (pos) => this.handleUpdate(pos.coords.latitude, pos.coords.longitude), (err) => this.handleError(err), this.initGeoConfig ); } private initWatcher() { this.watcherId = navigator.geolocation.watchPosition( (pos) => this.handleUpdate(pos.coords.latitude, pos.coords.longitude), (err) => this.handleError(err), this.geoConfig ); } private async handleUpdate(lat: number, lon: number) { const newHash = OwlClient.stationData.generateGeohash(lat, lon); if (newHash !== this.currentHash) { this.loading = true; try { const result = await OwlClient.stationData.getNearestStations(newHash); this.list = result.data; this.error = null; this.currentHash = newHash; } catch (e) { this.handleApiError(e); } finally { this.loading = false; } } } private handleError(err: GeolocationPositionError) { if (err.code === 1) { this.error = 'Location access denied by device'; } else { this.error = 'Waiting for GPS signal...'; } } private handleApiError(e: unknown) { if (e instanceof ValidationError) { this.error = `Request Error: ${e.reason} (Field: ${e.field})`; } else if (e instanceof ApiError) { switch (e.status) { case 404: this.error = 'No stations found nearby'; break; case 429: this.error = 'Too many requests, will retry'; break; case 500: this.error = 'Server Error, will retry'; break; default: this.error = `Service error: ${e.code}`; } } else { this.error = 'Connection lost, waiting for signal'; } console.error('OwlBoard API Error:', e); } } export const nearestStationsState = new NearestStationsState();