Send received message through WebSocket
- send received message from through WebSocket
- remove SocketIO from client
GitLab: #96
Change-Id: I7a8eec04010f0773428f914792c13decef393ebf
diff --git a/client/src/contexts/Socket.tsx b/client/src/contexts/Socket.tsx
deleted file mode 100644
index e397a43..0000000
--- a/client/src/contexts/Socket.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2022 Savoir-faire Linux Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program. If not, see
- * <https://www.gnu.org/licenses/>.
- */
-import { createContext, PropsWithChildren } from 'react';
-import { Socket } from 'socket.io-client';
-
-type ISocketContext = Socket;
-export const SocketContext = createContext<ISocketContext | undefined>(undefined);
-
-type SocketProviderProps = PropsWithChildren<{
- socket: Socket;
-}>;
-export const SocketProvider = ({ socket, children }: SocketProviderProps) => (
- <SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
-);
diff --git a/client/src/contexts/WebRTCProvider.tsx b/client/src/contexts/WebRTCProvider.tsx
index 86950f7..eb52e6b 100644
--- a/client/src/contexts/WebRTCProvider.tsx
+++ b/client/src/contexts/WebRTCProvider.tsx
@@ -16,7 +16,7 @@
* <https://www.gnu.org/licenses/>.
*/
-import { WebRTCIceCandidate, WebRTCSDP, WebSocketMessage, WebSocketMessageType } from 'jami-web-common';
+import { WebSocketMessageType } from 'jami-web-common';
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { WithChildren } from '../utils/utils';
@@ -73,7 +73,7 @@
const contactId = _contactId;
const [webRTCConnection, setWebRTCConnection] = useState<RTCPeerConnection | undefined>();
const localStreamRef = useRef<MediaStream>();
- const socket = useContext(WebSocketContext);
+ const webSocket = useContext(WebSocketContext);
useEffect(() => {
if (!webRTCConnection) {
@@ -118,16 +118,13 @@
}
const icecandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
- if (event.candidate && socket) {
+ if (event.candidate && webSocket) {
console.log('webRTCConnection : onicecandidate');
- socket.send({
- type: WebSocketMessageType.IceCandidate,
- data: {
- from: account.getId(),
- to: contactId,
- message: {
- candidate: event.candidate,
- },
+ webSocket.send(WebSocketMessageType.IceCandidate, {
+ from: account.getId(),
+ to: contactId,
+ message: {
+ candidate: event.candidate,
},
});
}
@@ -148,52 +145,41 @@
webRTCConnection.removeEventListener('icecandidate', icecandidateEventListener);
webRTCConnection.removeEventListener('track', trackEventListener);
};
- }, [webRTCConnection, isVideoOn, isAudioOn, socket, contactId, account]);
+ }, [webRTCConnection, isVideoOn, isAudioOn, webSocket, contactId, account]);
useEffect(() => {
- if (!webRTCConnection || !socket) {
+ if (!webRTCConnection || !webSocket) {
return;
}
- const sendWebRTCAnswer = async (message: WebSocketMessage) => {
- if (webRTCConnection && socket) {
- const remoteSdp: RTCSessionDescriptionInit = (message.data.message as WebRTCSDP).sdp;
- await webRTCConnection.setRemoteDescription(new RTCSessionDescription(remoteSdp));
+ webSocket.bind(WebSocketMessageType.WebRTCOffer, async (data) => {
+ if (webRTCConnection) {
+ await webRTCConnection.setRemoteDescription(new RTCSessionDescription(data.message.sdp));
const mySdp = await webRTCConnection.createAnswer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
});
await webRTCConnection.setLocalDescription(new RTCSessionDescription(mySdp));
- socket.send({
- type: WebSocketMessageType.WebRTCAnswer,
- data: {
- from: account.getId(),
- to: contactId,
- message: {
- sdp: mySdp,
- },
+ webSocket.send(WebSocketMessageType.WebRTCAnswer, {
+ from: account.getId(),
+ to: contactId,
+ message: {
+ sdp: mySdp,
},
});
- console.log('get offer and aswering');
}
- };
+ });
- const handleWebRTCAnswer = async (message: WebSocketMessage) => {
- const remoteSdp: RTCSessionDescriptionInit = (message.data.message as WebRTCSDP).sdp;
- await webRTCConnection.setRemoteDescription(new RTCSessionDescription(remoteSdp));
+ webSocket.bind(WebSocketMessageType.WebRTCAnswer, async (data) => {
+ await webRTCConnection.setRemoteDescription(new RTCSessionDescription(data.message.sdp));
console.log('get answer');
- };
+ });
- const addIceCandidate = async (message: WebSocketMessage) => {
- const candidate: RTCIceCandidateInit = (message.data.message as WebRTCIceCandidate).candidate;
- await webRTCConnection.addIceCandidate(new RTCIceCandidate(candidate));
+ webSocket.bind(WebSocketMessageType.IceCandidate, async (data) => {
+ await webRTCConnection.addIceCandidate(new RTCIceCandidate(data.message.candidate));
console.log('webRTCConnection : candidate add success');
- };
-
- socket.bind(WebSocketMessageType.WebRTCOffer, sendWebRTCAnswer);
- socket.bind(WebSocketMessageType.WebRTCAnswer, handleWebRTCAnswer);
- socket.bind(WebSocketMessageType.IceCandidate, addIceCandidate);
- }, [account, contactId, socket, webRTCConnection]);
+ });
+ }, [account, contactId, webSocket, webRTCConnection]);
const setAudioStatus = useCallback((isOn: boolean) => {
setIsAudioOn(isOn);
@@ -210,24 +196,21 @@
}, []);
const sendWebRTCOffer = useCallback(async () => {
- if (webRTCConnection && socket) {
+ if (webRTCConnection && webSocket) {
const sdp = await webRTCConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
});
- socket.send({
- type: WebSocketMessageType.WebRTCOffer,
- data: {
- from: account.getId(),
- to: contactId,
- message: {
- sdp,
- },
+ webSocket.send(WebSocketMessageType.WebRTCOffer, {
+ from: account.getId(),
+ to: contactId,
+ message: {
+ sdp,
},
});
await webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
}
- }, [account, contactId, socket, webRTCConnection]);
+ }, [account, contactId, webSocket, webRTCConnection]);
return (
<WebRTCContext.Provider
diff --git a/client/src/contexts/WebSocketProvider.tsx b/client/src/contexts/WebSocketProvider.tsx
index 86b794f..38317e2 100644
--- a/client/src/contexts/WebSocketProvider.tsx
+++ b/client/src/contexts/WebSocketProvider.tsx
@@ -15,18 +15,16 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { WebSocketMessage, WebSocketMessageType } from 'jami-web-common';
+import { WebSocketCallbacks, WebSocketMessage, WebSocketMessageTable, WebSocketMessageType } from 'jami-web-common';
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
import { apiUrl } from '../utils/constants';
import { WithChildren } from '../utils/utils';
import { useAuthContext } from './AuthProvider';
-export type WebSocketMessageFn = (message: WebSocketMessage) => void;
-
-interface IWebSocketContext {
- bind: (type: WebSocketMessageType, callback: WebSocketMessageFn) => void;
- send: WebSocketMessageFn;
+export interface IWebSocketContext {
+ bind: <T extends WebSocketMessageType>(type: T, callback: (data: WebSocketMessageTable[T]) => void) => void;
+ send: <T extends WebSocketMessageType>(type: T, data: WebSocketMessageTable[T]) => void;
}
export const WebSocketContext = createContext<IWebSocketContext | undefined>(undefined);
@@ -34,71 +32,88 @@
export default ({ children }: WithChildren) => {
const [isConnected, setIsConnected] = useState(false);
const webSocketRef = useRef<WebSocket>();
- const callbacksRef = useRef(new Map<WebSocketMessageType, WebSocketMessageFn[]>());
+ const callbacksRef = useRef<WebSocketCallbacks>({
+ [WebSocketMessageType.ConversationMessage]: [],
+ [WebSocketMessageType.ConversationView]: [],
+ [WebSocketMessageType.WebRTCOffer]: [],
+ [WebSocketMessageType.WebRTCAnswer]: [],
+ [WebSocketMessageType.IceCandidate]: [],
+ });
const { token: accessToken } = useAuthContext();
- const bind = useCallback((type: WebSocketMessageType, messageCallback: WebSocketMessageFn) => {
- const messageCallbacks = callbacksRef.current.get(type);
- if (messageCallbacks) {
- messageCallbacks.push(messageCallback);
- } else {
- callbacksRef.current.set(type, [messageCallback]);
- }
- }, []);
+ const context: IWebSocketContext = {
+ bind: useCallback((type, callback) => {
+ callbacksRef.current[type].push(callback);
+ }, []),
+ send: useCallback(
+ (type, data) => {
+ if (isConnected) {
+ webSocketRef.current?.send(JSON.stringify({ type, data }));
+ }
+ },
+ [isConnected]
+ ),
+ };
- const send = useCallback(
- (message: WebSocketMessage) => {
- if (isConnected) {
- webSocketRef.current?.send(JSON.stringify(message));
- }
- },
- [isConnected]
- );
-
- const handleOnOpen = useCallback(() => setIsConnected(true), []);
-
- const handleOnClose = useCallback(() => {
- setIsConnected(false);
- callbacksRef.current.clear();
- }, []);
-
- const handleOnMessage = useCallback(({ data }: MessageEvent<string>) => {
- const message: WebSocketMessage = JSON.parse(data);
- const messageCallbacks = callbacksRef.current.get(message.type);
- if (messageCallbacks) {
- for (const messageCallback of messageCallbacks) {
- messageCallback(message);
- }
- } else {
- console.warn(`Unhandled message of type ${message.type}`);
- }
- }, []);
-
- const handleOnError = useCallback((event: Event) => {
- console.error('Closing WebSocket due to an error:', event);
- webSocketRef.current?.close();
- }, []);
-
- useEffect(() => {
+ const connect = useCallback(() => {
const url = new URL(apiUrl);
url.protocol = 'ws:';
url.searchParams.set('accessToken', accessToken);
const webSocket = new WebSocket(url);
- webSocket.onopen = handleOnOpen;
- webSocket.onclose = handleOnClose;
- webSocket.onmessage = handleOnMessage;
- webSocket.onerror = handleOnError;
+
+ webSocket.onopen = () => {
+ console.debug('WebSocket connected');
+ setIsConnected(true);
+ };
+
+ webSocket.onclose = () => {
+ console.debug('WebSocket disconnected');
+ setIsConnected(false);
+ for (const callbacks of Object.values(callbacksRef.current)) {
+ callbacks.length = 0;
+ }
+ setTimeout(connect, 1000);
+ };
+
+ webSocket.onmessage = <T extends WebSocketMessageType>({ data }: MessageEvent<string>) => {
+ console.debug('WebSocket received message', data);
+ const message: WebSocketMessage<T> = JSON.parse(data);
+ if (!message.type || !message.data) {
+ console.warn(`Incorrect format (require type and data) ${message}`);
+ return;
+ }
+ if (!Object.values(WebSocketMessageType).includes(message.type)) {
+ console.warn(`Unhandled message of type: ${message.type}`);
+ return;
+ }
+ const callbacks = callbacksRef.current[message.type];
+ for (const callback of callbacks) {
+ callback(message.data);
+ }
+ };
+
+ webSocket.onerror = (event: Event) => {
+ console.error('Closing WebSocket due to an error:', event);
+ webSocketRef.current?.close();
+ };
webSocketRef.current = webSocket;
- return () => webSocket.close();
- }, [accessToken, handleOnOpen, handleOnClose, handleOnMessage, handleOnError]);
+ return () => {
+ switch (webSocket.readyState) {
+ case webSocket.CONNECTING:
+ webSocket.onopen = () => webSocket.close();
+ break;
+ case webSocket.OPEN:
+ webSocket.close();
+ break;
+ }
+ };
+ }, [accessToken]);
- return isConnected ? (
- <WebSocketContext.Provider value={{ bind, send }}>{children}</WebSocketContext.Provider>
- ) : (
- <>{children}</>
- );
+ useEffect(connect, [connect]);
+
+ return <WebSocketContext.Provider value={isConnected ? context : undefined}>{children}</WebSocketContext.Provider>;
};