Add node location access RPCs and operator visibility

This commit is contained in:
William Valentin
2026-02-16 12:30:55 -08:00
parent 1d16cd54e6
commit fe8674e108
19 changed files with 693 additions and 15 deletions
+93 -1
View File
@@ -1,5 +1,13 @@
import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeError, makeResponse, ErrorCode, GATEWAY_PROTOCOL_VERSION, parseNodeRegisterParams } from '../protocol.js';
import {
makeError,
makeResponse,
ErrorCode,
GATEWAY_PROTOCOL_VERSION,
parseNodeRegisterParams,
parseNodeLocationSetParams,
parseNodeLocationGetParams,
} from '../protocol.js';
export interface NodeRegistration {
nodeId: string;
@@ -12,14 +20,29 @@ export interface NodeRegistration {
export interface NodeConnectionState {
identity?: string;
node?: NodeRegistration;
location?: NodeLocation;
}
export interface NodeLocation {
latitude: number;
longitude: number;
accuracyMeters?: number;
altitudeMeters?: number;
headingDegrees?: number;
speedMps?: number;
source: 'gps' | 'network' | 'manual' | 'unknown';
capturedAt: number;
receivedAt: number;
}
export interface NodeHandlerDeps {
enabled: boolean;
locationEnabled: boolean;
allowedRoles: string[];
featureGates: Record<string, boolean>;
getConnectionState: (connectionId: string) => NodeConnectionState | undefined;
setNodeRegistration: (connectionId: string, registration: NodeRegistration) => void;
setNodeLocation: (connectionId: string, location: NodeLocation) => void;
}
export function createNodeHandlers(deps: NodeHandlerDeps) {
@@ -107,6 +130,74 @@ export function createNodeHandlers(deps: NodeHandlerDeps) {
});
},
'node.location.set': async (request: GatewayRequest): Promise<OutboundMessage> => {
if (!deps.enabled) {
return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
}
if (!deps.locationEnabled) {
return makeError(request.id, ErrorCode.AuthFailed, 'Node location access is disabled');
}
const parsed = parseNodeLocationSetParams(request.params);
if (!parsed) {
return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.location.set params');
}
const state = deps.getConnectionState(parsed.connectionId);
if (!state?.node) {
return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
}
const location: NodeLocation = {
latitude: parsed.latitude,
longitude: parsed.longitude,
accuracyMeters: parsed.accuracyMeters,
altitudeMeters: parsed.altitudeMeters,
headingDegrees: parsed.headingDegrees,
speedMps: parsed.speedMps,
source: parsed.source ?? 'unknown',
capturedAt: parsed.capturedAt ?? Date.now(),
receivedAt: Date.now(),
};
deps.setNodeLocation(parsed.connectionId, location);
return makeResponse(request.id, {
updated: true,
node: {
id: state.node.nodeId,
role: state.node.role,
},
location,
});
},
'node.location.get': async (request: GatewayRequest): Promise<OutboundMessage> => {
if (!deps.enabled) {
return makeError(request.id, ErrorCode.AuthFailed, 'Node RPC is disabled');
}
if (!deps.locationEnabled) {
return makeError(request.id, ErrorCode.AuthFailed, 'Node location access is disabled');
}
const parsed = parseNodeLocationGetParams(request.params);
if (!parsed) {
return makeError(request.id, ErrorCode.InvalidRequest, 'Invalid node.location.get params');
}
const state = deps.getConnectionState(parsed.connectionId);
if (!state?.node) {
return makeError(request.id, ErrorCode.AuthFailed, 'Node is not registered for this connection');
}
return makeResponse(request.id, {
node: {
id: state.node.nodeId,
role: state.node.role,
},
location: state.location ?? null,
});
},
'system.capabilities': async (request: GatewayRequest): Promise<OutboundMessage> => {
const params = request.params as { connectionId?: string } | undefined;
const connectionId = params?.connectionId;
@@ -117,6 +208,7 @@ export function createNodeHandlers(deps: NodeHandlerDeps) {
},
nodes: {
enabled: deps.enabled,
locationEnabled: deps.locationEnabled,
allowedRoles: deps.allowedRoles,
registered: Boolean(state?.node),
role: state?.node?.role,