From 3f7cc359a533c7078f418bfae6c5c0d62b0e40d1 Mon Sep 17 00:00:00 2001 From: RainbowLand Date: Tue, 24 May 2016 16:55:49 +0800 Subject: [PATCH 1/5] Add support for Windows Notification Service --- src/ParsePushAdapter.js | 6 +- src/WNS.js | 197 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/WNS.js diff --git a/src/ParsePushAdapter.js b/src/ParsePushAdapter.js index f40c08e6..18ec2d37 100644 --- a/src/ParsePushAdapter.js +++ b/src/ParsePushAdapter.js @@ -3,6 +3,7 @@ import Parse from 'parse'; import log from 'npmlog'; import APNS from './APNS'; import GCM from './GCM'; +import WNS from './WNS'; import { classifyInstallations } from './PushAdapterUtils'; const LOG_PREFIX = 'parse-server-push-adapter'; @@ -12,7 +13,7 @@ export class ParsePushAdapter { supportsPushTracking = true; constructor(pushConfig = {}) { - this.validPushTypes = ['ios', 'android']; + this.validPushTypes = ['ios', 'android','wp']; this.senderMap = {}; // used in PushController for Dashboard Features this.feature = { @@ -31,6 +32,9 @@ export class ParsePushAdapter { break; case 'android': this.senderMap[pushType] = new GCM(pushConfig[pushType]); + break; + case 'wp': + this.senderMap[pushType] = new WNS(pushConfig[pushType]); break; } } diff --git a/src/WNS.js b/src/WNS.js new file mode 100644 index 00000000..794d6050 --- /dev/null +++ b/src/WNS.js @@ -0,0 +1,197 @@ +"use strict"; + +import Parse from 'parse'; +import log from 'npmlog'; +import fs from 'fs'; +import wns from 'wns'; + +const LOG_PREFIX = 'parse-server-push-adapter WNS'; + +function WNS(args) { + if (typeof args !== 'object' || !args.clientID || !args.clientSecret || !args.accessTokenPath) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + 'WNS Configuration is invalid'); + } + + this.clientID = args.clientID; + this.clientSecret = args.clientSecret; + this.accessTokenPath = args.accessTokenPath; + + console.log("Init WNS successful!"); +} + +WNS.prototype.send = function(data, devices) +{ + devices = new Array(...devices); + var currentAccessToken = fs.readFileSync(this.accessTokenPath); + + if (typeof currentAccessToken !== 'string' || currentAccessToken.length == 0) + { + log.verbose(LOG_PREFIX, `currentAccessToken not existed.get new access`); + + currentAccessToken = getNewAccessToken(); + + if (currentAccessToken == false) + { + log.error(LOG_PREFIX, `cannot get currentAccessToken From WNS`); + return false; + } + + fs.writeFile(this.accessTokenPath,currentAccessToken,function(err) + { + if (err) + { + log.error(LOG_PREFIX, `cannot write new currentAccessToken to %s`,this.accessTokenPath); + } + } + ); + } + + var wnsPayload = getWNSToastPayload(data.title,data.alert); + + let promises = devices.map((device) => { + + return new Promise((resolve, reject) => { + wns.sendToastText02(device.wnsRequestURI,wnsPayload, + { + client_id: this.clientID, + client_secret: this.clientSecret, + accessToken: currentAccessToken + }, + function (error, result) + { + if (error) { + log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4)); + } else { + log.verbose(LOG_PREFIX, `WNS Response: %s`, JSON.stringify(response, null, 4)); + } + currentAccessToken = error ? error.newAccessToken : result.newAccessToken; + }); + }); + }); + return Parse.Promise.when(promises); + +/* (function (i, len, count, callback) + { + for (; i < len; ++i) { + (function (i) + { + device = devices[i]; + wns.sendToastText02(device.wnsRequestURI,wnsPayload, + { + client_id: this.clientID, + client_secret: this.clientSecret, + accessToken: currentAccessToken + }, + function (error, result) + { + if (error) { + log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4)); + } else { + log.verbose(LOG_PREFIX, `WNS Response: %s`, JSON.stringify(response, null, 4)); + } + currentAccessToken = error ? error.newAccessToken : result.newAccessToken; + if (++count === len) { + callback(); + } + }); + }(i)); + } + }(0, devices.length , 0, function () + { + + }));*/ +} + +WNS.prototype.getNewAccessToken = function() +{ + var payload = url.format({ + query: { + grant_type: 'client_credentials', + client_id: this.clientID, + client_secret: this.clientSecret, + scope: 'notify.windows.com' + } + }).substring(1); // strip leading ? + + // make the request for accessToken to live.com + + var options = { + host: 'login.live.com', + path: '/accesstoken.srf', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': payload.length + } + }; + + var newAccessToken; + var completed; + var req = https.request(options, function (res) { + var body = ''; + res.on('data', function (chunk) { body += chunk; }); + res.on('end', function () { + if (!completed) { + completed = true; + if (res.statusCode === 200) + { + var tokenResponse; + try + { + tokenResponse = JSON.parse(body); + if (typeof tokenResponse.access_token !== 'string' || tokenResponse.token_type !== 'bearer') + throw new Error('Invalid response'); + } + catch (e) + { + var error = new Error('Unable to obtain access token for WNS. Invalid response body: ' + body); + error.statusCode = res.statusCode; + error.headers = res.headers; + error.innerError = e; + return false; + } + + newAccessToken = tokenResponse.access_token; + + return newAccessToken; + } + else + { + var error = new Error('Unable to obtain access token for WNS. HTTP status code: ' + res.statusCode + + '. HTTP response body: ' + body); + error.statusCode = res.statusCode; + error.headers = res.headers; + error.innerError = body; + } + } + }); + }); + + req.on('error', function (error) + { + if (!completed) { + completed = true; + var result = new Error('Unable to send reqeust for access token to Windows Notification Service: ' + error.message); + result.innerError = error; + } + }); + + req.write(payload); + req.end(); + + return false; +} + +WNS.prototype.getWNSToastPayload = function(text1,text2) +{ + let payloadData = { + "text1": text1, + 'text2': text2 + }; + + return payloadData; +} + +module.exports = WNS; +export default WNS; From e140e29fd1fd0c766a20d12d60be9ffadfdab98e Mon Sep 17 00:00:00 2001 From: RainbowLand Date: Tue, 24 May 2016 17:28:31 +0800 Subject: [PATCH 2/5] Add support for Windows Notification Service Add dependencies of wns needed --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5eee9499..f82f1fb1 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "dependencies": { "apn": "^1.7.5", "node-gcm": "^0.14.0", + "wns": "^0.5.3", "npmlog": "^2.0.3", "parse": "^1.8.1" } From c00bd6633197bef1822b8d3b1c8f531e94da29fc Mon Sep 17 00:00:00 2001 From: RainbowLand Date: Tue, 24 May 2016 18:46:17 +0800 Subject: [PATCH 3/5] Fix autotest issue. --- spec/ParsePushAdapter.spec.js | 54 +++++++++++++++++++++++++---- src/WNS.js | 65 ++++++----------------------------- 2 files changed, 58 insertions(+), 61 deletions(-) diff --git a/spec/ParsePushAdapter.spec.js b/spec/ParsePushAdapter.spec.js index 639aa1f8..dce63100 100644 --- a/spec/ParsePushAdapter.spec.js +++ b/spec/ParsePushAdapter.spec.js @@ -1,6 +1,7 @@ var ParsePushAdapter = require('../src/index').ParsePushAdapter; var APNS = require('../src/APNS'); var GCM = require('../src/GCM'); +var WNS = require('../src/WNS'); describe('ParsePushAdapter', () => { it('can be initialized', (done) => { @@ -10,6 +11,11 @@ describe('ParsePushAdapter', () => { senderId: 'senderId', apiKey: 'apiKey' }, + wp: { + clientID: 'clientID', + clientSecret: 'clientSecret', + accessTokenPath: 'prodCert.pem' + }, ios: [ { cert: 'prodCert.pem', @@ -33,6 +39,10 @@ describe('ParsePushAdapter', () => { // Check android var androidSender = parsePushAdapter.senderMap['android']; expect(androidSender instanceof GCM).toBe(true); + // Check windows phone + var winSender = parsePushAdapter.senderMap['wp']; + expect(winSender instanceof WNS).toBe(true); + done(); }); @@ -54,13 +64,14 @@ describe('ParsePushAdapter', () => { it('can get valid push types', (done) => { var parsePushAdapter = new ParsePushAdapter(); - expect(parsePushAdapter.getValidPushTypes()).toEqual(['ios', 'android']); + expect(parsePushAdapter.getValidPushTypes()).toEqual(['ios', 'android','wp']); + done(); }); it('can classify installation', (done) => { // Mock installations - var validPushTypes = ['ios', 'android']; + var validPushTypes = ['ios', 'android', 'wp']; var installations = [ { deviceType: 'android', @@ -77,12 +88,17 @@ describe('ParsePushAdapter', () => { { deviceType: 'android', deviceToken: undefined - } + }, + { + deviceType: 'wp', + deviceToken: 'wpToken' + } ]; var deviceMap = ParsePushAdapter.classifyInstallations(installations, validPushTypes); expect(deviceMap['android']).toEqual([makeDevice('androidToken')]); expect(deviceMap['ios']).toEqual([makeDevice('iosToken')]); + expect(deviceMap['wp']).toEqual([makeDevice('wpToken')]); expect(deviceMap['win']).toBe(undefined); done(); }); @@ -96,10 +112,14 @@ describe('ParsePushAdapter', () => { }; var iosSender = { send: jasmine.createSpy('send') + }; + var wpSender = { + send: jasmine.createSpy('send') }; var senderMap = { ios: iosSender, - android: androidSender + android: androidSender, + wp: wpSender }; parsePushAdapter.senderMap = senderMap; // Mock installations @@ -119,7 +139,11 @@ describe('ParsePushAdapter', () => { { deviceType: 'android', deviceToken: undefined - } + }, + { + deviceType: 'wp', + deviceToken: 'wpToken' + } ]; var data = {}; @@ -137,6 +161,13 @@ describe('ParsePushAdapter', () => { expect(args[0]).toEqual(data); expect(args[1]).toEqual([ makeDevice('iosToken') + ]); + // Check wp sender + expect(wpSender.send).toHaveBeenCalled(); + args = wpSender.send.calls.first().args; + expect(args[0]).toEqual(data); + expect(args[1]).toEqual([ + makeDevice('wpToken') ]); done(); }); @@ -147,6 +178,11 @@ describe('ParsePushAdapter', () => { senderId: 'senderId', apiKey: 'apiKey' }, + wp: { + clientID: 'clientID', + clientSecret: 'clientSecret', + accessTokenPath: 'prodCert.pem' + }, ios: [ { cert: 'cert.cer', @@ -178,14 +214,18 @@ describe('ParsePushAdapter', () => { { deviceType: 'android', deviceToken: undefined - } + }, + { + deviceType: 'wp', + deviceToken: 'wpToken' + } ]; var parsePushAdapter = new ParsePushAdapter(pushConfig); parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { expect(Array.isArray(results)).toBe(true); - // 2x iOS, 1x android + // 2x iOS, 1x android, 1x wp8.1+ expect(results.length).toBe(3); results.forEach((result) => { expect(result.transmitted).toBe(false); diff --git a/src/WNS.js b/src/WNS.js index 794d6050..ce56873c 100644 --- a/src/WNS.js +++ b/src/WNS.js @@ -25,22 +25,18 @@ WNS.prototype.send = function(data, devices) devices = new Array(...devices); var currentAccessToken = fs.readFileSync(this.accessTokenPath); - if (typeof currentAccessToken !== 'string' || currentAccessToken.length == 0) - { + if (typeof currentAccessToken !== 'string' || currentAccessToken.length == 0) { log.verbose(LOG_PREFIX, `currentAccessToken not existed.get new access`); currentAccessToken = getNewAccessToken(); - if (currentAccessToken == false) - { + if (currentAccessToken == false) { log.error(LOG_PREFIX, `cannot get currentAccessToken From WNS`); return false; } - fs.writeFile(this.accessTokenPath,currentAccessToken,function(err) - { - if (err) - { + fs.writeFile(this.accessTokenPath,currentAccessToken,function(err) { + if (err) { log.error(LOG_PREFIX, `cannot write new currentAccessToken to %s`,this.accessTokenPath); } } @@ -52,14 +48,11 @@ WNS.prototype.send = function(data, devices) let promises = devices.map((device) => { return new Promise((resolve, reject) => { - wns.sendToastText02(device.wnsRequestURI,wnsPayload, - { + wns.sendToastText02(device.deviceToken,wnsPayload,{ client_id: this.clientID, client_secret: this.clientSecret, accessToken: currentAccessToken - }, - function (error, result) - { + }, function (error, result) { if (error) { log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4)); } else { @@ -70,37 +63,6 @@ WNS.prototype.send = function(data, devices) }); }); return Parse.Promise.when(promises); - -/* (function (i, len, count, callback) - { - for (; i < len; ++i) { - (function (i) - { - device = devices[i]; - wns.sendToastText02(device.wnsRequestURI,wnsPayload, - { - client_id: this.clientID, - client_secret: this.clientSecret, - accessToken: currentAccessToken - }, - function (error, result) - { - if (error) { - log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4)); - } else { - log.verbose(LOG_PREFIX, `WNS Response: %s`, JSON.stringify(response, null, 4)); - } - currentAccessToken = error ? error.newAccessToken : result.newAccessToken; - if (++count === len) { - callback(); - } - }); - }(i)); - } - }(0, devices.length , 0, function () - { - - }));*/ } WNS.prototype.getNewAccessToken = function() @@ -134,17 +96,14 @@ WNS.prototype.getNewAccessToken = function() res.on('end', function () { if (!completed) { completed = true; - if (res.statusCode === 200) - { + if (res.statusCode === 200) { var tokenResponse; - try - { + try{ tokenResponse = JSON.parse(body); if (typeof tokenResponse.access_token !== 'string' || tokenResponse.token_type !== 'bearer') throw new Error('Invalid response'); } - catch (e) - { + catch (e) { var error = new Error('Unable to obtain access token for WNS. Invalid response body: ' + body); error.statusCode = res.statusCode; error.headers = res.headers; @@ -156,8 +115,7 @@ WNS.prototype.getNewAccessToken = function() return newAccessToken; } - else - { + else { var error = new Error('Unable to obtain access token for WNS. HTTP status code: ' + res.statusCode + '. HTTP response body: ' + body); error.statusCode = res.statusCode; @@ -168,8 +126,7 @@ WNS.prototype.getNewAccessToken = function() }); }); - req.on('error', function (error) - { + req.on('error', function (error) { if (!completed) { completed = true; var result = new Error('Unable to send reqeust for access token to Windows Notification Service: ' + error.message); From 8601fcce616ad47f5887adf5c34b3995fbf7ecf5 Mon Sep 17 00:00:00 2001 From: RainbowLand Date: Tue, 24 May 2016 19:38:52 +0800 Subject: [PATCH 4/5] Fix issue when pem file not exist. --- spec/ParsePushAdapter.spec.js | 4 +- src/WNS.js | 95 +++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/spec/ParsePushAdapter.spec.js b/spec/ParsePushAdapter.spec.js index dce63100..16fec7bd 100644 --- a/spec/ParsePushAdapter.spec.js +++ b/spec/ParsePushAdapter.spec.js @@ -14,7 +14,7 @@ describe('ParsePushAdapter', () => { wp: { clientID: 'clientID', clientSecret: 'clientSecret', - accessTokenPath: 'prodCert.pem' + accessTokenPath: 'wpProdCert.pem' }, ios: [ { @@ -181,7 +181,7 @@ describe('ParsePushAdapter', () => { wp: { clientID: 'clientID', clientSecret: 'clientSecret', - accessTokenPath: 'prodCert.pem' + accessTokenPath: 'wpProdCert.pem' }, ios: [ { diff --git a/src/WNS.js b/src/WNS.js index ce56873c..8041b4af 100644 --- a/src/WNS.js +++ b/src/WNS.js @@ -22,47 +22,68 @@ function WNS(args) { WNS.prototype.send = function(data, devices) { - devices = new Array(...devices); - var currentAccessToken = fs.readFileSync(this.accessTokenPath); - - if (typeof currentAccessToken !== 'string' || currentAccessToken.length == 0) { - log.verbose(LOG_PREFIX, `currentAccessToken not existed.get new access`); - - currentAccessToken = getNewAccessToken(); - - if (currentAccessToken == false) { - log.error(LOG_PREFIX, `cannot get currentAccessToken From WNS`); - return false; + fs.access(this.accessTokenPath, fs.F_OK , (err) => { + if (err) { + fs.open( this.accessTokenPath , 'w' , (err,fd) => { + fs.close(fd , () => { + log.verbose(LOG_PREFIX, `create new accessToken file`); + + sendWNSNow(data,devices); + }); + } ); + } else { + sendWNSNow(data,devices); } - - fs.writeFile(this.accessTokenPath,currentAccessToken,function(err) { - if (err) { - log.error(LOG_PREFIX, `cannot write new currentAccessToken to %s`,this.accessTokenPath); - } - } - ); - } - - var wnsPayload = getWNSToastPayload(data.title,data.alert); - - let promises = devices.map((device) => { + }); +} - return new Promise((resolve, reject) => { - wns.sendToastText02(device.deviceToken,wnsPayload,{ - client_id: this.clientID, - client_secret: this.clientSecret, - accessToken: currentAccessToken - }, function (error, result) { - if (error) { - log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4)); +WNS.prototype.sendWNSNow(data,devices) +{ + devices = new Array(...devices); + + fs.readFile(this.accessTokenPath,(err,data) => { + if (err) { + log.verbose(LOG_PREFIX, `read accessToken file failed`); + } else { + currentAccessToken = data; + if (currentAccessToken.length == 0) { + log.verbose(LOG_PREFIX, `currentAccessToken not existed.get new access`); + + currentAccessToken = getNewAccessToken(); + + if (currentAccessToken == false) { + log.error(LOG_PREFIX, `cannot get currentAccessToken From WNS`); } else { - log.verbose(LOG_PREFIX, `WNS Response: %s`, JSON.stringify(response, null, 4)); + fs.writeFile(this.accessTokenPath,currentAccessToken,function(err) { + if (err) { + log.error(LOG_PREFIX, `cannot write new currentAccessToken to %s`,this.accessTokenPath); + } + }); } - currentAccessToken = error ? error.newAccessToken : result.newAccessToken; - }); - }); - }); - return Parse.Promise.when(promises); + + var wnsPayload = getWNSToastPayload(data.title,data.alert); + + let promises = devices.map((device) => { + return new Promise((resolve, reject) => { + wns.sendToastText02(device.deviceToken,wnsPayload,{ + client_id: this.clientID, + client_secret: this.clientSecret, + accessToken: currentAccessToken + }, function (error, result) { + if (error) { + log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4)); + } else { + log.verbose(LOG_PREFIX, `WNS Response: %s`, JSON.stringify(response, null, 4)); + } + currentAccessToken = error ? error.newAccessToken : result.newAccessToken; + }); + }); + }); + + return Parse.Promise.when(promises); + } + } +}); } WNS.prototype.getNewAccessToken = function() From 205fe04fbbf91727f2b387f40b5d6a820d33f7bb Mon Sep 17 00:00:00 2001 From: RainbowLand Date: Wed, 25 May 2016 17:38:48 +0800 Subject: [PATCH 5/5] Fix bugs --- src/WNS.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WNS.js b/src/WNS.js index 8041b4af..3f1d6e14 100644 --- a/src/WNS.js +++ b/src/WNS.js @@ -37,7 +37,7 @@ WNS.prototype.send = function(data, devices) }); } -WNS.prototype.sendWNSNow(data,devices) +WNS.prototype.sendWNSNow = function(data,devices) { devices = new Array(...devices);