From b6d5e35d6cc48863651b8224492c27f606577004 Mon Sep 17 00:00:00 2001 From: Chirag Choudhary Date: Thu, 4 Feb 2016 06:31:32 +0530 Subject: [PATCH 1/6] add subscription of one user to a gcm topic --- lib/constants.js | 6 +++ lib/instance-id.js | 107 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 lib/instance-id.js diff --git a/lib/constants.js b/lib/constants.js index 788cc98..3510ddd 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -12,6 +12,12 @@ var Constants = { 'GCM_SEND_URI' : 'https://gcm-http.googleapis.com:443/gcm/send', + 'GCM_IID_ENDPOINT' : 'https://iid.googleapis.com', + + 'GCM_SUBSCRIPTION_ENDPATH_1' : '/iid/v1/', + + 'GCM_SUBSCRIPTION_ENDPATH_2' : '/rel/topics/', + 'ERROR_QUOTA_EXCEEDED' : 'QuotaExceeded', 'ERROR_DEVICE_QUOTA_EXCEEDED' : 'DeviceQuotaExceeded', diff --git a/lib/instance-id.js b/lib/instance-id.js new file mode 100644 index 0000000..827e882 --- /dev/null +++ b/lib/instance-id.js @@ -0,0 +1,107 @@ +var Constants = require('./constants'); +var Sender = require('./sender'); + +//Set the name Instance Id from the google API docs +function InstanceId(key, options) { + if (!(this instanceof Sender)) { + return new Sender(key, options); + } + + this.key = key; + // Will be useful when implementing retries + this.options = options; +} + +// This function will add a user or a list of users to a topic. +InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { + if(!callback) { + callback = function() {}; + } + + if(typeof topic != "string" || topic == "" ) { + debug('Incorrect topic provided!'); + return process.nextTick(callback.bind(this, 'Incorrect topic provided!', null)); + } + + var singleSubscriber; + var subscriberList; + + if(typeof subscriber == "string") { + singleSubscriber = subscriber; + } + else if (!Array.isArray(subscriber) && typeof subscriber == "object") { + var o = Sender.extractRecipient(subscriber); + var theRecipient; + + if (o.err) { + return callback(o.err); + } + else { + theRecipient = o.recipient; + } + + if (Array.isArray(theRecipient)) { + subscriberList = theRecipient; + } + else { + singleSubscriber = theRecipient; + } + } + else { + if (Array.isArray(subscriber) && subscriber.length == 1) { + singleSubscriber = subscriber[0]; + } + else { + subscriberList = recipient; + } + } + + if(singleSubscriber != null){ + + var post_options_single = { + method: 'POST', + headers: { + 'Authorization': 'key=' + this.key + }, + uri: Constants.GCM_IID_ENDPOINT+Constants.GCM_SUBSCRIPTION_ENDPATH_1+token+Constants.GCM_SUBSCRIPTION_ENDPATH_2+topic + }; + + var post_req = req(request_options, function (err, res, resBodyJSON) { + if (err) { + return callback(err, null); + } + + if (!res) { + return callback('response is null', null); + } + + if (res.statusCode >= 500) { + debug('GCM service is unavailable (500)'); + return callback(res.statusCode, null); + } else if (res.statusCode === 401) { + debug('Unauthorized (401). Check that your API token is correct.'); + return callback(res.statusCode, null); + } else if (res.statusCode !== 200) { + debug('Invalid request (' + res.statusCode + '): ' + resBodyJSON); + return callback(res.statusCode, null); + } + + callback(null, resBodyJSON); + } + + + } + else if (subscriberList != null){ + + + } + else{ + + } + + + + +}; + +module.exports = InstanceId; From 5ff85ae8950bcf41b3cc31d48a8a6a47a87e740d Mon Sep 17 00:00:00 2001 From: Chirag Choudhary Date: Thu, 4 Feb 2016 06:50:26 +0530 Subject: [PATCH 2/6] add multi user subscription --- lib/constants.js | 4 ++++ lib/instance-id.js | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index 3510ddd..3fd4ca4 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -18,6 +18,10 @@ var Constants = { 'GCM_SUBSCRIPTION_ENDPATH_2' : '/rel/topics/', + 'GCM_IID_SUBSCRIBE_URI' : 'https://iid.googleapis.com/iid/v1:batchAdd', + + 'GCM_IID_UNSUBSCRIBE_URI' : 'https://iid.googleapis.com/iid/v1:batchAdd', + 'ERROR_QUOTA_EXCEEDED' : 'QuotaExceeded', 'ERROR_DEVICE_QUOTA_EXCEEDED' : 'DeviceQuotaExceeded', diff --git a/lib/instance-id.js b/lib/instance-id.js index 827e882..9a21674 100644 --- a/lib/instance-id.js +++ b/lib/instance-id.js @@ -92,16 +92,50 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { } else if (subscriberList != null){ + var body = {}; + body.to=topic; + body.registration_tokens=subscriberList; + var post_options = { + method: 'POST', + headers: { + 'Authorization': 'key=' + this.key + }, + uri: Constants.GCM_IID_SUBSCRIBE_URI, + json: body + }; - } - else{ + var post_req = req(request_options, function (err, res, resBodyJSON) { + if (err) { + return callback(err, null); + } - } + if (!res) { + return callback('response is null', null); + } + + if (res.statusCode >= 500) { + debug('GCM service is unavailable (500)'); + return callback(res.statusCode, null); + } else if (res.statusCode === 401) { + debug('Unauthorized (401). Check that your API token is correct.'); + return callback(res.statusCode, null); + } else if (res.statusCode !== 200) { + debug('Invalid request (' + res.statusCode + '): ' + resBodyJSON); + return callback(res.statusCode, null); + } + callback(null, resBodyJSON); + } + } + else{ + debug('No subscribers provided!'); + return process.nextTick(callback.bind(this, 'No subscribers provided!', null)); + } }; + module.exports = InstanceId; From a6f8a2d6e428c6b35e4936c9109c777e91db0cb5 Mon Sep 17 00:00:00 2001 From: Chirag Choudhary Date: Thu, 4 Feb 2016 07:09:53 +0530 Subject: [PATCH 3/6] adding info of a user corresponding to the info API in the iid --- lib/constants.js | 2 ++ lib/instance-id.js | 52 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index 3fd4ca4..0351a62 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -14,6 +14,8 @@ var Constants = { 'GCM_IID_ENDPOINT' : 'https://iid.googleapis.com', + 'GCM_INFO_ENDPATH' : '/iid/info/', + 'GCM_SUBSCRIPTION_ENDPATH_1' : '/iid/v1/', 'GCM_SUBSCRIPTION_ENDPATH_2' : '/rel/topics/', diff --git a/lib/instance-id.js b/lib/instance-id.js index 9a21674..99af62a 100644 --- a/lib/instance-id.js +++ b/lib/instance-id.js @@ -26,6 +26,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { var singleSubscriber; var subscriberList; + //TODO: create a separate function for this piece of code & use in sender.js as well if(typeof subscriber == "string") { singleSubscriber = subscriber; } @@ -58,7 +59,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { if(singleSubscriber != null){ - var post_options_single = { + var post_options = { method: 'POST', headers: { 'Authorization': 'key=' + this.key @@ -66,7 +67,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { uri: Constants.GCM_IID_ENDPOINT+Constants.GCM_SUBSCRIPTION_ENDPATH_1+token+Constants.GCM_SUBSCRIPTION_ENDPATH_2+topic }; - var post_req = req(request_options, function (err, res, resBodyJSON) { + var post_req = req(post_options, function (err, res, resBodyJSON) { if (err) { return callback(err, null); } @@ -105,7 +106,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { json: body }; - var post_req = req(request_options, function (err, res, resBodyJSON) { + var post_req = req(post_options, function (err, res, resBodyJSON) { if (err) { return callback(err, null); } @@ -137,5 +138,50 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { }; +//TODO: implement batch unsubscription +//TODO: implement a funciton which returs list of subscribed groups using info and setting details as true. + +InstanceId.prototype.info = function(token, callback) { + if(typeof token != "string") { + debug('Incorrect Instance ID token passed!'); + return process.nextTick(callback.bind(this, 'Incorrect Instance ID token passed!', null)); + } + + //TODO: letting users pass details=true and pass on in the url + var post_options = { + method: 'POST', + headers: { + 'Authorization': 'key=' + this.key + }, + uri: Constants.GCM_IID_ENDPOINT+Constants.GCM_INFO_ENDPATH+token + }; + + //TODO: create in a separate function, as this part of code is used twice in this file & once in sender.js + var post_req = req(post_options, function (err, res, resBodyJSON) { + if (err) { + return callback(err, null); + } + + if (!res) { + return callback('response is null', null); + } + + if (res.statusCode >= 500) { + debug('GCM service is unavailable (500)'); + return callback(res.statusCode, null); + } else if (res.statusCode === 401) { + debug('Unauthorized (401). Check that your API token is correct.'); + return callback(res.statusCode, null); + } else if (res.statusCode !== 200) { + debug('Invalid request (' + res.statusCode + '): ' + resBodyJSON); + return callback(res.statusCode, null); + } + + callback(null, resBodyJSON); + } + + + +} module.exports = InstanceId; From 8c3900fa57060bb0bdfb01cbe8eb74dff10378eb Mon Sep 17 00:00:00 2001 From: Chirag Choudhary Date: Sat, 6 Feb 2016 07:58:52 +0530 Subject: [PATCH 4/6] adding one subscriber to a topic works --- lib/instance-id.js | 23 +++++++++++++++-------- lib/node-gcm.js | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/instance-id.js b/lib/instance-id.js index 99af62a..5115750 100644 --- a/lib/instance-id.js +++ b/lib/instance-id.js @@ -1,10 +1,12 @@ var Constants = require('./constants'); +var req = require('request'); var Sender = require('./sender'); +var debug = require('debug')('node-gcm'); //Set the name Instance Id from the google API docs function InstanceId(key, options) { - if (!(this instanceof Sender)) { - return new Sender(key, options); + if (!(this instanceof InstanceId)) { + return new InstanceId(key, options); } this.key = key; @@ -13,7 +15,8 @@ function InstanceId(key, options) { } // This function will add a user or a list of users to a topic. -InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { +InstanceId.prototype.addToTopicNoRetry = function(topic, subscriber, callback) { + console.log(' addToTopicNoRetry of instance id inside node-gcm'); if(!callback) { callback = function() {}; } @@ -28,9 +31,11 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { //TODO: create a separate function for this piece of code & use in sender.js as well if(typeof subscriber == "string") { + console.log(' subscriber passed is a string. '); singleSubscriber = subscriber; } else if (!Array.isArray(subscriber) && typeof subscriber == "object") { + console.log('subscriber passed is a list or object. '); var o = Sender.extractRecipient(subscriber); var theRecipient; @@ -49,6 +54,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { } } else { + console.log('subscriber is not a string, neither list, probably json or something'); if (Array.isArray(subscriber) && subscriber.length == 1) { singleSubscriber = subscriber[0]; } @@ -58,15 +64,16 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { } if(singleSubscriber != null){ + console.log('subscriber not null ,initiating a post request to the IID endpoint.'); var post_options = { method: 'POST', headers: { 'Authorization': 'key=' + this.key }, - uri: Constants.GCM_IID_ENDPOINT+Constants.GCM_SUBSCRIPTION_ENDPATH_1+token+Constants.GCM_SUBSCRIPTION_ENDPATH_2+topic + uri: Constants.GCM_IID_ENDPOINT+Constants.GCM_SUBSCRIPTION_ENDPATH_1+singleSubscriber+Constants.GCM_SUBSCRIPTION_ENDPATH_2+topic }; - + var resBodyJSON; var post_req = req(post_options, function (err, res, resBodyJSON) { if (err) { return callback(err, null); @@ -88,7 +95,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { } callback(null, resBodyJSON); - } + }); } @@ -127,7 +134,7 @@ InstanceId.prototype.addToTopic = function(topic, subscriber, callback) { } callback(null, resBodyJSON); - } + }); } else{ @@ -178,7 +185,7 @@ InstanceId.prototype.info = function(token, callback) { } callback(null, resBodyJSON); - } + }); diff --git a/lib/node-gcm.js b/lib/node-gcm.js index 107f64e..f2982d6 100644 --- a/lib/node-gcm.js +++ b/lib/node-gcm.js @@ -9,3 +9,4 @@ exports.Message = require('./message'); exports.Result = require('./result'); exports.MulitcastResult = require('./multicastresult'); exports.Sender = require('./sender'); +exports.InstanceId = require('./instance-id'); From ffa28ee97b0504aefcf1b3b4446a14c1446d829d Mon Sep 17 00:00:00 2001 From: Chirag Choudhary Date: Sat, 6 Feb 2016 09:15:19 +0530 Subject: [PATCH 5/6] add multiple token to a topic --- lib/instance-id.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/instance-id.js b/lib/instance-id.js index 5115750..29ffc68 100644 --- a/lib/instance-id.js +++ b/lib/instance-id.js @@ -35,7 +35,7 @@ InstanceId.prototype.addToTopicNoRetry = function(topic, subscriber, callback) { singleSubscriber = subscriber; } else if (!Array.isArray(subscriber) && typeof subscriber == "object") { - console.log('subscriber passed is a list or object. '); + console.log('subscriber is not a list but an object. '); var o = Sender.extractRecipient(subscriber); var theRecipient; @@ -54,12 +54,12 @@ InstanceId.prototype.addToTopicNoRetry = function(topic, subscriber, callback) { } } else { - console.log('subscriber is not a string, neither list, probably json or something'); + console.log('subscriber is a list'); if (Array.isArray(subscriber) && subscriber.length == 1) { singleSubscriber = subscriber[0]; } else { - subscriberList = recipient; + subscriberList = subscriber; } } @@ -101,7 +101,8 @@ InstanceId.prototype.addToTopicNoRetry = function(topic, subscriber, callback) { } else if (subscriberList != null){ var body = {}; - body.to=topic; + // Gcm documentation is currently incorrect, http://stackoverflow.com/questions/35177152/topic-name-format-is-invalid-when-trying-to-subscribe-to-gcm-topic + body.to='/topics/'+topic; body.registration_tokens=subscriberList; var post_options = { From 090913e0ccaeb270549fdbd1c5665cf5607313bf Mon Sep 17 00:00:00 2001 From: Chirag Choudhary Date: Sat, 6 Feb 2016 09:23:48 +0530 Subject: [PATCH 6/6] adding few unit tests for InstanceId --- test/unit/instanceIdSpec.js | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/unit/instanceIdSpec.js diff --git a/test/unit/instanceIdSpec.js b/test/unit/instanceIdSpec.js new file mode 100644 index 0000000..214bfad --- /dev/null +++ b/test/unit/instanceIdSpec.js @@ -0,0 +1,62 @@ +"use strict"; + +var chai = require('chai'), + expect = chai.expect, + sinon = require('sinon'), + proxyquire = require('proxyquire'), + instanceIdPath = '../../lib/instance-id', + Constants = require('../../lib/constants'); + +describe('UNIT InstanceId', function () { + // Use object to set arguments passed into callback + var args = {}; + var requestStub = function (options, callback) { + args.options = options; + return callback( args.err, args.res, args.resBody ); + }; + + var InstanceId = proxyquire(instanceIdPath, { 'request': requestStub }); + + describe('constructor', function () { + var InstanceId = require(instanceIdPath); + + it('should call new on constructor if user does not', function () { + var instanceId = InstanceId(); + expect(instanceId).to.not.be.undefined; + expect(instanceId).to.be.instanceOf(InstanceId); + }); + + it('should create a InstanceId with key and options passed in', function () { + var options = { + proxy: 'http://myproxy.com', + maxSockets: 100, + timeout: 100 + }; + var key = 'myAPIKey', + instanceId = new InstanceId(key, options); + expect(instanceId).to.be.instanceOf(InstanceId); + expect(instanceId.key).to.equal(key); + expect(instanceId.options).to.deep.equal(options); + }); + + it.skip('should do something if not passed a valid key'); + }); + + + describe('addToTopicNoRetry()', function () { + // Set arguments passed in request proxy + function setArgs(err, res, resBody) { + args = { + err: err, + res: res, + resBody: resBody + }; + }; + + + + + + }); + +}); \ No newline at end of file