From a1b24da3e7e73341d004cddb371c67cf7250c36b Mon Sep 17 00:00:00 2001 From: George Deglin Date: Fri, 12 Feb 2016 18:32:39 -0800 Subject: [PATCH 1/7] WIP Add OneSignal Adapter --- spec/OneSignalPushAdapter.spec.js | 1 + spec/ParseInstallation.spec.js | 20 --- src/Adapters/Push/OneSignalPushAdapter.js | 209 ++++++++++++++++++++++ src/RestWrite.js | 5 - 4 files changed, 210 insertions(+), 25 deletions(-) create mode 100644 spec/OneSignalPushAdapter.spec.js create mode 100644 src/Adapters/Push/OneSignalPushAdapter.js diff --git a/spec/OneSignalPushAdapter.spec.js b/spec/OneSignalPushAdapter.spec.js new file mode 100644 index 00000000..8371f3a2 --- /dev/null +++ b/spec/OneSignalPushAdapter.spec.js @@ -0,0 +1 @@ +//todo \ No newline at end of file diff --git a/spec/ParseInstallation.spec.js b/spec/ParseInstallation.spec.js index 91bb9a23..cef6871e 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -133,26 +133,6 @@ describe('Installations', () => { }); }); - it('fails for android with device token', (done) => { - var installId = '12345678-abcd-abcd-abcd-123456789abc'; - var t = '11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306'; - var device = 'android'; - var input = { - 'installationId': installId, - 'deviceType': device, - 'deviceToken': t, - 'channels': ['foo', 'bar'] - }; - rest.create(config, auth.nobody(config), '_Installation', input) - .then(() => { - fail('Should not have been able to create an Installation.'); - done(); - }).catch((error) => { - expect(error.code).toEqual(114); - done(); - }); - }); - it('fails for android with missing type', (done) => { var installId = '12345678-abcd-abcd-abcd-123456789abc'; var input = { diff --git a/src/Adapters/Push/OneSignalPushAdapter.js b/src/Adapters/Push/OneSignalPushAdapter.js new file mode 100644 index 00000000..73f61ce8 --- /dev/null +++ b/src/Adapters/Push/OneSignalPushAdapter.js @@ -0,0 +1,209 @@ +"use strict"; +// ParsePushAdapter is the default implementation of +// PushAdapter, it uses GCM for android push and APNS +// for ios push. + +const Parse = require('parse/node').Parse; + +function OneSignalPushAdapter(pushConfig) { + this.https = require('https'); + + this.validPushTypes = ['ios', 'gcm','android']; + this.senderMap = {}; + + pushConfig = pushConfig || {}; + this.OneSignalConfig = {}; + this.OneSignalConfig['appId'] = pushConfig['oneSignalAppId']; + this.OneSignalConfig['apiKey'] = pushConfig['oneSignalApiKey']; + + this.senderMap['ios'] = this.sendToAPNS.bind(this); + this.senderMap['gcm'] = this.sendToGCM.bind(this); + this.senderMap['android'] = this.sendToGCM.bind(this); +} + +/** + * Get an array of valid push types. + * @returns {Array} An array of valid push types + */ +OneSignalPushAdapter.prototype.getValidPushTypes = function() { + return this.validPushTypes; +} + +OneSignalPushAdapter.prototype.send = function(data, installations) { + let deviceMap = classifyInstallation(installations, this.validPushTypes); + + let sendPromises = []; + for (let pushType in deviceMap) { + let sender = this.senderMap[pushType]; + if (!sender) { + console.log('Can not find sender for push type %s, %j', pushType, data); + continue; + } + let devices = deviceMap[pushType]; + + if(devices.length > 0) { + sendPromises.push(sender(data, devices)); + } + } + return Parse.Promise.when(sendPromises); +} + +OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { + + let post = {}; + if(data['badge']) { + if(data['badge'] == "Increment") { + post['ios_badgeType'] = 'Increase'; + post['ios_badgeCount'] = 1; + } else { + post['ios_badgeType'] = 'SetTo'; + post['ios_badgeCount'] = data['badge']; + } + delete data['badge']; + } + if(data['alert']) { + post['contents'] = {en: data['alert']}; + delete data['alert']; + } + if(data['sound']) { + post['ios_sound'] = data['sound']; + delete data['sound']; + } + if(data['content-available'] == 1) { + post['content_available'] = true; + delete data['content-available']; + } + post['data'] = data; + + let promise = new Parse.Promise(); + + var chunk = 2000 // OneSignal can process 2000 devices at a time + var tokenlength=tokens.length; + var offset = 0 + // handle onesignal response. Start next batch if there's not an error. + let handleResponse = function(err) { + if (err) { + return promise.reject(err, tokens.slice(i, tokens.length())); + } + + if(offset => tokenlength) { + promise.resolve() + } else { + this.sendNext(); + } + }.bind(this) + + this.sendNext = function() { + post['include_android_reg_ids'] = tokens.slice(offset,offset+chunk); + offset+=chunk; + this.sendToOneSignal(post, handleResponse); + }.bind(this) + + this.sendNext() + + return promise; +} + +OneSignalPushAdapter.prototype.sendToGCM = function(data,tokens) { + let post = {}; + + if(data['alert']) { + post['contents'] = {en: data['alert']}; + delete data['alert']; + } + if(data['title']) { + post['title'] = {en: data['title']}; + delete data['title']; + } + if(data['uri']) { + post['url'] = data['uri']; + } + + post['data'] = data; + + let promise = new Parse.Promise(); + + var chunk = 2000 // OneSignal can process 2000 devices at a time + var tokenlength=tokens.length; + var offset = 0 + // handle onesignal response. Start next batch if there's not an error. + let handleResponse = function(err) { + if (err) { + return promise.reject(err, tokens.slice(i, tokens.length())); + } + + if(offset => tokenlength) { + promise.resolve() + } else { + this.sendNext(); + } + }.bind(this); + + this.sendNext = function() { + post['include_android_reg_ids'] = tokens.slice(offset,offset+chunk);; + offset+=chunk; + this.sendToOneSignal(post, handleResponse); + }.bind(this); + + this.sendNext(); + return promise; +} + + +OneSignalPushAdapter.prototype.sendToOneSignal = function(data, cb) { + let headers = { + "Content-Type": "application/json", + "Authorization": "Basic "+this.OneSignalConfig['apiKey'] + }; + let options = { + host: "onesignal.com", + port: 443, + path: "/api/v1/notifications", + method: "POST", + headers: headers + }; + data['app_id'] = this.OneSignalConfig['appId']; + + let request = this.https.request(options, function(res) { + cb(null); + }); + request.on('error', function(e) { + cb(e); + }); + console.log(data); + request.write(JSON.stringify(data)) + request.end(); +} +/**g + * Classify the device token of installations based on its device type. + * @param {Object} installations An array of installations + * @param {Array} validPushTypes An array of valid push types(string) + * @returns {Object} A map whose key is device type and value is an array of device + */ +function classifyInstallation(installations, validPushTypes) { + // Init deviceTokenMap, create a empty array for each valid pushType + let deviceMap = {}; + for (let validPushType of validPushTypes) { + deviceMap[validPushType] = []; + } + for (let installation of installations) { + // No deviceToken, ignore + if (!installation.deviceToken) { + continue; + } + let pushType = installation.deviceType; + if (deviceMap[pushType]) { + deviceMap[pushType].push({ + deviceToken: installation.deviceToken + }); + } else { + console.log('Unknown push type from installation %j', installation); + } + } + return deviceMap; +} + +if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { + OneSignalPushAdapter.classifyInstallation = classifyInstallation; +} +module.exports = OneSignalPushAdapter; diff --git a/src/RestWrite.js b/src/RestWrite.js index 2a2b0ed2..b6c8f126 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -485,11 +485,6 @@ RestWrite.prototype.handleInstallation = function() { this.data.installationId = this.data.installationId.toLowerCase(); } - if (this.data.deviceToken && this.data.deviceType == 'android') { - throw new Parse.Error(114, - 'deviceToken may not be set for deviceType android'); - } - var promise = Promise.resolve(); if (this.query && this.query.objectId) { From ce4f13f3bf95a52af807fcde832f78f9006356d9 Mon Sep 17 00:00:00 2001 From: Lewuathe Date: Sat, 13 Feb 2016 17:44:43 +0900 Subject: [PATCH 2/7] Limit 100 records as default --- spec/ParseAPI.spec.js | 16 ++++++++++++++++ src/Routers/ClassesRouter.js | 2 ++ 2 files changed, 18 insertions(+) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 8670bdd2..fa11a307 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -129,6 +129,22 @@ describe('miscellaneous', function() { }); }); + it('query without limit get default 100 records', function(done) { + var objects = []; + for (var i = 0; i < 150; i++) { + objects.push(new TestObject({name: 'name' + i})); + } + Parse.Object.saveAll(objects).then(() => { + return new Parse.Query(TestObject).find(); + }).then((results) => { + expect(results.length).toEqual(100); + done(); + }, (error) => { + fail(error); + done(); + }); + }); + it('basic saveAll', function(done) { var alpha = new TestObject({ letter: 'alpha' }); var beta = new TestObject({ letter: 'beta' }); diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index a49d6d4a..c9fe9c48 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -12,6 +12,8 @@ export class ClassesRouter { } if (body.limit) { options.limit = Number(body.limit); + } else { + options.limit = Number(100); } if (body.order) { options.order = String(body.order); From 5a628516a688d668d2d54b7f459e332c746d9c84 Mon Sep 17 00:00:00 2001 From: George Deglin Date: Sat, 13 Feb 2016 18:26:17 -0800 Subject: [PATCH 3/7] OneSignalPushAdapter now correctly sends APNS and GCM notifications and handles errors --- src/Adapters/Push/OneSignalPushAdapter.js | 55 ++++++++++++++++------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/src/Adapters/Push/OneSignalPushAdapter.js b/src/Adapters/Push/OneSignalPushAdapter.js index 73f61ce8..aac8bdb0 100644 --- a/src/Adapters/Push/OneSignalPushAdapter.js +++ b/src/Adapters/Push/OneSignalPushAdapter.js @@ -30,6 +30,7 @@ OneSignalPushAdapter.prototype.getValidPushTypes = function() { } OneSignalPushAdapter.prototype.send = function(data, installations) { + console.log("Sending notification to "+installations.length+" devices.") let deviceMap = classifyInstallation(installations, this.validPushTypes); let sendPromises = []; @@ -50,7 +51,9 @@ OneSignalPushAdapter.prototype.send = function(data, installations) { OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { - let post = {}; + data= data['data'] + + var post = {}; if(data['badge']) { if(data['badge'] == "Increment") { post['ios_badgeType'] = 'Increase'; @@ -81,12 +84,12 @@ OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { var tokenlength=tokens.length; var offset = 0 // handle onesignal response. Start next batch if there's not an error. - let handleResponse = function(err) { - if (err) { - return promise.reject(err, tokens.slice(i, tokens.length())); + let handleResponse = function(wasSuccessful) { + if (!wasSuccessful) { + return promise.reject("OneSignal Error"); } - if(offset => tokenlength) { + if(offset >= tokenlength) { promise.resolve() } else { this.sendNext(); @@ -94,7 +97,10 @@ OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { }.bind(this) this.sendNext = function() { - post['include_android_reg_ids'] = tokens.slice(offset,offset+chunk); + post['include_ios_tokens'] = []; + tokens.slice(offset,offset+chunk).forEach(function(i) { + post['include_ios_tokens'].push(i['deviceToken']) + }) offset+=chunk; this.sendToOneSignal(post, handleResponse); }.bind(this) @@ -105,7 +111,9 @@ OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { } OneSignalPushAdapter.prototype.sendToGCM = function(data,tokens) { - let post = {}; + data= data['data'] + + var post = {}; if(data['alert']) { post['contents'] = {en: data['alert']}; @@ -127,23 +135,27 @@ OneSignalPushAdapter.prototype.sendToGCM = function(data,tokens) { var tokenlength=tokens.length; var offset = 0 // handle onesignal response. Start next batch if there's not an error. - let handleResponse = function(err) { - if (err) { - return promise.reject(err, tokens.slice(i, tokens.length())); + let handleResponse = function(wasSuccessful) { + if (!wasSuccessful) { + return promise.reject("OneSIgnal Error"); } - if(offset => tokenlength) { + if(offset >= tokenlength) { promise.resolve() } else { this.sendNext(); } }.bind(this); - this.sendNext = function() { - post['include_android_reg_ids'] = tokens.slice(offset,offset+chunk);; + this.sendNext = function() { + post['include_android_reg_ids'] = []; + tokens.slice(offset,offset+chunk).forEach(function(i) { + post['include_android_reg_ids'].push(i['deviceToken']) + }) offset+=chunk; this.sendToOneSignal(post, handleResponse); - }.bind(this); + }.bind(this) + this.sendNext(); return promise; @@ -165,12 +177,21 @@ OneSignalPushAdapter.prototype.sendToOneSignal = function(data, cb) { data['app_id'] = this.OneSignalConfig['appId']; let request = this.https.request(options, function(res) { - cb(null); + if(res.statusCode < 299) { + cb(true); + } else { + console.log('OneSignal Error'); + res.on('data', function(chunk) { + console.log(chunk.toString()) + }); + cb(false) + } }); request.on('error', function(e) { - cb(e); + console.log("Error connecting to OneSignal") + console.log(e); + cb(false); }); - console.log(data); request.write(JSON.stringify(data)) request.end(); } From 2ff6eff63a35e7c1e863ff4b7028648b6ff14792 Mon Sep 17 00:00:00 2001 From: George Deglin Date: Sat, 13 Feb 2016 23:38:39 -0800 Subject: [PATCH 4/7] Added OneSignalPushAdapter spec and fix a bug in OneSignalPushAdapter. --- spec/OneSignalPushAdapter.spec.js | 235 +++++++++++++++++++++- src/Adapters/Push/OneSignalPushAdapter.js | 8 +- 2 files changed, 238 insertions(+), 5 deletions(-) diff --git a/spec/OneSignalPushAdapter.spec.js b/spec/OneSignalPushAdapter.spec.js index 8371f3a2..e7f31768 100644 --- a/spec/OneSignalPushAdapter.spec.js +++ b/spec/OneSignalPushAdapter.spec.js @@ -1 +1,234 @@ -//todo \ No newline at end of file + +var OneSignalPushAdapter = require('../src/Adapters/Push/OneSignalPushAdapter'); + +describe('OneSignalPushAdapter', () => { + it('can be initialized', (done) => { + // Make mock config + var pushConfig = { + oneSignalAppId:"APP ID", + oneSignalApiKey:"API KEY" + }; + + var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig); + + var senderMap = oneSignalPushAdapter.senderMap; + + expect(senderMap.ios instanceof Function).toBe(true); + expect(senderMap.android instanceof Function).toBe(true); + done(); + }); + + it('can get valid push types', (done) => { + var oneSignalPushAdapter = new OneSignalPushAdapter(); + + expect(oneSignalPushAdapter.getValidPushTypes()).toEqual(['ios', 'android']); + done(); + }); + + it('can classify installation', (done) => { + // Mock installations + var validPushTypes = ['ios', 'android']; + var installations = [ + { + deviceType: 'android', + deviceToken: 'androidToken' + }, + { + deviceType: 'ios', + deviceToken: 'iosToken' + }, + { + deviceType: 'win', + deviceToken: 'winToken' + }, + { + deviceType: 'android', + deviceToken: undefined + } + ]; + + var deviceMap = OneSignalPushAdapter.classifyInstallation(installations, validPushTypes); + expect(deviceMap['android']).toEqual([makeDevice('androidToken')]); + expect(deviceMap['ios']).toEqual([makeDevice('iosToken')]); + expect(deviceMap['win']).toBe(undefined); + done(); + }); + + + it('can send push notifications', (done) => { + var oneSignalPushAdapter = new OneSignalPushAdapter(); + + // Mock android ios senders + var androidSender = jasmine.createSpy('send') + var iosSender = jasmine.createSpy('send') + + var senderMap = { + ios: iosSender, + android: androidSender + }; + oneSignalPushAdapter.senderMap = senderMap; + + // Mock installations + var installations = [ + { + deviceType: 'android', + deviceToken: 'androidToken' + }, + { + deviceType: 'ios', + deviceToken: 'iosToken' + }, + { + deviceType: 'win', + deviceToken: 'winToken' + }, + { + deviceType: 'android', + deviceToken: undefined + } + ]; + var data = {}; + + oneSignalPushAdapter.send(data, installations); + // Check android sender + expect(androidSender).toHaveBeenCalled(); + var args = androidSender.calls.first().args; + expect(args[0]).toEqual(data); + expect(args[1]).toEqual([ + makeDevice('androidToken') + ]); + // Check ios sender + expect(iosSender).toHaveBeenCalled(); + args = iosSender.calls.first().args; + expect(args[0]).toEqual(data); + expect(args[1]).toEqual([ + makeDevice('iosToken') + ]); + done(); + }); + + it("can send iOS notifications", (done) => { + var oneSignalPushAdapter = new OneSignalPushAdapter(); + var sendToOneSignal = jasmine.createSpy('sendToOneSignal'); + oneSignalPushAdapter.sendToOneSignal = sendToOneSignal; + + oneSignalPushAdapter.sendToAPNS({'data':{ + 'badge': 1, + 'alert': "Example content", + 'sound': "Example sound", + 'content-available': 1, + 'misc-data': 'Example Data' + }},[{'deviceToken':'iosToken1'},{'deviceToken':'iosToken2'}]) + + expect(sendToOneSignal).toHaveBeenCalled(); + var args = sendToOneSignal.calls.first().args; + expect(args[0]).toEqual({ + 'ios_badgeType':'SetTo', + 'ios_badgeCount':1, + 'contents': { 'en':'Example content'}, + 'ios_sound': 'Example sound', + 'content_available':true, + 'data':{'misc-data':'Example Data'}, + 'include_ios_tokens':['iosToken1','iosToken2'] + }) + done(); + }); + + it("can send Android notifications", (done) => { + var oneSignalPushAdapter = new OneSignalPushAdapter(); + var sendToOneSignal = jasmine.createSpy('sendToOneSignal'); + oneSignalPushAdapter.sendToOneSignal = sendToOneSignal; + + oneSignalPushAdapter.sendToGCM({'data':{ + 'title': 'Example title', + 'alert': 'Example content', + 'misc-data': 'Example Data' + }},[{'deviceToken':'androidToken1'},{'deviceToken':'androidToken2'}]) + + expect(sendToOneSignal).toHaveBeenCalled(); + var args = sendToOneSignal.calls.first().args; + expect(args[0]).toEqual({ + 'contents': { 'en':'Example content'}, + 'title': {'en':'Example title'}, + 'data':{'misc-data':'Example Data'}, + 'include_android_reg_ids': ['androidToken1','androidToken2'] + }) + done(); + }); + + it("can post the correct data", (done) => { + var pushConfig = { + oneSignalAppId:"APP ID", + oneSignalApiKey:"API KEY" + }; + var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig); + + var write = jasmine.createSpy('write'); + oneSignalPushAdapter.https = { + 'request': function(a,b) { + return { + 'end':function(){}, + 'on':function(a,b){}, + 'write':write + } + } + }; + + var installations = [ + { + deviceType: 'android', + deviceToken: 'androidToken' + }, + { + deviceType: 'ios', + deviceToken: 'iosToken' + }, + { + deviceType: 'win', + deviceToken: 'winToken' + }, + { + deviceType: 'android', + deviceToken: undefined + } + ]; + + oneSignalPushAdapter.send({'data':{ + 'title': 'Example title', + 'alert': 'Example content', + 'content-available':1, + 'misc-data': 'Example Data' + }}, installations); + + expect(write).toHaveBeenCalled(); + + // iOS + args = write.calls.first().args; + expect(args[0]).toEqual(JSON.stringify({ + 'contents': { 'en':'Example content'}, + 'content_available':true, + 'data':{'title':'Example title','misc-data':'Example Data'}, + 'include_ios_tokens':['iosToken'], + 'app_id':'APP ID' + })); + + // Android + args = write.calls.mostRecent().args; + expect(args[0]).toEqual(JSON.stringify({ + 'contents': { 'en':'Example content'}, + 'title': {'en':'Example title'}, + 'data':{"content-available":1,'misc-data':'Example Data'}, + 'include_android_reg_ids':['androidToken'], + 'app_id':'APP ID' + })); + + done(); + }); + + function makeDevice(deviceToken, appIdentifier) { + return { + deviceToken: deviceToken + }; + } + +}); diff --git a/src/Adapters/Push/OneSignalPushAdapter.js b/src/Adapters/Push/OneSignalPushAdapter.js index aac8bdb0..59a660f9 100644 --- a/src/Adapters/Push/OneSignalPushAdapter.js +++ b/src/Adapters/Push/OneSignalPushAdapter.js @@ -4,11 +4,12 @@ // for ios push. const Parse = require('parse/node').Parse; +var deepcopy = require('deepcopy'); function OneSignalPushAdapter(pushConfig) { this.https = require('https'); - this.validPushTypes = ['ios', 'gcm','android']; + this.validPushTypes = ['ios', 'android']; this.senderMap = {}; pushConfig = pushConfig || {}; @@ -17,7 +18,6 @@ function OneSignalPushAdapter(pushConfig) { this.OneSignalConfig['apiKey'] = pushConfig['oneSignalApiKey']; this.senderMap['ios'] = this.sendToAPNS.bind(this); - this.senderMap['gcm'] = this.sendToGCM.bind(this); this.senderMap['android'] = this.sendToGCM.bind(this); } @@ -51,7 +51,7 @@ OneSignalPushAdapter.prototype.send = function(data, installations) { OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { - data= data['data'] + data= deepcopy(data['data']); var post = {}; if(data['badge']) { @@ -111,7 +111,7 @@ OneSignalPushAdapter.prototype.sendToAPNS = function(data,tokens) { } OneSignalPushAdapter.prototype.sendToGCM = function(data,tokens) { - data= data['data'] + data= deepcopy(data['data']); var post = {}; From ea07eb506d8f08d6e2a467414872d9d3d3f975ee Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 15 Feb 2016 10:12:53 -0500 Subject: [PATCH 5/7] Clears session on password change - Fixes error type when passing an invalid session token --- spec/ParseUser.spec.js | 4 +++- src/RestWrite.js | 2 +- src/Routers/UsersRouter.js | 8 +++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 787a8ecb..e173f9f8 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -1606,7 +1606,9 @@ describe('Parse.User testing', () => { }).then(function(newUser) { fail('Session should have been invalidated'); done(); - }, function() { + }, function(err) { + expect(err.code).toBe(209); + expect(err.message).toBe('invalid session token'); done(); }); }); diff --git a/src/RestWrite.js b/src/RestWrite.js index 2a2b0ed2..34e7ae94 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -306,7 +306,7 @@ RestWrite.prototype.transformUser = function() { if (!this.data.password) { return; } - if (this.query) { + if (this.query && !this.auth.isMaster ) { this.storage['clearSessions'] = true; } return passwordCrypto.hash(this.data.password).then((hashedPassword) => { diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 5b894f75..c6ba1c1b 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -41,8 +41,7 @@ export class UsersRouter extends ClassesRouter { handleMe(req) { if (!req.info || !req.info.sessionToken) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.'); + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); } return rest.find(req.config, Auth.master(req.config), '_Session', { _session_token: req.info.sessionToken }, @@ -51,8 +50,7 @@ export class UsersRouter extends ClassesRouter { if (!response.results || response.results.length == 0 || !response.results[0].user) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.'); + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token'); } else { let user = response.results[0].user; return { response: user }; @@ -145,10 +143,10 @@ export class UsersRouter extends ClassesRouter { let router = new PromiseRouter(); router.route('GET', '/users', req => { return this.handleFind(req); }); router.route('POST', '/users', req => { return this.handleCreate(req); }); + router.route('GET', '/users/me', req => { return this.handleMe(req); }); router.route('GET', '/users/:objectId', req => { return this.handleGet(req); }); router.route('PUT', '/users/:objectId', req => { return this.handleUpdate(req); }); router.route('DELETE', '/users/:objectId', req => { return this.handleDelete(req); }); - router.route('GET', '/users/me', req => { return this.handleMe(req); }); router.route('GET', '/login', req => { return this.handleLogIn(req); }); router.route('POST', '/logout', req => { return this.handleLogOut(req); }); router.route('POST', '/requestPasswordReset', () => { From be92b4af6710182ec0829cdb90a4f1c33ae0c333 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 15 Feb 2016 10:21:01 -0500 Subject: [PATCH 6/7] Adds test to make sure Parse.User.become is functional --- spec/ParseUser.spec.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index e173f9f8..9d62f832 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -1607,11 +1607,32 @@ describe('Parse.User testing', () => { fail('Session should have been invalidated'); done(); }, function(err) { - expect(err.code).toBe(209); + expect(err.code).toBe(Parse.Error.INVALID_SESSION_TOKEN); expect(err.message).toBe('invalid session token'); done(); }); }); + + it('test parse user become', (done) => { + var sessionToken = null; + Parse.Promise.as().then(function() { + return Parse.User.signUp("flessard", "folo",{'foo':1}); + }).then(function(newUser) { + equal(Parse.User.current(), newUser); + sessionToken = newUser.getSessionToken(); + ok(sessionToken); + newUser.set('foo',2); + return newUser.save(); + }).then(function() { + return Parse.User.become(sessionToken); + }).then(function(newUser) { + equal(newUser.get('foo'), 2); + done(); + }, function(e) { + fail('The session should still be valid'); + done(); + }); + }); it('ensure logout works', (done) => { var user = null; From 8296d77f2889d1384d1437d27838beefc6022e8e Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 15 Feb 2016 22:18:19 -0500 Subject: [PATCH 7/7] Adds ability to pass qs params to cloud code functions --- spec/ParseAPI.spec.js | 29 +++++++++++++++++++++++++++++ src/functions.js | 7 +++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 8670bdd2..10f6b5f6 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -571,6 +571,35 @@ describe('miscellaneous', function() { done(); }); }); + + it('test cloud function query parameters', (done) => { + Parse.Cloud.define('echoParams', (req, res) => { + res.success(req.params); + }); + var headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': 'test', + 'X-Parse-Javascript-Key': 'test' + }; + request.post({ + headers: headers, + url: 'http://localhost:8378/1/functions/echoParams', //?option=1&other=2 + qs: { + option: 1, + other: 2 + }, + body: '{"foo":"bar", "other": 1}' + }, (error, response, body) => { + expect(error).toBe(null); + var res = JSON.parse(body).result; + expect(res.option).toEqual('1'); + // Make sure query string params override body params + expect(res.other).toEqual('2'); + expect(res.foo).toEqual("bar"); + delete Parse.Cloud.Functions['echoParams']; + done(); + }); + }); it('test cloud function parameter validation success', (done) => { // Register a function with validation diff --git a/src/functions.js b/src/functions.js index f8b8fbc9..c787a814 100644 --- a/src/functions.js +++ b/src/functions.js @@ -9,8 +9,11 @@ var router = new PromiseRouter(); function handleCloudFunction(req) { if (Parse.Cloud.Functions[req.params.functionName]) { + + const params = Object.assign({}, req.body, req.query); + if (Parse.Cloud.Validators[req.params.functionName]) { - var result = Parse.Cloud.Validators[req.params.functionName](req.body || {}); + var result = Parse.Cloud.Validators[req.params.functionName](params); if (!result) { throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Validation failed.'); } @@ -19,7 +22,7 @@ function handleCloudFunction(req) { return new Promise(function (resolve, reject) { var response = createResponseObject(resolve, reject); var request = { - params: req.body || {}, + params: params, master: req.auth && req.auth.isMaster, user: req.auth && req.auth.user, installationId: req.info.installationId