@qoretechnologies/reqraft 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/chromatic.config.json +5 -0
- package/dist/hooks/useWebSocket/useWebSocket.d.ts +27 -0
- package/dist/hooks/useWebSocket/useWebSocket.d.ts.map +1 -0
- package/dist/hooks/useWebSocket/useWebSocket.js +129 -0
- package/dist/hooks/useWebSocket/useWebSocket.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/websocket.d.ts +51 -0
- package/dist/utils/websocket.d.ts.map +1 -0
- package/dist/utils/websocket.js +264 -0
- package/dist/utils/websocket.js.map +1 -0
- package/package.json +16 -15
- package/src/hooks/useFetch/useFetch.stories.tsx +8 -11
- package/src/hooks/useWebSocket/useWebSocket.ts +146 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +481 -0
- package/src/index.tsx +2 -0
- package/src/utils/websocket.ts +258 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { forEach } from 'lodash';
|
|
2
|
+
import shortid from 'shortid';
|
|
3
|
+
import { fetchConfig, query } from './fetch';
|
|
4
|
+
|
|
5
|
+
export interface IReqraftWebSocketConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
reconnect?: boolean;
|
|
8
|
+
reconnectInterval?: number;
|
|
9
|
+
maxReconnectTries?: number;
|
|
10
|
+
useHeartbeat?: boolean;
|
|
11
|
+
onOpen?: (ev?: Event) => void;
|
|
12
|
+
onMessage?: (ev: MessageEvent) => void;
|
|
13
|
+
onClose?: (ev?: CloseEvent) => void;
|
|
14
|
+
onError?: (ev?: Event) => void;
|
|
15
|
+
onReconnecting?: (reconnectNumber?: number) => void;
|
|
16
|
+
onReconnectFailed?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ReqraftWebSocketsManager {
|
|
20
|
+
public static defaultConfig: IReqraftWebSocketConfig = {
|
|
21
|
+
reconnect: true,
|
|
22
|
+
url: '',
|
|
23
|
+
maxReconnectTries: 10,
|
|
24
|
+
reconnectInterval: 5000,
|
|
25
|
+
useHeartbeat: true,
|
|
26
|
+
};
|
|
27
|
+
public static connections: Record<
|
|
28
|
+
string,
|
|
29
|
+
{ socket: WebSocket; using: number; heartbeat?: NodeJS.Timeout }
|
|
30
|
+
> = {};
|
|
31
|
+
|
|
32
|
+
public static closeAll() {
|
|
33
|
+
forEach(this.connections, (connection) => {
|
|
34
|
+
connection.socket.close(4999);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public static addHandler(
|
|
39
|
+
url: string,
|
|
40
|
+
event: keyof WebSocketEventMap,
|
|
41
|
+
handler: (ev: Event | MessageEvent | CloseEvent) => void
|
|
42
|
+
) {
|
|
43
|
+
this.connections[url]?.socket.addEventListener(event, handler);
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
this.connections[url]?.socket.removeEventListener(event, handler);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static removeHandler(
|
|
51
|
+
url: string,
|
|
52
|
+
event: keyof WebSocketEventMap,
|
|
53
|
+
handler: (ev: Event | MessageEvent | CloseEvent) => void
|
|
54
|
+
) {
|
|
55
|
+
this.connections[url]?.socket.removeEventListener(event, handler);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class ReqraftWebSocket {
|
|
60
|
+
public isConnected: boolean;
|
|
61
|
+
public reconnectTries: number = 0;
|
|
62
|
+
public reconnectInterval: NodeJS.Timeout;
|
|
63
|
+
public options: IReqraftWebSocketConfig;
|
|
64
|
+
public readonly handlers: Record<
|
|
65
|
+
string,
|
|
66
|
+
{ type: keyof WebSocketEventMap; event: (ev: Event) => void }
|
|
67
|
+
> = {};
|
|
68
|
+
private socket: WebSocket;
|
|
69
|
+
|
|
70
|
+
constructor(config: IReqraftWebSocketConfig) {
|
|
71
|
+
this.isConnected = true;
|
|
72
|
+
this.reconnectTries = 0;
|
|
73
|
+
this.reconnectInterval = null;
|
|
74
|
+
this.options = { ...ReqraftWebSocketsManager.defaultConfig, ...config };
|
|
75
|
+
|
|
76
|
+
this.connect();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public addHandler(
|
|
80
|
+
event: keyof WebSocketEventMap,
|
|
81
|
+
handler: (ev: Event | MessageEvent | CloseEvent) => void
|
|
82
|
+
) {
|
|
83
|
+
const id = shortid.generate();
|
|
84
|
+
|
|
85
|
+
this.handlers[id] = { type: event, event: handler };
|
|
86
|
+
|
|
87
|
+
ReqraftWebSocketsManager.addHandler(this.options.url, event, handler);
|
|
88
|
+
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public removeHandler(id: string) {
|
|
93
|
+
ReqraftWebSocketsManager.removeHandler(
|
|
94
|
+
this.options.url,
|
|
95
|
+
this.handlers[id].type,
|
|
96
|
+
this.handlers[id].event
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
delete this.handlers[id];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private getSocketUrl() {
|
|
103
|
+
let wsUrl = fetchConfig.instance.replace('http', 'ws');
|
|
104
|
+
|
|
105
|
+
if (wsUrl.endsWith('/')) {
|
|
106
|
+
wsUrl = wsUrl.slice(0, -1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return `${wsUrl}/${this.options.url}?token=${fetchConfig.instanceToken}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public connect() {
|
|
113
|
+
if (!ReqraftWebSocketsManager.connections[this.options.url]) {
|
|
114
|
+
this.socket = new WebSocket(this.getSocketUrl());
|
|
115
|
+
this.socket.onopen = (ev) => {
|
|
116
|
+
this.reconnectTries = 0;
|
|
117
|
+
this.isConnected = true;
|
|
118
|
+
|
|
119
|
+
clearInterval(this.reconnectInterval);
|
|
120
|
+
|
|
121
|
+
this.reconnectInterval = undefined;
|
|
122
|
+
|
|
123
|
+
if (this.options.useHeartbeat) {
|
|
124
|
+
this.startHeartbeat();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.options?.onOpen?.(ev);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
ReqraftWebSocketsManager.connections[this.options.url] = {
|
|
131
|
+
socket: this.socket,
|
|
132
|
+
using: 0,
|
|
133
|
+
};
|
|
134
|
+
} else {
|
|
135
|
+
this.socket = ReqraftWebSocketsManager.connections[this.options.url].socket;
|
|
136
|
+
this.options?.onOpen?.(null);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Increment the number of parts using this connection
|
|
140
|
+
ReqraftWebSocketsManager.connections[this.options.url].using++;
|
|
141
|
+
|
|
142
|
+
if (this.options?.onMessage) {
|
|
143
|
+
this.addHandler('message', (event) => {
|
|
144
|
+
if ((<MessageEvent>event).data === 'pong') {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.options?.onMessage?.(<MessageEvent>event);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.addHandler('close', (event) => {
|
|
153
|
+
this.options?.onClose?.(<CloseEvent>event);
|
|
154
|
+
this.stopHeartbeat();
|
|
155
|
+
this.removeAllHandlers();
|
|
156
|
+
|
|
157
|
+
delete ReqraftWebSocketsManager.connections[this.options.url];
|
|
158
|
+
|
|
159
|
+
// Start the reconnect process
|
|
160
|
+
this.maybeReconnect((<CloseEvent>event).code);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
this.addHandler('error', (event) => {
|
|
164
|
+
this.options?.onError?.(event);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public send(data: string) {
|
|
169
|
+
this.socket.send(data);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public async checkServerStatus() {
|
|
173
|
+
const check = await query({ url: '/system/pid', cache: false });
|
|
174
|
+
|
|
175
|
+
if (check.code === 401) {
|
|
176
|
+
// Qorus is back up again but we need to re-authenticate
|
|
177
|
+
// Get the current pathname and redirect to the login page
|
|
178
|
+
window.location.href = fetchConfig.unauthorizedRedirect(window.location.pathname);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async tryReconnect() {
|
|
183
|
+
this.reconnectTries++;
|
|
184
|
+
this.options?.onReconnecting?.(this.reconnectTries);
|
|
185
|
+
|
|
186
|
+
await this.checkServerStatus();
|
|
187
|
+
|
|
188
|
+
this.connect();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public maybeReconnect(closeCode: number) {
|
|
192
|
+
if (this.options.reconnect && closeCode !== 4999) {
|
|
193
|
+
// If this is the first reconnect try
|
|
194
|
+
if (this.reconnectTries === 0) {
|
|
195
|
+
this.tryReconnect();
|
|
196
|
+
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// If we haven't reached the max number of reconnect tries
|
|
201
|
+
if (this.reconnectTries < this.options.maxReconnectTries) {
|
|
202
|
+
this.reconnectInterval = setTimeout(
|
|
203
|
+
this.tryReconnect.bind(this),
|
|
204
|
+
this.options.reconnectInterval
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Reconnect failed
|
|
211
|
+
this.isConnected = false;
|
|
212
|
+
this.reconnectInterval = undefined;
|
|
213
|
+
this.reconnectTries = 0;
|
|
214
|
+
|
|
215
|
+
this.options.onReconnectFailed?.();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public remove() {
|
|
220
|
+
if (!ReqraftWebSocketsManager.connections[this.options.url]) return;
|
|
221
|
+
// Decrement the number of parts using this connection
|
|
222
|
+
ReqraftWebSocketsManager.connections[this.options.url].using--;
|
|
223
|
+
// If this is the last part using the connection, close it
|
|
224
|
+
if (ReqraftWebSocketsManager.connections[this.options.url].using === 0) {
|
|
225
|
+
this.close();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Remove all handlers
|
|
230
|
+
this.removeAllHandlers();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public close() {
|
|
234
|
+
this.socket.close(4999);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public removeAllHandlers() {
|
|
238
|
+
forEach(this.handlers, (_handler, id) => {
|
|
239
|
+
this.removeHandler(id);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private startHeartbeat() {
|
|
244
|
+
// Start the heartbeat
|
|
245
|
+
clearInterval(ReqraftWebSocketsManager.connections[this.options.url].heartbeat);
|
|
246
|
+
ReqraftWebSocketsManager.connections[this.options.url].heartbeat = null;
|
|
247
|
+
ReqraftWebSocketsManager.connections[this.options.url].heartbeat = setInterval(() => {
|
|
248
|
+
this.socket.send('ping');
|
|
249
|
+
}, 3000);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private stopHeartbeat() {
|
|
253
|
+
if (!ReqraftWebSocketsManager.connections[this.options.url]) return;
|
|
254
|
+
|
|
255
|
+
clearInterval(ReqraftWebSocketsManager.connections[this.options.url].heartbeat);
|
|
256
|
+
ReqraftWebSocketsManager.connections[this.options.url].heartbeat = null;
|
|
257
|
+
}
|
|
258
|
+
}
|