// ── Traccar GPS API ────────────────────────────────────────────────────────── // Polls Traccar for real-time device positions. // Auth: session cookie via POST /api/session // ───────────────────────────────────────────────────────────────────────────── // Use proxy on same origin to avoid mixed content (HTTPS → HTTP) const TRACCAR_URL = window.location.hostname === 'localhost' ? 'http://tracker.targointernet.com:8082' : window.location.origin + '/traccar' const TRACCAR_USER = 'louis@targo.ca' const TRACCAR_PASS = 'targo2026' let _devices = [] // Use Basic auth — works through proxy without cookies function authOpts () { return { headers: { Authorization: 'Basic ' + btoa(TRACCAR_USER + ':' + TRACCAR_PASS), Accept: 'application/json', } } } // ── Devices ────────────────────────────────────────────────────────────────── export async function fetchDevices () { try { const res = await fetch(TRACCAR_URL + '/api/devices?all=true', authOpts()) if (res.ok) { _devices = await res.json() return _devices } } catch {} return _devices } // ── Positions ──────────────────────────────────────────────────────────────── // Traccar API only supports ONE deviceId per request — fetch in parallel export async function fetchPositions (deviceIds = null) { if (!deviceIds || !deviceIds.length) return [] const results = await Promise.allSettled( deviceIds.map(id => fetch(TRACCAR_URL + '/api/positions?deviceId=' + id, authOpts()) .then(r => r.ok ? r.json() : []) ) ) return results.flatMap(r => r.status === 'fulfilled' ? r.value : []) } // ── Get position for a specific device ─────────────────────────────────────── export async function fetchDevicePosition (deviceId) { const positions = await fetchPositions([deviceId]) return positions[0] || null } // ── Get all positions mapped by deviceId ───────────────────────────────────── export async function fetchAllPositions () { // Get devices we care about (online + offline with recent position) if (!_devices.length) await fetchDevices() const deviceIds = _devices.filter(d => d.positionId).map(d => d.id) if (!deviceIds.length) return {} const positions = await fetchPositions(deviceIds) const map = {} positions.forEach(p => { map[p.deviceId] = p }) return map } // ── Utility: match device to tech by uniqueId or name ──────────────────────── export function matchDeviceToTech (devices, techs) { const matched = [] for (const tech of techs) { const traccarId = tech.traccarDeviceId if (!traccarId) continue const device = devices.find(d => d.id === parseInt(traccarId) || d.uniqueId === traccarId) if (device) matched.push({ tech, device }) } return matched } // ── Session (required for WebSocket auth) ──────────────────────────────────── export async function createTraccarSession () { try { const res = await fetch(TRACCAR_URL + '/api/session', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ email: TRACCAR_USER, password: TRACCAR_PASS }), }) return res.ok } catch { return false } } export { TRACCAR_URL, _devices as cachedDevices }