---
title: Track SDK
description: Vehicle and asset GPS tracking with geofencing and location history. Real-time tracking for fleets, delivery drivers, drones, and any moving asset.
---

# @nolag/track

Vehicle and asset GPS tracking with geofencing and location history.

## Overview

Track vehicles, delivery drivers, drones, or any moving asset in real time. `@nolag/track` broadcasts location updates to all zone subscribers instantly. Geofence detection runs client-side using the haversine formula for circular boundaries and a ray-casting algorithm for polygon boundaries, so triggers fire without a server round-trip. Zones group assets by geographic area or fleet. Join multiple zones to observe overlapping regions. Your app owns one core NoLag client and injects it into `NoLagTrack`; the wrapper attaches its behaviour to that connection.

### Key Features

- Real-time GPS location broadcast to all zone subscribers
- In-memory location buffer per asset for client-side history
- Client-side geofence detection for circle (haversine) and polygon (ray-casting)
- Asset online/offline presence via lobby
- Optional metadata attached to each location point
- Automatic reconnect with zone and presence restoration

## How It Works

`NoLagTrack` attaches to an injected `@nolag/js-sdk` client and manages a lobby that tracks which assets are online. Calling `joinZone(name)` returns a `TrackingZone` that subscribes to two topics: `locations` for ephemeral GPS points and `_geofence` for ephemeral geofence configuration events. Both topics are ephemeral (no server-side retention) to handle high-frequency GPS data without storage overhead. Location history is maintained in-memory on the client. Geofence evaluation happens locally on receipt of each `locationUpdate` event. No additional server calls are made. The app owns the socket lifecycle; the wrapper never opens or closes it.

| Topic | Purpose | Replay |
| --- | --- | --- |
| `locations` | GPS location points with optional metadata | Ephemeral |
| `_geofence` | Geofence add/remove events (internal, client-side evaluation) | Ephemeral |

## Installation

```bash [Terminal]
npm install @nolag/track @nolag/js-sdk
```

One core NoLag client can back several wrapper SDKs at once, for example asset tracking, a dashboard, and notify on a single socket, as long as each wrapper uses a distinct `appName`. Each wrapper attaches its handlers on construction and releases them with `detach()`, and never touches the socket itself. Your app owns `connect()` and `disconnect()`.

## Quick Start

```typescript [TypeScript]
import { NoLag } from '@nolag/js-sdk'
import { NoLagTrack } from '@nolag/track'

// The app owns one core client. In a browser, pass a token provider so the
// SDK can mint fresh short-lived client tokens from your backend.
const client = NoLag(async () => (await (await fetch('/api/nolag-token')).json()).token)

// Inject the client into the track wrapper
const tracker = new NoLagTrack({ client, assetId: 'drv_001', assetName: 'Van 12' })

await client.connect()   // the app owns the connection
await tracker.ready()    // wrapper setup complete

// Join a tracking zone (groups assets by geographic area or fleet)
const zone = await tracker.joinZone('fleet-london')

// Device side: report location
zone.sendLocation({ lat: 51.5074, lng: -0.1278 }, { driverId: 'drv_001', speed: 42 })

// Add a circular geofence (haversine distance check)
zone.addGeofence({
  id: 'depot-central',
  type: 'circle',
  center: { lat: 51.5074, lng: -0.1278 },
  radiusMeters: 500,
  label: 'Central Depot',
})

// Add a polygon geofence (ray-casting algorithm)
zone.addGeofence({
  id: 'zone-east',
  type: 'polygon',
  coordinates: [
    { lat: 51.52, lng: -0.05 },
    { lat: 51.50, lng: -0.03 },
    { lat: 51.48, lng: -0.06 },
    { lat: 51.50, lng: -0.09 },
  ],
  label: 'East Zone',
})

// Controller side: listen for updates
zone.on('locationUpdate', ({ assetId, point, metadata, timestamp }) => {
  console.log(`Asset ${assetId} at ${point.lat}, ${point.lng}`)
})

zone.on('geofenceTriggered', ({ assetId, geofenceId, event }) => {
  console.log(`Asset ${assetId} ${event} geofence ${geofenceId}`)
})

// Get in-memory location history for a specific asset
const history = await zone.getLocationHistory('drv_001')
console.log(`${history.length} location points in buffer`)

// Teardown: the wrapper releases its handlers; the app closes the socket.
tracker.detach()
client.disconnect()
```

## API Reference

### NoLagTrack

#### Constructor Options

| Option | Type | Description |
| --- | --- | --- |
| `client` | `NoLagSocket` | **Required.** The injected core NoLag client the app owns and connects. |
| `assetId` | `string` | Stable identifier for this asset (auto-generated if omitted). |
| `assetName` | `string` | Optional human-readable name for this asset. |
| `metadata` | `Record<string, unknown>` | Optional custom data attached to asset presence. |
| `appName` | `string` | NoLag app for topic prefixes (default `'track'`). |
| `zoneNames` | `string[]` | Tracking zones to auto-join once the wrapper is ready. |
| `zones` | `Geofence[]` | Client-side geofence zones to register on every joined zone. |
| `maxLocationHistory` | `number` | Max location history entries per asset (default `500`). |
| `debug` | `boolean` | Enable wrapper debug logging (default `false`). |

| Method | Returns | Description |
| --- | --- | --- |
| `ready()` | `Promise<void>` | Resolves once wrapper setup completed. |
| `detach()` | `void` | Release this wrapper's handlers and topics; terminal, never closes the socket. |
| `joinZone(name)` | `Promise<TrackingZone>` | Subscribe to a tracking zone. Returns the zone instance. |
| `leaveZone(name)` | `Promise<void>` | Unsubscribe from a zone and release its resources. |
| `getOnlineAssets()` | `Asset[]` | Return the list of assets currently present in the lobby. |

### NoLagTrack Events

| Event | Payload | Description |
| --- | --- | --- |
| `connected` | none | WebSocket connection established. |
| `disconnected` | `reason: string` | Connection closed. |
| `reconnected` | none | Connection restored after a drop; zone membership and presence are restored automatically. |
| `error` | `error: Error` | A transport or protocol error occurred. |
| `assetOnline` | `asset: Asset` | An asset joined the lobby. |
| `assetOffline` | `asset: Asset` | An asset left the lobby. |

### TrackingZone

| Method | Returns | Description |
| --- | --- | --- |
| `sendLocation(point, metadata?)` | `void` | Broadcast a GPS point `{ lat, lng }` with optional metadata to zone subscribers. |
| `getLocationHistory(assetId?)` | `Promise<LocationPoint[]>` | Fetch location points from the in-memory buffer. Omit `assetId` to get all assets. |
| `addGeofence(geofence)` | `void` | Register a circle or polygon geofence evaluated on every incoming location update. |
| `removeGeofence(id)` | `void` | Deregister a geofence by its ID. |
| `getGeofences()` | `Geofence[]` | Return all currently registered geofences for this zone. |

### TrackingZone Events

| Event | Payload | Description |
| --- | --- | --- |
| `locationUpdate` | `{ assetId, point, metadata?, timestamp }` | A location point was received from an asset in this zone. |
| `assetJoined` | `asset: Asset` | An asset joined this zone. |
| `assetLeft` | `asset: Asset` | An asset left this zone. |
| `geofenceTriggered` | `{ assetId, geofenceId, geofence, event: 'enter' \| 'exit', point }` | An asset crossed a geofence boundary. Evaluated client-side on each location update. |
