diff --git a/docker-compose.yml b/docker-compose.yml index 4f3c55c7..e7b0c0c3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,168 +1,169 @@ version: '3' services: coturn: build: context: ./docker/coturn/ container_name: kolab-coturn environment: - REDIS_DBNAME=${COTURN_REDIS_DATABASE} - REDIS_PASSWORD=${COTURN_REDIS_PASSWORD} - REDIS_IP=${COTURN_REDIS_IP} - TURN_PUBLIC_IP=${COTURN_PUBLIC_IP} - TURN_LISTEN_PORT=3478 hostname: sturn.mgmt.com image: kolab-coturn network_mode: host restart: on-failure tty: true kolab: build: context: ./docker/kolab/ container_name: kolab depends_on: - mariadb extra_hosts: - "kolab.mgmt.com:127.0.0.1" environment: - DB_HOST=${DB_HOST} - DB_ROOT_PASSWORD=Welcome2KolabSystems healthcheck: interval: 10s test: test -f /tmp/kolab-init.done timeout: 5s retries: 30 hostname: kolab.mgmt.com image: kolab network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - /etc/letsencrypt/:/etc/letsencrypt/:ro - ./docker/certs/ca.cert:/etc/pki/tls/certs/ca.cert:ro - ./docker/certs/ca.cert:/etc/pki/ca-trust/source/anchors/ca.cert:ro - ./docker/certs/kolab.hosted.com.cert:/etc/pki/tls/certs/kolab.hosted.com.cert - ./docker/certs/kolab.hosted.com.key:/etc/pki/tls/certs/kolab.hosted.com.key - ./docker/certs/kolab.mgmt.com.cert:/etc/pki/tls/certs/kolab.mgmt.com.cert - ./docker/certs/kolab.mgmt.com.key:/etc/pki/tls/certs/kolab.mgmt.com.key - ./docker/kolab/utils:/root/utils:ro - ./src/.env:/.dockerenv:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro mariadb: container_name: kolab-mariadb environment: MYSQL_ROOT_PASSWORD: Welcome2KolabSystems TZ: "+02:00" healthcheck: interval: 10s test: test -e /var/run/mysqld/mysqld.sock timeout: 5s retries: 30 image: mariadb network_mode: host nginx: build: context: ./docker/nginx/ args: APP_WEBSITE_DOMAIN: ${APP_WEBSITE_DOMAIN:?err} container_name: kolab-nginx hostname: nginx.hosted.com image: kolab-nginx network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./docker/certs/imap.hosted.com.cert:/etc/pki/tls/certs/imap.hosted.com.cert - ./docker/certs/imap.hosted.com.key:/etc/pki/tls/private/imap.hosted.com.key pdns-sql: build: context: ./docker/pdns-sql/ container_name: kolab-pdns-sql depends_on: - mariadb hostname: pdns-sql image: apheleia/kolab-pdns-sql network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro proxy: build: context: ./docker/proxy/ container_name: kolab-proxy hostname: kanarip.internet-box.ch image: kolab-proxy network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./docker/certs/:/etc/certs/:ro - /etc/letsencrypt/:/etc/letsencrypt/:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro redis: build: context: ./docker/redis/ container_name: kolab-redis hostname: redis image: redis network_mode: host volumes: - ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro swoole: build: context: ./docker/swoole/ container_name: kolab-swoole image: apheleia/swoole:4.6.x worker: build: context: ./docker/worker/ container_name: kolab-worker depends_on: - kolab hostname: worker image: kolab-worker network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./src:/home/worker/src.orig:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro meet: build: context: ./docker/meet/ environment: - REDIS_IP=${MEET_REDIS_IP} - REDIS_PORT=${MEET_REDIS_PORT} - REDIS_DBNAME=${MEET_REDIS_DATABASE} - REDIS_PASSWORD=${MEET_REDIS_PASSWORD} - PUBLIC_IP=${MEET_PUBLIC_IP} + - PUBLIC_DOMAIN=${MEET_PUBLIC_DOMAIN} - TURN_SERVER=${MEET_TURN_SERVER} network_mode: host container_name: kolab-meet image: kolab-meet volumes: - /etc/letsencrypt/:/etc/letsencrypt/:ro - ./meet/server:/src/meet/:ro - ./docker/meet/build/node_modules:/root/node_modules - ./docker/certs/kolab.hosted.com.cert:/etc/pki/tls/certs/kolab.hosted.com.cert - ./docker/certs/kolab.hosted.com.key:/etc/pki/tls/certs/kolab.hosted.com.key diff --git a/meet/server/config/config.js b/meet/server/config/config.js index f28e7109..d498ad33 100644 --- a/meet/server/config/config.js +++ b/meet/server/config/config.js @@ -1,247 +1,249 @@ const os = require('os'); const userRoles = require('../userRoles'); const { BYPASS_ROOM_LOCK, BYPASS_LOBBY } = require('../access'); const { CHANGE_ROOM_LOCK, PROMOTE_PEER, MODIFY_ROLE, SEND_CHAT, MODERATE_CHAT, SHARE_AUDIO, SHARE_VIDEO, SHARE_SCREEN, EXTRA_VIDEO, SHARE_FILE, MODERATE_FILES, MODERATE_ROOM } = require('../permissions'); module.exports = { // URI and key for requesting geoip-based TURN server closest to the client // turnAPIKey : 'examplekey', // turnAPIURI : 'https://example.com/api/turn', // turnAPIparams : { // 'uri_schema' : 'turn', // 'transport' : 'tcp', // 'ip_ver' : 'ipv4', // 'servercount' : '2' // }, // turnAPITimeout : 2 * 1000, // Backup turnservers if REST fails or is not configured backupTurnServers : [ { urls : [ process.env.TURN_SERVER || 'turn:127.0.0.1:3478?transport=tcp' ], //FIXME we use hardcoded credentials for now username : 'username1', credential : 'password1' } ], // redis server options used for session storage redisOptions : { host: process.env.REDIS_IP || '127.0.0.1', port: process.env.REDIS_PORT || 6379, db: process.env.REDIS_DB || '3', ...(process.env.REDIS_PASSWORD ? {password: process.env.REDIS_PASSWORD} : {}) }, // session cookie secret cookieSecret : 'T0P-S3cR3t_cook!e', cookieName : 'edumeet.sid', // if you use encrypted private key the set the passphrase tls : { //cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`, // passphrase: 'key_password' //key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem` cert : `/etc/pki/tls/certs/kolab.hosted.com.cert`, key : `/etc/pki/tls/certs/kolab.hosted.com.key`, }, // listening Host or IP // If omitted listens on every IP. ("0.0.0.0" and "::") //listeningHost: 'localhost', // Listening port for https server. listeningPort : 12443, // Any http request is redirected to https. // Listening port for http server. listeningRedirectPort : 12080, // Listens only on http, only on listeningPort // listeningRedirectPort disabled // use case: loadbalancer backend httpOnly : true, + publicDomain : process.env.PUBLIC_DOMAIN || '127.0.0.1:12443', + pathPrefix : '/meetmedia', // 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 : '', accessFromRoles : { // The role(s) will gain access to the room // even if it is locked (!) [BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ], // The role(s) will gain access to the room without // going into the lobby. If you want to restrict access to your // server to only directly allow authenticated users, you could // add the userRoles.AUTHENTICATED to the user in the userMapping // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] [BYPASS_LOBBY] : [ userRoles.NORMAL ] }, permissionsFromRoles : { // The role(s) have permission to lock/unlock a room [CHANGE_ROOM_LOCK] : [ userRoles.MODERATOR ], // The role(s) have permission to promote a peer from the lobby [PROMOTE_PEER] : [ userRoles.NORMAL ], // The role(s) have permission to give/remove other peers roles [MODIFY_ROLE] : [ userRoles.NORMAL ], // The role(s) have permission to send chat messages [SEND_CHAT] : [ userRoles.NORMAL ], // The role(s) have permission to moderate chat [MODERATE_CHAT] : [ userRoles.MODERATOR ], // The role(s) have permission to share audio [SHARE_AUDIO] : [ userRoles.NORMAL ], // The role(s) have permission to share video [SHARE_VIDEO] : [ userRoles.NORMAL ], // The role(s) have permission to share screen [SHARE_SCREEN] : [ userRoles.NORMAL ], // The role(s) have permission to produce extra video [EXTRA_VIDEO] : [ userRoles.NORMAL ], // The role(s) have permission to share files [SHARE_FILE] : [ userRoles.NORMAL ], // The role(s) have permission to moderate files [MODERATE_FILES] : [ userRoles.MODERATOR ], // The role(s) have permission to moderate room (e.g. kick user) [MODERATE_ROOM] : [ userRoles.MODERATOR ] }, // Array of permissions. If no peer with the permission in question // is in the room, all peers are permitted to do the action. The peers // that are allowed because of this rule will not be able to do this // action as soon as a peer with the permission joins. In this example // everyone will be able to lock/unlock room until a MODERATOR joins. allowWhenRoleMissing : [ CHANGE_ROOM_LOCK ], // When truthy, the room will be open to all users when as long as there // are allready users in the room activateOnHostJoin : true, // When set, maxUsersPerRoom defines how many users can join // a single room. If not set, there is no limit. // maxUsersPerRoom : 20, // Room size before spreading to new router routerScaleSize : 40, // Socket timout value requestTimeout : 20000, // Socket retries when timeout requestRetries : 3, // Mediasoup settings mediasoup : { numWorkers : Object.keys(os.cpus()).length, // mediasoup Worker settings. worker : { logLevel : 'warn', logTags : [ 'info', 'ice', 'dtls', 'rtp', 'srtp', 'rtcp' ], rtcMinPort : 40000, rtcMaxPort : 49999 }, // mediasoup Router settings. router : { // Router media codecs. mediaCodecs : [ { kind : 'audio', mimeType : 'audio/opus', clockRate : 48000, channels : 2 }, { kind : 'video', mimeType : 'video/VP8', clockRate : 90000, parameters : { 'x-google-start-bitrate' : 1000 } }, { kind : 'video', mimeType : 'video/VP9', clockRate : 90000, parameters : { 'profile-id' : 2, 'x-google-start-bitrate' : 1000 } }, { kind : 'video', mimeType : 'video/h264', clockRate : 90000, parameters : { 'packetization-mode' : 1, 'profile-level-id' : '4d0032', 'level-asymmetry-allowed' : 1, 'x-google-start-bitrate' : 1000 } }, { kind : 'video', mimeType : 'video/h264', clockRate : 90000, parameters : { 'packetization-mode' : 1, 'profile-level-id' : '42e01f', 'level-asymmetry-allowed' : 1, 'x-google-start-bitrate' : 1000 } } ] }, // mediasoup WebRtcTransport settings. webRtcTransport : { listenIps : [ { ip: process.env.PUBLIC_IP || '127.0.0.1', announcedIp: null } // Can have multiple listening interfaces // change 2001:DB8::1 IPv6 to your server's IPv6 address!! // { ip: '2001:DB8::1', announcedIp: null } ], initialAvailableOutgoingBitrate : 1000000, minimumAvailableOutgoingBitrate : 600000, // Additional options that are not part of WebRtcTransportOptions. maxIncomingBitrate : 1500000 } } /* , // Prometheus exporter prometheus : { deidentify : false, // deidentify IP addresses // listen : 'localhost', // exporter listens on this address numeric : false, // show numeric IP addresses port : 8889, // allocated port quiet : false // include fewer labels } */ }; diff --git a/meet/server/server.js b/meet/server/server.js index 3df1d87a..fb4907ab 100755 --- a/meet/server/server.js +++ b/meet/server/server.js @@ -1,496 +1,486 @@ #!/usr/bin/env node process.title = 'edumeet-server'; const bcrypt = require('bcrypt'); const config = require('./config/config'); const fs = require('fs'); const http = require('http'); const spdy = require('spdy'); const express = require('express'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const compression = require('compression'); const mediasoup = require('mediasoup'); const AwaitQueue = require('awaitqueue'); const Logger = require('./lib/Logger'); const Room = require('./lib/Room'); const Peer = require('./lib/Peer'); const base64 = require('base-64'); const helmet = require('helmet'); const userRoles = require('./userRoles'); // auth const redis = require('redis'); const redisClient = redis.createClient(config.redisOptions); const expressSession = require('express-session'); const RedisStore = require('connect-redis')(expressSession); const sharedSession = require('express-socket.io-session'); const interactiveServer = require('./lib/interactiveServer'); const promExporter = require('./lib/promExporter'); const { v4: uuidv4 } = require('uuid'); /* eslint-disable no-console */ console.log('- process.env.DEBUG:', process.env.DEBUG); console.log('- config.mediasoup.worker.logLevel:', config.mediasoup.worker.logLevel); console.log('- config.mediasoup.worker.logTags:', config.mediasoup.worker.logTags); /* eslint-enable no-console */ const logger = new Logger(); const queue = new AwaitQueue(); let statusLogger = null; if ('StatusLogger' in config) statusLogger = new config.StatusLogger(); // mediasoup Workers. // @type {Array} const mediasoupWorkers = []; // Map of Room instances indexed by roomId. const rooms = new Map(); // Map of Peer instances indexed by peerId. const peers = new Map(); // TLS server configuration. const tls = { cert : fs.readFileSync(config.tls.cert), key : fs.readFileSync(config.tls.key), secureOptions : 'tlsv12', ciphers : [ 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-RSA-AES256-GCM-SHA384' ].join(':'), honorCipherOrder : true }; const app = express(); app.use(helmet.hsts()); const sharedCookieParser=cookieParser(); app.use(sharedCookieParser); app.use(bodyParser.json({ limit: '5mb' })); app.use(bodyParser.urlencoded({ limit: '5mb', extended: true })); const session = expressSession({ secret : config.cookieSecret, name : config.cookieName, resave : true, saveUninitialized : true, store : new RedisStore({ client: redisClient }), cookie : { secure : true, httpOnly : true, maxAge : 60 * 60 * 1000 // Expire after 1 hour since last request from user } }); if (config.trustProxy) { app.set('trust proxy', config.trustProxy); } app.use(session); let mainListener; let io; async function run() { try { // Open the interactive server. await interactiveServer(rooms, peers); // start Prometheus exporter if (config.prometheus) { await promExporter(rooms, peers, config.prometheus); } - // if (typeof (config.auth) === 'undefined') - // { - // logger.warn('Auth is not configured properly!'); - // } - // else - // { - // await setupAuth(); - // } - // Run a mediasoup Worker. await runMediasoupWorkers(); // Run HTTPS server. await runHttpsServer(); // Run WebSocketServer. await runWebSocketServer(); // eslint-disable-next-line no-unused-vars const errorHandler = (err, req, res, next) => { const trackingId = uuidv4(); res.status(500).send( `

Internal Server Error

If you report this error, please also report this tracking ID which makes it possible to locate your session in the logs which are available to the system administrator: ${trackingId}

` ); logger.error( 'Express error handler dump with tracking ID: %s, error dump: %o', trackingId, err); }; // eslint-disable-next-line no-unused-vars app.use(errorHandler); } catch (error) { logger.error('run() [error:"%o"]', error); } } function statusLog() { if (statusLogger) { statusLogger.log({ rooms : rooms, peers : peers }); } } async function runHttpsServer() { app.use(compression()); - // app.use('/.well-known/acme-challenge', express.static('public/.well-known/acme-challenge')); - - app.get('/ping', function (req, res, next) { - res.send('PONG3') + app.get(`${config.pathPrefix}/api/ping`, function (req, res, next) { + res.send('PONG') }) - app.get('/api/sessions', function (req, res, next) { + app.get(`${config.pathPrefix}/api/sessions`, function (req, res, next) { //TODO json.stringify res.json({ id : "testId" }) }) //Check if the room exists - app.get('/api/sessions/:session_id', function (req, res, next) { + app.get(`${config.pathPrefix}/api/sessions/:session_id`, function (req, res, next) { console.warn("Checking for room") let room = rooms.get(req.params.session_id); if (!room) { console.warn("doesn't exist") res.status(404).send() } else { console.warn("exist") res.status(200).send() } }) // Create room and return id - app.post('/api/sessions', async function (req, res, next) { + app.post(`${config.pathPrefix}/api/sessions`, async function (req, res, next) { console.warn("Creating new room", req.body.mediaMode, req.body.recordingMode) //FIXME we're truncating because of kolab4 database layout (should be fixed instead) const roomId = uuidv4().substring(0, 16) const room = await getOrCreateRoom({ roomId }); res.json({ id : roomId }) }) - app.post('/api/signal', async function (req, res, next) { + app.post(`${config.pathPrefix}/api/signal`, async function (req, res, next) { let data = req.body; const roomId = data['session']; const signalType = data['type']; const payload = data['data']; const peers = data['to']; if (peers) { for (const peerId of peers) { let peer = peers.get(peerId); peer.socket.emit( 'signal', data ); } } else { io.to(roomId).emit( 'signal', data ); } res.json({}) }); // Create connection in room (just wait for websocket instead? // $post = [ // 'json' => [ // 'role' => self::OV_ROLE_PUBLISHER, // 'data' => json_encode(['role' => $role]) // ] // ]; - app.post('/api/sessions/:session_id/connection', function (req, res, next) { + app.post(`${config.pathPrefix}/api/sessions/:session_id/connection`, function (req, res, next) { console.warn("Creating connection in session", req.params.session_id) roomId = req.params.session_id //FIXME we're truncating because of kolab4 database layout (should be fixed instnead) const peerId = uuidv4().substring(0, 16) //TODO create room already? peer = new Peer({ id: peerId, roomId }); peers.set(peerId, peer); peer.on('close', () => { peers.delete(peerId); statusLog(); }); peer.nickname = "Display Name"; // peer.picture = picture; peer.email = "email@test.com"; peer.authenticated = true; peer.addRole(userRoles.MODERATOR); peer.addRole(userRoles.AUTHENTICATED); - hostname = "localhost" //TODO from config - port = "12443" //TODO from config res.json({ id : peerId, - //When the below get's passed to the socket.io client we end up with something like wss://localhost:12443/socket.io/?peerId=peer1&roomId=room1&EIO=3&transport=websocket, - token : `ws://${hostname}:${port}/?peerId=${peerId}&roomId=${roomId}` + //When the below get's passed to the socket.io client we end up with something like (depending on the socket.io path) wss://${publicDomain}/meetmedia/signaling/?peerId=peer1&roomId=room1&EIO=3&transport=websocket, + token : `wss://${config.publicDomain}/?peerId=${peerId}&roomId=${roomId}` }) }) // app.all('*', async (req, res, next) => // { // logger.error('Something is happening'); // if (req.secure || config.httpOnly) // { // let ltiURL; // try // { // ltiURL = new URL(`${req.protocol}://${req.get('host')}${req.originalUrl}`); // } // catch (error) // { // logger.error('Error parsing LTI url: %o', error); // } // if ( // req.isAuthenticated && // req.user && // req.user.nickname && // !ltiURL.searchParams.get('nickname') && // !isPathAlreadyTaken(req.url) // ) // { // ltiURL.searchParams.append('nickname', req.user.nickname); // res.redirect(ltiURL); // } // else // { // const specialChars = "<>@!^*()[]{}:;|'\"\\,~`"; // for (let i = 0; i < specialChars.length; i++) // { // if (req.url.substring(1).indexOf(specialChars[i]) > -1) // { // req.url = `/${encodeURIComponent(encodeURI(req.url.substring(1)))}`; // res.redirect(`${req.url}`); // } // } // return next(); // } // } // else // res.redirect(`https://${req.hostname}${req.url}`); // }); // Serve all files in the public folder as static files. // app.use(express.static('public')); // app.use((req, res) => res.sendFile(`${__dirname}/public/index.html`)); if (config.httpOnly === true) { // http mainListener = http.createServer(app); } else { // https mainListener = spdy.createServer(tls, app); // http const redirectListener = http.createServer(app); if (config.listeningHost) redirectListener.listen(config.listeningRedirectPort, config.listeningHost); else redirectListener.listen(config.listeningRedirectPort); } console.info(`Listening on ${config.listeningPort} ${config.listeningHost}`) // https or http if (config.listeningHost) mainListener.listen(config.listeningPort, config.listeningHost); else mainListener.listen(config.listeningPort); } /** * Create a WebSocketServer to allow WebSocket connections from browsers. */ async function runWebSocketServer() { - io = require('socket.io')(mainListener, { cookie: false }); + io = require('socket.io')(mainListener, { + path: `${config.pathPrefix}/signaling`, + cookie: false + }); io.use( sharedSession(session, sharedCookieParser, { autoSave: true }) ); // Handle connections from clients. io.on('connection', (socket) => { logger.info("websocket connection") const { roomId, peerId } = socket.handshake.query; if (!roomId || !peerId) { logger.warn('connection request without roomId and/or peerId'); socket.disconnect(true); return; } logger.info( 'connection request [roomId:"%s", peerId:"%s"]', roomId, peerId); queue.push(async () => { const { token } = socket.handshake.session; const room = await getOrCreateRoom({ roomId }); let peer = peers.get(peerId); if (!peer) { logger.warn("Peer does not exist %s", peerId); socket.disconnect(true); return; } let returning = false; peer.socket = socket; //FIXME figure out to which extent we need to handle returning users // Returning user, remove if old peer exists // TODO maintain metadata? // if (peer) { // peer.close(); // returning = true; // } room.handlePeer({ peer, returning }); statusLog(); }) .catch((error) => { logger.error('room creation or room joining failed [error:"%o"]', error); if (socket) socket.disconnect(true); return; }); }); } /** * Launch as many mediasoup Workers as given in the configuration file. */ async function runMediasoupWorkers() { const { numWorkers } = config.mediasoup; logger.info('running %d mediasoup Workers...', numWorkers); for (let i = 0; i < numWorkers; ++i) { const worker = await mediasoup.createWorker( { logLevel : config.mediasoup.worker.logLevel, logTags : config.mediasoup.worker.logTags, rtcMinPort : config.mediasoup.worker.rtcMinPort, rtcMaxPort : config.mediasoup.worker.rtcMaxPort }); worker.on('died', () => { logger.error( 'mediasoup Worker died, exiting in 2 seconds... [pid:%d]', worker.pid); setTimeout(() => process.exit(1), 2000); }); mediasoupWorkers.push(worker); } } /** * Get a Room instance (or create one if it does not exist). */ async function getOrCreateRoom({ roomId }) { let room = rooms.get(roomId); // If the Room does not exist create a new one. if (!room) { logger.info('creating a new Room [roomId:"%s"]', roomId); room = await Room.create({ mediasoupWorkers, roomId, peers }); rooms.set(roomId, room); statusLog(); room.on('close', () => { rooms.delete(roomId); statusLog(); }); } return room; } run(); diff --git a/src/.env.example b/src/.env.example index ec041c81..4b6cae7c 100644 --- a/src/.env.example +++ b/src/.env.example @@ -1,165 +1,166 @@ APP_NAME=Kolab APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://127.0.0.1:8000 #APP_PASSPHRASE= APP_PUBLIC_URL= APP_DOMAIN=kolabnow.com APP_WEBSITE_DOMAIN=kolabnow.com APP_THEME=default APP_TENANT_ID=5 APP_LOCALE=en APP_LOCALES= APP_WITH_ADMIN=1 APP_WITH_RESELLER=1 APP_WITH_SERVICES=1 ASSET_URL=http://127.0.0.1:8000 WEBMAIL_URL=/apps SUPPORT_URL=/support SUPPORT_EMAIL= LOG_CHANNEL=stack LOG_SLOW_REQUESTS=5 DB_CONNECTION=mysql DB_DATABASE=kolabdev DB_HOST=127.0.0.1 DB_PASSWORD=kolab DB_PORT=3306 DB_USERNAME=kolabdev BROADCAST_DRIVER=redis CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=file SESSION_LIFETIME=120 OPENEXCHANGERATES_API_KEY="from openexchangerates.org" MFA_DSN=mysql://roundcube:Welcome2KolabSystems@127.0.0.1/roundcube MFA_TOTP_DIGITS=6 MFA_TOTP_INTERVAL=30 MFA_TOTP_DIGEST=sha1 IMAP_URI=ssl://127.0.0.1:11993 IMAP_ADMIN_LOGIN=cyrus-admin IMAP_ADMIN_PASSWORD=Welcome2KolabSystems IMAP_VERIFY_HOST=false IMAP_VERIFY_PEER=false LDAP_BASE_DN="dc=mgmt,dc=com" LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com" LDAP_HOSTS=127.0.0.1 LDAP_PORT=389 LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com" LDAP_SERVICE_BIND_PW="Welcome2KolabSystems" LDAP_USE_SSL=false LDAP_USE_TLS=false # Administrative LDAP_ADMIN_BIND_DN="cn=Directory Manager" LDAP_ADMIN_BIND_PW="Welcome2KolabSystems" LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com" # Hosted (public registration) LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com" LDAP_HOSTED_BIND_PW="Welcome2KolabSystems" LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com" COTURN_REDIS_IP=127.0.0.1 COTURN_REDIS_PORT=6379 COTURN_REDIS_DATABASE=3 COTURN_REDIS_PASSWORD=0 COTURN_PUBLIC_IP=127.0.0.1 MEET_REDIS_IP=127.0.0.1 MEET_REDIS_PORT=6379 MEET_REDIS_DATABASE=3 MEET_REDIS_PASSWORD=0 MEET_PUBLIC_IP=127.0.0.1 +MEET_PUBLIC_DOMAIN=127.0.0.1:12443 MEET_TURN_SERVER='turn:127.0.0.1:3478?transport=tcp' # "CDR" events, see https://docs.openvidu.io/en/2.13.0/reference-docs/openvidu-server-cdr/ #OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged] #OPENVIDU_WEBHOOK_HEADERS=[\"Authorization:\ Basic\ SOMETHING\"] PGP_ENABLED= PGP_BINARY= PGP_AGENT= PGP_GPGCONF= PGP_LENGTH= REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 SWOOLE_HOT_RELOAD_ENABLE=true SWOOLE_HTTP_ACCESS_LOG=true SWOOLE_HTTP_HOST=127.0.0.1 SWOOLE_HTTP_PORT=8000 SWOOLE_HTTP_REACTOR_NUM=1 SWOOLE_HTTP_WEBSOCKET=true SWOOLE_HTTP_WORKER_NUM=1 SWOOLE_OB_OUTPUT=true PAYMENT_PROVIDER= MOLLIE_KEY= STRIPE_KEY= STRIPE_PUBLIC_KEY= STRIPE_WEBHOOK_SECRET= MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="Example.com" MAIL_REPLYTO_ADDRESS="replyto@example.com" MAIL_REPLYTO_NAME=null DNS_TTL=3600 DNS_SPF="v=spf1 mx -all" DNS_STATIC="%s. MX 10 ext-mx01.mykolab.com." DNS_COPY_FROM=null AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_ASSET_PATH='/' MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # Generate with ./artisan passport:client --password #PASSPORT_PROXY_OAUTH_CLIENT_ID= #PASSPORT_PROXY_OAUTH_CLIENT_SECRET= PASSPORT_PRIVATE_KEY= PASSPORT_PUBLIC_KEY= COMPANY_NAME= COMPANY_ADDRESS= COMPANY_DETAILS= COMPANY_EMAIL= COMPANY_LOGO= COMPANY_FOOTER= VAT_COUNTRIES=CH,LI VAT_RATE=7.7 KB_ACCOUNT_DELETE= KB_ACCOUNT_SUSPENDED= diff --git a/src/resources/js/meet/socket.js b/src/resources/js/meet/socket.js index bf21f7d2..22ffd167 100644 --- a/src/resources/js/meet/socket.js +++ b/src/resources/js/meet/socket.js @@ -1,109 +1,112 @@ 'use strict' import io from "socket.io-client" import Config from './config.js' function Socket(url, options) { let eventHandlers = {} - const socket = io(url, { transports: ["websocket"] }) + const socket = io(url, { + path: '/meetmedia/signaling/', + transports: ["websocket"] + }) socket.on("connect", () => { console.log("WebSocket connect: " + socket.id) }) socket.on("disconnect", reason => { console.log("WebSocket disconnect: " + reason) this.trigger('disconnect', reason) }) socket.on("reconnect_failed", () => { console.log("WebSocket re-connect failed") this.trigger('reconnectFailed') }) socket.on("reconnect", attempt => { console.log("WebSocket re-connect (" + attempt + ")") }) socket.on("request", async (request, cb) => { console.log("Recv: " + request.method, request.data) this.trigger('request', request, cb) }) socket.on("notification", async notification => { console.log("Recv: " + notification.method, notification.data) this.trigger('notification', notification) }) this.close = () => { socket.close() } this.getRtpCapabilities = async () => { return await this.sendRequest('getRouterRtpCapabilities') } /** * Register event handlers */ this.on = (eventName, callback) => { eventHandlers[eventName] = callback } /** * Execute an event handler */ this.trigger = (...args) => { const eventName = args.shift() if (eventName in eventHandlers) { eventHandlers[eventName].apply(null, args) } } this.sendRequest = (method, data) => { return new Promise((resolve, reject) => { console.log("Send: " + method, data) socket.emit( 'request', { method, data }, withTimeout((error, response) => { if (error) { reject(error) } else { resolve(response) } }) ) }) } const withTimeout = (callback) => { let called = false const timer = setTimeout( () => { if (called) return called = true callback(new Error('Request timed out')) }, Config.requestTimeout ) return (...args) => { if (called) return called = true clearTimeout(timer) callback(...args) } } } export { Socket }