Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 1 | /* |
| 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. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 18 | import { WebSocketCallbacks, WebSocketMessage, WebSocketMessageTable, WebSocketMessageType } from 'jami-web-common'; |
Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 19 | import { createContext, useCallback, useEffect, useRef, useState } from 'react'; |
| 20 | |
| 21 | import { apiUrl } from '../utils/constants'; |
| 22 | import { WithChildren } from '../utils/utils'; |
| 23 | import { useAuthContext } from './AuthProvider'; |
| 24 | |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 25 | export 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. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 28 | } |
| 29 | |
| 30 | export const WebSocketContext = createContext<IWebSocketContext | undefined>(undefined); |
| 31 | |
| 32 | export default ({ children }: WithChildren) => { |
| 33 | const [isConnected, setIsConnected] = useState(false); |
| 34 | const webSocketRef = useRef<WebSocket>(); |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 35 | const callbacksRef = useRef<WebSocketCallbacks>({ |
| 36 | [WebSocketMessageType.ConversationMessage]: [], |
| 37 | [WebSocketMessageType.ConversationView]: [], |
| 38 | [WebSocketMessageType.WebRTCOffer]: [], |
| 39 | [WebSocketMessageType.WebRTCAnswer]: [], |
| 40 | [WebSocketMessageType.IceCandidate]: [], |
| 41 | }); |
Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 42 | |
| 43 | const { token: accessToken } = useAuthContext(); |
| 44 | |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 45 | 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. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 58 | |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 59 | const connect = useCallback(() => { |
Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 60 | const url = new URL(apiUrl); |
| 61 | url.protocol = 'ws:'; |
| 62 | url.searchParams.set('accessToken', accessToken); |
| 63 | |
| 64 | const webSocket = new WebSocket(url); |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 65 | |
| 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 | |
| 80 | webSocket.onmessage = <T extends WebSocketMessageType>({ data }: MessageEvent<string>) => { |
| 81 | console.debug('WebSocket received message', data); |
| 82 | const message: WebSocketMessage<T> = JSON.parse(data); |
| 83 | if (!message.type || !message.data) { |
| 84 | console.warn(`Incorrect format (require type and data) ${message}`); |
| 85 | return; |
| 86 | } |
| 87 | if (!Object.values(WebSocketMessageType).includes(message.type)) { |
| 88 | console.warn(`Unhandled message of type: ${message.type}`); |
| 89 | return; |
| 90 | } |
| 91 | const callbacks = callbacksRef.current[message.type]; |
| 92 | for (const callback of callbacks) { |
| 93 | callback(message.data); |
| 94 | } |
| 95 | }; |
| 96 | |
| 97 | webSocket.onerror = (event: Event) => { |
| 98 | console.error('Closing WebSocket due to an error:', event); |
| 99 | webSocketRef.current?.close(); |
| 100 | }; |
Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 101 | |
| 102 | webSocketRef.current = webSocket; |
| 103 | |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 104 | return () => { |
| 105 | switch (webSocket.readyState) { |
| 106 | case webSocket.CONNECTING: |
| 107 | webSocket.onopen = () => webSocket.close(); |
| 108 | break; |
| 109 | case webSocket.OPEN: |
| 110 | webSocket.close(); |
| 111 | break; |
| 112 | } |
| 113 | }; |
| 114 | }, [accessToken]); |
Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 115 | |
Issam E. Maghni | 0432cb7 | 2022-11-12 06:09:26 +0000 | [diff] [blame] | 116 | useEffect(connect, [connect]); |
| 117 | |
| 118 | return <WebSocketContext.Provider value={isConnected ? context : undefined}>{children}</WebSocketContext.Provider>; |
Issam E. Maghni | 09a3a1f | 2022-11-02 04:56:21 +0000 | [diff] [blame] | 119 | }; |