{ this.node = node; }}>
+ { isModerator &&
+
+ }
-
roomClient.setSelectedPeer(peerId)}
>
-
+
@@ -129,7 +143,11 @@ class ParticipantList extends React.PureComponent
})}
onClick={() => roomClient.setSelectedPeer(peerId)}
>
-
+
))}
@@ -142,6 +160,7 @@ ParticipantList.propTypes =
{
roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool,
+ isModerator : PropTypes.bool,
passivePeers : PropTypes.array,
selectedPeerId : PropTypes.string,
spotlightPeers : PropTypes.array,
@@ -151,6 +170,8 @@ ParticipantList.propTypes =
const mapStateToProps = (state) =>
{
return {
+ isModerator : state.me.roles.includes(userRoles.MODERATOR) ||
+ state.me.roles.includes(userRoles.ADMIN),
passivePeers : passivePeersSelector(state),
selectedPeerId : state.room.selectedPeerId,
spotlightPeers : spotlightPeersSelector(state)
@@ -165,6 +186,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 ca97e32..e065c7b 100644
--- a/app/src/reducers/me.js
+++ b/app/src/reducers/me.js
@@ -1,8 +1,11 @@
+import * as userRoles from './userRoles';
+
const initialState =
{
id : null,
picture : null,
isMobile : false,
+ roles : [ userRoles.ALL ],
canSendMic : false,
canSendWebcam : false,
canShareScreen : false,
@@ -49,6 +52,24 @@ const me = (state = initialState, action) =>
return { ...state, loggedIn: flag };
}
+ case 'ADD_ROLE':
+ {
+ if (state.roles.includes(action.payload.role))
+ return state;
+
+ 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 747d316..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) =>
@@ -110,7 +113,7 @@ const room = (state = initialState, action) =>
case 'TOGGLE_JOINED':
{
- const joined = !state.joined;
+ const joined = true;
return { ...state, joined };
}
@@ -163,6 +166,15 @@ 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 };
+
+ case 'CLOSE_MEETING_IN_PROGRESS':
+ return { ...state, closeMeetingInProgress: 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/app/src/translations/cn.json b/app/src/translations/cn.json
index 3b4cc2a..cb439e5 100644
--- a/app/src/translations/cn.json
+++ b/app/src/translations/cn.json
@@ -49,6 +49,9 @@
"room.spotlights": "Spotlight中的参与者",
"room.passive": "被动参与者",
"room.videoPaused": "该视频已暂停",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "登录",
"tooltip.logout": "注销",
@@ -60,6 +63,7 @@
"tooltip.lobby": "显示大厅",
"tooltip.settings": "显示设置",
"tooltip.participants": "显示参加者",
+ "tooltip.kickParticipant": null,
"label.roomName": "房间名称",
"label.chooseRoomButton": "继续",
diff --git a/app/src/translations/de.json b/app/src/translations/de.json
index 80969f5..d78e5bd 100644
--- a/app/src/translations/de.json
+++ b/app/src/translations/de.json
@@ -4,7 +4,7 @@
"socket.reconnected": "Verbindung wieder herges|tellt",
"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.consentUnderstand": "I understand",
"room.joined": "Konferenzraum betreten",
@@ -49,6 +49,9 @@
"room.spotlights": "Aktive Teinehmer",
"room.passive": "Passive Teilnehmer",
"room.videoPaused": "Video gestoppt",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Anmelden",
"tooltip.logout": "Abmelden",
@@ -60,9 +63,10 @@
"tooltip.lobby": "Warteraum",
"tooltip.settings": "Einstellungen",
"tooltip.participants": "Teilnehmer",
+ "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 e07eb6e..87be182 100644
--- a/app/src/translations/dk.json
+++ b/app/src/translations/dk.json
@@ -49,6 +49,9 @@
"room.spotlights": "Deltagere i fokus",
"room.passive": "Passive deltagere",
"room.videoPaused": "Denne video er sat på pause",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Log ind",
"tooltip.logout": "Log ud",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Vis lobby",
"tooltip.settings": "Vis indstillinger",
"tooltip.participants": "Vis deltagere",
+ "tooltip.kickParticipant": null,
"label.roomName": "Værelsesnavn",
"label.chooseRoomButton": "Fortsæt",
@@ -106,7 +110,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 4acc799..1c028df 100644
--- a/app/src/translations/el.json
+++ b/app/src/translations/el.json
@@ -49,6 +49,9 @@
"room.spotlights": "Συμμετέχοντες στο Spotlight",
"room.passive": "Παθητικοί συμμετέχοντες",
"room.videoPaused": "Το βίντεο έχει σταματήσει",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Σύνδεση",
"tooltip.logout": "Αποσύνδεση",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Εμφάνιση λόμπι",
"tooltip.settings": "Εμφάνιση ρυθμίσεων",
"tooltip.participants": "Εμφάνιση συμμετεχόντων",
+ "tooltip.kickParticipant": null,
"label.roomName": "Όνομα δωματίου",
"label.chooseRoomButton": "Συνέχεια",
@@ -75,8 +79,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/en.json b/app/src/translations/en.json
index 9f5be9c..b7248f5 100644
--- a/app/src/translations/en.json
+++ b/app/src/translations/en.json
@@ -49,6 +49,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",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Show lobby",
"tooltip.settings": "Show settings",
"tooltip.participants": "Show participants",
+ "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 0d8e07a..4325509 100644
--- a/app/src/translations/es.json
+++ b/app/src/translations/es.json
@@ -1,140 +1,144 @@
{
- "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.consentUnderstand": "I understand",
- "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.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.consentUnderstand": "I understand",
+ "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.participants": "Mostrar participantes",
+ "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.participants": "Mostrar participantes",
+ "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.permanentTopBar": "Barra superior permanente",
- "settings.lastn": "Cantidad de videos visibles",
+ "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.permanentTopBar": "Barra superior permanente",
+ "settings.lastn": "Cantidad de videos visibles",
- "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 ce2953b..8e889b4 100644
--- a/app/src/translations/fr.json
+++ b/app/src/translations/fr.json
@@ -1,139 +1,143 @@
{
- "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.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.consentUnderstand": "I understand",
- "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.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.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.participants": "Afficher les participants",
+ "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é",
"settings.permanentTopBar": "Barre supérieure permanente",
"settings.lastn": "Nombre de vidéos visibles",
- "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 ff01d7b..5e97b0c 100644
--- a/app/src/translations/hr.json
+++ b/app/src/translations/hr.json
@@ -49,6 +49,9 @@
"room.spotlights": "Učesnici u fokusu",
"room.passive": "Pasivni učesnici",
"room.videoPaused": "Video pauziran",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Prijava",
"tooltip.logout": "Odjava",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Prikaži predvorje",
"tooltip.settings": "Prikaži postavke",
"tooltip.participants": "Pokažite sudionike",
+ "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 6d63a7a..9c4399a 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.consentUnderstand": "I understand",
"room.joined": "Csatlakozátál a konferenciához",
@@ -49,6 +49,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": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Belépés",
"tooltip.logout": "Kilépés",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Az előszobában várakozók listája",
"tooltip.settings": "Beállítások",
"tooltip.participants": "Résztvevők",
+ "tooltip.kickParticipant": null,
"label.roomName": "Konferencia",
"label.chooseRoomButton": "Tovább",
diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json
index 3f3dc32..6c81dea 100644
--- a/app/src/translations/nb.json
+++ b/app/src/translations/nb.json
@@ -49,6 +49,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",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Vis lobby",
"tooltip.settings": "Vis innstillinger",
"tooltip.participants": "Vis deltakere",
+ "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 d5dfe03..662f878 100644
--- a/app/src/translations/pl.json
+++ b/app/src/translations/pl.json
@@ -49,6 +49,9 @@
"room.spotlights": "Aktywni uczestnicy",
"room.passive": "Pasywni uczestnicy",
"room.videoPaused": "To wideo jest wstrzymane.",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Zaloguj",
"tooltip.logout": "Wyloguj",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Pokaż poczekalnię",
"tooltip.settings": "Pokaż ustawienia",
"tooltip.participants": "Pokaż uczestników",
+ "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 9fefbfe..5f034e9 100644
--- a/app/src/translations/pt.json
+++ b/app/src/translations/pt.json
@@ -49,6 +49,9 @@
"room.spotlights": "Participantes em foco",
"room.passive": "Participantes passivos",
"room.videoPaused": "Este vídeo está em pausa",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Entrar",
"tooltip.logout": "Sair",
@@ -60,6 +63,7 @@
"tooltip.lobby": "Apresentar sala de espera",
"tooltip.settings": "Apresentar definições",
"tooltip.participants": "Apresentar participantes",
+ "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 23bd970..a187665 100644
--- a/app/src/translations/ro.json
+++ b/app/src/translations/ro.json
@@ -49,6 +49,9 @@
"room.spotlights": "Participanți în Spotlight",
"room.passive": "Participanți pasivi",
"room.videoPaused": "Acest video este pus pe pauză",
+ "room.muteAll": null,
+ "room.stopAllVideo": null,
+ "room.closeMeeting": null,
"tooltip.login": "Intră în cont",
"tooltip.logout": "Deconectare",
@@ -58,7 +61,9 @@
"tooltip.enterFullscreen": "Modul ecran complet",
"tooltip.leaveFullscreen": "Ieșire din modul ecran complet",
"tooltip.lobby": "Arată holul",
- "tooltip.settings": "Arată participanții",
+ "tooltip.settings": "Arată setăile",
+ "tooltip.participants": null,
+ "tooltip.kickParticipant": null,
"label.roomName": "Numele camerei",
"label.chooseRoomButton": "Continuare",
diff --git a/server/config/config.example.js b/server/config/config.example.js
index e0b78e4..53b1f05 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 =
{
@@ -63,18 +64,109 @@ 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
- // When truthy, the room will be open to all users when the first
- // authenticated user has already joined the room.
- activateOnHostJoin : true,
+ // This function will be called on successful login through oidc.
+ // Use this function to map your oidc userinfo to the Peer object.
+ // The roomId is equal to the room name.
+ // See examples below.
+ // Examples:
+ /*
+ // All authenicated users will be MODERATOR and AUTHENTICATED
+ userMapping : async ({ peer, roomId, 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, roomId, 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, roomId, userinfo }) =>
+ {
+ if (userinfo.email && userinfo.email.endsWith('@example.com'))
+ {
+ peer.addRole(userRoles.MODERATOR);
+ }
+
+ 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, 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)
+ {
+ 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 as long as there
+ // are allready users in 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..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,38 +53,42 @@ 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)
{
peer.socket.removeListener('request', peer.socketRequestHandler);
- peer.removeListener('authenticationChanged', peer.authenticationHandler);
+ peer.removeListener('gotRole', peer.gotRoleHandler);
+ peer.removeListener('displayNameChanged', peer.displayNameChangeHandler);
+ peer.removeListener('pictureChanged', peer.pictureChangeHandler);
peer.removeListener('close', peer.closeHandler);
peer.socketRequestHandler = null;
- peer.authenticationHandler = null;
+ peer.gotRoleHandler = null;
+ peer.displayNameChangeHandler = null;
+ peer.pictureChangeHandler = null;
peer.closeHandler = null;
this.emit('promotePeer', peer);
- this._peers.delete(peerId);
+ delete this._peers[peerId];
}
}
@@ -112,16 +117,25 @@ class Lobby extends EventEmitter
});
};
- peer.authenticationHandler = () =>
+ peer.gotRoleHandler = () =>
{
- 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 = () =>
@@ -133,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');
@@ -141,9 +155,11 @@ class Lobby extends EventEmitter
this._notification(peer.socket, 'enteredLobby');
- this._peers.set(peer.id, peer);
+ this._peers[peer.id] = peer;
- peer.on('authenticationChanged', peer.authenticationHandler);
+ peer.on('gotRole', peer.gotRoleHandler);
+ peer.on('displayNameChanged', peer.displayNameChangeHandler);
+ peer.on('pictureChanged', peer.pictureChangeHandler);
peer.socket.on('request', peer.socketRequestHandler);
@@ -169,8 +185,6 @@ class Lobby extends EventEmitter
peer.displayName = displayName;
- this.emit('changeDisplayName', peer);
-
cb();
break;
@@ -181,8 +195,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..1878a61 100644
--- a/server/lib/Peer.js
+++ b/server/lib/Peer.js
@@ -1,17 +1,20 @@
const EventEmitter = require('events').EventEmitter;
+const userRoles = require('../userRoles');
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);
+ logger.info('constructor() [id:"%s"]', id);
super();
this._id = id;
+ this._roomId = roomId;
+
this._authId = null;
this._socket = socket;
@@ -22,7 +25,7 @@ class Peer extends EventEmitter
this._inLobby = false;
- this._authenticated = false;
+ this._roles = [ userRoles.ALL ];
this._displayName = false;
@@ -40,8 +43,6 @@ class Peer extends EventEmitter
this._consumers = new Map();
- this._checkAuthentication();
-
this._handlePeer();
}
@@ -58,56 +59,25 @@ 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.use((packet, next) =>
+ if (this.socket)
{
- this._checkAuthentication();
-
- return next();
- });
-
- this.socket.on('disconnect', () =>
- {
- if (this.closed)
- return;
-
- logger.debug('"disconnect" event [id:%s]', this.id);
-
- this.close();
- });
- }
-
- _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;
+ this.socket.on('disconnect', () =>
+ {
+ if (this.closed)
+ return;
+
+ logger.debug('"disconnect" event [id:%s]', this.id);
+
+ this.close();
+ });
}
}
@@ -121,6 +91,16 @@ class Peer extends EventEmitter
this._id = id;
}
+ get roomId()
+ {
+ return this._roomId;
+ }
+
+ set roomId(roomId)
+ {
+ this._roomId = roomId;
+ }
+
get authId()
{
return this._authId;
@@ -166,21 +146,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 +230,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('gotRole', { newRole });
+ }
+ }
+
+ removeRole(oldRole)
+ {
+ if (this._roles.includes(oldRole))
+ {
+ this._roles = this._roles.filter((role) => role !== oldRole);
+
+ logger.info('removeRole() | [oldRole:"%s]"', oldRole);
+
+ this.emit('lostRole', { oldRole });
+ }
+ }
+
+ hasRole(role)
+ {
+ return this._roles.includes(role);
+ }
+
addTransport(id, transport)
{
this.transports.set(id, transport);
@@ -319,7 +316,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 5d86f11..f0ceb66 100644
--- a/server/lib/Room.js
+++ b/server/lib/Room.js
@@ -2,6 +2,7 @@ const EventEmitter = require('events').EventEmitter;
const axios = require('axios');
const Logger = require('./Logger');
const Lobby = require('./Lobby');
+const userRoles = require('../userRoles');
const config = require('../config/config');
const logger = new Logger('Room');
@@ -54,6 +55,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;
@@ -100,11 +107,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;
@@ -118,26 +122,27 @@ 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
+ // 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();
+ this._peers[peer.id].close();
+ }
- 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) => this._requiredRoles.includes(role)) ?
this._peerJoining(peer) :
this._handleGuest(peer);
}
@@ -145,21 +150,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');
}
}
@@ -179,9 +175,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) => this._requiredRoles.includes(role))
+ )
+ {
+ this._lobby.promotePeer(peer.id);
+
+ return;
+ }
});
this._lobby.on('changeDisplayName', (changedPeer) =>
@@ -304,7 +317,6 @@ class Room extends EventEmitter
}, 10000);
}
- // checks both room and lobby
checkEmpty()
{
return Object.keys(this._peers).length === 0;
@@ -324,12 +336,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;
@@ -402,25 +410,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 }) =>
@@ -449,6 +449,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, 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, true);
+ });
}
async _handleSocketRequest(peer, request, cb)
@@ -464,27 +490,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');
@@ -512,7 +517,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;
@@ -540,7 +548,8 @@ class Room extends EventEmitter
{
id : peer.id,
displayName : displayName,
- picture : picture
+ picture : picture,
+ roles : peer.roles
}
);
}
@@ -1106,6 +1115,92 @@ 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: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
+ );
+
+ cb();
+
+ // Close the room
+ this.close();
+
+ 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);
@@ -1319,13 +1414,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
{
diff --git a/server/server.js b/server/server.js
index 8faa837..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
@@ -190,7 +191,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
@@ -241,51 +242,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);
}
);
@@ -324,7 +280,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);
});
@@ -341,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());
});
@@ -349,35 +319,35 @@ 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));
- let displayName;
- let picture;
+ const { peerId, roomId } = state;
- if (req.user != null)
+ req.session.peerId = peerId;
+ req.session.roomId = roomId;
+
+ let peer = peers.get(peerId);
+
+ if (!peer) // User has no socket session yet, make temporary
+ peer = new Peer({ id: peerId, roomId });
+
+ if (peer && peer.roomId !== roomId) // The peer is mischievous
+ throw new Error('peer authenticated with wrong room');
+
+ if (peer && typeof config.userMapping === 'function')
{
- if (req.user.displayName != null)
- displayName = req.user.displayName;
- else
- displayName = '';
-
- if (req.user.picture != null)
- picture = req.user.picture;
- else
- picture = '/static/media/buddy.403cb9f6.svg';
+ await config.userMapping({
+ peer,
+ roomId,
+ userinfo : req.user._userinfo
+ });
}
- const peer = peers.get(state.id);
-
- peer && (peer.displayName = displayName);
- peer && (peer.picture = picture);
- peer && (peer.authenticated = true);
-
res.send(loginHelper({
- displayName,
- picture
+ displayName : peer.displayName,
+ picture : peer.picture
}));
}
);
@@ -495,12 +465,36 @@ 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);
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, roomId, userinfo: _userinfo });
+ }
+ }
+
room.handlePeer(peer);
})
.catch((error) =>
diff --git a/server/userRoles.js b/server/userRoles.js
new file mode 100644
index 0000000..c8cf886
--- /dev/null
+++ b/server/userRoles.js
@@ -0,0 +1,12 @@
+module.exports = {
+ // Allowed to enter locked rooms + all other priveleges
+ ADMIN : 'admin',
+ // Allowed to enter restricted rooms if configured.
+ // Allowed to moderate users in a room (mute all,
+ // spotlight video, kick users)
+ MODERATOR : 'moderator',
+ // Same as MODERATOR, but can't moderate users
+ AUTHENTICATED : 'authenticated',
+ // No priveleges
+ ALL : 'normal'
+};
\ No newline at end of file