Comparing version 1.1.4 to 1.1.5
441
lib/apn.js
@@ -6,13 +6,13 @@ var tls = require('tls'); | ||
var Errors = { | ||
'noErrorsEncountered': 0 | ||
, 'processingError': 1 | ||
, 'missingDeviceToken': 2 | ||
, 'missingTopic': 3 | ||
, 'missingPayload': 4 | ||
, 'invalidTokenSize': 5 | ||
, 'invalidTopicSize': 6 | ||
, 'invalidPayloadSize': 7 | ||
, 'invalidToken': 8 | ||
, 'none': 255 | ||
} | ||
'noErrorsEncountered': 0, | ||
'processingError': 1, | ||
'missingDeviceToken': 2, | ||
'missingTopic': 3, | ||
'missingPayload': 4, | ||
'invalidTokenSize': 5, | ||
'invalidTopicSize': 6, | ||
'invalidPayloadSize': 7, | ||
'invalidToken': 8, | ||
'none': 255 | ||
}; | ||
@@ -24,16 +24,20 @@ var Connection = function (optionArgs) { | ||
var self = this; | ||
var hasKey = hasCert = false; | ||
var hasKey = false; | ||
var hasCert = false; | ||
var socketOptions = {}; | ||
var openingSocket = false; | ||
var writeBuffer = []; | ||
var options = { | ||
cert: 'cert.pem' /* Certificate file */, | ||
certData: '', | ||
key: 'key.pem' /* Key file */, | ||
keyData: '', | ||
gateway: 'gateway.push.apple.com' /* gateway address */, | ||
port: 2195 /* gateway port */, | ||
enhanced: true /* enable enhanced format */, | ||
errorCallback: undefined /* Callback when error occurs */, | ||
cacheLength: 5 /* Number of notifications to cache for error purposes */ | ||
}; | ||
var options = { cert: 'cert.pem' /* Certificate file */ | ||
, key: 'key.pem' /* Key file */ | ||
, gateway: 'gateway.push.apple.com' /* gateway address */ | ||
, port: 2195 /* gateway port */ | ||
, enhanced: true /* enable enhanced format */ | ||
, errorCallback: undefined /* Callback when error occurs */ | ||
, cacheLength: 5 /* Number of notifications to cache for error purposes */ | ||
}; | ||
if (optionArgs) { | ||
@@ -46,9 +50,9 @@ var keys = Object.keys(options); | ||
} | ||
var readyToConnect = function () { | ||
return (hasKey && hasCert); | ||
} | ||
}; | ||
var onDrain = function() { | ||
if (writeBuffer.length) { | ||
var onDrain = function () { | ||
while (writeBuffer.length && self.socket.bufferSize == 0) { | ||
writeNotificationToSocket(writeBuffer.shift()); | ||
@@ -58,26 +62,26 @@ } | ||
var startSocket = function () { | ||
if(!self.openingSocket) { | ||
process.nextTick(function() { | ||
self.socket = tls.connect(options['port'], options['gateway'], socketOptions, | ||
callback = function() { | ||
if(!self.socket.authorized) { | ||
throw self.socket.authorizationError | ||
var startSocket = function () { | ||
if (!self.openingSocket) { | ||
process.nextTick(function () { | ||
self.socket = tls.connect(options['port'], options['gateway'], socketOptions, | ||
function () { | ||
if (!self.socket.authorized) { | ||
throw self.socket.authorizationError; | ||
} | ||
onDrain(); | ||
self.openingSocket=false; | ||
self.openingSocket = false; | ||
}); | ||
self.socket.on('data', function(data) { | ||
self.socket.on('data', function (data) { | ||
handleTransmissionError(data); | ||
}); | ||
self.socket.on('error', function(data) { | ||
self.socket.on('error', function () { | ||
self.socket.removeAllListeners(); | ||
self.socket = undefined; | ||
}); | ||
self.socket.once('close', function () { | ||
if(writeBuffer.length && readyToConnect()) { | ||
if (writeBuffer.length && readyToConnect()) { | ||
startSocket(); | ||
@@ -88,7 +92,7 @@ } | ||
} | ||
self.socket.removeAllListeners(); | ||
self.socket = undefined; | ||
}); | ||
self.socket.on("drain", onDrain); | ||
@@ -98,24 +102,36 @@ }); | ||
} | ||
}; | ||
var connect = invoke_after(function () { | ||
startSocket(); | ||
}); | ||
if (options['certData']) { | ||
socketOptions['cert'] = options['certData']; | ||
hasCert = true; | ||
} else { | ||
fs.readFile(options['cert'], connect(function (err, data) { | ||
if (err) { | ||
throw err; | ||
} | ||
socketOptions['cert'] = data.toString(); | ||
hasCert = true; | ||
})); | ||
} | ||
var connect = invoke_after(function() { startSocket(); }); | ||
fs.readFile(options['cert'], connect(function(err, data) { | ||
if(err) { | ||
throw err; | ||
} | ||
socketOptions['cert'] = data.toString(); | ||
hasCert = true; | ||
})); | ||
fs.readFile(options['key'], connect(function(err, data) { | ||
if(err) { | ||
throw err; | ||
} | ||
socketOptions['key'] = data.toString(); | ||
if (options['keyData']) { | ||
socketOptions['key'] = options['keyData']; | ||
hasKey = true; | ||
})); | ||
} else { | ||
fs.readFile(options['key'], connect(function (err, data) { | ||
if (err) { | ||
throw err; | ||
} | ||
socketOptions['key'] = data.toString(); | ||
hasKey = true; | ||
})); | ||
} | ||
var writeNotificationToSocket = function(data) { | ||
if (self.socket === undefined || self.socket.readyState != 'open') { | ||
var writeNotificationToSocket = function (data) { | ||
if (self.socket === undefined || self.socket.readyState != 'open') { | ||
if ((self.socket === undefined || self.socket.readyState == 'closed') && readyToConnect()) { | ||
@@ -134,9 +150,9 @@ startSocket(); | ||
var bufferDataForWrite = function(data) { | ||
var bufferDataForWrite = function (data) { | ||
writeBuffer.push(data); | ||
} | ||
}; | ||
this.sendNotification = function (note) { | ||
var encoding = 'utf8'; | ||
if(note.encoding) { | ||
if (note.encoding) { | ||
encoding = note.encoding; | ||
@@ -148,23 +164,24 @@ } | ||
var pos = 0; | ||
if(token === undefined) { | ||
if (token === undefined) { | ||
return Errors['missingDeviceToken']; | ||
} | ||
if(messageLength > 256) { | ||
if (messageLength > 256) { | ||
return Errors['invalidPayloadSize']; | ||
} | ||
note._uid = currentId++; | ||
if(options.enhanced) { | ||
var data = new Buffer(1 + 4 + 4 + 2 + token.length + 2 + messageLength); | ||
var data; | ||
if (options.enhanced) { | ||
data = new Buffer(1 + 4 + 4 + 2 + token.length + 2 + messageLength); | ||
// Command | ||
data[pos] = 1; | ||
pos++; | ||
// Identifier | ||
pos += int2buf(note._uid, data, pos, 4); | ||
// Expiry | ||
pos += int2buf(note.expiry, data, pos, 4); | ||
cachedNotes.push(note); | ||
@@ -174,7 +191,7 @@ tidyCachedNotes(); | ||
else { | ||
var data = new Buffer(1 + 2 + token.length + 2 + messageLength); | ||
data = new Buffer(1 + 2 + token.length + 2 + messageLength); | ||
data[pos] = 0; | ||
pos++; | ||
} | ||
pos += int2buf(token.length, data, pos, 2); | ||
@@ -186,12 +203,12 @@ pos += token.copy(data, pos, 0); | ||
writeNotificationToSocket(data); | ||
} | ||
var tidyCachedNotes = function() { | ||
}; | ||
var tidyCachedNotes = function () { | ||
// Maybe a timestamp should be stored for each note and kept for a duration? | ||
if(cachedNotes.length > options.cacheLength) { | ||
if (cachedNotes.length > options.cacheLength) { | ||
cachedNotes.shift(); | ||
} | ||
} | ||
var handleTransmissionError = function(data) { | ||
}; | ||
var handleTransmissionError = function (data) { | ||
// Need to check message that errors | ||
@@ -212,8 +229,8 @@ // return failed notification to owner | ||
var errorCode = data[1]; | ||
var identifier = bytes2int(data.slice(2,6), 4); | ||
var identifier = bytes2int(data.slice(2, 6), 4); | ||
var note = undefined; | ||
while(cachedNotes.length) { | ||
while (cachedNotes.length) { | ||
note = cachedNotes.shift(); | ||
if(note['_uid'] == identifier) { | ||
if (note['_uid'] == identifier) { | ||
break; | ||
@@ -224,3 +241,3 @@ } | ||
// Notify callback of failed notification | ||
if(typeof options.errorCallback == 'function') { | ||
if (typeof options.errorCallback == 'function') { | ||
options.errorCallback(errorCode, note); | ||
@@ -230,3 +247,3 @@ } | ||
var count = cachedNotes.length; | ||
for(var i=0; i<count; i++) { | ||
for (var i = 0; i < count; i++) { | ||
note = cachedNotes.shift(); | ||
@@ -237,3 +254,3 @@ self.sendNotification(note); | ||
} | ||
} | ||
}; | ||
@@ -245,52 +262,55 @@ var Notification = function () { | ||
this.device; | ||
this.alert = undefined; | ||
this.badge = undefined; | ||
this.sound = undefined; | ||
} | ||
}; | ||
Notification.prototype.toJSON = function() { | ||
if(this.payload.aps === undefined) { | ||
Notification.prototype.toJSON = function () { | ||
if (this.payload === undefined) { | ||
this.payload = {}; | ||
} | ||
if (this.payload.aps === undefined) { | ||
this.payload.aps = {}; | ||
} | ||
if(typeof this.badge == 'number') { | ||
if (typeof this.badge == 'number') { | ||
this.payload.aps.badge = this.badge; | ||
} | ||
if(typeof this.sound == 'string') { | ||
if (typeof this.sound == 'string') { | ||
this.payload.aps.sound = this.sound; | ||
} | ||
if(typeof this.alert == 'string' || typeof this.alert == 'object') { | ||
if (typeof this.alert == 'string' || typeof this.alert == 'object') { | ||
this.payload.aps.alert = this.alert; | ||
} | ||
return this.payload; | ||
} | ||
}; | ||
var Device = function (/* deviceToken, ascii=true */) { | ||
var self = this; | ||
self.token = undefined; | ||
if(arguments.length > 0) { | ||
if (arguments.length > 0) { | ||
self.setToken.apply(self, arguments); | ||
} | ||
} | ||
}; | ||
Device.prototype.parseToken = function (token) { | ||
token = token.replace(/\s/g, ""); | ||
length = Math.ceil(token.length / 2); | ||
hexToken = new Buffer(length); | ||
for(var i=0; i < token.length; i+=2) { | ||
word = token[i]; | ||
if((i + 1) >= token.length || typeof(token[i+1]) === undefined) { | ||
var length = Math.ceil(token.length / 2); | ||
var hexToken = new Buffer(length); | ||
for (var i = 0; i < token.length; i += 2) { | ||
var word = token[i]; | ||
if ((i + 1) >= token.length || typeof(token[i + 1]) === undefined) { | ||
word += '0'; | ||
} | ||
else { | ||
word += token[i+1]; | ||
word += token[i + 1]; | ||
} | ||
hexToken[i/2] = parseInt(word, 16); | ||
hexToken[i / 2] = parseInt(word, 16); | ||
} | ||
return hexToken; | ||
} | ||
}; | ||
Device.prototype.setToken = function (newToken, ascii) { | ||
if(ascii === undefined || ascii == true) { | ||
if (ascii === undefined || ascii == true) { | ||
newToken = this.parseToken(newToken); | ||
@@ -300,7 +320,8 @@ } | ||
return this; | ||
} | ||
}; | ||
Device.prototype.hexToken = function () { | ||
var out = [], | ||
len = this.token.length; | ||
var out = []; | ||
var len = this.token.length; | ||
var n; | ||
for (var i = 0; i < len; i++) { | ||
@@ -312,21 +333,25 @@ n = this.token[i]; | ||
return out.join(""); | ||
} | ||
}; | ||
var Feedback = function (optionArgs) { | ||
var self = this; | ||
var hasKey = hasCert = false; | ||
var socketOptions = {} | ||
var responsePacketLength = 38; | ||
var hasKey = false; | ||
var hasCert = false; | ||
var socketOptions = {}; | ||
var responsePacketLength = 38; | ||
var readBuffer = new Buffer(responsePacketLength); | ||
var readLength = 0; | ||
var options = { cert: 'cert.pem' /* Certificate file */ | ||
, key: 'key.pem' /* Key file */ | ||
, address: 'feedback.push.apple.com' /* feedback address */ | ||
, port: 2196 /* feedback port */ | ||
, feedback: false /* enable feedback service, set to callback */ | ||
, interval: 3600 /* interval in seconds to connect to feedback service */ | ||
}; | ||
var options = { | ||
cert: 'cert.pem', /* Certificate file */ | ||
certData: '', /* Certificate data */ | ||
key: 'key.pem', /* Key file */ | ||
keyData: '', /* Key data */ | ||
address: 'feedback.push.apple.com', /* feedback address */ | ||
port: 2196, /* feedback port */ | ||
feedback: false, /* enable feedback service, set to callback */ | ||
interval: 3600, /* interval in seconds to connect to feedback service */ | ||
}; | ||
if (optionArgs) { | ||
@@ -339,47 +364,74 @@ var keys = Object.keys(options); | ||
} | ||
if(typeof options['feedback'] != 'function') { | ||
if (typeof options['feedback'] != 'function') { | ||
return Error(-1, 'A callback function must be specified'); | ||
} | ||
this.readyToConnect = function () { | ||
return (hasKey && hasCert); | ||
} | ||
}; | ||
self.startSocket = function () { | ||
self.socket = tls.connect(options['port'], options['address'], socketOptions); | ||
self.socket.pair.on('secure', function () { if(!self.socket.authorized) { throw self.socket.authorizationError } }); | ||
self.socket.on('data', function(data) { processData(data); }); | ||
self.socket.once('error', function(data) {self.socket.removeAllListeners(); self.socket = undefined; }); | ||
self.socket.once('end', function () { }); | ||
self.socket.once('close', function () { self.socket.removeAllListeners(); self.socket = undefined; }); | ||
self.socket.pair.on('secure', function () { | ||
if (!self.socket.authorized) { | ||
throw self.socket.authorizationError; | ||
} | ||
}); | ||
self.socket.on('data', function (data) { | ||
processData(data); | ||
}); | ||
self.socket.once('error', function () { | ||
self.socket.removeAllListeners(); | ||
self.socket = undefined; | ||
}); | ||
self.socket.once('end', function () { | ||
}); | ||
self.socket.once('close', function () { | ||
self.socket.removeAllListeners(); | ||
self.socket = undefined; | ||
}); | ||
}; | ||
var connect = invoke_after(function () { | ||
self.startSocket(); | ||
}); | ||
if (options['certData']) { | ||
socketOptions['cert'] = options['certData']; | ||
hasCert = true; | ||
} else { | ||
fs.readFile(options['cert'], connect(function (err, data) { | ||
if (err) { | ||
throw err; | ||
} | ||
socketOptions['cert'] = data.toString(); | ||
hasCert = true; | ||
})); | ||
} | ||
var connect = invoke_after(function() { self.startSocket(); }); | ||
fs.readFile(options['cert'], connect(function(err, data) { | ||
if(err) { | ||
throw err; | ||
} | ||
socketOptions['cert'] = data.toString(); | ||
hasCert = true; | ||
})); | ||
fs.readFile(options['key'], connect(function(err, data) { | ||
if(err) { | ||
throw err; | ||
} | ||
socketOptions['key'] = data.toString(); | ||
if (options['keyData']) { | ||
socketOptions['key'] = options['keyData']; | ||
hasKey = true; | ||
})); | ||
if(options['interval'] > 0) { | ||
this.interval = setInterval(function() { self.request(); }, options['interval'] * 1000); | ||
} else { | ||
fs.readFile(options['key'], connect(function (err, data) { | ||
if (err) { | ||
throw err; | ||
} | ||
socketOptions['key'] = data.toString(); | ||
hasKey = true; | ||
})); | ||
} | ||
var processData = function(data) { | ||
if (options['interval'] > 0) { | ||
this.interval = setInterval(function () { | ||
self.request(); | ||
}, options['interval'] * 1000); | ||
} | ||
var processData = function (data) { | ||
var pos = 0; | ||
// If there is some buffered data, read the remainder and process this first. | ||
if(readLength > 0) { | ||
if(data.length < (responsePacketLength - readLength)) { | ||
if (readLength > 0) { | ||
if (data.length < (responsePacketLength - readLength)) { | ||
data.copy(readBuffer, readLength, 0); | ||
@@ -389,9 +441,9 @@ readLength += data.length; | ||
} | ||
data.copy(readBuffer, readLength, 0, responsePacketLength-readLength); | ||
data.copy(readBuffer, readLength, 0, responsePacketLength - readLength); | ||
decodeResponse(readBuffer, 0); | ||
pos = responsePacketLength-readLength; | ||
pos = responsePacketLength - readLength; | ||
readLength = 0; | ||
} | ||
while(pos<data.length-1) { | ||
if((data.length-pos) < responsePacketLength) { | ||
while (pos < data.length - 1) { | ||
if ((data.length - pos) < responsePacketLength) { | ||
//Buffer remaining data until next time | ||
@@ -405,45 +457,45 @@ data.copy(readBuffer, 0, pos); | ||
} | ||
} | ||
}; | ||
var decodeResponse = function(data, start) { | ||
time = bytes2int(data, 4, start); | ||
var decodeResponse = function (data, start) { | ||
var time = bytes2int(data, 4, start); | ||
start += 4; | ||
len = bytes2int(data, 2, start); | ||
var len = bytes2int(data, 2, start); | ||
start += 2; | ||
tok = new Buffer(len); | ||
data.copy(tok, 0, start, start+len); | ||
if(typeof options['feedback'] == 'function') { | ||
options['feedback'](time, new exports.device(tok, false)); | ||
var tok = new Buffer(len); | ||
data.copy(tok, 0, start, start + len); | ||
if (typeof options['feedback'] == 'function') { | ||
options['feedback'](time, new exports.Device(tok, false)); | ||
} | ||
} | ||
} | ||
}; | ||
Feedback.prototype.request = function () { | ||
if((this.socket === undefined || this.socket.readyState == 'closed') && this.readyToConnect()) { | ||
if ((this.socket === undefined || this.socket.readyState == 'closed') && this.readyToConnect()) { | ||
this.startSocket(); | ||
} | ||
} | ||
}; | ||
Feedback.prototype.cancel = function () { | ||
if(this.interval !== undefined) { | ||
if (this.interval !== undefined) { | ||
clearInterval(this.interval); | ||
} | ||
} | ||
}; | ||
function int2buf(number, buffer, start, length) { | ||
function int2buf (number, buffer, start, length) { | ||
length -= 1; | ||
for(var i=0; i<=length; i++) { | ||
buffer[start+length-i] = number & 0xff; | ||
for (var i = 0; i <= length; i++) { | ||
buffer[start + length - i] = number & 0xff; | ||
number = number >> 8; | ||
} | ||
return length+1; | ||
return length + 1; | ||
} | ||
function bytes2int(bytes, length, start) { | ||
if(start === undefined) start = 0; | ||
function bytes2int (bytes, length, start) { | ||
if (start === undefined) start = 0; | ||
var num = 0; | ||
length -= 1; | ||
for(var i=0; i<=length; i++) { | ||
num += (bytes[start+i] << ((length - i) * 8)); | ||
for (var i = 0; i <= length; i++) { | ||
num += (bytes[start + i] << ((length - i) * 8)); | ||
} | ||
@@ -453,9 +505,9 @@ return num; | ||
function invoke_after(callback) { | ||
function invoke_after (callback) { | ||
var n = 0; | ||
return function (delegate) { | ||
n++; | ||
return function() { | ||
return function () { | ||
delegate.apply(delegate, arguments); | ||
if(--n == 0) callback(); | ||
if (--n == 0) callback(); | ||
}; | ||
@@ -465,6 +517,11 @@ }; | ||
exports.Connection = Connection; | ||
exports.connection = Connection; | ||
exports.Notification = Notification; | ||
exports.notification = Notification; | ||
exports.Device = Device; | ||
exports.device = Device; | ||
exports.Feedback = Feedback; | ||
exports.feedback = Feedback; | ||
exports.error = Errors; | ||
exports.error = Errors; | ||
{ | ||
"name": "apn", | ||
"description": "An interface to the Apple Push Notification service for Node.js", | ||
"version": "1.1.4", | ||
"version": "1.1.5", | ||
"author": "Andrew Naylor <argon@mkbot.net>", | ||
@@ -6,0 +6,0 @@ "contributors": [ |
@@ -27,25 +27,35 @@ #node-apn | ||
var apns = require('apn'); | ||
### Exported Objects | ||
- Connection | ||
- Notification | ||
- Device | ||
- Feedback | ||
- errors | ||
### Connecting | ||
Create a new connection to the gateway server using a dictionary of options. The defaults are listed below: | ||
options = { cert: 'cert.pem' /* Certificate file */ | ||
, key: 'key.pem' /* Key file */ | ||
, gateway: 'gateway.push.apple.com' /* gateway address */ | ||
, port: 2195 /* gateway port */ | ||
, enhanced: true /* enable enhanced format */ | ||
, errorCallback: undefined /* Callback when error occurs */ | ||
, cacheLength: 5 /* Notifications to cache for error purposes */ | ||
}; | ||
var apnsConnection = new apns.connection(options); | ||
var options = { | ||
cert: 'cert.pem', /* Certificate file */ | ||
certData: null, /* Optional: if supplied uses this instead of Certificate File */ | ||
key: 'key.pem', /* Key file */ | ||
keyData: null, /* Optional: if supplied uses this instead of Key file */ | ||
gateway: 'gateway.push.apple.com',/* gateway address */ | ||
port: 2195, /* gateway port */ | ||
enhanced: true, /* enable enhanced format */ | ||
errorCallback: undefined, /* Callback when error occurs */ | ||
cacheLength: 5 /* Number of notifications to cache for error purposes */ | ||
}; | ||
var apnsConnection = new apns.Connection(options); | ||
### Sending a notification | ||
To send a notification first create a `Device` object. Pass it the device token as either a hexadecimal string, or alternatively as a `Buffer` object containing the binary token, setting the second argument to `false`. | ||
var myDevice = new apns.device(token /*, ascii=true*/); | ||
var myDevice = new apns.Device(token /*, ascii=true*/); | ||
Next create a notification object and set parameters. See the [payload documentation][pl] for more details | ||
var note = new apns.notification(); | ||
var note = new apns.Notification(); | ||
@@ -80,11 +90,14 @@ note.badge = 3; | ||
options = { cert: 'cert.pem' /* Certificate file */ | ||
, key: 'key.pem' /* Key file */ | ||
, address: 'feedback.push.apple.com' /* feedback address */ | ||
, port: 2196 /* feedback port */ | ||
, feedback: false /* callback function */ | ||
, interval: 3600 /* query interval in seconds */ | ||
}; | ||
var options = { | ||
cert: 'cert.pem', /* Certificate file */ | ||
certData: null, /* Certificate file contents */ | ||
key: 'key.pem', /* Key file */ | ||
keyData: null, /* Key file contents */ | ||
address: 'feedback.push.apple.com', /* feedback address */ | ||
port: 2196, /* feedback port */ | ||
feedback: false, /* enable feedback service, set to callback */ | ||
interval: 3600 /* interval in seconds to connect to feedback service */ | ||
}; | ||
var feedback = new apns.feedback(options); | ||
var feedback = new apns.Feedback(options); | ||
@@ -106,3 +119,3 @@ ## Converting your APNs Certificate | ||
Contributors: [Ian Babrou][bobrik], [dgthistle][dgthistle] | ||
Contributors: [Ian Babrou][bobrik], [dgthistle][dgthistle], [Keith Larsen][keithnlarsen], [Mike P][mypark] | ||
@@ -132,8 +145,18 @@ Special thanks to [Ben Noordhuis][bnoordhuis] for `invoke_after` code. | ||
[bnoordhuis]: http://bnoordhuis.nl | ||
[npm]: http://github.com/isaacs/npm | ||
[npm]: https://github.com/isaacs/npm | ||
[bobrik]: http://bobrik.name | ||
[dgthistle]: https://github.com/dgthistle | ||
[keithnlarsen]: https://github.com/keithnlarsen | ||
[mypark]: https://github.com/mypark | ||
## Changelog | ||
1.1.5: | ||
* Feature: Certificate and Key data can be passed directly when creating a new connection instead of providing a file name on disk. (See: `certData` and `keyData` options) | ||
* Deliver whole write buffer if the socket is ready. | ||
* Fixed some global memory leaks. | ||
* Tidied up some code formatting glitches flagged by jslint | ||
* Fixes #16, #17, #18, #19, #20 | ||
1.1.4: | ||
@@ -182,2 +205,2 @@ | ||
* Well I created a module; Version 0.0.0 had no code, and now it does, and it works, so that's pretty neat, right? | ||
* Well I created a module; Version 0.0.0 had no code, and now it does, and it works, so that's pretty neat, right? |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22547
443
202