Skip to content
Track That Ride
Menu

Quickstart

JavaScript quickstart

This quickstart follows one delivery live in a web page: the driver’s position, the status, the arrival time and each update the customer sees. The page calls Track That Ride’s public tracking endpoints directly, because they allow cross-origin reads. Use plain EventSource, or the @trackthatride/realtime package.

How it fits together

  1. Your server creates a delivery and stores its public_tracking_token (see the Node.js quickstart).
  2. Your page opens https://trackthatride.com/api/public/track/<token>/events with EventSource and updates as messages arrive.
  3. 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 404 JSON, 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/realtime through status changes, driver positions and ETA updates, and receive delivered.
  • A full live stream in production needs a real delivery in progress on your account.

Frequently asked questions

Can my page call Track That Ride directly?

Yes. GET /api/public/track/{token} and its /events stream send Access-Control-Allow-Origin: *, so a page on any domain can read them. No cookies are used, and the delivery’s token is the only credential.

Do I need an API key?

No. The public tracking endpoints are authorised by the delivery’s tracking token. Never put an API key in browser code; create deliveries on your server.

When would I proxy through my own server?

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. The optional relay below does that in about 30 lines.

What happens when the delivery ends?

The stream sends the final status. Seven days after the delivery ends the link expires and the endpoints answer 410, which closes the stream.

How often does the position update?

Whenever the driver’s phone sends a new fix, at most once every three seconds per driver, while the driver has the driver app open.

Get an API key

Sign up free with no card, create a key under API keys, and run this example against your own account.

Sign-up happens on trackthatride.com.