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>
95 lines
4.0 KiB
JavaScript
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 }
|