├── index.js ├── .gitignore ├── lib ├── result.js ├── node-gcm.js ├── multicastresult.js ├── message.js ├── constants.js └── sender.js ├── LICENSE ├── package.json └── README.md /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | 7 | module.exports = require('./lib/node-gcm'); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .idea 17 | -------------------------------------------------------------------------------- /lib/result.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | 7 | function Result() { 8 | this.messageId = undefined; 9 | this.canonicalRegistrationId = undefined; 10 | this.errorCode = undefined; 11 | } 12 | 13 | module.exports = Result; 14 | -------------------------------------------------------------------------------- /lib/node-gcm.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | 7 | exports.Constants = require('./constants'); 8 | exports.Message = require('./message'); 9 | exports.Result = require('./result'); 10 | exports.MulitcastResult = require('./multicastresult'); 11 | exports.Sender = require('./sender'); 12 | -------------------------------------------------------------------------------- /lib/multicastresult.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | 7 | function MulitcastResult() { 8 | this.success = undefined; 9 | this.failure = undefined; 10 | this.canonicalIds = undefined; 11 | this.multicastId = undefined; 12 | this.results = []; 13 | this.retryMulticastIds = []; 14 | } 15 | 16 | MulitcastResult.prototype.addResult = function (result) { 17 | this.results.push(result); 18 | }; 19 | 20 | MulitcastResult.prototype.getTotal = function () { 21 | return this.success + this.failure; 22 | }; 23 | 24 | module.exports = MulitcastResult; 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Marcus Farkas 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | 7 | 8 | function Message(obj) { 9 | if (obj) { 10 | this.collapseKey = obj.collapseKey || undefined; 11 | this.delayWhileIdle = obj.delayWhileIdle || undefined; 12 | this.timeToLive = obj.timeToLive || undefined; 13 | this.data = obj.data || {}; 14 | } else { 15 | this.collapseKey = undefined; 16 | this.delayWhileIdle = undefined; 17 | this.timeToLive = undefined; 18 | this.data = {}; 19 | } 20 | if (Object.keys(this.data).length > 0) { 21 | this.hasData = true; 22 | } else { 23 | this.hasData = false; 24 | } 25 | } 26 | 27 | Message.prototype.addData = Message.prototype.addDataWithKeyValue = function (key, value) { 28 | this.hasData = true; 29 | this.data[key] = value.toString(); 30 | }; 31 | 32 | Message.prototype.addDataWithObject = function (obj) { 33 | if (typeof obj === 'object' && Object.keys(obj).length > 0) { 34 | this.data = obj; 35 | this.hasData = true; 36 | } 37 | }; 38 | 39 | module.exports = Message; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-gcm", 3 | "description": "a Node.JS wrapper library-port for Google Cloud Messaging for Android", 4 | "version": "0.9.6", 5 | "author": "Marcus Farkas ", 6 | "contributors": [ 7 | { "name": "Marcus Farkas", "email": "toothlessgear@finitebox.com" }, 8 | { "name": "monkbroc", "email": "jvanier@gmail.com" }, 9 | { "name": "zlyinfinite", "email": ""}, 10 | { "name": "Yann Biancheri", "email": "yann.biancheri@gmail.com" }, 11 | { "name": "Hamid Palo", "email": "hamid@fogcreek.com"}, 12 | { "name": "Dotan J. Nahum", "email": "jondotan@gmail.com" }, 13 | { "name": "Max Rabin", "email": "rabin.max@gmail.com" }, 14 | { "name": "Olivier Poitrey", "email": "rs@dailymotion.com" }, 15 | { "name": "George Miroshnykov", "email": "george.miroshnykov@gmail.com" }, 16 | { "name": "Alejandro Garcia Gil", "email": "alejandro@ideaknow.com" }, 17 | { "name": "Ismael Gorissen", "email": "ismael.gorissen@gmail.com" }, 18 | { "name": "Joris Verbogt", "email": "joris@notifica.re" } 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "http://github.com/ToothlessGear/node-gcm.git", 23 | "web": "http://github.com/ToothlessGear/node-gcm" 24 | }, 25 | "keywords": ["google", "cloud", "push", "notifications", "android", "c2dm", "gcm"], 26 | "main": "index", 27 | "dependencies": {}, 28 | "devDependencies": {}, 29 | "optionalDependencies": {}, 30 | "engines": { 31 | "node": ">= 0.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | 7 | var Constants = { 8 | 9 | 'GCM_SEND_ENDPOINT' : 'android.googleapis.com', 10 | 11 | 'GCM_SEND_ENDPATH' : '/gcm/send', 12 | 13 | 'PARAM_REGISTRATION_ID' : 'registration_id', 14 | 15 | 'PARAM_COLLAPSE_KEY' : 'collapse_key', 16 | 17 | 'PARAM_DELAY_WHILE_IDLE' : 'delay_while_idle', 18 | 19 | 'PARAM_PAYLOAD_KEY' : 'data', 20 | 21 | 'PARAM_TIME_TO_LIVE' : 'time_to_live', 22 | 23 | 'ERROR_QUOTA_EXCEEDED' : 'QuotaExceeded', 24 | 25 | 'ERROR_DEVICE_QUOTA_EXCEEDED' : 'DeviceQuotaExceeded', 26 | 27 | 'ERROR_MISSING_REGISTRATION' : 'MissingRegistration', 28 | 29 | 'ERROR_INVALID_REGISTRATION' : 'InvalidRegistration', 30 | 31 | 'ERROR_MISMATCH_SENDER_ID' : 'MismatchSenderId', 32 | 33 | 'ERROR_NOT_REGISTERED' : 'NotRegistered', 34 | 35 | 'ERROR_MESSAGE_TOO_BIG' : 'MessageTooBig', 36 | 37 | 'ERROR_MISSING_COLLAPSE_KEY' : 'MissingCollapseKey', 38 | 39 | 'ERROR_UNAVAILABLE' : 'Unavailable', 40 | 41 | 'TOKEN_MESSAGE_ID' : 'id', 42 | 43 | 'TOKEN_CANONICAL_REG_ID' : 'registration_id', 44 | 45 | 'TOKEN_ERROR' : 'Error', 46 | 47 | 'JSON_REGISTRATION_IDS' : 'registration_ids', 48 | 49 | 'JSON_PAYLOAD' : 'data', 50 | 51 | 'JSON_SUCCESS' : 'success', 52 | 53 | 'JSON_FAILURE' : 'failure', 54 | 55 | 'JSON_CANONICAL_IDS' : 'canonical_ids', 56 | 57 | 'JSON_MULTICAST_ID' : 'multicast_id', 58 | 59 | 'JSON_RESULTS' : 'results', 60 | 61 | 'JSON_ERROR' : 'error', 62 | 63 | 'JSON_MESSAGE_ID' : 'message_id', 64 | 65 | 'UTF8' : 'UTF-8', 66 | 67 | 'BACKOFF_INITIAL_DELAY' : 1000, 68 | 69 | 'MAX_BACKOFF_DELAY' : 1024000 70 | }; 71 | 72 | module.exports = Constants; 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-gcm 2 | 3 | node-gcm is a Node.JS library for [**Google Cloud Messaging for Android**](http://developer.android.com/guide/google/gcm/index.html), which replaces Cloud 2 Device Messaging (C2DM). 4 | 5 | ## Installation 6 | ```bash 7 | $ npm install node-gcm 8 | ``` 9 | ##Requirements 10 | 11 | An Android device running 2.2 or newer and an API key as per [GCM getting started guide](http://developer.android.com/guide/google/gcm/gs.html). 12 | 13 | ## Usage 14 | 15 | ```js 16 | var gcm = require('node-gcm'); 17 | 18 | // create a message with default values 19 | var message = new gcm.Message(); 20 | 21 | // or with object values 22 | var message = new gcm.Message({ 23 | collapseKey: 'demo', 24 | delayWhileIdle: true, 25 | timeToLive: 3, 26 | data: { 27 | key1: 'message1', 28 | key2: 'message2' 29 | } 30 | }); 31 | 32 | var sender = new gcm.Sender('insert Google Server API Key here'); 33 | var registrationIds = []; 34 | 35 | // Optional 36 | // add new key-value in data object 37 | message.addDataWithKeyValue('key1','message1'); 38 | message.addDataWithKeyValue('key2','message2'); 39 | 40 | // or add a data object 41 | message.addDataWithObject({ 42 | key1: 'message1', 43 | key2: 'message2' 44 | }); 45 | 46 | // or with backwards compability of previous versions 47 | message.addData('key1','message1'); 48 | message.addData('key2','message2'); 49 | 50 | 51 | message.collapseKey = 'demo'; 52 | message.delayWhileIdle = true; 53 | message.timeToLive = 3; 54 | // END Optional 55 | 56 | // At least one required 57 | registrationIds.push('regId1'); 58 | registrationIds.push('regId2'); 59 | 60 | /** 61 | * Parameters: message-literal, registrationIds-array, No. of retries, callback-function 62 | */ 63 | sender.send(message, registrationIds, 4, function (err, result) { 64 | console.log(result); 65 | }); 66 | ``` 67 | 68 | And without retries 69 | ```js 70 | sender.sendNoRetry(message, registrationIds-array, function (err, result) { 71 | console.log(result); 72 | }); 73 | ``` 74 | 75 | ## Contribute! 76 | 77 | If you don't want to create a GitHub-Account, but still feel the urge to contribute... no problem! 78 | Just send me an [email](mailto:toothlessgear@finitebox.com) with your 79 | pull request from your private repository. 80 | Of course, you can also send me a patch directly inline your mail. 81 | Any help is much appreciated! 82 | 83 | ## Contributors 84 | * [monkbroc](https://github.com/monkbroc) 85 | * [zlyinfinite](https://github.com/zlyinfinite) 86 | * [Yann Biancheri](https://github.com/yannooo) 87 | * [Hamid Palo](https://github.com/hamidp) 88 | * [Dotan J.Nahum](https://github.com/jondot) 89 | * [Olivier Poitrey](https://github.com/rs) 90 | * [Max Rabin](https://github.com/maxrabin) 91 | * [George Miroshnykov](https://github.com/laggyluke) 92 | * [Alejandro Garcia](https://github.com/Alegege) 93 | * [Ismael Gorissen](https://github.com/igorissen) 94 | * [Joris Verbogt](https://github.com/silentjohnny) 95 | 96 | ## License 97 | 98 | (The MIT License) 99 | 100 | Copyright (c) 2013 Marcus Farkas <toothlessgear@finitebox.com> 101 | 102 | Permission is hereby granted, free of charge, to any person obtaining 103 | a copy of this software and associated documentation files (the 104 | 'Software'), to deal in the Software without restriction, including 105 | without limitation the rights to use, copy, modify, merge, publish, 106 | distribute, sublicense, and/or sell copies of the Software, and to 107 | permit persons to whom the Software is furnished to do so, subject to 108 | the following conditions: 109 | 110 | The above copyright notice and this permission notice shall be 111 | included in all copies or substantial portions of the Software. 112 | 113 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 114 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 115 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 116 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 117 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 118 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 119 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 120 | 121 | ## Changelog 122 | **0.9.6:** 123 | * fixed undefined "data" var 124 | * made constructor argument optional 125 | * added back addData method 126 | * updated README 127 | * updated contributors 128 | 129 | **0.9.5:** 130 | * change addData to addDataWithKeyValue 131 | * add new function addDataWithObject 132 | * message object can be initialised with another object 133 | * updated contributors 134 | 135 | **0.9.4:** 136 | * fix TypeError 137 | * updated contributors 138 | * updated README 139 | 140 | **0.9.3:** 141 | * new callback-style (Please check the example above) 142 | * fixes various issues (Read commit messages) 143 | * not making a distinction between a single and multiple result makes it easier for application-land code to handle 144 | 145 | **0.9.2:** 146 | * added error handler to HTTPS request to handle DNS exceptions 147 | * added multicast-messaging 148 | 149 | **0.9.1:** 150 | * first release 151 | -------------------------------------------------------------------------------- /lib/sender.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-gcm 3 | * Copyright(c) 2013 Marcus Farkas 4 | * MIT Licensed 5 | */ 6 | var Constants = require('./constants'); 7 | var Result = require('./result'); 8 | 9 | var https = require('https'); 10 | var timer = require('timers'); 11 | 12 | 13 | function Sender(key) { 14 | this.key = key; 15 | } 16 | 17 | var sendNoRetryMethod = Sender.prototype.sendNoRetry = function (message, registrationIds, callback) { 18 | var body = {}, 19 | result = new Result(), 20 | requestBody, 21 | post_options, 22 | post_req; 23 | 24 | body[Constants.JSON_REGISTRATION_IDS] = registrationIds; 25 | 26 | if (message.delayWhileIdle !== undefined) { 27 | body[Constants.PARAM_DELAY_WHILE_IDLE] = message.delayWhileIdle; 28 | } 29 | if (message.collapseKey !== undefined) { 30 | body[Constants.PARAM_COLLAPSE_KEY] = message.collapseKey; 31 | } 32 | if (message.timeToLive !== undefined) { 33 | body[Constants.PARAM_TIME_TO_LIVE] = message.timeToLive; 34 | } 35 | if (message.hasData) { 36 | body[Constants.PARAM_PAYLOAD_KEY] = message.data; 37 | } 38 | 39 | requestBody = JSON.stringify(body); 40 | post_options = { 41 | host: Constants.GCM_SEND_ENDPOINT, 42 | port: '443', 43 | path: Constants.GCM_SEND_ENDPATH, 44 | method: 'POST', 45 | headers: { 46 | 'Content-Type': 'application/json', 47 | 'Content-length': Buffer.byteLength(requestBody, 'utf8'), 48 | 'Authorization': 'key=' + this.key 49 | } 50 | }; 51 | 52 | post_req = https.request(post_options, function (res) { 53 | var statusCode = res.statusCode, 54 | buf = ''; 55 | res.setEncoding('utf-8'); 56 | res.on('data', function (data) { 57 | buf += data; 58 | }); 59 | 60 | res.on('end', function () { 61 | if (statusCode === 503) { 62 | console.log('GCM service is unavailable'); 63 | return callback(statusCode, null); 64 | } else if(statusCode == 401){ 65 | console.log('Unauthorized'); 66 | return callback(statusCode, null); 67 | } else if (statusCode !== 200) { 68 | console.log('Invalid request: ' + statusCode); 69 | return callback(statusCode, null); 70 | } 71 | 72 | // Make sure that we don't crash in case something goes wrong while 73 | // handling the response. 74 | try { 75 | var data = JSON.parse(buf); 76 | callback(null, data); 77 | } catch (e) { 78 | console.log("Error handling GCM response " + e); 79 | callback("error", null); 80 | } 81 | }); 82 | }); 83 | 84 | post_req.on('error', function (e) { 85 | console.log("Exception during GCM request: " + e); 86 | callback("request error", null); 87 | }); 88 | 89 | post_req.write(requestBody); 90 | post_req.end(); 91 | }; 92 | 93 | Sender.prototype.send = function (message, registrationId, retries, callback) { 94 | 95 | var attempt = 1, 96 | backoff = Constants.BACKOFF_INITIAL_DELAY; 97 | 98 | if (registrationId.length === 1) { 99 | 100 | this.sendNoRetry(message, registrationId, function lambda(err, result) { 101 | 102 | if (result === undefined) { 103 | if (attempt < retries) { 104 | var sleepTime = backoff * 2 * attempt; 105 | if (sleepTime > Constants.MAX_BACKOFF_DELAY) { 106 | sleepTime = Constants.MAX_BACKOFF_DELAY; 107 | } 108 | timer.setTimeout(function () { 109 | sendNoRetryMethod(message, registrationId, lambda); 110 | }, sleepTime); 111 | } else { 112 | console.log('Could not send message after ' + retries + ' attempts'); 113 | callback(null, result); 114 | } 115 | attempt += 1; 116 | } else callback(null, result); 117 | }); 118 | } else if (registrationId.length > 1) { 119 | this.sendNoRetry(message, registrationId, function lambda(err, result) { 120 | 121 | if (attempt < retries) { 122 | var sleepTime = backoff * 2 * attempt, 123 | unsentRegIds = [], 124 | i; 125 | if (sleepTime > Constants.MAX_BACKOFF_DELAY) { 126 | sleepTime = Constants.MAX_BACKOFF_DELAY; 127 | } 128 | 129 | if (result) { 130 | for (i = 0; i < registrationId.length; i += 1) { 131 | if (result.results[i].error === 'Unavailable') { 132 | unsentRegIds.push(registrationId[i]); 133 | } 134 | } 135 | } 136 | 137 | registrationId = unsentRegIds; 138 | if (registrationId.length !== 0) { 139 | timer.setTimeout(function () { 140 | sendNoRetryMethod(message, registrationId, lambda); 141 | }, sleepTime); 142 | attempt += 1; 143 | } else callback(null, result); 144 | 145 | } else { 146 | console.log('Could not send message to all devices after ' + retries + ' attempts'); 147 | callback(null, result); 148 | } 149 | }); 150 | } else { 151 | console.log('No RegistrationIds given!'); 152 | } 153 | }; 154 | 155 | module.exports = Sender; 156 | --------------------------------------------------------------------------------