From 09232ed880dda862ccbe4749db8a108652fc6fc5 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 2 Apr 2026 18:19:00 +0200 Subject: [PATCH] feat(websocket): add frontend WebSocket support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add useWebSocket composable with: - Auto-connect on login, disconnect on logout - Exponential backoff with ±25% jitter for reconnects - Auth failure detection to prevent reconnect loops - Trailing slash stripping from API_URL - Overlapping reconnect prevention - visibilityState check for fallback polling Replace notification polling with real-time WebSocket push in the Notifications component. Initial state is still loaded via REST on mount, with fallback polling when WebSocket is disconnected. Incoming notifications are deduplicated against already-loaded REST data. Notifications are reloaded via REST on WS disconnect to catch missed events. --- frontend/src/components/home/ContentAuth.vue | 4 + .../notifications/Notifications.vue | 70 ++++-- frontend/src/composables/useWebSocket.ts | 208 ++++++++++++++++++ frontend/src/stores/auth.ts | 4 + 4 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 frontend/src/composables/useWebSocket.ts diff --git a/frontend/src/components/home/ContentAuth.vue b/frontend/src/components/home/ContentAuth.vue index cceb8a48d..e61cef332 100644 --- a/frontend/src/components/home/ContentAuth.vue +++ b/frontend/src/components/home/ContentAuth.vue @@ -86,6 +86,7 @@ import {useProjectStore} from '@/stores/projects' import {useRouteWithModal} from '@/composables/useRouteWithModal' import {useRenewTokenOnFocus} from '@/composables/useRenewTokenOnFocus' import {useSidebarResize} from '@/composables/useSidebarResize' +import {useWebSocket} from '@/composables/useWebSocket' import {useAuthStore} from '@/stores/auth' const authStore = useAuthStore() @@ -136,6 +137,9 @@ watch(() => route.name as string, (routeName) => { useRenewTokenOnFocus() +const {connect} = useWebSocket() +connect() + const labelStore = useLabelStore() labelStore.loadAllLabels() diff --git a/frontend/src/components/notifications/Notifications.vue b/frontend/src/components/notifications/Notifications.vue index ca4e5264e..c8e702142 100644 --- a/frontend/src/components/notifications/Notifications.vue +++ b/frontend/src/components/notifications/Notifications.vue @@ -83,10 +83,11 @@