-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathWebSocketProvider.tsx
More file actions
130 lines (112 loc) · 3.67 KB
/
WebSocketProvider.tsx
File metadata and controls
130 lines (112 loc) · 3.67 KB
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import React, { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
// Context for managing multiple WebSocket connections across the app
interface WebSocketContextValue {
connections: Map<string, WebSocket>;
addConnection: (key: string, url: string, protocols?: string | string[]) => void;
removeConnection: (key: string) => void;
getConnection: (key: string) => WebSocket | undefined;
sendToConnection: (key: string, data: any) => boolean;
connectionStates: Map<string, 'connecting' | 'connected' | 'disconnected' | 'error'>;
}
const WebSocketContext = createContext<WebSocketContextValue | null>(null);
interface WebSocketProviderProps {
children: ReactNode;
globalConfig?: {
reconnectAttempts?: number;
reconnectDelay?: number;
heartbeatInterval?: number;
};
}
export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
children,
globalConfig = {},
}) => {
const [connections] = useState(() => new Map<string, WebSocket>());
const [connectionStates, setConnectionStates] = useState(
() => new Map<string, 'connecting' | 'connected' | 'disconnected' | 'error'>()
);
const addConnection = (key: string, url: string, protocols?: string | string[]) => {
// Close existing connection if it exists
const existingConnection = connections.get(key);
if (existingConnection) {
existingConnection.close();
}
try {
const ws = new WebSocket(url, protocols);
setConnectionStates(prev => new Map(prev).set(key, 'connecting'));
ws.onopen = () => {
setConnectionStates(prev => new Map(prev).set(key, 'connected'));
};
ws.onerror = () => {
setConnectionStates(prev => new Map(prev).set(key, 'error'));
};
ws.onclose = () => {
setConnectionStates(prev => new Map(prev).set(key, 'disconnected'));
connections.delete(key);
};
connections.set(key, ws);
} catch (error) {
console.error(`Failed to create WebSocket connection for ${key}:`, error);
setConnectionStates(prev => new Map(prev).set(key, 'error'));
}
};
const removeConnection = (key: string) => {
const connection = connections.get(key);
if (connection) {
connection.close();
connections.delete(key);
setConnectionStates(prev => {
const newStates = new Map(prev);
newStates.delete(key);
return newStates;
});
}
};
const getConnection = (key: string) => {
return connections.get(key);
};
const sendToConnection = (key: string, data: any): boolean => {
const connection = connections.get(key);
if (connection && connection.readyState === WebSocket.OPEN) {
try {
connection.send(JSON.stringify(data));
return true;
} catch (error) {
console.error(`Failed to send data to connection ${key}:`, error);
return false;
}
}
return false;
};
// Cleanup on unmount
useEffect(() => {
return () => {
connections.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
ws.close();
}
});
connections.clear();
};
}, [connections]);
const contextValue: WebSocketContextValue = {
connections,
addConnection,
removeConnection,
getConnection,
sendToConnection,
connectionStates,
};
return (
<WebSocketContext.Provider value={contextValue}>
{children}
</WebSocketContext.Provider>
);
};
export const useWebSocketContext = () => {
const context = useContext(WebSocketContext);
if (!context) {
throw new Error('useWebSocketContext must be used within a WebSocketProvider');
}
return context;
};