Skip to main content
Track and retrieve a user’s real-time location using the device’s native GPS. Supports live updates to your frontend as the user moves, chronological replay on app reopen, optional server delivery on each point, and distance-based triggers for high-accuracy use cases like running or navigation apps.
Background location tracking requires enabling the Background Location addon in your Despia dashboard. Go to Despia > App > Addons > Enable Background Location and rebuild your app. Foreground tracking works without this and requires no additional permissions.

Installation

npm install despia-native
import despia from 'despia-native';

How it works

Call location:// to start the native GPS session and stoplocation:// to end it and retrieve the full session array. While tracking is active, every GPS point is delivered to window.onLocationChange in real time and stored locally on the device. When tracking stops, the complete session is returned to your JavaScript for processing.
const isDespia = navigator.userAgent.toLowerCase().includes('despia')

if (isDespia && !despia.locationTracking) {
    despia("location://?buffer=10")
}
To stop tracking and retrieve the session:
const data = await despia("stoplocation://", ["locationSession"])
const locations = data.locationSession

Parameters

location:// accepts the following query parameters.
ParameterRequiredDescription
bufferYesMinimum seconds between time-based GPS updates. Use 5 for high frequency, 30 or higher for battery-conscious use cases.
serverNoRemote API endpoint that receives each location update as a POST request. Omit if you only need local storage or frontend updates.
movementNoDistance threshold in centimetres. When set, an additional update fires immediately whenever the device moves this distance, regardless of the buffer timer. Use 100 for 1 metre precision.

Live updates with window.onLocationChange

Define window.onLocationChange before calling location:// to receive every GPS point in real time as the user moves. Each call receives a single location object with an active flag indicating whether tracking is still running.
const isDespia = navigator.userAgent.toLowerCase().includes('despia')

window.onLocationChange = function(data) {
    if (!data.active) {
        // Tracking ended - finalize the route or show summary
        finalizeRoute()
        return
    }

    // Coordinates - use directly for map rendering
    drawPointOnMap(data.latitude, data.longitude)

    // Speed - convert m/s to min/km pace
    if (data.speed !== null && data.speed > 0) {
        const paceMinPerKm = (1000 / data.speed / 60).toFixed(2)
        updatePaceDisplay(paceMinPerKm + " min/km")
    }

    // Course - rotate a heading arrow
    if (data.course !== null) {
        headingArrow.style.transform = `rotate(${data.course}deg)`
    }

    // Accuracy - skip noisy points
    if (data.horizontalAccuracy > 10) return

    // Battery - show live drain
    updateBatteryIndicator(data.battery + "%")
}

if (isDespia && !despia.locationTracking) {
    despia("location://?buffer=60&movement=100")
}
When the app reopens during an active session, all buffered points are replayed into window.onLocationChange in chronological order so your frontend can reconstruct the full route without any additional code.

High-accuracy tracking

For use cases where each metre counts, combine movement with a long buffer to get distance-triggered updates as the primary signal and time-based updates as a heartbeat fallback.
window.onLocationChange = function(data) {
    if (!data.active) {
        finalizeRoute()
        return
    }
    drawPointOnMap(data.latitude, data.longitude)
    updatePace(data.speed)
}

// Fire on every 1 metre moved, with a 60s heartbeat
despia("location://?buffer=60&movement=100")
Filter for high-accuracy points using horizontalAccuracy before calculating distances. A value below 10 indicates a precise fix.
const accurate = locations.filter(loc => loc.horizontalAccuracy < 10)

Server delivery

When server is set, each GPS point is POSTed to your endpoint as it is recorded. Server delivery, window.onLocationChange, and local session storage all run simultaneously and independently. Loss of network does not affect local storage or frontend callbacks.
despia("location://?server=https://api.example.com/track?user=USER_ID&buffer=30&movement=100")
Each POST body matches the location object shape below.

Location object

Every location point, whether delivered via window.onLocationChange, the session array, or server POST, has the following shape.
{
    "latitude": 25.276987,
    "longitude": 55.296249,
    "timestamp": 1737219125.5,
    "gpsTimestamp": 1737219125.3,
    "speed": 2.5,
    "course": 87.3,
    "altitude": 12.4,
    "horizontalAccuracy": 5.2,
    "verticalAccuracy": 3.8,
    "battery": 85,
    "active": true
}
FieldDescriptionUsage
latitudeGPS latitude coordinateMap rendering, distance calculation
longitudeGPS longitude coordinateMap rendering, distance calculation
timestampUnix timestamp when the point was recorded on the device (seconds)Chronological ordering, elapsed time
gpsTimestampRaw GPS chip timestamp (seconds), may differ slightly from timestampHigh-precision timing
speedSpeed in metres per second. null if unavailablePace display: (1000 / data.speed / 60).toFixed(2) gives min/km
courseDirection of travel in degrees from 0 to 360. null if unavailableRotate a heading arrow: arrow.style.transform = 'rotate(' + data.course + 'deg)'
altitudeElevation above sea level in metresElevation gain charts
horizontalAccuracyAccuracy radius of the lat/lng in metres. Lower is betterFilter noisy points: discard if > 10
verticalAccuracyAccuracy of the altitude measurement in metresFilter noisy elevation data
batteryDevice battery percentage recorded at the exact moment of each GPS pointBattery drain per route: firstPoint.battery - lastPoint.battery
activetrue while tracking is running. false on the final event when tracking stopsDrive UI state, finalize route
Battery percentage is sampled at every GPS point throughout the session, giving you a precise drain curve for the entire route rather than just a start and end value.

Tracking state

Use despia.locationTracking to reflect the current tracking state in your UI. It is set to true when location:// is called and false when stoplocation:// is called.
if (despia.locationTracking) {
    // show stop button and active indicator
} else {
    // show start button
}

Calculate distance and analyse movement

Use the Haversine formula to calculate total distance from the session array after stopping.
const data = await despia("stoplocation://", ["locationSession"])
const locations = data.locationSession

// Filter for high-accuracy points only
const accurate = locations.filter(loc => loc.horizontalAccuracy < 10)

function calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 6371000
    const p1 = lat1 * Math.PI / 180
    const p2 = lat2 * Math.PI / 180
    const dp = (lat2 - lat1) * Math.PI / 180
    const dl = (lon2 - lon1) * Math.PI / 180
    const a = Math.sin(dp / 2) ** 2 + Math.cos(p1) * Math.cos(p2) * Math.sin(dl / 2) ** 2
    return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
}

let totalDistance = 0
for (let i = 1; i < accurate.length; i++) {
    totalDistance += calculateDistance(
        accurate[i - 1].latitude, accurate[i - 1].longitude,
        accurate[i].latitude, accurate[i].longitude
    )
}

console.log(`Total distance: ${(totalDistance / 1000).toFixed(2)} km`)

// Check movement
const isMoving = locations.some(loc => loc.speed !== null && loc.speed > 0.5)

// Battery drain
const drain = locations[0]?.battery - locations[locations.length - 1]?.battery
console.log(`Battery used: ${drain}%`)

Background tracking

Foreground tracking works with no additional permissions and continues as long as the app is in the foreground. To track while the app is backgrounded, enable the Background Location addon in your Despia dashboard. Despia manages the iOS blue badge indicator automatically and dismisses it as soon as stoplocation:// is called. On Android, Despia handles background location delivery across all major manufacturers including Samsung, Huawei, Xiaomi, and OnePlus, which apply aggressive background restrictions by default. No additional configuration is required. If you encounter missing server events on a specific Android device, contact location@despia.com.

Resources

NPM Package

despia-native