Theme
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.