• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / aiforge / 21002763057

14 Jan 2026 05:00PM UTC coverage: 82.93% (-1.8%) from 84.765%
21002763057

push

github

apowers313
chore: delint

993 of 1165 branches covered (85.24%)

Branch coverage included in aggregate %.

5206 of 6310 relevant lines covered (82.5%)

15.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.67
/src/client/hooks/useWebSocket.ts
1
/**
2
 * useWebSocket - React hook for WebSocket connections
3
 */
4
import { useState, useEffect, useCallback, useRef } from 'react';
1✔
5
import { ReconnectingWebSocket, type ReconnectingWebSocketOptions } from '@client/services/websocket.js';
1✔
6
import { waitForServerHealth } from '@client/services/api.js';
1✔
7

8
// Debug helper to get elapsed time since terminal switch started
9
function getElapsed(): string {
65✔
10
  const start = (window as unknown as { __terminalSwitchStart?: number }).__terminalSwitchStart;
65✔
11
  if (!start) return '?.??';
65!
12
  return (performance.now() - start).toFixed(2);
×
13
}
×
14

15
/**
16
 * WebSocket connection status
17
 */
18
export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
19

20
/**
21
 * Options for useWebSocket hook
22
 */
23
export interface UseWebSocketOptions {
24
  /**
25
   * Auto-connect on mount
26
   * @default true
27
   */
28
  autoConnect?: boolean;
29

30
  /**
31
   * Wait for server health check before connecting
32
   * This prevents connection attempts while server is starting up
33
   * @default false
34
   */
35
  waitForHealth?: boolean;
36

37
  /**
38
   * Callback when message is received
39
   */
40
  onMessage?: (data: unknown) => void;
41

42
  /**
43
   * Callback when connection opens
44
   */
45
  onOpen?: () => void;
46

47
  /**
48
   * Callback when connection closes
49
   */
50
  onClose?: () => void;
51

52
  /**
53
   * Callback when error occurs
54
   */
55
  onError?: (event: Event) => void;
56

57
  /**
58
   * ReconnectingWebSocket options
59
   */
60
  reconnectOptions?: Omit<ReconnectingWebSocketOptions, 'onMessage' | 'onOpen' | 'onClose' | 'onError'>;
61
}
62

63
/**
64
 * Return type for useWebSocket hook
65
 */
66
export interface UseWebSocketReturn {
67
  status: WebSocketStatus;
68
  isConnected: boolean;
69
  send: (data: unknown) => void;
70
  connect: () => void;
71
  disconnect: () => void;
72
}
73

74
/**
75
 * React hook for managing WebSocket connections
76
 */
77
export function useWebSocket(url: string, options: UseWebSocketOptions = {}): UseWebSocketReturn {
1✔
78
  const {
29✔
79
    autoConnect = true,
29✔
80
    waitForHealth = false,
29✔
81
    onMessage,
29✔
82
    onOpen,
29✔
83
    onClose,
29✔
84
    onError,
29✔
85
    reconnectOptions,
29✔
86
  } = options;
29✔
87

88
  const [status, setStatus] = useState<WebSocketStatus>('disconnected');
29✔
89
  const wsRef = useRef<ReconnectingWebSocket | null>(null);
29✔
90
  const healthCheckInProgressRef = useRef(false);
29✔
91

92
  // Store callbacks in refs to avoid reconnection on callback changes
93
  const onMessageRef = useRef(onMessage);
29✔
94
  const onOpenRef = useRef(onOpen);
29✔
95
  const onCloseRef = useRef(onClose);
29✔
96
  const onErrorRef = useRef(onError);
29✔
97

98
  useEffect(() => {
29✔
99
    onMessageRef.current = onMessage;
9✔
100
    onOpenRef.current = onOpen;
9✔
101
    onCloseRef.current = onClose;
9✔
102
    onErrorRef.current = onError;
9✔
103
  }, [onMessage, onOpen, onClose, onError]);
29✔
104

105
  const connect = useCallback(() => {
29✔
106
    console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket.connect() called, wsRef.current=${String(!!wsRef.current)}`);
9✔
107
    if (wsRef.current) {
9!
108
      console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket.connect() - already connected, returning`);
×
109
      return;
×
110
    }
×
111

112
    // If health check is enabled and not already in progress
113
    if (waitForHealth && !healthCheckInProgressRef.current) {
9✔
114
      healthCheckInProgressRef.current = true;
9✔
115
      setStatus('connecting');
9✔
116
      console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: Starting health check (waitForHealth=true)`);
9✔
117

118
      waitForServerHealth({ maxAttempts: 600, delayMs: 1000 })
9✔
119
        .then(() => {
9✔
120
          console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: Health check PASSED`);
9✔
121
          healthCheckInProgressRef.current = false;
9✔
122
          // Now actually connect
123
          if (!wsRef.current) {
9✔
124
            console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: Creating ReconnectingWebSocket`);
9✔
125
            wsRef.current = new ReconnectingWebSocket(url, {
9✔
126
              ...reconnectOptions,
9✔
127
              onMessage: (data): void => {
9✔
128
                onMessageRef.current?.(data);
3✔
129
              },
3✔
130
              onOpen: (): void => {
9✔
131
                console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: WebSocket OPEN`);
10✔
132
                setStatus('connected');
10✔
133
                onOpenRef.current?.();
10✔
134
              },
10✔
135
              onClose: (): void => {
9✔
136
                console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: WebSocket CLOSED`);
10✔
137
                setStatus('disconnected');
10✔
138
                onCloseRef.current?.();
10!
139
              },
10✔
140
              onError: (event): void => {
9✔
141
                console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: WebSocket ERROR`);
×
142
                setStatus('error');
×
143
                onErrorRef.current?.(event);
×
144
              },
×
145
            });
9✔
146
            console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: Calling wsRef.current.connect()`);
9✔
147
            wsRef.current.connect();
9✔
148
          }
9✔
149
        })
9✔
150
        .catch(() => {
9✔
151
          console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: Health check FAILED`);
×
152
          healthCheckInProgressRef.current = false;
×
153
          setStatus('error');
×
154
          onErrorRef.current?.(new Event('health_check_failed'));
×
155
        });
9✔
156
      return;
9✔
157
    }
9!
158

159
    console.log(`[TERMINAL_SWITCH] +${getElapsed()}ms - useWebSocket: Connecting without health check`);
×
160
    setStatus('connecting');
×
161

162
    wsRef.current = new ReconnectingWebSocket(url, {
×
163
      ...reconnectOptions,
×
164
      onMessage: (data): void => {
×
165
        onMessageRef.current?.(data);
×
166
      },
×
167
      onOpen: (): void => {
×
168
        setStatus('connected');
×
169
        onOpenRef.current?.();
×
170
      },
×
171
      onClose: (): void => {
×
172
        setStatus('disconnected');
×
173
        onCloseRef.current?.();
×
174
      },
×
175
      onError: (event): void => {
×
176
        setStatus('error');
×
177
        onErrorRef.current?.(event);
×
178
      },
×
179
    });
×
180

181
    wsRef.current.connect();
×
182
  }, [url, reconnectOptions, waitForHealth]);
29✔
183

184
  const disconnect = useCallback(() => {
29✔
185
    if (wsRef.current) {
8✔
186
      wsRef.current.close();
8✔
187
      wsRef.current = null;
8✔
188
      setStatus('disconnected');
8✔
189
    }
8✔
190
  }, []);
29✔
191

192
  const send = useCallback((data: unknown) => {
29✔
193
    wsRef.current?.send(data);
22✔
194
  }, []);
29✔
195

196
  // Auto-connect on mount
197
  useEffect(() => {
29✔
198
    if (autoConnect) {
9✔
199
      connect();
9✔
200
    }
9✔
201

202
    return (): void => {
9✔
203
      // Reset health check flag on cleanup to handle React StrictMode double-mount
204
      healthCheckInProgressRef.current = false;
9✔
205
      if (wsRef.current) {
9✔
206
        wsRef.current.close();
1✔
207
        wsRef.current = null;
1✔
208
      }
1✔
209
    };
9✔
210
  }, [autoConnect, connect]);
29✔
211

212
  return {
29✔
213
    status,
29✔
214
    isConnected: status === 'connected',
29✔
215
    send,
29✔
216
    connect,
29✔
217
    disconnect,
29✔
218
  };
29✔
219
}
29✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc