diff --git a/app/gulpfile.js b/app/gulpfile.js
index 3eaa076..a214167 100644
--- a/app/gulpfile.js
+++ b/app/gulpfile.js
@@ -206,6 +206,7 @@ gulp.task('livebrowser', (done) =>
{
open : 'external',
host : config.domain,
+ port : 3000,
server :
{
baseDir : OUTPUT_DIR
@@ -226,6 +227,7 @@ gulp.task('browser', (done) =>
{
open : 'external',
host : config.domain,
+ port : 3000,
server :
{
baseDir : OUTPUT_DIR
diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js
index 464c2c2..a3ff903 100644
--- a/app/lib/RoomClient.js
+++ b/app/lib/RoomClient.js
@@ -20,9 +20,9 @@ const ROOM_OPTIONS =
const VIDEO_CONSTRAINS =
{
- qvga : { width: { ideal: 320 }, height: { ideal: 240 } },
- vga : { width: { ideal: 640 }, height: { ideal: 480 } },
- hd : { width: { ideal: 1280 }, height: { ideal: 720 } }
+ qvga : { width: { ideal: 320 }, height: { ideal: 240 }, aspectRatio: 1.334 },
+ vga : { width: { ideal: 640 }, height: { ideal: 480 }, aspectRatio: 1.334 },
+ hd : { width: { ideal: 800 }, height: { ideal: 600 }, aspectRatio: 1.334 }
};
export default class RoomClient
@@ -37,6 +37,9 @@ export default class RoomClient
const protooUrl = getProtooUrl(peerName, roomId);
const protooTransport = new protooClient.WebSocketTransport(protooUrl);
+ // window element to external login site
+ this._loginWindow;
+
// Closed flag.
this._closed = false;
@@ -60,6 +63,7 @@ export default class RoomClient
// mediasoup-client Room instance.
this._room = new mediasoupClient.Room(ROOM_OPTIONS);
+ this._room.roomId = roomId;
// Transport for sending.
this._sendTransport = null;
@@ -111,6 +115,18 @@ export default class RoomClient
this._dispatch(stateActions.setRoomState('closed'));
}
+ login()
+ {
+ const url = `/login?roomId=${this._room.roomId}&peerName=${this._peerName}`;
+
+ this._loginWindow = window.open(url, 'loginWindow');
+ }
+
+ closeLoginWindow()
+ {
+ this._loginWindow.close();
+ }
+
changeDisplayName(displayName)
{
logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
@@ -750,6 +766,35 @@ export default class RoomClient
break;
}
+
+ // This means: server wants to change MY displayName
+ case 'auth':
+ {
+ logger.debug('got auth event from server', request.data);
+ accept();
+
+ if (request.data.verified == true)
+ {
+ this.changeDisplayName(request.data.name);
+ this._dispatch(requestActions.notify(
+ {
+ text : `Authenticated successfully: ${request.data}`
+ }
+ ));
+ }
+ else
+ {
+ this._dispatch(requestActions.notify(
+ {
+ text : `Authentication failed: ${request.data}`
+ }
+ ));
+ }
+ this.closeLoginWindow();
+ break;
+
+ }
+
case 'raisehand-message':
{
accept();
diff --git a/app/lib/components/Peers.jsx b/app/lib/components/Peers.jsx
index fdd4a92..abb8d62 100644
--- a/app/lib/components/Peers.jsx
+++ b/app/lib/components/Peers.jsx
@@ -13,24 +13,23 @@ class Peers extends React.Component
{
super();
this.state = {
- ratio : 4 / 3
+ ratio : 1.334
};
-
}
updateDimensions()
{
const n = this.props.peers.length;
- if (n == 0)
+ if (n == 0)
{
return;
}
const width = this.refs.peers.clientWidth;
const height = this.refs.peers.clientHeight;
-
+
let x, y, space;
-
+
for (let rows = 1; rows < 100; rows = rows + 1)
{
x = width / Math.ceil(n / rows);
@@ -43,8 +42,8 @@ class Peers extends React.Component
}
space = height - (y * (rows));
if (space < y)
- {
- break;
+ {
+ break;
}
}
if (Math.ceil(this.props.peerWidth) !== Math.ceil(0.9 * x))
@@ -52,17 +51,17 @@ class Peers extends React.Component
this.props.onComponentResize(0.9 * x, 0.9 * y);
}
}
-
+
componentDidMount()
{
window.addEventListener('resize', this.updateDimensions.bind(this));
}
-
- componentWillUnmount()
+
+ componentWillUnmount()
{
window.removeEventListener('resize', this.updateDimensions.bind(this));
}
-
+
render()
{
const {
@@ -71,15 +70,15 @@ class Peers extends React.Component
peerWidth,
peerHeight
} = this.props;
-
- const style =
+
+ const style =
{
'width' : peerWidth,
'height' : peerHeight
};
this.updateDimensions();
-
+
return (
{
@@ -127,7 +126,7 @@ const mapStateToProps = (state) =>
// TODO: This is not OK since it's creating a new array every time, so triggering a
// component rendering.
const peersArray = Object.values(state.peers);
-
+
return {
peers : peersArray,
activeSpeakerName : state.room.activeSpeakerName,
diff --git a/app/stylus/components/PeerView.styl b/app/stylus/components/PeerView.styl
index 1230284..f5dbdc0 100644
--- a/app/stylus/components/PeerView.styl
+++ b/app/stylus/components/PeerView.styl
@@ -170,7 +170,7 @@
flex: 100 100 auto;
height: 100%;
width: 100%;
- object-fit: cover;
+ object-fit: contain;
user-select: none;
transition-property: opacity;
transition-duration: .15s;
diff --git a/server/config.example.js b/server/config.example.js
index 43695cb..8ad774b 100644
--- a/server/config.example.js
+++ b/server/config.example.js
@@ -1,5 +1,18 @@
module.exports =
{
+ // oAuth2 conf
+ oauth2 :
+ {
+ client_id : '',
+ client_secret : '',
+ providerID : '',
+ redirect_uri : 'https://mYDomainName:port/auth-callback',
+ authorization_endpoint : '',
+ userinfo_endpoint : '',
+ token_endpoint : '',
+ scopes : { request : [ 'openid', 'userid','profile'] },
+ response_type : 'code'
+ },
// Listening hostname for `gulp live|open`.
domain : 'localhost',
tls :
@@ -7,6 +20,8 @@ module.exports =
cert : `${__dirname}/certs/mediasoup-demo.localhost.cert.pem`,
key : `${__dirname}/certs/mediasoup-demo.localhost.key.pem`
},
+ // Listening port for https server.
+ listeningPort : 3443,
mediasoup :
{
// mediasoup Server settings.
diff --git a/server/http-helpers.js b/server/http-helpers.js
new file mode 100644
index 0000000..de66801
--- /dev/null
+++ b/server/http-helpers.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const headers = {
+ "access-control-allow-origin": "*",
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
+ "access-control-allow-headers": "content-type, accept",
+ "access-control-max-age": 10,
+ "Content-Type": "application/json"
+};
+
+exports.prepareResponse = function(req, cb) {
+ var data = "";
+ req.on('data', function(chunk) { data += chunk; });
+ req.on('end', function() { cb(data); });
+};
+
+exports.respond = function(res, data, status) {
+ status = status || 200;
+ res.writeHead(status, headers);
+ res.end(data);
+};
+
+exports.send404 = function(res) {
+ exports.respond(res, 'Not Found', 404);
+};
+
+exports.redirector = function(res, loc, status) {
+ status = status || 302;
+ res.writeHead(status, { Location: loc });
+ res.end();
+};
diff --git a/server/lib/Room.js b/server/lib/Room.js
index dbe067e..c6b5089 100644
--- a/server/lib/Room.js
+++ b/server/lib/Room.js
@@ -268,7 +268,6 @@ class Room extends EventEmitter
const { mediaPeer } = protooPeer.data;
mediaPeer.appData.raiseHand = request.data.raiseHandState;
-
// Spread to others via protoo.
this._protooRoom.spread(
'raisehand-message',
diff --git a/server/router.js b/server/router.js
new file mode 100644
index 0000000..97da297
--- /dev/null
+++ b/server/router.js
@@ -0,0 +1,194 @@
+'use strict';
+
+const EventEmitter = require( 'events' );
+const eventEmitter = new EventEmitter();
+const path = require('path');
+const url = require('url');
+const httpHelpers = require('./http-helpers');
+const fs = require('fs');
+const config = require('./config');
+const utils = require('./util');
+const querystring = require('querystring');
+const https = require('https')
+const Logger = require('./lib/Logger');
+
+const logger = new Logger();
+
+let authRequests = {}; // ongoing auth requests :
+/*
+{
+ state:
+ {
+ peerName:'peerName'
+ code:'oauth2 code',
+ roomId: 'romid',
+ }
+}
+*/
+const actions = {
+ 'GET': function(req, res) {
+ var parsedUrl = url.parse(req.url,true);
+ if ( parsedUrl.pathname === '/auth-callback' )
+ {
+ if ( typeof(authRequests[parsedUrl.query.state]) != 'undefined' )
+ {
+ console.log('got authorization code for access token: ',parsedUrl.query,authRequests[parsedUrl.query.state]);
+ const auth = "Basic " + new Buffer(config.oauth2.client_id + ":" + config.oauth2.client_secret).toString("base64");
+ const postUrl = url.parse(config.oauth2.token_endpoint);
+ let postData = querystring.stringify({
+ "grant_type":"authorization_code",
+ "code":parsedUrl.query.code,
+ "redirect_uri":config.oauth2.redirect_uri
+ });
+
+ let request = https.request( {
+ host : postUrl.hostname,
+ path : postUrl.pathname,
+ port : postUrl.port,
+ method : 'POST',
+ headers :
+ {
+ 'Content-Type' : 'application/x-www-form-urlencoded',
+ 'Authorization' : auth,
+ 'Content-Length': Buffer.byteLength(postData)
+ }
+ }, function(res)
+ {
+ res.setEncoding("utf8");
+ let body = "";
+ res.on("data", data => {
+ body += data;
+ });
+ res.on("end", () => {
+ if ( res.statusCode == 200 )
+ {
+ console.log('We\'ve got an access token!', body);
+ body = JSON.parse(body);
+ authRequests[parsedUrl.query.state].access_token =
+ body.access_token;
+ const auth = "Bearer " + body.access_token;
+ const getUrl = url.parse(config.oauth2.userinfo_endpoint);
+ let request = https.request( {
+ host : getUrl.hostname,
+ path : getUrl.pathname,
+ port : getUrl.port,
+ method : 'GET',
+ headers :
+ {
+ 'Authorization' : auth,
+ }
+ }, function(res)
+ {
+ res.setEncoding("utf8");
+ let body = '';
+ res.on("data", data => {
+ body += data;
+ });
+ res.on("end", () => {
+ // we don't need this any longer:
+ delete authRequests[parsedUrl.query.state].access_token;
+
+ body = JSON.parse(body);
+ console.log(body);
+ if ( res.statusCode == 200 )
+ {
+ authRequests[parsedUrl.query.state].verified = true;
+ if ( typeof(body.sub) != 'undefined')
+ {
+ authRequests[parsedUrl.query.state].sub = body.sub;
+ }
+ if ( typeof(body.name) != 'undefined')
+ {
+ authRequests[parsedUrl.query.state].name = body.name;
+ }
+ if ( typeof(body.picture) != 'undefined')
+ {
+ authRequests[parsedUrl.query.state].picture = body.picture;
+ }
+ } else {
+ {
+ authRequests[parsedUrl.query.state].verified = false;
+ }
+ }
+ eventEmitter.emit('auth',
+ authRequests[parsedUrl.query.state]);
+
+ delete authRequests[parsedUrl.query.state];
+ });
+ });
+ request.write(' ');
+ request.end;
+ }
+ else
+ {
+ console.log('access_token denied',body);
+ authRequests[parsedUrl.query.state].verified = false;
+ delete authRequests[parsedUrl.query.state].access_token;
+ eventEmitter.emit('auth',
+ authRequests[parsedUrl.query.state]);
+ }
+ });
+ });
+ request.write(postData);
+ request.end;
+ }
+ else
+ {
+ logger.warn('Got authorization_code for unseen state:', parsedUrl)
+ }
+ }
+ else if (parsedUrl.pathname === '/login') {
+ const state = utils.random(10);
+ httpHelpers.redirector(res, config.oauth2.authorization_endpoint
+ + '?client_id=' + config.oauth2.client_id
+ + '&redirect_uri=' + config.oauth2.redirect_uri
+ + '&state=' + state
+ + '&scopes=' + config.oauth2.scopes.request.join('+')
+ + '&response_type=' + config.oauth2.response_type);
+ authRequests[state] =
+ {
+ 'roomId' : parsedUrl.query.roomId,
+ 'peerName' : parsedUrl.query.peerName
+ };
+ console.log('Started authorization process: ', parsedUrl.query);
+ }
+ else
+ {
+ console.log('requested url:', parsedUrl.pathname);
+ var resolvedBase = path.resolve('./public');
+ var safeSuffix = path.normalize(req.url).replace(/^(\.\.[\/\\])+/, '');
+ var fileLoc = path.join(resolvedBase, safeSuffix);
+
+ var stream = fs.createReadStream(fileLoc);
+
+ // Handle non-existent file -> delivering index.html
+ stream.on('error', function(error) {
+ stream = fs.createReadStream(path.resolve('./public/index.html'));
+ res.statusCode = 200;
+ stream.pipe(res);
+ });
+
+ // File exists, stream it to user
+ res.statusCode = 200;
+ stream.pipe(res);
+ }
+ },
+
+ 'POST': function(req, res) {
+ httpHelpers.prepareResponse(req, function(data) {
+ // Do something with the data that was just collected by the helper
+ // e.g., validate and save to db
+ // either redirect or respond
+ // should be based on result of the operation performed in response to the POST request intent
+ // e.g., if user wants to save, and save fails, throw error
+ httpHelpers.redirector(res, /* redirect path , optional status code - defaults to 302 */);
+ });
+ }
+};
+
+module.exports = eventEmitter;
+
+module.exports.handleRequest = function(req, res) {
+ var action = actions[req.method];
+ action ? action(req, res) : httpHelpers.send404(res);
+};
diff --git a/server/server.js b/server/server.js
index 07a9664..514b089 100755
--- a/server/server.js
+++ b/server/server.js
@@ -14,7 +14,9 @@ console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
const fs = require('fs');
const https = require('https');
+const router = require('./router');
const url = require('url');
+const path = require('path');
const protooServer = require('protoo-server');
const mediasoup = require('mediasoup');
const readline = require('readline');
@@ -77,25 +79,34 @@ mediaServer.on('newroom', (room) =>
});
});
-// HTTPS server for the protoo WebSocket server.
+// HTTPS server
const tls =
{
cert : fs.readFileSync(config.tls.cert),
key : fs.readFileSync(config.tls.key)
};
-const httpsServer = https.createServer(tls, (req, res) =>
+const httpsServer = https.createServer(tls, router.handleRequest);
+httpsServer.listen(config.listeningPort, '0.0.0.0', () =>
{
- res.writeHead(404, 'Not Here');
- res.end();
+ logger.info('Server running, port: ',config.listeningPort);
});
-httpsServer.listen(3443, '0.0.0.0', () =>
-{
- logger.info('protoo WebSocket server running');
-});
+router.on('auth',function(event){
+ console.log('router: Got an event: ',event)
+ if ( rooms.has(event.roomId) )
+ {
+ const room = rooms.get(event.roomId)._protooRoom;
+ if ( room.hasPeer(event.peerName) )
+ {
+ const peer = room.getPeer(event.peerName);
+ peer.send('auth', event)
+ }
+ }
+})
-// Protoo WebSocket server.
+// Protoo WebSocket server listens to same webserver so everythink is available
+// via same port
const webSocketServer = new protooServer.WebSocketServer(httpsServer,
{
maxReceivedFrameSize : 960000, // 960 KBytes.
diff --git a/server/util.js b/server/util.js
new file mode 100644
index 0000000..cfa4548
--- /dev/null
+++ b/server/util.js
@@ -0,0 +1,18 @@
+'use strict';
+
+var crypto = require('crypto');
+
+exports.random = function (howMany, chars) {
+ chars = chars
+ || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
+ var rnd = crypto.randomBytes(howMany)
+ , value = new Array(howMany)
+ , len = len = Math.min(256, chars.length)
+ , d = 256 / len
+
+ for (var i = 0; i < howMany; i++) {
+ value[i] = chars[Math.floor(rnd[i] / d)]
+ };
+
+ return value.join('');
+}