Skip to main content

Offline tracking and sync

Background Location Pro is offline first. Every accepted point is written to storage on the device before it is emitted to JS or uploaded. A phone can go through a tunnel, a flight or a dead zone for hours and nothing is lost. The queue drains automatically when connectivity returns.

How delivery works​

  • Sync tick: every 30 seconds, or every 5 seconds with setDemoMode(true). You can force one with syncNow().
  • Offline: no requests are sent while online is false. Points accumulate in pending.
  • Batches: up to batchSize points per request, oldest first. Configure with setSyncPolicy().
  • Idempotent: a batch keeps the same batchId across retries, so your server can safely de-duplicate.
  • Durable: pending points and retry state survive process death and reboots.

Backend contract​

The tracker sends:

POST {serverUrl}/api/location/track
Content-Type: application/json; charset=utf-8
Idempotency-Key: <batchId>
X-Device-Id: <deviceId>
{
"batchId": "6f1c2a7e-...",
"deviceId": "a3b9...",
"points": [
{
"id": "c0ffee-...",
"lat": 12.97194,
"lng": 77.59369,
"accuracy": 8.5,
"speed": 1.4,
"bearing": 92.0,
"altitude": 914.2,
"timestamp": 1762325460000
}
]
}
FieldMeaning
batchIdStable across retries of the same batch. Use it for de-duplication.
deviceIdStable per install. Also available as getStatus().deviceId.
points[].idUnique per point. Matches LocationEvent.id emitted to JS.
lat, lngCoordinates in degrees.
accuracyHorizontal accuracy radius in meters.
speedMeters per second.
bearing, altitudeDegrees and meters.
timestampCapture time in epoch milliseconds.

How your server should respond​

ResponseTracker behavior
2xxThe batch is acknowledged. Points leave the queue and count toward synced.
5xx, 429, network error, timeoutRetried with jittered exponential backoff, capped at maxBackoffMs. A Retry-After header is honored.
Other 4xxCounted as a rejection. After maxAttempts rejections the batch moves to the dead-letter state and counts toward rejected.
Make your endpoint idempotent

Store batchId (or each point id) with a unique constraint and return 2xx for duplicates. A batch can be delivered more than once if the response is lost on a flaky network. Idempotency makes that harmless.

A minimal Express handler:

app.post('/api/location/track', async (req, res) => {
const {batchId, deviceId, points} = req.body;
if (await db.batches.exists(batchId)) {
return res.status(200).json({ok: true, duplicate: true});
}
await db.transaction(async tx => {
await tx.batches.insert({batchId, deviceId});
await tx.points.insertMany(points.map(p => ({...p, deviceId})));
});
res.status(200).json({ok: true});
});

Authentication​

startTracking() takes only a base URL, and the Evaluation edition does not attach custom headers. For evaluation, use an unguessable URL path or an IP allowlist. The licensed editions include the full source, so you can add auth headers, tokens or request signing.

Observing the queue from JS​

LocationTracker.onSyncStatus(e => {
console.log({
online: e.online,
pending: e.pending, // on device, not yet acknowledged
synced: e.synced, // acknowledged by your server
retryAttempt: e.retryAttempt, // 0 when healthy
nextRetryAt: e.nextRetryAt, // epoch ms, 0 when nothing scheduled
message: e.message,
});
});

getStatus() adds rejected (dead-lettered points) and lastSyncError (for example http_503 or TimedOut).

Tuning the sync policy​

// batchSize, maxBackoffMs, maxAttempts
LocationTracker.setSyncPolicy(100, 5 * 60_000, 5);
GoalSuggested policy
Live map with a fast backendsetDemoMode(true), batchSize 25 to 50
Battery-friendly fleet trackingDemo mode off, batchSize 100 to 200, maxBackoffMs 5 to 15 minutes
Never drop data, even on persistent 4xxmaxAttempts = 0 (keep retrying forever)

The policy is persisted and applies to the next batch.