From d20f0c161fe46c7db4cb5c3ac478260a7d3a9ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 01:36:11 +0100 Subject: [PATCH 01/10] Handle client reconnects better --- app/src/RoomClient.js | 29 +++++++++++ server/lib/Room.js | 115 ++++++++++++++++++++++++++++-------------- server/package.json | 4 +- server/server.js | 4 +- 4 files changed, 111 insertions(+), 41 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index e133a30..204643d 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1520,6 +1520,28 @@ export default class RoomClient }) })); + if (this._screenSharingProducer) + this._screenSharingProducer.close(); + + if (this._webcamProducer) + this._webcamProducer.close(); + + if (this._micProducer) + this._micProducer.close(); + + // Close mediasoup Transports. + if (this._sendTransport) + { + this._sendTransport.close(); + this._sendTransport = null; + } + + if (this._recvTransport) + { + this._recvTransport.close(); + this._recvTransport = null; + } + store.dispatch(roomActions.setRoomState('connecting')); }); @@ -1721,6 +1743,13 @@ export default class RoomClient break; } + + case 'roomBack': + { + await this._joinRoom({ joinVideo }); + + break; + } case 'lockRoom': { diff --git a/server/lib/Room.js b/server/lib/Room.js index f0ceb66..b6ee85b 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -2,6 +2,8 @@ const EventEmitter = require('events').EventEmitter; const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); +const { v4: uuidv4 } = require('uuid'); +const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); const config = require('../config/config'); @@ -46,6 +48,8 @@ class Room extends EventEmitter super(); this.setMaxListeners(Infinity); + this._uuid = uuidv4(); + // Room ID. this._roomId = roomId; @@ -120,9 +124,26 @@ class Room extends EventEmitter this.emit('close'); } - handlePeer(peer) + handlePeer({ peer, token }) { - logger.info('handlePeer() [peer:"%s", roles:"%s"]', peer.id, peer.roles); + logger.info('handlePeer() [peer:"%s", roles:"%s", token:"%s"]', peer.id, peer.roles, token); + + let verifiedPeer = false; + + if (token) + { + try + { + const decoded = jwt.verify(token, this._uuid); + + if (decoded.id === peer.id) + verifiedPeer = true; + } + catch (err) + { + logger.warn('handlePeer() | invalid token'); + } + } // Allow reconnections, remove old peer if (this._peers[peer.id]) @@ -134,8 +155,11 @@ class Room extends EventEmitter this._peers[peer.id].close(); } + // Returning user + if (verifiedPeer) + this._peerJoining(peer, true); // Always let ADMIN in, even if locked - if (peer.roles.includes(userRoles.ADMIN)) + else if (peer.roles.includes(userRoles.ADMIN)) this._peerJoining(peer); else if (this._locked) this._parkPeer(peer); @@ -332,7 +356,7 @@ class Room extends EventEmitter } } - async _peerJoining(peer) + async _peerJoining(peer, returning = false) { peer.socket.join(this._roomId); @@ -343,45 +367,58 @@ class Room extends EventEmitter this._handlePeer(peer); - let turnServers; - - if ('turnAPIURI' in config) + if (returning) { - try - { - const { data } = await axios.get( - config.turnAPIURI, - { - params : { - 'uri_schema' : 'turn', - 'transport' : 'tcp', - 'ip_ver' : 'ipv4', - 'servercount' : '2', - 'api_key' : config.turnAPIKey, - 'ip' : peer.socket.request.connection.remoteAddress - } - }); - - turnServers = [ { - urls : data.uris, - username : data.username, - credential : data.password - } ]; - } - catch (error) - { - if ('backupTurnServers' in config) - turnServers = config.backupTurnServers; - - logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); - } + this._notification(peer.socket, 'roomBack'); } - else if ('backupTurnServers' in config) + else { - turnServers = config.backupTurnServers; - } + const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); - this._notification(peer.socket, 'roomReady', { turnServers }); + peer.socket.handshake.session.token = token; + + peer.socket.handshake.session.save(); + + let turnServers; + + if ('turnAPIURI' in config) + { + try + { + const { data } = await axios.get( + config.turnAPIURI, + { + params : { + 'uri_schema' : 'turn', + 'transport' : 'tcp', + 'ip_ver' : 'ipv4', + 'servercount' : '2', + 'api_key' : config.turnAPIKey, + 'ip' : peer.socket.request.connection.remoteAddress + } + }); + + turnServers = [ { + urls : data.uris, + username : data.username, + credential : data.password + } ]; + } + catch (error) + { + if ('backupTurnServers' in config) + turnServers = config.backupTurnServers; + + logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); + } + } + else if ('backupTurnServers' in config) + { + turnServers = config.backupTurnServers; + } + + this._notification(peer.socket, 'roomReady', { turnServers }); + } } _handlePeer(peer) diff --git a/server/package.json b/server/package.json index b05676d..ae1f3b9 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,7 @@ "express-socket.io-session": "^1.3.5", "helmet": "^3.21.2", "ims-lti": "^3.0.2", + "jsonwebtoken": "^8.5.1", "mediasoup": "^3.5.5", "openid-client": "^3.7.3", "passport": "^0.4.0", @@ -32,6 +33,7 @@ "pidusage": "^2.0.17", "redis": "^2.8.0", "socket.io": "^2.3.0", - "spdy": "^4.0.1" + "spdy": "^4.0.1", + "uuid": "^7.0.2" } } diff --git a/server/server.js b/server/server.js index ebf951e..2e50a8c 100755 --- a/server/server.js +++ b/server/server.js @@ -467,6 +467,8 @@ async function runWebSocketServer() const room = await getOrCreateRoom({ roomId }); const peer = new Peer({ id: peerId, roomId, socket }); + const { token } = socket.handshake.session; + peers.set(peerId, peer); peer.on('close', () => peers.delete(peerId)); @@ -495,7 +497,7 @@ async function runWebSocketServer() } } - room.handlePeer(peer); + room.handlePeer({ peer, token }); }) .catch((error) => { From 3043098f0c42ad727e7e743b80d9893d265fabda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 10:38:51 +0100 Subject: [PATCH 02/10] Remove jwt, not needed --- server/lib/Room.js | 27 ++++----------------------- server/package.json | 1 - server/server.js | 25 ++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index b6ee85b..4950123 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -3,7 +3,6 @@ const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); const { v4: uuidv4 } = require('uuid'); -const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); const config = require('../config/config'); @@ -128,31 +127,15 @@ class Room extends EventEmitter { logger.info('handlePeer() [peer:"%s", roles:"%s", token:"%s"]', peer.id, peer.roles, token); - let verifiedPeer = false; + // This peer is returning, reconnect + const verifiedPeer = token && token === this._uuid; - if (token) - { - try - { - const decoded = jwt.verify(token, this._uuid); - - if (decoded.id === peer.id) - verifiedPeer = true; - } - catch (err) - { - logger.warn('handlePeer() | invalid token'); - } - } - - // Allow reconnections, remove old peer + // Should not happen if (this._peers[peer.id]) { logger.warn( 'handleConnection() | there is already a peer with same peerId [peer:"%s"]', peer.id); - - this._peers[peer.id].close(); } // Returning user @@ -373,9 +356,7 @@ class Room extends EventEmitter } else { - const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); - - peer.socket.handshake.session.token = token; + peer.socket.handshake.session.token = this._uuid; peer.socket.handshake.session.save(); diff --git a/server/package.json b/server/package.json index ae1f3b9..50a6831 100644 --- a/server/package.json +++ b/server/package.json @@ -25,7 +25,6 @@ "express-socket.io-session": "^1.3.5", "helmet": "^3.21.2", "ims-lti": "^3.0.2", - "jsonwebtoken": "^8.5.1", "mediasoup": "^3.5.5", "openid-client": "^3.7.3", "passport": "^0.4.0", diff --git a/server/server.js b/server/server.js index 2e50a8c..5e19248 100755 --- a/server/server.js +++ b/server/server.js @@ -464,11 +464,30 @@ async function runWebSocketServer() queue.push(async () => { - const room = await getOrCreateRoom({ roomId }); - const peer = new Peer({ id: peerId, roomId, socket }); - const { token } = socket.handshake.session; + const room = await getOrCreateRoom({ roomId }); + + let peer = peers.get(peerId); + + if (peer) + { + if (token) + { + peer.close(); + + peer = null; + } + else + { + socket.disconnect(true); + + return; + } + } + + peer = new Peer({ id: peerId, roomId, socket }); + peers.set(peerId, peer); peer.on('close', () => peers.delete(peerId)); From 2e68a7d891e4bd9c081e19aaee876b2baca23ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 21:17:03 +0100 Subject: [PATCH 03/10] Bug in Room close --- server/lib/Room.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 2ff3759..18875e9 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -107,8 +107,8 @@ class Room extends EventEmitter // Close the peers. for (const peer in this._peers) { - if (!peer.closed) - peer.close(); + if (!this._peers[peer].closed) + this._peers[peer].close(); } this._peers = null; From 34cdac8102c0ddfef471e1152f0a01f5c0754446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 21:49:38 +0100 Subject: [PATCH 04/10] Bug in Lobby close() --- server/lib/Lobby.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js index 75cae49..d4bbb37 100644 --- a/server/lib/Lobby.js +++ b/server/lib/Lobby.js @@ -26,8 +26,8 @@ class Lobby extends EventEmitter // Close the peers. for (const peer in this._peers) { - if (!peer.closed) - peer.close(); + if (!this._peers[peer].closed) + this._peers[peer].close(); } this._peers = null; From a1dc652d0498716f0636a560a99399537401fb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 27 Mar 2020 22:57:04 +0100 Subject: [PATCH 05/10] Linting and translations --- app/package.json | 3 +- app/src/RoomClient.js | 27 +++++--- app/src/actions/consumerActions.js | 10 ++- app/src/actions/meActions.js | 12 ++-- app/src/actions/peerActions.js | 1 - app/src/components/ChooseRoom.js | 3 +- app/src/components/Containers/Me.js | 61 +++++++++---------- app/src/components/Controls/TopBar.js | 18 +++--- app/src/components/JoinDialog.js | 3 +- .../MeetingDrawer/ParticipantList/ListPeer.js | 36 +++++------ app/src/components/MeetingViews/Filmstrip.js | 34 +++++------ app/src/components/Room.js | 6 +- app/src/components/Settings/Settings.js | 22 +++---- .../VideoContainers/FullScreenView.js | 4 +- app/src/reducers/settings.js | 2 +- app/src/translations/cn.json | 3 + app/src/translations/cz.json | 6 ++ app/src/translations/de.json | 3 + app/src/translations/dk.json | 3 + app/src/translations/el.json | 3 + app/src/translations/en.json | 3 + app/src/translations/es.json | 3 + app/src/translations/fr.json | 3 + app/src/translations/hr.json | 3 + app/src/translations/hu.json | 3 + app/src/translations/it.json | 6 ++ app/src/translations/nb.json | 3 + app/src/translations/pl.json | 3 + app/src/translations/pt.json | 3 + app/src/translations/ro.json | 3 + 30 files changed, 176 insertions(+), 117 deletions(-) diff --git a/app/package.json b/app/package.json index 89a116e..b58d66a 100644 --- a/app/package.json +++ b/app/package.json @@ -46,7 +46,8 @@ "test": "react-scripts test", "eject": "react-scripts eject", "electron": "electron --no-sandbox .", - "dev": "nf start -p 3000" + "dev": "nf start -p 3000", + "lint": "eslint -c .eslintrc.json --ext .js src" }, "browserslist": [ ">0.2%", diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index e6f9bc8..bb896c4 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -231,7 +231,7 @@ export default class RoomClient this._hark = null; // Local MediaStream for hark - this._harkStream = null + this._harkStream = null; // Local webcam mediasoup Producer. this._webcamProducer = null; @@ -1162,21 +1162,30 @@ export default class RoomClient ...VIDEO_CONSTRAINS[resolution] } }); - if (stream){ + + if (stream) + { const track = stream.getVideoTracks()[0]; - if (track) { + + if (track) + { await this._webcamProducer.replaceTrack({ track }); store.dispatch( producerActions.setProducerTrack(this._webcamProducer.id, track)); - } else { - logger.warn('getVideoTracks Error: First Video Track is null') + } + else + { + logger.warn('getVideoTracks Error: First Video Track is null'); } - } else { - logger.warn ('getUserMedia Error: Stream is null!') } + else + { + logger.warn('getUserMedia Error: Stream is null!'); + } + store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId)); await this._updateWebcams(); @@ -2340,7 +2349,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined, + iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined, proprietaryConstraints : PC_PROPRIETARY_CONSTRAINTS }); @@ -2402,7 +2411,7 @@ export default class RoomClient iceParameters, iceCandidates, dtlsParameters, - iceServers : this._turnServers, + iceServers : this._turnServers, // TODO: Fix for issue #72 iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined }); diff --git a/app/src/actions/consumerActions.js b/app/src/actions/consumerActions.js index b8460a6..249d156 100644 --- a/app/src/actions/consumerActions.js +++ b/app/src/actions/consumerActions.js @@ -35,12 +35,10 @@ export const setConsumerPreferredLayers = (consumerId, spatialLayer, temporalLay }); export const setConsumerPriority = (consumerId, priority) => - { - return { - type : 'SET_CONSUMER_PRIORITY', - payload : { consumerId, priority } - }; - }; + ({ + type : 'SET_CONSUMER_PRIORITY', + payload : { consumerId, priority } + }); export const setConsumerTrack = (consumerId, track) => ({ diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index 6970a3f..fc72592 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -34,11 +34,11 @@ export const setPicture = (picture) => }); export const setMediaCapabilities = ({ - canSendMic, - canSendWebcam, - canShareScreen, - canShareFiles - }) => + canSendMic, + canSendWebcam, + canShareScreen, + canShareFiles +}) => ({ type : 'SET_MEDIA_CAPABILITIES', payload : { canSendMic, canSendWebcam, canShareScreen, canShareFiles } @@ -92,7 +92,7 @@ export const setDisplayNameInProgress = (flag) => payload : { flag } }); - export const setIsSpeaking = (flag) => +export const setIsSpeaking = (flag) => ({ type : 'SET_IS_SPEAKING', payload : { flag } diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index 1a87151..dc41568 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -46,7 +46,6 @@ export const setPeerPicture = (peerId, picture) => payload : { peerId, picture } }); - export const addPeerRole = (peerId, role) => ({ type : 'ADD_PEER_ROLE', diff --git a/app/src/components/ChooseRoom.js b/app/src/components/ChooseRoom.js index 31ff420..b81ff47 100644 --- a/app/src/components/ChooseRoom.js +++ b/app/src/components/ChooseRoom.js @@ -178,7 +178,8 @@ const ChooseRoom = ({ + })} + > }, controls : { - position : 'absolute', - width : '100%', - height : '100%', - backgroundColor : 'rgba(0, 0, 0, 0.3)', - display : 'flex', - flexDirection : 'column', - justifyContent : 'center', - alignItems : 'flex-end', - padding : theme.spacing(1), - zIndex : 21, - opacity : 0, - transition : 'opacity 0.3s', - touchAction : 'none', - pointerEvents : 'none', - '&.hover' : + position : 'absolute', + width : '100%', + height : '100%', + display : 'flex', + flexDirection : 'column', + justifyContent : 'center', + alignItems : 'flex-end', + padding : theme.spacing(1), + zIndex : 21, + touchAction : 'none', + pointerEvents : 'none', + '& p' : { - opacity : 1 - }, - '& p' : - { - position : 'absolute', - float : 'left', - top : '50%', - left : '50%', - transform : 'translate(-50%, -50%)', - color : 'rgba(255, 255, 255, 0.5)', - fontSize : '7em', - margin : 0 + position : 'absolute', + float : 'left', + top : '50%', + left : '50%', + opacity : 0, + transition : 'opacity 0.3s', + transform : 'translate(-50%, -50%)', + color : 'rgba(255, 255, 255, 0.5)', + fontSize : '7em', + margin : 0, + '&.hover' : + { + opacity : 1 + } } }, ptt : @@ -112,8 +111,8 @@ const styles = (theme) => padding : '15px', borderRadius : '20px', textAlign : 'center', - opacity : 0, - '&.enabled' : + opacity : 0, + '&.enabled' : { opacity : 1 } @@ -317,13 +316,13 @@ const Me = (props) => + />

diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 6ef8dae..50a20c0 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -47,7 +47,7 @@ const styles = (theme) => }, divider : { - marginLeft : theme.spacing(3), + marginLeft : theme.spacing(3) }, show : { @@ -364,7 +364,7 @@ TopBar.propTypes = room : appPropTypes.Room.isRequired, peersLength : PropTypes.number, lobbyPeers : PropTypes.array, - permanentTopBar : PropTypes.bool, + permanentTopBar : PropTypes.bool, myPicture : PropTypes.string, loggedIn : PropTypes.bool.isRequired, loginEnabled : PropTypes.bool.isRequired, @@ -383,14 +383,14 @@ TopBar.propTypes = const mapStateToProps = (state) => ({ - room : state.room, - peersLength : peersLengthSelector(state), - lobbyPeers : lobbyPeersKeySelector(state), + room : state.room, + peersLength : peersLengthSelector(state), + lobbyPeers : lobbyPeersKeySelector(state), permanentTopBar : state.settings.permanentTopBar, - loggedIn : state.me.loggedIn, - loginEnabled : state.me.loginEnabled, - myPicture : state.me.picture, - unread : state.toolarea.unreadMessages + + loggedIn : state.me.loggedIn, + loginEnabled : state.me.loginEnabled, + myPicture : state.me.picture, + unread : state.toolarea.unreadMessages + state.toolarea.unreadFiles }); diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index d6af3fe..814a18d 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -339,7 +339,8 @@ const JoinDialog = ({ + })} + > id : 'tooltip.muteScreenSharing', defaultMessage : 'Mute participant share' })} - color={ screenVisible ? 'primary' : 'secondary'} - disabled={ peer.peerScreenInProgress } + color={screenVisible ? 'primary' : 'secondary'} + disabled={peer.peerScreenInProgress} onClick={() => - { - screenVisible ? - roomClient.modifyPeerConsumer(peer.id, 'screen', true) : - roomClient.modifyPeerConsumer(peer.id, 'screen', false); - }} + { + screenVisible ? + roomClient.modifyPeerConsumer(peer.id, 'screen', true) : + roomClient.modifyPeerConsumer(peer.id, 'screen', false); + }} > { screenVisible ? @@ -172,14 +172,14 @@ const ListPeer = (props) => id : 'tooltip.muteParticipant', defaultMessage : 'Mute participant' })} - color={ micEnabled ? 'primary' : 'secondary'} - disabled={ peer.peerAudioInProgress } + color={micEnabled ? 'primary' : 'secondary'} + disabled={peer.peerAudioInProgress} onClick={() => - { - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} + { + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} > { micEnabled ? @@ -193,11 +193,11 @@ const ListPeer = (props) => id : 'tooltip.kickParticipant', defaultMessage : 'Kick out participant' })} - disabled={ peer.peerKickInProgress } + disabled={peer.peerKickInProgress} onClick={() => - { - roomClient.kickPeer(peer.id); - }} + { + roomClient.kickPeer(peer.id); + }} > diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js index 503948e..2ed11c6 100644 --- a/app/src/components/MeetingViews/Filmstrip.js +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -287,28 +287,28 @@ class Filmstrip extends React.PureComponent } Filmstrip.propTypes = { - roomClient : PropTypes.any.isRequired, - activeSpeakerId : PropTypes.string, - advancedMode : PropTypes.bool, - peers : PropTypes.object.isRequired, - consumers : PropTypes.object.isRequired, - myId : PropTypes.string.isRequired, - selectedPeerId : PropTypes.string, - spotlights : PropTypes.array.isRequired, - boxes : PropTypes.number, - classes : PropTypes.object.isRequired + roomClient : PropTypes.any.isRequired, + activeSpeakerId : PropTypes.string, + advancedMode : PropTypes.bool, + peers : PropTypes.object.isRequired, + consumers : PropTypes.object.isRequired, + myId : PropTypes.string.isRequired, + selectedPeerId : PropTypes.string, + spotlights : PropTypes.array.isRequired, + boxes : PropTypes.number, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => { return { - activeSpeakerId : state.room.activeSpeakerId, - selectedPeerId : state.room.selectedPeerId, - peers : state.peers, - consumers : state.consumers, - myId : state.me.id, - spotlights : state.room.spotlights, - boxes : videoBoxesSelector(state) + activeSpeakerId : state.room.activeSpeakerId, + selectedPeerId : state.room.selectedPeerId, + peers : state.peers, + consumers : state.consumers, + myId : state.me.id, + spotlights : state.room.spotlights, + boxes : videoBoxesSelector(state) }; }; diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 1d6a180..aef3987 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -159,13 +159,13 @@ class Room extends React.PureComponent } > diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 4817efe..91ba0db 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -353,15 +353,15 @@ const Settings = ({ Settings.propTypes = { - roomClient : PropTypes.any.isRequired, - me : appPropTypes.Me.isRequired, - room : appPropTypes.Room.isRequired, - settings : PropTypes.object.isRequired, - onToggleAdvancedMode : PropTypes.func.isRequired, + roomClient : PropTypes.any.isRequired, + me : appPropTypes.Me.isRequired, + room : appPropTypes.Room.isRequired, + settings : PropTypes.object.isRequired, + onToggleAdvancedMode : PropTypes.func.isRequired, onTogglePermanentTopBar : PropTypes.func.isRequired, - handleChangeMode : PropTypes.func.isRequired, - handleCloseSettings : PropTypes.func.isRequired, - classes : PropTypes.object.isRequired + handleChangeMode : PropTypes.func.isRequired, + handleCloseSettings : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => @@ -374,10 +374,10 @@ const mapStateToProps = (state) => }; const mapDispatchToProps = { - onToggleAdvancedMode : settingsActions.toggleAdvancedMode, + onToggleAdvancedMode : settingsActions.toggleAdvancedMode, onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, - handleChangeMode : roomActions.setDisplayMode, - handleCloseSettings : roomActions.setSettingsOpen + handleChangeMode : roomActions.setDisplayMode, + handleCloseSettings : roomActions.setSettingsOpen }; export default withRoomContext(connect( diff --git a/app/src/components/VideoContainers/FullScreenView.js b/app/src/components/VideoContainers/FullScreenView.js index 5549712..05d1484 100644 --- a/app/src/components/VideoContainers/FullScreenView.js +++ b/app/src/components/VideoContainers/FullScreenView.js @@ -135,7 +135,7 @@ FullScreenView.propTypes = consumer : appPropTypes.Consumer, toggleConsumerFullscreen : PropTypes.func.isRequired, toolbarsVisible : PropTypes.bool, - permanentTopBar : PropTypes.bool, + permanentTopBar : PropTypes.bool, classes : PropTypes.object.isRequired }; @@ -143,7 +143,7 @@ const mapStateToProps = (state) => ({ consumer : state.consumers[state.room.fullScreenConsumer], toolbarsVisible : state.room.toolbarsVisible, - permanentTopBar : state.settings.permanentTopBar + permanentTopBar : state.settings.permanentTopBar }); const mapDispatchToProps = (dispatch) => diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 4b3dde4..21d59db 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -6,7 +6,7 @@ const initialState = advancedMode : false, resolution : 'medium', // low, medium, high, veryhigh, ultra lastN : 4, - permanentTopBar : true + permanentTopBar : true }; const settings = (state = initialState, action) => diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index cb439e5..572d84e 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "登录", "tooltip.logout": "注销", diff --git a/app/src/translations/cz.json b/app/src/translations/cz.json index 82ed962..1be5e7b 100644 --- a/app/src/translations/cz.json +++ b/app/src/translations/cz.json @@ -48,6 +48,12 @@ "room.spotlights": "Aktivní Účastníci", "room.passive": "Pasivní Účastníci", "room.videoPaused": "Toto video bylo pozastaveno", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Přihlášení", "tooltip.logout": "Odhlášení", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index d78e5bd..8aa3a4e 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Anmelden", "tooltip.logout": "Abmelden", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 87be182..1eccd1f 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Log ind", "tooltip.logout": "Log ud", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 1c028df..9125308 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Σύνδεση", "tooltip.logout": "Αποσύνδεση", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index b7248f5..55a80dd 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -52,6 +52,9 @@ "room.muteAll": "Mute all", "room.stopAllVideo": "Stop all video", "room.closeMeeting": "Close meeting", + "room.speechUnsupported": "Your browser does not support speech recognition", + + "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", "tooltip.login": "Log in", "tooltip.logout": "Log out", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 4325509..ef27cdc 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Entrar", "tooltip.logout": "Salir", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 87643b5..d057178 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Connexion", "tooltip.logout": "Déconnexion", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 5e97b0c..5f0d6a6 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Prijava", "tooltip.logout": "Odjava", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 9c4399a..c7dedf1 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Belépés", "tooltip.logout": "Kilépés", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 2234260..4529279 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -49,6 +49,12 @@ "room.spotlights": "Partecipanti in Evidenza", "room.passive": "Participanti Passivi", "room.videoPaused": "Il video è in pausa", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Log in", "tooltip.logout": "Log out", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 6c81dea..1841857 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -52,6 +52,9 @@ "room.muteAll": "Demp alle", "room.stopAllVideo": "Stopp all video", "room.closeMeeting": "Avslutt møte", + "room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning", + + "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", "tooltip.login": "Logg in", "tooltip.logout": "Logg ut", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 662f878..c5415fb 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Zaloguj", "tooltip.logout": "Wyloguj", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 5f034e9..4c30812 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Entrar", "tooltip.logout": "Sair", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index a187665..0fae5de 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -52,6 +52,9 @@ "room.muteAll": null, "room.stopAllVideo": null, "room.closeMeeting": null, + "room.speechUnsupported": null, + + "me.mutedPTT": null, "tooltip.login": "Intră în cont", "tooltip.logout": "Deconectare", From de0cb996566659152a1fdc4524a16bae7f4ecc61 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Sat, 28 Mar 2020 00:09:49 +0100 Subject: [PATCH 06/10] small layout fixes; enable clientside auto mute for > 4 peers --- app/src/RoomClient.js | 6 ++++- app/src/components/Containers/Me.js | 35 +++++++++++++++-------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index e6f9bc8..96c9622 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2477,7 +2477,11 @@ export default class RoomClient { if (this._mediasoupDevice.canProduce('audio')) if (!this._muted) - this.enableMic(); + { + await this.enableMic(); + if (peers.length > 4) + this.muteMic(); + } if (joinVideo && this._mediasoupDevice.canProduce('video')) this.enableWebcam(); diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 433d34d..f96831e 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -78,31 +78,32 @@ const styles = (theme) => alignItems : 'flex-end', padding : theme.spacing(1), zIndex : 21, - opacity : 0, + opacity : 1, transition : 'opacity 0.3s', touchAction : 'none', pointerEvents : 'none', - '&.hover' : - { - opacity : 1 - }, '& p' : { - position : 'absolute', - float : 'left', - top : '50%', - left : '50%', - transform : 'translate(-50%, -50%)', - color : 'rgba(255, 255, 255, 0.5)', - fontSize : '7em', - margin : 0 + position : 'absolute', + float : 'left', + top : '50%', + left : '50%', + transform : 'translate(-50%, -50%)', + color : 'rgba(255, 255, 255, 0.5)', + fontSize : '7em', + margin : 0, + opacity : 0, + '&.hover' : + { + opacity : 1 + }, } }, ptt : { position : 'absolute', float : 'left', - bottom : '10%', + top : '10%', left : '50%', transform : 'translate(-50%, 0%)', color : 'rgba(255, 255, 255, 0.7)', @@ -112,7 +113,7 @@ const styles = (theme) => padding : '15px', borderRadius : '20px', textAlign : 'center', - opacity : 0, + opacity : 0, '&.enabled' : { opacity : 1 @@ -292,7 +293,7 @@ const Me = (props) => >
setHover(true)} onMouseOut={() => setHover(false)} onTouchStart={() => @@ -313,7 +314,7 @@ const Me = (props) => }, 2000); }} > -

+

Date: Sat, 28 Mar 2020 01:20:26 +0100 Subject: [PATCH 07/10] linting + effect for ptt --- app/src/components/Containers/Me.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index eeea591..3003f3d 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -82,7 +82,7 @@ const styles = (theme) => transition : 'opacity 0.3s', touchAction : 'none', pointerEvents : 'none', - '& p' : + '& p' : { position : 'absolute', float : 'left', @@ -93,10 +93,11 @@ const styles = (theme) => fontSize : '7em', margin : 0, opacity : 0, - '&.hover' : + transition : 'opacity 0.1s ease-in-out', + '&.hover' : { opacity : 1 - }, + } } }, ptt : @@ -114,9 +115,11 @@ const styles = (theme) => borderRadius : '20px', textAlign : 'center', opacity : 0, - '&.enabled' : + transition : 'opacity 1s ease', + '&.enabled' : { - opacity : 1 + transition : 'opacity 0.1s', + opacity : 1 } } }); From c521bb9ad15f28c5abd5573901a9379e8db77385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 28 Mar 2020 21:17:59 +0100 Subject: [PATCH 08/10] Firefox relay if turnservers configured, fixes #160 --- app/src/RoomClient.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index fac58da..81e4181 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2349,7 +2349,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined, + iceTransportPolicy : this._device.flag === 'firefox' && this._turnServers ? 'relay' : undefined, proprietaryConstraints : PC_PROPRIETARY_CONSTRAINTS }); @@ -2413,7 +2413,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined + iceTransportPolicy : this._device.flag === 'firefox' && this._turnServers ? 'relay' : undefined }); this._recvTransport.on( From 87d40375624c1b6559e5734a49aa7909d36ed6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sat, 28 Mar 2020 23:20:37 +0100 Subject: [PATCH 09/10] We need jwt to make sure no one can hijack peerId --- app/src/RoomClient.js | 24 +++++++++++++++++++++++- server/lib/Room.js | 30 ++++++++++++++++++++++++------ server/package.json | 1 + server/server.js | 25 ++++++++++++------------- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 204643d..d301b86 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1521,24 +1521,46 @@ export default class RoomClient })); if (this._screenSharingProducer) + { this._screenSharingProducer.close(); + store.dispatch( + producerActions.removeProducer(this._screenSharingProducer.id)); + + this._screenSharingProducer = null; + } + if (this._webcamProducer) + { this._webcamProducer.close(); + store.dispatch( + producerActions.removeProducer(this._webcamProducer.id)); + + this._webcamProducer = null; + } + if (this._micProducer) + { this._micProducer.close(); - // Close mediasoup Transports. + store.dispatch( + producerActions.removeProducer(this._micProducer.id)); + + this._micProducer = null; + } + if (this._sendTransport) { this._sendTransport.close(); + this._sendTransport = null; } if (this._recvTransport) { this._recvTransport.close(); + this._recvTransport = null; } diff --git a/server/lib/Room.js b/server/lib/Room.js index 4950123..521007d 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -3,6 +3,7 @@ const axios = require('axios'); const Logger = require('./Logger'); const Lobby = require('./Lobby'); const { v4: uuidv4 } = require('uuid'); +const jwt = require('jsonwebtoken'); const userRoles = require('../userRoles'); const config = require('../config/config'); @@ -123,12 +124,27 @@ class Room extends EventEmitter this.emit('close'); } - handlePeer({ peer, token }) + verifyPeer({ id, token }) { - logger.info('handlePeer() [peer:"%s", roles:"%s", token:"%s"]', peer.id, peer.roles, token); + try + { + const decoded = jwt.verify(token, this._uuid); - // This peer is returning, reconnect - const verifiedPeer = token && token === this._uuid; + logger.info('verifyPeer() [decoded:"%o"]', decoded); + + return decoded.id === id; + } + catch (err) + { + logger.warn('verifyPeer() | invalid token'); + } + + return false; + } + + handlePeer({ peer, returning }) + { + logger.info('handlePeer() [peer:"%s", roles:"%s", returning:"%s"]', peer.id, peer.roles, returning); // Should not happen if (this._peers[peer.id]) @@ -139,7 +155,7 @@ class Room extends EventEmitter } // Returning user - if (verifiedPeer) + if (returning) this._peerJoining(peer, true); // Always let ADMIN in, even if locked else if (peer.roles.includes(userRoles.ADMIN)) @@ -356,7 +372,9 @@ class Room extends EventEmitter } else { - peer.socket.handshake.session.token = this._uuid; + const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true }); + + peer.socket.handshake.session.token = token; peer.socket.handshake.session.save(); diff --git a/server/package.json b/server/package.json index 50a6831..ae1f3b9 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,7 @@ "express-socket.io-session": "^1.3.5", "helmet": "^3.21.2", "ims-lti": "^3.0.2", + "jsonwebtoken": "^8.5.1", "mediasoup": "^3.5.5", "openid-client": "^3.7.3", "passport": "^0.4.0", diff --git a/server/server.js b/server/server.js index 5e19248..c1c0f61 100755 --- a/server/server.js +++ b/server/server.js @@ -469,21 +469,20 @@ async function runWebSocketServer() const room = await getOrCreateRoom({ roomId }); let peer = peers.get(peerId); + let returning = false; - if (peer) - { - if (token) - { + if (peer && !token) + { // Don't allow hijacking sessions + socket.disconnect(true); + + return; + } + else if (token && room.verifyPeer({ id: peerId, token })) + { // Returning user, remove if old peer exists + if (peer) peer.close(); - peer = null; - } - else - { - socket.disconnect(true); - - return; - } + returning = true; } peer = new Peer({ id: peerId, roomId, socket }); @@ -516,7 +515,7 @@ async function runWebSocketServer() } } - room.handlePeer({ peer, token }); + room.handlePeer({ peer, returning }); }) .catch((error) => { From 9065996abd6f75f60640dacca797cc333b728fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Sat, 28 Mar 2020 21:43:25 +0100 Subject: [PATCH 10/10] Add trustProxy option to server config --- server/config/config.example.js | 6 ++++++ server/server.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/server/config/config.example.js b/server/config/config.example.js index 53b1f05..b33c04b 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -64,6 +64,12 @@ module.exports = // listeningRedirectPort disabled // use case: loadbalancer backend httpOnly : false, + // WebServer/Express trust proxy config for httpOnly mode + // You can find more info: + // - https://expressjs.com/en/guide/behind-proxies.html + // - https://www.npmjs.com/package/proxy-addr + // use case: loadbalancer backend + trustProxy : '', // This function will be called on successful login through oidc. // Use this function to map your oidc userinfo to the Peer object. // The roomId is equal to the room name. diff --git a/server/server.js b/server/server.js index c1c0f61..853cd47 100755 --- a/server/server.js +++ b/server/server.js @@ -100,6 +100,10 @@ const session = expressSession({ } }); +if (config.trustProxy) { + app.set('trust proxy', config.trustProxy); +} + app.use(session); passport.serializeUser((user, done) =>