]> git.ipfire.org Git - pbs.git/commitdiff
frontend: Create a WebSocket which listens to events
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 4 Jul 2025 14:57:24 +0000 (14:57 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 4 Jul 2025 14:57:24 +0000 (14:57 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
frontend/src/App.vue
frontend/src/composables/events.ts [new file with mode: 0644]

index 53ccde72b7382c9a35d00bb3979644e5ef62161e..0fcc303dff2fca10d19b29f5ad2cc7e218a9c0a4 100644 (file)
@@ -5,6 +5,10 @@
        import { useAuth } from "@/composables/auth";
        const auth = useAuth();
 
+       // Events
+       import { useEvents } from "@/composables/events"
+       const { isConnected } = useEvents()
+
        // Current Timestamp
        const now = new Date();
 </script>
                                </div>
 
                                <div class="navbar-end">
+                                       <div class="navbar-item" v-if="!isConnected">
+                                               DISCONNECTED
+                                       </div>
+
                                        <RouterLink to="/login" class="navbar-item" v-if="!auth.isLoggedIn.value">
                                                {{ $t("Login") }}
                                        </RouterLink>
diff --git a/frontend/src/composables/events.ts b/frontend/src/composables/events.ts
new file mode 100644 (file)
index 0000000..f8107d4
--- /dev/null
@@ -0,0 +1,111 @@
+import { ref, onUnmounted } from "vue";
+
+// The socket
+const socket = ref<WebSocket | null>(null);
+
+// To check whether we are connected
+const isConnected = ref(false);
+
+const reconnectHoldoffTime = 3000; // 3 seconds
+let reconnectAttempts: number = 0;
+let reconnectTimeout: number | null = null;
+
+// Listeners
+const listeners: Map<string, Set<(data: any) => void>> = new Map();
+
+function connect() {
+       // Clear any reconnect timers
+       if (reconnectTimeout) {
+               clearTimeout(reconnectTimeout);
+               reconnectTimeout = null;
+       }
+
+       // Don't connect again once connected
+       if (socket.value && socket.value.readyState <= 1)
+               return;
+
+       // Connect the socket
+       socket.value = new WebSocket("/api/v1/events");
+
+       // Called when the socket connected
+       socket.value.onopen = () => {
+               // We are now connected
+               isConnected.value = true;
+       }
+
+       // Called when the socket was closed
+       socket.value.onclose = () => {
+               // We are now disconnected
+               isConnected.value = false;
+
+               // Reconnect soon
+               reconnect();
+       }
+
+       // Called when there has been an error
+       socket.value.onerror = () => {
+               socket.value?.close();
+       }
+
+       // Called when we received a message
+       socket.value.onmessage = (event) => {
+               try {
+                       // Parse the event
+                       const { type, payload } = JSON.parse(event.data);
+
+                       // Call all callbacks
+                       const callbacks = listeners.get(type);
+                       if (callbacks) {
+                               callbacks.forEach(cb => cb(payload));
+                       }
+               } catch (e) {
+                       console.error("Failed to parse event:", e);
+               }
+       };
+}
+
+// Closes the socket
+function close() {
+       socket.value?.close();
+}
+
+// Will schedule a reconnect
+function reconnect() {
+       if (reconnectTimeout)
+               return;
+
+       // Increment attempts
+       reconnectAttempts++;
+
+       // Reconnect again after the holdoff time
+       reconnectTimeout = setTimeout(() => connect(), reconnectHoldoffTime);
+}
+
+export function useEvents() {
+       // Connect to the API
+       connect();
+
+       // Subscribe a new event listener
+       function subscribe(type: string, cb: (data: any) => void) {
+               if (!listeners.has(type)) {
+                       listeners.set(type, new Set());
+               }
+               listeners.get(type)!.add(cb);
+       }
+
+       // Remove the event listener
+       function unsubscribe(type: string, cb: (data: any) => void) {
+               listeners.get(type)?.delete(cb);
+       }
+
+       // Remove all listeners on cleanup
+       onUnmounted(() => {
+               listeners.clear();
+       });
+
+       return {
+               isConnected,
+               subscribe,
+               unsubscribe,
+       };
+}