├── .gitignore ├── LICENSE ├── README.md ├── cordova-both.js ├── package.js ├── richsilv:cordova-notifications_tests.js └── versions.json /.gitignore: -------------------------------------------------------------------------------- 1 | .meteor/local 2 | .meteor/meteorite 3 | .build* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Richard Silverton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | meteor-cordova-notifications 2 | ============================ 3 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/richsilv/meteor-cordova-notifications?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Simple push notifications for Cordova apps built using Meteor. 6 | 7 | ## Why? 8 | 9 | Push notifications are a widely used and extremely useful feature of native apps. However, although packages exist to help with device registration and notification presentation on the client side (Cordova) and coordination of calls to the notifications server on the server side (e.g. NPM), due to the separation of client and server code, setting these up still required some wiring to be done on the part of the developer. 10 | 11 | In Meteor, the ability to drop Cordova-specific and server-specific code into the same package obviates the need for any heavy-lifting on the part of the developer, whilst the Accounts package provides the infrastructure necessary for storing notification ids by user. 12 | 13 | The result is a plug-and-play notifications package which should be extremely straightforward to use. 14 | 15 | **NOTE THAT AT PRESENT, ONLY ANDROID NOTIFICATIONS (VIA GCM) ARE IMPLEMENTED** 16 | 17 | ## How to use this Package 18 | 19 | Firstly, set up a new Google API project and enable GCM using [these instructions](https://console.developers.google.com/flows/enableapi?apiid=googlecloudmessaging&keyType=SERVER_SIDE&r=0.0.0.0/0). 20 | 21 | Then, in a code block that will run on *both client and server*: 22 | 23 | ```javascript 24 | var notificationClient = new NotificationClient(options) 25 | ``` 26 | 27 | ### options 28 | 29 | *__senderId__ (required)* - the Project Number of your Google API project (given at the top of the "Overview" page). 30 | 31 | *__gcmAuthorization__ (required)* - the GCM API key you obtained by following the Android instructions linked above. Note that this should **NOT** be visible to the client, so it's recommended to pull this from [Meteor.settings](https://docs.meteor.com/#/full/meteordeploy) or else a server-only collection in such a way that `null` or `undefined` will be passed on the the client side, but the correct code on the server side. 32 | 33 | *__registeredCallback__ (optional)* - a function to call on the client once it registers a new *regid* in the database and is thus ready to accept push notifications. 34 | 35 | *__messageHandler__ (optional)* - a function to override the default handler which is called on receipt of a new notification message. It is called with three arguments: `payload`, which contains the data payload supplied to the notifications server, `foreground`, a boolean which indicates whether the application is currently running in the foreground, and `coldstart`, a boolean which indicates whether the notification has been fired because it has been tapped in the notifications tray. The default handler behaviour is described below. 36 | 37 | *__removeOnLogout__ (optional)* - if *true* this will instruct the server to remove a users regid on logout to ensure that their device does not receive notifications whilst they're logged out. The regid will be added again if they log back in. 38 | 39 | ### Example settings and initialisation 40 | 41 | **settings.json:** 42 | 43 | ```json 44 | { 45 | "GCM": { 46 | "authorization": "YOUR_AUTHORIZATION_KEY" 47 | }, 48 | "public": { 49 | "GCM": { 50 | "senderId": "YOUR_PROJECT_ID" 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | **somewhere that runs on both client and server:** 57 | 58 | ```javascript 59 | Meteor.startup(function() { 60 | Meteor.notificationClient = new NotificationClient({ 61 | senderId: Meteor.settings.public.GCM.senderId, 62 | gcmAuthorization: Meteor.settings.GCM && Meteor.settings.GCM.authorization 63 | }) 64 | }) 65 | ``` 66 | 67 | ## API 68 | 69 | Note that NotificationClient can only be used to send messages to users who have previously logged in to the app on a mobile device (via any means). 70 | 71 | ### NotificationClient.sendNotification(users, data) [SERVER ONLY] 72 | 73 | Send a notification to the specified users. 74 | 75 | *__users__* - can be an individual user object, an array of user objects, a cursor on the Meteor.users collection, or a single userId. 76 | 77 | *__data__* - the payload to send in the notification. In default usage, this would contain two properties: 78 | 79 | * *title* - the title of the notifications message. 80 | * *message* - the message body. 81 | 82 | If the application is currently open in the foreground, a default UI alert box pops up with the supplied title and message. If the application is not open in the foreground, a local notification is added to the notifications tray with the supplied title and message. 83 | 84 | ### Overriding the default behavior 85 | 86 | The GCM protocol is actually much more flexible than this - an arbitrary object (up to 4kb) can be passed as the payload, whilst the client callback on receipt of notifications can be completeley customised by overriding the `messageHandler` callback (see above). See `cordova-both.js` for the code behind the default behaviour. 87 | 88 | Note that a `Cordova` object is added to the global namespace on the client side - this is necessary as the GCM server requires a function in global scope to be supplied as a callback. 89 | 90 | ## A Note about Schemas 91 | 92 | In order to use this package along with the excellent [Collection2](https://github.com/aldeed/meteor-collection2), any Schema you set up for user docs will have to allow an optional `regid` field for GCM and a `status` field for compatability with the included `mizzao:user-status` package. For example: 93 | 94 | ```javascript 95 | Schemas.User = new SimpleSchema({ 96 | status: { 97 | type: Object, 98 | optional: true, 99 | blackbox: true 100 | }, 101 | regid: { 102 | type: String, 103 | optional: true 104 | } 105 | ... // other stuff 106 | }); 107 | ``` 108 | 109 | ## Demo - [here](https://github.com/richsilv/cordova-notifications-demo) 110 | 111 | ## TODO 112 | 113 | * Add Apple notifications. 114 | * Allow user to choose alert/local-notification/none upon receipt of messages when the app is in the foreground/background. 115 | -------------------------------------------------------------------------------- /cordova-both.js: -------------------------------------------------------------------------------- 1 | Cordova = {}; 2 | 3 | if (Meteor.isCordova) { 4 | 5 | NotificationClient = function(options) { 6 | 7 | if (!options || !options.senderId) { 8 | throw new Meteor.Error('required_options', 'senderId must be supplied as an option as a minimum'); 9 | } 10 | 11 | var instance = {}; 12 | 13 | var successHandler = options.successHandler || function(data) { 14 | console.log("Success: " + JSON.stringify(data)); 15 | }; 16 | 17 | var errorHandler = options.errorHandler || function(e) { 18 | console.log("Error " + e); 19 | }; 20 | 21 | var messageHandler = options.messageHandler || function(payload, foreground, coldstart) { 22 | if (!payload) return null; 23 | if (foreground && !coldstart) { 24 | navigator.notification.alert( 25 | payload.message, 26 | options.alertCallback, 27 | payload.title 28 | ); 29 | } else { 30 | window.plugin.notification.local.add( 31 | _.extend({}, options.notificationOptions, { 32 | message: payload.message, 33 | title: payload.title, 34 | autoCancel: true 35 | }) 36 | ); 37 | } 38 | }; 39 | 40 | Cordova.onNotificationGCM = options.onNotificationGCM || function(res) { 41 | if (res.event === 'registered') { 42 | if (res.regid) { 43 | Meteor.call('cordova-notifications/updateRegid', res.regid, options.registeredCallback && options.registeredCallback.bind(res)); 44 | } 45 | } else if (res.event === 'message') { 46 | messageHandler(res.payload, res.foreground, res.coldstart); 47 | } 48 | } 49 | 50 | Tracker.autorun(function(c) { 51 | 52 | if (Meteor.user()) { 53 | if (device.platform.toLowerCase() === 'android') { 54 | window.plugins.pushNotification.register(successHandler, errorHandler, { 55 | "senderID": options.senderId.toString(), 56 | "ecb": "Cordova.onNotificationGCM" 57 | }); 58 | } else { 59 | // TODO: APN HANDLER REGISTRATION HERE 60 | } 61 | } 62 | }); 63 | 64 | return instance 65 | 66 | } 67 | 68 | } else if (Meteor.isServer) { 69 | 70 | NotificationClient = function(options) { 71 | 72 | if (!options || !options.gcmAuthorization || !options.senderId) { 73 | return false; 74 | } 75 | 76 | var Future = Npm.require('fibers/future'), 77 | instance = {}; 78 | 79 | instance.sendNotification = function(users, data) { 80 | 81 | if (typeof users === 'string') 82 | users = Meteor.users.find(users).fetch(); 83 | else if (typeof users === "object" && users._id) 84 | users = [users]; 85 | else if (users instanceof Mongo.Cursor) 86 | users = users.fetch() 87 | else if (!users instanceof Array) 88 | throw new Meteor.Error('bad_users_argument', 'Supplied user(s) data is not one of: user id, user object, cursor, array of user objects.'); 89 | 90 | var regids = _.without( 91 | _.pluck(users, 'regid'), 92 | undefined), 93 | payload = { 94 | registration_ids: regids, 95 | data: data 96 | }, 97 | headers = { 98 | 'Content-Type': 'application/json', 99 | 'Authorization': 'key=' + options.gcmAuthorization 100 | }, 101 | url = "https://android.googleapis.com/gcm/send", 102 | fut = new Future(); 103 | 104 | if (regids.length) { 105 | HTTP.post(url, { 106 | headers: headers, 107 | data: payload 108 | }, 109 | function(err, res) { 110 | if (err) { 111 | fut.throw(err); 112 | } else { 113 | fut.return({ 114 | response: res, 115 | userCount: regids.length 116 | }); 117 | } 118 | } 119 | ); 120 | } else { 121 | fut.return(null); 122 | } 123 | 124 | return fut.wait(); 125 | 126 | }; 127 | 128 | Meteor.methods({ 129 | 'cordova-notifications/updateRegid': function(regid) { 130 | check && check(regid, String); 131 | Meteor.users.update(this.userId, { 132 | $set: { 133 | regid: regid 134 | } 135 | }); 136 | } 137 | }); 138 | 139 | if (options.removeOnLogout) { 140 | 141 | Meteor.users.find({ 142 | "status.online": true 143 | }).observe({ 144 | removed: function(user) { 145 | Meteor.users.update(user._id, { 146 | $unset: { 147 | regid: true 148 | } 149 | }); 150 | } 151 | }); 152 | 153 | } 154 | 155 | return instance; 156 | 157 | } 158 | 159 | } else { 160 | 161 | NotificationClient = function() {}; 162 | 163 | } 164 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'richsilv:cordova-notifications', 3 | summary: 'Simple push notifications for Cordova apps built using Meteor.', 4 | git: 'https://github.com/richsilv/meteor-cordova-notifications', 5 | version: "0.2.0" 6 | }); 7 | 8 | Cordova.depends({ 9 | "de.appplant.cordova.plugin.local-notification" : "0.8.1", 10 | "com.phonegap.plugins.pushplugin" : "2.2.1", 11 | "org.apache.cordova.dialogs": "0.2.10" 12 | }); 13 | 14 | Package.on_use(function(api) { 15 | 16 | api.use('tracker@1.0.3', 'web.cordova'); 17 | api.use('http@1.0.8', 'server'); 18 | api.use('accounts-base@1.1.2', 'client'); 19 | api.use('mizzao:user-status@0.6.3', 'server'); 20 | 21 | api.add_files('cordova-both.js'); 22 | 23 | api.export('NotificationClient'); 24 | api.export('Cordova'); 25 | 26 | }); 27 | 28 | Package.on_test(function(api) { 29 | api.use('richsilv:cordova-notifications'); 30 | api.use('tinytest'); 31 | 32 | api.add_files('richsilv:cordova-notifications_tests.js'); 33 | }); 34 | -------------------------------------------------------------------------------- /richsilv:cordova-notifications_tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Test this package by running this command from you app 3 | * folder: 4 | * 5 | * > meteor test-packages richsilv:cordova-notifications 6 | * 7 | */ 8 | 9 | Tinytest.add('richsilv:cordova-notifications - main test', function (test) { 10 | test.ok(); 11 | }); -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | [ 4 | "accounts-base", 5 | "1.1.2" 6 | ], 7 | [ 8 | "application-configuration", 9 | "1.0.3" 10 | ], 11 | [ 12 | "base64", 13 | "1.0.1" 14 | ], 15 | [ 16 | "binary-heap", 17 | "1.0.1" 18 | ], 19 | [ 20 | "callback-hook", 21 | "1.0.1" 22 | ], 23 | [ 24 | "check", 25 | "1.0.2" 26 | ], 27 | [ 28 | "ddp", 29 | "1.0.11" 30 | ], 31 | [ 32 | "ejson", 33 | "1.0.4" 34 | ], 35 | [ 36 | "follower-livedata", 37 | "1.0.2" 38 | ], 39 | [ 40 | "geojson-utils", 41 | "1.0.1" 42 | ], 43 | [ 44 | "http", 45 | "1.0.8" 46 | ], 47 | [ 48 | "id-map", 49 | "1.0.1" 50 | ], 51 | [ 52 | "json", 53 | "1.0.1" 54 | ], 55 | [ 56 | "localstorage", 57 | "1.0.1" 58 | ], 59 | [ 60 | "logging", 61 | "1.0.5" 62 | ], 63 | [ 64 | "meteor", 65 | "1.1.3" 66 | ], 67 | [ 68 | "minimongo", 69 | "1.0.5" 70 | ], 71 | [ 72 | "mongo", 73 | "1.0.8" 74 | ], 75 | [ 76 | "ordered-dict", 77 | "1.0.1" 78 | ], 79 | [ 80 | "random", 81 | "1.0.1" 82 | ], 83 | [ 84 | "retry", 85 | "1.0.1" 86 | ], 87 | [ 88 | "service-configuration", 89 | "1.0.2" 90 | ], 91 | [ 92 | "tracker", 93 | "1.0.3" 94 | ], 95 | [ 96 | "underscore", 97 | "1.0.1" 98 | ], 99 | [ 100 | "url", 101 | "1.0.2" 102 | ] 103 | ], 104 | "pluginDependencies": [], 105 | "toolVersion": "meteor-tool@1.0.35", 106 | "format": "1.0" 107 | } --------------------------------------------------------------------------------