diff --git a/support/aphlict/server/aphlict_server.js b/support/aphlict/server/aphlict_server.js index 7d222d3b1c..9b0bff0837 100644 --- a/support/aphlict/server/aphlict_server.js +++ b/support/aphlict/server/aphlict_server.js @@ -1,125 +1,127 @@ +'use strict'; + var JX = require('./lib/javelin').JX; var http = require('http'); var https = require('https'); var util = require('util'); var fs = require('fs'); function parse_command_line_arguments(argv) { var config = { 'client-port': 22280, 'admin-port': 22281, 'client-host': '0.0.0.0', 'admin-host': '127.0.0.1', log: '/var/log/aphlict.log', 'ssl-key': null, 'ssl-cert': null, test: false }; for (var ii = 2; ii < argv.length; ii++) { var arg = argv[ii]; var matches = arg.match(/^--([^=]+)=(.*)$/); if (!matches) { throw new Error("Unknown argument '" + arg + "'!"); } if (!(matches[1] in config)) { throw new Error("Unknown argument '" + matches[1] + "'!"); } config[matches[1]] = matches[2]; } config['client-port'] = parseInt(config['client-port'], 10); config['admin-port'] = parseInt(config['admin-port'], 10); return config; } require('./lib/AphlictLog'); var debug = new JX.AphlictLog() .addConsole(console); var config = parse_command_line_arguments(process.argv); process.on('uncaughtException', function(err) { var context = null; if (err.code == 'EACCES' && err.path == config.log) { context = util.format( 'Unable to open logfile ("%s"). Check that permissions are set ' + 'correctly.', err.path); } var message = [ '\n<<< UNCAUGHT EXCEPTION! >>>', ]; if (context) { message.push(context); } message.push(err.stack); debug.log(message.join('\n\n')); }); try { require('ws'); } catch (ex) { throw new Error( 'You need to install the Node.js "ws" module for websocket support. ' + 'See "Notifications User Guide: Setup and Configuration" in the ' + 'documentation for instructions. ' + ex.toString()); } // NOTE: Require these only after checking for the "ws" module, since they // depend on it. require('./lib/AphlictAdminServer'); require('./lib/AphlictClientServer'); var ssl_config = { enabled: (config['ssl-key'] || config['ssl-cert']) }; // Load the SSL certificates (if any were provided) now, so that runs with // `--test` will see any errors. if (ssl_config.enabled) { ssl_config.key = fs.readFileSync(config['ssl-key']); ssl_config.cert = fs.readFileSync(config['ssl-cert']); } // Add the logfile so we'll fail if we can't write to it. if (config.log) { debug.addLog(config.log); } // If we're just doing a configuration test, exit here before starting any // servers. if (config.test) { debug.log('Configuration test OK.'); process.exit(0); } var server; if (ssl_config.enabled) { server = https.createServer({ key: ssl_config.key, cert: ssl_config.cert }, function(req, res) { res.writeHead(501); res.end('HTTP/501 Use Websockets\n'); }); } else { server = http.createServer(function() {}); } var client_server = new JX.AphlictClientServer(server); var admin_server = new JX.AphlictAdminServer(); client_server.setLogger(debug); admin_server.setLogger(debug); admin_server.setClientServer(client_server); client_server.listen(config['client-port'], config['client-host']); admin_server.listen(config['admin-port'], config['admin-host']); debug.log('Started Server (PID %d)', process.pid); diff --git a/support/aphlict/server/lib/AphlictAdminServer.js b/support/aphlict/server/lib/AphlictAdminServer.js index 0166475a55..55a1e15e4e 100644 --- a/support/aphlict/server/lib/AphlictAdminServer.js +++ b/support/aphlict/server/lib/AphlictAdminServer.js @@ -1,135 +1,137 @@ +'use strict'; + var JX = require('./javelin').JX; require('./AphlictListenerList'); var http = require('http'); JX.install('AphlictAdminServer', { construct: function() { this.setLogger(new JX.AphlictLog()); this._startTime = new Date().getTime(); this._messagesIn = 0; this._messagesOut = 0; var handler = this._handler.bind(this); this._server = http.createServer(handler); }, members: { _messagesIn: null, _messagesOut: null, _server: null, _startTime: null, getListeners: function() { return this.getListenerList().getListeners(); }, getListenerList: function() { return this.getClientServer().getListenerList(); }, listen: function() { return this._server.listen.apply(this._server, arguments); }, _handler: function(request, response) { var self = this; // Publishing a notification. if (request.url == '/') { if (request.method == 'POST') { var body = ''; request.on('data', function(data) { body += data; }); request.on('end', function() { try { var msg = JSON.parse(body); self.getLogger().log( 'Received notification: ' + JSON.stringify(msg)); ++this._messagesIn; try { self._transmit(msg); response.writeHead(200, {'Content-Type': 'text/plain'}); } catch (err) { self.getLogger().log( '<%s> Internal Server Error! %s', request.socket.remoteAddress, err); response.writeHead(500, 'Internal Server Error'); } } catch (err) { self.getLogger().log( '<%s> Bad Request! %s', request.socket.remoteAddress, err); response.writeHead(400, 'Bad Request'); } finally { response.end(); } }); } else { response.writeHead(405, 'Method Not Allowed'); response.end(); } } else if (request.url == '/status/') { var status = { 'uptime': (new Date().getTime() - this._startTime), 'clients.active': this.getListenerList().getActiveListenerCount(), 'clients.total': this.getListenerList().getTotalListenerCount(), 'messages.in': this._messagesIn, 'messages.out': this._messagesOut, 'version': 6 }; response.writeHead(200, {'Content-Type': 'application/json'}); response.write(JSON.stringify(status)); response.end(); } else { response.writeHead(404, 'Not Found'); response.end(); } }, /** * Transmits a message to all subscribed listeners. */ _transmit: function(message) { var listeners = this.getListeners().filter(function(client) { return client.isSubscribedToAny(message.subscribers); }); for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; try { listener.writeMessage(message); ++this._messagesOut; this.getLogger().log( '<%s> Wrote Message', listener.getDescription()); } catch (error) { this.getListenerList().removeListener(listener); this.getLogger().log( '<%s> Write Error: %s', listener.getDescription(), error); } } }, }, properties: { clientServer: null, logger: null, } }); diff --git a/support/aphlict/server/lib/AphlictClientServer.js b/support/aphlict/server/lib/AphlictClientServer.js index da139cc221..4ee6be0054 100644 --- a/support/aphlict/server/lib/AphlictClientServer.js +++ b/support/aphlict/server/lib/AphlictClientServer.js @@ -1,90 +1,92 @@ +'use strict'; + var JX = require('./javelin').JX; require('./AphlictListenerList'); require('./AphlictLog'); var util = require('util'); var WebSocket = require('ws'); JX.install('AphlictClientServer', { construct: function(server) { this.setListenerList(new JX.AphlictListenerList()); this.setLogger(new JX.AphlictLog()); this._server = server; }, members: { _server: null, listen: function() { var self = this; var server = this._server.listen.apply(this._server, arguments); var wss = new WebSocket.Server({server: server}); wss.on('connection', function(ws) { var listener = self.getListenerList().addListener(ws); function log() { self.getLogger().log( util.format('<%s>', listener.getDescription()) + ' ' + util.format.apply(null, arguments)); } log('Connected from %s.', ws._socket.remoteAddress); ws.on('message', function(data) { log('Received message: %s', data); var message; try { message = JSON.parse(data); } catch (err) { log('Message is invalid: %s', err.message); return; } switch (message.command) { case 'subscribe': log( 'Subscribed to: %s', JSON.stringify(message.data)); listener.subscribe(message.data); break; case 'unsubscribe': log( 'Unsubscribed from: %s', JSON.stringify(message.data)); listener.unsubscribe(message.data); break; default: log( 'Unrecognized command "%s".', message.command || ''); } }); wss.on('close', function() { self.getListenerList().removeListener(listener); log('Disconnected.'); }); wss.on('error', function(err) { log('Error: %s', err.message); }); }); }, }, properties: { listenerList: null, logger: null, } }); diff --git a/support/aphlict/server/lib/AphlictListener.js b/support/aphlict/server/lib/AphlictListener.js index 809a244a8e..e23105fb6f 100644 --- a/support/aphlict/server/lib/AphlictListener.js +++ b/support/aphlict/server/lib/AphlictListener.js @@ -1,55 +1,57 @@ +'use strict'; + var JX = require('./javelin').JX; JX.install('AphlictListener', { construct: function(id, socket) { this._id = id; this._socket = socket; }, members: { _id: null, _socket: null, _subscriptions: {}, getID: function() { return this._id; }, subscribe: function(phids) { for (var i = 0; i < phids.length; i++) { var phid = phids[i]; this._subscriptions[phid] = true; } return this; }, unsubscribe: function(phids) { for (var i = 0; i < phids.length; i++) { var phid = phids[i]; delete this._subscriptions[phid]; } return this; }, isSubscribedToAny: function(phids) { var intersection = phids.filter(function(phid) { return phid in this._subscriptions; }, this); return intersection.length > 0; }, getSocket: function() { return this._socket; }, getDescription: function() { return 'Listener/' + this.getID(); }, writeMessage: function(message) { this._socket.send(JSON.stringify(message)); }, }, }); diff --git a/support/aphlict/server/lib/AphlictListenerList.js b/support/aphlict/server/lib/AphlictListenerList.js index 9edefcdd5d..22f2c7fc70 100644 --- a/support/aphlict/server/lib/AphlictListenerList.js +++ b/support/aphlict/server/lib/AphlictListenerList.js @@ -1,58 +1,60 @@ +'use strict'; + var JX = require('./javelin').JX; require('./AphlictListener'); JX.install('AphlictListenerList', { construct: function() { this._listeners = {}; }, members: { _listeners: null, _nextID: 0, _totalListenerCount: 0, addListener: function(socket) { var listener = new JX.AphlictListener(this._generateNextID(), socket); this._listeners[listener.getID()] = listener; this._totalListenerCount++; return listener; }, removeListener: function(listener) { var id = listener.getID(); if (id in this._listeners) { delete this._listeners[id]; } }, getListeners: function() { var keys = Object.keys(this._listeners); var listeners = []; for (var i = 0; i < keys.length; i++) { listeners.push(this._listeners[keys[i]]); } return listeners; }, getActiveListenerCount: function() { return this._listeners.length; }, getTotalListenerCount: function() { return this._totalListenerCount; }, _generateNextID: function() { do { this._nextID = (this._nextID + 1) % 1000000000000; } while (this._nextID in this._listeners); return this._nextID; }, }, }); diff --git a/support/aphlict/server/lib/AphlictLog.js b/support/aphlict/server/lib/AphlictLog.js index 654f98f308..77d40793cb 100644 --- a/support/aphlict/server/lib/AphlictLog.js +++ b/support/aphlict/server/lib/AphlictLog.js @@ -1,45 +1,47 @@ +'use strict'; + var JX = require('./javelin').JX; var fs = require('fs'); var util = require('util'); JX.install('AphlictLog', { construct: function() { this._consoles = []; this._logs = []; }, members: { _consoles: null, _logs: null, addConsole: function(console) { this._consoles.push(console); return this; }, addLog: function(path) { this._logs.push(fs.createWriteStream(path, { flags: 'a', encoding: 'utf8', - mode: 0664, + mode: '0664', })); return this; }, log: function() { var str = util.format.apply(null, arguments); var date = new Date().toLocaleString(); str = '[' + date + '] ' + str; var ii; for (ii = 0; ii < this._consoles.length; ii++) { this._consoles[ii].log(str); } for (ii = 0; ii < this._logs.length; ii++) { this._logs[ii].write(str + '\n'); } }, }, }); diff --git a/support/lint/node.jshintrc b/support/lint/node.jshintrc index d0e7626761..f7ade8d7f4 100644 --- a/support/lint/node.jshintrc +++ b/support/lint/node.jshintrc @@ -1,22 +1,23 @@ { "bitwise": true, "curly": true, "immed": true, "indent": 2, "latedef": true, "newcap": true, "noarg": true, "quotmark": "single", "undef": true, "unused": true, "expr": true, "loopfunc": true, + "strict": true, "sub": true, "globals": { "JX": true, "__DEV__": false }, "node": true }