/** * Where a rider is right now, and how their phone is doing. * * ── This is a different backend, and that is the whole point ──────────────── * * `jupiter.nearle.app` — the platform's older API, which the xpress console * runs against. Fiesta has no equivalent: `getriderperiodiclogs` does not exist * there under any prefix, and `partners/getriderlogs`, the closest-looking * Fiesta endpoint, is a heartbeat log that repeats one fixed coordinate per * rider all day (see `riderShifts`). So this is the ONLY live rider position * on the platform, and it was missed once already by checking Fiesta alone. * * Verified 2026-09-10: rider 852 polled thirty seconds apart moved about 140 m, * with battery, connection and accuracy all changing. Genuinely live. * * Same database as Fiesta underneath — `getallriders` on jupiter and * `getriders` on Fiesta return identical rosters and identical on-duty state — * so a userid from one is a userid in the other. * * ── One rider per call ────────────────────────────────────────────────────── * * There is no fleet form: `?userid=N` answers for that rider, and omitting it * answers for whichever rider reported most recently, which is not useful. A * fleet view therefore fans out, which is fine at the sizes involved — a * merchant's round is a handful of riders. * * ── Freshness is the caller's problem, and must be shown ──────────────────── * * The endpoint always answers, and it answers with the LAST known fix however * old. Riders 883, 897 and 1111 come back with positions from 5, 3 and 12 days * ago and nothing in the payload flags them as stale. A map that draws those * next to a live one is lying, so `logdate` is parsed here and every consumer * is handed an age rather than a bare position. */ const JUPITER_BASE = ( import.meta.env?.['VITE_JUPITER_BASE'] ?? 'https://jupiter.nearle.app' ) .trim() .replace(/\/+$/, ''); /** One rider's live snapshot, exactly as jupiter sends it. */ export interface RiderSnapshot { userid: number; username?: string; latitude?: string; longitude?: string; /** `2026-09-10 11:25:11` — server local time, no zone. */ logdate?: string; /** `"95%"`, with the sign. */ battery?: string; /** `mobile`, `wifi`, `none`. */ connection?: string; /** Metres of GPS uncertainty, as a string. 100.0 is a poor fix. */ accuracy?: string; /** Km/h and degrees, both as strings. */ speed?: string; heading?: string; /** `idle`, `active`, and whatever else the app decides to send. */ status?: string; /** The order they are on, when they are on one. */ orderid?: string; is_charging?: boolean; is_background?: boolean; /** `enabled` / `disabled` — a disabled one explains a stale position. */ location_service?: string; } export class TelemetryError extends Error { constructor(message: string) { super(message); this.name = 'TelemetryError'; } } export const telemetryApi = { /** * One rider's latest reported position and phone state. * * Never throws for "this rider has never reported" — that answers 200 with an * empty-ish body, and the caller wants to draw the rider as unreachable * rather than show an error. It throws only when jupiter itself cannot be * reached, which is a different thing and worth saying out loud: jupiter can * be down while Fiesta is fine, and vice versa. */ rider: async (userid: number, signal?: AbortSignal): Promise => { let response: Response; try { response = await fetch( `${JUPITER_BASE}/live/api/v1/utils/getriderperiodiclogs?userid=${userid}`, { headers: { Accept: 'application/json' }, signal }, ); } catch (error) { if ((error as Error)?.name === 'AbortError') throw error; throw new TelemetryError( 'Could not reach the live rider service. It is a separate backend from the rest of the console, so everything else keeps working.', ); } if (!response.ok) { throw new TelemetryError(`The live rider service answered ${response.status}.`); } const payload = (await response.json().catch(() => null)) as | { data?: RiderSnapshot } | null; const data = payload?.data; // A rider who has never opened the app comes back without coordinates. // Null rather than an empty object, so "no fix" is one check everywhere. return data && (data.latitude || data.longitude) ? data : null; }, };