From e674daaf1ab3277c60be984f2b71f09951476466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 20 Mar 2020 21:14:08 +0100 Subject: [PATCH 01/21] Add user roles for server. --- server/userRoles.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 server/userRoles.js diff --git a/server/userRoles.js b/server/userRoles.js new file mode 100644 index 0000000..ba5fd59 --- /dev/null +++ b/server/userRoles.js @@ -0,0 +1,12 @@ +module.exports = { + // Allowed to enter locked rooms + all other priveleges + ADMIN : 0, + // Allowed to enter restricted rooms if configured. + // Allowed to moderate users in a room (mute all, + // spotlight video, kick users) + MODERATOR : 1, + // Same as MODERATOR, but can't moderate users + AUTHENTICATED : 2, + // No priveleges + ALL : 3 +}; \ No newline at end of file From 587f185f09c6acd653afe442c9cf409bfbbb125c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 20 Mar 2020 21:14:43 +0100 Subject: [PATCH 02/21] Small fix for error in room reducer. --- app/src/reducers/room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 747d316..c4b29fb 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -110,7 +110,7 @@ const room = (state = initialState, action) => case 'TOGGLE_JOINED': { - const joined = !state.joined; + const joined = true; return { ...state, joined }; } From 743b9e48692792d3aa84d763258cd76c699feb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 20 Mar 2020 21:15:35 +0100 Subject: [PATCH 03/21] Syntax --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 8faa837..a5d68a9 100755 --- a/server/server.js +++ b/server/server.js @@ -190,7 +190,7 @@ function setupLTI(ltiConfig) } if (lti.lis_person_name_full) { - user.displayName=lti.lis_person_name_full; + user.displayName = lti.lis_person_name_full; } // Perform local authentication if necessary From c70740f5c702fdae5fd363d658e443bb359a0eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 20 Mar 2020 21:16:16 +0100 Subject: [PATCH 04/21] Add support for user roles --- server/config/config.example.js | 98 +++++++++++++++++++++++++++++---- server/lib/Lobby.js | 41 +++++++++----- server/lib/Peer.js | 85 ++++++++++++---------------- server/lib/Room.js | 51 ++++++++++------- server/server.js | 77 ++++++++++---------------- 5 files changed, 209 insertions(+), 143 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 740a9ae..ee225b1 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -1,4 +1,5 @@ const os = require('os'); +const userRoles = require('../userRoles'); module.exports = { @@ -50,18 +51,95 @@ module.exports = // listeningRedirectPort disabled // use case: loadbalancer backend httpOnly : false, - // If this is set to true, only signed-in users will be able - // to join a room directly. Non-signed-in users (guests) will - // always be put in the lobby regardless of room lock status. - // If false, there is no difference between guests and signed-in - // users when joining. - requireSignInToAccess : true, - // This flag has no effect when requireSignInToAccess is false + // This function will be called on successful login through oidc. + // Use this function to map your oidc userinfo to the Peer object, + // see examples below. + // Examples: + /* + // All authenicated users will be MODERATOR and AUTHENTICATED + userMapping : async ({ peer, userinfo }) => + { + peer.addRole(userRoles.MODERATOR); + peer.addRole(userRoles.AUTHENTICATED); + }, + // All authenicated users will be AUTHENTICATED, + // and those with the moderator role set in the userinfo + // will also be MODERATOR + userMapping : async ({ peer, userinfo }) => + { + if ( + Array.isArray(userinfo.meet_roles) && + userinfo.meet_roles.includes('moderator') + ) + { + peer.addRole(userRoles.MODERATOR); + } + + if ( + Array.isArray(userinfo.meet_roles) && + userinfo.meet_roles.includes('meetingadmin') + ) + { + peer.addRole(userRoles.ADMIN); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + // All authenicated users will be AUTHENTICATED, + // and those with email ending with @example.com + // will also be MODERATOR + userMapping : async ({ peer, userinfo }) => + { + if (userinfo.email && userinfo.email.endsWith('@example.com')) + { + peer.addRole(userRoles.MODERATOR); + } + + peer.addRole(userRoles.AUTHENTICATED); + },*/ + userMapping : async ({ peer, userinfo }) => + { + if (userinfo.picture != null) + { + if (!userinfo.picture.match(/^http/g)) + { + peer.picture = `data:image/jpeg;base64, ${userinfo.picture}`; + } + else + { + peer.picture = userinfo.picture; + } + } + + if (userinfo.nickname != null) + { + peer.displayName = userinfo.nickname; + } + + if (userinfo.name != null) + { + peer.displayName = userinfo.name; + } + + if (userinfo.email != null) + { + peer.email = userinfo.email; + } + }, + // Required roles for Access. All users have the role "ALL" by default. + // Other roles need to be added in the "userMapping" function. This + // is an Array of roles. userRoles.ADMIN have all priveleges and access + // always. + // + // Example: + // [ userRoles.MODERATOR, userRoles.AUTHENTICATED ] + // This will allow all MODERATOR and AUTHENTICATED users access. + requiredRolesForAccess : [ userRoles.ALL ], // When truthy, the room will be open to all users when the first - // authenticated user has already joined the room. - activateOnHostJoin : true, + // AUTHENTICATED or MODERATOR user joins the room. + activateOnHostJoin : true, // Mediasoup settings - mediasoup : + mediasoup : { numWorkers : Object.keys(os.cpus()).length, // mediasoup Worker settings. diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js index 75f4bf9..7372455 100644 --- a/server/lib/Lobby.js +++ b/server/lib/Lobby.js @@ -75,11 +75,15 @@ class Lobby extends EventEmitter if (peer) { peer.socket.removeListener('request', peer.socketRequestHandler); - peer.removeListener('authenticationChanged', peer.authenticationHandler); + peer.removeListener('rolesChange', peer.roleChangeHandler); + peer.removeListener('displayNameChanged', peer.displayNameChangeHandler); + peer.removeListener('pictureChanged', peer.pictureChangeHandler); peer.removeListener('close', peer.closeHandler); peer.socketRequestHandler = null; - peer.authenticationHandler = null; + peer.roleChangeHandler = null; + peer.displayNameChangeHandler = null; + peer.pictureChangeHandler = null; peer.closeHandler = null; this.emit('promotePeer', peer); @@ -112,16 +116,25 @@ class Lobby extends EventEmitter }); }; - peer.authenticationHandler = () => + peer.roleChangeHandler = () => { - logger.info('parkPeer() | authenticationChange [peer:"%s"]', peer.id); + logger.info('parkPeer() | rolesChange [peer:"%s"]', peer.id); - if (peer.authenticated) - { - this.emit('changeDisplayName', peer); - this.emit('changePicture', peer); - this.emit('peerAuthenticated', peer); - } + this.emit('peerRolesChanged', peer); + }; + + peer.displayNameChangeHandler = () => + { + logger.info('parkPeer() | displayNameChange [peer:"%s"]', peer.id); + + this.emit('changeDisplayName', peer); + }; + + peer.pictureChangeHandler = () => + { + logger.info('parkPeer() | pictureChange [peer:"%s"]', peer.id); + + this.emit('changePicture', peer); }; peer.closeHandler = () => @@ -143,7 +156,9 @@ class Lobby extends EventEmitter this._peers.set(peer.id, peer); - peer.on('authenticationChanged', peer.authenticationHandler); + peer.on('rolesChange', peer.roleChangeHandler); + peer.on('displayNameChanged', peer.displayNameChangeHandler); + peer.on('pictureChanged', peer.pictureChangeHandler); peer.socket.on('request', peer.socketRequestHandler); @@ -169,8 +184,6 @@ class Lobby extends EventEmitter peer.displayName = displayName; - this.emit('changeDisplayName', peer); - cb(); break; @@ -181,8 +194,6 @@ class Lobby extends EventEmitter peer.picture = picture; - this.emit('changePicture', peer); - cb(); break; diff --git a/server/lib/Peer.js b/server/lib/Peer.js index cce62aa..93ee9b8 100644 --- a/server/lib/Peer.js +++ b/server/lib/Peer.js @@ -1,4 +1,6 @@ const EventEmitter = require('events').EventEmitter; +const userRoles = require('../userRoles'); +const config = require('../config/config'); const Logger = require('./Logger'); const logger = new Logger('Peer'); @@ -22,7 +24,7 @@ class Peer extends EventEmitter this._inLobby = false; - this._authenticated = false; + this._roles = [ userRoles.ALL ]; this._displayName = false; @@ -40,8 +42,6 @@ class Peer extends EventEmitter this._consumers = new Map(); - this._checkAuthentication(); - this._handlePeer(); } @@ -66,13 +66,6 @@ class Peer extends EventEmitter _handlePeer() { - this.socket.use((packet, next) => - { - this._checkAuthentication(); - - return next(); - }); - this.socket.on('disconnect', () => { if (this.closed) @@ -84,33 +77,6 @@ class Peer extends EventEmitter }); } - _checkAuthentication() - { - if ( - Boolean(this.socket.handshake.session.passport) && - Boolean(this.socket.handshake.session.passport.user) - ) - { - const { - id, - displayName, - picture, - email - } = this.socket.handshake.session.passport.user; - - id && (this.authId = id); - displayName && (this.displayName = displayName); - picture && (this.picture = picture); - email && (this.email = email); - - this.authenticated = true; - } - else - { - this.authenticated = false; - } - } - get id() { return this._id; @@ -166,21 +132,9 @@ class Peer extends EventEmitter this._inLobby = inLobby; } - get authenticated() + get roles() { - return this._authenticated; - } - - set authenticated(authenticated) - { - if (authenticated !== this._authenticated) - { - const oldAuthenticated = this._authenticated; - - this._authenticated = authenticated; - - this.emit('authenticationChanged', { oldAuthenticated }); - } + return this._roles; } get displayName() @@ -262,6 +216,35 @@ class Peer extends EventEmitter return this._consumers; } + addRole(newRole) + { + if (!this._roles.includes(newRole)) + { + this._roles.push(newRole); + + logger.info('addRole() | [newRole:"%s]"', newRole); + + this.emit('rolesChange', { newRole }); + } + } + + removeRole(oldRole) + { + if (this._roles.includes(oldRole)) + { + this._roles = this._roles.filter((role) => role !== oldRole); + + logger.info('removeRole() | [oldRole:"%s]"', oldRole); + + this.emit('rolesChange', { oldRole }); + } + } + + hasRole(role) + { + return this._roles.includes(role); + } + addTransport(id, transport) { this.transports.set(id, transport); diff --git a/server/lib/Room.js b/server/lib/Room.js index f25f31d..140d4ee 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1,6 +1,7 @@ const EventEmitter = require('events').EventEmitter; const Logger = require('./Logger'); const Lobby = require('./Lobby'); +const userRoles = require('../userRoles'); const config = require('../config/config'); const logger = new Logger('Room'); @@ -117,9 +118,8 @@ class Room extends EventEmitter handlePeer(peer) { - logger.info('handlePeer() [peer:"%s"]', peer.id); + logger.info('handlePeer() [peer:"%s", roles:"%s"]', peer.id, peer.roles); - // This will allow reconnects to join despite lock if (this._peers[peer.id]) { logger.warn( @@ -130,13 +130,16 @@ class Room extends EventEmitter return; } + + // Always let ADMIN in, even if locked + if (peer.roles.includes(userRoles.ADMIN)) + this._peerJoining(peer); else if (this._locked) - { this._parkPeer(peer); - } else { - peer.authenticated ? + // If the user has a role in config.requiredRolesForAccess, let them in + peer.roles.some((role) => config.requiredRolesForAccess.includes(role)) ? this._peerJoining(peer) : this._handleGuest(peer); } @@ -144,21 +147,12 @@ class Room extends EventEmitter _handleGuest(peer) { - if (config.requireSignInToAccess) - { - if (config.activateOnHostJoin && !this.checkEmpty()) - { - this._peerJoining(peer); - } - else - { - this._parkPeer(peer); - this._notification(peer.socket, 'signInRequired'); - } - } + if (config.activateOnHostJoin && !this.checkEmpty()) + this._peerJoining(peer); else { - this._peerJoining(peer); + this._parkPeer(peer); + this._notification(peer.socket, 'signInRequired'); } } @@ -178,9 +172,26 @@ class Room extends EventEmitter } }); - this._lobby.on('peerAuthenticated', (peer) => + this._lobby.on('peerRolesChanged', (peer) => { - !this._locked && this._lobby.promotePeer(peer.id); + // Always let admin in, even if locked + if (peer.roles.includes(userRoles.ADMIN)) + { + this._lobby.promotePeer(peer.id); + + return; + } + + // If the user has a role in config.requiredRolesForAccess, let them in + if ( + !this._locked && + peer.roles.some((role) => config.requiredRolesForAccess.includes(role)) + ) + { + this._lobby.promotePeer(peer.id); + + return; + } }); this._lobby.on('changeDisplayName', (changedPeer) => diff --git a/server/server.js b/server/server.js index a5d68a9..43b888b 100755 --- a/server/server.js +++ b/server/server.js @@ -241,51 +241,6 @@ function setupOIDC(oidcIssuer) _claims : tokenset.claims }; - if (userinfo.picture != null) - { - if (!userinfo.picture.match(/^http/g)) - { - user.picture = `data:image/jpeg;base64, ${userinfo.picture}`; - } - else - { - user.picture = userinfo.picture; - } - } - - if (userinfo.nickname != null) - { - user.displayName = userinfo.nickname; - } - - if (userinfo.name != null) - { - user.displayName = userinfo.name; - } - - if (userinfo.email != null) - { - user.email = userinfo.email; - } - - if (userinfo.given_name != null) - { - user.name={}; - user.name.givenName = userinfo.given_name; - } - - if (userinfo.family_name != null) - { - if (user.name == null) user.name={}; - user.name.familyName = userinfo.family_name; - } - - if (userinfo.middle_name != null) - { - if (user.name == null) user.name={}; - user.name.middleName = userinfo.middle_name; - } - return done(null, user); } ); @@ -349,7 +304,7 @@ async function setupAuth() app.get( '/auth/callback', passport.authenticate('oidc', { failureRedirect: '/auth/login' }), - (req, res) => + async (req, res) => { const state = JSON.parse(base64.decode(req.query.state)); @@ -373,7 +328,11 @@ async function setupAuth() peer && (peer.displayName = displayName); peer && (peer.picture = picture); - peer && (peer.authenticated = true); + + if (peer && typeof config.userMapping === 'function') + { + await config.userMapping({ peer, userinfo: req.user._userinfo }); + } res.send(loginHelper({ displayName, @@ -501,6 +460,30 @@ async function runWebSocketServer() peer.on('close', () => peers.delete(peerId)); + if ( + Boolean(socket.handshake.session.passport) && + Boolean(socket.handshake.session.passport.user) + ) + { + const { + id, + displayName, + picture, + email, + _userinfo + } = socket.handshake.session.passport.user; + + peer.authId= id; + peer.displayName = displayName; + peer.picture = picture; + peer.email = email; + + if (typeof config.userMapping === 'function') + { + await config.userMapping({ peer, userinfo: _userinfo }); + } + } + room.handlePeer(peer); }) .catch((error) => From 7f2f27b858a63706c7996c9024b0ba83ba6083d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 22 Mar 2020 00:43:47 +0100 Subject: [PATCH 05/21] Add support for moderating rooms. Kick user, mute all users, stop all videos. --- app/src/RoomClient.js | 176 +++++++++++++++++- app/src/actions/meActions.js | 12 ++ app/src/actions/peerActions.js | 19 ++ app/src/actions/roomActions.js | 12 ++ .../ParticipantList/ListModerator.js | 101 ++++++++++ .../MeetingDrawer/ParticipantList/ListPeer.js | 58 ++---- .../ParticipantList/ParticipantList.js | 25 ++- app/src/reducers/me.js | 18 ++ app/src/reducers/peers.js | 21 +++ app/src/reducers/room.js | 6 + app/src/reducers/userRoles.js | 4 + server/lib/Lobby.js | 8 +- server/lib/Peer.js | 7 +- server/lib/Room.js | 97 +++++++++- server/userRoles.js | 8 +- 15 files changed, 515 insertions(+), 57 deletions(-) create mode 100644 app/src/components/MeetingDrawer/ParticipantList/ListModerator.js create mode 100644 app/src/reducers/userRoles.js diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 1efcab2..4edc5c3 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1126,6 +1126,66 @@ export default class RoomClient lobbyPeerActions.setLobbyPeerPromotionInProgress(peerId, false)); } + async kickPeer(peerId) + { + logger.debug('kickPeer() [peerId:"%s"]', peerId); + + store.dispatch( + peerActions.setPeerKickInProgress(peerId, true)); + + try + { + await this.sendRequest('moderator:kickPeer', { peerId }); + } + catch (error) + { + logger.error('kickPeer() failed: %o', error); + } + + store.dispatch( + peerActions.setPeerKickInProgress(peerId, false)); + } + + async muteAllPeers() + { + logger.debug('muteAllPeers()'); + + store.dispatch( + roomActions.setMuteAllInProgress(true)); + + try + { + await this.sendRequest('moderator:muteAll'); + } + catch (error) + { + logger.error('muteAllPeers() failed: %o', error); + } + + store.dispatch( + roomActions.setMuteAllInProgress(false)); + } + + async stopAllPeerVideo() + { + logger.debug('stopAllPeerVideo()'); + + store.dispatch( + roomActions.setStopAllVideoInProgress(true)); + + try + { + await this.sendRequest('moderator:stopAllVideo'); + } + catch (error) + { + logger.error('stopAllPeerVideo() failed: %o', error); + } + + store.dispatch( + roomActions.setStopAllVideoInProgress(false)); + } + // type: mic/webcam/screen // mute: true/false async modifyPeerConsumer(peerId, type, mute) @@ -1902,10 +1962,10 @@ export default class RoomClient case 'newPeer': { - const { id, displayName, picture } = notification.data; + const { id, displayName, picture, roles } = notification.data; store.dispatch( - peerActions.addPeer({ id, displayName, picture, consumers: [] })); + peerActions.addPeer({ id, displayName, picture, roles, consumers: [] })); store.dispatch(requestActions.notify( { @@ -2004,6 +2064,96 @@ export default class RoomClient break; } + + case 'moderator:mute': + { + // const { peerId } = notification.data; + + if (this._micProducer && !this._micProducer.paused) + { + this.muteMic(); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'moderator.mute', + defaultMessage : 'Moderator muted your microphone' + }) + })); + } + + break; + } + + case 'moderator:stopVideo': + { + // const { peerId } = notification.data; + + this.disableWebcam(); + this.disableScreenSharing(); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'moderator.mute', + defaultMessage : 'Moderator stopped your video' + }) + })); + + break; + } + + case 'moderator:kick': + { + // Need some feedback + this.close(); + + break; + } + + case 'gotRole': + { + const { peerId, role } = notification.data; + + if (peerId === this._peerId) + { + store.dispatch(meActions.addRole({ role })); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'roles.gotRole', + defaultMessage : `You got the role: ${role}` + }) + })); + } + else + store.dispatch(peerActions.addPeerRole({ peerId, role })); + + break; + } + + case 'lostRole': + { + const { peerId, role } = notification.data; + + if (peerId === this._peerId) + { + store.dispatch(meActions.removeRole({ role })); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'roles.lostRole', + defaultMessage : `You lost the role: ${role}` + }) + })); + } + else + store.dispatch(peerActions.removePeerRole({ peerId, role })); + + break; + } default: { @@ -2158,7 +2308,7 @@ export default class RoomClient canShareFiles : this._torrentSupport })); - const { peers } = await this.sendRequest( + const { roles, peers } = await this.sendRequest( 'join', { displayName : displayName, @@ -2166,7 +2316,25 @@ export default class RoomClient rtpCapabilities : this._mediasoupDevice.rtpCapabilities }); - logger.debug('_joinRoom() joined, got peers [peers:"%o"]', peers); + logger.debug('_joinRoom() joined [peers:"%o", roles:"%o"]', peers, roles); + + const myRoles = store.getState().me.roles; + + for (const role of roles) + { + if (!myRoles.includes(role)) + { + store.dispatch(meActions.addRole({ role })); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'roles.gotRole', + defaultMessage : `You got the role: ${role}` + }) + })); + } + } for (const peer of peers) { diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index b704880..bdff144 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -10,6 +10,18 @@ export const loggedIn = (flag) => payload : { flag } }); +export const addRole = ({ role }) => + ({ + type : 'ADD_ROLE', + payload : { role } + }); + +export const removeRole = (role) => + ({ + type : 'REMOVE_ROLE', + payload : { role } + }); + export const setPicture = (picture) => ({ type : 'SET_PICTURE', diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index 4caca32..1a87151 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -45,3 +45,22 @@ export const setPeerPicture = (peerId, picture) => type : 'SET_PEER_PICTURE', payload : { peerId, picture } }); + + +export const addPeerRole = (peerId, role) => + ({ + type : 'ADD_PEER_ROLE', + payload : { peerId, role } + }); + +export const removePeerRole = (peerId, role) => + ({ + type : 'REMOVE_PEER_ROLE', + payload : { peerId, role } + }); + +export const setPeerKickInProgress = (peerId, flag) => + ({ + type : 'SET_PEER_KICK_IN_PROGRESS', + payload : { peerId, flag } + }); diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index 560c77d..9ad1e08 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -109,4 +109,16 @@ export const toggleConsumerFullscreen = (consumerId) => ({ type : 'TOGGLE_FULLSCREEN_CONSUMER', payload : { consumerId } + }); + +export const setMuteAllInProgress = (flag) => + ({ + type : 'MUTE_ALL_IN_PROGRESS', + payload : { flag } + }); + +export const setStopAllVideoInProgress = (flag) => + ({ + type : 'STOP_ALL_VIDEO_IN_PROGRESS', + payload : { flag } }); \ No newline at end of file diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js new file mode 100644 index 0000000..66f64bd --- /dev/null +++ b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js @@ -0,0 +1,101 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import PropTypes from 'prop-types'; +import { withRoomContext } from '../../../RoomContext'; +import { useIntl, FormattedMessage } from 'react-intl'; +import Button from '@material-ui/core/Button'; + +const styles = (theme) => + ({ + root : + { + padding : theme.spacing(1), + width : '100%', + overflow : 'hidden', + cursor : 'auto', + display : 'flex' + }, + actionButtons : + { + display : 'flex' + }, + divider : + { + marginLeft : theme.spacing(2) + } + }); + +const ListModerator = (props) => +{ + const intl = useIntl(); + + const { + roomClient, + room, + classes + } = props; + + return ( +
+ +
+ +
+ ); +}; + +ListModerator.propTypes = +{ + roomClient : PropTypes.any.isRequired, + room : PropTypes.object.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => ({ + room : state.room +}); + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room === next.room + ); + } + } +)(withStyles(styles)(ListModerator))); \ No newline at end of file diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 9f74e65..26a8ab3 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -12,6 +12,7 @@ import MicIcon from '@material-ui/icons/Mic'; import MicOffIcon from '@material-ui/icons/MicOff'; import ScreenIcon from '@material-ui/icons/ScreenShare'; import ScreenOffIcon from '@material-ui/icons/StopScreenShare'; +import ExitIcon from '@material-ui/icons/ExitToApp'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import HandIcon from '../../../images/icon-hand-white.svg'; @@ -91,40 +92,6 @@ const styles = (theme) => flexDirection : 'row', justifyContent : 'flex-start', alignItems : 'center' - }, - button : - { - flex : '0 0 auto', - margin : '0.3rem', - borderRadius : 2, - backgroundColor : 'rgba(0, 0, 0, 0.5)', - cursor : 'pointer', - transitionProperty : 'opacity, background-color', - transitionDuration : '0.15s', - width : 'var(--media-control-button-size)', - height : 'var(--media-control-button-size)', - opacity : 0.85, - '&:hover' : - { - opacity : 1 - }, - '&.unsupported' : - { - pointerEvents : 'none' - }, - '&.disabled' : - { - pointerEvents : 'none', - backgroundColor : 'var(--media-control-botton-disabled)' - }, - '&.on' : - { - backgroundColor : 'var(--media-control-botton-on)' - }, - '&.off' : - { - backgroundColor : 'var(--media-control-botton-off)' - } } }); @@ -134,6 +101,7 @@ const ListPeer = (props) => const { roomClient, + isModerator, peer, micConsumer, screenConsumer, @@ -185,9 +153,8 @@ const ListPeer = (props) => })} color={ screenVisible ? 'primary' : 'secondary'} disabled={ peer.peerScreenInProgress } - onClick={(e) => + onClick={() => { - e.stopPropagation(); screenVisible ? roomClient.modifyPeerConsumer(peer.id, 'screen', true) : roomClient.modifyPeerConsumer(peer.id, 'screen', false); @@ -207,9 +174,8 @@ const ListPeer = (props) => })} color={ micEnabled ? 'primary' : 'secondary'} disabled={ peer.peerAudioInProgress } - onClick={(e) => + onClick={() => { - e.stopPropagation(); micEnabled ? roomClient.modifyPeerConsumer(peer.id, 'mic', true) : roomClient.modifyPeerConsumer(peer.id, 'mic', false); @@ -221,6 +187,21 @@ const ListPeer = (props) => } + { isModerator && + + { + roomClient.kickPeer(peer.id); + }} + > + + + }
); @@ -230,6 +211,7 @@ ListPeer.propTypes = { roomClient : PropTypes.any.isRequired, advancedMode : PropTypes.bool, + isModerator : PropTypes.bool, peer : appPropTypes.Peer.isRequired, micConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer, diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index 3313d2b..01dbf9d 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -11,7 +11,9 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import ListPeer from './ListPeer'; import ListMe from './ListMe'; +import ListModerator from './ListModerator'; import Volume from '../../Containers/Volume'; +import * as userRoles from '../../../reducers/userRoles'; const styles = (theme) => ({ @@ -76,6 +78,7 @@ class ParticipantList extends React.PureComponent const { roomClient, advancedMode, + isModerator, passivePeers, selectedPeerId, spotlightPeers, @@ -84,6 +87,17 @@ class ParticipantList extends React.PureComponent return (
{ this.node = node; }}> + { isModerator && +
    +
  • + +
  • + +
+ }
  • roomClient.setSelectedPeer(peerId)} > - +
  • @@ -129,7 +143,7 @@ class ParticipantList extends React.PureComponent })} onClick={() => roomClient.setSelectedPeer(peerId)} > - + ))}
@@ -142,6 +156,7 @@ ParticipantList.propTypes = { roomClient : PropTypes.any.isRequired, advancedMode : PropTypes.bool, + isModerator : PropTypes.bool, passivePeers : PropTypes.array, selectedPeerId : PropTypes.string, spotlightPeers : PropTypes.array, @@ -150,7 +165,12 @@ ParticipantList.propTypes = const mapStateToProps = (state) => { + const isModerator = + state.me.roles.includes(userRoles.MODERATOR) || + state.me.roles.includes(userRoles.ADMIN); + return { + isModerator, passivePeers : passivePeersSelector(state), selectedPeerId : state.room.selectedPeerId, spotlightPeers : spotlightPeersSelector(state) @@ -165,6 +185,7 @@ const ParticipantListContainer = withRoomContext(connect( areStatesEqual : (next, prev) => { return ( + prev.me.roles === next.me.roles && prev.peers === next.peers && prev.room.spotlights === next.room.spotlights && prev.room.selectedPeerId === next.room.selectedPeerId diff --git a/app/src/reducers/me.js b/app/src/reducers/me.js index e80375a..4bad22f 100644 --- a/app/src/reducers/me.js +++ b/app/src/reducers/me.js @@ -1,7 +1,10 @@ +import * as userRoles from './userRoles'; + const initialState = { id : null, picture : null, + roles : [ userRoles.ALL ], canSendMic : false, canSendWebcam : false, canShareScreen : false, @@ -43,6 +46,21 @@ const me = (state = initialState, action) => return { ...state, loggedIn: flag }; } + case 'ADD_ROLE': + { + const roles = [ ...state.roles, action.payload.role ]; + + return { ...state, roles }; + } + + case 'REMOVE_ROLE': + { + const roles = state.roles.filter((role) => + role !== action.payload.role); + + return { ...state, roles }; + } + case 'SET_PICTURE': return { ...state, picture: action.payload.picture }; diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index ddd104f..e3d207d 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -16,6 +16,9 @@ const peer = (state = {}, action) => case 'SET_PEER_SCREEN_IN_PROGRESS': return { ...state, peerScreenInProgress: action.payload.flag }; + + case 'SET_PEER_KICK_IN_PROGRESS': + return { ...state, peerKickInProgress: action.payload.flag }; case 'SET_PEER_RAISE_HAND_STATE': return { ...state, raiseHandState: action.payload.raiseHandState }; @@ -40,6 +43,21 @@ const peer = (state = {}, action) => return { ...state, picture: action.payload.picture }; } + case 'ADD_PEER_ROLE': + { + const roles = [ ...state.roles, action.payload.role ]; + + return { ...state, roles }; + } + + case 'REMOVE_PEER_ROLE': + { + const roles = state.roles.filter((role) => + role !== action.payload.role); + + return { ...state, roles }; + } + default: return state; } @@ -71,6 +89,8 @@ const peers = (state = {}, action) => case 'SET_PEER_RAISE_HAND_STATE': case 'SET_PEER_PICTURE': case 'ADD_CONSUMER': + case 'ADD_PEER_ROLE': + case 'REMOVE_PEER_ROLE': { const oldPeer = state[action.payload.peerId]; @@ -82,6 +102,7 @@ const peers = (state = {}, action) => return { ...state, [oldPeer.id]: peer(oldPeer, action) }; } + case 'SET_PEER_KICK_IN_PROGRESS': case 'REMOVE_CONSUMER': { const oldPeer = state[action.payload.peerId]; diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index c4b29fb..9d483b8 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -163,6 +163,12 @@ const room = (state = initialState, action) => return { ...state, spotlights }; } + case 'MUTE_ALL_IN_PROGRESS': + return { ...state, muteAllInProgress: action.payload.flag }; + + case 'STOP_ALL_VIDEO_IN_PROGRESS': + return { ...state, stopAllVideoInProgress: action.payload.flag }; + default: return state; } diff --git a/app/src/reducers/userRoles.js b/app/src/reducers/userRoles.js new file mode 100644 index 0000000..217a760 --- /dev/null +++ b/app/src/reducers/userRoles.js @@ -0,0 +1,4 @@ +export const ADMIN = 'admin'; +export const MODERATOR = 'moderator'; +export const AUTHENTICATED = 'authenticated'; +export const ALL = 'normal'; \ No newline at end of file diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js index 7372455..b5d9fb6 100644 --- a/server/lib/Lobby.js +++ b/server/lib/Lobby.js @@ -75,13 +75,13 @@ class Lobby extends EventEmitter if (peer) { peer.socket.removeListener('request', peer.socketRequestHandler); - peer.removeListener('rolesChange', peer.roleChangeHandler); + peer.removeListener('gotRole', peer.gotRoleHandler); peer.removeListener('displayNameChanged', peer.displayNameChangeHandler); peer.removeListener('pictureChanged', peer.pictureChangeHandler); peer.removeListener('close', peer.closeHandler); peer.socketRequestHandler = null; - peer.roleChangeHandler = null; + peer.gotRoleHandler = null; peer.displayNameChangeHandler = null; peer.pictureChangeHandler = null; peer.closeHandler = null; @@ -116,7 +116,7 @@ class Lobby extends EventEmitter }); }; - peer.roleChangeHandler = () => + peer.gotRoleHandler = () => { logger.info('parkPeer() | rolesChange [peer:"%s"]', peer.id); @@ -156,7 +156,7 @@ class Lobby extends EventEmitter this._peers.set(peer.id, peer); - peer.on('rolesChange', peer.roleChangeHandler); + peer.on('gotRole', peer.gotRoleHandler); peer.on('displayNameChanged', peer.displayNameChangeHandler); peer.on('pictureChanged', peer.pictureChangeHandler); diff --git a/server/lib/Peer.js b/server/lib/Peer.js index 93ee9b8..2b63c87 100644 --- a/server/lib/Peer.js +++ b/server/lib/Peer.js @@ -224,7 +224,7 @@ class Peer extends EventEmitter logger.info('addRole() | [newRole:"%s]"', newRole); - this.emit('rolesChange', { newRole }); + this.emit('gotRole', { newRole }); } } @@ -236,7 +236,7 @@ class Peer extends EventEmitter logger.info('removeRole() | [oldRole:"%s]"', oldRole); - this.emit('rolesChange', { oldRole }); + this.emit('lostRole', { oldRole }); } } @@ -302,7 +302,8 @@ class Peer extends EventEmitter { id : this.id, displayName : this.displayName, - picture : this.picture + picture : this.picture, + roles : this.roles }; return peerInfo; diff --git a/server/lib/Room.js b/server/lib/Room.js index 140d4ee..b397ae1 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -420,6 +420,32 @@ class Room extends EventEmitter picture : peer.picture }, true); }); + + peer.on('gotRole', ({ newRole }) => + { + // Ensure the Peer is joined. + if (!peer.joined) + return; + + // Spread to others + this._notification(peer.socket, 'gotRole', { + peerId : peer.id, + role : newRole + }, true); + }); + + peer.on('lostRole', ({ oldRole }) => + { + // Ensure the Peer is joined. + if (!peer.joined) + return; + + // Spread to others + this._notification(peer.socket, 'lostRole', { + peerId : peer.id, + role : oldRole + }, true); + }); } async _handleSocketRequest(peer, request, cb) @@ -483,7 +509,10 @@ class Room extends EventEmitter .filter((joinedPeer) => joinedPeer.id !== peer.id) .map((joinedPeer) => (joinedPeer.peerInfo)); - cb(null, { peers: peerInfos }); + cb(null, { + roles : peer.roles, + peers : peerInfos + }); // Mark the new Peer as joined. peer.joined = true; @@ -511,7 +540,8 @@ class Room extends EventEmitter { id : peer.id, displayName : displayName, - picture : picture + picture : picture, + roles : peer.roles } ); } @@ -1077,6 +1107,69 @@ class Room extends EventEmitter break; } + case 'moderator:muteAll': + { + if ( + !peer.hasRole(userRoles.MODERATOR) && + !peer.hasRole(userRoles.ADMIN) + ) + throw new Error('peer does not have moderator priveleges'); + + // Spread to others + this._notification(peer.socket, 'moderator:mute', { + peerId : peer.id + }, true); + + cb(); + + break; + } + + case 'moderator:stopAllVideo': + { + if ( + !peer.hasRole(userRoles.MODERATOR) && + !peer.hasRole(userRoles.ADMIN) + ) + throw new Error('peer does not have moderator priveleges'); + + // Spread to others + this._notification(peer.socket, 'moderator:stopVideo', { + peerId : peer.id + }, true); + + cb(); + + break; + } + + case 'moderator:kickPeer': + { + if ( + !peer.hasRole(userRoles.MODERATOR) && + !peer.hasRole(userRoles.ADMIN) + ) + throw new Error('peer does not have moderator priveleges'); + + const { peerId } = request.data; + + const kickPeer = this._peers[peerId]; + + if (!kickPeer) + throw new Error(`peer with id "${peerId}" not found`); + + this._notification( + kickPeer.socket, + 'moderator:kick' + ); + + kickPeer.close(); + + cb(); + + break; + } + default: { logger.error('unknown request.method "%s"', request.method); diff --git a/server/userRoles.js b/server/userRoles.js index ba5fd59..c8cf886 100644 --- a/server/userRoles.js +++ b/server/userRoles.js @@ -1,12 +1,12 @@ module.exports = { // Allowed to enter locked rooms + all other priveleges - ADMIN : 0, + ADMIN : 'admin', // Allowed to enter restricted rooms if configured. // Allowed to moderate users in a room (mute all, // spotlight video, kick users) - MODERATOR : 1, + MODERATOR : 'moderator', // Same as MODERATOR, but can't moderate users - AUTHENTICATED : 2, + AUTHENTICATED : 'authenticated', // No priveleges - ALL : 3 + ALL : 'normal' }; \ No newline at end of file From 603368007a8382b1cc37e877c7507f5bb60d8056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 22 Mar 2020 15:20:49 +0100 Subject: [PATCH 06/21] Added roomid to authenitcation data flow to make mapping user info better. --- app/src/RoomClient.js | 2 +- server/config/config.example.js | 28 +++++++++++++++++++++------- server/lib/Peer.js | 15 +++++++++++++-- server/server.js | 18 +++++++++++++----- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 4edc5c3..3188526 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -391,7 +391,7 @@ export default class RoomClient login() { - const url = `/auth/login?id=${this._peerId}`; + const url = `/auth/login?peerId=${this._peerId}&roomId=${this._roomId}`; window.open(url, 'loginWindow'); } diff --git a/server/config/config.example.js b/server/config/config.example.js index ee225b1..5c7291d 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -52,12 +52,13 @@ module.exports = // use case: loadbalancer backend httpOnly : false, // This function will be called on successful login through oidc. - // Use this function to map your oidc userinfo to the Peer object, - // see examples below. + // Use this function to map your oidc userinfo to the Peer object. + // The roomId is equal to the room name. + // See examples below. // Examples: /* // All authenicated users will be MODERATOR and AUTHENTICATED - userMapping : async ({ peer, userinfo }) => + userMapping : async ({ peer, roomId, userinfo }) => { peer.addRole(userRoles.MODERATOR); peer.addRole(userRoles.AUTHENTICATED); @@ -65,7 +66,7 @@ module.exports = // All authenicated users will be AUTHENTICATED, // and those with the moderator role set in the userinfo // will also be MODERATOR - userMapping : async ({ peer, userinfo }) => + userMapping : async ({ peer, roomId, userinfo }) => { if ( Array.isArray(userinfo.meet_roles) && @@ -88,7 +89,7 @@ module.exports = // All authenicated users will be AUTHENTICATED, // and those with email ending with @example.com // will also be MODERATOR - userMapping : async ({ peer, userinfo }) => + userMapping : async ({ peer, roomId, userinfo }) => { if (userinfo.email && userinfo.email.endsWith('@example.com')) { @@ -96,8 +97,21 @@ module.exports = } peer.addRole(userRoles.AUTHENTICATED); - },*/ - userMapping : async ({ peer, userinfo }) => + } + // All authenicated users will be AUTHENTICATED, + // and those with email ending with @example.com + // will also be MODERATOR + userMapping : async ({ peer, roomId, userinfo }) => + { + if (userinfo.email && userinfo.email.endsWith('@example.com')) + { + peer.addRole(userRoles.MODERATOR); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + */ + userMapping : async ({ peer, roomId, userinfo }) => { if (userinfo.picture != null) { diff --git a/server/lib/Peer.js b/server/lib/Peer.js index 2b63c87..1affec9 100644 --- a/server/lib/Peer.js +++ b/server/lib/Peer.js @@ -1,19 +1,20 @@ const EventEmitter = require('events').EventEmitter; const userRoles = require('../userRoles'); -const config = require('../config/config'); const Logger = require('./Logger'); const logger = new Logger('Peer'); class Peer extends EventEmitter { - constructor({ id, socket }) + constructor({ id, roomId, socket }) { logger.info('constructor() [id:"%s", socket:"%s"]', id, socket.id); super(); this._id = id; + this._roomId = roomId; + this._authId = null; this._socket = socket; @@ -87,6 +88,16 @@ class Peer extends EventEmitter this._id = id; } + get roomId() + { + return this._roomId; + } + + set roomId(roomId) + { + this._roomId = roomId; + } + get authId() { return this._authId; diff --git a/server/server.js b/server/server.js index 43b888b..4d08c09 100755 --- a/server/server.js +++ b/server/server.js @@ -279,7 +279,8 @@ async function setupAuth() { passport.authenticate('oidc', { state : base64.encode(JSON.stringify({ - id : req.query.id + peerId : req.query.peerId, + roomId : req.query.roomId })) })(req, res, next); }); @@ -324,14 +325,21 @@ async function setupAuth() picture = '/static/media/buddy.403cb9f6.svg'; } - const peer = peers.get(state.id); + const peer = peers.get(state.peerId); + + if (peer && peer.roomId !== state.roomId) // The peer is mischievous + throw new Error('peer authenticated with wrong room'); peer && (peer.displayName = displayName); peer && (peer.picture = picture); if (peer && typeof config.userMapping === 'function') { - await config.userMapping({ peer, userinfo: req.user._userinfo }); + await config.userMapping({ + peer, + roomId : state.roomId, + userinfo : req.user._userinfo + }); } res.send(loginHelper({ @@ -454,7 +462,7 @@ async function runWebSocketServer() queue.push(async () => { const room = await getOrCreateRoom({ roomId }); - const peer = new Peer({ id: peerId, socket }); + const peer = new Peer({ id: peerId, roomId, socket }); peers.set(peerId, peer); @@ -480,7 +488,7 @@ async function runWebSocketServer() if (typeof config.userMapping === 'function') { - await config.userMapping({ peer, userinfo: _userinfo }); + await config.userMapping({ peer, roomId, userinfo: _userinfo }); } } From ed6f256fb3afdb6fbe69c4f01e8fed77e8a5edf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 22 Mar 2020 19:55:06 +0100 Subject: [PATCH 07/21] Various cleanups --- server/lib/Room.js | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index b397ae1..5ede4fa 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -120,15 +120,14 @@ class Room extends EventEmitter { logger.info('handlePeer() [peer:"%s", roles:"%s"]', peer.id, peer.roles); + // Allow reconnections, remove old peer if (this._peers[peer.id]) { logger.warn( 'handleConnection() | there is already a peer with same peerId [peer:"%s"]', peer.id); - peer.close(); - - return; + this._peers[peer.id].close(); } // Always let ADMIN in, even if locked @@ -461,27 +460,6 @@ class Room extends EventEmitter case 'join': { - - try - { - if (peer.socket.handshake.session.passport.user.displayName) - { - this._notification( - peer.socket, - 'changeDisplayname', - { - peerId : peer.id, - displayName : peer.socket.handshake.session.passport.user.displayName, - oldDisplayName : '' - }, - true - ); - } - } - catch (error) - { - logger.error(error); - } // Ensure the Peer is not already joined. if (peer.joined) throw new Error('Peer already joined'); From 002950d708dffb03ccd6ff55be2c4fd3cce71bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 23 Mar 2020 14:44:12 +0100 Subject: [PATCH 08/21] Fix for authentication data flow --- app/src/reducers/me.js | 3 +++ server/lib/Peer.js | 25 ++++++++++++++----------- server/server.js | 30 ++++++++---------------------- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/app/src/reducers/me.js b/app/src/reducers/me.js index 4bad22f..2c8f33c 100644 --- a/app/src/reducers/me.js +++ b/app/src/reducers/me.js @@ -48,6 +48,9 @@ const me = (state = initialState, action) => case 'ADD_ROLE': { + if (state.roles.includes(action.payload.role)) + return state; + const roles = [ ...state.roles, action.payload.role ]; return { ...state, roles }; diff --git a/server/lib/Peer.js b/server/lib/Peer.js index 1affec9..1878a61 100644 --- a/server/lib/Peer.js +++ b/server/lib/Peer.js @@ -8,7 +8,7 @@ class Peer extends EventEmitter { constructor({ id, roomId, socket }) { - logger.info('constructor() [id:"%s", socket:"%s"]', id, socket.id); + logger.info('constructor() [id:"%s"]', id); super(); this._id = id; @@ -59,23 +59,26 @@ class Peer extends EventEmitter transport.close(); }); - if (this._socket) - this._socket.disconnect(true); + if (this.socket) + this.socket.disconnect(true); this.emit('close'); } _handlePeer() { - this.socket.on('disconnect', () => + if (this.socket) { - if (this.closed) - return; - - logger.debug('"disconnect" event [id:%s]', this.id); - - this.close(); - }); + this.socket.on('disconnect', () => + { + if (this.closed) + return; + + logger.debug('"disconnect" event [id:%s]', this.id); + + this.close(); + }); + } } get id() diff --git a/server/server.js b/server/server.js index 4d08c09..55e0cfc 100755 --- a/server/server.js +++ b/server/server.js @@ -309,42 +309,28 @@ async function setupAuth() { const state = JSON.parse(base64.decode(req.query.state)); - let displayName; - let picture; + const { peerId, roomId } = state; - if (req.user != null) - { - if (req.user.displayName != null) - displayName = req.user.displayName; - else - displayName = ''; + let peer = peers.get(peerId); - if (req.user.picture != null) - picture = req.user.picture; - else - picture = '/static/media/buddy.403cb9f6.svg'; - } + if (!peer) // User has no socket session yet, make temporary + peer = new Peer({ id: peerId, roomId }); - const peer = peers.get(state.peerId); - - if (peer && peer.roomId !== state.roomId) // The peer is mischievous + if (peer && peer.roomId !== roomId) // The peer is mischievous throw new Error('peer authenticated with wrong room'); - peer && (peer.displayName = displayName); - peer && (peer.picture = picture); - if (peer && typeof config.userMapping === 'function') { await config.userMapping({ peer, - roomId : state.roomId, + roomId, userinfo : req.user._userinfo }); } res.send(loginHelper({ - displayName, - picture + displayName : peer.displayName, + picture : peer.picture })); } ); From 764e02c7321d43768761efe85128569c906ffcd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 23 Mar 2020 22:52:53 +0100 Subject: [PATCH 09/21] Typos --- app/src/RoomClient.js | 8 ++++---- app/src/actions/meActions.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 3188526..dbdedcf 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2117,7 +2117,7 @@ export default class RoomClient if (peerId === this._peerId) { - store.dispatch(meActions.addRole({ role })); + store.dispatch(meActions.addRole(role)); store.dispatch(requestActions.notify( { @@ -2128,7 +2128,7 @@ export default class RoomClient })); } else - store.dispatch(peerActions.addPeerRole({ peerId, role })); + store.dispatch(peerActions.addPeerRole(peerId, role)); break; } @@ -2139,7 +2139,7 @@ export default class RoomClient if (peerId === this._peerId) { - store.dispatch(meActions.removeRole({ role })); + store.dispatch(meActions.removeRole(role)); store.dispatch(requestActions.notify( { @@ -2150,7 +2150,7 @@ export default class RoomClient })); } else - store.dispatch(peerActions.removePeerRole({ peerId, role })); + store.dispatch(peerActions.removePeerRole(peerId, role)); break; } diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index bdff144..73fdaa8 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -10,7 +10,7 @@ export const loggedIn = (flag) => payload : { flag } }); -export const addRole = ({ role }) => +export const addRole = (role) => ({ type : 'ADD_ROLE', payload : { role } From 013abb15baab59327dcfa7ea22f28e9e8538f75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 23 Mar 2020 22:57:18 +0100 Subject: [PATCH 10/21] Include original peer in socket broadcast --- server/lib/Room.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 5ede4fa..1fed90b 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -430,7 +430,7 @@ class Room extends EventEmitter this._notification(peer.socket, 'gotRole', { peerId : peer.id, role : newRole - }, true); + }, true, true); }); peer.on('lostRole', ({ oldRole }) => @@ -443,7 +443,7 @@ class Room extends EventEmitter this._notification(peer.socket, 'lostRole', { peerId : peer.id, role : oldRole - }, true); + }, true, true); }); } @@ -1361,13 +1361,16 @@ class Room extends EventEmitter }); } - _notification(socket, method, data = {}, broadcast = false) + _notification(socket, method, data = {}, broadcast = false, includeSender = false) { if (broadcast) { socket.broadcast.to(this._roomId).emit( 'notification', { method, data } ); + + if (includeSender) + socket.emit('notification', { method, data }); } else { From c1cb0445fbed21a72dba66ec9c3ed0bef012f7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 23 Mar 2020 23:06:08 +0100 Subject: [PATCH 11/21] Wrong state handling --- .../ParticipantList/ParticipantList.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index 01dbf9d..dbf5491 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -143,7 +143,11 @@ class ParticipantList extends React.PureComponent })} onClick={() => roomClient.setSelectedPeer(peerId)} > - + ))} @@ -165,12 +169,9 @@ ParticipantList.propTypes = const mapStateToProps = (state) => { - const isModerator = - state.me.roles.includes(userRoles.MODERATOR) || - state.me.roles.includes(userRoles.ADMIN); - return { - isModerator, + isModerator : state.me.roles.includes(userRoles.MODERATOR) || + state.me.roles.includes(userRoles.ADMIN), passivePeers : passivePeersSelector(state), selectedPeerId : state.room.selectedPeerId, spotlightPeers : spotlightPeersSelector(state) From 9b8853f9849852742410b25a9f787fd0879dc635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Mon, 23 Mar 2020 23:18:06 +0100 Subject: [PATCH 12/21] Typo --- app/src/RoomClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index dbdedcf..ba99e1d 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2324,7 +2324,7 @@ export default class RoomClient { if (!myRoles.includes(role)) { - store.dispatch(meActions.addRole({ role })); + store.dispatch(meActions.addRole(role)); store.dispatch(requestActions.notify( { From 43b218fb3a77bbf13406cfa8a2212023210e0880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 00:56:26 +0100 Subject: [PATCH 13/21] Remove roles on logout --- server/server.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/server.js b/server/server.js index 55e0cfc..ee29495 100755 --- a/server/server.js +++ b/server/server.js @@ -18,6 +18,7 @@ const Peer = require('./lib/Peer'); const base64 = require('base-64'); const helmet = require('helmet'); +const userRoles = require('./userRoles'); const { loginHelper, logoutHelper @@ -297,6 +298,19 @@ async function setupAuth() // logout app.get('/auth/logout', (req, res) => { + const { peerId } = req.session; + + const peer = peers.get(peerId); + + if (peer) + { + for (const role of peer.roles) + { + if (role !== userRoles.ALL) + peer.removeRole(role); + } + } + req.logout(); res.send(logoutHelper()); }); @@ -311,6 +325,9 @@ async function setupAuth() const { peerId, roomId } = state; + req.session.peerId = peerId; + req.session.roomId = roomId; + let peer = peers.get(peerId); if (!peer) // User has no socket session yet, make temporary From dd6d07391d4b48acd5bae155153be8269d9c8a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 00:57:14 +0100 Subject: [PATCH 14/21] Remove login on room chooser --- app/src/components/ChooseRoom.js | 103 ++----------------------------- 1 file changed, 5 insertions(+), 98 deletions(-) diff --git a/app/src/components/ChooseRoom.js b/app/src/components/ChooseRoom.js index 15b3955..e800228 100644 --- a/app/src/components/ChooseRoom.js +++ b/app/src/components/ChooseRoom.js @@ -1,21 +1,15 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Link } from 'react-router-dom'; -import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; -import { withRoomContext } from '../RoomContext'; import isElectron from 'is-electron'; import PropTypes from 'prop-types'; import { useIntl, FormattedMessage } from 'react-intl'; import randomString from 'random-string'; import Dialog from '@material-ui/core/Dialog'; import DialogContentText from '@material-ui/core/DialogContentText'; -import IconButton from '@material-ui/core/IconButton'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import Avatar from '@material-ui/core/Avatar'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; -import Tooltip from '@material-ui/core/Tooltip'; import CookieConsent from 'react-cookie-consent'; import MuiDialogTitle from '@material-ui/core/DialogTitle'; import MuiDialogContent from '@material-ui/core/DialogContent'; @@ -88,63 +82,12 @@ const styles = (theme) => const DialogTitle = withStyles(styles)((props) => { - const [ open, setOpen ] = useState(false); - - const intl = useIntl(); - - useEffect(() => - { - const openTimer = setTimeout(() => setOpen(true), 1000); - const closeTimer = setTimeout(() => setOpen(false), 4000); - - return () => - { - clearTimeout(openTimer); - clearTimeout(closeTimer); - }; - }, []); - - const { children, classes, myPicture, onLogin, ...other } = props; - - const handleTooltipClose = () => - { - setOpen(false); - }; - - const handleTooltipOpen = () => - { - setOpen(true); - }; + const { children, classes, ...other } = props; return ( { window.config && window.config.logo && Logo } {children} - { window.config && window.config.loginEnabled && - - - { myPicture ? - - : - - } - - - } ); }); @@ -165,9 +108,6 @@ const DialogActions = withStyles((theme) => ({ }))(MuiDialogActions); const ChooseRoom = ({ - roomClient, - loggedIn, - myPicture, classes }) => { @@ -184,13 +124,7 @@ const ChooseRoom = ({ paper : classes.dialogPaper }} > - - { - loggedIn ? roomClient.logout() : roomClient.login(); - }} - > + { window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
@@ -255,34 +189,7 @@ const ChooseRoom = ({ ChooseRoom.propTypes = { - roomClient : PropTypes.any.isRequired, - loginEnabled : PropTypes.bool.isRequired, - loggedIn : PropTypes.bool.isRequired, - myPicture : PropTypes.string, - classes : PropTypes.object.isRequired + classes : PropTypes.object.isRequired }; -const mapStateToProps = (state) => -{ - return { - loginEnabled : state.me.loginEnabled, - loggedIn : state.me.loggedIn, - myPicture : state.me.picture - }; -}; - -export default withRoomContext(connect( - mapStateToProps, - null, - null, - { - areStatesEqual : (next, prev) => - { - return ( - prev.me.loginEnabled === next.me.loginEnabled && - prev.me.loggedIn === next.me.loggedIn && - prev.me.picture === next.me.picture - ); - } - } -)(withStyles(styles)(ChooseRoom))); \ No newline at end of file +export default withStyles(styles)(ChooseRoom); \ No newline at end of file From 4135be978935029e5acc8da794d49030cd6b8a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 00:59:35 +0100 Subject: [PATCH 15/21] Remove duplicate signaling --- app/src/RoomClient.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index ba99e1d..ad4955a 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -407,16 +407,8 @@ export default class RoomClient const { displayName, picture } = data; - if (store.getState().room.state === 'connected') - { - this.changeDisplayName(displayName); - this.changePicture(picture); - } - else - { - store.dispatch(settingsActions.setDisplayName(displayName)); - store.dispatch(meActions.setPicture(picture)); - } + store.dispatch(settingsActions.setDisplayName(displayName)); + store.dispatch(meActions.setPicture(picture)); store.dispatch(meActions.loggedIn(true)); From d756dd4721f377e53a117929efd467b685b008b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 01:23:17 +0100 Subject: [PATCH 16/21] Cleanup --- server/lib/Lobby.js | 27 ++++++++++++++------------- server/lib/Room.js | 28 ++++++---------------------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js index b5d9fb6..75cae49 100644 --- a/server/lib/Lobby.js +++ b/server/lib/Lobby.js @@ -14,7 +14,7 @@ class Lobby extends EventEmitter // Closed flag. this._closed = false; - this._peers = new Map(); + this._peers = {}; } close() @@ -23,27 +23,28 @@ class Lobby extends EventEmitter this._closed = true; - this._peers.forEach((peer) => + // Close the peers. + for (const peer in this._peers) { if (!peer.closed) peer.close(); - }); + } - this._peers.clear(); + this._peers = null; } checkEmpty() { logger.info('checkEmpty()'); - return this._peers.size === 0; + return Object.keys(this._peers).length === 0; } peerList() { logger.info('peerList()'); - return Array.from(this._peers.values()).map((peer) => + return Object.values(this._peers).map((peer) => ({ peerId : peer.id, displayName : peer.displayName @@ -52,25 +53,25 @@ class Lobby extends EventEmitter hasPeer(peerId) { - return this._peers.has(peerId); + return this._peers[peerId] != null; } promoteAllPeers() { logger.info('promoteAllPeers()'); - this._peers.forEach((peer) => + for (const peer in this._peers) { if (peer.socket) this.promotePeer(peer.id); - }); + } } promotePeer(peerId) { logger.info('promotePeer() [peer:"%s"]', peerId); - const peer = this._peers.get(peerId); + const peer = this._peers[peerId]; if (peer) { @@ -87,7 +88,7 @@ class Lobby extends EventEmitter peer.closeHandler = null; this.emit('promotePeer', peer); - this._peers.delete(peerId); + delete this._peers[peerId]; } } @@ -146,7 +147,7 @@ class Lobby extends EventEmitter this.emit('peerClosed', peer); - this._peers.delete(peer.id); + delete this._peers[peer.id]; if (this.checkEmpty()) this.emit('lobbyEmpty'); @@ -154,7 +155,7 @@ class Lobby extends EventEmitter this._notification(peer.socket, 'enteredLobby'); - this._peers.set(peer.id, peer); + this._peers[peer.id] = peer; peer.on('gotRole', peer.gotRoleHandler); peer.on('displayNameChanged', peer.displayNameChangeHandler); diff --git a/server/lib/Room.js b/server/lib/Room.js index 1fed90b..af5bea5 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -100,11 +100,8 @@ class Room extends EventEmitter // Close the peers. for (const peer in this._peers) { - if (Object.prototype.hasOwnProperty.call(this._peers, peer)) - { - if (!peer.closed) - peer.close(); - } + if (!peer.closed) + peer.close(); } this._peers = null; @@ -313,7 +310,6 @@ class Room extends EventEmitter }, 10000); } - // checks both room and lobby checkEmpty() { return Object.keys(this._peers).length === 0; @@ -333,12 +329,8 @@ class Room extends EventEmitter { peer.socket.join(this._roomId); - const index = this._lastN.indexOf(peer.id); - - if (index === -1) // We don't have this peer, add to end - { - this._lastN.push(peer.id); - } + // If we don't have this peer, add to end + !this._lastN.includes(peer.id) && this._lastN.push(peer.id); this._peers[peer.id] = peer; @@ -372,25 +364,17 @@ class Room extends EventEmitter // If the Peer was joined, notify all Peers. if (peer.joined) - { this._notification(peer.socket, 'peerClosed', { peerId: peer.id }, true); - } - const index = this._lastN.indexOf(peer.id); - - if (index > -1) // We have this peer in the list, remove - { - this._lastN.splice(index, 1); - } + // Remove from lastN + this._lastN = this._lastN.filter((id) => id !== peer.id); delete this._peers[peer.id]; // If this is the last Peer in the room and // lobby is empty, close the room after a while. if (this.checkEmpty() && this._lobby.checkEmpty()) - { this.selfDestructCountdown(); - } }); peer.on('displayNameChanged', ({ oldDisplayName }) => From 04b2d6d4435f81ef6243586e9dbd03185b519381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 01:43:40 +0100 Subject: [PATCH 17/21] Add "close meeting" function for moderator --- app/src/RoomClient.js | 20 +++++++++ app/src/actions/roomActions.js | 6 +++ .../ParticipantList/ListModerator.js | 17 +++++++ app/src/reducers/room.js | 44 +++++++++++-------- server/lib/Room.js | 27 ++++++++++++ 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index ad4955a..520b034 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1178,6 +1178,26 @@ export default class RoomClient roomActions.setStopAllVideoInProgress(false)); } + async closeMeeting() + { + logger.debug('closeMeeting()'); + + store.dispatch( + roomActions.setCloseMeetingInProgress(true)); + + try + { + await this.sendRequest('moderator:closeMeeting'); + } + catch (error) + { + logger.error('closeMeeting() failed: %o', error); + } + + store.dispatch( + roomActions.setCloseMeetingInProgress(false)); + } + // type: mic/webcam/screen // mute: true/false async modifyPeerConsumer(peerId, type, mute) diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index 9ad1e08..6003b9e 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -121,4 +121,10 @@ export const setStopAllVideoInProgress = (flag) => ({ type : 'STOP_ALL_VIDEO_IN_PROGRESS', payload : { flag } + }); + +export const setCloseMeetingInProgress = (flag) => + ({ + type : 'CLOSE_MEETING_IN_PROGRESS', + payload : { flag } }); \ No newline at end of file diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js index 66f64bd..1c711a1 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js @@ -71,6 +71,23 @@ const ListModerator = (props) => defaultMessage='Stop all video' /> +
+
); }; diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js index 9d483b8..d963a0c 100644 --- a/app/src/reducers/room.js +++ b/app/src/reducers/room.js @@ -1,24 +1,27 @@ const initialState = { - name : '', - state : 'new', // new/connecting/connected/disconnected/closed, - locked : false, - inLobby : false, - signInRequired : false, - accessCode : '', // access code to the room if locked and joinByAccessCode == true - joinByAccessCode : true, // if true: accessCode is a possibility to open the room - activeSpeakerId : null, - torrentSupport : false, - showSettings : false, - fullScreenConsumer : null, // ConsumerID - windowConsumer : null, // ConsumerID - toolbarsVisible : true, - mode : 'democratic', - selectedPeerId : null, - spotlights : [], - settingsOpen : false, - lockDialogOpen : false, - joined : false + name : '', + state : 'new', // new/connecting/connected/disconnected/closed, + locked : false, + inLobby : false, + signInRequired : false, + accessCode : '', // access code to the room if locked and joinByAccessCode == true + joinByAccessCode : true, // if true: accessCode is a possibility to open the room + activeSpeakerId : null, + torrentSupport : false, + showSettings : false, + fullScreenConsumer : null, // ConsumerID + windowConsumer : null, // ConsumerID + toolbarsVisible : true, + mode : 'democratic', + selectedPeerId : null, + spotlights : [], + settingsOpen : false, + lockDialogOpen : false, + joined : false, + muteAllInProgress : false, + stopAllVideoInProgress : false, + closeMeetingInProgress : false }; const room = (state = initialState, action) => @@ -169,6 +172,9 @@ const room = (state = initialState, action) => case 'STOP_ALL_VIDEO_IN_PROGRESS': return { ...state, stopAllVideoInProgress: action.payload.flag }; + case 'CLOSE_MEETING_IN_PROGRESS': + return { ...state, closeMeetingInProgress: action.payload.flag }; + default: return state; } diff --git a/server/lib/Room.js b/server/lib/Room.js index af5bea5..6ac3746 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1105,6 +1105,33 @@ class Room extends EventEmitter break; } + case 'moderator:closeMeeting': + { + if ( + !peer.hasRole(userRoles.MODERATOR) && + !peer.hasRole(userRoles.ADMIN) + ) + throw new Error('peer does not have moderator priveleges'); + + this._notification( + peer.socket, + 'moderator:kick', + null, + true + ); + + // Close the peers. + for (const kickedPeer in this._peers) + { + if (peer !== kickedPeer) + kickedPeer.close(); + } + + cb(); + + break; + } + case 'moderator:kickPeer': { if ( From 253de07d95b750738a8bfcb0fef985f4c6e518db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 10:42:55 +0100 Subject: [PATCH 18/21] Update translations, needs translation --- app/src/translations/cn.json | 4 ++++ app/src/translations/de.json | 4 ++++ app/src/translations/dk.json | 4 ++++ app/src/translations/el.json | 4 ++++ app/src/translations/en.json | 4 ++++ app/src/translations/es.json | 4 ++++ app/src/translations/fr.json | 4 ++++ app/src/translations/hr.json | 4 ++++ app/src/translations/hu.json | 4 ++++ app/src/translations/nb.json | 4 ++++ app/src/translations/pl.json | 4 ++++ app/src/translations/pt.json | 4 ++++ app/src/translations/ro.json | 4 ++++ 13 files changed, 52 insertions(+) diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 2c99211..a8a4f85 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -48,6 +48,9 @@ "room.spotlights": "Spotlight中的参与者", "room.passive": "被动参与者", "room.videoPaused": "该视频已暂停", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "登录", "tooltip.logout": "注销", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "退出全屏", "tooltip.lobby": "显示大厅", "tooltip.settings": "显示设置", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "房间名称", "label.chooseRoomButton": "继续", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 476c048..d7195e1 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -48,6 +48,9 @@ "room.spotlights": "Aktive Teinehmer", "room.passive": "Passive Teilnehmer", "room.videoPaused": "Video gestoppt", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Anmelden", "tooltip.logout": "Abmelden", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Vollbild verlassen", "tooltip.lobby": "Warteraum", "tooltip.settings": "Einstellungen", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Room name", "label.chooseRoomButton": "Continue", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index b46ca92..9832483 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -48,6 +48,9 @@ "room.spotlights": "Deltagere i fokus", "room.passive": "Passive deltagere", "room.videoPaused": "Denne video er sat på pause", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Log ind", "tooltip.logout": "Log ud", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Efterlad fuldskærm", "tooltip.lobby": "Vis lobby", "tooltip.settings": "Vis indstillinger", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Værelsesnavn", "label.chooseRoomButton": "Fortsæt", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 7a1d04e..a6ae4f1 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -48,6 +48,9 @@ "room.spotlights": "Συμμετέχοντες στο Spotlight", "room.passive": "Παθητικοί συμμετέχοντες", "room.videoPaused": "Το βίντεο έχει σταματήσει", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Σύνδεση", "tooltip.logout": "Αποσύνδεση", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Έξοδος από την πλήρη οθόνη", "tooltip.lobby": "Εμφάνιση λόμπι", "tooltip.settings": "Εμφάνιση ρυθμίσεων", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Όνομα δωματίου", "label.chooseRoomButton": "Συνέχεια", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 2df5c04..9b0db07 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -48,6 +48,9 @@ "room.spotlights": "Participants in Spotlight", "room.passive": "Passive Participants", "room.videoPaused": "This video is paused", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Log in", "tooltip.logout": "Log out", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Leave fullscreen", "tooltip.lobby": "Show lobby", "tooltip.settings": "Show settings", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Room name", "label.chooseRoomButton": "Continue", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index b4afe4a..f4ac0c5 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -48,6 +48,9 @@ "room.spotlights": "Participantes destacados", "room.passive": "Participantes pasivos", "room.videoPaused": "El vídeo está pausado", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Entrar", "tooltip.logout": "Salir", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Salir de la pantalla completa", "tooltip.lobby": "Mostrar sala de espera", "tooltip.settings": "Mostrar ajustes", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Nombre de la sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 34f5ce5..b43beca 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -48,6 +48,9 @@ "room.spotlights" : " Participants actifs", "room.passive" : " Participants passifs", "room.videoPaused" : " La vidéo est en pause", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login" : " Connexion", "tooltip.logout" : " Déconnexion", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen" : " Quitter le plein écran", "tooltip.lobby" : " Afficher la salle d'attente", "tooltip.settings" : " Afficher les paramètres", + "tooltip.kickParticipant": "Kick out participant", "label.roomName" : " Nom de la salle", "label.chooseRoomButton" : " Continuer", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index b56333b..ceda4a8 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -48,6 +48,9 @@ "room.spotlights": "Učesnici u fokusu", "room.passive": "Pasivni učesnici", "room.videoPaused": "Video pauziran", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Prijava", "tooltip.logout": "Odjava", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Izađi iz punog ekrana", "tooltip.lobby": "Prikaži predvorje", "tooltip.settings": "Prikaži postavke", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Naziv sobe", "label.chooseRoomButton": "Nastavi", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 32d9f9c..f4e543d 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -48,6 +48,9 @@ "room.spotlights": "Látható résztvevők", "room.passive": "Passzív résztvevők", "room.videoPaused": "Ez a videóstream szünetel", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Belépés", "tooltip.logout": "Kilépés", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Kilépés teljes képernyős módból", "tooltip.lobby": "Az előszobában várakozók listája", "tooltip.settings": "Beállítások", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 3f017e0..52620e9 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -48,6 +48,9 @@ "room.spotlights": "Deltakere i fokus", "room.passive": "Passive deltakere", "room.videoPaused": "Denne videoen er inaktiv", + "room.muteAll": "Demp alle", + "room.stopAllVideo": "Stopp all video", + "room.closeMeeting": "Avslutt møte", "tooltip.login": "Logg in", "tooltip.logout": "Logg ut", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Forlat fullskjerm", "tooltip.lobby": "Vis lobby", "tooltip.settings": "Vis innstillinger", + "tooltip.kickParticipant": "Spark ut deltaker", "label.roomName": "Møtenavn", "label.chooseRoomButton": "Fortsett", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 60367f2..8aec8a5 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -48,6 +48,9 @@ "room.spotlights": "Aktywni uczestnicy", "room.passive": "Pasywni uczestnicy", "room.videoPaused": "To wideo jest wstrzymane.", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Zaloguj", "tooltip.logout": "Wyloguj", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Wyłącz tryb pełnoekranowy", "tooltip.lobby": "Pokaż poczekalnię", "tooltip.settings": "Pokaż ustawienia", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Nazwa konferencji", "label.chooseRoomButton": "Kontynuuj", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index b371966..e72445f 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -48,6 +48,9 @@ "room.spotlights": "Participantes em foco", "room.passive": "Participantes passivos", "room.videoPaused": "Este vídeo está em pausa", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Entrar", "tooltip.logout": "Sair", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Sair de ecrã completo", "tooltip.lobby": "Apresentar sala de espera", "tooltip.settings": "Apresentar definições", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Nome da sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 56c6b24..4ca59b8 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -48,6 +48,9 @@ "room.spotlights": "Participanți în Spotlight", "room.passive": "Participanți pasivi", "room.videoPaused": "Acest video este pus pe pauză", + "room.muteAll": "Mute all", + "room.stopAllVideo": "Stop all video", + "room.closeMeeting": "Close meeting", "tooltip.login": "Intră în cont", "tooltip.logout": "Deconectare", @@ -58,6 +61,7 @@ "tooltip.leaveFullscreen": "Ieșire din modul ecran complet", "tooltip.lobby": "Arată holul", "tooltip.settings": "Arată setăile", + "tooltip.kickParticipant": "Kick out participant", "label.roomName": "Numele camerei", "label.chooseRoomButton": "Continuare", From 0b62856414e14a00d3f7f89918deddfc627f90a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 11:04:44 +0100 Subject: [PATCH 19/21] Messages that are not translated are now null --- app/src/translations/cn.json | 8 +- app/src/translations/de.json | 14 +- app/src/translations/dk.json | 10 +- app/src/translations/el.json | 12 +- app/src/translations/es.json | 252 +++++++++++++++++------------------ app/src/translations/fr.json | 252 +++++++++++++++++------------------ app/src/translations/hr.json | 8 +- app/src/translations/hu.json | 10 +- app/src/translations/pl.json | 8 +- app/src/translations/pt.json | 8 +- app/src/translations/ro.json | 8 +- 11 files changed, 295 insertions(+), 295 deletions(-) diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index a8a4f85..d8c12e8 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -48,9 +48,9 @@ "room.spotlights": "Spotlight中的参与者", "room.passive": "被动参与者", "room.videoPaused": "该视频已暂停", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "登录", "tooltip.logout": "注销", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "退出全屏", "tooltip.lobby": "显示大厅", "tooltip.settings": "显示设置", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "房间名称", "label.chooseRoomButton": "继续", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index d7195e1..74df546 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -4,7 +4,7 @@ "socket.reconnected": "Verbindung wieder hergestellt", "socket.requestError": "Fehler bei Serveranfrage", - "room.chooseRoom": "Choose the name of the room you would like to join", + "room.chooseRoom": null, "room.cookieConsent": "Diese Seite verwendet Cookies, um die Benutzerfreundlichkeit zu erhöhen", "room.joined": "Konferenzraum betreten", "room.cantJoin": "Betreten des Raumes nicht möglich", @@ -48,9 +48,9 @@ "room.spotlights": "Aktive Teinehmer", "room.passive": "Passive Teilnehmer", "room.videoPaused": "Video gestoppt", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Anmelden", "tooltip.logout": "Abmelden", @@ -61,10 +61,10 @@ "tooltip.leaveFullscreen": "Vollbild verlassen", "tooltip.lobby": "Warteraum", "tooltip.settings": "Einstellungen", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, - "label.roomName": "Room name", - "label.chooseRoomButton": "Continue", + "label.roomName": null, + "label.chooseRoomButton": null, "label.yourName": "Dein Name", "label.newWindow": "In separatem Fenster öffnen", "label.fullscreen": "Vollbild", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 9832483..0629dab 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -48,9 +48,9 @@ "room.spotlights": "Deltagere i fokus", "room.passive": "Passive deltagere", "room.videoPaused": "Denne video er sat på pause", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Log ind", "tooltip.logout": "Log ud", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Efterlad fuldskærm", "tooltip.lobby": "Vis lobby", "tooltip.settings": "Vis indstillinger", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Værelsesnavn", "label.chooseRoomButton": "Fortsæt", @@ -106,7 +106,7 @@ "filesharing.finished": "Filen er færdig med at downloade", "filesharing.save": "Gem", "filesharing.sharedFile": "{displayName} delte en fil", - "filesharing.download": "Download", + "filesharing.download": null, "filesharing.missingSeeds": "Hvis denne proces tager lang tid, er der muligvis ikke nogen, der seedede denne torrent. Prøv at bede nogen om at uploade den fil, du ønsker at hente.", "device.devicesChanged": "Detekteret ndringer i dine enheder, konfigurer dine enheder i indstillingsdialogen", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index a6ae4f1..4134c0f 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -48,9 +48,9 @@ "room.spotlights": "Συμμετέχοντες στο Spotlight", "room.passive": "Παθητικοί συμμετέχοντες", "room.videoPaused": "Το βίντεο έχει σταματήσει", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Σύνδεση", "tooltip.logout": "Αποσύνδεση", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Έξοδος από την πλήρη οθόνη", "tooltip.lobby": "Εμφάνιση λόμπι", "tooltip.settings": "Εμφάνιση ρυθμίσεων", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Όνομα δωματίου", "label.chooseRoomButton": "Συνέχεια", @@ -77,8 +77,8 @@ "label.shareFile": "Διαμοιραστείτε ένα αρχείο", "label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται", "label.unknown": "Άγνωστο", - "label.democratic": "Democratic view", - "label.filmstrip": "Filmstrip view", + "label.democratic": null, + "label.filmstrip": null, "label.low": "Χαμηλή", "label.medium": "Μέτρια", "label.high": "Υψηλή (HD)", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index f4ac0c5..b871885 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -1,140 +1,140 @@ { - "socket.disconnected": "Desconectado", - "socket.reconnecting": "Desconectado, intentando reconectar", - "socket.reconnected": "Reconectado", - "socket.requestError": "Error en la petición al servidor", + "socket.disconnected": "Desconectado", + "socket.reconnecting": "Desconectado, intentando reconectar", + "socket.reconnected": "Reconectado", + "socket.requestError": "Error en la petición al servidor", - "room.chooseRoom": "Indique el nombre de la sala a la que le gustaría unirse", - "room.cookieConsent": "Esta web utiliza cookies para mejorar la experiencia de usuario", - "room.joined": "Se ha unido a la sala", - "room.cantJoin": "No ha sido posible unirse a la sala", - "room.youLocked": "Ha cerrado la sala", - "room.cantLock": "No ha sido posible cerrar la sala", - "room.youUnLocked": "Ha abierto la sala", - "room.cantUnLock": "No ha sido posible abrir la sala", - "room.locked": "La sala ahora es privada", - "room.unlocked": "La sala ahora es pública", - "room.newLobbyPeer": "Nuevo participante en la sala de espera", - "room.lobbyPeerLeft": "Un participante en espera ha salido", - "room.lobbyPeerChangedDisplayName": "Participante en espera cambió su nombre a {displayName}", - "room.lobbyPeerChangedPicture": "Participante en espera cambió su foto", - "room.setAccessCode": "Código de acceso de la sala actualizado", - "room.accessCodeOn": "Código de acceso de la sala activado", - "room.accessCodeOff": "Código de acceso de la sala desactivado", - "room.peerChangedDisplayName": "{oldDisplayName} es ahora {displayName}", - "room.newPeer": "{displayName} se unió a la sala", - "room.newFile": "Nuevo fichero disponible", - "room.toggleAdvancedMode": "Cambiado a modo avanzado", - "room.setDemocraticView": "Cambiado a modo democrático", - "room.setFilmStripView": "Cambiado a modo viñeta", - "room.loggedIn": "Ha iniciado sesión", - "room.loggedOut": "Ha cerrado su sesión", - "room.changedDisplayName": "Ha cambiado su nombre a {displayName}", - "room.changeDisplayNameError": "Hubo un error al intentar cambiar su nombre", - "room.chatError": "No ha sido posible enviar su mensaje", - "room.aboutToJoin": "Está a punto de unirse a una reunión", - "room.roomId": "ID de la sala: {roomName}", - "room.setYourName": "Indique el nombre con el que quiere participar y cómo quiere unirse:", - "room.audioOnly": "Solo sonido", - "room.audioVideo": "Sonido y vídeo", - "room.youAreReady": "Ok, está preparado", - "room.emptyRequireLogin": "¡La sala está vacía! Puede iniciar sesión para comenzar la reunión o esperar hasta que el anfitrión se una", - "room.locketWait": "La sala es privada - espere hasta que alguien le invite ...", - "room.lobbyAdministration": "Administración de la sala de espera", - "room.peersInLobby": "Participantes en la sala de espera", - "room.lobbyEmpty": "La sala de espera está vacía", - "room.hiddenPeers": "{hiddenPeersCount, plural, one {participante} other {participantes}}", - "room.me": "Yo", - "room.spotlights": "Participantes destacados", - "room.passive": "Participantes pasivos", - "room.videoPaused": "El vídeo está pausado", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.chooseRoom": "Indique el nombre de la sala a la que le gustaría unirse", + "room.cookieConsent": "Esta web utiliza cookies para mejorar la experiencia de usuario", + "room.joined": "Se ha unido a la sala", + "room.cantJoin": "No ha sido posible unirse a la sala", + "room.youLocked": "Ha cerrado la sala", + "room.cantLock": "No ha sido posible cerrar la sala", + "room.youUnLocked": "Ha abierto la sala", + "room.cantUnLock": "No ha sido posible abrir la sala", + "room.locked": "La sala ahora es privada", + "room.unlocked": "La sala ahora es pública", + "room.newLobbyPeer": "Nuevo participante en la sala de espera", + "room.lobbyPeerLeft": "Un participante en espera ha salido", + "room.lobbyPeerChangedDisplayName": "Participante en espera cambió su nombre a {displayName}", + "room.lobbyPeerChangedPicture": "Participante en espera cambió su foto", + "room.setAccessCode": "Código de acceso de la sala actualizado", + "room.accessCodeOn": "Código de acceso de la sala activado", + "room.accessCodeOff": "Código de acceso de la sala desactivado", + "room.peerChangedDisplayName": "{oldDisplayName} es ahora {displayName}", + "room.newPeer": "{displayName} se unió a la sala", + "room.newFile": "Nuevo fichero disponible", + "room.toggleAdvancedMode": "Cambiado a modo avanzado", + "room.setDemocraticView": "Cambiado a modo democrático", + "room.setFilmStripView": "Cambiado a modo viñeta", + "room.loggedIn": "Ha iniciado sesión", + "room.loggedOut": "Ha cerrado su sesión", + "room.changedDisplayName": "Ha cambiado su nombre a {displayName}", + "room.changeDisplayNameError": "Hubo un error al intentar cambiar su nombre", + "room.chatError": "No ha sido posible enviar su mensaje", + "room.aboutToJoin": "Está a punto de unirse a una reunión", + "room.roomId": "ID de la sala: {roomName}", + "room.setYourName": "Indique el nombre con el que quiere participar y cómo quiere unirse:", + "room.audioOnly": "Solo sonido", + "room.audioVideo": "Sonido y vídeo", + "room.youAreReady": "Ok, está preparado", + "room.emptyRequireLogin": "¡La sala está vacía! Puede iniciar sesión para comenzar la reunión o esperar hasta que el anfitrión se una", + "room.locketWait": "La sala es privada - espere hasta que alguien le invite ...", + "room.lobbyAdministration": "Administración de la sala de espera", + "room.peersInLobby": "Participantes en la sala de espera", + "room.lobbyEmpty": "La sala de espera está vacía", + "room.hiddenPeers": "{hiddenPeersCount, plural, one {participante} other {participantes}}", + "room.me": "Yo", + "room.spotlights": "Participantes destacados", + "room.passive": "Participantes pasivos", + "room.videoPaused": "El vídeo está pausado", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, - "tooltip.login": "Entrar", - "tooltip.logout": "Salir", - "tooltip.admitFromLobby": "Admitir desde la sala de espera", - "tooltip.lockRoom": "Configurar sala como privada", - "tooltip.unLockRoom": "Configurar sala como pública", - "tooltip.enterFullscreen": "Presentar en pantalla completa", - "tooltip.leaveFullscreen": "Salir de la pantalla completa", - "tooltip.lobby": "Mostrar sala de espera", - "tooltip.settings": "Mostrar ajustes", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.login": "Entrar", + "tooltip.logout": "Salir", + "tooltip.admitFromLobby": "Admitir desde la sala de espera", + "tooltip.lockRoom": "Configurar sala como privada", + "tooltip.unLockRoom": "Configurar sala como pública", + "tooltip.enterFullscreen": "Presentar en pantalla completa", + "tooltip.leaveFullscreen": "Salir de la pantalla completa", + "tooltip.lobby": "Mostrar sala de espera", + "tooltip.settings": "Mostrar ajustes", + "tooltip.kickParticipant": null, - "label.roomName": "Nombre de la sala", - "label.chooseRoomButton": "Continuar", - "label.yourName": "Su nombre", - "label.newWindow": "Nueva ventana", - "label.fullscreen": "Pantalla completa", - "label.openDrawer": "Abrir panel", - "label.leave": "Salir", - "label.chatInput": "Escriba su mensaje...", - "label.chat": "Chat", - "label.filesharing": "Compartir ficheros", - "label.participants": "Participantes", - "label.shareFile": "Compartir fichero", - "label.fileSharingUnsupported": "Compartir ficheros no está disponible", - "label.unknown": "Desconocido", - "label.democratic": "Vista democrática", - "label.filmstrip": "Vista en viñeta", - "label.low": "Baja", - "label.medium": "Media", - "label.high": "Alta (HD)", - "label.veryHigh": "Muy alta (FHD)", - "label.ultra": "Ultra (UHD)", - "label.close": "Cerrar", + "label.roomName": "Nombre de la sala", + "label.chooseRoomButton": "Continuar", + "label.yourName": "Su nombre", + "label.newWindow": "Nueva ventana", + "label.fullscreen": "Pantalla completa", + "label.openDrawer": "Abrir panel", + "label.leave": "Salir", + "label.chatInput": "Escriba su mensaje...", + "label.chat": "Chat", + "label.filesharing": "Compartir ficheros", + "label.participants": "Participantes", + "label.shareFile": "Compartir fichero", + "label.fileSharingUnsupported": "Compartir ficheros no está disponible", + "label.unknown": "Desconocido", + "label.democratic": "Vista democrática", + "label.filmstrip": "Vista en viñeta", + "label.low": "Baja", + "label.medium": "Media", + "label.high": "Alta (HD)", + "label.veryHigh": "Muy alta (FHD)", + "label.ultra": "Ultra (UHD)", + "label.close": "Cerrar", - "settings.settings": "Ajustes", - "settings.camera": "Cámara", - "settings.selectCamera": "Seleccionar dispositivo de vídeo", - "settings.cantSelectCamera": "No ha sido posible seleccionar el dispositivo de vídeo", - "settings.audio": "Dispositivo de sonido", - "settings.selectAudio": "Seleccione dispositivo de sonido", - "settings.cantSelectAudio": "No ha sido posible seleccionar el dispositivo de sonido", - "settings.resolution": "Seleccione su resolución de imagen", - "settings.layout": "Disposición de la sala", - "settings.selectRoomLayout": "Seleccione la disposición de la sala", - "settings.advancedMode": "Modo avanzado", + "settings.settings": "Ajustes", + "settings.camera": "Cámara", + "settings.selectCamera": "Seleccionar dispositivo de vídeo", + "settings.cantSelectCamera": "No ha sido posible seleccionar el dispositivo de vídeo", + "settings.audio": "Dispositivo de sonido", + "settings.selectAudio": "Seleccione dispositivo de sonido", + "settings.cantSelectAudio": "No ha sido posible seleccionar el dispositivo de sonido", + "settings.resolution": "Seleccione su resolución de imagen", + "settings.layout": "Disposición de la sala", + "settings.selectRoomLayout": "Seleccione la disposición de la sala", + "settings.advancedMode": "Modo avanzado", - "filesharing.saveFileError": "No ha sido posible guardar el fichero", - "filesharing.startingFileShare": "Intentando compartir el fichero", - "filesharing.successfulFileShare": "El fichero se compartió con éxito", - "filesharing.unableToShare": "No ha sido posible compartir el fichero", - "filesharing.error": "Hubo un error al compartir el fichero", - "filesharing.finished": "Descarga del fichero finalizada", - "filesharing.save": "Guardar", - "filesharing.sharedFile": "{displayName} compartió un fichero", - "filesharing.download": "Descargar", - "filesharing.missingSeeds": "Si este proceso demora en exceso, puede ocurrir que no haya nadie compartiendo el fichero. Pruebe a pedirle a alguien que vuelva a subir el fichero que busca.", + "filesharing.saveFileError": "No ha sido posible guardar el fichero", + "filesharing.startingFileShare": "Intentando compartir el fichero", + "filesharing.successfulFileShare": "El fichero se compartió con éxito", + "filesharing.unableToShare": "No ha sido posible compartir el fichero", + "filesharing.error": "Hubo un error al compartir el fichero", + "filesharing.finished": "Descarga del fichero finalizada", + "filesharing.save": "Guardar", + "filesharing.sharedFile": "{displayName} compartió un fichero", + "filesharing.download": "Descargar", + "filesharing.missingSeeds": "Si este proceso demora en exceso, puede ocurrir que no haya nadie compartiendo el fichero. Pruebe a pedirle a alguien que vuelva a subir el fichero que busca.", - "devices.devicesChanged": "Sus dispositivos han cambiado, vuelva a configurarlos en la ventana de ajustes", + "devices.devicesChanged": "Sus dispositivos han cambiado, vuelva a configurarlos en la ventana de ajustes", - "device.audioUnsupported": "Sonido no disponible", - "device.activateAudio": "Activar sonido", - "device.muteAudio": "Silenciar sonido", - "device.unMuteAudio": "Reactivar sonido", + "device.audioUnsupported": "Sonido no disponible", + "device.activateAudio": "Activar sonido", + "device.muteAudio": "Silenciar sonido", + "device.unMuteAudio": "Reactivar sonido", - "device.videoUnsupported": "Vídeo no disponible", - "device.startVideo": "Iniciar vídeo", - "device.stopVideo": "Detener vídeo", + "device.videoUnsupported": "Vídeo no disponible", + "device.startVideo": "Iniciar vídeo", + "device.stopVideo": "Detener vídeo", - "device.screenSharingUnsupported": "Compartir pantalla no disponible", - "device.startScreenSharing": "Iniciar compartir pantalla", - "device.stopScreenSharing": "Detener compartir pantalla", + "device.screenSharingUnsupported": "Compartir pantalla no disponible", + "device.startScreenSharing": "Iniciar compartir pantalla", + "device.stopScreenSharing": "Detener compartir pantalla", - "devices.microphoneDisconnected": "Micrófono desconectado", - "devices.microphoneError": "Hubo un error al acceder a su micrófono", - "devices.microPhoneMute": "Desactivar micrófono", - "devices.micophoneUnMute": "Activar micrófono", - "devices.microphoneEnable": "Micrófono activado", - "devices.microphoneMuteError": "No ha sido posible desactivar su micrófono", - "devices.microphoneUnMuteError": "No ha sido posible activar su micrófono", + "devices.microphoneDisconnected": "Micrófono desconectado", + "devices.microphoneError": "Hubo un error al acceder a su micrófono", + "devices.microPhoneMute": "Desactivar micrófono", + "devices.micophoneUnMute": "Activar micrófono", + "devices.microphoneEnable": "Micrófono activado", + "devices.microphoneMuteError": "No ha sido posible desactivar su micrófono", + "devices.microphoneUnMuteError": "No ha sido posible activar su micrófono", - "devices.screenSharingDisconnected": "Pantalla compartida desconectada", - "devices.screenSharingError": "Hubo un error al acceder a su pantalla", + "devices.screenSharingDisconnected": "Pantalla compartida desconectada", + "devices.screenSharingError": "Hubo un error al acceder a su pantalla", - "devices.cameraDisconnected": "Cámara desconectada", - "devices.cameraError": "Hubo un error al acceder a su cámara" + "devices.cameraDisconnected": "Cámara desconectada", + "devices.cameraError": "Hubo un error al acceder a su cámara" } diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index b43beca..37b02f1 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -1,139 +1,139 @@ { - "socket.disconnected" : " Vous avez été déconnecté", - "socket.reconnecting" : " Vous avez été déconnecté, reconnexion en cours", - "socket.reconnected" : " Vous êtes reconnecté", - "socket.requestError" : " Erreur sur une requête serveur", + "socket.disconnected": "Vous avez été déconnecté", + "socket.reconnecting": "Vous avez été déconnecté, reconnexion en cours", + "socket.reconnected": "Vous êtes reconnecté", + "socket.requestError": "Erreur sur une requête serveur", - "room.chooseRoom" : " Choisissez le nom de la réunion que vous souhaitez rejoindre", - "room.cookieConsent" : " Ce site utilise les cookies pour améliorer votre expérience utilisateur", - "room.joined" : " Vous avez rejoint la salle", - "room.cantJoin" : " Impossible de rejoindre la salle", - "room.youLocked" : " Vous avez privatisé la salle", - "room.cantLock" : " Impossible de privatiser la salle", - "room.youUnLocked" : " Vous avez dé-privatiser la salle", - "room.cantUnLock" : " Impossible de dé-privatiser la réunion", - "room.locked" : " La réunion est privée", - "room.unlocked" : " La réunion est publique", - "room.newLobbyPeer" : " Un nouveau participant est dans la salle d’attente", - "room.lobbyPeerLeft" : " Un participant de la salle d’attente vient de partir", - "room.lobbyPeerChangedDisplayName" : " Un participant dans la salle d’attente a changé de nom pour {displayName}", - "room.lobbyPeerChangedPicture" : " Un participant dans le hall à changer de photo", - "room.setAccessCode" : " Code d’accès à la réunion mis à jour", - "room.accessCodeOn" : " Code d’accès à la réunion activée", - "room.accessCodeOff" : " Code d’accès à la réunion désactivée", - "room.peerChangedDisplayName" : " {oldDisplayName} est maintenant {displayName}", - "room.newPeer" : " {displayName} a rejoint la salle", - "room.newFile" : " Nouveau fichier disponible", - "room.toggleAdvancedMode" : " Basculer en mode avancé", - "room.setDemocraticView" : " Passer en vue démocratique", - "room.setFilmStripView" : " Passer en vue vignette", - "room.loggedIn" : " Vous êtes connecté", - "room.loggedOut" : " Vous êtes déconnecté", - "room.changedDisplayName" : " Votre nom à changer pour {displayname}", - "room.changeDisplayNameError" : " Une erreur s’est produite pour votre changement de nom", - "room.chatError" : " Impossible d’envoyer un message", - "room.aboutToJoin" : " Vous allez rejoindre une réunion", - "room.roomId" : " Salle ID: {roomName}", - "room.setYourName" : " Choisissez votre nom de participant puis comment vous connecter :", - "room.audioOnly" : " Audio uniquement", - "room.audioVideo" : " Audio et Vidéo", - "room.youAreReady" : " Ok, vous êtes prêt", - "room.emptyRequireLogin" : " La réunion est vide ! Vous pouvez vous connecter pour commencer la réunion ou attendre qu'un hôte se connecte", - "room.locketWait" : " La réunion est privatisée - attendez que quelqu’un vous laisse entrer", - "room.lobbyAdministration" : " Administration de la salle d’attente", - "room.peersInLobby" : " Participants dans la salle d’attente", - "room.lobbyEmpty" : " Il n'y a actuellement aucun participant dans la salle d'attente", - "room.hiddenPeers" : " {hiddenPeersCount, plural, one {participant} other {participants}}", - "room.me" : " Moi", - "room.spotlights" : " Participants actifs", - "room.passive" : " Participants passifs", - "room.videoPaused" : " La vidéo est en pause", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.chooseRoom": "Choisissez le nom de la réunion que vous souhaitez rejoindre", + "room.cookieConsent": "Ce site utilise les cookies pour améliorer votre expérience utilisateur", + "room.joined": "Vous avez rejoint la salle", + "room.cantJoin": "Impossible de rejoindre la salle", + "room.youLocked": "Vous avez privatisé la salle", + "room.cantLock": "Impossible de privatiser la salle", + "room.youUnLocked": "Vous avez dé-privatiser la salle", + "room.cantUnLock": "Impossible de dé-privatiser la réunion", + "room.locked": "La réunion est privée", + "room.unlocked": "La réunion est publique", + "room.newLobbyPeer": "Un nouveau participant est dans la salle d’attente", + "room.lobbyPeerLeft": "Un participant de la salle d’attente vient de partir", + "room.lobbyPeerChangedDisplayName": "Un participant dans la salle d’attente a changé de nom pour {displayName}", + "room.lobbyPeerChangedPicture": "Un participant dans le hall à changer de photo", + "room.setAccessCode": "Code d’accès à la réunion mis à jour", + "room.accessCodeOn": "Code d’accès à la réunion activée", + "room.accessCodeOff": "Code d’accès à la réunion désactivée", + "room.peerChangedDisplayName": "{oldDisplayName} est maintenant {displayName}", + "room.newPeer": "{displayName} a rejoint la salle", + "room.newFile": "Nouveau fichier disponible", + "room.toggleAdvancedMode": "Basculer en mode avancé", + "room.setDemocraticView": "Passer en vue démocratique", + "room.setFilmStripView": "Passer en vue vignette", + "room.loggedIn": "Vous êtes connecté", + "room.loggedOut": "Vous êtes déconnecté", + "room.changedDisplayName": "Votre nom à changer pour {displayname}", + "room.changeDisplayNameError": "Une erreur s’est produite pour votre changement de nom", + "room.chatError": "Impossible d’envoyer un message", + "room.aboutToJoin": "Vous allez rejoindre une réunion", + "room.roomId": "Salle ID: {roomName}", + "room.setYourName": "Choisissez votre nom de participant puis comment vous connecter:", + "room.audioOnly": "Audio uniquement", + "room.audioVideo": "Audio et Vidéo", + "room.youAreReady": "Ok, vous êtes prêt", + "room.emptyRequireLogin": "La réunion est vide ! Vous pouvez vous connecter pour commencer la réunion ou attendre qu'un hôte se connecte", + "room.locketWait": "La réunion est privatisée - attendez que quelqu’un vous laisse entrer", + "room.lobbyAdministration": "Administration de la salle d’attente", + "room.peersInLobby": "Participants dans la salle d’attente", + "room.lobbyEmpty": "Il n'y a actuellement aucun participant dans la salle d'attente", + "room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}", + "room.me": "Moi", + "room.spotlights": "Participants actifs", + "room.passive": "Participants passifs", + "room.videoPaused": "La vidéo est en pause", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, - "tooltip.login" : " Connexion", - "tooltip.logout" : " Déconnexion", - "tooltip.admitFromLobby" : " Autorisé depuis la salle d'attente", - "tooltip.lockRoom" : " Privatisation de la salle", - "tooltip.unLockRoom" : " Dé-privatisation de la salle", - "tooltip.enterFullscreen" : " Afficher en plein écran", - "tooltip.leaveFullscreen" : " Quitter le plein écran", - "tooltip.lobby" : " Afficher la salle d'attente", - "tooltip.settings" : " Afficher les paramètres", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.login": "Connexion", + "tooltip.logout": "Déconnexion", + "tooltip.admitFromLobby": "Autorisé depuis la salle d'attente", + "tooltip.lockRoom": "Privatisation de la salle", + "tooltip.unLockRoom": "Dé-privatisation de la salle", + "tooltip.enterFullscreen": "Afficher en plein écran", + "tooltip.leaveFullscreen": "Quitter le plein écran", + "tooltip.lobby": "Afficher la salle d'attente", + "tooltip.settings": "Afficher les paramètres", + "tooltip.kickParticipant": null, - "label.roomName" : " Nom de la salle", - "label.chooseRoomButton" : " Continuer", - "label.yourName" : " Votre nom", - "label.newWindow" : " Nouvelle fenêtre", - "label.fullscreen" : " Plein écran", - "label.openDrawer" : " Ouvrir Drawer", - "label.leave" : " Quiter", - "label.chatInput" : " Entrer un message", - "label.chat" : " Chat", - "label.filesharing" : " Partage de fichier", - "label.participants" : " Participants", - "label.shareFile" : " Partager un fichier", - "label.fileSharingUnsupported" : " Partage de fichier non supporté", - "label.unknown" : " Inconnu", - "label.democratic" : " Vue démocratique", - "label.filmstrip" : " Vue avec miniature", - "label.low" : " Basse définition", - "label.medium" : " Définition normale", - "label.high" : " Haute Définition (HD)", - "label.veryHigh" : " Très Haute Définition (FHD)", - "label.ultra" : " Ultra Haute Définition", - "label.close" : " Fermer", + "label.roomName": "Nom de la salle", + "label.chooseRoomButton": "Continuer", + "label.yourName": "Votre nom", + "label.newWindow": "Nouvelle fenêtre", + "label.fullscreen": "Plein écran", + "label.openDrawer": "Ouvrir Drawer", + "label.leave": "Quiter", + "label.chatInput": "Entrer un message", + "label.chat": "Chat", + "label.filesharing": "Partage de fichier", + "label.participants": "Participants", + "label.shareFile": "Partager un fichier", + "label.fileSharingUnsupported": "Partage de fichier non supporté", + "label.unknown": "Inconnu", + "label.democratic": "Vue démocratique", + "label.filmstrip": "Vue avec miniature", + "label.low": "Basse définition", + "label.medium": "Définition normale", + "label.high": "Haute Définition (HD)", + "label.veryHigh": "Très Haute Définition (FHD)", + "label.ultra": "Ultra Haute Définition", + "label.close": "Fermer", - "settings.settings" : " Paramètres", - "settings.camera" : " Caméra", - "settings.selectCamera" : " Sélectionner votre caméra", - "settings.cantSelectCamera" : " Impossible de sélectionner votre caméra", - "settings.audio" : " Microphone", - "settings.selectAudio" : " Sélectionner votre microphone", - "settings.cantSelectAudio" : " Impossible de sélectionner votre la caméra", - "settings.resolution" : " Sélection votre résolution", - "settings.layout" : " Mode d'affichage de la salle", - "settings.selectRoomLayout" : " Sélectionner l'affiche de la salle", - "settings.advancedMode" : " Mode avancé", + "settings.settings": "Paramètres", + "settings.camera": "Caméra", + "settings.selectCamera": "Sélectionner votre caméra", + "settings.cantSelectCamera": "Impossible de sélectionner votre caméra", + "settings.audio": "Microphone", + "settings.selectAudio": "Sélectionner votre microphone", + "settings.cantSelectAudio": "Impossible de sélectionner votre la caméra", + "settings.resolution": "Sélection votre résolution", + "settings.layout": "Mode d'affichage de la salle", + "settings.selectRoomLayout": "Sélectionner l'affiche de la salle", + "settings.advancedMode": "Mode avancé", - "filesharing.saveFileError" : " Impossible d'enregistrer le fichier", - "filesharing.startingFileShare" : " Début du transfert de fichier", - "filesharing.successfulFileShare" : " Fichier transféré", - "filesharing.unableToShare" : " Impossible de transférer le fichier", - "filesharing.error" : " Erreur lors du transfert de fichier", - "filesharing.finished" : " Fin du transfert de fichier", - "filesharing.save" : " Sauver", - "filesharing.sharedFile" : " {displayName} a partagé un fichier", - "filesharing.download" : " Télécharger", - "filesharing.missingSeeds" : " Si le téléchargement prend trop de temps c’est qu’il n’y a peut-être plus personne qui partage ce torrent. Demander à quelqu’un de repartager le document.", - "devices.devicesChanged" : " Vos périphériques ont changé, reconfigurer vos périphériques avec le menu paramètre", + "filesharing.saveFileError": "Impossible d'enregistrer le fichier", + "filesharing.startingFileShare": "Début du transfert de fichier", + "filesharing.successfulFileShare": "Fichier transféré", + "filesharing.unableToShare": "Impossible de transférer le fichier", + "filesharing.error": "Erreur lors du transfert de fichier", + "filesharing.finished": "Fin du transfert de fichier", + "filesharing.save": "Sauver", + "filesharing.sharedFile": "{displayName} a partagé un fichier", + "filesharing.download": "Télécharger", + "filesharing.missingSeeds": "Si le téléchargement prend trop de temps c’est qu’il n’y a peut-être plus personne qui partage ce torrent. Demander à quelqu’un de repartager le document.", + "devices.devicesChanged": "Vos périphériques ont changé, reconfigurer vos périphériques avec le menu paramètre", - "device.audioUnsupported" : " Microphone non supporté", - "device.activateAudio" : " Activer l'audio", - "device.muteAudio" : " Désactiver l'audio", - "device.unMuteAudio" : " Réactiver l'audio", + "device.audioUnsupported": "Microphone non supporté", + "device.activateAudio": "Activer l'audio", + "device.muteAudio": "Désactiver l'audio", + "device.unMuteAudio": "Réactiver l'audio", - "device.videoUnsupported" : " Vidéo non supporté", - "device.startVideo" : " Démarrer la vidéo", - "device.stopVideo" : " Arrêter la vidéo", + "device.videoUnsupported": "Vidéo non supporté", + "device.startVideo": "Démarrer la vidéo", + "device.stopVideo": "Arrêter la vidéo", - "device.screenSharingUnsupported" : " Partage d'écran non supporté", - "device.startScreenSharing" : " Démarrer le partage d 'écran'", - "device.stopScreenSharing" : " Arrêter le partage d'écran", + "device.screenSharingUnsupported": "Partage d'écran non supporté", + "device.startScreenSharing": "Démarrer le partage d 'écran'", + "device.stopScreenSharing": "Arrêter le partage d'écran", - "devices.microphoneDisconnected" : " Microphone déconnecté", - "devices.microphoneError" : " Une erreur est apparue lors de l'accès à votre microphone", - "devices.microPhoneMute" : " Désactiver le microphone", - "devices.micophoneUnMute" : " Réactiver le microphone", - "devices.microphoneEnable" : " Activer le microphone", - "devices.microphoneMuteError" : " Impossible de désactiver le microphone", - "devices.microphoneUnMuteError" : " Impossible de réactiver le microphone", + "devices.microphoneDisconnected": "Microphone déconnecté", + "devices.microphoneError": "Une erreur est apparue lors de l'accès à votre microphone", + "devices.microPhoneMute": "Désactiver le microphone", + "devices.micophoneUnMute": "Réactiver le microphone", + "devices.microphoneEnable": "Activer le microphone", + "devices.microphoneMuteError": "Impossible de désactiver le microphone", + "devices.microphoneUnMuteError": "Impossible de réactiver le microphone", - "devices.screenSharingDisconnected" : " Partage d'écran déconnecté", - "devices.screenSharingError" : " Une erreur est apparue lors de l'accès à votre partage d'écran", + "devices.screenSharingDisconnected": "Partage d'écran déconnecté", + "devices.screenSharingError": "Une erreur est apparue lors de l'accès à votre partage d'écran", - "devices.cameraDisconnected" : " Caméra déconnectée", - "devices.cameraError" : " Une erreur est apparue lors de l'accès à votre caméra" + "devices.cameraDisconnected": "Caméra déconnectée", + "devices.cameraError": "Une erreur est apparue lors de l'accès à votre caméra" } diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index ceda4a8..d71b360 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -48,9 +48,9 @@ "room.spotlights": "Učesnici u fokusu", "room.passive": "Pasivni učesnici", "room.videoPaused": "Video pauziran", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Prijava", "tooltip.logout": "Odjava", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Izađi iz punog ekrana", "tooltip.lobby": "Prikaži predvorje", "tooltip.settings": "Prikaži postavke", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Naziv sobe", "label.chooseRoomButton": "Nastavi", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index f4e543d..1391d19 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -4,7 +4,7 @@ "socket.reconnected": "Sikeres újarkapcsolódás", "socket.requestError": "Sikertelen szerver lekérés", - "room.chooseRoom": "Choose the name of the room you would like to join", + "room.chooseRoom": null, "room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ", "room.joined": "Csatlakozátál a konferenciához", "room.cantJoin": "Sikertelen csatlakozás a konferenciához", @@ -48,9 +48,9 @@ "room.spotlights": "Látható résztvevők", "room.passive": "Passzív résztvevők", "room.videoPaused": "Ez a videóstream szünetel", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Belépés", "tooltip.logout": "Kilépés", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Kilépés teljes képernyős módból", "tooltip.lobby": "Az előszobában várakozók listája", "tooltip.settings": "Beállítások", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 8aec8a5..ee2a9eb 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -48,9 +48,9 @@ "room.spotlights": "Aktywni uczestnicy", "room.passive": "Pasywni uczestnicy", "room.videoPaused": "To wideo jest wstrzymane.", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Zaloguj", "tooltip.logout": "Wyloguj", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Wyłącz tryb pełnoekranowy", "tooltip.lobby": "Pokaż poczekalnię", "tooltip.settings": "Pokaż ustawienia", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Nazwa konferencji", "label.chooseRoomButton": "Kontynuuj", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index e72445f..b2c5c0d 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -48,9 +48,9 @@ "room.spotlights": "Participantes em foco", "room.passive": "Participantes passivos", "room.videoPaused": "Este vídeo está em pausa", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Entrar", "tooltip.logout": "Sair", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Sair de ecrã completo", "tooltip.lobby": "Apresentar sala de espera", "tooltip.settings": "Apresentar definições", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Nome da sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 4ca59b8..66217cc 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -48,9 +48,9 @@ "room.spotlights": "Participanți în Spotlight", "room.passive": "Participanți pasivi", "room.videoPaused": "Acest video este pus pe pauză", - "room.muteAll": "Mute all", - "room.stopAllVideo": "Stop all video", - "room.closeMeeting": "Close meeting", + "room.muteAll": null, + "room.stopAllVideo": null, + "room.closeMeeting": null, "tooltip.login": "Intră în cont", "tooltip.logout": "Deconectare", @@ -61,7 +61,7 @@ "tooltip.leaveFullscreen": "Ieșire din modul ecran complet", "tooltip.lobby": "Arată holul", "tooltip.settings": "Arată setăile", - "tooltip.kickParticipant": "Kick out participant", + "tooltip.kickParticipant": null, "label.roomName": "Numele camerei", "label.chooseRoomButton": "Continuare", From c914cbcb9ff5a4efd9689c9b5fb925fb5e25ee31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 18:35:43 +0100 Subject: [PATCH 20/21] Properly close the room on moderator close --- server/lib/Room.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 6ac3746..5f9ea4d 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1120,15 +1120,11 @@ class Room extends EventEmitter true ); - // Close the peers. - for (const kickedPeer in this._peers) - { - if (peer !== kickedPeer) - kickedPeer.close(); - } - cb(); + // Close the room + this.close(); + break; } From 92a0370499344ecaf0637ea7cd5715f4e901bdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 24 Mar 2020 19:40:09 +0100 Subject: [PATCH 21/21] Don't break existing configs --- server/config/config.example.js | 4 ++-- server/lib/Room.js | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 5c7291d..23de620 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -149,8 +149,8 @@ module.exports = // [ userRoles.MODERATOR, userRoles.AUTHENTICATED ] // This will allow all MODERATOR and AUTHENTICATED users access. requiredRolesForAccess : [ userRoles.ALL ], - // When truthy, the room will be open to all users when the first - // AUTHENTICATED or MODERATOR user joins the room. + // When truthy, the room will be open to all users when as long as there + // are allready users in the room activateOnHostJoin : true, // Mediasoup settings mediasoup : diff --git a/server/lib/Room.js b/server/lib/Room.js index 5f9ea4d..3129937 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -54,6 +54,12 @@ class Room extends EventEmitter // Locked flag. this._locked = false; + // Required roles to access + this._requiredRoles = [ userRoles.ALL ]; + + if ('requiredRolesForAccess' in config) + this._requiredRoles = config.requiredRolesForAccess; + // if true: accessCode is a possibility to open the room this._joinByAccesCode = true; @@ -135,7 +141,7 @@ class Room extends EventEmitter else { // If the user has a role in config.requiredRolesForAccess, let them in - peer.roles.some((role) => config.requiredRolesForAccess.includes(role)) ? + peer.roles.some((role) => this._requiredRoles.includes(role)) ? this._peerJoining(peer) : this._handleGuest(peer); } @@ -181,7 +187,7 @@ class Room extends EventEmitter // If the user has a role in config.requiredRolesForAccess, let them in if ( !this._locked && - peer.roles.some((role) => config.requiredRolesForAccess.includes(role)) + peer.roles.some((role) => this._requiredRoles.includes(role)) ) { this._lobby.promotePeer(peer.id);