diff --git a/CHANGELOG.md b/CHANGELOG.md index bda55dd..471b71f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,32 @@ # Changelog +## 3.2.1 + +* Fix: permananent top bar by default +* Fix: `httpOnly` mode https redirect +* Add some extra checks for video stream and track +* Add Italian translation +* Add Czech translation +* Add new server option `trustProxy` for load balancing http only use case +* Add HAproxy load balance example +* Add LTI LMS integration documentation +* Fix spacing of leave button +* Fix for sharing same file multiple times + ## 3.2 * Add munin plugin -* Add muted=true search param to disble audio by deffault +* Add `muted=true` search param to disable audio by default * Modify webtorrent tracker * Add key shortcut `space` for audio mute * Add key shortcut `v` for video mute * Add user configurable LastN -* Add option to sticky top bar (sticky by default) -* update mediasoup server -* Add simulcast options to app config (disabled by default) -* Add stats option to get counts of rooms and peers -* Add httpOnly option for loadbalancer backend setups +* Add option to permananent top bar (permanent by default) +* Update mediasoup server +* Add `simulcast` options to app config (disabled by default) +* Add `stats` option to get counts of rooms and peers +* Add `httpOnly` option for loadbalancer backend setups * LTI integration for LMS systems like moodle -* Add muted=false search parameter * Add translations (12+1 languages) * Add support IPv6 * Many other fixes and refactorings @@ -33,10 +45,10 @@ * Updated to mediasoup v3 * Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client) - - OpenID Connect discovery - - Auth code flow + * OpenID Connect discovery + * Auth code flow * Add spdy http2 support. - - Notice it does not supports node 11.x + * Notice it does not supports node 11.x * Updated to Material UI v4 ## 2.0 diff --git a/HAproxy.md b/HAproxy.md new file mode 100644 index 0000000..095a6bf --- /dev/null +++ b/HAproxy.md @@ -0,0 +1,109 @@ +# Howto deploy a (room based) load balanced cluster + +This example will show how to setup an HA proxy to provide load balancing between several +multiparty-meeting servers. + +## IP and DNS + +In this basic example we use the following names and ips: + +### Backend + +* `mm1.example.com` <=> `192.0.2.1` +* `mm2.example.com` <=> `192.0.2.2` +* `mm3.example.com` <=> `192.0.2.3` + +### Redis + +* `redis.example.com` <=> `192.0.2.4` + +### Load balancer HAproxy + +* `meet.example.com` <=> `192.0.2.5` + +## Deploy multiple multiparty-meeting servers + +This is most easily done using Ansible (see below), but can be done +in any way you choose (manual, Docker, Ansible). + +Read more here: [mm-ansible](https://github.com/misi/mm-ansible) +[![asciicast](https://asciinema.org/a/311365.svg)](https://asciinema.org/a/311365) + +## Setup Redis for central HTTP session store + +### Use one Redis for all multiparty-meeting servers + +* Deploy a Redis cluster for all instances. + * We will use in our actual example `192.0.2.4` as redis HA cluster ip. It is out of scope howto deploy it. + +OR + +* For testing you can use Redis from one the multiparty-meeting servers. e.g. If you plan only for testing on your first multiparty-meeting server. + * Configure Redis `redis.conf` to not only bind to your loopback but also to your global ip address too: + + ``` plaintext + bind 192.0.2.1 + ``` + + This example sets this to `192.0.2.1`, change this according to your local installation. + + * Change your firewall config to allow incoming Redis. Example (depends on the type of firewall): + + ``` plaintext + chain INPUT { + policy DROP; + + saddr mm2.example.com proto tcp dport 6379 ACCEPT; + saddr mm3.example.com proto tcp dport 6379 ACCEPT; + } + ``` + + * **Set a password, or if you don't (like in this basic example) take care to set strict firewall rules** + +## Configure multiparty-meeting servers + +### App config + +mm/configs/app/config.js + +``` js +multipartyServer : 'meet.example.com', +``` + +### Server config + +mm/configs/server/config.js + +``` js +redisOptions : { host: '192.0.2.4'}, +listeningPort: 80, +httpOnly: true, +trustProxy : ['192.0.2.5'], +``` + +## Deploy HA proxy + +* Configure cerificate / letsencrypt for `meet.example.com` + * In this example we put a complete chain and private key in /root/certificate.pem. +* Install and setup haproxy + + `apt install haproxy` + +* Add to /etc/haproxy/haproxy.cfg config + + ``` plaintext + backend multipartymeeting + balance url_param roomId + hash-type consistent + + server mm1 192.0.2.1:80 check maxconn 20 verify none + server mm2 192.0.2.2:80 check maxconn 20 verify none + server mm3 192.0.2.3:80 check maxconn 20 verify none + + frontend meet.example.com + bind 192.0.2.5:80 + bind 192.0.2.5:443 ssl crt /root/certificate.pem + http-request redirect scheme https unless { ssl_fc } + reqadd X-Forwarded-Proto:\ https + default_backend multipartymeeting + ``` diff --git a/LTI/LTI.md b/LTI/LTI.md new file mode 100644 index 0000000..ff6b9ec --- /dev/null +++ b/LTI/LTI.md @@ -0,0 +1,61 @@ +# Learning Tools Interoperability (LTI) + +## LTI + +Read more about IMS Global defined interface for tools like our VideoConference system integration with Learning Managment Systems(LMS) (e.g. moodle). +See: [IMS Global Learning Tool Interoperability](https://www.imsglobal.org/activity/learning-tools-interoperability) + +We implemented LTI interface version 1.0/1.1 + +### Server config auth section LTI settings + +Set in server configuration a random key and secret + +``` json +auth : + { + lti : + { + consumerKey : 'key', + consumerSecret : 'secret' + }, + } +``` + +### Configure your LMS system with secret and key settings above + +#### Auth tool URL + +Set tool URL to your server with path /auth/lti + +``` url +https://mm.example.com/auth/lti +``` + +#### In moodle find external tool plugin setting and external tool action + +See: [moodle external tool settings](https://docs.moodle.org/38/en/External_tool_settings) + +#### Add and activity + +![Add external tool](lti1.png) + +#### Setup Activity + +##### Activity setup basic form + +Open fully the settings **Click on show more!!** +![Add external tool config](lti2.png) + +##### Empty full form + +![Opened external tool config](lti3.png) + +##### Filled out form + +![Filled out external tool config](lti4.png) + +## moodle plugin + +Alternativly you can use multipartymeeting moodle plugin: +[https://github.com/misi/moodle-mod_multipartymeeting](https://github.com/misi/moodle-mod_multipartymeeting) diff --git a/LTI/lti1.png b/LTI/lti1.png new file mode 100644 index 0000000..cc420c4 Binary files /dev/null and b/LTI/lti1.png differ diff --git a/LTI/lti2.png b/LTI/lti2.png new file mode 100644 index 0000000..8c1d271 Binary files /dev/null and b/LTI/lti2.png differ diff --git a/LTI/lti3.png b/LTI/lti3.png new file mode 100644 index 0000000..3b16d35 Binary files /dev/null and b/LTI/lti3.png differ diff --git a/LTI/lti4.png b/LTI/lti4.png new file mode 100644 index 0000000..c36bbe2 Binary files /dev/null and b/LTI/lti4.png differ diff --git a/README.md b/README.md index 2f1de98..82a1c6a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ If you want the automatic approach, you can find a docker image [here](https://h If you want the ansible approach, you can find ansible role [here](https://github.com/misi/mm-ansible/). [![asciicast](https://asciinema.org/a/311365.svg)](https://asciinema.org/a/311365) - ## Manual installation * Prerequisites: Currently multiparty-meeting will only run on nodejs v10.* @@ -106,6 +105,12 @@ $ systemctl enable multiparty-meeting * 4443/tcp (default `npm start` port for developing with live browser reload, not needed in production enviroments - adjustable in app/package.json) * 40000-49999/udp/tcp (media ports - adjustable in `server/config.js`) +## Load balanced installation +To deploy this as a load balanced cluster, have a look at [HAproxy](HAproxy.md). + +## Learning management integration +To integrate with an LMS (e.g. Moodle), have a look at [LTI](LTI/LTI.md). + ## TURN configuration * You need an addtional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/config.js` diff --git a/app/package.json b/app/package.json index e7958ec..15b24d4 100644 --- a/app/package.json +++ b/app/package.json @@ -11,9 +11,10 @@ "@material-ui/core": "^4.5.1", "@material-ui/icons": "^4.5.1", "bowser": "^2.7.0", + "create-torrent": "^4.4.1", "dompurify": "^2.0.7", "domready": "^1.0.8", - "end-of-stream": "1.4.0", + "end-of-stream": "1.4.1", "file-saver": "^2.0.2", "hark": "^1.2.3", "is-electron": "^2.2.0", @@ -37,7 +38,7 @@ "riek": "^1.1.0", "socket.io-client": "^2.3.0", "source-map-explorer": "^2.1.0", - "webtorrent": "^0.107.16" + "webtorrent": "^0.107.17" }, "scripts": { "analyze": "source-map-explorer build/static/js/*", diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 9b63866..f783957 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -14,6 +14,8 @@ import * as consumerActions from './actions/consumerActions'; import * as producerActions from './actions/producerActions'; import * as notificationActions from './actions/notificationActions'; +let createTorrent; + let WebTorrent; let saveAs; @@ -704,26 +706,48 @@ export default class RoomClient }) })); - this._webTorrent.seed( - files, - { announceList: [['wss://tracker.lab.vvc.niif.hu:443']] }, - (torrent) => + createTorrent(files, (err, torrent) => + { + if (err) { - store.dispatch(requestActions.notify( + return store.dispatch(requestActions.notify( { + type : 'error', text : intl.formatMessage({ - id : 'filesharing.successfulFileShare', - defaultMessage : 'File successfully shared' + id : 'filesharing.unableToShare', + defaultMessage : 'Unable to share file' }) })); + } - store.dispatch(fileActions.addFile( - this._peerId, - torrent.magnetURI - )); + const existingTorrent = this._webTorrent.get(torrent); - this._sendFile(torrent.magnetURI); - }); + if (existingTorrent) + { + return this._sendFile(existingTorrent.magnetURI); + } + + this._webTorrent.seed( + files, + { announceList: [ [ 'wss://tracker.lab.vvc.niif.hu:443' ] ] }, + (newTorrent) => + { + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'filesharing.successfulFileShare', + defaultMessage : 'File successfully shared' + }) + })); + + store.dispatch(fileActions.addFile( + this._peerId, + newTorrent.magnetURI + )); + + this._sendFile(newTorrent.magnetURI); + }); + }); } // { file, name, picture } @@ -1081,8 +1105,8 @@ export default class RoomClient logger.debug( 'changeWebcam() | new selected webcam [device:%o]', device); - - this._webcamProducer.track.stop(); + if (this._webcamProducer && this._webcamProducer.track) + this._webcamProducer.track.stop(); logger.debug('changeWebcam() | calling getUserMedia()'); @@ -1094,14 +1118,21 @@ export default class RoomClient ...VIDEO_CONSTRAINS[resolution] } }); - - const track = stream.getVideoTracks()[0]; - - await this._webcamProducer.replaceTrack({ track }); - - store.dispatch( - producerActions.setProducerTrack(this._webcamProducer.id, track)); - + if (stream){ + const track = stream.getVideoTracks()[0]; + if (track) { + await this._webcamProducer.replaceTrack({ track }); + + store.dispatch( + producerActions.setProducerTrack(this._webcamProducer.id, track)); + + } else { + logger.warn('getVideoTracks Error: First Video Track is null') + } + + } else { + logger.warn ('getUserMedia Error: Stream is null!') + } store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId)); await this._updateWebcams(); @@ -1337,6 +1368,13 @@ export default class RoomClient async _loadDynamicImports() { + ({ default: createTorrent } = await import( + + /* webpackPrefetch: true */ + /* webpackChunkName: "createtorrent" */ + 'create-torrent' + )); + ({ default: WebTorrent } = await import( /* webpackPrefetch: true */ @@ -2059,6 +2097,30 @@ export default class RoomClient try { + this._torrentSupport = WebTorrent.WEBRTC_SUPPORT; + + this._webTorrent = this._torrentSupport && new WebTorrent({ + tracker : { + rtcConfig : { + iceServers : this._turnServers + } + } + }); + + this._webTorrent.on('error', (error) => + { + logger.error('Filesharing [error:"%o"]', error); + + store.dispatch(requestActions.notify( + { + type : 'error', + text : intl.formatMessage({ + id : 'filesharing.error', + defaultMessage : 'There was a filesharing error' + }) + })); + }); + this._mediasoupDevice = new mediasoupClient.Device(); const routerRtpCapabilities = @@ -2177,7 +2239,7 @@ export default class RoomClient canShareFiles : this._torrentSupport })); - const { peers } = await this.sendRequest( + const { peers, authenticated } = await this.sendRequest( 'join', { displayName : displayName, @@ -2185,6 +2247,8 @@ export default class RoomClient rtpCapabilities : this._mediasoupDevice.rtpCapabilities }); + store.dispatch(meActions.loggedIn(authenticated)); + logger.debug('_joinRoom() joined, got peers [peers:"%o"]', peers); for (const peer of peers) diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 6ef8dae..cd1e566 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -78,8 +78,8 @@ const styles = (theme) => }, actionButton : { - margin : theme.spacing(1), - padding : 0 + margin : theme.spacing(1, 0), + padding : theme.spacing(0, 1) } }); @@ -439,4 +439,4 @@ export default withRoomContext(connect( ); } } -)(withStyles(styles, { withTheme: true })(TopBar))); \ No newline at end of file +)(withStyles(styles, { withTheme: true })(TopBar))); diff --git a/app/src/index.js b/app/src/index.js index 2c6711c..e4a442a 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -34,6 +34,8 @@ import messagesPortuguese from './translations/pt'; import messagesChinese from './translations/cn'; import messagesSpanish from './translations/es'; import messagesCroatian from './translations/hr'; +import messagesCzech from './translations/cs'; +import messagesItalian from './translations/it'; import './index.css'; @@ -55,7 +57,9 @@ const messages = 'pt' : messagesPortuguese, 'zh' : messagesChinese, 'es' : messagesSpanish, - 'hr' : messagesCroatian + 'hr' : messagesCroatian, + 'cs' : messagesCzech, + 'it' : messagesItalian }; const locale = navigator.language.split(/[-_]/)[0]; // language without region code diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 210243b..4b3dde4 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -5,7 +5,8 @@ const initialState = selectedAudioDevice : null, advancedMode : false, resolution : 'medium', // low, medium, high, veryhigh, ultra - lastN : 4 + lastN : 4, + permanentTopBar : true }; const settings = (state = initialState, action) => diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json new file mode 100644 index 0000000..82ed962 --- /dev/null +++ b/app/src/translations/cs.json @@ -0,0 +1,136 @@ +{ + "socket.disconnected": "Spojení přerušeno", + "socket.reconnecting": "Spojení přerušeno, navazuji spojení..", + "socket.reconnected": "Znovu připojeno", + "socket.requestError": "Chyba při požadavku na server", + + "room.chooseRoom": "Vyberte jméno místnosti, kam se chcete připojit", + "room.cookieConsent": "Tato stránka používá cookies s cílem zajistit co možná nejlepší uživatelský zážitek", + "room.joined": "Připojili jste se do místnosti", + "room.cantJoin": "Není možné se připojit do místnosti", + "room.youLocked": "Místnost byla uzamčena", + "room.cantLock": "Není možné uzamknout místnost", + "room.youUnLocked": "Místnost byla odemčena", + "room.cantUnLock": "Není možné odemknout místnost", + "room.locked": "Místnost je uzamknutá", + "room.unlocked": "Místnost je odemknutá", + "room.newLobbyPeer": "Nový účastník vstoupil do přijímací místnosti", + "room.lobbyPeerLeft": "Účastník opustil přijímací místnost", + "room.lobbyPeerChangedDisplayName": "Účastník v přijímací místnosti změnil jméno na {displayName}", + "room.lobbyPeerChangedPicture": "Účastník v přijímací místnosti změnil obrázek", + "room.setAccessCode": "Přístupový kód místnosti aktualizován", + "room.accessCodeOn": "Přístupový kód místnosti je aktivován", + "room.accessCodeOff": "Přístupový kód místnosti je deaktivován", + "room.peerChangedDisplayName": "{oldDisplayName} je nyní {displayName}", + "room.newPeer": "{displayName} se připojil do místnosti", + "room.newFile": "Je dostupný nový soubor", + "room.toggleAdvancedMode": "Přepnuto do pokročilého módu", + "room.setDemocraticView": "Rozvržení změněno na: demokratický pohled", + "room.setFilmStripView": "Rozvržení změněno na: filmový pás", + "room.loggedIn": "Jste přihlášeni", + "room.loggedOut": "Jste odhlášeni", + "room.changedDisplayName": "Vaše jméno bylo změněno na {displayName}", + "room.changeDisplayNameError": "Při změně Vašeho jména se vyskytla chyba", + "room.chatError": "Zprávu nebylo možné odeslat", + "room.aboutToJoin": "Připojujete se ke konferenci", + "room.roomId": "ID místnosti: {roomName}", + "room.setYourName": "Zadejte své jméno a vyberte, jak se chcete připojit:", + "room.audioOnly": "Pouze Audio", + "room.audioVideo": "Audio a Video", + "room.youAreReady": "Ok, jste připraven", + "room.emptyRequireLogin": "Místnost je prázdná! Pro spuštění konference se přihlašte nebo počkejte, než se připojí hostitel", + "room.locketWait": "Místnost je uzamčena - vydržte, než vás někdo pustí dovnitř ...", + "room.lobbyAdministration": "Administrace Přijímací místnosti", + "room.peersInLobby": "Účastníci v Přijímací místnosti", + "room.lobbyEmpty": "V Přijímací místnosti v současné době nikdo není", + "room.hiddenPeers": "{hiddenPeersCount, plural, one {účastník} other {účastníci}}", + "room.me": "Já", + "room.spotlights": "Aktivní Účastníci", + "room.passive": "Pasivní Účastníci", + "room.videoPaused": "Toto video bylo pozastaveno", + + "tooltip.login": "Přihlášení", + "tooltip.logout": "Odhlášení", + "tooltip.admitFromLobby": "Povolit uživatele z Přijímací místnosti", + "tooltip.lockRoom": "Uzamknout místnost", + "tooltip.unLockRoom": "Odemknout místnost", + "tooltip.enterFullscreen": "Zapnout režim celé obrazovky (fullscreen)", + "tooltip.leaveFullscreen": "Vypnout režim celé obrazovky (fullscreen)", + "tooltip.lobby": "Ukázat Přijímací místnost", + "tooltip.settings": "Zobrazit nastavení", + + "label.roomName": "Jméno místnosti", + "label.chooseRoomButton": "Pokračovat", + "label.yourName": "Vaše jméno", + "label.newWindow": "Nové okno", + "label.fullscreen": "Režim celé obrazovky (Fullscreen)", + "label.openDrawer": "Otevřít zásuvku", + "label.leave": "Opustit", + "label.chatInput": "Napište zprávu chatu...", + "label.chat": "Chat", + "label.filesharing": "Sdílení souborů", + "label.participants": "Účastníci", + "label.shareFile": "Sdílet soubor", + "label.fileSharingUnsupported": "Sdílení souborů není podporováno", + "label.unknown": "Neznámý", + "label.democratic": "Rozvržení: Demokratické", + "label.filmstrip": "Rozvržení: Filmový pás", + "label.low": "Nízké", + "label.medium": "Střední", + "label.high": "Vysoké (HD)", + "label.veryHigh": "Velmi vysoké (FHD)", + "label.ultra": "Ultra (UHD)", + "label.close": "Zavřít", + + "settings.settings": "Nastavení", + "settings.camera": "Kamera", + "settings.selectCamera": "Vyberte video zařízení", + "settings.cantSelectCamera": "Není možné vybrat video zařízení", + "settings.audio": "Audio zařízení", + "settings.selectAudio": "Vyberte audio zařízení", + "settings.cantSelectAudio": "Není možno vybrat audio zařízení", + "settings.resolution": "Vyberte rozlišení vašeho videa", + "settings.layout": "Rozvržení místnosti", + "settings.selectRoomLayout": "Vyberte rozvržení místnosti", + "settings.advancedMode": "Pokočilý mód", + + "filesharing.saveFileError": "Není možné uložit soubor", + "filesharing.startingFileShare": "Pokouším se sdílet soubor", + "filesharing.successfulFileShare": "Soubor byl úspěšně sdílen", + "filesharing.unableToShare": "Není možné sdílet soubor", + "filesharing.error": "Při sdílení souboru se vyskytla chyba", + "filesharing.finished": "Stahování souboru dokončeno", + "filesharing.save": "Uložit", + "filesharing.sharedFile": "{displayName} sdílel(a) soubor", + "filesharing.download": "Stáhnout", + "filesharing.missingSeeds": "Pokud tento proces trvá dlouho, nikdo zřejmě neseeduje tento torrent. Pokuste se někoho požádat, aby znovu nahrál soubor, který potřebujete.", + + "devices.devicesChanged": "Vaše zařízení se změnila, nastavte je v dialogu nastavení", + + "device.audioUnsupported": "Audio není podporováno", + "device.activateAudio": "Aktivovat audio", + "device.muteAudio": "Ztišit zvuk", + "device.unMuteAudio": "Zrušit ztišení zvuku", + + "device.videoUnsupported": "Video není podporováno", + "device.startVideo": "Spustit video", + "device.stopVideo": "Zastavit video", + + "device.screenSharingUnsupported": "Sdílení obrazovky není podporováno", + "device.startScreenSharing": "Spustit sdílení obrazovky", + "device.stopScreenSharing": "Zastavit sdílení obrazovky", + + "devices.microphoneDisconnected": "Mikrofon odpojen", + "devices.microphoneError": "Při přístupu k vašemu mikrofonu se vyskytla chyba", + "devices.microPhoneMute": "Mikrofon ztišen", + "devices.micophoneUnMute": "Ztišení mikrofonu zrušeno", + "devices.microphoneEnable": "Mikrofon povolen", + "devices.microphoneMuteError": "Není možné ztišit váš mikrofon", + "devices.microphoneUnMuteError": "Není možné zrušit ztišení vašeho mikrofonu", + + "devices.screenSharingDisconnected" : "Sdílení obrazovky odpojeno", + "devices.screenSharingError": "Při přístupu k vaší obrazovce se vyskytla chyba", + + "devices.cameraDisconnected": "Kamera odpojena", + "devices.cameraError": "Při přístupu k vaší kameře se vyskytla chyba" +} diff --git a/app/src/translations/it.json b/app/src/translations/it.json new file mode 100644 index 0000000..2234260 --- /dev/null +++ b/app/src/translations/it.json @@ -0,0 +1,140 @@ +{ + "socket.disconnected": "Sei stato disconnesso", + "socket.reconnecting": "Sei stato disconnesso, stiamo provando a riconnetterti", + "socket.reconnected": "Sei stato riconnesso", + "socket.requestError": "Errore di richiesta al server", + + "room.chooseRoom": "Scegli il nome della stanza in cui vorresti entrare", + "room.cookieConsent": "Questo sito fa uso di cookies per migliorare l'esperienza utente", + "room.consentUnderstand": "Ho capito", + "room.joined": "Sei entrato nella stanza", + "room.cantJoin": "Impossibile entrare nella stanza", + "room.youLocked": "Hai bloccato la stanza", + "room.cantLock": "Impossibile bloccare la stanza", + "room.youUnLocked": "Hai sbloccato la stanza", + "room.cantUnLock": "Impossibile sbloccare la stanza", + "room.locked": "La stanza è ora bloccata", + "room.unlocked": "La stanza è ora sbloccata", + "room.newLobbyPeer": "Un nuovo partecipante è entrato nella lobby", + "room.lobbyPeerLeft": "Un partecipante ha abbandonato la lobby", + "room.lobbyPeerChangedDisplayName": "Un partecipante della lobby ha cambiato nome in {displayName}", + "room.lobbyPeerChangedPicture": "Un partecipante della lobby ha cambiato immagine del profilo", + "room.setAccessCode": "Codice d'accesso della stanza aggiornato", + "room.accessCodeOn": "Il codice d'accesso della stanza è attivato", + "room.accessCodeOff": "Il codice d'accesso della stanza è disattivato", + "room.peerChangedDisplayName": "{oldDisplayName} ora si chiama {displayName}", + "room.newPeer": "{displayName} è entrato nella stanza", + "room.newFile": "Nuovo file disponibile", + "room.toggleAdvancedMode": "Attivata/disattivata modalità avanzata", + "room.setDemocraticView": "Aspetto cambiato in vista democratica", + "room.setFilmStripView": "Aspetto cambiato in vista filmstrip", + "room.loggedIn": "Hai eseguito il log in", + "room.loggedOut": "Hai eseguito il log out", + "room.changedDisplayName": "Il tuo nome visualizzato è cambiato in{displayName}", + "room.changeDisplayNameError": "Errore nel modificare il tuo nome visualizzato", + "room.chatError": "Impossibile mandare messaggio in chat", + "room.aboutToJoin": "Stai per entrare in un meeting", + "room.roomId": "ID stanza: {roomName}", + "room.setYourName": "Imposta il tuo nome per la partecipazione, e scegli in che modo vuoi entrare:", + "room.audioOnly": "Solo Audio", + "room.audioVideo": "Audio e Video", + "room.youAreReady": "Ok, sei pronto", + "room.emptyRequireLogin": "La stanza è vuota! Puoi fare il log in per iniziare il meeting, o aspettare finchè l'host non entra", + "room.locketWait": "La stanza è bloccata - resta in attesa finchè qualcuno non ti fa entrare...", + "room.lobbyAdministration": "Amministrazione lobby", + "room.peersInLobby": "Participanti della lobby", + "room.lobbyEmpty": "Attualmente non c'è nessuno nella lobby", + "room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}", + "room.me": "Io", + "room.spotlights": "Partecipanti in Evidenza", + "room.passive": "Participanti Passivi", + "room.videoPaused": "Il video è in pausa", + + "tooltip.login": "Log in", + "tooltip.logout": "Log out", + "tooltip.admitFromLobby": "Ammetti dalla lobby", + "tooltip.lockRoom": "Blocca stanza", + "tooltip.unLockRoom": "Sblocca stanza", + "tooltip.enterFullscreen": "Modalità schermo intero", + "tooltip.leaveFullscreen": "Esci dalla modalità schermo intero", + "tooltip.lobby": "Mostra lobby", + "tooltip.settings": "Mostra impostazioni", + "tooltip.participants": "Mostra partecipanti", + + "label.roomName": "Nome della stanza", + "label.chooseRoomButton": "Continua", + "label.yourName": "Il tuo nome", + "label.newWindow": "Nuova finestra", + "label.fullscreen": "Schermo intero", + "label.openDrawer": "Apri cassetto", + "label.leave": "Esci", + "label.chatInput": "Inserire messaggio...", + "label.chat": "Chat", + "label.filesharing": "Condivisione file", + "label.participants": "Partecipanti", + "label.shareFile": "Condividi file", + "label.fileSharingUnsupported": "Condivisione file non supportata", + "label.unknown": "Sconosciuto", + "label.democratic": "Vista Democratica", + "label.filmstrip": "Vista Filmstrip", + "label.low": "Bassa", + "label.medium": "Media", + "label.high": "Alta (HD)", + "label.veryHigh": "Molto alta (FHD)", + "label.ultra": "Ultra (UHD)", + "label.close": "Chiudi", + + "settings.settings": "Impostazioni", + "settings.camera": "Videocamera", + "settings.selectCamera": "Seleziona dispositivo video", + "settings.cantSelectCamera": "Impossibile selezionare dispositivo video", + "settings.audio": "Dispositivo audio", + "settings.selectAudio": "Seleziona dispositivo audio", + "settings.cantSelectAudio": "Impossibile selezionare dispositivo audio", + "settings.resolution": "Seleziona risoluzione", + "settings.layout": "Aspetto stanza", + "settings.selectRoomLayout": "Seleziona aspetto stanza", + "settings.advancedMode": "Modalità avanzata", + "settings.permanentTopBar": "Barra superiore permanente", + "settings.lastn": "Numero di video visibili", + + "filesharing.saveFileError": "Impossibile salvare file", + "filesharing.startingFileShare": "Tentativo di condivisione file", + "filesharing.successfulFileShare": "File condiviso con successo", + "filesharing.unableToShare": "Impossibile condividere file", + "filesharing.error": "Errore generico nella condivisione file", + "filesharing.finished": "Il file è stato scaricato", + "filesharing.save": "Salva", + "filesharing.sharedFile": "{displayName} ha condiviso un file", + "filesharing.download": "Download", + "filesharing.missingSeeds": "Se questo processo impiega troppo tempo, questo torrent potrebbe non avere alcun seed. Prova a chiedere a qualcuno di caricare nuovamente il file che desideri.", + + "devices.devicesChanged": "Il tuo dispositivo è cambiato, configura i dispositivi nel menù di impostazioni", + + "device.audioUnsupported": "Dispositivo audio non supportato", + "device.activateAudio": "Attiva audio", + "device.muteAudio": "Silenzia audio", + "device.unMuteAudio": "Riattiva audio", + + "device.videoUnsupported": "Dispositivo video non supportato", + "device.startVideo": "Attiva video", + "device.stopVideo": "Disattiva video", + + "device.screenSharingUnsupported": "Condivisione Schermo non supportata", + "device.startScreenSharing": "Inizia Condivisione Schermo", + "device.stopScreenSharing": "Termina Condivisione Schermo", + + "devices.microphoneDisconnected": "Microfono scollegato", + "devices.microphoneError": "Errore con l'accesso al microfono", + "devices.microPhoneMute": "Microfono silenziato", + "devices.micophoneUnMute": "Microfono riattivato", + "devices.microphoneEnable": "Microfono attivo", + "devices.microphoneMuteError": "Impossibile silenziare il microfono", + "devices.microphoneUnMuteError": "Impossibile riattivare il microfono", + + "devices.screenSharingDisconnected" : "Disconnesso da Condivisione Schermo", + "devices.screenSharingError": "Errore con l'accesso al tuo schermo", + + "devices.cameraDisconnected": "Videocamera scollegata", + "devices.cameraError": "Errore con l'accesso alla videocamera" +} diff --git a/server/config/config.example.js b/server/config/config.example.js index 740a9ae..eef6271 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -50,6 +50,12 @@ module.exports = // listeningRedirectPort disabled // use case: loadbalancer backend httpOnly : false, + // WebServer/Express trust proxy config for httpOnly mode + // You can find more info: + // - https://expressjs.com/en/guide/behind-proxies.html + // - https://www.npmjs.com/package/proxy-addr + // use case: loadbalancer backend + trustProxy : '', // 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. diff --git a/server/lib/Room.js b/server/lib/Room.js index f25f31d..bec3e9e 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -472,7 +472,7 @@ class Room extends EventEmitter .filter((joinedPeer) => joinedPeer.id !== peer.id) .map((joinedPeer) => (joinedPeer.peerInfo)); - cb(null, { peers: peerInfos }); + cb(null, { peers: peerInfos, authenticated: peer.authenticated }); // Mark the new Peer as joined. peer.joined = true; diff --git a/server/server.js b/server/server.js index 8faa837..8bc24de 100755 --- a/server/server.js +++ b/server/server.js @@ -99,6 +99,10 @@ const session = expressSession({ } }); +if (config.trustProxy) { + app.set('trust proxy', config.trustProxy); +} + app.use(session); passport.serializeUser((user, done) => @@ -167,7 +171,7 @@ function setupLTI(ltiConfig) const ltiStrategy = new LTIStrategy( ltiConfig, - function(req, lti, done) + (req, lti, done) => { // LTI launch parameters if (lti) @@ -332,7 +336,7 @@ async function setupAuth() // lti launch app.post('/auth/lti', passport.authenticate('lti', { failureRedirect: '/' }), - function(req, res) + (req, res) => { res.redirect(`/${req.user.room}`); } @@ -342,7 +346,7 @@ async function setupAuth() app.get('/auth/logout', (req, res) => { req.logout(); - res.send(logoutHelper()); + req.session.destroy(() => res.send(logoutHelper())); }); // callback @@ -391,7 +395,7 @@ async function runHttpsServer() app.all('*', async (req, res, next) => { - if (req.secure) + if (req.secure || config.httpOnly ) { const ltiURL = new URL(`${req.protocol }://${ req.get('host') }${req.originalUrl}`);