From 257b10fc8165bebec7cadc6c19177c8fd87ec240 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 6 May 2026 07:18:47 +0000 Subject: [PATCH] fix(web): guard malformed operational events --- web/src/App.tsx | 7 ++++--- web/src/hooks/useWebSocket.ts | 14 ++++++++++++++ web/src/tabs/AdminTab.tsx | 15 ++++++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 0af8656..3ba1ef6 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -63,9 +63,10 @@ function App() { const showMarketplaceTab = isAdmin || tabFlags.marketplace; // Critical system events (for the alert banner) - const recentCriticalEvents = (botState.operationalEvents ?? []).filter(e => - (e.severity === 'ERROR' || e.severity === 'WARN') && - Date.now() - e.timestamp < 600_000 + const recentCriticalEvents = (botState.operationalEvents ?? []).filter((e): e is NonNullable => + Boolean(e) + && (e.severity === 'ERROR' || e.severity === 'WARN') + && Date.now() - e.timestamp < 600_000 ); const hasCriticalEvents = recentCriticalEvents.length > 0; diff --git a/web/src/hooks/useWebSocket.ts b/web/src/hooks/useWebSocket.ts index 7aa97cb..8a3ac37 100644 --- a/web/src/hooks/useWebSocket.ts +++ b/web/src/hooks/useWebSocket.ts @@ -185,6 +185,16 @@ export interface BotState { }>; } +function isOperationalEventRecord(value: unknown): value is NonNullable[number] { + if (!value || typeof value !== 'object') return false; + const event = value as Record; + return typeof event.id === 'string' + && typeof event.type === 'string' + && typeof event.severity === 'string' + && typeof event.message === 'string' + && typeof event.timestamp === 'number'; +} + export const DEFAULT_BOT_STATE: BotState = { symbols: {}, positions: [], @@ -381,6 +391,10 @@ export const useWebSocket = (url: string) => { }); newSocket.on('operational_event', (event: any) => { + if (!isOperationalEventRecord(event)) { + console.warn('Ignoring malformed operational_event payload', event); + return; + } setBotState(prev => ({ ...prev, operationalEvents: [event, ...(prev.operationalEvents || [])].slice(0, EVENT_BUFFER_LIMIT) diff --git a/web/src/tabs/AdminTab.tsx b/web/src/tabs/AdminTab.tsx index ee84c40..61280d0 100644 --- a/web/src/tabs/AdminTab.tsx +++ b/web/src/tabs/AdminTab.tsx @@ -75,6 +75,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => { const observabilityHealth = (botState.health || {}) as any; const tradingControl = botState.health?.tradingControl; const isPaused = tradingControl?.mode === 'PAUSED'; + const operationalEvents = (botState.operationalEvents ?? []).filter((event): event is NonNullable => Boolean(event)); const formatDuration = (ms?: number) => { if (!ms || !Number.isFinite(ms)) return 'Idle'; @@ -376,18 +377,18 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => { - Buffer: {botState.operationalEvents?.length || 0} events + Buffer: {operationalEvents.length || 0} events {/* 24h Severity Summary Bar */} - {botState.operationalEvents && botState.operationalEvents.length > 0 && ( + {operationalEvents.length > 0 && (
Errors: - {botState.operationalEvents.filter(e => e.severity === 'ERROR' && (Date.now() - e.timestamp < 86400000)).length} + {operationalEvents.filter(e => e.severity === 'ERROR' && (Date.now() - e.timestamp < 86400000)).length}
@@ -395,7 +396,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
Warnings: - {botState.operationalEvents.filter(e => e.severity === 'WARN' && (Date.now() - e.timestamp < 86400000)).length} + {operationalEvents.filter(e => e.severity === 'WARN' && (Date.now() - e.timestamp < 86400000)).length}
@@ -403,7 +404,7 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
Info: - {botState.operationalEvents.filter(e => e.severity === 'INFO' && (Date.now() - e.timestamp < 86400000)).length} + {operationalEvents.filter(e => e.severity === 'INFO' && (Date.now() - e.timestamp < 86400000)).length}
@@ -413,14 +414,14 @@ export const AdminTab = ({ botState, socket }: AdminTabProps) => {
)}
- {(!botState.operationalEvents || botState.operationalEvents.length === 0) ? ( + {(operationalEvents.length === 0) ? (

No actionable issues detected

) : (
- {botState.operationalEvents.map((event) => ( + {operationalEvents.map((event) => (