Skip to content

Commit ebc151d

Browse files
Michał Fąferekmfaferek93
authored andcommitted
fix(faults): enable real-time toast notifications for SSE fault events
- Subscribe to fault SSE stream on connect, unsubscribe on disconnect - Listen for named SSE events (fault_confirmed, fault_cleared) - Handle confirmed and cleared events separately: - fault_confirmed: add/update fault, show warning toast - fault_cleared: remove fault from list, show success toast - Extract fault data from SSE payload structure { event_type, fault, timestamp }
1 parent f02b5e6 commit ebc151d

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

src/lib/sovd-api.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,26 +1738,44 @@ export class SovdApiClient {
17381738
* @param onError Callback for errors
17391739
* @returns Cleanup function to close the connection
17401740
*/
1741-
subscribeFaultStream(onFault: (fault: Fault) => void, onError?: (error: Error) => void): () => void {
1741+
subscribeFaultStream(
1742+
onFaultConfirmed: (fault: Fault) => void,
1743+
onFaultCleared: (fault: Fault) => void,
1744+
onError?: (error: Error) => void
1745+
): () => void {
17421746
const eventSource = new EventSource(this.getUrl('faults/stream'));
17431747

1744-
eventSource.onmessage = (event) => {
1748+
const parseFault = (event: MessageEvent): Fault | null => {
17451749
try {
1746-
// API may return raw fault format that needs transformation
1750+
// API returns: { event_type, fault, timestamp }
17471751
const rawData = JSON.parse(event.data);
1748-
// Check if this is the raw API format (has fault_code) or already transformed
1749-
if ('fault_code' in rawData) {
1750-
const fault = this.transformFault(rawData);
1751-
onFault(fault);
1752-
} else {
1753-
// Already in Fault format
1754-
onFault(rawData as Fault);
1752+
const faultData = rawData.fault || rawData;
1753+
if ('fault_code' in faultData) {
1754+
return this.transformFault(faultData);
17551755
}
1756+
return faultData as Fault;
17561757
} catch {
17571758
onError?.(new Error('Failed to parse fault event'));
1759+
return null;
17581760
}
17591761
};
17601762

1763+
eventSource.addEventListener('fault_confirmed', (event: MessageEvent) => {
1764+
const fault = parseFault(event);
1765+
if (fault) onFaultConfirmed(fault);
1766+
});
1767+
1768+
eventSource.addEventListener('fault_cleared', (event: MessageEvent) => {
1769+
const fault = parseFault(event);
1770+
if (fault) onFaultCleared(fault);
1771+
});
1772+
1773+
// Fallback for unnamed events - treat as confirmed
1774+
eventSource.onmessage = (event: MessageEvent) => {
1775+
const fault = parseFault(event);
1776+
if (fault) onFaultConfirmed(fault);
1777+
};
1778+
17611779
eventSource.onerror = () => {
17621780
onError?.(new Error('Fault stream connection error'));
17631781
};

src/lib/store.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,10 @@ export const useAppStore = create<AppState>()(
557557

558558
// Load root entities after successful connection
559559
await get().loadRootEntities();
560+
561+
// Subscribe to fault stream for real-time toast notifications
562+
get().subscribeFaultStream();
563+
560564
return true;
561565
} catch (error) {
562566
const message = error instanceof Error ? error.message : 'Connection failed';
@@ -573,6 +577,9 @@ export const useAppStore = create<AppState>()(
573577
// Stop execution polling
574578
get().stopExecutionPolling();
575579

580+
// Unsubscribe from fault stream
581+
get().unsubscribeFaultStream();
582+
576583
set({
577584
serverUrl: null,
578585
baseEndpoint: '',
@@ -1392,6 +1399,7 @@ export const useAppStore = create<AppState>()(
13921399
}
13931400

13941401
const cleanup = client.subscribeFaultStream(
1402+
// onFaultConfirmed
13951403
(fault) => {
13961404
const { faults } = get();
13971405
// Add or update fault in the list
@@ -1407,6 +1415,15 @@ export const useAppStore = create<AppState>()(
14071415
}
14081416
toast.warning(`Fault: ${fault.message}`, { autoClose: 5000 });
14091417
},
1418+
// onFaultCleared - no toast here, clearFault() already shows one for UI-triggered clears
1419+
(fault) => {
1420+
const { faults } = get();
1421+
const newFaults = faults.filter(
1422+
(f) => !(f.code === fault.code && f.entity_id === fault.entity_id)
1423+
);
1424+
set({ faults: newFaults });
1425+
},
1426+
// onError
14101427
(error) => {
14111428
toast.error(`Fault stream error: ${error.message}`);
14121429
}

0 commit comments

Comments
 (0)