blob: 86b794f2a34cdcebf0e1d734311d47655c2cd328 [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 */
18import { WebSocketMessage, WebSocketMessageType } from 'jami-web-common';
19import { createContext, useCallback, useEffect, useRef, useState } from 'react';
20
21import { apiUrl } from '../utils/constants';
22import { WithChildren } from '../utils/utils';
23import { useAuthContext } from './AuthProvider';
24
25export type WebSocketMessageFn = (message: WebSocketMessage) => void;
26
27interface IWebSocketContext {
28 bind: (type: WebSocketMessageType, callback: WebSocketMessageFn) => void;
29 send: WebSocketMessageFn;
30}
31
32export const WebSocketContext = createContext<IWebSocketContext | undefined>(undefined);
33
34export default ({ children }: WithChildren) => {
35 const [isConnected, setIsConnected] = useState(false);
36 const webSocketRef = useRef<WebSocket>();
37 const callbacksRef = useRef(new Map<WebSocketMessageType, WebSocketMessageFn[]>());
38
39 const { token: accessToken } = useAuthContext();
40
41 const bind = useCallback((type: WebSocketMessageType, messageCallback: WebSocketMessageFn) => {
42 const messageCallbacks = callbacksRef.current.get(type);
43 if (messageCallbacks) {
44 messageCallbacks.push(messageCallback);
45 } else {
46 callbacksRef.current.set(type, [messageCallback]);
47 }
48 }, []);
49
50 const send = useCallback(
51 (message: WebSocketMessage) => {
52 if (isConnected) {
53 webSocketRef.current?.send(JSON.stringify(message));
54 }
55 },
56 [isConnected]
57 );
58
59 const handleOnOpen = useCallback(() => setIsConnected(true), []);
60
61 const handleOnClose = useCallback(() => {
62 setIsConnected(false);
63 callbacksRef.current.clear();
64 }, []);
65
66 const handleOnMessage = useCallback(({ data }: MessageEvent<string>) => {
67 const message: WebSocketMessage = JSON.parse(data);
68 const messageCallbacks = callbacksRef.current.get(message.type);
69 if (messageCallbacks) {
70 for (const messageCallback of messageCallbacks) {
71 messageCallback(message);
72 }
73 } else {
74 console.warn(`Unhandled message of type ${message.type}`);
75 }
76 }, []);
77
78 const handleOnError = useCallback((event: Event) => {
79 console.error('Closing WebSocket due to an error:', event);
80 webSocketRef.current?.close();
81 }, []);
82
83 useEffect(() => {
84 const url = new URL(apiUrl);
85 url.protocol = 'ws:';
86 url.searchParams.set('accessToken', accessToken);
87
88 const webSocket = new WebSocket(url);
89 webSocket.onopen = handleOnOpen;
90 webSocket.onclose = handleOnClose;
91 webSocket.onmessage = handleOnMessage;
92 webSocket.onerror = handleOnError;
93
94 webSocketRef.current = webSocket;
95
96 return () => webSocket.close();
97 }, [accessToken, handleOnOpen, handleOnClose, handleOnMessage, handleOnError]);
98
99 return isConnected ? (
100 <WebSocketContext.Provider value={{ bind, send }}>{children}</WebSocketContext.Provider>
101 ) : (
102 <>{children}</>
103 );
104};