gigafibre-fsm/apps/dispatch/src/api/traccar.js
louispaulb 7da22ff132 merge: import dispatch-app into apps/dispatch/ (17 commits preserved)
Integrates the Dispatch PWA (Vue/Quasar) into the gigafibre-fsm monorepo.
Full git history accessible via `git log -- apps/dispatch/`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 08:08:51 -04:00

95 lines
4.0 KiB
JavaScript

// ── 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 }