You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							200 lines
						
					
					
						
							5.0 KiB
						
					
					
				
			
		
		
	
	
							200 lines
						
					
					
						
							5.0 KiB
						
					
					
				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;
 | 
						|
 |