From be992d7debc0f891436ed5ae63dfa4e1fa2b7999 Mon Sep 17 00:00:00 2001 From: juliausti Date: Mon, 18 Aug 2014 17:31:35 +0300 Subject: [PATCH 1/4] Add ios and android push notification plugins --- config/development.config.js | 4 ++ config/production.config.js | 4 ++ config/staging.config.js | 4 ++ config/test.config.js | 4 ++ example/server.js | 40 +++++++++--- source/transport.js | 123 +++++++++++++++++++++++++++++++++-- 6 files changed, 166 insertions(+), 13 deletions(-) diff --git a/config/development.config.js b/config/development.config.js index 366fe17..74e73f9 100644 --- a/config/development.config.js +++ b/config/development.config.js @@ -16,6 +16,10 @@ var config = { }, gcm : { serverApiKey: 'fake-google-server-api-key' + }, + apn : { + cert: 'fake-cert-path', + key: 'fake-key-path' } }, diff --git a/config/production.config.js b/config/production.config.js index 9f3d516..9d66ba2 100644 --- a/config/production.config.js +++ b/config/production.config.js @@ -16,6 +16,10 @@ var config = { }, gcm : { serverApiKey: process.env.GOOGLE_SERVER_API_KEY + }, + apn : { + cert: process.env.APPLE_CERT, + key: process.env.APPLE_KEY } }, diff --git a/config/staging.config.js b/config/staging.config.js index 350fb0a..dcc41a3 100644 --- a/config/staging.config.js +++ b/config/staging.config.js @@ -16,6 +16,10 @@ var config = { }, gcm : { serverApiKey: process.env.GOOGLE_SERVER_API_KEY + }, + apn : { + cert: process.env.APPLE_CERT, + key: process.env.APPLE_KEY } }, diff --git a/config/test.config.js b/config/test.config.js index 354e575..7119236 100644 --- a/config/test.config.js +++ b/config/test.config.js @@ -16,6 +16,10 @@ var config = { }, gcm : { serverApiKey: 'fake-google-server-api-key' + }, + apn : { + cert: 'fake-cert-path', + key: 'fake-key-path' } }, diff --git a/example/server.js b/example/server.js index bb740b0..6a422c6 100644 --- a/example/server.js +++ b/example/server.js @@ -68,16 +68,40 @@ notifier }); }) .execute('send-android-push-notification', function (a, transport, callback) { - var registrationIds = []; - registrationIds.push(a.data.deviceId); + var tokens = []; + tokens.push(a.data.token); - var message = transport.android.message; - message.addDataWithObject({ - key1: 'message1', - key2: 'message2' + transport.android.push({ + message: {key1: 'value1', key2: 'value2'}, + tokens: tokens, + retries: 3 + }, callback); + }); + +notifier + .receive('user-completed-action', function (e, actions, callback) { + actions.create('send-ios-push-notification', {user: e.user}, callback); + }) + .resolve('send-ios-push-notification', function (a, actions, callback) { + asyncRequestForUser(actions.user, function (err, user) { + if (err) { + return callback(err); + } + + actions.resolved(a, {deviceId: user.deviceId}, callback); }); + }) + .execute('send-ios-push-notification', function (a, transport, callback) { + var tokens = []; + tokens.push(a.data.token); - transport.android.push.send(message, registrationIds, 4, callback); + transport.ios.push({ + production: false, // use specific gateway based on 'production' property. + passphrase: 'secretPhrase', + alert: { "body" : "Your turn!", "action-loc-key" : "Play" , "launch-image" : "mysplash.png"}, + badge: 1, + tokens: tokens + }, callback); }); notifier.start(process.env.NODE_PORT || 3031); @@ -87,7 +111,7 @@ function asyncRequestForUser(userId, callback) { email: 'example@likeastore.com', name: 'alexander.beletsky', phone: '+3805554455', - deviceId: 'regId123' + token: 'regId123' }; process.nextTick(function () { diff --git a/source/transport.js b/source/transport.js index 205eb02..b755a12 100644 --- a/source/transport.js +++ b/source/transport.js @@ -1,11 +1,15 @@ var mandrill = require('node-mandrill'); var twilio = require('twilio'); var gcm = require('node-gcm'); +var apn = require('apn'); +var logger = require('./utils/logger'); var config = require('../config'); var setupMandrill = function () { if (!validConfig()) { - throw new Error('missing mandrill token, please update config.transport.mandrill section'); + var errorMsg = 'missing mandrill token, please update config.transport.mandrill section'; + logger.error(errorMsg); + throw new Error(errorMsg); } return mandrill(config.transport.mandrill.token); @@ -17,7 +21,9 @@ var setupMandrill = function () { var setupTwillio = function () { if (!validConfig()) { - throw new Error('missing twilio account SID or auth Token, please update config.transport.twilio section'); + var errorMsg = 'missing twilio account SID or auth Token, please update config.transport.twilio section'; + logger.error(errorMsg); + throw new Error(errorMsg); } return twilio(config.transport.twilio.accountSid, config.transport.twilio.authToken); @@ -33,19 +39,126 @@ var setupAndroidPushNotification = function () { } return { - push: new gcm.Sender(config.transport.gcm.serverApiKey), - message: new gcm.Message() + push: push }; + function push(options, callback) { + if(!validOptions()) { + var errorMsg = "missing 'options' or required required fields, please make sure you have provided 'options'"; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + var service = new gcm.Sender(config.transport.gcm.serverApiKey); + var message = new gcm.Message(); + + message.addDataWithObject(options.message); + service.send(message, options.tokens, options.retries, callback); + + function validOptions() { + return options.message && options.tokens && options.retries; + } + } + function validConfig() { return config.transport.gcm && config.transport.gcm.serverApiKey; } }; +var setupIOSPushNotification = function () { + + if(!validConfig()) { + var errorMsg = "missing 'cert.pem' or 'key.pem', please update 'config.transport.apn section'"; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + return { + push: push + } + + function validConfig() { + return config.transport.apn && (config.transport.apn.cert && config.transport.apn.key); + }; + + function push(options, callback) { + var service, note; + var productionGateway = 'gateway.push.apple.com', + developmentGateway = 'gateway.sandbox.push.apple.com'; + + if(!validOptions()) { + var errorMsg = "missing 'options' or required , please make sure you have provided"; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + initConnection(); + initNotification(); + + service.pushNotification(note, options.tokens); + + function validOptions() { + return options && options.alert && options.production && (options.tokens && options.tokens.length > 0); + } + + function initConnection() { + service = new apn.connection({ + production: options.production, + gateway: options.production ? productionGateway : developmentGateway, + port: options.port || 2195, + enhanced: options.enhanced || true, + cacheLength: options.cacheLength || 100, + // errorCallback: callback, + cert: config.transport.apn.cert, + key: config.transport.apn.key, + passphrase: options.passphrase + }); + + service.on('connected', function() { + logger.info('APN Connected.'); + }); + + service.on('transmitted', function(notification, device) { + return callback(null, "Notification transmitted to:" + device.token.toString('hex')); + }); + + service.on('transmissionError', function(errCode, notification, device) { + if(errCode === 8) { + var errorMsg = 'A error code of 8 indicates that the device token is invalid. This could be for a number of reasons - are you using the correct environment? i.e. Production vs. Sandbox'; + logger.error(errorMsg); + return callback(errorMsg); + } + + return callback('Notification caused error: ' + errCode + ' for device ', device, notification); + }); + + service.on('timeout', function() { + var errorMsg = 'APNS connection timeout'; + logger.warning(errorMsg); + return callback(errorMsg); + }); + + service.on('socketError', function() { + var errorMsg = 'APNS socket error'; + logger.error(errorMsg) + }); + } + + function initNotification() { + note = new apn.notification(); + note.sound = options.sound || 'notification-beep.wav'; + note.alert = options.alert || { "body" : "Your turn!", "action-loc-key" : "Play" , "launch-image" : "mysplash.png"}; + note.payload = options.payload || {'messageFrom': 'Notifier'}; + note.badge = options.badge; + } + } +}; + var transport = { mandrill: setupMandrill(), twillio: setupTwillio(), - android: setupAndroidPushNotification() + android: setupAndroidPushNotification(), + ios: setupIOSPushNotification() }; module.exports = transport; \ No newline at end of file From 0ff508b5bdf36053301cf9c43b73240ceb02002b Mon Sep 17 00:00:00 2001 From: juliausti Date: Mon, 18 Aug 2014 18:29:16 +0300 Subject: [PATCH 2/4] small fixes --- source/transport.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/transport.js b/source/transport.js index b755a12..f3f1068 100644 --- a/source/transport.js +++ b/source/transport.js @@ -75,11 +75,11 @@ var setupIOSPushNotification = function () { return { push: push - } + }; function validConfig() { return config.transport.apn && (config.transport.apn.cert && config.transport.apn.key); - }; + } function push(options, callback) { var service, note; @@ -140,7 +140,7 @@ var setupIOSPushNotification = function () { service.on('socketError', function() { var errorMsg = 'APNS socket error'; - logger.error(errorMsg) + logger.error(errorMsg); }); } From d87481e6c649109bd3b14962ba4b20df83dc60b7 Mon Sep 17 00:00:00 2001 From: juliausti Date: Mon, 18 Aug 2014 22:34:46 +0300 Subject: [PATCH 3/4] Edit README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e471940..ed1a8af 100644 --- a/README.md +++ b/README.md @@ -114,10 +114,10 @@ Supported now: * [Mandrill](https://github.com/jimrubenstein/node-mandrill) * [Twillio](https://github.com/twilio/twilio-node) * [Android push notification](https://github.com/ToothlessGear/node-gcm) +* [iOS push notification](https://github.com/argon/node-apn) Will be added soon: * [Mailgun](https://github.com/jimrubenstein/node-mandrill) -* [iOS push notification](https://github.com/argon/node-apn) If you want to extend transport support: @@ -146,7 +146,7 @@ notifier .execute('created-action', function () { /* ... */ }); // start the server -notifier.listen(process.env.PORT); +notifier.start(process.env.PORT); ``` Update `development.config.js` and `production.config.js` configuration. For now, configuration requires connection string to MongoDB, accessToken (shared secret) to access service, mandrill and logentries tokens. From 43d003c0cc3fb2d5f3f821f2ec7842a9c0d214c4 Mon Sep 17 00:00:00 2001 From: juliausti Date: Wed, 20 Aug 2014 17:00:43 +0300 Subject: [PATCH 4/4] Correct example code. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed1a8af..d3fd345 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ $ curl http://notifier.likeastore.com/ Send first notification, ```bash -$ echo '{"event": "incoming-event"}' | curl -d @- http://notifier.likeastore.com/api/events?access_token=ACCESS_TOKEN +$ echo '{"event": "incoming-event"}' | curl -H "Content-Type:application/json" -d @- http://notifier.likeastore.com/api/events?access_token=ACCESS_TOKEN ``` ## Getting started