Skip to content

React / React Native client

Reference implementation for an in-cab or browser-side client that streams telemetry into N2E's IoT contract.

This page is intentionally framework-agnostic enough to drop into either a Next.js dashboard or a React Native driver app. The repo already ships a sample driver experience under monitoring_dashboard/src/app/driver-dashboard/.

1. Sign in and capture the token

Whether you are in React (browser) or React Native (mobile), the auth flow is the same.

ts
// auth.ts
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000'

export async function signIn(email: string, password: string) {
  const res = await fetch(`${API_BASE}/auth/signin`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password }),
  })
  if (!res.ok) throw new Error('signin failed')
  const data = await res.json()
  return data.token as string
}

In React Native, store the token in expo-secure-store or react-native-keychain. In a browser the platform already sets a session cookie — you can also reuse document.cookie if your client lives on the same origin.

2. Open the telemetry stream (browser)

The N2E IoT endpoint is a WebSocket implemented via Encore's streamIn. Connect with the bearer token in the Authorization query (browsers cannot set custom headers on WebSocket):

ts
// useTelemetryStream.ts
import { useEffect, useRef } from 'react'
import type { TelemetryEvent } from './types'

export function useTelemetryStream(
  token: string | null,
  source: AsyncIterable<TelemetryEvent>,
) {
  const wsRef = useRef<WebSocket | null>(null)

  useEffect(() => {
    if (!token) return

    const url = new URL('/iot/telemetry', process.env.NEXT_PUBLIC_API_URL!)
    url.protocol = url.protocol.replace('http', 'ws')
    url.searchParams.set('token', token)

    const ws = new WebSocket(url.toString())
    wsRef.current = ws

    ws.onopen = async () => {
      for await (const frame of source) {
        if (ws.readyState !== WebSocket.OPEN) break
        ws.send(JSON.stringify(frame))
      }
    }

    ws.onerror = (err) => console.error('telemetry stream error', err)
    ws.onclose = () => { wsRef.current = null }

    return () => ws.close()
  }, [token, source])
}

3. Open the telemetry stream (React Native)

React Native's WebSocket is a thin wrapper around the browser API and works identically. The only difference is auth header support — native sockets can set Authorization directly:

ts
const ws = new WebSocket(`${WS_BASE}/iot/telemetry`, undefined, {
  headers: { Authorization: `Bearer ${token}` },
})

4. Build the frame source

A typical RN device app reads OBD-II / J1939 over Bluetooth, normalises it and yields TelemetryEvent frames:

ts
// frames.ts
import type { TelemetryEvent } from './types'

export async function* sampleEverySecond(
  vehicleId: string,
  read: () => Promise<RawObd>, // your hardware adapter
): AsyncGenerator<TelemetryEvent> {
  while (true) {
    const raw = await read()
    yield {
      vehicle_id: vehicleId,
      timestamp: new Date().toISOString(),
      powertrain: {
        engine_rpm: raw.rpm,
        engine_torque_pct: raw.torque ?? 0,
        fuel_rate_lph: raw.fuelRate ?? 0,
        coolant_temp_c: raw.coolantC,
        oil_pressure_kpa: raw.oilKpa,
        boost_pressure_kpa: raw.boostKpa,
        nox_ppm: raw.noxPpm ?? 0,
      },
      speed_kmh: raw.speedKmh,
      odometer_km: raw.odometerKm,
      engine_hours: raw.engineHours,
      coordinates: { lat: raw.lat, long: raw.lng },
    }
    await new Promise((r) => setTimeout(r, 1000))
  }
}

Pair sampleEverySecond with useTelemetryStream and you have a working device client:

tsx
function DriverApp() {
  const token = useAuthToken()
  const source = useMemo(
    () => sampleEverySecond(VEHICLE_ID, readObd),
    [],
  )
  useTelemetryStream(token, source)
  return <DriverHud />
}

5. Buffering across reconnects

The IoT contract drops stale frames (older than the last seen timestamp for a vehicle_id). Buffer locally on the device:

ts
const buffer: TelemetryEvent[] = []

async function flushTo(ws: WebSocket) {
  while (buffer.length) {
    const frame = buffer[0]!
    if (ws.readyState !== WebSocket.OPEN) return
    ws.send(JSON.stringify(frame))
    buffer.shift()
  }
}

On reconnect, call flushTo(ws) once before resuming live samples. On RN, persist buffer to AsyncStorage every few seconds so a process restart doesn't lose data.

6. Trip-end event

When the driver shuts down the engine and the dashcam finalises the video file:

ts
await fetch(`${API_BASE}/iot/trip-end`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({
    vehicle_id: VEHICLE_ID,
    trip_id: crypto.randomUUID(),
    end_timestamp: new Date().toISOString(),
    video_metadata: { file_size_gb: 4.7, duration_minutes: 312 },
  }),
})

7. Testing without hardware

Use the /iot/telemetry/emit helper to generate synthetic frames from the operator UI. From the AI Risk Management page, the NORMAL / WARNING / CRITICAL buttons call this endpoint and let you see the end-to-end pipeline without a physical truck.

Putting it together

[OBD/J1939 hardware]
        │   raw frames

[React Native adapter] ──── normalise to TelemetryEvent
        │                       ▲
        ▼                       │
[useTelemetryStream] ────── WebSocket ──> /iot/telemetry
        │                                     │
        ▼                                     ▼
[Buffer + retry]                          telemetryTopic


                                  [N2E backend: store + AI risk]

See OEM telematics mapping for adapting Mercedes-Benz, IVECO and Caterpillar feeds into this shape.

N2E Fleet Management User Guide.