Comparing version 1.1.0 to 1.2.0
@@ -23,3 +23,13 @@ 'use strict'; | ||
ATTRIBUTES_UPDATED: 'attributesUpdated', | ||
ATTRIBUTES_UPDATE_FAILED: 'attributesUpdateFailed' | ||
ATTRIBUTES_UPDATE_FAILED: 'attributesUpdateFailed', | ||
TOPIC_CREATED: 'topicCreated', | ||
CREATE_TOPIC_FAILED: 'createTopicFailed', | ||
TOPIC_DELETED: 'topicDeleted', | ||
DELETE_TOPIC_FAILED: 'deleteTopicFailed', | ||
SUBSCRIBED: 'subscribed', | ||
SUBSCRIBE_FAILED: 'subscribeFailed', | ||
UNSUBSCRIBED: 'unsubscribed', | ||
UNSUBSCRIBE_FAILED: 'unsubscribeFailed', | ||
PUBLISH_FAILED: 'publishFailed', | ||
PUBLISHED_MESSAGE: 'publishedMessage' | ||
}; | ||
@@ -176,3 +186,3 @@ | ||
}; | ||
this.sns.setEndpointAttributes(params, function(err, res) { | ||
this.sns.setEndpointAttributes(params, function(err) { | ||
if (!err) { | ||
@@ -336,4 +346,245 @@ self.emit(EMITTED_EVENTS.ATTRIBUTES_UPDATED, endpointArn, attributes); | ||
/** | ||
* Send a message to an Android or iOS device. | ||
* Create a topic. | ||
* @param {String} name | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.createTopic = function(name, callback) { | ||
var params = { | ||
Name: name | ||
}; | ||
var self = this; | ||
self.sns.createTopic(params, function(err, res) { | ||
if (err) { | ||
self.emit(EMITTED_EVENTS.CREATE_TOPIC_FAILED, name, err); | ||
return callback(err); | ||
} | ||
if (!res || !res.TopicArn) { | ||
return callback(new Error('Response or TopicArn is null')); | ||
} | ||
self.emit(EMITTED_EVENTS.TOPIC_CREATED, res.TopicArn, name); | ||
callback(null, res.TopicArn); | ||
}); | ||
}; | ||
/** | ||
* Delete a topic. | ||
* @param {String} topicArn | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.deleteTopic = function(topicArn, callback) { | ||
var params = { | ||
TopicArn: topicArn | ||
}; | ||
var self = this; | ||
self.sns.deleteTopic(params, function(err) { | ||
if (err) { | ||
self.emit(EMITTED_EVENTS.DELETE_TOPIC_FAILED, topicArn, err); | ||
return callback(err); | ||
} | ||
self.emit(EMITTED_EVENTS.TOPIC_DELETED, topicArn); | ||
callback(); | ||
}); | ||
}; | ||
/** | ||
* Get all topics for this account. | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.getTopics = function(callback) { | ||
var topics = []; | ||
var self = this; | ||
var nextToken; | ||
async.doWhilst(function(next) { | ||
self._getTopics(nextToken, function(err, res) { | ||
if (err) { | ||
return next(err); | ||
} | ||
nextToken = res.NextToken; | ||
if (res.Topics && res.Topics.length) { | ||
topics = topics.concat(res.Topics); | ||
} | ||
next(); | ||
}); | ||
}, function() { | ||
return !!nextToken; | ||
}, function(err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
callback(null, topics); | ||
}); | ||
}; | ||
/** | ||
* Get topics with the given NextToken (for paging). | ||
* @param {String} nextToken | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype._getTopics = function(nextToken, callback) { | ||
var params = {}; | ||
if (nextToken) { | ||
params.NextToken = nextToken; | ||
} | ||
this.sns.listTopics(params, callback); | ||
}; | ||
/** | ||
* Get subscriptions; either all subscriptions for this account or those for the topic ARN specified. | ||
* @param {String} topicArn | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.getSubscriptions = function(topicArn, callback) { | ||
if (typeof topicArn === 'function') { | ||
callback = topicArn; | ||
topicArn = undefined; | ||
} | ||
var subscriptions = []; | ||
var self = this; | ||
var nextToken; | ||
async.doWhilst(function(next) { | ||
self._getSubscriptions(nextToken, topicArn, function(err, res) { | ||
if (err) { | ||
return next(err); | ||
} | ||
nextToken = res.NextToken; | ||
if (res.Subscriptions && res.Subscriptions.length) { | ||
subscriptions = subscriptions.concat(res.Subscriptions); | ||
} | ||
next(); | ||
}); | ||
}, function() { | ||
return !!nextToken; | ||
}, function(err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
callback(null, subscriptions); | ||
}); | ||
}; | ||
/** | ||
* Get subscriptions with the given NextToken (for paging). | ||
* @param {String} nextToken | ||
* @param {String} topicArn | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype._getSubscriptions = function(nextToken, topicArn, callback) { | ||
var params = {}; | ||
if (nextToken) { | ||
params.NextToken = nextToken; | ||
} | ||
if (!topicArn) { | ||
this.sns.listSubscriptions(params, callback); | ||
return; | ||
} | ||
params.TopicArn = topicArn; | ||
this.sns.listSubscriptionsByTopic(params, callback); | ||
}; | ||
/** | ||
* Subscribe an endpoint to a topic. | ||
* @param {String} endpointArn | ||
* @param {String} topicArn | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.subscribe = function(endpointArn, topicArn, callback) { | ||
var params = { | ||
Endpoint: endpointArn, | ||
TopicArn: topicArn, | ||
Protocol: 'application' | ||
}; | ||
var self = this; | ||
self.sns.subscribe(params, function(err, res) { | ||
if (err) { | ||
self.emit(EMITTED_EVENTS.SUBSCRIBE_FAILED, endpointArn, topicArn, err); | ||
return callback(err); | ||
} | ||
if (!res || !res.SubscriptionArn) { | ||
return callback(new Error('Response or SubscriptionArn is null')); | ||
} | ||
self.emit(EMITTED_EVENTS.SUBSCRIBED, res.SubscriptionArn, endpointArn, topicArn); | ||
callback(null, res.SubscriptionArn); | ||
}); | ||
}; | ||
/** | ||
* Unsubscribe an endpoint from a topic via its SubscriptionArn. | ||
* @param {String} subscriptionArn | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.unsubscribe = function(subscriptionArn, callback) { | ||
var params = { | ||
SubscriptionArn: subscriptionArn | ||
}; | ||
var self = this; | ||
self.sns.unsubscribe(params, function(err) { | ||
if (err) { | ||
self.emit(EMITTED_EVENTS.UNSUBSCRIBE_FAILED, subscriptionArn, err); | ||
return callback(err); | ||
} | ||
self.emit(EMITTED_EVENTS.UNSUBSCRIBED, subscriptionArn); | ||
callback(); | ||
}); | ||
}; | ||
/** | ||
* Publish a message to a topic identified by its topic ARN. | ||
* Message is JSON object. | ||
* @param {String} topicArn | ||
* @param {Object} message | ||
* @param {Function} callback | ||
*/ | ||
Interface.prototype.publishToTopic = function(topicArn, message, callback) { | ||
if (!validateMessageStructure(message)) { | ||
return callback(new Error('Argument "message" must be in SNS multi-platform publishing format.')); | ||
} | ||
var self = this; | ||
self.sns.publish({ | ||
Message: JSON.stringify(message), | ||
TopicArn: topicArn, | ||
MessageStructure: 'json', | ||
}, function(err, res) { | ||
if (err) { | ||
self.emit(EMITTED_EVENTS.PUBLISH_FAILED, topicArn, err); | ||
return callback(err); | ||
} | ||
if (!res || !res.MessageId) { | ||
return callback(new Error('Response or MessageId is null')); | ||
} | ||
self.emit(EMITTED_EVENTS.PUBLISHED_MESSAGE, topicArn, res.MessageId); | ||
callback(null, res.MessageId); | ||
}); | ||
function validateMessageStructure(message) { | ||
if (!message.default) { | ||
return false; | ||
} | ||
if (typeof message.default !== 'string') { | ||
return false; | ||
} | ||
return true; | ||
} | ||
}; | ||
/** | ||
* Send a message to an Android or iOS device identified by its Endpoint ARN. | ||
* Message is JSON object. | ||
* @param {String} endpointArn | ||
@@ -475,4 +726,4 @@ * @param {Object} message | ||
} | ||
} else if (message != null && typeof message === 'object') { | ||
if(message.GCM || message.ADM || message.default) { | ||
} else if (message && typeof message === 'object') { | ||
if (message.GCM || message.ADM || message.default) { | ||
callback(null, message); | ||
@@ -479,0 +730,0 @@ } else { |
{ | ||
"name": "sns-mobile", | ||
"version": "1.1.0", | ||
"description": "Push notifications to Android, Kindle Fire, and iOS devices easily using this module.", | ||
"version": "1.2.0", | ||
"description": "Send push notifications to Android, Kindle Fire, and iOS devices easily.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha test/" | ||
"test": "mocha test/", | ||
"test-debug": "mocha --debug-brk test/" | ||
}, | ||
@@ -21,3 +22,7 @@ "bugs": { | ||
"gcm", | ||
"kindle", | ||
"fire", | ||
"push", | ||
"ios", | ||
"android", | ||
"notification", | ||
@@ -28,3 +33,3 @@ "simple notification service" | ||
"async": "0.2.10", | ||
"aws-sdk": "2.0.0-rc3" | ||
"aws-sdk": "2.1.5" | ||
}, | ||
@@ -31,0 +36,0 @@ "devDependencies": { |
122
README.md
@@ -8,2 +8,8 @@ # Important Update | ||
## Installation | ||
```sh | ||
npm install sns-mobile | ||
``` | ||
## Setting Up a User for SNS | ||
@@ -36,3 +42,3 @@ To use Amazon SNS you need a Secret Access Key and an Access Key Id. Getting these will require you to create a user under the IAM section of AWS and attach a User Policy with the following Policy Document: | ||
```javascript | ||
var SNS = require('sns-push-mobile'), | ||
var SNS = require('sns-mobile'), | ||
EVENTS = SNS.EVENTS; | ||
@@ -74,2 +80,8 @@ | ||
## Running Tests | ||
```sh | ||
SNS_KEY_ID="<your_sns_key_id>" SNS_ACCESS_KEY="<your_sns_access_key>" SNS_ANDROID_ARN="arn:aws:sns:..." npm test | ||
``` | ||
## Events Emitted | ||
@@ -91,2 +103,12 @@ Instances created will emit events as listed below and callbacks should have the shown format. Event strings can be accessed as shown below. | ||
// EVENTS.ATTRIBUTES_UPDATED | ||
// EVENTS.TOPIC_CREATED | ||
// EVENTS.CREATE_TOPIC_FAILED | ||
// EVENTS.TOPIC_DELETED | ||
// EVENTS.DELETE_TOPIC_FAILED | ||
// EVENTS.SUBSCRIBED | ||
// EVENTS.SUBSCRIBE_FAILED | ||
// EVENTS.UNSUBSCRIBED | ||
// EVENTS.UNSUBSCRIBE_FAILED | ||
// EVENTS.PUBLISH_FAILED | ||
// EVENTS.PUBLISHED_MESSAGE | ||
@@ -162,3 +184,62 @@ var myApp = new SNS({ | ||
#### topicCreated | ||
``` | ||
function (topicArn, topicName) {} | ||
``` | ||
Emitted when a topic has been created successfully. | ||
#### createTopicFailed | ||
``` | ||
function (topicName) {} | ||
``` | ||
Emitted when an attempt to create a topic has failed. | ||
#### topicDeleted | ||
``` | ||
function (topicArn) {} | ||
``` | ||
Emitted when a topic has been deleted successfully. | ||
#### deleteTopicFailed | ||
``` | ||
function (topicArn) {} | ||
``` | ||
Emitted when an attempt to delete a topic has failed. | ||
#### subscribed | ||
``` | ||
function (subscriptionArn, endpointArn, topicArn) {} | ||
``` | ||
Emitted when an endpoint has been subscribed to a topic successfully. | ||
#### subscribeFailed | ||
``` | ||
function (endpointArn, topicArn, err) {} | ||
``` | ||
Emitted when an attempt to subscribe an endpoint to a topic has failed. | ||
#### unsubscribed | ||
``` | ||
function (subscriptionArn) {} | ||
``` | ||
Emitted when an endpoint has been unsubscribed from a topic successfully. | ||
#### unsubscribeFailed | ||
``` | ||
function (subscriptionArn, err) {} | ||
``` | ||
Emitted when an attempt to unsubscribe an endpoint from a topic has failed. | ||
#### publishedMessage | ||
``` | ||
function (topicArn, messageId) {} | ||
``` | ||
Emitted when a message has been published to a topic successfully. | ||
#### publishFailed | ||
``` | ||
function (topicArn, err) {} | ||
``` | ||
Emitted when an attempt to publish a message to a topic has failed. | ||
## API | ||
@@ -201,5 +282,14 @@ | ||
#### getTopics(callback) | ||
Get all topics by paging through them. The callback(err, topics) receives an Array containing topic objects. | ||
#### getSubscriptions(topicArn, callback) | ||
Get all subscriptions for the topic with the given topicArn by paging through them. The callback(err, subscriptions) receives an Array containing subscription objects. | ||
#### addUser(deviceToken, [data], callback) | ||
Add a device/user to SNS with optional extra data. Callback has format fn(err, endpointArn). | ||
#### createTopic(name, callback) | ||
Create a new topic with the given name. The callback has the format fn(err, topicArn). | ||
#### setAttributes(endpointArn, attributes, callback) | ||
@@ -217,5 +307,28 @@ Update an existing endpoint's attributes. Attributes is an object with the following optional properties: | ||
#### deleteTopic(topicArn, callback) | ||
Delete the topic with the given topicArn. The callback has the format fn(err). | ||
#### sendMessage(endpointArn, message, callback) | ||
Send a message to a user. The _message_ parameter can be a String, or an Object with the formats below. The callback format is callback(err, messageId). | ||
#### subscribe(endpointArn, topicArn, callback) | ||
Subscribe an endpoint to a topic. The callback has the format fn(err, subscriptionArn). | ||
#### unsubscribe(subscriptionArn, callback) | ||
Unsubscribe an endpoint from a topic via the given subscriptionArn. The callback has the format fn(err). | ||
#### publishToTopic(topicArn, message, callback) | ||
Publish a message a topic. The callback has the format fn(err, messageId). | ||
Please note that the message *must* be in the final Amazon SNS format as | ||
specified [here](http://docs.aws.amazon.com/sns/latest/dg/mobile-push-send-custommessage.html#mobile-push-send-multiplatform), i.e. it | ||
*must* contain a key called `default` and platform-specific messages *must* already be JSON-stringified. Example: | ||
```json | ||
{ | ||
"default": "Default message which must be present when publishing a message to a topic. Will only be used if a message is not present for one of the notification platforms.", | ||
"APNS": "{\"aps\":{\"alert\": \"Check out these awesome deals!\",\"url\":\"www.amazon.com\"} }", | ||
"GCM":"{\"data\":{\"message\":\"Check out these awesome deals!\",\"url\":\"www.amazon.com\"}}" | ||
} | ||
``` | ||
You can read about Amazon SNS message formats [here](http://docs.aws.amazon.com/sns/latest/dg/mobile-push-send-custommessage.html). | ||
@@ -225,3 +338,3 @@ | ||
``` | ||
```js | ||
{ | ||
@@ -238,3 +351,3 @@ aps: { | ||
``` | ||
```js | ||
{ | ||
@@ -258,5 +371,6 @@ data: { | ||
Thanks to these peeps for contributions: | ||
Thanks to these awesome folks for contributions: | ||
* [abiskop](https://github.com/abiskop) | ||
* [iclems](https://github.com/iclems) | ||
* [race](https://github.com/race) |
console.warn('Ensure that SNS_ACCESS_KEY, SNS_KEY_ID and SNS_ANDROID_ARN env vars are set for these tests!\n'); | ||
var assert = require('assert'), | ||
SNS = require('../lib/Interface'); | ||
SNS = require('../lib/interface'); | ||
@@ -20,2 +20,5 @@ var SNS_KEY_ID = process.env['SNS_KEY_ID'], | ||
var theTopicArnThatThisTestCreated; | ||
var theSubscriptionArnThatThisTestCreated; | ||
it('Should have events and supported platforms exposed on the interface', function() { | ||
@@ -31,4 +34,4 @@ assert(SNS.SUPPORTED_PLATFORMS); | ||
apiVersion: '2010-03-31', | ||
accessKeyId: SNS_ACCESS_KEY, | ||
secretAccessKey: SNS_KEY_ID, | ||
accessKeyId: SNS_KEY_ID, | ||
secretAccessKey: SNS_ACCESS_KEY, | ||
platformApplicationArn: ANDROID_ARN | ||
@@ -45,4 +48,4 @@ }); | ||
apiVersion: '2010-03-31', | ||
accessKeyId: SNS_ACCESS_KEY, | ||
secretAccessKey: SNS_KEY_ID, | ||
accessKeyId: SNS_KEY_ID, | ||
secretAccessKey: SNS_ACCESS_KEY, | ||
platformApplicationArn: ANDROID_ARN | ||
@@ -63,4 +66,4 @@ }); | ||
apiVersion: '2010-03-31', | ||
accessKeyId: SNS_ACCESS_KEY, | ||
secretAccessKey: SNS_KEY_ID, | ||
accessKeyId: SNS_KEY_ID, | ||
secretAccessKey: SNS_ACCESS_KEY, | ||
platformApplicationArn: ANDROID_ARN | ||
@@ -189,3 +192,3 @@ }); | ||
assert(res); | ||
assert(typeof res === 'string'); | ||
assert.strictEqual(typeof res, 'string'); | ||
done(); | ||
@@ -195,2 +198,122 @@ }) | ||
}); | ||
it('Should create a topic.', function(done) { | ||
sns.createTopic('this_is_a_test_dummy_486438735', function(err, topicArn) { | ||
assert(!err); | ||
assert(topicArn); | ||
theTopicArnThatThisTestCreated = topicArn; | ||
done(); | ||
}); | ||
}); | ||
it('Should list topics.', function(done) { | ||
sns.getTopics(function(err, topics) { | ||
assert(!err); | ||
assert(topics); | ||
assert(topics.length > 0); | ||
var theTopicThatWasCreatedEarlier = topics.filter(function(topic) { | ||
return topic.TopicArn === theTopicArnThatThisTestCreated; | ||
})[0]; | ||
assert(theTopicThatWasCreatedEarlier) | ||
done(); | ||
}); | ||
}); | ||
it('Should subscribe a user to a topic.', function(done) { | ||
sns.addUser('yetanotherfakedeviceid', JSON.stringify({ | ||
username: 'anotherfakeuser' | ||
}), function(err, endpointArn) { | ||
sns.subscribe(endpointArn, theTopicArnThatThisTestCreated, function(err, subscriptionArn) { | ||
assert(!err); | ||
assert(subscriptionArn); | ||
theSubscriptionArnThatThisTestCreated = subscriptionArn; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('Should list all subscriptions.', function(done) { | ||
sns.getSubscriptions(function(err, subscriptions) { | ||
assert(!err); | ||
assert(subscriptions); | ||
assert(subscriptions.length > 0); | ||
var theSubscriptionThatWasCreatedEarlier = subscriptions.filter(function(subscription) { | ||
return subscription.SubscriptionArn === theSubscriptionArnThatThisTestCreated; | ||
})[0]; | ||
assert(theSubscriptionThatWasCreatedEarlier) | ||
done(); | ||
}); | ||
}); | ||
it('Should list subscriptions by topic.', function(done) { | ||
sns.getSubscriptions(theTopicArnThatThisTestCreated, function(err, subscriptions) { | ||
assert(!err); | ||
assert(subscriptions); | ||
assert(subscriptions.length > 0); | ||
var theSubscriptionThatWasCreatedEarlier = subscriptions.filter(function(subscription) { | ||
return subscription.SubscriptionArn === theSubscriptionArnThatThisTestCreated; | ||
})[0]; | ||
assert(theSubscriptionThatWasCreatedEarlier) | ||
done(); | ||
}); | ||
}); | ||
it('Should fail to publish a message to a topic if message body is missing "default" key.', function(done) { | ||
sns.publishToTopic(theTopicArnThatThisTestCreated, { | ||
foo: 'missing top-level key "default"' | ||
}, function(err, res) { | ||
assert(err); | ||
assert.equal(err.message, 'Argument "message" must be in SNS multi-platform publishing format.'); | ||
done(); | ||
}) | ||
}); | ||
it('Should fail to publish a message to a topic if "default" value in message body is not a string.', function(done) { | ||
sns.publishToTopic(theTopicArnThatThisTestCreated, { | ||
'default': { | ||
that: 'is_not_a_string_but_an_object' | ||
} | ||
}, function(err, res) { | ||
assert(err); | ||
assert.equal(err.message, 'Argument "message" must be in SNS multi-platform publishing format.'); | ||
done(); | ||
}) | ||
}); | ||
it('Should publish a message to a topic.', function(done) { | ||
var messageBody = { | ||
'default': JSON.stringify({ | ||
data: 'Hello Topic', | ||
moreData: 'Hello Topic' | ||
}), | ||
'APNS': JSON.stringify({ | ||
aps: { | ||
alert: 'test dummy text' | ||
} | ||
}), | ||
'GCM': JSON.stringify({ | ||
some: 'value' | ||
}) | ||
} | ||
sns.publishToTopic(theTopicArnThatThisTestCreated, messageBody, function(err, res) { | ||
assert(!err); | ||
assert(res); | ||
assert.strictEqual(typeof res, 'string'); | ||
done(); | ||
}) | ||
}); | ||
it('Should unsubscribe a user from a topic.', function(done) { | ||
sns.unsubscribe(theSubscriptionArnThatThisTestCreated, function(err) { | ||
assert(!err); | ||
done(); | ||
}); | ||
}); | ||
it('Should delete a topic.', function(done) { | ||
sns.deleteTopic(theTopicArnThatThisTestCreated, function(err) { | ||
assert(!err); | ||
done(); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
41606
941
367
+ Addedaws-sdk@2.1.5(transitive)
+ Addedsax@0.4.2(transitive)
+ Addedxml2js@0.2.6(transitive)
- Removedaws-sdk@2.0.0-rc3(transitive)
- Removedsax@1.4.1(transitive)
- Removedxml2js@0.2.4(transitive)
Updatedaws-sdk@2.1.5