Skip to content

Commit 2b9065b

Browse files
Googlerbojeil-google
authored andcommitted
Adds firebaseui.auth.AuthUI.getInstance(appId) to get the corresponding AuthUI instance as specified by the provided appId.
Adds a destroy method to destroy an AuthUI instance. Fixes Nascar buttons getting grayed when user tabs through them. Hides cancel button when only Email provider is used and accountchooser.com is disabled. PiperOrigin-RevId: 162376230 Change-Id: Ie7cc3c0f86fef05bf5322af342cc1abc2e6037d1
1 parent 3511c91 commit 2b9065b

18 files changed

+508
-70
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,26 @@ middle of performing a sign-in flow. You should generally avoid re-rendering the
800800
widget in the middle of an action, but if you do, to avoid the warning, you
801801
should use the `reset()` method before re-rendering the widget.
802802

803+
### Tips for initializing a new UI instance with the same Auth instance
804+
805+
When trying to initialize a new UI widget with the same Auth instance, you will
806+
get an `app/duplicate-app` error. In general, you should keep a reference to
807+
the AuthUI instance and instead call `reset()` and then `start(...)` again to
808+
re-render the widget.
809+
810+
If you don't keep a reference to that AuthUI instance, you can get the reference
811+
by calling `firebaseui.auth.AuthUI.getInstance(appId)` where `appId` is the same
812+
as the optional one used to initialize the AuthUI instance. If none was provided
813+
just call `firebaseui.auth.AuthUI.getInstance()`.
814+
815+
This is the recommended way but you also have the option to delete the AuthUI
816+
instance by calling `ui.delete()` which returns a promise that resolves on
817+
successful deletion. You can then initialize a new UI instance with the same
818+
Auth instance without getting the `app/duplicate-app` error. At any time, you
819+
can only have one AuthUI instance with the same `appId` or the same Auth
820+
instance.
821+
822+
803823
### FirebaseUI is broken in IE11 when deployed on a local server accessed through `localhost` (but works when deployed on a remote server)
804824

805825
Several developers reported issues with IE11 when testing the widget integration on a server deployed locally, accessing the application through a `localhost` address. However, it doesn't impact applications deployed on a server (as you can verify in the [demo app](https://fir-ui-demo-84a6c.firebaseapp.com/)).

changelog.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
feature - Adds firebaseui.auth.AuthUI.getInstance(appId) to get the corresponding AuthUI instance as specified by the provided appId.
2+
feature - Adds a destroy method to destroy an AuthUI instance.
3+
fixed - Fixes Nascar buttons getting grayed when user tabs through them.
4+
fixed - Hides cancel button when only Email provider is used and accountchooser.com is disabled.

javascript/testing/appclient.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ goog.setTestOnly();
2222

2323
var Disposable = goog.require('goog.Disposable');
2424
var FakeAuthClient = goog.require('firebaseui.auth.testing.FakeAuthClient');
25+
var GoogPromise = goog.require('goog.Promise');
2526

2627

2728

@@ -48,4 +49,13 @@ FakeAppClient.prototype.auth = function() {
4849
return this.auth_;
4950
};
5051

52+
53+
/**
54+
* Dummy app delete method.
55+
* @return {!GoogPromise} The promise that resolves upon deletion.
56+
*/
57+
FakeAppClient.prototype.delete = function() {
58+
return GoogPromise.resolve();
59+
};
60+
5161
exports = FakeAppClient;

javascript/ui/page/signin.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ goog.require('goog.dom.selection');
3131
* UI component for the user to enter their email.
3232
* @param {function()} onEmailEnter Callback to invoke when enter key (or its
3333
* equivalent) is detected.
34-
* @param {function()} onCancelClick Callback to invoke when cancel button
34+
* @param {?function()=} opt_onCancelClick Callback to invoke when cancel button
3535
* is clicked.
3636
* @param {string=} opt_email The email to prefill.
3737
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
@@ -40,28 +40,27 @@ goog.require('goog.dom.selection');
4040
*/
4141
firebaseui.auth.ui.page.SignIn = function(
4242
onEmailEnter,
43-
onCancelClick,
43+
opt_onCancelClick,
4444
opt_email,
4545
opt_domHelper) {
4646
firebaseui.auth.ui.page.SignIn.base(
4747
this,
4848
'constructor',
4949
firebaseui.auth.soy2.page.signIn,
50-
{email: opt_email},
50+
{email: opt_email, displayCancelButton: !!opt_onCancelClick},
5151
opt_domHelper,
5252
'signIn');
5353
this.onEmailEnter_ = onEmailEnter;
54-
this.onCancelClick_ = onCancelClick;
54+
this.onCancelClick_ = opt_onCancelClick;
5555
};
5656
goog.inherits(firebaseui.auth.ui.page.SignIn, firebaseui.auth.ui.page.Base);
5757

5858

5959
/** @override */
6060
firebaseui.auth.ui.page.SignIn.prototype.enterDocument = function() {
6161
this.initEmailElement(this.onEmailEnter_);
62-
var self = this;
6362
// Handle a click on the submit button or cancel button.
64-
this.initFormElement(this.onEmailEnter_, this.onCancelClick_);
63+
this.initFormElement(this.onEmailEnter_, this.onCancelClick_ || undefined);
6564
this.setupFocus_();
6665
firebaseui.auth.ui.page.SignIn.base(this, 'enterDocument');
6766
};

javascript/ui/page/signin_test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,28 @@ function testSignIn_pageEvents() {
112112
}
113113

114114

115+
function testSignIn_noOnCancelClick() {
116+
component.dispose();
117+
// Initialize component with no onCancelClick callback.
118+
component = new firebaseui.auth.ui.page.SignIn(
119+
goog.bind(
120+
firebaseui.auth.ui.element.EmailTestHelper.prototype.onEnter,
121+
emailTestHelper));
122+
component.render(root);
123+
emailTestHelper.setComponent(component);
124+
// No cancel button
125+
assertNull(component.getSecondaryLinkElement());
126+
// Submit button should be available.
127+
assertNotNull(component.getSubmitElement());
128+
// Confirm pressing enter in email field will submit form.
129+
emailTestHelper.resetState();
130+
assertFalse(emailTestHelper.enterPressed_);
131+
goog.testing.events.fireKeySequence(
132+
component.getEmailElement(), goog.events.KeyCodes.ENTER);
133+
assertTrue(emailTestHelper.enterPressed_);
134+
}
135+
136+
115137
function testSignIn_getPageId() {
116138
assertEquals('signIn', component.getPageId());
117139
}

javascript/widgets/authui.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ goog.require('goog.events.EventType');
7070
* @constructor
7171
*/
7272
firebaseui.auth.AuthUI = function(auth, opt_appId) {
73+
/** @private {boolean} Whether the current instance is deleted. */
74+
this.deleted_ = false;
75+
// Check if an instance with the same key exists. If so, throw an error,
76+
// otherwise, save that instance.
77+
// Get the instance get.
78+
var key = firebaseui.auth.AuthUI.getInstanceKey_(opt_appId);
79+
if (firebaseui.auth.AuthUI.instances_[key]) {
80+
// An instance exists for this key. Throw an error.
81+
throw new Error(
82+
'An AuthUI instance already exists for the key "' + key + '"');
83+
} else {
84+
// New instance, save reference to it.
85+
firebaseui.auth.AuthUI.instances_[key] = this;
86+
}
7387
/** @private {!firebase.auth.Auth} The Firebase Auth instance. */
7488
this.auth_ = auth;
7589
var tempApp = firebase.initializeApp({
@@ -112,6 +126,48 @@ firebaseui.auth.AuthUI = function(auth, opt_appId) {
112126
};
113127

114128

129+
/** Resets all internal globals. Used for testing only. */
130+
firebaseui.auth.AuthUI.resetAllInternals = function() {
131+
firebaseui.auth.AuthUI.instances_ = {};
132+
firebaseui.auth.AuthUI.widgetElement_ = null;
133+
};
134+
135+
136+
/**
137+
* @private {!Object.<!string, !firebaseui.auth.AuthUI>} Map containing the
138+
* firebaseui.auth.AuthUI instances keyed by their app IDs.
139+
*/
140+
firebaseui.auth.AuthUI.instances_ = {};
141+
142+
143+
/**
144+
* Returns the instance key corresponding the appId provided.
145+
* @param {?string=} opt_appId The optional app ID whose instance is to be
146+
* provided.
147+
* @return {string} The key corresponding to the provided app ID.
148+
* @private
149+
*/
150+
firebaseui.auth.AuthUI.getInstanceKey_ = function(opt_appId) {
151+
return opt_appId || firebaseui.auth.AuthUI.DEFAULT_INSTANCE_KEY_;
152+
};
153+
154+
155+
/**
156+
* Returns the AuthUI instance corresponding to the appId provided.
157+
* @param {?string=} opt_appId The optional app ID whose instance is to be
158+
* provided.
159+
* @return {?firebaseui.auth.AuthUI} The AuthUI instance corresponding to the
160+
* app ID provided.
161+
*/
162+
firebaseui.auth.AuthUI.getInstance = function(opt_appId) {
163+
var key = firebaseui.auth.AuthUI.getInstanceKey_(opt_appId);
164+
if (firebaseui.auth.AuthUI.instances_[key]) {
165+
return firebaseui.auth.AuthUI.instances_[key];
166+
}
167+
return null;
168+
};
169+
170+
115171
/**
116172
* The suffix of the temp Auth instance app name.
117173
* @const {string}
@@ -120,6 +176,14 @@ firebaseui.auth.AuthUI = function(auth, opt_appId) {
120176
firebaseui.auth.AuthUI.TEMP_APP_NAME_SUFFIX_ = '-firebaseui-temp';
121177

122178

179+
/**
180+
* The default instance key when no app ID is provided.
181+
* @const {string}
182+
* @private
183+
*/
184+
firebaseui.auth.AuthUI.DEFAULT_INSTANCE_KEY_ = '[DEFAULT]';
185+
186+
123187
/**
124188
* If sign-in succeeded, returns the signed in user. If sign-in was
125189
* unsuccessful, fails with an error. If no redirect operation was called,
@@ -129,6 +193,8 @@ firebaseui.auth.AuthUI.TEMP_APP_NAME_SUFFIX_ = '-firebaseui-temp';
129193
* from the redirect-based sign-in flow.
130194
*/
131195
firebaseui.auth.AuthUI.prototype.getRedirectResult = function() {
196+
// Check if instance is already destroyed.
197+
this.checkIfDestroyed_();
132198
if (!this.getRedirectResult_) {
133199
this.getRedirectResult_ = goog.Promise.resolve(
134200
this.getAuth().getRedirectResult());
@@ -142,6 +208,8 @@ firebaseui.auth.AuthUI.prototype.getRedirectResult = function() {
142208
* component.
143209
*/
144210
firebaseui.auth.AuthUI.prototype.setCurrentComponent = function(component) {
211+
// Check if instance is already destroyed.
212+
this.checkIfDestroyed_();
145213
this.currentComponent_ = component;
146214
};
147215

@@ -175,6 +243,8 @@ firebaseui.auth.AuthUI.getAuthUi = function() {
175243
* developer provided Auth instance.
176244
*/
177245
firebaseui.auth.AuthUI.prototype.getAuth = function() {
246+
// Check if instance is already destroyed.
247+
this.checkIfDestroyed_();
178248
return this.tempAuth_;
179249
};
180250

@@ -184,13 +254,17 @@ firebaseui.auth.AuthUI.prototype.getAuth = function() {
184254
* instance.
185255
*/
186256
firebaseui.auth.AuthUI.prototype.getExternalAuth = function() {
257+
// Check if instance is already destroyed.
258+
this.checkIfDestroyed_();
187259
return this.auth_;
188260
};
189261

190262
/**
191263
* @return {string|undefined} The app id if provided.
192264
*/
193265
firebaseui.auth.AuthUI.prototype.getAppId = function() {
266+
// Check if instance is already destroyed.
267+
this.checkIfDestroyed_();
194268
return this.appId_;
195269
};
196270

@@ -204,6 +278,8 @@ firebaseui.auth.AuthUI.prototype.getAppId = function() {
204278
* @return {boolean} Whether the app has pending operations to be performed.
205279
*/
206280
firebaseui.auth.AuthUI.prototype.isPending = function() {
281+
// Check if instance is already destroyed.
282+
this.checkIfDestroyed_();
207283
return firebaseui.auth.storage.hasPendingEmailCredential(this.getAppId());
208284
};
209285

@@ -217,6 +293,8 @@ firebaseui.auth.AuthUI.prototype.isPending = function() {
217293
* @param {Object} config The configuration for sign-in button.
218294
*/
219295
firebaseui.auth.AuthUI.prototype.start = function(element, config) {
296+
// Check if instance is already destroyed.
297+
this.checkIfDestroyed_();
220298
var self = this;
221299

222300
// There is a problem when config in second call modifies accountchooser.com
@@ -289,6 +367,8 @@ firebaseui.auth.AuthUI.prototype.initElement_ = function(element) {
289367
* @param {?goog.Promise|?firebase.Promise|?function()} p The pending promise.
290368
*/
291369
firebaseui.auth.AuthUI.prototype.registerPending = function(p) {
370+
// Check if instance is already destroyed.
371+
this.checkIfDestroyed_();
292372
var self = this;
293373
if (p) {
294374
this.pending_.push(p);
@@ -310,12 +390,16 @@ firebaseui.auth.AuthUI.prototype.registerPending = function(p) {
310390
* getter.
311391
*/
312392
firebaseui.auth.AuthUI.prototype.getAuthUiGetter = function() {
393+
// Check if instance is already destroyed.
394+
this.checkIfDestroyed_();
313395
return firebaseui.auth.AuthUI.getAuthUi;
314396
};
315397

316398

317399
/** Reset rendered widget and removes it from display. */
318400
firebaseui.auth.AuthUI.prototype.reset = function() {
401+
// Check if instance is already destroyed.
402+
this.checkIfDestroyed_();
319403
// Remove the "lang" attribute that we set in start().
320404
if (this.widgetElement_) {
321405
this.widgetElement_.removeAttribute('lang');
@@ -404,6 +488,8 @@ firebaseui.auth.AuthUI.prototype.initPageChangeListener_ = function(element) {
404488
* @param {*} value The value of the configuration.
405489
*/
406490
firebaseui.auth.AuthUI.prototype.updateConfig = function(name, value) {
491+
// Check if instance is already destroyed.
492+
this.checkIfDestroyed_();
407493
this.config_.update(name, value);
408494
};
409495

@@ -413,6 +499,8 @@ firebaseui.auth.AuthUI.prototype.updateConfig = function(name, value) {
413499
* @param {Object} config The application configuration.
414500
*/
415501
firebaseui.auth.AuthUI.prototype.setConfig = function(config) {
502+
// Check if instance is already destroyed.
503+
this.checkIfDestroyed_();
416504
this.config_.setConfig(config);
417505
};
418506

@@ -421,6 +509,8 @@ firebaseui.auth.AuthUI.prototype.setConfig = function(config) {
421509
* @return {firebaseui.auth.widget.Config} The application configuration.
422510
*/
423511
firebaseui.auth.AuthUI.prototype.getConfig = function() {
512+
// Check if instance is already destroyed.
513+
this.checkIfDestroyed_();
424514
return this.config_;
425515
};
426516

@@ -429,5 +519,41 @@ firebaseui.auth.AuthUI.prototype.getConfig = function() {
429519
* Triggers the sign-in flow.
430520
*/
431521
firebaseui.auth.AuthUI.prototype.signIn = function() {
522+
// Check if instance is already destroyed.
523+
this.checkIfDestroyed_();
432524
firebaseui.auth.widget.handler.startSignIn(this);
433525
};
526+
527+
528+
/**
529+
* Checks if the instance is destroyed. If so, throws an error.
530+
* @private
531+
*/
532+
firebaseui.auth.AuthUI.prototype.checkIfDestroyed_ = function() {
533+
if (this.deleted_) {
534+
throw new Error('AuthUI instance is deleted!');
535+
}
536+
};
537+
538+
539+
/**
540+
* Destroys the AuthUI instance.
541+
* @return {!firebase.Promise} The promise that resolves when the instance
542+
* is successfully deleted.
543+
*/
544+
firebaseui.auth.AuthUI.prototype.delete = function() {
545+
var self = this;
546+
// Check if instance is already destroyed.
547+
this.checkIfDestroyed_();
548+
// Delete the temporary app instance.
549+
return this.tempAuth_.app.delete().then(function() {
550+
// Get instance key.
551+
var key = firebaseui.auth.AuthUI.getInstanceKey_(self.getAppId());
552+
// Delete any saved AuthUI instance.
553+
delete firebaseui.auth.AuthUI.instances_[key];
554+
// Reset current instance.
555+
self.reset();
556+
// Mark as deleted.
557+
self.deleted_ = true;
558+
});
559+
};

0 commit comments

Comments
 (0)