Skip to content

Commit 3175bb8

Browse files
committed
Merge branch 'onIdTokenChanged'
# Conflicts: # CHANGELOG.md
2 parents 496b37f + 23a6289 commit 3175bb8

File tree

6 files changed

+248
-39
lines changed

6 files changed

+248
-39
lines changed

API.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ Authentication methods for simulating changes to the auth state of a Firebase re
179179
Changes the active authentication credentials to the `authData` object.
180180
Before changing the authentication state, `changeAuthState` checks the
181181
`user` object against the current authentication data.
182-
`onAuthStateChanged` listeners will only be triggered if the data is not
183-
deeply equal.
182+
`onIdTokenChanged` listeners will be triggered if the data is not
183+
deeply equal. `onAuthStateChanged` listeners will be triggered if the
184+
data is deeply equal but with different ID token validity.
184185

185186
`user` should be a `MockUser` object or an object with the same fields
186187
as `MockUser`. To simulate no user being authenticated, pass `null` for
@@ -229,9 +230,13 @@ Finds a user previously created with [`createUser`](https://www.firebase.com/doc
229230
##### `updateUser(user)` -> `Promise<MockUser>`
230231

231232
Replace the existing user with a new one, by matching uid. Throws an
232-
error if no user exists whose uid matches the given user's uid. Resolves
233-
with the updated user when complete. This operation is queued until the
234-
next flush.
233+
error if no user exists whose uid matches the given user's uid.
234+
Appropriate `onAuthStateChanged` and `onIdTokenChanged` listeners will
235+
be triggered if the new user has the same `uid` as the current
236+
authenticated user.
237+
238+
Resolves with the updated user when complete. This operation is queued
239+
until the next flush.
235240

236241
## Server Timestamps
237242

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
- Changelog
10+
- Mock `auth.Auth.onIdTokenChanged()` method, matching the previous
11+
behavior of `onAuthStateChanged()` (see below)
1012
- Support for Firebase Messaging (Admin API)
1113

1214
### Changed
15+
- (Breaking) Consistent with Firebase SDK [version 4.0.0](https://firebase.google.com/support/release-notes/js#version_500_-_may_8_2018) and later,
16+
and later, `onAuthStateChanged` no longer issues an event when a new
17+
ID token is issued for the same user. The `onIdTokenChanged` method is
18+
now mocked, keeping the previous behavior.
1319
- `MockStorageFile.download()` now allows omitting the destination arg;
1420
in that case, it simply resolves the `Promise` with the file contents
1521
and does not write it anywhere else.

src/firebase-auth.js

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function FirebaseAuth () {
99
this.currentUser = null;
1010
this._auth = {
1111
listeners: [],
12+
idTokenListeners: [],
1213
completionListeners: [],
1314
users: [],
1415
uidCounter: 1
@@ -17,10 +18,10 @@ function FirebaseAuth () {
1718

1819
FirebaseAuth.prototype.changeAuthState = function (userData) {
1920
this._defer('changeAuthState', _.toArray(arguments), function() {
20-
if (!_.isEqual(this.currentUser, userData)) {
21-
this.currentUser = _.isObject(userData) ? userData : null;
22-
this._triggerAuthEvent();
23-
}
21+
userData = _.isObject(userData) ? userData : null;
22+
const oldUser = _.cloneDeep(this.currentUser);
23+
this.currentUser = userData;
24+
this._notify_state_listeners(oldUser);
2425
});
2526
};
2627

@@ -47,6 +48,13 @@ FirebaseAuth.prototype.onAuthStateChanged = function (callback) {
4748
}
4849
};
4950

51+
FirebaseAuth.prototype.onIdTokenChanged = function (callback) {
52+
const currentUser = this.currentUser;
53+
this._auth.idTokenListeners.push({fn: callback});
54+
callback.call(null, _.cloneDeep(currentUser));
55+
return () => this.offAuth(callback);
56+
};
57+
5058
FirebaseAuth.prototype.getUserByEmail = function (email, onComplete) {
5159
var err = this._nextErr('getUserByEmail');
5260
var self = this;
@@ -100,6 +108,11 @@ FirebaseAuth.prototype.updateUser = function (newUser) {
100108
reject(new Error('Tried to update a nonexistent user'));
101109
} else {
102110
self._auth.users[i] = newUser.clone();
111+
if (this.currentUser && this.currentUser.uid === newUser.uid) {
112+
const oldUser = this.currentUser.clone();
113+
this.currentUser = self._auth.users[i];
114+
this._notify_state_listeners(oldUser);
115+
}
103116
resolve(newUser);
104117
}
105118
});
@@ -167,15 +180,14 @@ Object.keys(signinMethods)
167180
FirebaseAuth.prototype[method] = function () {
168181
var self = this;
169182
var user = getUser.apply(this, arguments);
170-
var promise = new Promise(function(resolve, reject) {
183+
return new Promise(function(resolve, reject) {
171184
self._authEvent(method, function(err) {
172185
if (err) reject(err);
173186
self.currentUser = user;
174187
resolve(user);
175188
self._triggerAuthEvent();
176189
}, true);
177190
});
178-
return promise;
179191
};
180192
});
181193

@@ -218,6 +230,15 @@ FirebaseAuth.prototype._triggerAuthEvent = function () {
218230
listeners.forEach(function (parts) {
219231
parts.fn.call(parts.context, _.cloneDeep(user));
220232
});
233+
this._triggerIdTokenEvent();
234+
};
235+
236+
FirebaseAuth.prototype._triggerIdTokenEvent = function () {
237+
var user = this.currentUser;
238+
var listeners = _.cloneDeep(this._auth.idTokenListeners);
239+
listeners.forEach(function (parts) {
240+
parts.fn.call(parts.context, _.cloneDeep(user));
241+
});
221242
};
222243

223244
FirebaseAuth.prototype._getUser = function (uid) {
@@ -239,12 +260,11 @@ FirebaseAuth.prototype.onAuth = function (onComplete, context) {
239260
};
240261

241262
FirebaseAuth.prototype.offAuth = function (onComplete, context) {
242-
var index = _.findIndex(this._auth.listeners, function (listener) {
263+
function shouldRemove(listener) {
243264
return listener.fn === onComplete && listener.context === context;
244-
});
245-
if (index > -1) {
246-
this._auth.listeners.splice(index, 1);
247265
}
266+
[this._auth.listeners, this._auth.idTokenListeners]
267+
.forEach(event => _.remove(event, shouldRemove));
248268
};
249269

250270
FirebaseAuth.prototype.unauth = function () {
@@ -256,7 +276,7 @@ FirebaseAuth.prototype.unauth = function () {
256276

257277
FirebaseAuth.prototype.signOut = function () {
258278
var self = this, updateuser = this.currentUser !== null;
259-
var promise = new Promise(function(resolve, reject) {
279+
return new Promise(function(resolve, reject) {
260280
self._authEvent('signOut', function(err) {
261281
if (err) reject(err);
262282
self.currentUser = null;
@@ -267,7 +287,6 @@ FirebaseAuth.prototype.signOut = function () {
267287
}
268288
}, true);
269289
});
270-
return promise;
271290
};
272291

273292
FirebaseAuth.prototype.createUserWithEmailAndPassword = function (email, password) {
@@ -502,14 +521,54 @@ FirebaseAuth.prototype.setCustomUserClaims = function (uid, claims) {
502521
});
503522
};
504523

524+
FirebaseAuth.prototype._notify_state_listeners = function (previousUser) {
525+
const difference = scanDifference(previousUser, this.currentUser);
526+
if (difference === 'different_user') {
527+
this._triggerAuthEvent();
528+
} else if(difference === 'different_token') {
529+
this._triggerIdTokenEvent();
530+
} else if (difference === 'same') {
531+
// do nothing
532+
} else {
533+
throw new Error('Unexpected result from scanDifference');
534+
}
535+
536+
function scanDifference(oldUser, newUser) {
537+
if (_.isObject(oldUser)) {
538+
if (_.isObject(newUser)) {
539+
if (_.isEqual(oldUser, newUser)) {
540+
return 'same';
541+
} else {
542+
return equalExceptToken(oldUser, newUser) ?
543+
'different_token' : 'different_user';
544+
}
545+
} else {
546+
return 'different_user';
547+
}
548+
} else {
549+
return _.isObject(newUser) ? 'different_user' : 'same';
550+
}
551+
}
552+
553+
function equalExceptToken(user1, user2) {
554+
const u1 = user1.clone();
555+
const u2 = user2.clone();
556+
delete u1._idtoken;
557+
delete u1._tokenValidity;
558+
delete u2._idtoken;
559+
delete u2._tokenValidity;
560+
return _.isEqual(u1, u2);
561+
}
562+
};
563+
505564
FirebaseAuth.prototype._nextUid = function () {
506565
return 'simplelogin:' + (this._auth.uidCounter++);
507566
};
508567

509568
FirebaseAuth.prototype._validateNewUid = function (uid) {
510569
if (uid) {
511570
var user = _.find(this._auth.users, function(user) {
512-
return user.uid == uid;
571+
return user.uid === uid;
513572
});
514573
if (user) {
515574
var err = new Error('The provided uid is already in use by an existing user. Each user must have a unique uid.');
@@ -523,7 +582,7 @@ FirebaseAuth.prototype._validateNewUid = function (uid) {
523582
FirebaseAuth.prototype._validateExistingUid = function (uid) {
524583
if (uid) {
525584
var user = _.find(this._auth.users, function(user) {
526-
return user.uid == uid;
585+
return user.uid === uid;
527586
});
528587
if (!user) {
529588
var err = new Error('There is no existing user record corresponding to the provided identifier.');

src/user.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,8 @@ MockFirebaseUser.prototype.updateProfile = function (profile) {
114114
};
115115

116116
MockFirebaseUser.prototype.getIdToken = function (forceRefresh) {
117-
var self = this;
118-
return new Promise(function(resolve) {
119-
if (forceRefresh) {
120-
self._refreshIdToken();
121-
}
122-
resolve(self._idtoken);
123-
});
117+
return (forceRefresh ? this._refreshIdToken() : Promise.resolve())
118+
.then(() => this._idtoken);
124119
};
125120

126121
MockFirebaseUser.prototype.toJSON = function() {

0 commit comments

Comments
 (0)