Files
ulweb/web/lib/use-devices-stream.ts
ulhub 842cb23e1f Add device-certificate mTLS auth, live position tracking, and API docs
Introduces a CA/PKI module so field devices can authenticate to Mosquitto
over TLS (8883) with per-device client certificates (CN = serial number)
instead of a shared password, with matching Devices/MQTT-Certs UI. Adds
live transmitter position tracking alongside logged points, an MQTTS
transport option in the simulator for exercising the real cert-auth path,
and Swagger API docs at /api/docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 01:40:12 +00:00

72 lines
1.9 KiB
TypeScript

import { useEffect, useRef } from 'react';
import type { MapPoint } from '../components/map/types';
export interface DeviceStreamEvent {
deviceId: string;
lastSeenAt?: string;
isActive?: boolean;
disabledReason?: string | null;
position?: MapPoint;
job?: { id: string; ticketNumber: string; title: string };
}
// Subscribes to live device updates (position pings, log points, and
// admin enable/disable) for an org over the backend WebSocket, so the
// devices page reflects device activity without a manual refresh.
export function useDevicesStream(orgId: string | null, onEvent: (event: DeviceStreamEvent) => void) {
const eventRef = useRef(onEvent);
eventRef.current = onEvent;
useEffect(() => {
if (!orgId) {
return;
}
let socket: WebSocket | null = null;
let closed = false;
let attempt = 0;
let timer: ReturnType<typeof setTimeout> | null = null;
const channel = `org:${orgId}:devices`;
const connect = () => {
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
socket = new WebSocket(`${proto}://${window.location.host}/api/ws`);
socket.onopen = () => {
attempt = 0;
socket?.send(JSON.stringify({ type: 'subscribe', channel }));
};
socket.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'device') {
eventRef.current(msg as DeviceStreamEvent);
}
} catch {
// ignore malformed frames
}
};
socket.onclose = () => {
if (closed) {
return;
}
attempt += 1;
const delay = Math.min(1000 * 2 ** attempt, 15000);
timer = setTimeout(connect, delay);
};
};
connect();
return () => {
closed = true;
if (timer) {
clearTimeout(timer);
}
socket?.close();
};
}, [orgId]);
}