@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,146 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useEffectOnce, useUnmount } from 'react-use';
|
|
3
|
+
import { IReqraftWebSocketConfig, ReqraftWebSocket } from '../../utils/websocket';
|
|
4
|
+
|
|
5
|
+
export interface IUseReqraftWebSocketOptions extends IReqraftWebSocketConfig {
|
|
6
|
+
onMessage?: (ev: MessageEvent) => void;
|
|
7
|
+
useState?: boolean;
|
|
8
|
+
includeSentMessagesInState?: boolean;
|
|
9
|
+
includeLogMessagesInState?: boolean;
|
|
10
|
+
openOnMount?: boolean;
|
|
11
|
+
closeOnUnmount?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IUseReqraftWebSocket {
|
|
15
|
+
messages: string[];
|
|
16
|
+
status: keyof typeof ReqraftWebSocketStatus;
|
|
17
|
+
open: () => void;
|
|
18
|
+
close: () => void;
|
|
19
|
+
socket: ReqraftWebSocket;
|
|
20
|
+
send: (data: string) => void;
|
|
21
|
+
clear: () => void;
|
|
22
|
+
on: (type: keyof WebSocketEventMap, handler: (ev: Event) => void) => void;
|
|
23
|
+
addMessage: (message: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export enum ReqraftWebSocketStatus {
|
|
27
|
+
OPEN = 'OPEN',
|
|
28
|
+
CLOSED = 'CLOSED',
|
|
29
|
+
CONNECTING = 'CONNECTING',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const useReqraftWebSocket = (options: IUseReqraftWebSocketOptions): IUseReqraftWebSocket => {
|
|
33
|
+
const [messages, setMessages] = useState<string[]>([]);
|
|
34
|
+
const [status, setStatus] = useState<keyof typeof ReqraftWebSocketStatus>('CLOSED');
|
|
35
|
+
const [socket, setSocket] = useState<ReqraftWebSocket>(undefined);
|
|
36
|
+
|
|
37
|
+
const updateStates = (status: keyof typeof ReqraftWebSocketStatus, log?: string) => {
|
|
38
|
+
setStatus(status);
|
|
39
|
+
|
|
40
|
+
if (log && options?.includeLogMessagesInState && options?.useState) {
|
|
41
|
+
setMessages((prev) => [...prev, log]);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleOpen = (ev?: Event) => {
|
|
46
|
+
updateStates(ReqraftWebSocketStatus.OPEN, 'Connection opened');
|
|
47
|
+
|
|
48
|
+
options?.onOpen?.(ev);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const open = () => {
|
|
52
|
+
const socket = new ReqraftWebSocket({
|
|
53
|
+
...options,
|
|
54
|
+
onOpen: handleOpen,
|
|
55
|
+
onMessage: (ev) => {
|
|
56
|
+
if (options?.useState) {
|
|
57
|
+
setMessages((prev) => [...prev, ev.data]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
options?.onMessage?.(ev);
|
|
61
|
+
},
|
|
62
|
+
onClose: (...args) => {
|
|
63
|
+
updateStates(ReqraftWebSocketStatus.CLOSED, 'Connection closed');
|
|
64
|
+
|
|
65
|
+
options?.onClose?.(...args);
|
|
66
|
+
},
|
|
67
|
+
onError: (...args) => {
|
|
68
|
+
updateStates(ReqraftWebSocketStatus.CLOSED, 'Connection error');
|
|
69
|
+
|
|
70
|
+
options?.onError?.(...args);
|
|
71
|
+
},
|
|
72
|
+
onReconnecting: (reconnectNumber) => {
|
|
73
|
+
updateStates(
|
|
74
|
+
ReqraftWebSocketStatus.CONNECTING,
|
|
75
|
+
`Reconnecting... Attempt ${reconnectNumber}`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
options?.onReconnecting?.(reconnectNumber);
|
|
79
|
+
},
|
|
80
|
+
onReconnectFailed: () => {
|
|
81
|
+
updateStates(ReqraftWebSocketStatus.CLOSED, 'Reconnect failed');
|
|
82
|
+
|
|
83
|
+
options?.onReconnectFailed?.();
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
setSocket(socket);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const close = () => {
|
|
91
|
+
socket?.remove();
|
|
92
|
+
setSocket(undefined);
|
|
93
|
+
setStatus(ReqraftWebSocketStatus.CLOSED);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const send = (data: string) => {
|
|
97
|
+
socket?.send(data);
|
|
98
|
+
|
|
99
|
+
if (options?.includeSentMessagesInState) {
|
|
100
|
+
setMessages((prev) => [...prev, data]);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const on = (type: keyof WebSocketEventMap, handler: (ev: Event) => void) => {
|
|
105
|
+
// Special case for message event
|
|
106
|
+
// We want to handle it differently
|
|
107
|
+
// We want to filter out the ping messages
|
|
108
|
+
if (type === 'message') {
|
|
109
|
+
socket?.addHandler('message', (ev) => {
|
|
110
|
+
if ((<MessageEvent>ev).data === 'pong') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
handler(ev);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
socket?.addHandler(type, handler);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const clear = () => {
|
|
124
|
+
setMessages([]);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const addMessage = (message: string) => {
|
|
128
|
+
if (options?.useState) {
|
|
129
|
+
setMessages((prev) => [...prev, message]);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
useEffectOnce(() => {
|
|
134
|
+
if (options?.openOnMount) {
|
|
135
|
+
open();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
useUnmount(() => {
|
|
140
|
+
if (options?.closeOnUnmount) {
|
|
141
|
+
close();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return { messages, status, open, socket, close, send, clear, on, addMessage };
|
|
146
|
+
};
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { ReqoreControlGroup, ReqoreP, ReqorePanel } from '@qoretechnologies/reqore';
|
|
2
|
+
import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
|
|
3
|
+
import { StoryObj } from '@storybook/react';
|
|
4
|
+
import { expect, fn, waitFor, within } from '@storybook/test';
|
|
5
|
+
import { Server } from 'mock-socket';
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
7
|
+
import { useMount } from 'react-use';
|
|
8
|
+
import { sleep, testsClickButton, testsWaitForText } from '../../../__tests__/utils';
|
|
9
|
+
import { StoryMeta } from '../../types';
|
|
10
|
+
import { ReqraftWebSocketsManager } from '../../utils/websocket';
|
|
11
|
+
import { IUseReqraftWebSocketOptions, useReqraftWebSocket } from './useWebSocket';
|
|
12
|
+
|
|
13
|
+
const CompWithHook = (args: IUseReqraftWebSocketOptions) => {
|
|
14
|
+
const { status, open, close, send, messages, clear } = useReqraftWebSocket(args);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<ReqorePanel
|
|
18
|
+
minimal
|
|
19
|
+
size='small'
|
|
20
|
+
label={`Websocket Status: ${status}`}
|
|
21
|
+
actions={[
|
|
22
|
+
{ label: 'Connect', icon: 'PlayLine', onClick: open },
|
|
23
|
+
{ label: 'Disconnect', icon: 'StopLine', onClick: close },
|
|
24
|
+
{ label: 'Clear', icon: 'CloseLine', onClick: clear },
|
|
25
|
+
{ label: 'Kill', icon: 'CloseLine', onClick: () => send('kill') },
|
|
26
|
+
{ label: 'Send', icon: 'MessageLine', onClick: () => send('This is a test message') },
|
|
27
|
+
]}
|
|
28
|
+
>
|
|
29
|
+
{args.includeLogMessagesInState || args.useState ?
|
|
30
|
+
<ReqoreControlGroup vertical>
|
|
31
|
+
{messages.map((message, index) => (
|
|
32
|
+
<ReqoreP key={index}>{message}</ReqoreP>
|
|
33
|
+
))}
|
|
34
|
+
</ReqoreControlGroup>
|
|
35
|
+
: null}
|
|
36
|
+
</ReqorePanel>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const meta = {
|
|
41
|
+
title: 'Hooks/useWebSocket',
|
|
42
|
+
async beforeEach() {
|
|
43
|
+
const url = `wss://hq.qoretechnologies.com:8092/log-test?token=${process.env.REACT_APP_QORUS_TOKEN}`;
|
|
44
|
+
let server = new Server(url);
|
|
45
|
+
let killTimeout: NodeJS.Timeout;
|
|
46
|
+
|
|
47
|
+
server.on('connection', (socket) => {
|
|
48
|
+
if (killTimeout) {
|
|
49
|
+
server.close();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
socket.on('message', (data) => {
|
|
54
|
+
if (data === 'ping') {
|
|
55
|
+
socket.send('pong');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (data === 'kill') {
|
|
60
|
+
server.close();
|
|
61
|
+
|
|
62
|
+
killTimeout = setTimeout(() => {
|
|
63
|
+
server = new Server(url);
|
|
64
|
+
killTimeout = null;
|
|
65
|
+
}, 3000);
|
|
66
|
+
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
socket.send(`Received message: ${data}`);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
killTimeout && clearTimeout(killTimeout);
|
|
76
|
+
killTimeout = null;
|
|
77
|
+
server.close();
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
args: {
|
|
81
|
+
onOpen: fn(),
|
|
82
|
+
onMessage: fn(),
|
|
83
|
+
onClose: fn(),
|
|
84
|
+
onReconnecting: fn(),
|
|
85
|
+
onError: fn(),
|
|
86
|
+
onReconnectFailed: fn(),
|
|
87
|
+
reconnect: false,
|
|
88
|
+
closeOnUnmount: true,
|
|
89
|
+
url: 'log-test',
|
|
90
|
+
},
|
|
91
|
+
parameters: {
|
|
92
|
+
chromatic: { disable: true },
|
|
93
|
+
},
|
|
94
|
+
render: (args) => {
|
|
95
|
+
return <CompWithHook {...args} />;
|
|
96
|
+
},
|
|
97
|
+
} as StoryMeta<any, IUseReqraftWebSocketOptions>;
|
|
98
|
+
|
|
99
|
+
export default meta;
|
|
100
|
+
export type Story = StoryObj<typeof meta>;
|
|
101
|
+
|
|
102
|
+
export const Default: Story = {};
|
|
103
|
+
export const OpenManually: Story = {
|
|
104
|
+
play: async ({ args }) => {
|
|
105
|
+
await testsClickButton({ label: 'Connect' });
|
|
106
|
+
await testsWaitForText('Websocket Status: OPEN');
|
|
107
|
+
await expect(args.onOpen).toHaveBeenCalled();
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
export const OpenOnMount: Story = {
|
|
111
|
+
args: {
|
|
112
|
+
openOnMount: true,
|
|
113
|
+
},
|
|
114
|
+
play: async ({ args }) => {
|
|
115
|
+
await testsWaitForText('Websocket Status: OPEN');
|
|
116
|
+
await expect(args.onOpen).toHaveBeenCalled();
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const CloseManually: Story = {
|
|
121
|
+
...OpenOnMount,
|
|
122
|
+
play: async ({ args, ...rest }) => {
|
|
123
|
+
await OpenOnMount.play({ args, ...rest });
|
|
124
|
+
await testsClickButton({ label: 'Disconnect' });
|
|
125
|
+
await testsWaitForText('Websocket Status: CLOSED');
|
|
126
|
+
await sleep(300);
|
|
127
|
+
await expect(args.onClose).toHaveBeenCalled();
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const Reconnects: Story = {
|
|
132
|
+
args: {
|
|
133
|
+
reconnect: true,
|
|
134
|
+
maxReconnectTries: 5,
|
|
135
|
+
openOnMount: true,
|
|
136
|
+
},
|
|
137
|
+
play: async ({ args, ...rest }) => {
|
|
138
|
+
await OpenOnMount.play({ args, ...rest });
|
|
139
|
+
await testsClickButton({ label: 'Kill' });
|
|
140
|
+
await testsWaitForText('Websocket Status: CONNECTING');
|
|
141
|
+
await expect(args.onReconnecting).toHaveBeenCalled();
|
|
142
|
+
await testsWaitForText('Websocket Status: OPEN');
|
|
143
|
+
await expect(args.onOpen).toHaveBeenCalled();
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const ReconnectFails: Story = {
|
|
148
|
+
args: {
|
|
149
|
+
reconnect: true,
|
|
150
|
+
maxReconnectTries: 3,
|
|
151
|
+
openOnMount: true,
|
|
152
|
+
reconnectInterval: 500,
|
|
153
|
+
},
|
|
154
|
+
play: async ({ args, ...rest }) => {
|
|
155
|
+
await OpenOnMount.play({ args, ...rest });
|
|
156
|
+
await testsClickButton({ label: 'Kill' });
|
|
157
|
+
await testsWaitForText('Websocket Status: CONNECTING');
|
|
158
|
+
await expect(args.onReconnecting).toHaveBeenCalled();
|
|
159
|
+
await testsWaitForText('Websocket Status: CLOSED');
|
|
160
|
+
await waitFor(() => expect(args.onReconnectFailed).toHaveBeenCalled(), { timeout: 10000 });
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const SendMessage: Story = {
|
|
165
|
+
args: {
|
|
166
|
+
...OpenOnMount.args,
|
|
167
|
+
includeSentMessagesInState: true,
|
|
168
|
+
useState: true,
|
|
169
|
+
},
|
|
170
|
+
play: async ({ args, ...rest }) => {
|
|
171
|
+
await OpenOnMount.play({ args, ...rest });
|
|
172
|
+
await testsClickButton({ label: 'Send' });
|
|
173
|
+
|
|
174
|
+
await sleep(300);
|
|
175
|
+
|
|
176
|
+
await expect(args.onMessage).toHaveBeenCalledWith(
|
|
177
|
+
expect.objectContaining({ data: 'Received message: This is a test message' })
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const WithLogs: Story = {
|
|
183
|
+
args: {
|
|
184
|
+
...Reconnects.args,
|
|
185
|
+
includeLogMessagesInState: true,
|
|
186
|
+
useState: true,
|
|
187
|
+
reconnectInterval: 500,
|
|
188
|
+
},
|
|
189
|
+
play: async ({ args, ...rest }) => {
|
|
190
|
+
await Reconnects.play({ args, ...rest });
|
|
191
|
+
|
|
192
|
+
await testsWaitForText('Reconnecting... Attempt 4');
|
|
193
|
+
await testsWaitForText('Connection opened');
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const ClearsMessages: Story = {
|
|
198
|
+
...SendMessage,
|
|
199
|
+
play: async ({ args, ...rest }) => {
|
|
200
|
+
await SendMessage.play({ args, ...rest });
|
|
201
|
+
await testsClickButton({ label: 'Clear' });
|
|
202
|
+
await sleep(300);
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
interface IConnectionProps extends IUseReqraftWebSocketOptions {
|
|
207
|
+
onPanelClose?: () => void;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const ConnectionOne = ({ onPanelClose, ...args }: IConnectionProps) => {
|
|
211
|
+
const { status, open, close, send, messages, clear, on, addMessage } = useReqraftWebSocket({
|
|
212
|
+
...args,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (status === 'OPEN') {
|
|
217
|
+
on('message', () => {
|
|
218
|
+
addMessage('I HAVE JUST RECEIVED A MESSAGE HA!');
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}, [status]);
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<ReqorePanel
|
|
225
|
+
minimal
|
|
226
|
+
fluid
|
|
227
|
+
onClose={onPanelClose}
|
|
228
|
+
closeButtonProps={{
|
|
229
|
+
className: 'close-button',
|
|
230
|
+
}}
|
|
231
|
+
size='small'
|
|
232
|
+
label={`First Connection Status: ${status}`}
|
|
233
|
+
actions={[
|
|
234
|
+
{ label: 'Connect', icon: 'PlayLine', onClick: open },
|
|
235
|
+
{ label: 'Disconnect', icon: 'StopLine', onClick: close },
|
|
236
|
+
{ label: 'Clear', icon: 'CloseLine', onClick: clear },
|
|
237
|
+
{ label: 'Kill', icon: 'CloseLine', onClick: () => send('kill') },
|
|
238
|
+
{ label: 'Send', icon: 'MessageLine', onClick: () => send('This is a test message') },
|
|
239
|
+
]}
|
|
240
|
+
>
|
|
241
|
+
{args.includeLogMessagesInState || args.useState ?
|
|
242
|
+
<ReqoreControlGroup vertical>
|
|
243
|
+
{messages.map((message, index) => (
|
|
244
|
+
<ReqoreP key={index}>{message}</ReqoreP>
|
|
245
|
+
))}
|
|
246
|
+
</ReqoreControlGroup>
|
|
247
|
+
: null}
|
|
248
|
+
</ReqorePanel>
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const ConnectionTwo = ({ onPanelClose, ...args }: IConnectionProps) => {
|
|
253
|
+
const { status, open, close, send, messages, clear, on, addMessage } = useReqraftWebSocket(args);
|
|
254
|
+
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (status === 'OPEN') {
|
|
257
|
+
on('close', () => {
|
|
258
|
+
addMessage('Why did you close it?!');
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}, [status]);
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<ReqorePanel
|
|
265
|
+
minimal
|
|
266
|
+
fluid
|
|
267
|
+
size='small'
|
|
268
|
+
onClose={onPanelClose}
|
|
269
|
+
closeButtonProps={{
|
|
270
|
+
className: 'close-button',
|
|
271
|
+
}}
|
|
272
|
+
label={`Second Connection Status: ${status}`}
|
|
273
|
+
actions={[
|
|
274
|
+
{ label: 'Connect', icon: 'PlayLine', onClick: open },
|
|
275
|
+
{ label: 'Disconnect', icon: 'StopLine', onClick: close },
|
|
276
|
+
{ label: 'Clear', icon: 'CloseLine', onClick: clear },
|
|
277
|
+
{ label: 'Kill', icon: 'CloseLine', onClick: () => send('kill') },
|
|
278
|
+
{ label: 'Send', icon: 'MessageLine', onClick: () => send('This is a test message') },
|
|
279
|
+
]}
|
|
280
|
+
>
|
|
281
|
+
{args.includeLogMessagesInState || args.useState ?
|
|
282
|
+
<ReqoreControlGroup vertical>
|
|
283
|
+
{messages.map((message, index) => (
|
|
284
|
+
<ReqoreP key={index}>{message}</ReqoreP>
|
|
285
|
+
))}
|
|
286
|
+
</ReqoreControlGroup>
|
|
287
|
+
: null}
|
|
288
|
+
</ReqorePanel>
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const ConnectionThree = ({ onPanelClose, ...args }: IConnectionProps) => {
|
|
293
|
+
const { status, open, close, send, messages, clear, on, addMessage } = useReqraftWebSocket({
|
|
294
|
+
...args,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
if (status === 'OPEN') {
|
|
299
|
+
on('message', () => {
|
|
300
|
+
addMessage('I ALSO HAVE A CUSTOM HANDLER FOR MESSAGES!');
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}, [status]);
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<ReqorePanel
|
|
307
|
+
minimal
|
|
308
|
+
fluid
|
|
309
|
+
intent={
|
|
310
|
+
status === 'CLOSED' ? 'danger'
|
|
311
|
+
: status === 'CONNECTING' ?
|
|
312
|
+
'pending'
|
|
313
|
+
: ('success' as TReqoreIntent)
|
|
314
|
+
}
|
|
315
|
+
size='small'
|
|
316
|
+
closeButtonProps={{
|
|
317
|
+
className: 'close-button',
|
|
318
|
+
}}
|
|
319
|
+
onClose={onPanelClose}
|
|
320
|
+
label={`Third Connection Status: ${status}`}
|
|
321
|
+
actions={[
|
|
322
|
+
{ label: 'Connect', icon: 'PlayLine', onClick: open },
|
|
323
|
+
{ label: 'Disconnect', icon: 'StopLine', onClick: close },
|
|
324
|
+
{ label: 'Clear', icon: 'CloseLine', onClick: clear },
|
|
325
|
+
{ label: 'Kill', icon: 'CloseLine', onClick: () => send('kill') },
|
|
326
|
+
{ label: 'Send', icon: 'MessageLine', onClick: () => send('This is a test message') },
|
|
327
|
+
]}
|
|
328
|
+
>
|
|
329
|
+
{args.includeLogMessagesInState || args.useState ?
|
|
330
|
+
<ReqoreControlGroup vertical>
|
|
331
|
+
{messages.map((message, index) => (
|
|
332
|
+
<ReqoreP key={index}>{message}</ReqoreP>
|
|
333
|
+
))}
|
|
334
|
+
</ReqoreControlGroup>
|
|
335
|
+
: null}
|
|
336
|
+
</ReqorePanel>
|
|
337
|
+
);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export const MultipleConnections: Story = {
|
|
341
|
+
args: {
|
|
342
|
+
includeLogMessagesInState: true,
|
|
343
|
+
useState: true,
|
|
344
|
+
},
|
|
345
|
+
// @ts-expect-error customprops
|
|
346
|
+
render: (args: IUseReqraftWebSocketOptions) => {
|
|
347
|
+
const [conectionStatus, setConnectionStatus] = useState<string>('CLOSED');
|
|
348
|
+
const [panels, setPanels] = useState({ 1: true, 2: true, 3: true });
|
|
349
|
+
|
|
350
|
+
useMount(() => {
|
|
351
|
+
setConnectionStatus(
|
|
352
|
+
ReqraftWebSocketsManager.connections[args.url]?.socket ? 'OPEN' : 'CLOSED'
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
setTimeout(() => {
|
|
358
|
+
setConnectionStatus(
|
|
359
|
+
ReqraftWebSocketsManager.connections[args.url]?.socket ? 'OPEN' : 'CLOSED'
|
|
360
|
+
);
|
|
361
|
+
}, 500);
|
|
362
|
+
}, [panels]);
|
|
363
|
+
|
|
364
|
+
const handlePanelClose = (panel: number) => {
|
|
365
|
+
setPanels((prev) => ({ ...prev, [panel]: false }));
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<ReqorePanel
|
|
370
|
+
minimal
|
|
371
|
+
flat
|
|
372
|
+
size='small'
|
|
373
|
+
label={`Multiple Connections: ${conectionStatus}`}
|
|
374
|
+
actions={[
|
|
375
|
+
{
|
|
376
|
+
label: 'Close All',
|
|
377
|
+
onClick: () => ReqraftWebSocketsManager.connections[args.url].socket.close(),
|
|
378
|
+
},
|
|
379
|
+
]}
|
|
380
|
+
>
|
|
381
|
+
<ReqoreControlGroup vertical>
|
|
382
|
+
{panels[1] && <ConnectionOne {...args} onPanelClose={() => handlePanelClose(1)} />}
|
|
383
|
+
{panels[2] && <ConnectionTwo {...args} onPanelClose={() => handlePanelClose(2)} />}
|
|
384
|
+
{panels[3] && <ConnectionThree {...args} onPanelClose={() => handlePanelClose(3)} />}
|
|
385
|
+
</ReqoreControlGroup>
|
|
386
|
+
</ReqorePanel>
|
|
387
|
+
);
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const MultipleConnectionsOpenOnMount: Story = {
|
|
392
|
+
...MultipleConnections,
|
|
393
|
+
args: {
|
|
394
|
+
...MultipleConnections.args,
|
|
395
|
+
openOnMount: true,
|
|
396
|
+
},
|
|
397
|
+
play: async ({ args }) => {
|
|
398
|
+
await testsWaitForText('First Connection Status: OPEN');
|
|
399
|
+
await testsWaitForText('Second Connection Status: OPEN');
|
|
400
|
+
await testsWaitForText('Third Connection Status: OPEN');
|
|
401
|
+
|
|
402
|
+
await expect(args.onOpen).toHaveBeenCalled();
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
export const MultipleConnectionsClosedAtOnce: Story = {
|
|
407
|
+
...MultipleConnections,
|
|
408
|
+
args: {
|
|
409
|
+
...MultipleConnections.args,
|
|
410
|
+
openOnMount: true,
|
|
411
|
+
},
|
|
412
|
+
play: async (args) => {
|
|
413
|
+
await MultipleConnectionsOpenOnMount.play(args);
|
|
414
|
+
|
|
415
|
+
await testsClickButton({ label: 'Close All' });
|
|
416
|
+
|
|
417
|
+
await testsWaitForText('Multiple Connections: CLOSED');
|
|
418
|
+
await testsWaitForText('Why did you close it?!');
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
export const ConnectionIsClosedWhenAllUsersAreClosed: Story = {
|
|
423
|
+
...MultipleConnections,
|
|
424
|
+
args: {
|
|
425
|
+
...MultipleConnections.args,
|
|
426
|
+
openOnMount: true,
|
|
427
|
+
},
|
|
428
|
+
play: async (args) => {
|
|
429
|
+
await MultipleConnectionsOpenOnMount.play(args);
|
|
430
|
+
|
|
431
|
+
await testsClickButton({ selector: '.close-button' });
|
|
432
|
+
await testsWaitForText('Multiple Connections: OPEN');
|
|
433
|
+
await testsClickButton({ selector: '.close-button' });
|
|
434
|
+
await testsWaitForText('Multiple Connections: OPEN');
|
|
435
|
+
await testsClickButton({ selector: '.close-button' });
|
|
436
|
+
await testsWaitForText('Multiple Connections: CLOSED');
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
export const MultipleConnectionsHaveCustomHandlers: Story = {
|
|
441
|
+
...MultipleConnections,
|
|
442
|
+
args: {
|
|
443
|
+
...MultipleConnections.args,
|
|
444
|
+
openOnMount: true,
|
|
445
|
+
},
|
|
446
|
+
play: async (args) => {
|
|
447
|
+
const canvas = within(args.canvasElement);
|
|
448
|
+
|
|
449
|
+
await MultipleConnectionsOpenOnMount.play(args);
|
|
450
|
+
|
|
451
|
+
await testsClickButton({ label: 'Send' });
|
|
452
|
+
|
|
453
|
+
await testsWaitForText('I HAVE JUST RECEIVED A MESSAGE HA!');
|
|
454
|
+
await testsWaitForText('I ALSO HAVE A CUSTOM HANDLER FOR MESSAGES!');
|
|
455
|
+
|
|
456
|
+
// Disconnect the 3rd connection
|
|
457
|
+
await testsClickButton({ label: 'Disconnect', nth: 2 });
|
|
458
|
+
await testsClickButton({ label: 'Send' });
|
|
459
|
+
await testsClickButton({ label: 'Send' });
|
|
460
|
+
|
|
461
|
+
await sleep(500);
|
|
462
|
+
|
|
463
|
+
await expect(canvas.queryAllByText('I HAVE JUST RECEIVED A MESSAGE HA!')).toHaveLength(3);
|
|
464
|
+
await expect(canvas.queryAllByText('I ALSO HAVE A CUSTOM HANDLER FOR MESSAGES!')).toHaveLength(
|
|
465
|
+
1
|
|
466
|
+
);
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
export const MultipleConnectionsCanBeDisconnectedAndReconnected: Story = {
|
|
471
|
+
...MultipleConnectionsHaveCustomHandlers,
|
|
472
|
+
play: async (args) => {
|
|
473
|
+
await MultipleConnectionsHaveCustomHandlers.play(args);
|
|
474
|
+
|
|
475
|
+
await testsClickButton({ label: 'Disconnect', nth: 1 });
|
|
476
|
+
await testsClickButton({ label: 'Connect', nth: 2 });
|
|
477
|
+
|
|
478
|
+
await testsWaitForText('Second Connection Status: CLOSED');
|
|
479
|
+
await testsWaitForText('Third Connection Status: OPEN');
|
|
480
|
+
},
|
|
481
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -9,9 +9,11 @@ export {
|
|
|
9
9
|
|
|
10
10
|
export { IReqraftUseFetch, useFetch } from './hooks/useFetch/useFetch';
|
|
11
11
|
export { TReqraftUseStorage, useReqraftStorage } from './hooks/useStorage/useStorage';
|
|
12
|
+
export * from './hooks/useWebSocket/useWebSocket';
|
|
12
13
|
export {
|
|
13
14
|
ReqraftProvider,
|
|
14
15
|
ReqraftQueryClient,
|
|
15
16
|
initializeReqraft,
|
|
16
17
|
} from './providers/ReqraftProvider';
|
|
17
18
|
export { query } from './utils/fetch';
|
|
19
|
+
export * from './utils/websocket';
|