|
|
var net = require('net'), crypto = require('crypto'), format = require('util').format, fs = require('fs');
var nl = '\r\n';
/** * Create a new GNTP request of the given `type`. * * @param {String} type either NOTIFY or REGISTER * @api private */
function GNTP(type, opts) { opts = opts || {}; this.type = type; this.host = opts.host || 'localhost'; this.port = opts.port || 23053; this.request = 'GNTP/1.0 ' + type + ' NONE' + nl; this.resources = []; this.attempts = 0; this.maxAttempts = 5; }
/** * Build a response object from the given `resp` response string. * * The response object has a key/value pair for every header in the response, and * a `.state` property equal to either OK, ERROR, or CALLBACK. * * An example GNTP response: * * GNTP/1.0 -OK NONE\r\n * Response-Action: REGISTER\r\n * \r\n * * Which would parse to: * * { state: 'OK', 'Response-Action': 'REGISTER' } * * @param {String} resp * @return {Object} * @api private */
GNTP.prototype.parseResp = function(resp) { var parsed = {}, head, body; resp = resp.slice(0, resp.indexOf(nl + nl)).split(nl); head = resp[0]; body = resp.slice(1);
parsed.state = head.match(/-(OK|ERROR|CALLBACK)/)[0].slice(1); body.forEach(function(ln) { ln = ln.split(': '); parsed[ln[0]] = ln[1]; });
return parsed; };
/** * Call `GNTP.send()` with the given arguments after a certain delay. * * @api private */
GNTP.prototype.retry = function() { var self = this, args = arguments; setTimeout(function() { self.send.apply(self, args); }, 750); };
/** * Add a resource to the GNTP request. * * @param {Buffer} file * @return {String} * @api private */
GNTP.prototype.addResource = function(file) { var id = crypto.createHash('md5').update(file).digest('hex'), header = 'Identifier: ' + id + nl + 'Length: ' + file.length + nl + nl; this.resources.push({ header: header, file: file }); return 'x-growl-resource://' + id; };
/** * Append another header `name` with a value of `val` to the request. If `val` is * undefined, the header will be left out. * * @param {String} name * @param {String} val * @api public */
GNTP.prototype.add = function(name, val) { if (val === undefined) return;
/* Handle icon files when they're image paths or Buffers. */ if (/-Icon/.test(name) && !/^https?:\/\//.test(val) ) { if (/\.(png|gif|jpe?g)$/.test(val)) val = this.addResource(fs.readFileSync(val)); else if (val instanceof Buffer) val = this.addResource(val); }
this.request += name + ': ' + val + nl; };
/** * Append a newline to the request. * * @api public */
GNTP.prototype.newline = function() { this.request += nl; };
/** * Send the GNTP request, calling `callback` after successfully sending the * request. * * An example GNTP request: * * GNTP/1.0 REGISTER NONE\r\n * Application-Name: Growly.js\r\n * Notifications-Count: 1\r\n * \r\n * Notification-Name: default\r\n * Notification-Display-Name: Default Notification\r\n * Notification-Enabled: True\r\n * \r\n * * @param {Function} callback which will be passed the parsed response * @api public */
GNTP.prototype.send = function(callback) { var self = this, socket = net.connect(this.port, this.host), resp = '';
callback = callback || function() {};
this.attempts += 1;
socket.on('connect', function() { socket.write(self.request);
self.resources.forEach(function(res) { socket.write(res.header); socket.write(res.file); socket.write(nl + nl); }); });
socket.on('data', function(data) { resp += data.toString();
/* Wait until we have a complete response which is signaled by two CRLF's. */ if (resp.slice(resp.length - 4) !== (nl + nl)) return;
resp = self.parseResp(resp);
/* We have to manually close the connection for certain responses; otherwise, reset `resp` to prepare for the next response chunk. */ if (resp.state === 'ERROR' || resp.state === 'CALLBACK') socket.end(); else resp = ''; });
socket.on('end', function() { /* Retry on 200 (timed out), 401 (unknown app), or 402 (unknown notification). */ if (['200', '401', '402'].indexOf(resp['Error-Code']) >= 0) { if (self.attempts <= self.maxAttempts) { self.retry(callback); } else { var msg = 'GNTP request to "%s:%d" failed with error code %s (%s)'; callback(new Error(format(msg, self.host, self.port, resp['Error-Code'], resp['Error-Description']))); } } else { callback(undefined, resp); } });
socket.on('error', function() { callback(new Error(format('Error while sending GNTP request to "%s:%d"', self.host, self.port))); socket.destroy(); }); };
module.exports = GNTP;
|