Comparing version 0.3.1 to 0.4.0
0.4.0 / 2013-02-15 | ||
================== | ||
* example: [error.mitigation] remove trim for device | ||
* device: improve string regexp to remove all non-alphanumeric | ||
* message: [alert] if only key, set as body | ||
* message: [device] allow device constructor as set | ||
* agent: [close] refactor to wait for queue to finish current | ||
* pgk: update breeze-queue to 0.4.x | ||
* message: [expires] do unix calculation on set, no serialize | ||
* Merge branch 'feature/cache' | ||
* agent: [mock] fix reference errors | ||
* examples: [error.mitigation] refactor to handle different situations | ||
* agent: [all] implement cache mechanism | ||
* test: [cache] increase fuzziness of timing | ||
* message: [device] if no args, return device | ||
* code: [gateway response] change status to code. | ||
* errors: add GatewayNotificationError for apn response errors | ||
* test: [cache] increase test delays for more leighway | ||
* codecs: [gateway.response] add gateway response codec | ||
* cache: store settings on self | ||
* pgk: [breeze-queue] update to 0.3.x | ||
* test: resume running all tests | ||
* test: [cache] add tests for cache constructor | ||
* cache: add the cache constructor | ||
* examples: [basic] load key from certs folder | ||
* agent: [live] remove extraneous console logs" | ||
* message: fix bug preventing 0 expires to proceed | ||
* Merge branch 'refactor/defaultEnhanced' | ||
* makefile: turn live tests off by default | ||
* test: refactor tests for default enhanced codec | ||
* message/agent: [codec] make enhanced the default codec | ||
* test: better naming structure | ||
* test: [message] increase setters test coverage | ||
* message: clean up setters | ||
* docs: note that ios project can be used with tests | ||
* makefile: allow for custom timeouts | ||
* Merge branch 'feature/expires' | ||
* test: [message] add exiration tests | ||
* message: add support for expiration, enabling enhanced codec | ||
* dpes: add tea-ms | ||
* Merge branch 'feature/device' | ||
* test: [message] refactor to use Device constructor | ||
* message: refactor to use Device constructor | ||
* test: [device] add device tests | ||
* device: add device constructor | ||
* deps: update lotus to 0.4.x | ||
0.3.1 / 2013-01-27 | ||
@@ -3,0 +51,0 @@ ================== |
/*! | ||
* apnagent - Message Constructor | ||
* Copyright(c) 2012 Jake Luer <jake@alogicalparadox.com> | ||
* apnagent | ||
* Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com> | ||
* MIT Licensed | ||
@@ -11,3 +11,3 @@ */ | ||
exports.version = '0.3.1'; | ||
exports.version = '0.4.0'; | ||
@@ -27,2 +27,8 @@ /*! | ||
/*! | ||
* Device | ||
*/ | ||
exports.Device = require('./apnagent/device'); | ||
/*! | ||
* Errors | ||
@@ -29,0 +35,0 @@ */ |
@@ -22,3 +22,4 @@ /*! | ||
var codecs = require('../codecs') | ||
var Cache = require('../cache') | ||
, codecs = require('../codecs') | ||
, errors = require('../errors') | ||
@@ -28,2 +29,23 @@ , Message = require('../message'); | ||
/*! | ||
* Constants | ||
*/ | ||
// max unique ids | ||
var INT32 = 0xffffffff; | ||
// Apple notification error messages | ||
var notifErrors = { | ||
0: 'No errors encountered' | ||
, 1: 'Processing error' | ||
, 2: 'Missing device token' | ||
, 3: 'Missing topic' | ||
, 4: 'Missing payload' | ||
, 5: 'Invalid token size' | ||
, 6: 'Invalid topic size' | ||
, 7: 'Invalid payload size' | ||
, 8: 'Invalid token' | ||
, 255: 'None (unknown)' | ||
}; | ||
/*! | ||
* Primary Export | ||
@@ -41,23 +63,29 @@ */ | ||
function Base () { | ||
var self = this | ||
, outgoing, queue; | ||
EventEmitter.call(this, { delimeter: ':' }); | ||
// configuration | ||
this.connected = false; | ||
this.gatewayOpts = null; | ||
this.gatewayError = null; | ||
this.lastId = -1; | ||
this.disable('sandbox'); | ||
this.set('codec', 'simple'); | ||
this.set('cache ttl', '10m'); | ||
this.set('codec', 'enhanced'); | ||
this.set('expires', 0); | ||
this.enable('reconnect'); | ||
this.set('reconnect delay', 3000); | ||
this.disable('sandbox'); | ||
var self = this | ||
, cache, decoder, encoder, queue; | ||
// temp storage for messages | ||
cache = new Cache(); | ||
// queue handles writing buffers to gateway | ||
queue = new Queue(this.queueIterator.bind(this), 1); | ||
queue = new Queue(function () { | ||
debug('(queue) iterate'); | ||
self._queueIterator.apply(self, arguments); | ||
}, 1); | ||
// emit queue errors on agent | ||
queue.onerror = function (err) { | ||
debug('queue error: %s', err.message); | ||
self.emit([ 'queue', 'error' ], err); | ||
debug('(queue) error: %s', err.message); | ||
self.emit('queue:error', err); | ||
}; | ||
@@ -67,19 +95,38 @@ | ||
queue.drain = function () { | ||
debug('queue has been drained'); | ||
self.emit([ 'queue', 'drain' ]); | ||
debug('(queue) drain'); | ||
self.emit('queue:drain'); | ||
}; | ||
// outgoing handles converting msg json to buffers | ||
outgoing = lotus.createWriterStream(); | ||
outgoing.use(0, codecs.getInterface('gateway simple', 'writer')); | ||
outgoing.use(1, codecs.getInterface('gateway enhanced', 'writer')); | ||
outgoing.on('data', function (buf) { | ||
debug('pushing message to send queue'); | ||
self.queue.push(buf, self.connected); | ||
// decoder handles incoming apn errors | ||
decoder = lotus.createReaderStream(); | ||
decoder.use(8, codecs.getInterface('gateway response', 'reader')); | ||
decoder.on('data', function (obj) { | ||
obj.code = obj.code[0]; | ||
var message = notifErrors[obj.code] || 'None (unknown)' | ||
, err = new errors.GatewayNotificationError(message, obj) | ||
, cached = self.cache.get(obj.identifier) | ||
, msg = null; | ||
if (cached) { | ||
msg = new Message(self, 'enhanced', cached.json.payload); | ||
msg.device(cached.json.deviceToken); | ||
msg.meta.expires = cached.json.expiration; | ||
} | ||
debug('(notification) error: %s', err.message); | ||
self.gatewayError = err; | ||
self.emit('notification:error', err, msg); | ||
}); | ||
// encoder handles converting msg json to buffers | ||
encoder = lotus.createWriterStream(); | ||
encoder.use(0, codecs.getInterface('gateway simple', 'writer')); | ||
encoder.use(1, codecs.getInterface('gateway enhanced', 'writer')); | ||
// mount objects | ||
this.outgoing = outgoing; | ||
this.cache = cache; | ||
this.decoder = decoder; | ||
this.encoder = encoder; | ||
this.gateway = null; | ||
this.queue = queue; | ||
this.gateway = null; | ||
} | ||
@@ -105,4 +152,4 @@ | ||
* | ||
* @param {String} encoding (default: utf8) | ||
* @param {String} codec (simple or enhanced) | ||
* @param {String} encoding (default: utf8) | ||
* @name create | ||
@@ -112,5 +159,5 @@ * @api public | ||
Base.prototype.createMessage = function (codec, enc) { | ||
Base.prototype.createMessage = function (enc, codec) { | ||
codec = codec || this.get('codec'); | ||
return new Message(this, codec, enc); | ||
return new Message(this, codec, { enc: enc }); | ||
}; | ||
@@ -136,3 +183,3 @@ | ||
if (i > 4294967296) { | ||
if (i > INT32) { | ||
i = this.lastId = 0; | ||
@@ -167,4 +214,5 @@ } | ||
process.nextTick(function () { | ||
debug('serialize message failed: %s', err.message); | ||
debug('(message) error: %s', err.message); | ||
cb(err); | ||
self.emit('notification:error', err, msg); | ||
}); | ||
@@ -175,3 +223,3 @@ } | ||
if (!~[ 0, 1 ].indexOf(codec)) { | ||
error(new errors.SerializationError('Invalid codec: ' + codecStr, null, arguments.callee)); | ||
error(new errors.SerializationError('Invalid codec: ' + codecStr)); | ||
return this; | ||
@@ -188,9 +236,5 @@ } | ||
// write json to outgoing codec stream | ||
process.nextTick(function () { | ||
debug('writing message to codec'); | ||
self.outgoing.write(codec, json); | ||
cb(); | ||
}); | ||
// write json to to the queue | ||
debug('(queue) push: %d', json.identifier); | ||
self.queue.push({ codec: codec, json: json }, cb); | ||
return this; | ||
@@ -206,3 +250,3 @@ }; | ||
var err = new Error('Gateway connect not implemented.'); | ||
this.emit([ 'gateway', 'error' ], err); | ||
this.emit('gateway:error', err); | ||
cb(err); | ||
@@ -209,0 +253,0 @@ }; |
@@ -13,2 +13,3 @@ /*! | ||
, inherits = require('tea-inherits') | ||
, ms = require('tea-ms') | ||
, tls = require('tls'); | ||
@@ -69,3 +70,6 @@ | ||
var self = this | ||
, delay = this.get('reconnect delay') | ||
, opts = util.gatewayOptions(this) | ||
, recon = this.enabled('reconnect') | ||
, ttl = this.get('cache ttl') | ||
, gateway; | ||
@@ -75,7 +79,6 @@ | ||
if (!opts.pfx && !opts.key && !opts.cert) { | ||
console.log(opts); | ||
process.nextTick(function () { | ||
var err = new errors.GatewayAuthorizationError('Insufficient credentials'); | ||
debug('gateway error: insufficient credentials'); | ||
self.emit([ 'gateway', 'error' ], err); | ||
debug('(gateway) error: %s', err.message); | ||
self.emit('gateway:error', err); | ||
cb(err); | ||
@@ -87,18 +90,42 @@ }); | ||
debug('gateway initializing connection to %s:%d', opts.host, opts.port); | ||
this.gatewayOpts = opts; | ||
// how to perform a reconnect | ||
function reconnect () { | ||
var gwe = self.gatewayError | ||
, pos = 0; | ||
// connect to tls service using opts | ||
if (gwe && 'undefined' !== typeof gwe.identifier) { | ||
debug('(cache) since: %d', gwe.identifier); | ||
self.cache.sinceId(gwe.identifier, function (obj, id) { | ||
debug('(queue) push: %d', id); | ||
self.queue.pushAt(pos++, obj); | ||
}); | ||
} | ||
debug('(gateway) reconnecting'); | ||
self.connect(function (err) { | ||
if (err) return; | ||
debug('(gateway) reconnected'); | ||
self.emit('gateway:reconnect'); | ||
}); | ||
} | ||
// reset state | ||
this.gatewayError = null; | ||
// connect to gateway | ||
debug('(gateway) connecting - %s:%d', opts.host, opts.port); | ||
gateway = tls.connect(opts, function () { | ||
if (gateway.authorized) { | ||
debug('gateway connection established to %s:%d', opts.host, opts.port); | ||
debug('(gateway) connected - %s:%d', opts.host, opts.port); | ||
self.connected = true; | ||
self.cache.ttl = ttl; | ||
self.cache.resume(); | ||
self.queue.resume(); | ||
self.emit([ 'gateway', 'connect' ], opts.host, opts.port); | ||
self.emit('gateway:connect'); | ||
cb(); | ||
} else { | ||
var err = new errors.GatewayAuthorizationError(gateway.authorizationError); | ||
debug('gateway connection denied to %s:%d', opts.host, opts.port, gateway.authorizationError); | ||
debug('(gateway) unauthorized - %s:%d', opts.host, opts.port, gateway.authorizationError); | ||
self.gateway.destroy(); | ||
self.emit([ 'gateway', 'error' ], err); | ||
self.emit('gateway:error', err); | ||
cb(err); | ||
@@ -110,27 +137,21 @@ } | ||
gateway.on('close', function () { | ||
self.cache.pause(); | ||
self.queue.pause(); | ||
self.gatewayOpts = null; | ||
self.gateway = null; | ||
// if user DID trigger disconnect or reconnect disabled | ||
if (!self.connected || !self.enabled('reconnect')) { | ||
debug('gateway connection closed to %s:%d', opts.host, opts.port); | ||
self.emit([ 'gateway', 'close' ]); | ||
if (self.connected && recon) { | ||
debug('(gateway) disconnected - %s:%d', opts.host, opts.port); | ||
self.connected = false; | ||
setTimeout(reconnect, ms(delay)); | ||
} else { | ||
debug('(gateway) closed - %s:%d', opts.host, opts.port); | ||
self.connected = false; | ||
self.emit('gateway:close'); | ||
} | ||
}); | ||
// if user DID NOT trigger disconnect | ||
else if (self.connected && self.enabled('reconnect')) { | ||
debug('gateway connection unexpectedly closed to %s:%d', opts.host, opts.port); | ||
setTimeout(function () { | ||
debug('gateway triggering reconnect'); | ||
self.connect(function () { | ||
var host = self.gatewayOpts.host | ||
, port = self.gatewayOpts.port; | ||
debug('gateway reconnected successful to %s:%d', host, port); | ||
self.emit([ 'gateway', 'reconnect' ], host, port); | ||
}); | ||
}, self.get('reconnect delay')); | ||
} | ||
self.connected = false; | ||
// handle incoming data | ||
gateway.on('data', function (buf) { | ||
debug('(gateway) data: %d bytes', buf.length); | ||
self.decoder.write(buf); | ||
}); | ||
@@ -140,4 +161,4 @@ | ||
gateway.on('error', function (err) { | ||
debug('gateway socket error: %s', err.message || 'Unspecified Error'); | ||
self.emit([ 'gateway', 'error' ], err); | ||
debug('(gateway) error: %s', err.message || 'Unspecified Error'); | ||
self.emit('gateway:error', err); | ||
}); | ||
@@ -147,2 +168,3 @@ | ||
this.gateway = gateway; | ||
return this; | ||
}; | ||
@@ -159,25 +181,56 @@ | ||
Agent.prototype.close = function () { | ||
if (!this.connected) return; | ||
var host = this.gatewayOpts.host | ||
, port = this.gatewayOpts.port; | ||
debug('closing gateway connection to %s:%d', host, port); | ||
Agent.prototype.close = function (cb) { | ||
cb = cb || function () {}; | ||
// leave if nothing needs to be done | ||
if (!this.connected) { | ||
process.nextTick(cb); | ||
return this; | ||
} | ||
var self = this | ||
, drain = this.queue.drain; | ||
// wait for queue to finish processing current | ||
this.queue.drain = function () { | ||
debug('(gateway) disconnecting'); | ||
self.cache.pause(); | ||
self.gateway.once('close', cb); | ||
self.gateway.destroy(); | ||
self.queue.drain = drain; | ||
}; | ||
// set things in motion | ||
this.connected = false; | ||
this.queue.pause(); | ||
this.gateway.destroy(); | ||
return this; | ||
}; | ||
Agent.prototype.queueIterator = function (buf, next) { | ||
Agent.prototype._queueIterator = function (obj, next) { | ||
// requeue if not connected | ||
if (!this.gateway || !this.connected) { | ||
debug('socket write queue pausing self'); | ||
this.queue.pause() | ||
this.queue.push(buf); | ||
debug('(queue) pause: not connected'); | ||
this.queue.pause(); | ||
this.queue.pushAt(0, obj); | ||
return next(); | ||
} | ||
debug('writing message to socket'); | ||
this.gateway.write(buf, function () { | ||
debug('writing message success'); | ||
next(); | ||
var cache = this.cache | ||
, encoder = this.encoder | ||
, gateway = this.gateway | ||
, id = obj.json.identifier; | ||
// wait for encoded message | ||
encoder.once('data', function (buf) { | ||
debug('(gateway) write: %d', id); | ||
gateway.write(buf, function () { | ||
debug('(cache) push: %d', id); | ||
cache.push(id, obj); | ||
next(); | ||
}); | ||
}); | ||
// encode message | ||
debug('(encoder) write: %d', id); | ||
encoder.write(obj.codec, obj.json); | ||
}; |
@@ -13,2 +13,3 @@ /*! | ||
, inherits = require('tea-inherits') | ||
, ms = require('tea-ms') | ||
, lotus = require('lotus'); | ||
@@ -70,8 +71,32 @@ | ||
var self = this | ||
, delay = this.get('reconnect delay') | ||
, opts = util.gatewayOptions(this) | ||
, recon = this.enabled('reconnect') | ||
, ttl = this.get('cache ttl') | ||
, gateway; | ||
debug('mock gateway initializing'); | ||
this.gatewayOpts = opts; | ||
// how to perform a reconnect | ||
function reconnect () { | ||
var gwe = self.gatewayError | ||
, pos = 0; | ||
if (gwe && 'undefined' !== typeof gwe.identifier) { | ||
debug('(cache) since: %d', gwe.identifier); | ||
self.cache.sinceId(gwe.identifier, function (obj, id) { | ||
debug('(queue) push: %d', id); | ||
self.queue.pushAt(pos++, obj); | ||
}); | ||
} | ||
debug('(gateway) reconnecting'); | ||
self.connect(function (err) { | ||
if (err) return; | ||
debug('(gateway) reconnected'); | ||
self.emit('gateway:reconnect'); | ||
}); | ||
} | ||
// reset state | ||
this.gatewayError = null; | ||
// mock gateway parses the outgoing buffers back to json | ||
@@ -82,6 +107,18 @@ gateway = lotus.createReaderStream(); | ||
// simulate async connect | ||
debug('(gateway) connecting - mock'); | ||
process.nextTick(function () { | ||
debug('(gateway) connected - mock'); | ||
self.connected = true; | ||
self.cache.ttl = ttl; | ||
self.cache.resume(); | ||
self.queue.resume(); | ||
self.emit('gateway:connect'); | ||
cb(); | ||
}); | ||
// data event is emitted for each message | ||
gateway.on('data', function (json) { | ||
debug('mock gateway incoming message', json); | ||
self.emit([ 'mock', 'message' ], json); | ||
debug('(gateway) incoming message', json); | ||
self.emit('mock:message', json); | ||
}); | ||
@@ -91,4 +128,4 @@ | ||
gateway.on('error', function (err) { | ||
debug('mock gateway "socket" error: %s', err.message || 'Unspecified Error'); | ||
self.emit([ 'gateway', 'error' ], err); | ||
debug('(gateway) error: %s', err.message || 'Unspecified Error'); | ||
self.emit('gateway:error', err); | ||
}); | ||
@@ -98,38 +135,20 @@ | ||
gateway.on('end', function () { | ||
self.cache.pause(); | ||
self.queue.pause(); | ||
self.gatewayOpts = null; | ||
self.gateway = null; | ||
// if user DID trigger disconnect or reconnect disabled | ||
if (!self.connected || !self.enabled('reconnect')) { | ||
debug('mock gateway connection closed'); | ||
self.emit([ 'gateway', 'close' ]); | ||
if (self.connected && recon) { | ||
debug('(gateway) disconnected - mock'); | ||
self.connected = false; | ||
setTimeout(reconnect, ms(delay)); | ||
} else { | ||
debug('(gateway) closed - mock'); | ||
self.connected = false; | ||
self.emit('gateway:close'); | ||
} | ||
// if user DID NOT trigger disconnect | ||
else if (self.connected && self.enabled('reconnect')) { | ||
debug('mock gateway connection unexpectedly closed'); | ||
setTimeout(function () { | ||
debug('mock gateway triggering reconnect'); | ||
self.connect(function () { | ||
debug('mock gateway reconnected successful'); | ||
self.emit([ 'gateway', 'reconnect' ], null, null); | ||
}); | ||
}, self.get('reconnect delay')); | ||
} | ||
self.connected = false; | ||
}); | ||
// simulate async connect | ||
process.nextTick(function () { | ||
debug('mock gateway connection established'); | ||
self.connected = true; | ||
self.queue.resume(); | ||
self.emit([ 'gateway', 'connect' ], null, null); | ||
cb(); | ||
}); | ||
// mount | ||
this.gateway = gateway; | ||
return this; | ||
}; | ||
@@ -146,21 +165,57 @@ | ||
Mock.prototype.close = function () { | ||
debug('closing mock gateway connection'); | ||
Mock.prototype.close = function (cb) { | ||
cb = cb || function () {}; | ||
// leave if nothing needs to be done | ||
if (!this.connected) { | ||
process.nextTick(cb); | ||
return this; | ||
} | ||
var self = this | ||
, drain = this.queue.drain; | ||
// wait for queue to finish processing current | ||
this.queue.drain = function () { | ||
debug('(gateway) disconnecting'); | ||
self.cache.pause(); | ||
process.nextTick(cb); | ||
self.gateway.end(); | ||
self.queue.drain = drain; | ||
}; | ||
// set things in motion | ||
this.connected = false; | ||
this.queue.pause(); | ||
this.gateway.end(); | ||
return this; | ||
}; | ||
Mock.prototype.queueIterator = function (buf, next) { | ||
Mock.prototype._queueIterator = function (obj, next) { | ||
// requeue if not connected | ||
if (!this.gateway || !this.connected) { | ||
debug('mock write queue pausing self'); | ||
this.queue.pause() | ||
this.queue.push(buf); | ||
debug('(queue) pause: not connected'); | ||
this.queue.pause(); | ||
this.queue.pushAt(0, obj); | ||
return next(); | ||
} | ||
debug('writing message to mock gateway'); | ||
this.gateway.write(buf); | ||
debug('writing message success'); | ||
process.nextTick(next); // make async | ||
var cache = this.cache | ||
, encoder = this.encoder | ||
, gateway = this.gateway | ||
, id = obj.json.identifier; | ||
// wait for encoded message | ||
encoder.once('data', function (buf) { | ||
debug('(gateway) write: %d', id); | ||
gateway.write(buf); | ||
process.nextTick(function () { | ||
debug('(cache) push: %d', id); | ||
cache.push(id, obj); | ||
next(); | ||
}); | ||
}); | ||
// encode message | ||
debug('(encoder) write: %d', id); | ||
encoder.write(obj.codec, obj.json); | ||
}; |
@@ -67,3 +67,3 @@ /*! | ||
, id: 0 | ||
, mod: require('./simple') | ||
, mod: require('./gateway.simple') | ||
}); | ||
@@ -78,3 +78,13 @@ | ||
, id: 1 | ||
, mod: require('./enhanced') | ||
, mod: require('./gateway.enhanced') | ||
}); | ||
/*! | ||
* Push `enhanced` gateway codec | ||
*/ | ||
codecs.push({ | ||
name: 'gateway response' | ||
, id: 8 | ||
, mod: require('./gateway.response') | ||
}); |
@@ -19,2 +19,3 @@ /*! | ||
'GatewayAuthorizationError' | ||
, 'GatewayNotificationError' | ||
, 'SerializationError' | ||
@@ -21,0 +22,0 @@ ]; |
@@ -11,3 +11,4 @@ /*! | ||
var extend = require('tea-extend'); | ||
var extend = require('tea-extend') | ||
, ms = require('tea-ms'); | ||
@@ -18,3 +19,4 @@ /*! | ||
var errors = require('./errors') | ||
var Device = require('./device') | ||
, errors = require('./errors') | ||
, util = require('./util'); | ||
@@ -41,22 +43,44 @@ | ||
function Message (agent, codec, enc) { | ||
function Message (agent, codec, opts) { | ||
this._agent = agent; | ||
this.encoding = enc || 'utf8'; | ||
this.meta = { | ||
codec: codec | ||
, identifier: this._agent | ||
? this._agent.nextId() | ||
: 0 | ||
}; | ||
this.encoding = 'utf8'; | ||
this.meta = {}; | ||
this.meta.codec = codec; | ||
this.meta.device = new Device(); | ||
this.meta.expires = null; | ||
this.settings = {}; | ||
this.payload = {}; | ||
this.aps = {}; | ||
} | ||
Object.defineProperty(Message.prototype, 'id', { | ||
get: function () { | ||
return this.meta.identifier; | ||
opts = opts || {}; | ||
// import custom encoding | ||
if (opts.enc) this.encoding = opts.enc; | ||
// import custom variables | ||
for (var name in opts) { | ||
if (name === 'aps' || name === 'enc') continue; | ||
this.set(name, opts[name]); | ||
} | ||
if (opts.aps) { | ||
// import badge/sound | ||
if (opts.aps.badge) this.badge(opts.aps.badge); | ||
if (opts.aps.sound) this.sound(opts.aps.sound); | ||
// import alert | ||
if (opts.aps.alert && 'string' === typeof opts.aps.alert) { | ||
this.alert('body', opts.aps.alert); | ||
} else if (opts.aps.alert) { | ||
this.alert(opts.aps.alert); | ||
} | ||
}); | ||
} | ||
if (agent && 'simple' !== codec) { | ||
this.expires(agent.get('expires')); | ||
} | ||
} | ||
/** | ||
@@ -80,10 +104,9 @@ * .set (key, value) | ||
Message.prototype.set = function (key, value) { | ||
if ('string' == typeof key) { | ||
if ('object' === typeof key) { | ||
for (var name in key) { | ||
this.set(name, key[name]); | ||
} | ||
} else { | ||
if (key === 'aps') return; | ||
this.payload[key] = value; | ||
} else { | ||
for (name in key) { | ||
if (name === 'aps') continue; | ||
this.payload[name] = key[name]; | ||
} | ||
} | ||
@@ -103,2 +126,9 @@ | ||
* | ||
* Allowed keys: | ||
* - `body` | ||
* - `action-loc-key' | ||
* - `log-key` | ||
* - `loc-args` | ||
* - `launch-image` | ||
* | ||
* @param {String|Object} | ||
@@ -119,10 +149,11 @@ * @param {Mixed} value | ||
if ('string' == typeof key) { | ||
if ('object' === typeof key) { | ||
for (var name in key) { | ||
this.alert(name, key[name]); | ||
} | ||
} else if (arguments.length === 1) { | ||
this.aps['body'] = key; | ||
} else { | ||
if (!~allowed.indexOf(key)) return this; | ||
this.aps[key] = value; | ||
} else { | ||
for (var name in key) { | ||
if (!~allowed.indexOf(name)) continue; | ||
this.aps[name] = key[name]; | ||
} | ||
} | ||
@@ -147,7 +178,10 @@ | ||
Message.prototype.device = function (device) { | ||
if ('string' === typeof device) { | ||
device = new Buffer(device.replace(/\s/g, ''), 'hex'); | ||
if (!arguments.length) { | ||
return this.meta.device; | ||
} else if (device instanceof Device) { | ||
this.meta.device = device; | ||
} else { | ||
this.meta.device.token = device; | ||
} | ||
this.meta.device = device; | ||
return this; | ||
@@ -157,22 +191,4 @@ }; | ||
/** | ||
* .codec (name) | ||
* .expires (time) | ||
* | ||
* Overwrite the default codec to be used when | ||
* encoding for transfer. | ||
* | ||
* @param {String} codec name | ||
* @returns {this} for chaining | ||
* @api public | ||
*/ | ||
Message.prototype.codec = function (name) { | ||
this.meta.codec = name; | ||
return this; | ||
}; | ||
/** | ||
* .id (id) | ||
* | ||
* > NOTE: not currently implemented | ||
* | ||
* Set the message expiration date when being used | ||
@@ -184,3 +200,3 @@ * with the enhanced codec. If you are composing a | ||
* Should be provided as the number of ms until expiration | ||
* or as a string that can be converded, such as `1d`. | ||
* or as a string that can be converted, such as `1d`. | ||
* | ||
@@ -195,3 +211,17 @@ * See APNS documentation for more information. | ||
Message.prototype.expires = function (time) { | ||
//TODO: implement this | ||
if ('number' === typeof time) { | ||
this.meta.expires = time === 0 ? time : ms.unix(time); | ||
this.meta.codec = 'enhanced'; | ||
} else if (true === time) { | ||
this.meta.expires = 0; | ||
this.meta.codec = 'enhanced'; | ||
} else if (!time) { | ||
this.meta.expires = null; | ||
this.meta.codec = 'simple' | ||
} else { | ||
this.meta.expires = ms.unix(time); | ||
this.meta.codec = 'enhanced'; | ||
} | ||
return this; | ||
}; | ||
@@ -247,3 +277,4 @@ | ||
Message.prototype.serialize = function () { | ||
var enc = this.encoding | ||
var codec = this.meta.codec | ||
, enc = this.encoding | ||
, payload = {} | ||
@@ -253,5 +284,21 @@ , SE = errors.SerializationError | ||
// check for device | ||
if (!this.meta.device.toBuffer()) { | ||
throw new SE('Message device not specified.', null, ssf); | ||
} | ||
// enhanced codec requires expiration | ||
if ('enhanced' === codec && null === this.meta.expires) { | ||
throw new SE('Message expiration not specified for enhanced codec delivery.', null, ssf); | ||
} | ||
// enchanced code requires agent to generate an id | ||
if ('enhanced' === codec && !this._agent) { | ||
throw new SE('Message agent not specified for enhanced codec delivery.', null, ssf); | ||
} | ||
// copy over extra variables | ||
extend(payload, this.payload); | ||
// set encoding if not utf8 | ||
if (enc !== 'utf8') { | ||
@@ -273,2 +320,3 @@ payload.enc = this.encoding; | ||
// check to ensure body is not to long, shorten if possible | ||
var str = JSON.stringify(payload) | ||
@@ -297,52 +345,14 @@ , len = Buffer.byteLength(str, enc); | ||
if (!this.meta.device) { | ||
throw new SE('Device must be specified', null, ssf); | ||
} | ||
return { | ||
deviceToken: this.meta.device | ||
, identifier: this.id | ||
, payload: payload | ||
}; | ||
// construct the response | ||
var res = {}; | ||
res.deviceToken = this.meta.device.toBuffer(); | ||
res.expiration = this.meta.expires | ||
res.identifier = this._agent | ||
? this._agent.nextId() | ||
: null; | ||
res.payload = payload; | ||
return res; | ||
}; | ||
/** | ||
* .import (json) | ||
* | ||
* Import a compatible payload into this message. | ||
* If the message already has variables defined, they | ||
* may be overwritten or merged with incoming values. | ||
* Suggested use is only with an empty message. | ||
* | ||
* @param {Object} json payload | ||
* @api public | ||
*/ | ||
Message.prototype.import = function (json) { | ||
// import custom encoding | ||
if (json.enc) this.encoding = json.enc; | ||
// import custom variables | ||
for (var name in json) { | ||
if (name === 'aps' || name === 'enc') continue; | ||
this.set(name, json[name]); | ||
} | ||
if (json.aps) { | ||
// import badge/sound | ||
if (json.aps.badge) this.badge(json.aps.badge); | ||
if (json.aps.sound) this.sound(json.aps.sound); | ||
// import alert | ||
if (json.aps.alert && 'string' === typeof json.aps.alert) { | ||
this.alert('body', json.aps.alert); | ||
} else if (json.aps.alert) { | ||
this.alert(json.aps.alert); | ||
} | ||
} | ||
return this; | ||
}; | ||
/** | ||
* .send (cb) | ||
@@ -349,0 +359,0 @@ * |
{ | ||
"name": "apnagent" | ||
, "version": "0.3.1" | ||
, "version": "0.4.0" | ||
, "description": "Node adapter for Apple Push Notification (APN) Service." | ||
@@ -32,9 +32,10 @@ , "author": "Jake Luer <jake@alogicalpardox.com> (http://alogicalparadox.com)" | ||
, "dependencies": { | ||
"breeze-queue": "0.2.x" | ||
"breeze-queue": "0.4.x" | ||
, "drip": "1.1.x" | ||
, "facet": "0.3.x" | ||
, "lotus": "0.3.x" | ||
, "lotus": "0.4.x" | ||
, "tea-error": "0.1.x" | ||
, "tea-extend": "0.2.x" | ||
, "tea-inherits": "0.1.x" | ||
, "tea-ms": "0.1.x" | ||
, "sherlock": "*" | ||
@@ -44,4 +45,5 @@ } | ||
"chai": "*" | ||
, "chai-spies": "*" | ||
, "mocha": "*" | ||
} | ||
} |
@@ -19,3 +19,3 @@ # APN Agent [![Build Status](https://travis-ci.org/logicalparadox/apnagent.png?branch=master)](https://travis-ci.org/logicalparadox/apnagent) | ||
- [apnagent-ios](https://github.com/logicalparadox/apnagent-ios): Tiny iOS application for use with the example(s). | ||
- [apnagent-ios](https://github.com/logicalparadox/apnagent-ios): Tiny iOS application for use with the example(s) and live tests. | ||
@@ -22,0 +22,0 @@ ## Installation |
45189
18
1508
9
3
+ Addedtea-ms@0.1.x
+ Addedbreeze-queue@0.4.0(transitive)
+ Addedlotus@0.4.1(transitive)
+ Addedtea-ms@0.1.0(transitive)
+ Addedtea-type@0.1.0(transitive)
- Removedbreeze-queue@0.2.0(transitive)
- Removedlotus@0.3.1(transitive)
Updatedbreeze-queue@0.4.x
Updatedlotus@0.4.x