let tls = require('tls'); var inject = require('reconnect-core'); var slice = [].slice; var MumbleClient = require('mumble-client'); var NodeCodecs = require('mumble-client-codecs-node'); var fs = require('fs'); var execSync = require('child_process').execSync; var currentPath = require('path').dirname(require.main.filename); const utils = require('./utils'); const OpusEncoder = require('node-opus').OpusEncoder; class Mumble { constructor(id, configs, connectedCallback) { console.log('------', configs) this.id = id; this.configs = configs; let mumble = configs.mumble; this._createCertificatesForUser(id, () => { this._makeMumbleConnection(id, mumble.port, mumble.host, id, this.configs.murmurPassword, connectedCallback); }) } // Create cerificates by running createCerts.sh script. _createCertificatesForUser(id, callback) { var code; var path = currentPath + '/certs/' + id + '-cert.pem'; if (!fs.existsSync(path)) { utils.writeLog(`Creating certificates for asset ${id}`) try { code = execSync(currentPath + '/createCerts.sh ' + id); } catch (e) { return callback(false); } return callback(true); } else { utils.writeLog(`Certificates for asset ${id} already created`); return callback(true); } } _makeMumbleConnection(id, port, host, username, password, connectedCallback) { // If ca does not exist create them. var rootPath = currentPath + '/certs/root-cert.pem'; if (!fs.existsSync(rootPath)) { utils.writeLog(`Creating certificates for Mumble`); try { execSync('./createCA.sh'); } catch(e) { utils.writeLog(`Could not crete Mumble certificates. Error:${e}`); utils.writeErrorLog(`ERROR_MUMBLE`); throw 'Could not crete CA certificates. ' + e; } } else { utils.writeLog(`Certificates for Mumble already created`); } var reconnect = inject(function(){ var args = slice.call(arguments); var cleartextStream = tls.connect.apply(tls, args); cleartextStream.on('secureConnect', function(){ // if(cleartextStream.authorized){ // cleartextStream.emit('connect'); // }else{ // cleartextStream.emit('error', cleartextStream.authorizationError); // } }); return cleartextStream; }); var secureContext = tls.createSecureContext({ ca: [ fs.readFileSync(currentPath + '/certs/root-cert.pem'), fs.readFileSync(currentPath + '/certs/ca1-cert.pem'), ], key: fs.readFileSync(currentPath + '/certs/' + this.id + '-key.pem'), cert: fs.readFileSync(currentPath + '/certs/' + this.id + '-cert.pem'), }); // Create connection socket with Murmur server. reconnect((socket) => { this.socket = socket; // Connect to mumble server. this.client = new MumbleClient({ username: '' + username, password, codecs: NodeCodecs }); this.client.connectDataStream(this.socket); this._prepareClient() }) .once('connect', function (con) { // con = underlying connection connectedCallback(); }) .on('connect', function (con) { // con = underlying connection }) .on('reconnect', function (n, delay) { // n = current number of reconnect // delay = delay used before reconnect console.log('------ Server reconnect...'); // utils.writeErrorLog(`ERROR_MUMBLE_RECONNECT`); // utils.exitWriteErrorLog(`ERROR_MUMBLE_RECONNECT`); }) .on('disconnect', function (err) { // err = possible error console.log('err', err) utils.writeErrorLog(`ERROR_MUMBLE_DISCONNECT`); }) .on('error', function (err) { // never forget // console.log(err) // connectedCallback(err); utils.writeLog('Mumble _makeMumbleConnection ERROR', err); utils.writeErrorLog(`ERROR_MUMBLE`); }) .connect({ port: port, host: host, rejectUnauthorized: false, secureContext: secureContext }); } _prepareClient() { // Register all users this.client.users.forEach(user => this._newUser(user)); // Register future users this.client.on('newUser', user => this._newUser(user)) this.client.on('newChannel', channel => this._newChannel(channel)) } _newUser(user) { // @TODO: Ugly hack for the voice to work with mp3. It is the _transform function from DecoderStream class (mumble-client-codecs-node project). user.on('voice', voice => { voice._transform = function (chunk, encoding, callback) { if (chunk.codec === 'Opus' && chunk.frame) { if (!this._opus) { this._opus = new OpusEncoder(48000, 1); } callback(null, { target: chunk.target, pcm: chunk.frame ? this._opus.decode(chunk.frame) : {buffer: Buffer.alloc(3840)}, numberOfChannels: 1, position: chunk.position }); } else { callback(); } } }); user.on('update', (actor, properties) => { }).on('remove', () => { }).on('voice', stream => { // Leave all commented and empty. We currently dont need to receive voice. // console.log(`User ${user.username} started takling`); // Leave it empty. Its used for 'end' event to be trigered. stream.on('data', (data) => { }); stream.on('end', () => { // console.log(`User ${user.username} stopped takling`) }); }); } _newChannel(channel) { } } module.exports = Mumble