All files / src/hooks useTaskEvents.ts

96.72% Statements 59/61
85% Branches 17/20
100% Functions 6/6
96.72% Lines 59/61

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 751x         1x 1x   1x 2x 2x   2x 2x 2x 2x   2x 4x 4x   4x 1x 1x   4x 3x     3x 3x 1x 1x 1x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 1x 1x 1x 1x 3x   4x 2x 2x 2x 2x 2x 2x   4x 1x 1x 4x   2x   2x 2x 2x 2x 2x 2x 2x  
import { useEffect, useRef } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { websocketUrl } from "../api";
import type { WebEvent } from "../types";
 
const INITIAL_DELAY = 1000;
const MAX_DELAY = 30000;
 
export function useTaskEvents(): void {
  const client = useQueryClient();
  const retryDelay = useRef(INITIAL_DELAY);
 
  useEffect(() => {
    let socket: WebSocket | null = null;
    let retryTimer: ReturnType<typeof setTimeout> | null = null;
    let disposed = false;
 
    function connect() {
      if (disposed) return;
      socket = new WebSocket(websocketUrl("/api/events/app"));
 
      socket.onopen = () => {
        retryDelay.current = INITIAL_DELAY;
      };
 
      socket.onmessage = (message) => {
        if (typeof message.data !== "string") {
          return;
        }
        const event = JSON.parse(message.data) as WebEvent;
        if (event.type === "task_updated" || event.type === "task_deleted") {
          void client.invalidateQueries({ queryKey: ["tasks"] });
          return;
        }
        if (event.type === "board_stages_updated") {
          void client.invalidateQueries({ queryKey: ["board-stages"] });
          void client.invalidateQueries({ queryKey: ["tasks"] });
          void client.invalidateQueries({ queryKey: ["bootstrap"] });
          return;
        }
        if (
          event.type === "live_session_started"
          || event.type === "live_session_updated"
          || event.type === "live_session_bound"
          || event.type === "live_session_ended"
        ) {
          void client.invalidateQueries({ queryKey: ["sessions"] });
          void client.invalidateQueries({ queryKey: ["bootstrap"] });
          void client.invalidateQueries({ queryKey: ["live-sessions"] });
        }
      };
 
      socket.onclose = () => {
        if (disposed) return;
        retryTimer = setTimeout(() => {
          retryDelay.current = Math.min(retryDelay.current * 2, MAX_DELAY);
          connect();
        }, retryDelay.current);
      };
 
      socket.onerror = () => {
        socket?.close();
      };
    }
 
    connect();
 
    return () => {
      disposed = true;
      if (retryTimer) clearTimeout(retryTimer);
      socket?.close();
    };
  }, [client]);
}