├── .npmignore ├── .gitignore ├── lib ├── index.js └── sns.js ├── Gruntfile.js ├── tests ├── index.js └── event.json ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('lambda:push-notifications'), 2 | factory = require('./sns'); 3 | 4 | module.exports = function(options) { 5 | var handler = factory(options); 6 | 7 | return function(event, done) { 8 | var message; 9 | 10 | try { 11 | message = JSON.parse(event.Records[0].Sns.Message); 12 | } 13 | catch(e) { 14 | return done(e); 15 | } 16 | 17 | debug('Received message:', message); 18 | 19 | handler(message, done); 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'), 2 | pkg = require('./package.json'); 3 | 4 | module.exports = function(grunt) { 5 | require('load-grunt-tasks')(grunt); 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | lambda_invoke: { 10 | default: { 11 | options: { 12 | 'file_name': './tests/index.js', 13 | 'handler': 'handler', 14 | 'event': './tests/event.json' 15 | } 16 | } 17 | } 18 | }); 19 | 20 | grunt.registerTask('default', []); 21 | grunt.registerTask('test', ['lambda_invoke']); 22 | }; 23 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | require('debug').enable("*:*"); 2 | 3 | var factory = require('./../lib'); 4 | 5 | exports.handler = function(event, context) { 6 | var handler = factory({ 7 | sns: { 8 | region: 'us-west-2' 9 | }, 10 | apns: { 11 | sandbox: false, 12 | application: 'arn:aws:sns:us-west-2:123:app/APNS/APNS_APP' 13 | }, 14 | gcm: { 15 | application: 'arn:aws:sns:us-west-2:123:app/GCM/GCM_APP' 16 | } 17 | }); 18 | 19 | handler(event, function(err) { 20 | if (err && err.code !== 'EndpointDisabled') { 21 | return context.done(err, 'Task failed.'); 22 | } 23 | 24 | context.succeed(); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-push-notifications", 3 | "version": "1.0.0", 4 | "description": "AWS Lambda-based push notifications.", 5 | "main": "lib", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/artema/lambda-push-notifications.git" 9 | }, 10 | "keywords": [ 11 | "AWS", 12 | "lambda", 13 | "SNS", 14 | "notifications" 15 | ], 16 | "author": "Artem Abashev ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/artema/lambda-push-notifications/issues" 20 | }, 21 | "homepage": "https://github.com/artema/lambda-push-notifications#readme", 22 | "dependencies": { 23 | "async": "^1.4.2", 24 | "aws-sdk": "^2.1.49", 25 | "debug": "^2.2.0" 26 | }, 27 | "bundledDependencies": [ 28 | "async", 29 | "debug" 30 | ], 31 | "devDependencies": { 32 | "grunt": "^0.4.5", 33 | "grunt-aws-lambda": "^0.10.0", 34 | "load-grunt-tasks": "^3.2.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "EventVersion": "1.0", 5 | "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", 6 | "EventSource": "aws:sns", 7 | "Sns": { 8 | "SignatureVersion": "1", 9 | "Timestamp": "1970-01-01T00:00:00.000Z", 10 | "Signature": "EXAMPLE", 11 | "SigningCertUrl": "EXAMPLE", 12 | "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", 13 | "Message": "{\"apns_recipients\":[\"3DF9405DFEFBC330EFD8E9A37E6DF67BD6DE64E9FEE432C7C16DEE06F1C3CBC2\"],\"gcm_recipients\":[\"APA91bGL_FUKgVQ56XZ5Y1p-ouy65l7uNAQovsDFHtynzaX_wL17O97UXyYuYO20fqAUanezvWnQTOL2cbBeAnFhYYC20edyqU4IlHmJ-mR-335l31pvAglwfpWX2BIurH0qi8OkoVk0nJhgM5TQGmP9D6h3q6SnLw\"],\"apns\":{\"alert\":\"Test\",\"badge\":1,\"sound\":\"default\"},\"gcm\":{\"text\":\"Test\"}}", 14 | "MessageAttributes": {}, 15 | "Type": "Notification", 16 | "UnsubscribeUrl": "EXAMPLE", 17 | "TopicArn": "arn:aws:sns:EXAMPLE", 18 | "Subject": "TestInvoke" 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Amazon Web Services Lambda based push notifications. Publish notifications to a single SNS topic and broadcast push notifications to APNS and GCM services from a Lambda function. 2 | 3 | # Installation 4 | 5 | ``` 6 | npm install lambda-push-notifications --save 7 | ``` 8 | 9 | # Usage 10 | 11 | Create a Lambda function with an SNS event source: 12 | 13 | ``` 14 | var factory = require('lambda-push-notifications'); 15 | 16 | exports.handler = function(event, context) { 17 | var handler = factory({ 18 | //Configure SNS for the AWS SDK for Node here 19 | sns: { 20 | region: 'us-west-2' 21 | }, 22 | //APNS application for push notifications 23 | apns: { 24 | sandbox: false, 25 | application: 'arn:aws:sns:us-west-2:123456789:app/APNS/APNS_APPLICATION' 26 | }, 27 | //GCM application for push notifications 28 | gcm: { 29 | application: 'arn:aws:sns:us-west-2:123456789:app/GCM/GCM_APPLICATION' 30 | } 31 | }); 32 | 33 | handler(event, function(err) { 34 | //Ignore EndpointDisabled errors 35 | if (err && err.code !== 'EndpointDisabled') { 36 | return context.done(err, 'Task failed.'); 37 | } 38 | 39 | context.succeed(); 40 | }); 41 | }; 42 | ``` 43 | 44 | Source SNS topic should receive notifications in the following format: 45 | 46 | ``` 47 | { 48 | apns: { alert: 'Test message', badge: 1, sound: 'default' } 49 | gcm: { text: 'Test message', data: 12345 } 50 | apns_recipients: ['APNS device token'], 51 | gcm_recipients: ['GCM device token'] 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /lib/sns.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'), 2 | async = require('async'), 3 | debug = require('debug')('lambda:push-notifications'); 4 | 5 | function sendMessage(sns, applicationArn, recipientToken, message, done) { 6 | sns.createPlatformEndpoint({ 7 | PlatformApplicationArn: applicationArn, 8 | Token: recipientToken 9 | }, function(err, data) { 10 | if (err) { 11 | debug('Unable to create a platform endpoint for application ' + 12 | applicationArn + ' with token ' + recipientToken); 13 | debug(err, err.stack); 14 | return done(err); 15 | } 16 | 17 | sns.publish({ 18 | TargetArn: data.EndpointArn, 19 | Message: message, 20 | MessageStructure: 'json' 21 | }, function(err) { 22 | if (err) { 23 | debug('Unable to publish to ' + data.EndpointArn); 24 | debug(err, err.stack); 25 | return done(err); 26 | } 27 | 28 | done(); 29 | }); 30 | }); 31 | } 32 | 33 | function taskFactory(sns, options, message) { 34 | var tasks = []; 35 | 36 | if (message.gcm && message.gcm_recipients && message.gcm_recipients.length > 0) { 37 | var gcmPayload = JSON.stringify({ 38 | 'default': '', 39 | 'GCM': JSON.stringify({ data: message.gcm }) 40 | }); 41 | 42 | debug('Sending notifications to ' + message.gcm_recipients.length + ' GCM endpoints...'); 43 | 44 | message.gcm_recipients.forEach(function(recipient) { 45 | tasks.push(function(next) { 46 | sendMessage(sns, options.gcm.application, recipient, gcmPayload, function(err) { 47 | if (err) { 48 | debug('Unable to send GCM message.', err); 49 | return next(err); 50 | } 51 | 52 | next(); 53 | }); 54 | }); 55 | }); 56 | } 57 | 58 | if (message.apns && message.apns_recipients && message.apns_recipients.length > 0) { 59 | var apnsKey = options.apns.sandbox ? 'APNS_SANDBOX' : 'APNS'; 60 | var apnsPayload = { 'default': '' }; 61 | 62 | apnsPayload[apnsKey] = JSON.stringify({ aps: message.apns }); 63 | apnsPayload = JSON.stringify(apnsPayload); 64 | 65 | debug('Sending notifications to ' + message.apns_recipients.length + ' APNS endpoints...'); 66 | 67 | message.apns_recipients.forEach(function(recipient) { 68 | tasks.push(function(next) { 69 | sendMessage(sns, options.apns.application, recipient, apnsPayload, function(err) { 70 | if (err) { 71 | debug('Unable to send APNS message.', err); 72 | return next(err); 73 | } 74 | 75 | next(); 76 | }); 77 | }); 78 | }); 79 | } 80 | 81 | return tasks; 82 | } 83 | 84 | module.exports = function(options) { 85 | var sns = new AWS.SNS(options.sns); 86 | 87 | return function(message, done) { 88 | var tasks = taskFactory(sns, options, message); 89 | 90 | async.parallel(tasks, function(err) { 91 | debug('Completed ' + tasks.length + ' tasks.'); 92 | done(err); 93 | }); 94 | }; 95 | }; 96 | --------------------------------------------------------------------------------