From 73bbc1db925057768ec9a13c95b2d75bfc8f5f55 Mon Sep 17 00:00:00 2001 From: Hiro Protagonist Date: Tue, 18 Apr 2017 17:23:59 +1200 Subject: [PATCH] should_use_redux! --- #main.js# | 494 ++++++++++++++++++++++++++++++++++++ .#main.js | 1 + .idea/codeStyleSettings.xml | 9 + .idea/doccam-pi.iml | 12 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 295 +++++++++++++++++++++ .tern-port | 1 + .tern-project | 7 + main.js | 89 +++---- main.js.orig | 488 +++++++++++++++++++++++++++++++++++ src/.tern-port | 1 + src/ffmpegCommand.js | 251 ++++++++++++++++++ src/ffmpegCommand.js~ | 246 ++++++++++++++++++ src/logger.js | 49 ++++ src/logger.js~ | 2 + src/status.js | 117 +++++++++ src/status.js~ | 19 ++ 18 files changed, 2040 insertions(+), 55 deletions(-) create mode 100755 #main.js# create mode 120000 .#main.js create mode 100644 .idea/codeStyleSettings.xml create mode 100644 .idea/doccam-pi.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 .tern-port create mode 100644 .tern-project create mode 100755 main.js.orig create mode 100644 src/.tern-port create mode 100644 src/ffmpegCommand.js create mode 100644 src/ffmpegCommand.js~ create mode 100644 src/logger.js create mode 100644 src/logger.js~ create mode 100644 src/status.js create mode 100644 src/status.js~ diff --git a/#main.js# b/#main.js# new file mode 100755 index 0000000..0fcf12f --- /dev/null +++ b/#main.js# @@ -0,0 +1,494 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Main Module - Communicates with the Server and Orchestrates the other Moules. // +/////////////////////////////////////////////////////////////////////////////////// + +const sock = require('socket.io-client'); +const ffmpeg = require('fluent-ffmpeg'); +const http = require('http'); +const path = require('path'); +const fs = require('fs'); +const WMStrm = require(__dirname + '/lib/memWrite.js'); +const exec = require('child_process').exec; +const execSync = require('child_process').execSync; +const spawnP = require('child_process').spawn; + +/** + * Custom Modules + * Yet to be initialized. + */ +const logger = require('src/logger.js'); + +/////////////////////////////////////////////////////////////////////////////// +// Declarations // +/////////////////////////////////////////////////////////////////////////////// + +// Force a Stop. +let mustBe = false; + +// Restart the stream after it stopped. +let restart = false; + +// Central State Variable +// NOTE: Could be done with redux! +let status = { + status: 0, + error: -1 +} + +// Minor declarations. +let config, source, snapSource, stopTimeout; + +let spawn = function() { + source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile; + ffmpeg.setFfmpegPath(config.ffmpegPath); + delete cmd; + cmd = ffmpeg({ + source: source, + stdoutLines: 20 + }); + if (config.customOutputOptions !== "") + cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + if (config.customAudioOptions !== "") + cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.AudioCodec('copy'); + if (config.customVideoOptions !== "") + cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.videoCodec('copy'); + + cmd.on('start', function(commandLine) { + status.running = 0; + logger.log(logger.importance[4], 'Spawned Ffmpeg with command: ' + commandLine); + }) + .on('end', function(o, e) { + imDead('Normal Stop.', e); + }) + .on('error', function(err, o, e) { + if (err.message.indexOf(source) > -1) + criticalProblem(0, e, handleDisc, config.camIP, config.camPort); + else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1) + criticalProblem(1, e, handleDisc, 'a.rtmp.youtube.com/live2/', 1935); + else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1) + criticalProblem(2, e, function() {}); + else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1) + imDead('Normal Stop.', e); + else + imDead(err.message, e); + }) + .outputFormat('flv') + .outputOptions(['-bufsize 50000k', '-tune film']) + .output('rtmp://a.rtmp.youtube.com/live2/' + config.key); + + status.error = -1; + socket.emit('change', { + type: 'startStop', + change: { + running: 0, + error: -1 + } + }); + cmd.run(); +}; + +let getSnap = function(cb) { + snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile; + let picBuff = new WMStrm(); + recCmd = ffmpeg(snapSource) + .on('start', function(commandLine) { + logger.log(logger.importance[4], 'Snapshot ' + commandLine); + }) + .on('error', function(err, o, e) {}) + .outputFormat('mjpeg') + .frames(1) + .stream(picBuff, { + end: true + }); + picBuff.on('finish', function() { + try { + cb(picBuff.memStore.toString('base64')); + delete pickBuff; + } catch (e) { + cb(false); + } + }); +} + +function imDead(why, e = '') { + if(stopTimeout) { + clearTimeout(stopTimeout); + stopTimeout = false; + } + + status.running = 1; + socket.emit('change', { + type: 'startStop', + change: { + running: 1 + } + }); + if (restart) { + spawn(); + restart = false; + } + if (!mustBe) { + logger.log(logger.importance[2], 'Crash! ' + e); + setTimeout(function() { + spawn(); + }, 1000); + } + mustBe = false; +} + +function criticalProblem(err, e, handler, ...args) { + if (!mustBe) { + setTimeout(function() { + status.running = 2; + status.error = err; + logger.log(logger.importance[3], 'Critical Problem: ' + errors[err] + '\n' + e); + socket.emit('change', { + type: 'error', + change: { + running: 2, + error: err + } + }); + handler(args) + }, 1000); + } + mustBe = false; +} + +function handleDisc(info) { + let [host, port] = info; + isReachable(host, port, is => { + if (is) { + spawn(); + } else { + setTimeout(function() { + handleDisc(info); + }, 10000); + } + }); +} + +function isReachable(host, port, callback) { + http.get({ + host: host.split('/')[0], + port: port + }, function(res) { + callback(true); + }).on("error", function(e) { + if (e.message == "socket hang up") { + setTimeout(function() { + callback(true); + }, 1000); + } else + callback(false); + }); +} + +function stopFFMPEG() { + cmd.kill('SIGINT'); + stopTimeout = setTimeout(() => { + logger.log(logger.importance[3], "Force Stop!"); + cmd.kill(); + }, 3000); +} + +var commandHandlers = function commandHandlers(command, cb) { + var handlers = { + startStop: function() { + if(restart) + return; + + if (status.running !== 2) + if (status.running === 0) { + logger.log(logger.importance[1], "Stop Command!"); + mustBe = true; + stopFFMPEG(); + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Stopped!' + } + }, command.sender); + } else { + logger.log(logger.importance[1], "Start Command!"); + spawn(); + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Started!' + } + }, command.sender); + } + }, + snap: function() { + getSnap(snap => { + socket.emit('data', { + type: 'snap', + data: snap, + }, command.sender); + }); + }, + config: function() { + socket.emit('data', { + type: 'config', + data: config, + }, command.sender); + }, + changeSettings: function() { + for (let set in command.data) { + if (typeof config[set] !== 'undefined') + config[set] = command.data[set]; + } + let oldConfigured; + if (config.configured === true) + oldConfigured = true; + config.configured = true; + fs.writeFile(__dirname + '/config.js', + JSON.stringify(config, + undefined, 2), + function(err) { + if (err) { + socket.emit('data', { + type: 'message', + data: { + title: 'Error', + type: 'error', + text: 'Can\'t save the Settings!\n' + err.message + } + }, command.sender); + } else { + restartSSH(() => { + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Settings Saved!' + } + }, command.sender); + if (oldConfigured) { + socket.emit('change', { + type: 'settings', + change: { + config: command.data + } + }); + stopFFMPEG(); + spawn(); + } else { + socket.disconnect(); + init(); + } + }); + } + }); + }, + restart: function() { + if (status.running === 0) { + logger.log(logger.importance[1], "Restart Command!"); + mustBe = true; + restart = true; + stopFFMPEG(); + } else { + logger.log(logger.importance[1], "Start Command!"); + spawn(); + } + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Restarted!' + } + }, command.sender); + }, + restartSSH: function() { + restartSSH(() => { + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Restarted SSH Tunnels!' + } + }, command.sender); + }); + }, + getLogs: function() { + logger.query({limit: 100}, function (err, data) { + data = data.file; + if (err) { + data = []; + } else + if (data.length === 0) + data = []; + socket.emit('data', { + type: 'logs', + data: data + }, command.sender); + }); + } + }; + + //call the handler + var call = handlers[command.command]; + if (call) + call(); +} + +function restartSSH(cb) { + exec('forever stop SSH-Serv', () => { + connectSSH(() => { + socket.emit('change', { + change: { + ssh: { + port: status.ssh.port, + camForwardPort: status.ssh.camForwardPort + } + } + }); + cb(); + }); + }); +} + +function handleKill() { + process.stdin.resume(); + logger.log(logger.importance[0], "Received Shutdown Command"); + mustBe = true; + cmd.kill(); + process.exit(0); +} + +process.on('SIGTERM', function() { + handleKill(); +}); + +//let's go +function init() { + config = readConfig(); + + if (config.configured) { + socket = sock(config.master + '/pi'); + initSocket(); + status.name = config.name; + if (!cmd) + spawn(); + } else { + socket = sock(config.master + '/pi', { + query: "unconfigured=true" + }); + status.running = 2; + initSocket(); + } +} + + +function initSSH(cb) { + status.ssh = { + // that Could come from the Master server! + user: config['ssh-user'], + localUser: config['ssh-local-user'], + masterPort: config['ssh-port'] + }; + + checkSSH((alive) => { + if (alive) + cb(); + else + connectSSH(cb); + }); +} + +function connectSSH(cb = function() { + socket.emit('change', { + change: { + ssh: { + port: status.ssh.port, + camForwardPort: status.ssh.camForwardPort + } + } + }); +}) { + socket.emit('getSSHPort', ports => { + [status.ssh.port, status.ssh.camForwardPort] = ports; + let ssh = exec(`forever start -a --killSignal=SIGINT --uid SSH-Serv sshManager.js ${status.ssh.port} ${status.ssh.camForwardPort} ${config.camPanelPort}`, { + detached: true, + shell: true, + cwd: __dirname + }); + ssh.on('error', (err) => { + socket.emit('data', { + type: 'message', + data: { + title: 'Error', + type: 'error', + text: 'Could not start SSH tunnels!' + } + }, command.sender); + }); + cb(); + }); +} + +function checkSSH(cb) { + let m, pid, alive; + let re = /SSH-Serv.*?sshManager\.js\s([0-9]+)\s([0-9]+).*log.*[^STOPPED]+/g; + exec('forever list', (error, stdout, stderr) => { + if (error) + throw error; + let alive = false; + while ((m = re.exec(stdout)) !== null) { + if (m.index === re.lastIndex) { + re.lastIndex++; + } + if (alive) { + exec('forever stop SSH-Serv'); + cb(false) + return; + } else { + [, status.ssh.port, status.ssh.camForwardPort] = m; + alive = true; + } + } + cb(alive); + }); +} + +function initSocket() { + socket.on('connect', function() { + logger.log(logger.importance[0], 'Connected to Master: ' + config.master + '.'); + if (config['ssh-user']) + initSSH(err => { + if (err) + throw err; + socket.emit('meta', status); + }); + else { + socket.emit('meta', status); + } + }); + + socket.on('disconnect', function() { + socket.disconnect(); + init(); + }); + + socket.on('command', (command, cb) => { + commandHandlers(command, cb); + }); +} + +function readConfig() { + return JSON.parse(fs.readFileSync(__dirname + '/config.js')); +} + +init(); diff --git a/.#main.js b/.#main.js new file mode 120000 index 0000000..9ae5c4c --- /dev/null +++ b/.#main.js @@ -0,0 +1 @@ +hiro@ArLeenUX.4902:1492242088 \ No newline at end of file diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..5555dd2 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/.idea/doccam-pi.iml b/.idea/doccam-pi.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/doccam-pi.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0a46902 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..ca79842 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + project + + + true + + + + DIRECTORY + + false + + + + + + + + + + + + + + + + + + + + + + 1492326299503 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.tern-port b/.tern-port new file mode 100644 index 0000000..e4d1f11 --- /dev/null +++ b/.tern-port @@ -0,0 +1 @@ +36411 \ No newline at end of file diff --git a/.tern-project b/.tern-project new file mode 100644 index 0000000..9d36338 --- /dev/null +++ b/.tern-project @@ -0,0 +1,7 @@ +{ + "plugins": { + "node": {}, + "lint": {}, + "node-extension": {} + } +} \ No newline at end of file diff --git a/main.js b/main.js index 4e84e3d..0fcf12f 100755 --- a/main.js +++ b/main.js @@ -1,3 +1,7 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Main Module - Communicates with the Server and Orchestrates the other Moules. // +/////////////////////////////////////////////////////////////////////////////////// + const sock = require('socket.io-client'); const ffmpeg = require('fluent-ffmpeg'); const http = require('http'); @@ -8,56 +12,31 @@ const exec = require('child_process').exec; const execSync = require('child_process').execSync; const spawnP = require('child_process').spawn; +/** + * Custom Modules + * Yet to be initialized. + */ +const logger = require('src/logger.js'); + +/////////////////////////////////////////////////////////////////////////////// +// Declarations // +/////////////////////////////////////////////////////////////////////////////// + +// Force a Stop. let mustBe = false; + +// Restart the stream after it stopped. let restart = false; -let config, source, snapSource, stopTimeout; -const importance = ['normal', 'info', 'warning', 'danger', 'success']; -var customLevels = { - levels: { - normal: 0, - info: 1, - warning: 2, - danger: 3, - success: 4 - }, - colors: { - normal: 'white', - info: 'blue', - warning: 'orange', - danger: 'red', - success: 'green' - } -}; - -let winston = require('winston'); -let logger = new(winston.Logger)({ - levels: customLevels.levels, - transports: [ - new(winston.transports.Console)({ - level: 'success' - }), - new(winston.transports.File)({ - filename: __dirname + '/process.log', - colorize: true, - timestamp: true, - level: 'success', - json: true, - maxsize: 500000, - maxFiles: 10 - }) - ] -}); -winston.addColors(customLevels.colors); - -let dir = '/home'; +// Central State Variable +// NOTE: Could be done with redux! let status = { status: 0, error: -1 } -let errors = ['Camera Disconnected', 'YoutTube Disconnected', 'Wrong ffmpeg executable.']; -let cmd; +// Minor declarations. +let config, source, snapSource, stopTimeout; let spawn = function() { source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile; @@ -80,7 +59,7 @@ let spawn = function() { cmd.on('start', function(commandLine) { status.running = 0; - logger.log(importance[4], 'Spawned Ffmpeg with command: ' + commandLine); + logger.log(logger.importance[4], 'Spawned Ffmpeg with command: ' + commandLine); }) .on('end', function(o, e) { imDead('Normal Stop.', e); @@ -110,14 +89,14 @@ let spawn = function() { } }); cmd.run(); -} +}; let getSnap = function(cb) { snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile; let picBuff = new WMStrm(); recCmd = ffmpeg(snapSource) .on('start', function(commandLine) { - logger.log(importance[4], 'Snapshot ' + commandLine); + logger.log(logger.importance[4], 'Snapshot ' + commandLine); }) .on('error', function(err, o, e) {}) .outputFormat('mjpeg') @@ -153,7 +132,7 @@ function imDead(why, e = '') { restart = false; } if (!mustBe) { - logger.log(importance[2], 'Crash! ' + e); + logger.log(logger.importance[2], 'Crash! ' + e); setTimeout(function() { spawn(); }, 1000); @@ -166,7 +145,7 @@ function criticalProblem(err, e, handler, ...args) { setTimeout(function() { status.running = 2; status.error = err; - logger.log(importance[3], 'Critical Problem: ' + errors[err] + '\n' + e); + logger.log(logger.importance[3], 'Critical Problem: ' + errors[err] + '\n' + e); socket.emit('change', { type: 'error', change: { @@ -187,7 +166,7 @@ function handleDisc(info) { spawn(); } else { setTimeout(function() { - handleDisc(info) + handleDisc(info); }, 10000); } }); @@ -212,7 +191,7 @@ function isReachable(host, port, callback) { function stopFFMPEG() { cmd.kill('SIGINT'); stopTimeout = setTimeout(() => { - logger.log(importance[3], "Force Stop!"); + logger.log(logger.importance[3], "Force Stop!"); cmd.kill(); }, 3000); } @@ -225,7 +204,7 @@ var commandHandlers = function commandHandlers(command, cb) { if (status.running !== 2) if (status.running === 0) { - logger.log(importance[1], "Stop Command!"); + logger.log(logger.importance[1], "Stop Command!"); mustBe = true; stopFFMPEG(); socket.emit('data', { @@ -237,7 +216,7 @@ var commandHandlers = function commandHandlers(command, cb) { } }, command.sender); } else { - logger.log(importance[1], "Start Command!"); + logger.log(logger.importance[1], "Start Command!"); spawn(); socket.emit('data', { type: 'message', @@ -314,12 +293,12 @@ var commandHandlers = function commandHandlers(command, cb) { }, restart: function() { if (status.running === 0) { - logger.log(importance[1], "Restart Command!"); + logger.log(logger.importance[1], "Restart Command!"); mustBe = true; restart = true; stopFFMPEG(); } else { - logger.log(importance[1], "Start Command!"); + logger.log(logger.importance[1], "Start Command!"); spawn(); } socket.emit('data', { @@ -383,7 +362,7 @@ function restartSSH(cb) { function handleKill() { process.stdin.resume(); - logger.log(importance[0], "Received Shutdown Command"); + logger.log(logger.importance[0], "Received Shutdown Command"); mustBe = true; cmd.kill(); process.exit(0); @@ -486,7 +465,7 @@ function checkSSH(cb) { function initSocket() { socket.on('connect', function() { - logger.log(importance[0], 'Connected to Master: ' + config.master + '.'); + logger.log(logger.importance[0], 'Connected to Master: ' + config.master + '.'); if (config['ssh-user']) initSSH(err => { if (err) diff --git a/main.js.orig b/main.js.orig new file mode 100755 index 0000000..66cd10c --- /dev/null +++ b/main.js.orig @@ -0,0 +1,488 @@ +const sock = require('socket.io-client'); +const ffmpeg = require('fluent-ffmpeg'); +const http = require('http'); +const path = require('path'); +const fs = require('fs'); +const WMStrm = require(__dirname + '/lib/memWrite.js'); +const exec = require('child_process').exec; +const execSync = require('child_process').execSync; +const spawnP = require('child_process').spawn; + + +const logger = require('src/logger.js'); + +/** + * Global Variables + */ + +// Force a Stop. +let mustBe = false; + +// Restart the stream after it stopped. +let restart = false; + +// Central State Variable +// NOTE: Could be done with redux! +let status = { + status: 0, + error: -1 +} + +// Minor declarations. +let config, source, snapSource, stopTimeout; + + +let spawn = function() { + source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile; + ffmpeg.setFfmpegPath(config.ffmpegPath); + delete cmd; + cmd = ffmpeg({ + source: source, + stdoutLines: 20 + }); + if (config.customOutputOptions !== "") + cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + if (config.customAudioOptions !== "") + cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.AudioCodec('copy'); + if (config.customVideoOptions !== "") + cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.videoCodec('copy'); + + cmd.on('start', function(commandLine) { + status.running = 0; + logger.log(logger.importance[4], 'Spawned Ffmpeg with command: ' + commandLine); + }) + .on('end', function(o, e) { + imDead('Normal Stop.', e); + }) + .on('error', function(err, o, e) { + if (err.message.indexOf(source) > -1) + criticalProblem(0, e, handleDisc, config.camIP, config.camPort); + else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1) + criticalProblem(1, e, handleDisc, 'a.rtmp.youtube.com/live2/', 1935); + else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1) + criticalProblem(2, e, function() {}); + else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1) + imDead('Normal Stop.', e); + else + imDead(err.message, e); + }) + .outputFormat('flv') + .outputOptions(['-bufsize 50000k', '-tune film']) + .output('rtmp://a.rtmp.youtube.com/live2/' + config.key); + + status.error = -1; + socket.emit('change', { + type: 'startStop', + change: { + running: 0, + error: -1 + } + }); + cmd.run(); +}; + +let getSnap = function(cb) { + snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile; + let picBuff = new WMStrm(); + recCmd = ffmpeg(snapSource) + .on('start', function(commandLine) { + logger.log(logger.importance[4], 'Snapshot ' + commandLine); + }) + .on('error', function(err, o, e) {}) + .outputFormat('mjpeg') + .frames(1) + .stream(picBuff, { + end: true + }); + picBuff.on('finish', function() { + try { + cb(picBuff.memStore.toString('base64')); + delete pickBuff; + } catch (e) { + cb(false); + } + }); +} + +function imDead(why, e = '') { + if(stopTimeout) { + clearTimeout(stopTimeout); + stopTimeout = false; + } + + status.running = 1; + socket.emit('change', { + type: 'startStop', + change: { + running: 1 + } + }); + if (restart) { + spawn(); + restart = false; + } + if (!mustBe) { + logger.log(logger.importance[2], 'Crash! ' + e); + setTimeout(function() { + spawn(); + }, 1000); + } + mustBe = false; +} + +function criticalProblem(err, e, handler, ...args) { + if (!mustBe) { + setTimeout(function() { + status.running = 2; + status.error = err; + logger.log(logger.importance[3], 'Critical Problem: ' + errors[err] + '\n' + e); + socket.emit('change', { + type: 'error', + change: { + running: 2, + error: err + } + }); + handler(args) + }, 1000); + } + mustBe = false; +} + +function handleDisc(info) { + let [host, port] = info; + isReachable(host, port, is => { + if (is) { + spawn(); + } else { + setTimeout(function() { + handleDisc(info); + }, 10000); + } + }); +} + +function isReachable(host, port, callback) { + http.get({ + host: host.split('/')[0], + port: port + }, function(res) { + callback(true); + }).on("error", function(e) { + if (e.message == "socket hang up") { + setTimeout(function() { + callback(true); + }, 1000); + } else + callback(false); + }); +} + +function stopFFMPEG() { + cmd.kill('SIGINT'); + stopTimeout = setTimeout(() => { + logger.log(logger.importance[3], "Force Stop!"); + cmd.kill(); + }, 3000); +} + +var commandHandlers = function commandHandlers(command, cb) { + var handlers = { + startStop: function() { + if(restart) + return; + + if (status.running !== 2) + if (status.running === 0) { + logger.log(logger.importance[1], "Stop Command!"); + mustBe = true; + stopFFMPEG(); + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Stopped!' + } + }, command.sender); + } else { + logger.log(logger.importance[1], "Start Command!"); + spawn(); + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Started!' + } + }, command.sender); + } + }, + snap: function() { + getSnap(snap => { + socket.emit('data', { + type: 'snap', + data: snap, + }, command.sender); + }); + }, + config: function() { + socket.emit('data', { + type: 'config', + data: config, + }, command.sender); + }, + changeSettings: function() { + for (let set in command.data) { + if (typeof config[set] !== 'undefined') + config[set] = command.data[set]; + } + let oldConfigured; + if (config.configured === true) + oldConfigured = true; + config.configured = true; + fs.writeFile(__dirname + '/config.js', + JSON.stringify(config, + undefined, 2), + function(err) { + if (err) { + socket.emit('data', { + type: 'message', + data: { + title: 'Error', + type: 'error', + text: 'Can\'t save the Settings!\n' + err.message + } + }, command.sender); + } else { + restartSSH(() => { + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Settings Saved!' + } + }, command.sender); + if (oldConfigured) { + socket.emit('change', { + type: 'settings', + change: { + config: command.data + } + }); + stopFFMPEG(); + spawn(); + } else { + socket.disconnect(); + init(); + } + }); + } + }); + }, + restart: function() { + if (status.running === 0) { + logger.log(logger.importance[1], "Restart Command!"); + mustBe = true; + restart = true; + stopFFMPEG(); + } else { + logger.log(logger.importance[1], "Start Command!"); + spawn(); + } + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Restarted!' + } + }, command.sender); + }, + restartSSH: function() { + restartSSH(() => { + socket.emit('data', { + type: 'message', + data: { + title: 'Success', + type: 'success', + text: 'Restarted SSH Tunnels!' + } + }, command.sender); + }); + }, + getLogs: function() { + logger.query({limit: 100}, function (err, data) { + data = data.file; + if (err) { + data = []; + } else + if (data.length === 0) + data = []; + socket.emit('data', { + type: 'logs', + data: data + }, command.sender); + }); + } + }; + + //call the handler + var call = handlers[command.command]; + if (call) + call(); +} + +function restartSSH(cb) { + exec('forever stop SSH-Serv', () => { + connectSSH(() => { + socket.emit('change', { + change: { + ssh: { + port: status.ssh.port, + camForwardPort: status.ssh.camForwardPort + } + } + }); + cb(); + }); + }); +} + +function handleKill() { + process.stdin.resume(); + logger.log(logger.importance[0], "Received Shutdown Command"); + mustBe = true; + cmd.kill(); + process.exit(0); +} + +process.on('SIGTERM', function() { + handleKill(); +}); + +//let's go +function init() { + config = readConfig(); + + if (config.configured) { + socket = sock(config.master + '/pi'); + initSocket(); + status.name = config.name; + if (!cmd) + spawn(); + } else { + socket = sock(config.master + '/pi', { + query: "unconfigured=true" + }); + status.running = 2; + initSocket(); + } +} + + +function initSSH(cb) { + status.ssh = { + // that Could come from the Master server! + user: config['ssh-user'], + localUser: config['ssh-local-user'], + masterPort: config['ssh-port'] + }; + + checkSSH((alive) => { + if (alive) + cb(); + else + connectSSH(cb); + }); +} + +function connectSSH(cb = function() { + socket.emit('change', { + change: { + ssh: { + port: status.ssh.port, + camForwardPort: status.ssh.camForwardPort + } + } + }); +}) { + socket.emit('getSSHPort', ports => { + [status.ssh.port, status.ssh.camForwardPort] = ports; + let ssh = exec(`forever start -a --killSignal=SIGINT --uid SSH-Serv sshManager.js ${status.ssh.port} ${status.ssh.camForwardPort} ${config.camPanelPort}`, { + detached: true, + shell: true, + cwd: __dirname + }); + ssh.on('error', (err) => { + socket.emit('data', { + type: 'message', + data: { + title: 'Error', + type: 'error', + text: 'Could not start SSH tunnels!' + } + }, command.sender); + }); + cb(); + }); +} + +function checkSSH(cb) { + let m, pid, alive; + let re = /SSH-Serv.*?sshManager\.js\s([0-9]+)\s([0-9]+).*log.*[^STOPPED]+/g; + exec('forever list', (error, stdout, stderr) => { + if (error) + throw error; + let alive = false; + while ((m = re.exec(stdout)) !== null) { + if (m.index === re.lastIndex) { + re.lastIndex++; + } + if (alive) { + exec('forever stop SSH-Serv'); + cb(false) + return; + } else { + [, status.ssh.port, status.ssh.camForwardPort] = m; + alive = true; + } + } + cb(alive); + }); +} + +function initSocket() { + socket.on('connect', function() { + logger.log(logger.importance[0], 'Connected to Master: ' + config.master + '.'); + if (config['ssh-user']) + initSSH(err => { + if (err) + throw err; + socket.emit('meta', status); + }); + else { + socket.emit('meta', status); + } + }); + + socket.on('disconnect', function() { + socket.disconnect(); + init(); + }); + + socket.on('command', (command, cb) => { + commandHandlers(command, cb); + }); +} + +function readConfig() { + return JSON.parse(fs.readFileSync(__dirname + '/config.js')); +} + +init(); diff --git a/src/.tern-port b/src/.tern-port new file mode 100644 index 0000000..f2eac45 --- /dev/null +++ b/src/.tern-port @@ -0,0 +1 @@ +44205 \ No newline at end of file diff --git a/src/ffmpegCommand.js b/src/ffmpegCommand.js new file mode 100644 index 0000000..e7c7fcb --- /dev/null +++ b/src/ffmpegCommand.js @@ -0,0 +1,251 @@ +/////////////////////////////////////////////////////////////////////////////// +// A Wrapper for Fluent-FFMPEG with a custom command. // +/////////////////////////////////////////////////////////////////////////////// + +const ffmpeg = require('fluent-ffmpeg'); +const http = require('http'); + +/////////////////////////////////////////////////////////////////////////////// +// Declarations // +/////////////////////////////////////////////////////////////////////////////// + +// Reference to itself. (Object oriented this. Only used to call public methods.) +let self = false; + +// The Variable for the FFMpeg command. +let cmd = ffmpeg({ + stdoutLines: 20 +}); + +// The Config, Logger and a handle for the kill timeout. +let config, logger, stopHandle = false, connectHandle = false; + +// True if stream should be restarted. +let restart = false; + +// Error Texts +let errorDescriptions = ['Camera Disconnected', + 'YoutTube Disconnected', + 'Wrong ffmpeg executable.', + 'Unknown Error - Restarting']; + +// Internal Status +let status = { + streaming: false, + error: false +}; + +// The stream source url. Yet to be set. +let source = ""; + +/////////////////////////////////////////////////////////////////////////////// +// Code // +/////////////////////////////////////////////////////////////////////////////// + +/** + * Interface to the ffmpeg process. Uses fluent-ffmpeg. + * @param {Object} _config Configuration for the stream. @see config.js.example + * @param {Object} _logger Logger Instance from main module. + */ +let command = function(_config, _logger){ + // singleton + if(self) + return self; + + + // TODO: Better Error Checking + if(!_config) + throw new Error("Invalid Config"); + + if(!_logger) + throw new Error("Invalid Logger"); + + config = _config; + + self = this; + + // (Re)Create the ffmpeg command and configure it. + let createCommand = function() { + // Clear inputs + cmd._inputs = []; + + // TODO: Multi Protocol + source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile; + cmd.input(source); + + // Custom config if any. + if (config.customOutputOptions !== "") + cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + + if (config.customAudioOptions !== "") + cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.AudioCodec('copy'); + + if (config.customVideoOptions !== "") + cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.videoCodec('copy'); + + // Output Options. + cmd.outputFormat('flv') + .outputOptions(['-bufsize 50000k', '-tune film']) + .output('rtmp://a.rtmp.youtube.com/live2/' + config.key); + + // Register events. + cmd.on('start', started); + + cmd.on('end', stopped); + + cmd.on('error', crashed); + }; + + let ffmpegCommand = function() { + return cmd; + }; + + // NOTE: Maybe better error resolving strategy. + // Start streaming. + let start = function() { + // Ignore if we try to reconnect. + if(connectHandle) + return; + + cmd.run(); + }; + + // Restart the stream; + let restart = function() { + if(status.streaming) { + restart = true; // NOTE: not very nice + this.stop(); + } else + this.start(); + }; + + // Stop streaming. + let stop = function() { + cmd.kill('SIGINT'); + + stopHandle = setTimeout(() => { + logger.log(logger.importance[3], "Force Stop!"); + cmd.kill(); + }, 3000); + }; + + let setConfig = function(_conf) { + config = _conf; + }; +}; + +/** + * Utilities + */ + +// Handle Stop and Restart +function stopped() { + status.streaming = false; + + // Clear force kill Timeout + if(stopTimeout) { + clearTimeout(stopTimeout); + stopTimeout = false; + } + + // Restart the stream; + if (restart) { + self.start(); + } + + cmd.emit('stopped'); +} + +// TODO: Restart = false in stopped? +// Hande Stat +function started() { + cmd.emit(restart ? 'restarted' : 'started'); + restart = false; + status.error = false; + status.streaming = true; +} + +/** + * Error Handling + */ + +/** + * Log and handle crashes. Notify the main module. + * @param { Object } error - Error object from the crash. + * @param { String } stdout + * @param { String } stderr + */ +function crashed(error, stdout, stderr){ + // Can't connect to the + if (err.message.indexOf(source) > -1) + status.error = 0; + + // Can't connect to the Internet / YouTube + else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1) + status.error = 1; + + // Wrong FFMPEG Executable + else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1) + status.error = 2; + + // Stopped by us - SIGINT Shouldn't lead to a crash. + else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1){ + stopped(); + return; + } + + // Some unknown Problem, just try to restart. + else { + status.error = 3; + + // Just restart in a Second + setTimeout(function(){ + self.start(); + }, 1000); + } + + logger.log(logger.importance[2], `Crashed: ${erro}\nSTDERR: ${stderr}`); +} + +/** + * Probe the connection to the host on the port and restart the stream afterwards. + * @param { string } host + * @param { number } port + */ +function tryReconnect( host, port ){ + if (!host || !port) + return; + + http.get({ + host: host.split('/')[0], + port: port + }, function(res) { + // We have a response! Connection works. + setTimeout(function() { + // NOTE: Ugly! + + // We have a response! Connection works. + connectHandle = false; + self.start(); + }, 1000); + }).on("error", function(e) { + if (e.message == "socket hang up") { + setTimeout(function() { + // NOTE: Ugly! + + // We have a response! Connection works. + connectHandle = false; + self.start(); + }, 1000); + } else { + // try again + connectHandle = setTimeout(function(){ + tryReconnect(host, port); + }, 1000); + } + }); +} diff --git a/src/ffmpegCommand.js~ b/src/ffmpegCommand.js~ new file mode 100644 index 0000000..4b3f4f5 --- /dev/null +++ b/src/ffmpegCommand.js~ @@ -0,0 +1,246 @@ +/////////////////////////////////////////////////////////////////////////////// +// A Wrapper for Fluent-FFMPEG with a custom command. // +/////////////////////////////////////////////////////////////////////////////// + +const ffmpeg = require('fluent-ffmpeg'); +const http = require('http'); + +/////////////////////////////////////////////////////////////////////////////// +// Declarations // +/////////////////////////////////////////////////////////////////////////////// + +// This +let self; + +// The Variable for the FFMpeg command. +let cmd = ffmpeg({ + stdoutLines: 20 +}); + +// The Config, Logger and a handle for the kill timeout. +let config, logger, stopHandle = false, connectHandle = false; + +// True if stream should be restarted. +let restart = false; + +// Error Texts +let errorDescriptions = ['Camera Disconnected', + 'YoutTube Disconnected', + 'Wrong ffmpeg executable.', + 'Unknown Error - Restarting']; + +// Internal Status +let status = { + streaming: false, + error: false +}; + +// The stream source url. Yet to be set. +let source = ""; + +/////////////////////////////////////////////////////////////////////////////// +// Code // +/////////////////////////////////////////////////////////////////////////////// + +/** + * Interface to the ffmpeg process. Uses fluent-ffmpeg. + * @param {Object} _config Configuration for the stream. @see config.js.example + * @param {Object} _logger Logger Instance from main module. + */ +let command = function(_config, _logger){ + // TODO: Better Error Checking + if(!_config) + throw new Error("Invalid Config"); + + if(!_logger) + throw new Error("Invalid Logger"); + + config = _config; + + self = this; + + // (Re)Create the ffmpeg command and configure it. + let createCommand = function() { + // Clear inputs + cmd._inputs = []; + + // TODO: Multi Protocol + source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile; + cmd.input(source); + + // Custom config if any. + if (config.customOutputOptions !== "") + cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + + if (config.customAudioOptions !== "") + cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.AudioCodec('copy'); + + if (config.customVideoOptions !== "") + cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(',')); + else + cmd.videoCodec('copy'); + + // Output Options. + cmd.outputFormat('flv') + .outputOptions(['-bufsize 50000k', '-tune film']) + .output('rtmp://a.rtmp.youtube.com/live2/' + config.key); + + // Register events. + cmd.on('start', started); + + cmd.on('end', stopped); + + cmd.on('error', crashed); + }; + + let ffmpegCommand = function() { + return cmd; + }; + + // NOTE: Maybe better error resolving strategy. + // Start streaming. + let start = function() { + // Ignore if we try to reconnect. + if(connectHandle) + return; + + cmd.run(); + }; + + // Restart the stream; + let restart = function() { + if(status.streaming) { + restart = true; // NOTE: not very nice + this.stop(); + } else + this.start(); + }; + + // Stop streaming. + let stop = function() { + cmd.kill('SIGINT'); + + stopHandle = setTimeout(() => { + logger.log(logger.importance[3], "Force Stop!"); + cmd.kill(); + }, 3000); + }; + + let setConfig = function(_conf) { + config = _conf; + }; +}; + +/** + * Utilities + */ + +// Handle Stop and Restart +function stopped() { + status.streaming = false; + + // Clear force kill Timeout + if(stopTimeout) { + clearTimeout(stopTimeout); + stopTimeout = false; + } + + // Restart the stream; + if (restart) { + self.start(); + } + + cmd.emit('stopped'); +} + +// TODO: Restart = false in stopped? +// Hande Stat +function started() { + cmd.emit(restart ? 'restarted' : 'started'); + restart = false; + status.error = false; + status.streaming = true; +} + +/** + * Error Handling + */ + +/** + * Log and handle crashes. Notify the main module. + * @param { Object } error - Error object from the crash. + * @param { String } stdout + * @param { String } stderr + */ +function crashed(error, stdout, stderr){ + // Can't connect to the + if (err.message.indexOf(source) > -1) + status.error = 0; + + // Can't connect to the Internet / YouTube + else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1) + status.error = 1; + + // Wrong FFMPEG Executable + else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1) + status.error = 2; + + // Stopped by us - SIGINT Shouldn't lead to a crash. + else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1){ + stopped(); + return; + } + + // Some unknown Problem, just try to restart. + else { + status.error = 3; + + // Just restart in a Second + setTimeout(function(){ + self.start(); + }, 1000); + } + + logger.log(logger.importance[2], `Crashed: ${erro}\nSTDERR: ${stderr}`); +} + +/** + * Probe the connection to the host on the port and restart the stream afterwards. + * @param { string } host + * @param { number } port + */ +function tryReconnect( host, port ){ + if (!host || !port) + return; + + http.get({ + host: host.split('/')[0], + port: port + }, function(res) { + // We have a response! Connection works. + setTimeout(function() { + // NOTE: Ugly! + + // We have a response! Connection works. + connectHandle = false; + self.start(); + }, 1000); + }).on("error", function(e) { + if (e.message == "socket hang up") { + setTimeout(function() { + // NOTE: Ugly! + + // We have a response! Connection works. + connectHandle = false; + self.start(); + }, 1000); + } else { + // try again + connectHandle = setTimeout(function(){ + tryReconnect(host, port); + }, 1000); + } + }); +} diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..82234c6 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////// +// Winston Logger Wrapper with Custom Colors and Transports // +/////////////////////////////////////////////////////////////////////////////// + +let winston = require('winston'); + +const customLevels = { + levels: { + normal: 0, + info: 1, + warning: 2, + danger: 3, + success: 4 + }, + colors: { + normal: 'white', + info: 'blue', + warning: 'orange', + danger: 'red', + success: 'green' + } +}; + +// Set the Colors +winston.addColors(customLevels.colors); + +let logger = new(winston.Logger)({ + levels: customLevels.levels, + transports: [ + new(winston.transports.Console)({ + level: 'success' + }), + new(winston.transports.File)({ + filename: __dirname + '/process.log', + colorize: true, + timestamp: true, + level: 'success', + json: true, + maxsize: 500000, + maxFiles: 10 + }) + ] +}); + +logger.importance = ['normal', 'info', 'warning', 'danger', 'success']; + +// Export the Logger +module.exports = logger; + diff --git a/src/logger.js~ b/src/logger.js~ new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/logger.js~ @@ -0,0 +1,2 @@ + + diff --git a/src/status.js b/src/status.js new file mode 100644 index 0000000..269e04a --- /dev/null +++ b/src/status.js @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////////// +// Redux wrapper to hold the status object and the master server about changes. // +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Declarations // +/////////////////////////////////////////////////////////////////////////////// + +// Reference to itself. +let self = false; + +// The socket.io connection from the main-module. +let _socket; + +// The Properties of the status. +let _properties; + +// Allowed values for the properties. +let _allowed; + +// Action names for the properties. +let _actions; + +/////////////////////////////////////////////////////////////////////////////// +// Code // +/////////////////////////////////////////////////////////////////////////////// + +/** + * Wrapper to hold the status object and the master server about changes. + * + * The state is a shallow object which contains non-object values. + * @param { Object } socket Socket.io instance from the main module.. + * @param { Object } properties The state properties to be set and read. + * @param { Array } properties.name.allowed Array of allowed values. The first value is the default value. + * @param { String } properties.name.action Name of the action to send to the server. + */ +let state = function( socket, properties ){ + // singleton + if(self) + return self; + + self = this; + _socket = socket; + + // Initialize the Properties + for(let prop in properties){ + // When already there or invalid, skip. + if(this[prop] || !(properties[prop] instanceof Array)) + continue; + + // Set prop and allowed; + _properties[prop] = properties[prop][0]; + _allowed[prop] = Object.assign([], properties[prop]); + + + // Getter function. + ( + function(prop){ + Object.defineProperty(self, prop, { + get: function(){ + return this.get(prop); + } + }); + } + )(prop); + } + + return this; +}; + +module.exports = state; + +/** + * Get a state value. + * @param {String} prop Name of the state property. + * @return { * } The properties value or false if it is not found.x + */ +state.prototype.get = function(prop){ + if(_properties[prop]) + return _properties[prop]; + else + return false; +}; + +/** + * Set a state value. + * @param {String} prop Name of the state property. + * @param { * } value Value to set the property to. + * @return { * } The properties new value or false if not allowed or not found. + * + * /** + * Set state values. + * @param { Object } properties An object with the properties to be changed. + * @return { * } The properties new value or false if not allowed or not found. + */ +state.prototype.set = function(prop, value){ + let change; + + if(!value){ + if(typeof prop !== 'object') + return false; + } else { + if(typeof value == 'Object') + } + + if(!_properties[prop] || !_properties[prop].allowed[value]) + return false; + + // NOTE: Maybe unmutable! + _properties[prop].value = value; + return _properties[prop].value; +}; + +/** + * Utilities + */ + diff --git a/src/status.js~ b/src/status.js~ new file mode 100644 index 0000000..395db58 --- /dev/null +++ b/src/status.js~ @@ -0,0 +1,19 @@ +/////////////////////////////////////////////////////////////////////////////// +// Wrapper to hold the status object and the master server about changes. // +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Declarations // +/////////////////////////////////////////////////////////////////////////////// + +let _socket; + +/////////////////////////////////////////////////////////////////////////////// +// Code // +/////////////////////////////////////////////////////////////////////////////// + +/** + * Wrapper to hold the status object and the master server about changes. + * @param {Object} _socket - Socket.io instance from the main module.. + */ +module.exports = function( _socket ){};