From 9fe0cd64d0ef33dad286bcd8a5726b33bcebbd97 Mon Sep 17 00:00:00 2001 From: Moritz Langenstein <ml5717@ic.ac.uk> Date: Sat, 12 Oct 2019 13:46:41 +0100 Subject: [PATCH] (ml5717) Pre-built signalbuddy to dist/ --- .gitignore | 1 - dist/server.js | 125 +++++++++++++++++++++++++++++++ dist/sockets.js | 190 ++++++++++++++++++++++++++++++++++++++++++++++++ dist/util.js | 12 +++ 4 files changed, 327 insertions(+), 1 deletion(-) create mode 100755 dist/server.js create mode 100755 dist/sockets.js create mode 100644 dist/util.js diff --git a/.gitignore b/.gitignore index bf0ecd7..c388811 100755 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ node_modules ecosystem.config.js Dockerfile .drone.yml -dist .jshintignore .jshintrc yarn-error.log diff --git a/dist/server.js b/dist/server.js new file mode 100755 index 0000000..3800b3b --- /dev/null +++ b/dist/server.js @@ -0,0 +1,125 @@ +'use strict'; + +var _getconfig = require('getconfig'); + +var _getconfig2 = _interopRequireDefault(_getconfig); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +var _os = require('os'); + +var _os2 = _interopRequireDefault(_os); + +var _stickySession = require('sticky-session'); + +var _stickySession2 = _interopRequireDefault(_stickySession); + +var _farmhash = require('farmhash'); + +var _farmhash2 = _interopRequireDefault(_farmhash); + +var _net = require('net'); + +var _net2 = _interopRequireDefault(_net); + +var _cluster = require('cluster'); + +var _cluster2 = _interopRequireDefault(_cluster); + +var _http = require('http'); + +var _http2 = _interopRequireDefault(_http); + +var _https = require('https'); + +var _https2 = _interopRequireDefault(_https); + +var _sockets = require('./sockets'); + +var _sockets2 = _interopRequireDefault(_sockets); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var port = parseInt(process.env.PORT || _getconfig2.default.server.port, 10); +var redisEndpoint = process.env.REDIS_ENDPOINT || _getconfig2.default.redis.endpoint; +var redisPort = process.env.REDIS_PORT || _getconfig2.default.redis.port; +var numProcesses = _os2.default.cpus().length; + +if (_cluster2.default.isMaster) { + var workers = []; + var spawn = function spawn(i) { + workers[i] = _cluster2.default.fork(); + // Persistence + workers[i].on('exit', function (code, signal) { + console.log('Worker ' + i + ' exited with signal ' + signal); + console.log('Respawning worker', i); + spawn(i); + }); + }; + + for (var i = 0; i < numProcesses; i += 1) { + console.log('Starting worker ' + (i + 1)); + spawn(i); + } + + var workerIndex = function workerIndex(ip, len) { + return (// Farmhash is the fastest and works with IPv6, too + _farmhash2.default.fingerprint32(ip) % len + ); + }; + + // Create the outside facing server listening on our port. + var masterServer = _net2.default.createServer({ pauseOnConnect: true }, function (connection) { + // We received a connection and need to pass it to the appropriate + // worker. Get the worker for this connection's source IP and pass + // it the connection. + var worker = workers[workerIndex(connection.remoteAddress, numProcesses)]; + worker.send('sticky-session:connection', connection); + }).listen(port); + + console.log('Listening at ' + (_getconfig2.default.server.secure ? 'https' : 'http') + '://' + (process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost') + ':' + port + '/'); +} else { + var serverHandler = function serverHandler(req, res) { + if (req.url === '/healthcheck') { + console.log(Date.now(), 'healthcheck'); + res.writeHead(200); + res.end(); + return; + } + res.writeHead(404); + res.end('worker: ' + _cluster2.default.worker.id); + }; + + var server = null; + + // Create an http(s) server instance to that socket.io can listen to + if (_getconfig2.default.server.secure) { + server = _https2.default.Server({ + key: _fs2.default.readFileSync(process.env.PRIV_KEY || _getconfig2.default.server.key), + cert: _fs2.default.readFileSync(process.env.CERT || _getconfig2.default.server.cert), + passphrase: _getconfig2.default.server.password + }, serverHandler); + } else { + server = _http2.default.Server(serverHandler); + } + if (!_stickySession2.default.listen(server, port)) { + // Master + } else { + // Worker + } + server.listen(0); + (0, _sockets2.default)(server, Object.assign({ redisEndpoint: redisEndpoint, redisPort: redisPort }, _getconfig2.default)); + if (_getconfig2.default.uid) process.setuid(_getconfig2.default.uid); + process.on('message', function (message, connection) { + if (message !== 'sticky-session:connection') { + return; + } + // Emulate a connection event on the server by emitting the + // event with the connection the master sent us. + server.emit('connection', connection); + + connection.resume(); + }); +} \ No newline at end of file diff --git a/dist/sockets.js b/dist/sockets.js new file mode 100755 index 0000000..fdae315 --- /dev/null +++ b/dist/sockets.js @@ -0,0 +1,190 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _arguments = arguments; + +var _socket = require('socket.io'); + +var _socket2 = _interopRequireDefault(_socket); + +var _v = require('uuid/v4'); + +var _v2 = _interopRequireDefault(_v); + +var _crypto = require('crypto'); + +var _crypto2 = _interopRequireDefault(_crypto); + +var _socket3 = require('socket.io-redis'); + +var _socket4 = _interopRequireDefault(_socket3); + +var _util = require('./util'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (server, config) { + var io = _socket2.default.listen(server); + io.adapter((0, _socket4.default)({ host: config.redis.host, port: config.redis.port })); + io.on('connection', function (client) { + client.resources = { + screen: false, + video: true, + audio: false + }; + + // pass a message to another id + client.on('message', function (details) { + if (!details) return; + var otherClient = io.to(details.to); + if (!otherClient) return; + details.from = client.id; + otherClient.emit('message', details); + }); + + client.on('join', join); + client.on('getClients', getClients); + client.on('getClientCount', getClientCount); + client.on('getMyId', getClientId); + + function removeFeed(type) { + if (client.room) { + io.in(client.room).emit('remove', { + id: client.id, + type: type + }); + if (!type) { + client.leave(client.room); + client.room = undefined; + } + } + } + + function join(name, cb) { + // sanity check + if (typeof name !== 'string') return; + // check if maximum number of clients reached + if (config.rooms && config.rooms.maxClients > 0) { + getClientCount(name).then(function (count) { + if (count > config.rooms.maxClients) { + removeFeed(); + } + }); + } + // leave any existing rooms + removeFeed(); + getClients(name, function (err, clients) { + return (0, _util.safeCb)(cb)(err, Object.assign({}, { you: client.id }, clients)); + }); + client.join(name); + client.room = name; + } + + function getClients(roomName, callback) { + describeRoom(roomName).then(function (description) { + var obj = { clients: {} }; + description.forEach(function (k) { + obj.clients[k] = client.resources; + }); + (0, _util.safeCb)(callback)(null, obj); + }).catch(function (err) { + return (0, _util.safeCb)(callback)(err, null); + }); + } + + function getClientCount(roomName, callback) { + clientsInRoom(roomName).then(function (num) { + if (roomName) (0, _util.safeCb)(callback)(num); + }); + } + + function getClientId(callback) { + (0, _util.safeCb)(callback)(client.id); + } + + // we don't want to pass "leave" directly because the + // event type string of "socket end" gets passed too. + client.on('disconnect', function () { + removeFeed(); + }); + + client.on('leave', function () { + removeFeed(); + }); + + client.on('create', function (name, cb) { + if (_arguments.length === 2) { + cb = typeof cb === 'function' ? cb : function () {}; + name = name || (0, _v2.default)(); + } else { + cb = name; + name = (0, _v2.default)(); + } + // check if exists + io.in(name).clients(function (err, clients) { + if (clients && clients.length) { + (0, _util.safeCb)(cb)('taken'); + } else { + join(name); + (0, _util.safeCb)(cb)(null, name); + } + }); + }); + + /* + client.on('trace', (data) => { + // console.log('trace', JSON.stringify([data.type, data.session, data.prefix, data.peer, data.time, data.value])); + }); + */ + + // tell client about stun and turn servers and generate nonces + client.emit('stunservers', config.stunservers || []); + + // create shared secret nonces for TURN authentication + // the process is described in draft-uberti-behave-turn-rest + var credentials = []; + // allow selectively vending turn credentials based on origin. + var origin = client.handshake.headers.origin; + + if (!config.turnorigins || config.turnorigins.includes(origin)) { + config.turnservers.forEach(function (server) { + var hmac = _crypto2.default.createHmac('sha1', server.secret); + // default to 86400 seconds timeout unless specified + var username = '' + (Math.floor(new Date().getTime() / 1000) + parseInt(server.expiry || 86400, 10)); + hmac.update(username); + credentials.push({ + username: username, + credential: hmac.digest('base64'), + urls: server.urls || server.url + }); + }); + } + client.emit('turnservers', credentials); + }); + + function describeRoom(roomName) { + return new Promise(function (resolve, reject) { + io.in(roomName).clients(function (err, clients) { + if (err) { + reject(err); + return; + } + resolve(clients); + }); + }); + } + + function clientsInRoom(roomName) { + return new Promise(function (resolve, reject) { + io.in(roomName).clients(function (err, clients) { + if (err) { + reject(err); + return; + } + resolve(clients.length); + }); + }); + } +}; \ No newline at end of file diff --git a/dist/util.js b/dist/util.js new file mode 100644 index 0000000..56930e9 --- /dev/null +++ b/dist/util.js @@ -0,0 +1,12 @@ +'use strict'; + +function safeCb(cb) { + if (typeof cb === 'function') { + return cb; + } + return function () {}; +} + +module.exports = { + safeCb: safeCb +}; \ No newline at end of file -- GitLab