How it fits together
- Your server creates a delivery and stores its
public_tracking_token(see the Node.js quickstart). - Your page opens
https://trackthatride.com/api/public/track/<token>/eventswithEventSourceand updates as messages arrive. - No server-side relay is needed: the public tracking endpoints allow cross-origin reads.
1. The page (plain JavaScript)
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Follow a delivery</title>
</head>
<body>
<h1>Your delivery</h1>
<p>Status: <strong id="status">connecting…</strong></p>
<p>Arriving at about: <strong id="eta">—</strong></p>
<p>Driver position: <span id="position">waiting for the driver…</span></p>
<ul id="updates"></ul>
<script>
// Follow one delivery live with plain JavaScript, straight from the browser. The token is the
// delivery's public_tracking_token (no API key is used). Track That Ride's public tracking
// endpoints allow cross-origin reads, so a page on any domain can call them directly.
// Optional: to proxy through your own server instead, run examples/node/tracking-relay.mjs
// and set BASE = '' so the page calls /track/<token> on its own origin.
const BASE = 'https://trackthatride.com/api/public';
const token = new URLSearchParams(location.search).get('token');
const $ = (id) => document.getElementById(id);
const time = (iso) => new Date(iso).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
if (token) {
follow(token);
} else {
$('status').textContent = 'no token: add ?token=<public_tracking_token> to the address';
}
function follow(token) {
const stream = new EventSource(`${BASE}/track/${encodeURIComponent(token)}/events`);
stream.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch (msg.type) {
case 'connected': // the current snapshot
$('status').textContent = msg.delivery.status;
if (msg.delivery.eta) $('eta').textContent = time(msg.delivery.eta);
if (msg.driver?.latitude != null) $('position').textContent = `${msg.driver.latitude}, ${msg.driver.longitude}`;
break;
case 'driver_location':
$('position').textContent = `${msg.location.latitude.toFixed(5)}, ${msg.location.longitude.toFixed(5)}`;
break;
case 'ride_status_change':
$('status').textContent = msg.status;
break;
case 'eta_update':
$('eta').textContent = time(msg.eta);
break;
case 'ride_update': {
const li = document.createElement('li');
li.textContent = msg.update.title;
$('updates').prepend(li);
break;
}
case 'resync': // an update was too large to stream: refetch the snapshot
fetch(`${BASE}/track/${encodeURIComponent(token)}`)
.then((r) => r.json())
.then((s) => ($('status').textContent = s.delivery.status));
break;
}
};
// EventSource reconnects by itself; a rejected link (404, 410 expired) closes the stream.
stream.onerror = () => {
if (stream.readyState === EventSource.CLOSED) $('status').textContent = 'link not valid or expired';
};
}
</script>
</body>
</html>
Save it as track.html, serve it from any web server and open it with ?token=<public_tracking_token>. The
page shows the current status straight away (from the connected message), then live positions as the driver moves.
Messages you will receive
Each message is JSON with a type: connected, driver_location, ride_status_change,
eta_update, ride_update, plus heartbeat, reconnect and resync. The
payloads are listed in public tracking and real-time streams.
Using the SDK
The same thing with @trackthatride/realtime 1.1.2. It handles the stream format, reconnects with back-off, resumes where it left off and tells you once when the delivery is delivered:
// npm install @trackthatride/realtime
import { TrackingSSEClient } from '@trackthatride/realtime';
// In the browser the built-in EventSource is used and baseUrl defaults to https://trackthatride.com.
// In Node, pass one: new TrackingSSEClient({ eventSource: EventSource }) with EventSource from 'eventsource'.
const stream = new TrackingSSEClient();
stream.on('ride-update', (e) => showStatus(e.status));
stream.on('location-update', (e) => moveDriver(e.latitude, e.longitude));
stream.on('eta-update', (e) => showArrival(e.estimatedArrival));
stream.on('delivered', () => stream.disconnect());
stream.on('error', (e) => console.error(e.code, e.message));
stream.connectToPublicTracking(publicTrackingToken); On your server, @trackthatride/core 2.0.1 creates the delivery and gives you the token:
// npm install @trackthatride/core
import { createClient } from '@trackthatride/core';
// baseUrl is optional and defaults to https://trackthatride.com
const client = createClient({ apiKey: process.env.TTR_API_KEY });
const { data: ride } = await client.createRide({
customer_name: 'Ada Lovelace',
phone_number: '+447700900123',
pickup_address: '1 Broad St, London',
pickup_contact_name: 'Front desk',
pickup_phone: '+447700900456',
estimated_pickup_time: new Date(Date.now() + 15 * 60_000).toISOString(),
address: '5 Marina Rd, London',
});
// Give this to the page that follows the delivery (no API key needed there)
console.log(ride.public_tracking_token); Optional: proxy through your own server
You do not need this. If you want the requests on your own domain, this relay passes the snapshot and the stream through unchanged and
forwards Last-Event-ID so reconnects resume. Run it, and set BASE = '' in the page above.
// Optional: proxy one delivery's live tracking through your own server. Node.js 18+, no dependencies.
//
// You do not need this: Track That Ride's public tracking endpoints allow cross-origin reads, so a page
// can call them directly (examples/browser/track.html does). Use it only if you want the requests on
// your own domain, for example to keep the tracking token out of third-party request logs or to add
// your own caching or access rules. It passes through, unchanged:
// GET /track/:token JSON snapshot
// GET /track/:token/events Server-Sent Events
// Usage: set BASE = '' in examples/browser/track.html, run node tracking-relay.mjs, then open
// http://localhost:3001/?token=<public_tracking_token>
import fs from 'node:fs';
import http from 'node:http';
import { Readable } from 'node:stream';
const UPSTREAM = 'https://trackthatride.com/api/public/track';
const PAGE = new URL('../browser/track.html', import.meta.url);
http
.createServer(async (req, res) => {
if (req.method === 'GET' && (req.url === '/' || req.url.startsWith('/?'))) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }).end(fs.readFileSync(PAGE));
return;
}
const m = req.method === 'GET' && req.url.match(/^\/track\/([A-Za-z0-9._-]{21,})(\/events)?$/);
if (!m) {
res.writeHead(404).end();
return;
}
const [, token, events] = m;
const headers = {};
if (events && req.headers['last-event-id']) headers['Last-Event-ID'] = req.headers['last-event-id']; // resume after a reconnect
const controller = new AbortController();
req.on('close', () => controller.abort()); // browser left: close the upstream stream too
try {
const upstream = await fetch(`${UPSTREAM}/${token}${events ?? ''}`, { headers, signal: controller.signal });
res.writeHead(upstream.status, {
'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
'Cache-Control': 'no-cache',
...(events ? { Connection: 'keep-alive', 'X-Accel-Buffering': 'no' } : {}),
});
if (upstream.body) Readable.fromWeb(upstream.body).pipe(res);
else res.end();
} catch (err) {
if (!res.headersSent) res.writeHead(502).end();
else res.end();
}
})
.listen(3001, () => console.log('Relay on http://localhost:3001/track/<token>/events'));
What we tested
-
The page’s cross-origin path against the live API: a page on another origin calls the snapshot and stream directly, and an unknown token
comes back as the API’s readable
404JSON, so the page shows that the link is not valid. -
The SDK path end to end against a local copy of the API, using the published packages’ tarballs: create a delivery with
@trackthatride/core, follow it with@trackthatride/realtimethrough status changes, driver positions and ETA updates, and receivedelivered. - A full live stream in production needs a real delivery in progress on your account.