Merge branch 'develop' of github.com:havfo/multiparty-meeting into develop
commit
7510a5da27
32
CHANGELOG.md
32
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
[](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
|
||||
```
|
||||
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
#### Setup Activity
|
||||
|
||||
##### Activity setup basic form
|
||||
|
||||
Open fully the settings **Click on show more!!**
|
||||

|
||||
|
||||
##### Empty full form
|
||||
|
||||

|
||||
|
||||
##### Filled out form
|
||||
|
||||

|
||||
|
||||
## moodle plugin
|
||||
|
||||
Alternativly you can use multipartymeeting moodle plugin:
|
||||
[https://github.com/misi/moodle-mod_multipartymeeting](https://github.com/misi/moodle-mod_multipartymeeting)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
|
|
@ -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/).
|
||||
[](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`
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ const styles = (theme) =>
|
|||
},
|
||||
actionButton :
|
||||
{
|
||||
margin : theme.spacing(1),
|
||||
padding : 0
|
||||
margin : theme.spacing(1, 0),
|
||||
padding : theme.spacing(0, 1)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -446,4 +446,4 @@ export default withRoomContext(connect(
|
|||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles, { withTheme: true })(TopBar)));
|
||||
)(withStyles(styles, { withTheme: true })(TopBar)));
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ import messagesPortuguese from './translations/pt';
|
|||
import messagesChinese from './translations/cn';
|
||||
import messagesSpanish from './translations/es';
|
||||
import messagesCroatian from './translations/hr';
|
||||
import messagesCzech from './translations/cz';
|
||||
import messagesCzech from './translations/cs';
|
||||
import messagesItalian from './translations/it';
|
||||
import messagesUkrainian from './translations/uk';
|
||||
|
||||
import './index.css';
|
||||
|
||||
|
|
@ -57,7 +59,9 @@ const messages =
|
|||
'zh' : messagesChinese,
|
||||
'es' : messagesSpanish,
|
||||
'hr' : messagesCroatian,
|
||||
'cz' : messagesCzech
|
||||
'cs' : messagesCzech,
|
||||
'it' : messagesItalian,
|
||||
'uk' : messagesUkrainian
|
||||
};
|
||||
|
||||
const locale = navigator.language.split(/[-_]/)[0]; // language without region code
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
"room.chooseRoom": "Wybór konferencji",
|
||||
"room.cookieConsent": "Ta strona internetowa wykorzystuje pliki cookie w celu zwiększenia wygody użytkowania.",
|
||||
"room.consentUnderstand": "I understand",
|
||||
"room.consentUnderstand": "Rozumiem",
|
||||
"room.joined": "Podłączono do konferencji",
|
||||
"room.cantJoin": "Brak możliwości dołączenia do pokoju",
|
||||
"room.youLocked": "Zakluczono pokój",
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
"settings.selectRoomLayout": "Ustawienia układu konferencji",
|
||||
"settings.advancedMode": "Tryb zaawansowany",
|
||||
"settings.permanentTopBar": "Stały górny pasek",
|
||||
"settings.lastn": "Liczba widocznych filmów",
|
||||
"settings.lastn": "Liczba widocznych uczestników (zdalnych)",
|
||||
|
||||
"filesharing.saveFileError": "Nie można zapisać pliku",
|
||||
"filesharing.startingFileShare": "Próba udostępnienia pliku",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"socket.disconnected": "Ви відключені",
|
||||
"socket.reconnecting": "Ви від'єдналися, намагаєтесь знову підключитися",
|
||||
"socket.reconnected": "Ви знову підключилися",
|
||||
"socket.requestError": "Помилка при запиті сервера",
|
||||
|
||||
"room.chooseRoom": "Виберіть назву кімнати, до якої хочете приєднатися",
|
||||
"room.cookieConsent": "Цей веб-сайт використовує файли cookie для поліпшення роботи користувачів",
|
||||
"room.consentUnderstand": "Я розумію",
|
||||
"room.joined": "Ви приєдналися до кімнати",
|
||||
"room.cantJoin": "Неможливо приєднатися до кімнати",
|
||||
"room.youLocked": "Ви заблокували кімнату",
|
||||
"room.cantLock": "Не вдається заблокувати кімнату",
|
||||
"room.youUnLocked": "Ви розблокували кімнату",
|
||||
"room.cantUnLock": "Не вдається розблокувати кімнату",
|
||||
"room.locked": "Кімната зараз заблокована",
|
||||
"room.unlocked": "Кімната зараз розблокована",
|
||||
"room.newLobbyPeer": "Новий учасник увійшов у зал очікування",
|
||||
"room.lobbyPeerLeft": "Учасник вийшов із зала очікування",
|
||||
"room.lobbyPeerChangedDisplayName": "Учасник у залі очікування змінив ім'я на {displayName}",
|
||||
"room.lobbyPeerChangedPicture": "Учасник залу очікування змінив зображення",
|
||||
"room.setAccessCode": "Код доступу до кімнати оновлений",
|
||||
"room.accessCodeOn": "Код доступу до кімнати зараз активований",
|
||||
"room.accessCodeOff": "Код доступу до кімнати зараз відключений",
|
||||
"room.peerChangedDisplayName": "{oldDisplayName} змінив ім'я на {displayName}",
|
||||
"room.newPeer": "{displayName} приєднався до кімнати",
|
||||
"room.newFile": "Новий файл є у доступі",
|
||||
"room.toggleAdvancedMode": "Увімкнено розширений режим",
|
||||
"room.setDemocratView": "Змінено макет на демократичний вигляд",
|
||||
"room.setFilmStripView": "Змінено макет на вид фільму",
|
||||
"room.loggedIn": "Ви ввійшли в систему",
|
||||
"room.loggedOut": "Ви вийшли з системи",
|
||||
"room.changedDisplayName": "Відображуване ім’я змінено на {displayName}",
|
||||
"room.changeDisplayNameError": "Сталася помилка під час зміни вашого відображуваного імені",
|
||||
"room.chatError": "Не вдається надіслати повідомлення в чаті",
|
||||
"room.aboutToJoin": "Ви збираєтесь приєднатися до зустрічі",
|
||||
"room.roomId": "Ідентифікатор кімнати: {roomName}",
|
||||
"room.setYourName": "Встановіть своє ім'я для участі та виберіть, як ви хочете приєднатися:",
|
||||
"room.audioOnly": "Тільки аудіо",
|
||||
"room.audioVideo": "Аудіо та відео",
|
||||
"room.youAreReady": "Добре, ви готові",
|
||||
"room.emptyRequireLogin": "Кімната порожня! Ви можете увійти, щоб розпочати зустріч або чекати, поки хост приєднається",
|
||||
"room.locketWait": "Кімната заблокована - дочекайтеся, поки хтось не впустить вас у ...",
|
||||
"room.lobbyAdministration": "Адміністрація залу очікування",
|
||||
"room.peersInLobby": "Учасники залу очікувань",
|
||||
"room.lobbyEmpty": "Наразі у залі очікувань немає нікого",
|
||||
"room.hiddenPeers": "{hiddenPeersCount, множина, один {учасник} інший {учасників}}",
|
||||
"room.me": "Я",
|
||||
"room.spotlights": "Учасники у центрі уваги",
|
||||
"room.passive": "Пасивні учасники",
|
||||
"room.videoPaused": "Це відео призупинено",
|
||||
|
||||
"tooltip.login": "Увійти",
|
||||
"tooltip.logout": "Вихід",
|
||||
"tooltip.admitFromLobby": "Вхід із залу очікувань",
|
||||
"tooltip.lockRoom": "Заблокувати кімнату",
|
||||
"tooltip.unLockRoom": "Розблокувати кімнату",
|
||||
"tooltip.enterFullscreen": "Вивести повний екран",
|
||||
"tooltip.leaveFullscreen": "Залишити повноекранний екран",
|
||||
"tooltip.lobby": "Показати зал очікувань",
|
||||
"tooltip.settings": "Показати налаштування",
|
||||
"tooltip.participants": "Показати учасників",
|
||||
|
||||
"label.roomName": "Назва кімнати",
|
||||
"label.chooseRoomButton": "Продовжити",
|
||||
"label.yourName": "Ваше ім'я",
|
||||
"label.newWindow": "Нове вікно",
|
||||
"label.fullscreen": "Повний екран",
|
||||
"label.openDrawer": "Відкрити ящик",
|
||||
"label.leave": "Залишити",
|
||||
"label.chatInput": "Введіть повідомлення чату ...",
|
||||
"label.chat": "Чат",
|
||||
"label.filesharing": "Обмін файлами",
|
||||
"label.participants": "Учасники",
|
||||
"label.shareFile": "Надіслати файл",
|
||||
"label.fileSharingUnsupported": "Обмін файлами не підтримується",
|
||||
"label.unknown": "Невідомо",
|
||||
"label.democrat": "Демократичний вигляд",
|
||||
"label.filmstrip": "У вигляді кінострічки",
|
||||
"label.low": "Низький",
|
||||
"label.medium": "Середній",
|
||||
"label.high": "Високий (HD)",
|
||||
"label.veryHigh": "Дуже високий (FHD)",
|
||||
"label.ultra": "Ультра (UHD)",
|
||||
"label.close": "Закрити",
|
||||
|
||||
"settings.settings": "Налаштування",
|
||||
"settings.camera": "Камера",
|
||||
"settings.selectCamera": "Вибрати відеопристрій",
|
||||
"settings.cantSelectCamera": "Неможливо вибрати відеопристрій",
|
||||
"settings.audio": "Аудіопристрій",
|
||||
"settings.selectAudio": "Вибрати аудіопристрій",
|
||||
"settings.cantSelectAudio": "Неможливо вибрати аудіопристрій",
|
||||
"settings.resolution": "Виберіть роздільну здатність відео",
|
||||
"settings.layout": "Розміщення кімнати",
|
||||
"settings.selectRoomLayout": "Вибір розташування кімнати",
|
||||
"settings.advancedMode": "Розширений режим",
|
||||
"settings.permanentTopBar": "Постійний верхній рядок",
|
||||
"settings.lastn": "Кількість видимих відео",
|
||||
|
||||
"filesharing.saveFileError": "Неможливо зберегти файл",
|
||||
"filesharing.startingFileShare": "Спроба поділитися файлом",
|
||||
"filesharing.successfulFileShare": "Файл готовий для обміну",
|
||||
"filesharing.unableToShare": "Неможливо поділитися файлом",
|
||||
"filesharing.error": "Виникла помилка обміну файлами",
|
||||
"filesharing.finished": "Завантаження файлу закінчено",
|
||||
"filesharing.save": "Зберегти",
|
||||
"filesharing.sharedFile": "{displayName} поділився файлом",
|
||||
"filesharing.download": "Завантажити",
|
||||
"filesharing.missingSeeds": "Якщо цей процес триває тривалий час, може не з’явиться хтось, хто роздає цей торрент. Спробуйте попросити когось перезавантажити потрібний файл.",
|
||||
|
||||
"devices.devicesChanged": "Ваші пристрої змінилися, налаштуйте ваші пристрої в діалоговому вікні налаштувань",
|
||||
|
||||
"device.audioUnsupported": "Аудіо не підтримується",
|
||||
"device.activateAudio": "Активувати звук",
|
||||
"device.muteAudio": "Вимкнути звук",
|
||||
"device.unMuteAudio": "Увімкнути звук",
|
||||
|
||||
"device.videoUnsupported": "Відео не підтримується",
|
||||
"device.startVideo": "Запустити відео",
|
||||
"device.stopVideo": "Зупинити відео",
|
||||
|
||||
"device.screenSharingUnsupported": "Обмін екраном не підтримується",
|
||||
"device.startScreenSharing": "Початок спільного використання екрана",
|
||||
"device.stopScreenSharing": "Зупинити спільний доступ до екрана",
|
||||
|
||||
"devices.microphoneDisconnected": "Мікрофон відключений",
|
||||
"devices.microphoneError": "Сталася помилка під час доступу до мікрофона",
|
||||
"devices.microPhoneMute": "Вимкнено ваш мікрофон",
|
||||
"devices.micophoneUnMute": "Не ввімкнено ваш мікрофон",
|
||||
"devices.microphoneEnable": "Увімкнено мікрофон",
|
||||
"devices.microphoneMuteError": "Не вдається вимкнути мікрофон",
|
||||
"devices.microphoneUnMuteError": "Неможливо ввімкнути мікрофон",
|
||||
|
||||
"devices.screenSharingDisconnected": "Спільний доступ до екрана відключений",
|
||||
"devices.screenSharingError": "Сталася помилка під час доступу до екрану",
|
||||
|
||||
"devices.cameraDisconnected": "Камера відключена",
|
||||
"devices.cameraError": "Під час доступу до камери сталася помилка"
|
||||
}
|
||||
|
|
@ -58,6 +58,9 @@ module.exports =
|
|||
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
|
||||
key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem`
|
||||
},
|
||||
// listening Host or IP
|
||||
// If ommitted listens on every IP. ("0.0.0.0" and "::")
|
||||
//listeningHost: 'localhost',
|
||||
// Listening port for https server.
|
||||
listeningPort : 443,
|
||||
// Any http request is redirected to https.
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ function setupLTI(ltiConfig)
|
|||
|
||||
const ltiStrategy = new LTIStrategy(
|
||||
ltiConfig,
|
||||
function(req, lti, done)
|
||||
(req, lti, done) =>
|
||||
{
|
||||
// LTI launch parameters
|
||||
if (lti)
|
||||
|
|
@ -310,7 +310,7 @@ async function setupAuth()
|
|||
// lti launch
|
||||
app.post('/auth/lti',
|
||||
passport.authenticate('lti', { failureRedirect: '/' }),
|
||||
function(req, res)
|
||||
(req, res) =>
|
||||
{
|
||||
res.redirect(`/${req.user.room}`);
|
||||
}
|
||||
|
|
@ -333,7 +333,7 @@ async function setupAuth()
|
|||
}
|
||||
|
||||
req.logout();
|
||||
res.send(logoutHelper());
|
||||
req.session.destroy(() => res.send(logoutHelper()));
|
||||
});
|
||||
|
||||
// callback
|
||||
|
|
@ -427,11 +427,17 @@ async function runHttpsServer()
|
|||
// http
|
||||
const redirectListener = http.createServer(app);
|
||||
|
||||
redirectListener.listen(config.listeningRedirectPort);
|
||||
if(config.listeningHost)
|
||||
redirectListener.listen(config.listeningRedirectPort, config.listeningHost);
|
||||
else
|
||||
redirectListener.listen(config.listeningRedirectPort);
|
||||
}
|
||||
|
||||
// https or http
|
||||
mainListener.listen(config.listeningPort);
|
||||
if(config.listeningHost)
|
||||
mainListener.listen(config.listeningPort, config.listeningHost);
|
||||
else
|
||||
mainListener.listen(config.listeningPort);
|
||||
}
|
||||
|
||||
function isPathAlreadyTaken(url)
|
||||
|
|
|
|||
Loading…
Reference in New Issue