blob: 8c530c6fef65c91dfdc06de10a3ac8e3abb8b097 [file] [log] [blame]
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +00001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050018import { WebSocketMessage, WebSocketMessageTable, WebSocketMessageType } from 'jami-web-common';
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000019import { createContext, useCallback, useEffect, useRef, useState } from 'react';
20
21import { apiUrl } from '../utils/constants';
22import { WithChildren } from '../utils/utils';
23import { useAuthContext } from './AuthProvider';
24
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050025type WebSocketCallback<T extends WebSocketMessageType> = (data: WebSocketMessageTable[T]) => void;
26
27type WebSocketCallbacks = {
28 [key in WebSocketMessageType]: Set<WebSocketCallback<key>>;
29};
30
31const buildWebSocketCallbacks = (): WebSocketCallbacks => {
32 const webSocketCallback = {} as WebSocketCallbacks;
33 for (const messageType of Object.values(WebSocketMessageType)) {
34 webSocketCallback[messageType] = new Set<WebSocketCallback<typeof messageType>>();
35 }
36 return webSocketCallback;
37};
38
simona5c54ef2022-11-18 05:26:06 -050039type BindFunction = <T extends WebSocketMessageType>(
40 type: T,
41 callback: (data: WebSocketMessageTable[T]) => void
42) => void;
43type SendFunction = <T extends WebSocketMessageType>(type: T, data: WebSocketMessageTable[T]) => void;
44
Issam E. Maghni0432cb72022-11-12 06:09:26 +000045export interface IWebSocketContext {
simona5c54ef2022-11-18 05:26:06 -050046 bind: BindFunction;
47 unbind: BindFunction;
48 send: SendFunction;
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000049}
50
51export const WebSocketContext = createContext<IWebSocketContext | undefined>(undefined);
52
53export default ({ children }: WithChildren) => {
54 const [isConnected, setIsConnected] = useState(false);
55 const webSocketRef = useRef<WebSocket>();
simona5c54ef2022-11-18 05:26:06 -050056 const callbacksRef = useRef<WebSocketCallbacks>(buildWebSocketCallbacks());
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000057
58 const { token: accessToken } = useAuthContext();
59
simona5c54ef2022-11-18 05:26:06 -050060 const bind: BindFunction = useCallback((type, callback) => {
61 const callbacks = callbacksRef.current[type];
62 callbacks.add(callback);
63 }, []);
64
65 const unbind: BindFunction = useCallback((type, callback) => {
66 const callbacks = callbacksRef.current[type];
67 callbacks.delete(callback);
68 }, []);
69
70 const send: SendFunction = useCallback(
71 (type, data) => {
72 if (isConnected) {
73 webSocketRef.current?.send(JSON.stringify({ type, data }));
74 }
75 },
76 [isConnected]
77 );
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000078
Issam E. Maghni0432cb72022-11-12 06:09:26 +000079 const connect = useCallback(() => {
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000080 const url = new URL(apiUrl);
81 url.protocol = 'ws:';
82 url.searchParams.set('accessToken', accessToken);
83
84 const webSocket = new WebSocket(url);
Issam E. Maghni0432cb72022-11-12 06:09:26 +000085
86 webSocket.onopen = () => {
87 console.debug('WebSocket connected');
88 setIsConnected(true);
89 };
90
91 webSocket.onclose = () => {
92 console.debug('WebSocket disconnected');
93 setIsConnected(false);
94 for (const callbacks of Object.values(callbacksRef.current)) {
simona5c54ef2022-11-18 05:26:06 -050095 callbacks.clear();
Issam E. Maghni0432cb72022-11-12 06:09:26 +000096 }
97 setTimeout(connect, 1000);
98 };
99
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500100 webSocket.onmessage = <T extends WebSocketMessageType>(event: MessageEvent<string>) => {
101 const messageString = event.data;
102 console.debug('WebSocket received message', messageString);
103
104 const message: WebSocketMessage<T> = JSON.parse(messageString);
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000105 if (!message.type || !message.data) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500106 console.warn('WebSocket message is not a valid WebSocketMessage (missing type or data fields)');
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000107 return;
108 }
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500109
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000110 if (!Object.values(WebSocketMessageType).includes(message.type)) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500111 console.warn(`Invalid WebSocket message type: ${message.type}`);
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000112 return;
113 }
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500114
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000115 const callbacks = callbacksRef.current[message.type];
116 for (const callback of callbacks) {
117 callback(message.data);
118 }
119 };
120
121 webSocket.onerror = (event: Event) => {
122 console.error('Closing WebSocket due to an error:', event);
123 webSocketRef.current?.close();
124 };
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +0000125
126 webSocketRef.current = webSocket;
127
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000128 return () => {
129 switch (webSocket.readyState) {
130 case webSocket.CONNECTING:
131 webSocket.onopen = () => webSocket.close();
132 break;
133 case webSocket.OPEN:
134 webSocket.close();
135 break;
136 }
137 };
138 }, [accessToken]);
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +0000139
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000140 useEffect(connect, [connect]);
141
simonf929a362022-11-18 16:53:45 -0500142 const value: IWebSocketContext | undefined = isConnected
143 ? {
144 bind,
145 unbind,
146 send,
simona5c54ef2022-11-18 05:26:06 -0500147 }
simonf929a362022-11-18 16:53:45 -0500148 : undefined;
149
150 return <WebSocketContext.Provider value={value}>{children}</WebSocketContext.Provider>;
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +0000151};