├── .env ├── test ├── android.config.json ├── ios.config.json └── test.js ├── _fcm.js ├── package.json ├── _apn.js ├── _express.js ├── index.js └── README.md /.env: -------------------------------------------------------------------------------- 1 | ios_p8=[p8 filename] 2 | ios_keyid=[ios keyId] 3 | ios_teamid=[ios teamId] 4 | android_apikey=[Firebase cloud messaging api key] 5 | -------------------------------------------------------------------------------- /test/android.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "$push.android", 3 | "options": { 4 | "to": "[ANDROID DEVICE TOKEN]", 5 | "data": { 6 | "href": { 7 | "url": "https://news.ycombinator.com", 8 | "view": "web" 9 | } 10 | }, 11 | "priority": "high", 12 | "notification": { 13 | "title": "android", 14 | "sound": "default", 15 | "body": "this is a message for android" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /_fcm.js: -------------------------------------------------------------------------------- 1 | var FCM = require('fcm-push'); 2 | module.exports = { 3 | init: function(options) { 4 | this.fcm = new FCM(process.env.android_apikey); 5 | }, 6 | send: function(options) { 7 | this.fcm.send(options, function(err, response){ 8 | if (err) { 9 | console.log("Something has gone wrong!"); 10 | } else { 11 | console.log("Successfully sent with response: ", response); 12 | } 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/ios.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "$push.ios", 3 | "options": { 4 | "to": { 5 | "token": "[ios device token]", 6 | "topic": "[ios app bundle identifier]" 7 | }, 8 | "data": { 9 | "href": { 10 | "url": "https://news.ycombinator.com", 11 | "view": "web" 12 | } 13 | }, 14 | "notification": { 15 | "alert": "\uD83D\uDCE7 \u2709 You have a new message", 16 | "sound": "default" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "micropush", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index", 8 | "test": "node test/test" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "apn": "^2.1.5", 14 | "body-parser": "^1.17.2", 15 | "dotenv": "^4.0.0", 16 | "express": "^4.15.4", 17 | "fcm-push": "^1.1.2" 18 | }, 19 | "devDependencies": { 20 | "request": "^2.81.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var examples = { 3 | ios: require('./ios.config.json'), 4 | android: require('./android.config.json') 5 | } 6 | 7 | // Customize 8 | var config = { 9 | os: "android", 10 | root: "[micropush server root url]" 11 | }; 12 | 13 | request({ 14 | uri: config.root + '/send', 15 | method: 'POST', 16 | json: examples[config.os], 17 | }, function (error, response, body) { 18 | if (!error && response.statusCode == 200) { 19 | console.log(body.id) 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /_apn.js: -------------------------------------------------------------------------------- 1 | const apn = require('apn'); 2 | module.exports = { 3 | init: function(options) { 4 | this.apnProvider = new apn.Provider(options); 5 | }, 6 | send: function(options) { 7 | var note = new apn.Notification(); 8 | 9 | // set aps payload 10 | for(let key in options.notification) { 11 | note.aps[key] = options.notification[key]; 12 | } 13 | 14 | // set custom data payload 15 | for(let key in options.data) { 16 | note.payload[key] = options.data[key]; 17 | } 18 | 19 | // set topic 20 | note.topic = options.to.topic; 21 | 22 | // send 23 | this.apnProvider.send(note, options.to.token).then( (result) => { 24 | console.log(JSON.stringify(result)); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /_express.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const bodyParser = require('body-parser') 3 | module.exports = { 4 | init: function(options) { 5 | const app = express() 6 | app.use(bodyParser.urlencoded({ extended: false })) 7 | app.use(bodyParser.json()) 8 | app.use(express.static('.')) 9 | app.use(function(req, res, next) { 10 | res.header("Access-Control-Allow-Origin", "*"); 11 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 12 | next(); 13 | }); 14 | if(options) { 15 | this.view = options.view; 16 | for(let method in options.routes) { 17 | let handlers = options.routes[method]; 18 | for(let route in handlers) { 19 | console.log(method); 20 | app[method](route, handlers[route]); 21 | console.log(route); 22 | } 23 | } 24 | } 25 | app.listen(process.env.PORT || 3000, function () { console.log('Listening!') }) 26 | }, 27 | respond: function(options) { 28 | options.res.send(this.view[options.response.view]) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | var $express = require('./_express'); 3 | var $apn = require('./_apn'); 4 | var $fcm = require('./_fcm'); 5 | $apn.init({ 6 | token: { 7 | key: process.env.ios_p8, 8 | keyId: process.env.ios_keyid, 9 | teamId: process.env.ios_teamid 10 | }, 11 | production: true 12 | }); 13 | $fcm.init({ 14 | token: process.env.android_apikey 15 | }) 16 | $express.init({ 17 | view: { 18 | body: { 19 | "response": "success" 20 | } 21 | }, 22 | routes: { 23 | post: { 24 | "/": function(req, res){ 25 | let body = req.body; 26 | let tokens = body.type.split(".") 27 | let os = tokens[1]; 28 | if(tokens[0].toLowerCase() === '$push') { 29 | if(os === 'ios') { 30 | $apn.send(body.options); 31 | } else if (os === 'android') { 32 | $fcm.send(body.options); 33 | } 34 | } 35 | $express.respond({ 36 | req: req, 37 | res: res, 38 | response: { model: "", view: "body" } 39 | }) 40 | } 41 | } 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micropush 2 | 3 | Dead simple Microservice for push notifications on iOS and Android. 4 | 5 | # Install 6 | 7 | 1. Download 8 | 9 | ``` 10 | git clone https://github.com/Jasonette/micropush.git 11 | ``` 12 | 13 | 2. Set up 14 | 15 | 1. **Get private keys** 16 | - iOS : [Generate a p8 file from Apple developers website](http://help.apple.com/xcode/mac/current/#/dev54d690a66?sub=dev73a37248c) and paste the p8 file into micropush root folder 17 | - Android : Sign up to Google Firebase and [get your Firebase Cloud Messaging API key](https://stackoverflow.com/questions/37337512/where-can-i-find-the-api-key-for-firebase-cloud-messaging) 18 | 19 | 2. **Update `.env` file:** Open the `.env` file inside the root directory and make changes. 20 | - If supporting iOS 21 | 1. update the `ios_p8` attribute value with the name of the `p8` file you pasted (example: `apns.p8`) 22 | 2. update both the `ios_keyid` and `ios_teamid` with corresponding values ([Learn how to retrieve keyid and teamid](https://www.google.com/search?q=apns+teamid+keyid&oq=apns+teamid+keyid)) 23 | - If supporting Android, 24 | 1. update the `android_apikey` attribute with the api key we got from "Get private keys" step. 25 | 26 | 3. Deploy 27 | 28 | - Development: Just run `npm start` to run the server on localhost 29 | - Production: Push to heroku or wherever you want. That's it! 30 | 31 | # Usage 32 | 33 | Sending a push is a simple as: 34 | 35 | - making a POST request of `'Content-Type': 'application/json'` 36 | - to your Micropush server endpoint 37 | - with a JSON payload that follows the convention described below: 38 | 39 | 40 | # Micropush JSON protocol 41 | 42 | The payload contains two attributes: 43 | 44 | 1. **type**: Either `$push.android` or `$push.ios` 45 | 2. **options**: options to send to micropush. Micropush will interpret this JSON depending on whether it's `$push.android` or `$push.ios`. 46 | 47 | Micropush will interpret the `options` object based on the `type` attribute. See below section for details. 48 | 49 | # Options syntax 50 | 51 | ## Android 52 | 53 | In case of Android, the protocol is straight-forward. It completely follows the Firebase Cloud Messaging HTTP protocol: https://firebase.google.com/docs/cloud-messaging/http-server-ref 54 | 55 | Here's an example (Notice that the `options` object follows the [FCM Downstream HTTP messages protocol](https://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-json) : 56 | 57 | ``` 58 | { 59 | "type": "$push.android", 60 | "options": { 61 | "to": "[DEVICE_TOKEN_GOES_HERE]", 62 | "data": { 63 | "href": { 64 | "url": "https://news.ycombinator.com", 65 | "view": "web" 66 | } 67 | }, 68 | "priority": "high", 69 | "notification": { 70 | "title": "android", 71 | "sound": "default", 72 | "body": "this is a message for android" 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | ## iOS 79 | 80 | In case of iOS it's not interpreted literally. Instead there are **3 attributes:** 81 | 82 | 1. **to**: Must contain two sub-attributes "token" and "topic" 83 | - **token**: Device token to send push to 84 | - **topic**: Your app's bundle id 85 | 2. **notification**: This part represents the [aps payload](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1) you send to APNS. 86 | 3. **data**: Custom JSON payload which is accessible as `userInfo` inside [application:didReceiveRemoteNotification:](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428430-application) 87 | 88 | Here's an example JSON payload for iOS: 89 | 90 | ``` 91 | { 92 | "type": "$push.ios", 93 | "options": { 94 | "to": { 95 | "token": "[DEVICE_TOKEN_GOES_HERE]", 96 | "topic": "[APP_BUNDLE_ID_GOES_HERE]" 97 | }, 98 | "data": { 99 | "href": { 100 | "url": "https://news.ycombinator.com", 101 | "view": "web" 102 | } 103 | }, 104 | "notification": { 105 | "alert": "\uD83D\uDCE7 \u2709 You have a new message", 106 | "sound": "default" 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | ## Example 113 | 114 | - Check out `/test` folder for console based examples. 115 | - Check out [Micropusher](https://github.com/Jasonette/micropusher) to instantly try on the web 116 | --------------------------------------------------------------------------------