Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions spec/ParseInstallation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,110 @@ describe('Installations', () => {
});
});

it('clears deviceToken via Delete op without crashing the lookup', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const t = '11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306';
const input = {
installationId: installId,
deviceType: 'ios',
deviceToken: t,
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ deviceToken: { __op: 'Delete' } }
);
})
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
expect(results[0].deviceToken).toBeUndefined();
expect(results[0].installationId).toEqual(installId);
done();
})
.catch(err => {
jfail(err);
done();
});
});

it('clears deviceToken via null without crashing the lookup', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const t = '11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306';
const input = {
installationId: installId,
deviceType: 'ios',
deviceToken: t,
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ deviceToken: null }
);
})
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
expect(results[0].deviceToken == null).toBeTrue();
expect(results[0].installationId).toEqual(installId);
Comment on lines +605 to +606
done();
})
.catch(err => {
jfail(err);
done();
});
});

it('clears deviceToken alongside another field update', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const t = '11433856eed2f1285fb3aa11136718c1198ed5647875096952c66bf8cb976306';
const input = {
installationId: installId,
deviceType: 'ios',
deviceToken: t,
appVersion: '1',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ deviceToken: { __op: 'Delete' }, deviceType: 'ios', appVersion: '2' }
);
})
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
expect(results[0].deviceToken).toBeUndefined();
expect(results[0].appVersion).toEqual('2');
expect(results[0].installationId).toEqual(installId);
done();
})
.catch(err => {
jfail(err);
done();
});
});

it('update fails to change deviceType', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
let input = {
Expand Down
35 changes: 23 additions & 12 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -1295,9 +1295,19 @@ RestWrite.prototype.handleInstallation = function () {
return;
}

// A client clearing the deviceToken sends either null or { __op: 'Delete' }.
// Treat that as "no deviceToken to identify/match by" so we do not feed the
// operator object into the lookup query (which would fail Mongo transform).
const clearingDeviceToken =
this.data.deviceToken === null ||
(typeof this.data.deviceToken === 'object' &&
this.data.deviceToken !== null &&
this.data.deviceToken.__op === 'Delete');
Comment on lines +1299 to +1305
let deviceTokenForLookup = clearingDeviceToken ? undefined : this.data.deviceToken;

if (
!this.query &&
!this.data.deviceToken &&
!deviceTokenForLookup &&
!this.data.installationId &&
!this.auth.installationId
) {
Expand All @@ -1309,8 +1319,9 @@ RestWrite.prototype.handleInstallation = function () {

// If the device token is 64 characters long, we assume it is for iOS
// and lowercase it.
if (this.data.deviceToken && this.data.deviceToken.length == 64) {
this.data.deviceToken = this.data.deviceToken.toLowerCase();
if (deviceTokenForLookup && deviceTokenForLookup.length == 64) {
this.data.deviceToken = deviceTokenForLookup.toLowerCase();
deviceTokenForLookup = this.data.deviceToken;
}

// We lowercase the installationId if present
Expand All @@ -1330,7 +1341,7 @@ RestWrite.prototype.handleInstallation = function () {
}

// Updating _Installation but not updating anything critical
if (this.query && !this.data.deviceToken && !installationId && !this.data.deviceType) {
if (this.query && !deviceTokenForLookup && !installationId && !this.data.deviceType) {
return;
}

Expand All @@ -1353,8 +1364,8 @@ RestWrite.prototype.handleInstallation = function () {
installationId: installationId,
});
}
if (this.data.deviceToken) {
orQueries.push({ deviceToken: this.data.deviceToken });
if (deviceTokenForLookup) {
orQueries.push({ deviceToken: deviceTokenForLookup });
}

if (orQueries.length == 0) {
Expand All @@ -1379,7 +1390,7 @@ RestWrite.prototype.handleInstallation = function () {
if (result.installationId == installationId) {
installationIdMatch = result;
}
if (result.deviceToken == this.data.deviceToken) {
if (deviceTokenForLookup && result.deviceToken == deviceTokenForLookup) {
deviceTokenMatches.push(result);
}
});
Expand All @@ -1397,9 +1408,9 @@ RestWrite.prototype.handleInstallation = function () {
throw new Parse.Error(136, 'installationId may not be changed in this ' + 'operation');
}
if (
this.data.deviceToken &&
deviceTokenForLookup &&
objectIdMatch.deviceToken &&
this.data.deviceToken !== objectIdMatch.deviceToken &&
deviceTokenForLookup !== objectIdMatch.deviceToken &&
!this.data.installationId &&
!objectIdMatch.installationId
) {
Expand Down Expand Up @@ -1451,7 +1462,7 @@ RestWrite.prototype.handleInstallation = function () {
// deviceToken, and return nil to signal that a new object should be
// created.
const delQuery = {
deviceToken: this.data.deviceToken,
deviceToken: deviceTokenForLookup,
installationId: {
$ne: installationId,
},
Expand Down Expand Up @@ -1486,12 +1497,12 @@ RestWrite.prototype.handleInstallation = function () {
validSchemaController: this.validSchemaController,
});
} else {
if (this.data.deviceToken && idMatch.deviceToken != this.data.deviceToken) {
if (deviceTokenForLookup && idMatch.deviceToken != deviceTokenForLookup) {
// We're setting the device token on an existing installation, so
// we should try cleaning out old installations that match this
// device token.
const delQuery = {
deviceToken: this.data.deviceToken,
deviceToken: deviceTokenForLookup,
};
// We have a unique install Id, use that to preserve
// the interesting installation
Expand Down
Loading