blob: e6e246bcc0dd5bcb4223e6f06a4b4244772af2c6 [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 */
Issam E. Maghni0432cb72022-11-12 06:09:26 +000018import { WebSocketCallbacks, 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
Issam E. Maghni0432cb72022-11-12 06:09:26 +000025export interface IWebSocketContext {
26 bind: <T extends WebSocketMessageType>(type: T, callback: (data: WebSocketMessageTable[T]) => void) => void;
27 send: <T extends WebSocketMessageType>(type: T, data: WebSocketMessageTable[T]) => void;
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000028}
29
30export const WebSocketContext = createContext<IWebSocketContext | undefined>(undefined);
31
32export default ({ children }: WithChildren) => {
33 const [isConnected, setIsConnected] = useState(false);
34 const webSocketRef = useRef<WebSocket>();
Issam E. Maghni0432cb72022-11-12 06:09:26 +000035 const callbacksRef = useRef<WebSocketCallbacks>({
36 [WebSocketMessageType.ConversationMessage]: [],
37 [WebSocketMessageType.ConversationView]: [],
38 [WebSocketMessageType.WebRTCOffer]: [],
39 [WebSocketMessageType.WebRTCAnswer]: [],
40 [WebSocketMessageType.IceCandidate]: [],
41 });
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000042
43 const { token: accessToken } = useAuthContext();
44
Issam E. Maghni0432cb72022-11-12 06:09:26 +000045 const context: IWebSocketContext = {
46 bind: useCallback((type, callback) => {
47 callbacksRef.current[type].push(callback);
48 }, []),
49 send: useCallback(
50 (type, data) => {
51 if (isConnected) {
52 webSocketRef.current?.send(JSON.stringify({ type, data }));
53 }
54 },
55 [isConnected]
56 ),
57 };
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000058
Issam E. Maghni0432cb72022-11-12 06:09:26 +000059 const connect = useCallback(() => {
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +000060 const url = new URL(apiUrl);
61 url.protocol = 'ws:';
62 url.searchParams.set('accessToken', accessToken);
63
64 const webSocket = new WebSocket(url);
Issam E. Maghni0432cb72022-11-12 06:09:26 +000065
66 webSocket.onopen = () => {
67 console.debug('WebSocket connected');
68 setIsConnected(true);
69 };
70
71 webSocket.onclose = () => {
72 console.debug('WebSocket disconnected');
73 setIsConnected(false);
74 for (const callbacks of Object.values(callbacksRef.current)) {
75 callbacks.length = 0;
76 }
77 setTimeout(connect, 1000);
78 };
79
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050080 webSocket.onmessage = <T extends WebSocketMessageType>(event: MessageEvent<string>) => {
81 const messageString = event.data;
82 console.debug('WebSocket received message', messageString);
83
84 const message: WebSocketMessage<T> = JSON.parse(messageString);
Issam E. Maghni0432cb72022-11-12 06:09:26 +000085 if (!message.type || !message.data) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050086 console.warn('WebSocket message is not a valid WebSocketMessage (missing type or data fields)');
Issam E. Maghni0432cb72022-11-12 06:09:26 +000087 return;
88 }
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050089
Issam E. Maghni0432cb72022-11-12 06:09:26 +000090 if (!Object.values(WebSocketMessageType).includes(message.type)) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050091 console.warn(`Invalid WebSocket message type: ${message.type}`);
Issam E. Maghni0432cb72022-11-12 06:09:26 +000092 return;
93 }
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050094
Issam E. Maghni0432cb72022-11-12 06:09:26 +000095 const callbacks = callbacksRef.current[message.type];
96 for (const callback of callbacks) {
97 callback(message.data);
98 }
99 };
100
101 webSocket.onerror = (event: Event) => {
102 console.error('Closing WebSocket due to an error:', event);
103 webSocketRef.current?.close();
104 };
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +0000105
106 webSocketRef.current = webSocket;
107
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000108 return () => {
109 switch (webSocket.readyState) {
110 case webSocket.CONNECTING:
111 webSocket.onopen = () => webSocket.close();
112 break;
113 case webSocket.OPEN:
114 webSocket.close();
115 break;
116 }
117 };
118 }, [accessToken]);
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +0000119
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000120 useEffect(connect, [connect]);
121
122 return <WebSocketContext.Provider value={isConnected ? context : undefined}>{children}</WebSocketContext.Provider>;
Issam E. Maghni09a3a1f2022-11-02 04:56:21 +0000123};