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

DigitalTolk / ex / 24964376795

26 Apr 2026 06:52PM UTC coverage: 90.069% (-0.3%) from 90.372%
24964376795

Pull #13

github

web-flow
Merge a40c3174a into c7a0902b0
Pull Request #13: Docker fixes

1506 of 1799 branches covered (83.71%)

Branch coverage included in aggregate %.

955 of 1086 new or added lines in 34 files covered. (87.94%)

7935 of 8683 relevant lines covered (91.39%)

18.35 hits per line

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

76.64
/frontend/src/hooks/useWebSocket.ts
1
import { useEffect, useRef } from 'react';
2
import { getAccessToken } from '@/lib/api';
3
import { EventType } from '@/lib/event-types';
4
import { setWSSender } from '@/lib/ws-sender';
5

6
type WSCallback = (data: unknown) => void;
7

8
interface UseWebSocketOptions {
9
  onMessageNew?: WSCallback;
10
  onMessageEdited?: WSCallback;
11
  onMessageDeleted?: WSCallback;
12
  onMembersChanged?: WSCallback;
13
  onConversationNew?: WSCallback;
14
  onChannelArchived?: WSCallback;
15
  onChannelUpdated?: WSCallback;
16
  onChannelNew?: WSCallback;
17
  onChannelRemoved?: WSCallback;
18
  onPresenceChanged?: WSCallback;
19
  onEmojiAdded?: WSCallback;
20
  onEmojiRemoved?: WSCallback;
21
  onUserUpdated?: WSCallback;
22
  onAttachmentDeleted?: WSCallback;
23
  onChannelMuted?: WSCallback;
24
  onNotification?: WSCallback;
25
  onTyping?: WSCallback;
26
  enabled?: boolean;
27
}
28

29
export function useWebSocket(options: UseWebSocketOptions) {
30
  const callbacksRef = useRef(options);
20✔
31
  const wsRef = useRef<WebSocket | null>(null);
20✔
32
  const retryCountRef = useRef(0);
20✔
33
  const retryTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
20✔
34
  const enabledRef = useRef(options.enabled);
20✔
35

36
  useEffect(() => {
20✔
37
    callbacksRef.current = options;
20✔
38
    enabledRef.current = options.enabled;
20✔
39
  });
40

41
  useEffect(() => {
20✔
42
    if (!options.enabled) return;
20✔
43

44
    function connect() {
45
      const token = getAccessToken();
22✔
46
      if (!token) return;
22✔
47

48
      const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
21!
49
      const url = `${proto}//${window.location.host}/api/v1/ws?token=${encodeURIComponent(token)}`;
22✔
50
      const ws = new WebSocket(url);
22✔
51
      wsRef.current = ws;
22✔
52

53
      ws.onopen = () => {
22✔
54
        retryCountRef.current = 0;
9✔
55
        // Expose the live socket's send to other components (typing
56
        // indicator and similar ephemera) without prop-drilling.
57
        setWSSender((frame) => ws.send(frame));
9✔
58
      };
59

60
      ws.onmessage = (event) => {
22✔
61
        try {
11✔
62
          const msg = JSON.parse(event.data);
11✔
63
          const payload = typeof msg.data === 'string' ? JSON.parse(msg.data) : msg.data ?? msg;
11✔
64
          switch (msg.type) {
11!
65
            case EventType.MessageNew:
66
              callbacksRef.current.onMessageNew?.(payload);
2✔
67
              break;
2✔
68
            case EventType.MessageEdited:
69
              callbacksRef.current.onMessageEdited?.(payload);
1✔
70
              break;
1✔
71
            case EventType.MessageDeleted:
72
              callbacksRef.current.onMessageDeleted?.(payload);
1✔
73
              break;
1✔
74
            case EventType.MembersChanged:
75
              callbacksRef.current.onMembersChanged?.(payload);
1✔
76
              break;
1✔
77
            case EventType.ConversationNew:
78
              callbacksRef.current.onConversationNew?.(payload);
1✔
79
              break;
1✔
80
            case EventType.ChannelArchived:
81
              callbacksRef.current.onChannelArchived?.(payload);
1✔
82
              break;
1✔
83
            case EventType.ChannelUpdated:
84
              callbacksRef.current.onChannelUpdated?.(payload);
1✔
85
              break;
1✔
86
            case EventType.ChannelNew:
87
              callbacksRef.current.onChannelNew?.(payload);
1✔
88
              break;
1✔
89
            case EventType.ChannelRemoved:
90
              callbacksRef.current.onChannelRemoved?.(payload);
1✔
91
              break;
1✔
92
            case EventType.PresenceChanged:
93
              callbacksRef.current.onPresenceChanged?.(payload);
×
94
              break;
×
95
            case EventType.EmojiAdded:
96
              callbacksRef.current.onEmojiAdded?.(payload);
×
97
              break;
×
98
            case EventType.EmojiRemoved:
99
              callbacksRef.current.onEmojiRemoved?.(payload);
×
100
              break;
×
101
            case EventType.UserUpdated:
102
              callbacksRef.current.onUserUpdated?.(payload);
×
103
              break;
×
104
            case EventType.AttachmentDeleted:
105
              callbacksRef.current.onAttachmentDeleted?.(payload);
×
106
              break;
×
107
            case EventType.ChannelMuted:
108
              callbacksRef.current.onChannelMuted?.(payload);
×
109
              break;
×
110
            case EventType.NotificationNew:
111
              callbacksRef.current.onNotification?.(payload);
×
112
              break;
×
113
            case 'typing':
NEW
114
              callbacksRef.current.onTyping?.(payload);
×
NEW
115
              break;
×
116
          }
117
        } catch {
118
          // ignore parse errors
119
        }
120
      };
121

122
      ws.onclose = () => {
22✔
123
        wsRef.current = null;
5✔
124
        setWSSender(null);
5✔
125
        if (!enabledRef.current) return;
5✔
126
        const backoff = Math.min(1000 * Math.pow(2, retryCountRef.current), 30000);
4✔
127
        retryCountRef.current++;
4✔
128
        retryTimerRef.current = setTimeout(connect, backoff);
4✔
129
      };
130

131
      ws.onerror = () => {
22✔
132
        ws.close();
1✔
133
      };
134
    }
135

136
    connect();
18✔
137

138
    return () => {
18✔
139
      if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
18✔
140
      if (wsRef.current) {
18✔
141
        wsRef.current.close();
17✔
142
        wsRef.current = null;
17✔
143
      }
144
      setWSSender(null);
18✔
145
    };
146
  }, [options.enabled]);
147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc