Skip to content

Commit d355eec

Browse files
wti806bojeil-google
authored andcommitted
FirebaseUI release (#305)
* Implements anonymous user upgrade functionality with FirebaseUI Sanitizes display name in firebaseui. Clears cached `app.getRedirectResult` after `signInWithRedirect` or `linkWithRedirect` resolves. Fixes the bug when account linking doesn't get triggered in Cordova applications. Adds description for building localized npm builds and how to require them. Implements isPendingRedirect needed to tell whether there is a pending redirect opearation. Fixes dangling internal auth state when the firebaseui instance is reset. PiperOrigin-RevId: 184305421 Change-Id: I684d83fd5ba96ea3a78850ce374207a590171cee * Revert some of the changes in soy template. 'for' doesn't work with open source version compiler. Change back to 'foreach' Change-Id: I14040b19cb294b1f4f90cf10400ce575c53414cf * added changelog bumps gstatic CDN version Sort changelog and add one more change fix typo and style
1 parent a2b741b commit d355eec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+6710
-145
lines changed

README.md

Lines changed: 199 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,17 @@ Localized versions of the widget are available through the CDN. To use a localiz
6363
localized JS library instead of the default library:
6464

6565
```html
66-
<script src="https://www.gstatic.com/firebasejs/ui/2.5.1/firebase-ui-auth__{LANGUAGE_CODE}.js"></script>
67-
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/2.5.1/firebase-ui-auth.css" />
66+
<script src="https://www.gstatic.com/firebasejs/ui/2.6.0/firebase-ui-auth__{LANGUAGE_CODE}.js"></script>
67+
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/2.6.0/firebase-ui-auth.css" />
6868
```
6969

7070
where `{LANGUAGE_CODE}` is replaced by the code of the language you want. For example, the French
7171
version of the library is available at
72-
`https://www.gstatic.com/firebasejs/ui/2.5.1/firebase-ui-auth__fr.js`. The list of available
72+
`https://www.gstatic.com/firebasejs/ui/2.6.0/firebase-ui-auth__fr.js`. The list of available
7373
languages and their respective language codes can be found at [LANGUAGES.md](LANGUAGES.md).
7474

7575
Right-to-left languages also require the right-to-left version of the stylesheet, available at
76-
`https://www.gstatic.com/firebasejs/ui/2.5.1/firebase-ui-auth-rtl.css`, instead of the default
76+
`https://www.gstatic.com/firebasejs/ui/2.6.0/firebase-ui-auth-rtl.css`, instead of the default
7777
stylesheet. The supported right-to-left languages are Arabic (ar), Farsi (fa), and Hebrew (iw).
7878

7979
### Option 2: npm Module
@@ -129,6 +129,9 @@ FirebaseUI includes the following flows:
129129
by default.)
130130
6. [Account Chooser](https://www.accountchooser.com/learnmore.html?lang=en) for
131131
remembering emails
132+
7. Integration with
133+
[one-tap sign-up](https://developers.google.com/identity/one-tap/web/overview)
134+
8. Ability to upgrade anonymous users through sign-in/sign-up.
132135

133136
### Configuring sign-in providers
134137

@@ -203,6 +206,18 @@ for a more in-depth example, showcasing a Single Page Application mode.
203206
</html>
204207
```
205208

209+
When redirecting back from accountchooser.com or Identity Providers like Google
210+
and Facebook, `start()` method needs to be called to finish the sign-in flow.
211+
To check if there is a pending redirect operation to complete a sign-in attempt,
212+
check `isPendingRedirect()` before deciding whether to render FirebaseUI
213+
via `start()`.
214+
215+
```javascript
216+
if (ui.isPendingRedirect()) {
217+
ui.start('#firebaseui-auth-container', uiConfig);
218+
}
219+
```
220+
206221
Here is how you would track the Auth state across all your pages:
207222

208223
```html
@@ -284,6 +299,19 @@ FirebaseUI supports the following configuration parameters.
284299
</thead>
285300
<tbody>
286301
<tr>
302+
<td>autoUpgradeAnonymousUsers</td>
303+
<td>No</td>
304+
<td>
305+
Whether to automatically upgrade existing anonymous users on sign-in/sign-up.
306+
See <a href="#upgrading-anonymous-users">Upgrading anonymous users</a>.
307+
<br/>
308+
<em>Default:</em>
309+
<code>false</code>
310+
When set to <code>true</code>, <code>signInFailure</code> callback is
311+
required to be provided to handle merge conflicts.
312+
</td>
313+
</tr>
314+
<tr>
287315
<td>callbacks</td>
288316
<td>No</td>
289317
<td>
@@ -594,6 +622,29 @@ static `signInSuccessUrl` in config.
594622
If the callback returns `false` or nothing, the page is not automatically
595623
redirected.
596624

625+
#### `signInFailure(error)`
626+
627+
The `signInFailure` callback is provided to handle any unrecoverable error
628+
encountered during the sign-in process.
629+
The error provided here is a `firebaseui.auth.AuthUIError` error with the
630+
following properties.
631+
632+
**firebaseui.auth.AuthUIError properties:**
633+
634+
|Name |Type |Optional |Description |
635+
|---------|----------------|---------|-----------------------|
636+
|`code` |`string` |No |The corresponding error code. Currently the only error code supported is `firebaseui/anonymous-upgrade-merge-conflict` |
637+
|`credential` |`firebase.auth.AuthCredential`|Yes |The existing non-anonymous user credential the user tried to sign in with.|
638+
639+
**Should return: `Promise<void>|void`**
640+
641+
FirebaseUI will wait for the returned promise to handle the reported error
642+
before clearing the UI. If no promise is returned, the UI will be cleared on
643+
completion. Even when this callback resolves, `signInSuccess` callback will not
644+
be triggered.
645+
646+
This callback is required when `autoUpgradeAnonymousUsers` is enabled.
647+
597648
#### `uiShown()`
598649

599650
This callback is triggered the first time the widget UI is rendered. This is
@@ -624,6 +675,14 @@ FirebaseUI is displayed.
624675
// or whether we leave that to developer to handle.
625676
return true;
626677
},
678+
signInFailure: function(error) {
679+
// Some unrecoverable error occurred during sign-in.
680+
// Return a promise when error handling is completed and FirebaseUI
681+
// will reset, clearing any UI. This commonly occurs for error code
682+
// 'firebaseui/anonymous-upgrade-merge-conflict' when merge conflict
683+
// occurs. Check below for more details on this.
684+
return handleUIError(error);
685+
},
627686
uiShown: function() {
628687
// The widget is rendered.
629688
// Hide the loader.
@@ -677,6 +736,130 @@ FirebaseUI is displayed.
677736
</html>
678737
```
679738

739+
### Upgrading anonymous users
740+
741+
#### Enabling anonymous user upgrade
742+
743+
When an anonymous user signs in or signs up with a permanent account, you want
744+
to be sure the user can continue with what they were doing before signing up.
745+
For example, an anonymous user might have items in their shopping cart.
746+
At check-out, you prompt the user to sign in or sign up. After the user is
747+
signed in, the user's shopping cart should contain any items the user added
748+
while signed in anonymously.
749+
750+
To support this behavior, FirebaseUI makes it easy to "upgrade" an anonymous
751+
account to a permanent account. To do so, simply set `autoUpgradeAnonymousUsers`
752+
to `true` when you configure the sign-in UI (this option is disabled by
753+
default).
754+
755+
FirebaseUI links the new credential with the anonymous account using Firebase
756+
Auth's `linkWithCredential` method:
757+
```javascript
758+
anonymousUser.linkWithCredential(permanentCredential);
759+
```
760+
The user will retain the same `uid` at the end of the flow and all data keyed
761+
on that identifier would still be associated with that same user.
762+
763+
#### Handling anonymous user upgrade merge conflicts
764+
765+
There are cases when a user, initially signed in anonymously, tries to
766+
upgrade to an existing Firebase user. For example, a user may have signed up
767+
with a Google credential on another device. When trying to upgrade to the
768+
existing Google user, an error `auth/credential-already-in-use` will be thrown
769+
by Firebase Auth as an existing user cannot be linked to another existing user.
770+
No two users can share the same credential. In that case, both user data
771+
have to be merged before one user is discarded (typically the anonymous user).
772+
In the case above, the anonymous user shopping cart will be copied locally,
773+
the anonymous user will be deleted and then the user is signed in with the
774+
permanent credential. The anonymous user data in temporary storage will be
775+
copied back to the non-anonymous user.
776+
777+
FirebaseUI will trigger the `signInFailure` callback with an error code
778+
`firebaseui/anonymous-upgrade-merge-conflict` when the above occurs. The error
779+
object will also contain the permanent credential.
780+
Sign-in with the permanent credential should be triggered in the callback to
781+
complete sign-in.
782+
Before sign-in can be completed via
783+
`auth.signInWithCredential(error.credential)`, the data of the anonymous user
784+
must be copied and the anonymous user deleted. After sign-in completion, the
785+
data has to be copied back to the non-anonymous user. An example below
786+
illustrates how this flow would work if user data is persisted using Firebase
787+
Realtime Database.
788+
789+
**Example:**
790+
791+
```javascript
792+
// Temp variable to hold the anonymous user data if needed.
793+
var data = null;
794+
// Hold a reference to the anonymous current user.
795+
var anonymousUser = firebase.auth().currentUser;
796+
ui.start('#firebaseui-auth-container', {
797+
// Whether to upgrade anonymous users should be explicitly provided.
798+
// The user must already be signed in anonymously before FirebaseUI is
799+
// rendered.
800+
autoUpgradeAnonymousUsers: true,
801+
signInSuccessUrl: '<url-to-redirect-to-on-success>',
802+
signInOptions: [
803+
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
804+
firebase.auth.FacebookAuthProvider.PROVIDER_ID,
805+
firebase.auth.EmailAuthProvider.PROVIDER_ID,
806+
firebase.auth.PhoneAuthProvider.PROVIDER_ID
807+
],
808+
callbacks: {
809+
signInSuccess: function(user, credential, redirectUrl) {
810+
// Process result. This will not trigger on merge conflicts.
811+
// On success redirect to signInSuccessUrl.
812+
return true;
813+
},
814+
// signInFailure callback must be provided to handle merge conflicts which
815+
// occur when an existing credential is linked to an anonymous user.
816+
signInFailure: function(error) {
817+
// For merge conflicts, the error.code will be
818+
// 'firebaseui/anonymous-upgrade-merge-conflict'.
819+
if (error.code != 'firebaseui/anonymous-upgrade-merge-conflict') {
820+
return Promise.resolve();
821+
}
822+
// The credential the user tried to sign in with.
823+
var cred = error.credential;
824+
// If using Firebase Realtime Database. The anonymous user data has to be
825+
// copied to the non-anonymous user.
826+
var app = firebase.app();
827+
// Save anonymous user data first.
828+
return app.database().ref('users/' + firebase.auth().currentUser.uid)
829+
.once('value')
830+
.then(function(snapshot) {
831+
data = snapshot.val();
832+
// This will trigger onAuthStateChanged listener which
833+
// could trigger a redirect to another page.
834+
// Ensure the upgrade flow is not interrupted by that callback
835+
// and that this is given enough time to complete before
836+
// redirection.
837+
return firebase.auth().signInWithCredential(cred);
838+
})
839+
.then(function(user) {
840+
// Original Anonymous Auth instance now has the new user.
841+
return app.database().ref('users/' + user.uid).set(data);
842+
})
843+
.then(function() {
844+
// Delete anonymnous user.
845+
return anonymousUser.delete();
846+
}).then(function() {
847+
// Clear data in case a new user signs in, and the state change
848+
// triggers.
849+
data = null;
850+
// FirebaseUI will reset and the UI cleared when this promise
851+
// resolves.
852+
// signInSuccess will not run. Successful sign-in logic has to be
853+
// run explicitly.
854+
window.location.assign('<url-to-redirect-to-on-success>');
855+
});
856+
857+
}
858+
}
859+
});
860+
```
861+
862+
680863
## Customizing FirebaseUI for authentication
681864

682865
Currently, FirebaseUI does not offer customization out of the box. However, the
@@ -762,6 +945,18 @@ where `{LANGUAGE_CODE}` is replaced by the
762945
can be built with `npm run build build-js-fr`. This will create a binary
763946
`firebaseui__fr.js` in the `dist/` folder.
764947

948+
To build a localized npm FirebaseUI module, run:
949+
```bash
950+
npm run build build-npm-{LANGUAGE_CODE}
951+
```
952+
Make sure all underscore symbols in the `LANGUAGE_CODE` are replaced with
953+
dashes.
954+
This will generate `dist/npm__{LANGUAGE_CODE}.js`.
955+
You can then import/require it:
956+
```javascript
957+
import firebaseui from './npm__{LANGUAGE_CODE}';
958+
```
959+
765960
### Running the demo app
766961

767962
To run the demo app, you must have a Firebase project set up on the

changelog.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
1+
feature - Implements anonymous user upgrade functionality with FirebaseUI.
2+
feature - Implements `isPendingRedirect` needed to tell whether there is a pending redirect operation.
3+
fixed - Sanitizes display name in firebaseui.
4+
fixed - Clears cached `app.getRedirectResult` after `signInWithRedirect` or `linkWithRedirect` resolves.
5+
fixed - Fixes the bug when account linking doesn't get triggered in Cordova applications.
6+
fixed - Adds description for building localized npm builds and how to require them.
7+
fixed - Fixes dangling internal auth state when the firebaseui instance is reset.
8+
fixed - Updates closure open source builder to latest version fixing dependency on `eval` and older `marked` module which had some vulnerability issues.

javascript/testing/acclient.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var acClient = goog.require('firebaseui.auth.acClient');
3434
var FakeAcClient = function() {
3535
this.selectTried_ = false;
3636
this.skipSelect_ = false;
37+
this.preSkip_ = null;
3738
this.available_ = true;
3839
this.localAccounts_ = null;
3940
this.acResult_ = null;
@@ -97,9 +98,13 @@ FakeAcClient.prototype.setAvailability =
9798
/**
9899
* Sets whether to skip selecting an account.
99100
* @param {boolean} skip Whether to skip selecting an account.
101+
* @param {function()=} opt_preSkip Callback to invoke before onSkip callback.
100102
*/
101-
FakeAcClient.prototype.setSkipSelect = function(skip) {
103+
FakeAcClient.prototype.setSkipSelect = function(skip, opt_preSkip) {
102104
this.skipSelect_ = skip;
105+
if (skip) {
106+
this.preSkip_ = opt_preSkip || null;
107+
}
103108
};
104109

105110

@@ -176,6 +181,9 @@ FakeAcClient.prototype.trySelectAccount_ = function(
176181
this.localAccounts_ = opt_localAccounts;
177182
this.callbackUrl_ = opt_callbackUrl;
178183
if (this.skipSelect_) {
184+
if (this.preSkip_) {
185+
this.preSkip_();
186+
}
179187
onSkipSelect(this.available_);
180188
}
181189
};

javascript/testing/auth.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,21 @@ FakeAuthClient.AuthAsyncMethod = {
189189
APPLY_ACTION_CODE: 'applyActionCode',
190190
CHECK_ACTION_CODE: 'checkActionCode',
191191
CONFIRM_PASSWORD_RESET: 'confirmPasswordReset',
192+
CREATE_USER_AND_RETRIEVE_DATA_WITH_EMAIL_AND_PASSWORD:
193+
'createUserAndRetrieveDataWithEmailAndPassword',
192194
CREATE_USER_WITH_EMAIL_AND_PASSWORD: 'createUserWithEmailAndPassword',
193195
FETCH_PROVIDERS_FOR_EMAIL: 'fetchProvidersForEmail',
194196
GET_REDIRECT_RESULT: 'getRedirectResult',
195197
SEND_PASSWORD_RESET_EMAIL: 'sendPasswordResetEmail',
196198
SET_PERSISTENCE: 'setPersistence',
197199
SIGN_IN_AND_RETRIEVE_DATA_WITH_CREDENTIAL:
198200
'signInAndRetrieveDataWithCredential',
201+
SIGN_IN_AND_RETRIEVE_DATA_WITH_CUSTOM_TOKEN:
202+
'signInAndRetrieveDataWithCustomToken',
203+
SIGN_IN_AND_RETRIEVE_DATA_WITH_EMAIL_AND_PASSWORD:
204+
'signInAndRetrieveDataWithEmailAndPassword',
205+
SIGN_IN_ANONYMOUSLY_AND_RETRIEVE_DATA:
206+
'signInAnonymouslyAndRetrieveData',
199207
SIGN_IN_WITH_CREDENTIAL: 'signInWithCredential',
200208
SIGN_IN_WITH_CUSTOM_TOKEN: 'signInWithCustomToken',
201209
SIGN_IN_WITH_EMAIL_AND_PASSWORD: 'signInWithEmailAndPassword',
@@ -242,6 +250,7 @@ FakeAuthClient.UserProperty = {
242250
DISPLAY_NAME: 'displayName',
243251
EMAIL: 'email',
244252
EMAIL_VERIFIED: 'emailVerified',
253+
IS_ANONYMOUS: 'isAnonymous',
245254
PHONE_NUMBER: 'phoneNumber',
246255
PHOTO_URL: 'photoURL',
247256
PROVIDER_DATA: 'providerData',

javascript/utils/acclient.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ firebaseui.auth.acClient.init = function(
123123
/**
124124
* Starts the flow to select an account from accountchooser.com.
125125
* It first checks whether accountchooser.com has accounts. If not,
126-
* {@code onSkipSelect} is called instead of redirecting to accountchooser.com.
126+
* `onSkipSelect` is called instead of redirecting to accountchooser.com.
127127
*
128128
* @param {function(boolean)} onSkipSelect The callback function invoked when
129129
* the account selection can be skipped. A boolean availability flag is
130130
* passed. It is true if accountchooser.com is available, false otherwise.
131-
* @param {Array<firebaseui.auth.Account>} opt_localAccounts The local account
131+
* @param {Array<firebaseui.auth.Account>=} opt_localAccounts The local account
132132
* list to pass to accountchooser.com.
133133
* @param {string=} opt_callbackUrl The URL to return to when the flow finishes.
134134
* The default is current URL.
@@ -163,7 +163,7 @@ firebaseui.auth.acClient.trySelectAccount = function(
163163
/**
164164
* Starts the flow to store or update an account into accountchooser.com.
165165
* It first checks whether the account needs to be stored or updated. If not,
166-
* the {@code onSkipStore} is called instead of redirecting to
166+
* the `onSkipStore` is called instead of redirecting to
167167
* accountchooser.com.
168168
*
169169
* @param {firebaseui.auth.Account} account The account to add.
@@ -298,7 +298,7 @@ firebaseui.auth.acClient.DummyApi.prototype.update =
298298

299299
/**
300300
* Checkes if accountchooser.com is disabled. The callback is always invoked
301-
* with a {@code true}.
301+
* with a `true`.
302302
*
303303
* @param {function(boolean=, Object=)} callback The callback function.
304304
*/

javascript/utils/account.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ firebaseui.auth.Account.prototype.toPlainObject = function() {
8080

8181

8282
/**
83-
* Converts a plain account object to {@code firebaseui.auth.Account}.
83+
* Converts a plain account object to `firebaseui.auth.Account`.
8484
* @param {!Object} account The plain object representation of an account.
8585
* @return {firebaseui.auth.Account} The account.
8686
*/

javascript/utils/config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ firebaseui.auth.Config.prototype.update = function(name, value) {
9797

9898
/**
9999
* Gets the configuration value for the given name. If an unrecognized name is
100-
* specified, an {@code Error} is thrown.
100+
* specified, an `Error` is thrown.
101101
*
102102
* @param {string} name The name of the configuration.
103103
* @return {*|undefined} The configuration value.
@@ -112,7 +112,7 @@ firebaseui.auth.Config.prototype.get = function(name) {
112112

113113
/**
114114
* Gets the configuration value for the given name. If an unrecognized name is
115-
* specified or the value is not provided, an {@code Error} is thrown.
115+
* specified or the value is not provided, an `Error` is thrown.
116116
*
117117
* @param {string} name The name of the configuration.
118118
* @return {*} The configuration value.

0 commit comments

Comments
 (0)