diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 4589e14e0..d3cc7d718 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -1704,6 +1704,10 @@ func (api *API) notificationsSSE(w http.ResponseWriter, r *http.Request) { return } + // Flush the response to ensure the headers are immediately and eventSource + // onopen is triggered client-side. + flusher.Flush() + for { select { case notification := <-notifications: diff --git a/web/ui/mantine-ui/package.json b/web/ui/mantine-ui/package.json index ec8ef8902..aae8ba99b 100644 --- a/web/ui/mantine-ui/package.json +++ b/web/ui/mantine-ui/package.json @@ -25,6 +25,7 @@ "@mantine/dates": "^7.11.2", "@mantine/hooks": "^7.11.2", "@mantine/notifications": "^7.11.2", + "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", "@prometheus-io/codemirror-promql": "0.300.0-beta.0", diff --git a/web/ui/mantine-ui/src/components/NotificationsProvider.tsx b/web/ui/mantine-ui/src/components/NotificationsProvider.tsx index 44510061e..a331e524b 100644 --- a/web/ui/mantine-ui/src/components/NotificationsProvider.tsx +++ b/web/ui/mantine-ui/src/components/NotificationsProvider.tsx @@ -3,6 +3,7 @@ import { useSettings } from '../state/settingsSlice'; import { NotificationsContext } from '../state/useNotifications'; import { Notification, NotificationsResult } from "../api/responseTypes/notifications"; import { useAPIQuery } from '../api/api'; +import { fetchEventSource } from '@microsoft/fetch-event-source'; export const NotificationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { pathPrefix } = useSettings(); @@ -24,31 +25,47 @@ export const NotificationsProvider: React.FC<{ children: React.ReactNode }> = ({ }, [data, isError]); useEffect(() => { - const eventSource = new EventSource(`${pathPrefix}/api/v1/notifications/live`); - - eventSource.onmessage = (event) => { - const notification: Notification = JSON.parse(event.data); - - setNotifications((prev: Notification[]) => { - const updatedNotifications = [...prev.filter((n: Notification) => n.text !== notification.text)]; - - if (notification.active) { - updatedNotifications.push(notification); + const controller = new AbortController(); + fetchEventSource(`${pathPrefix}/api/v1/notifications/live`, { + signal: controller.signal, + async onopen(response) { + if (response.ok) { + if (response.status === 200) { + setNotifications([]); + setIsConnectionError(false); + } else if (response.status === 204) { + controller.abort(); + setShouldFetchFromAPI(true); + } + } else { + setIsConnectionError(true); + throw new Error(`Unexpected response: ${response.status} ${response.statusText}`); } + }, + onmessage(event) { + const notification: Notification = JSON.parse(event.data); - return updatedNotifications; - }); - }; + setNotifications((prev: Notification[]) => { + const updatedNotifications = [...prev.filter((n: Notification) => n.text !== notification.text)]; - eventSource.onerror = () => { - eventSource.close(); - // We do not call setIsConnectionError(true), we only set it to true if - // the fallback API does not work either. - setShouldFetchFromAPI(true); - }; + if (notification.active) { + updatedNotifications.push(notification); + } + + return updatedNotifications; + }); + }, + onclose() { + throw new Error("Server closed the connection"); + }, + onerror() { + setIsConnectionError(true); + return 5000; + }, + }); return () => { - eventSource.close(); + controller.abort(); }; }, [pathPrefix]); diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 2dc1fcdfe..49a907480 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -39,6 +39,7 @@ "@mantine/dates": "^7.11.2", "@mantine/hooks": "^7.11.2", "@mantine/notifications": "^7.11.2", + "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", "@prometheus-io/codemirror-promql": "0.300.0-beta.0", @@ -2255,6 +2256,11 @@ "react": "^18.2.0" } }, + "node_modules/@microsoft/fetch-event-source": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", + "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==" + }, "node_modules/@nexucis/fuzzy": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.5.1.tgz",