diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29b..e78e8297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +* Added `FirebaseUiHandler` API to support [using external identities with Google Cloud IAP](https://github.com/firebase/firebaseui-web/blob/master/firebaseuihandler/README.md). +* Added default configurations for `SAML` and `OIDC` providers. +* Fixed the missing id attributes for input elements. diff --git a/README.md b/README.md index 686be87d..33fc11a2 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,12 @@ FirebaseUI. 5. [Customization](#customizing-firebaseui-for-authentication) 6. [Advanced](#advanced) 7. [Developer Setup](#developer-setup) -8. [Cordova Setup](#cordova-setup) -9. [React DOM Setup](#react-dom-setup) -10. [Angular Setup](#angular-setup) -11. [Known issues](#known-issues) -12. [Release Notes](#release-notes) +8. [IAP External Identities Support with FirebaseUI](#iap-external-identities-support-with-firebaseui) +9. [Cordova Setup](#cordova-setup) +10. [React DOM Setup](#react-dom-setup) +11. [Angular Setup](#angular-setup) +12. [Known issues](#known-issues) +13. [Release Notes](#release-notes) ## Demo @@ -1687,6 +1688,13 @@ the other terminal that has the exported variables, run the tests: npm test -- --saucelabs --tunnelIdentifier= ``` +## IAP External Identities Support with FirebaseUI + +You can use FirebaseUI to build the authentication page to use external +identities with +[Google Cloud IAP](https://cloud.google.com/iap/docs/external-identities). +The documentation can be found [here](firebaseuihandler/README.md). + ## Cordova Setup ### Introduction diff --git a/externs/firebaseui-externs.js b/externs/firebaseui-externs.js index b1ce8f61..a276da4d 100644 --- a/externs/firebaseui-externs.js +++ b/externs/firebaseui-externs.js @@ -112,6 +112,324 @@ firebaseui.auth.AuthUI.prototype.delete = function() {}; firebaseui.auth.AuthUI.prototype.isPendingRedirect = function() {}; +/** + * The CIAP Error interface. + * + * @interface + */ +firebaseui.auth.CIAPError = function() {}; + +/** + * The short error code. + * + * @type {string} + */ +firebaseui.auth.CIAPError.prototype.code; + +/** + * The human-readable error message. + * + * @type {string} + */ +firebaseui.auth.CIAPError.prototype.message; + +/** + * The HTTP error code number. + * + * @type {number|undefined} + */ +firebaseui.auth.CIAPError.prototype.httpErrorCode; + +/** + * The underlying reason error if available. + * + * @type {!Error|undefined} + */ +firebaseui.auth.CIAPError.prototype.reason; + +/** + * Returns a JSON-serializable representation of the error. + * @return {!Object} The plain object representation of the error. + */ +firebaseui.auth.CIAPError.prototype.toJSON = function() {}; + + +/** + * The CIAP recoverable error interface. + * @interface + * @extends {firebaseui.auth.CIAPError} + */ +firebaseui.auth.CIAPRetryError = function() {}; + + +/** + * The retry callback to recover from error. + * @return {!Promise} A promise that resolves on retry completion. + */ +firebaseui.auth.CIAPRetryError.prototype.retry = function() {}; + + +/** + * Defines the structure for the object used to identify the project. + * + * @interface + */ +firebaseui.auth.ProjectConfig = function() {}; + + +/** + * The project ID. + * @type {string} + */ +firebaseui.auth.ProjectConfig.prototype.projectId; + + +/** + * The API key. + * @type {string} + */ +firebaseui.auth.ProjectConfig.prototype.apiKey; + + +/** + * Defines the structure of matching tenant and providers enabled for the + * tenant. + * + * @interface + */ +firebaseui.auth.SelectedTenantInfo = function() {}; + + +/** + * The email being used to select the tenant. + * + * @type {string|undefined} + */ +firebaseui.auth.SelectedTenantInfo.prototype.email; + + +/** + * The ID of the selected tenant. Null for top-level project. + * + * @type {?string} + */ +firebaseui.auth.SelectedTenantInfo.prototype.tenantId; + + +/** + * The matching providers for the selected tenant. + * + * @type {!Array} + */ +firebaseui.auth.SelectedTenantInfo.prototype.providerIds; + + +/** + * Defines all the CIAP callbacks that can be passed to a + * `firebaseui.auth.CIAPHandlerConfig` object. + * + * @interface + */ +firebaseui.auth.CIAPCallbacks = function() {}; + + +/** + * Defines the callback which will get triggered when the sign-in UI is shown. + * The tenant ID is passed to the callback. + * @param {?string} tenantId The tenant ID. Null for top-level project. + */ +firebaseui.auth.CIAPCallbacks.prototype.signInUiShown = function(tenantId) {}; + + +/** + * Defines the callback which will get triggered when the tenant selection UI + * is shown. + */ +firebaseui.auth.CIAPCallbacks.prototype.selectTenantUiShown = function() {}; + + +/** + * Defines the callback which will get triggered when the tenant selection UI + * is hidden. + */ +firebaseui.auth.CIAPCallbacks.prototype.selectTenantUiHidden = function() {}; + + +/** + * The `beforeSignInSuccess` callback is provided to handle additional + * processing on the user before finishing sign-in. + * @param {!firebase.User} currentUser The current user to be processed before + * finishing sign-in. + * @return {!Promise} A promise that resolves when the + * processing is finished. + */ +firebaseui.auth.CIAPCallbacks.prototype.beforeSignInSuccess = + function(currentUser) {}; + + +/** + * CIAP authentication handler related configuration settings. + * + * @interface + */ +firebaseui.auth.CIAPHandlerConfig = function() {}; + + +/** + * The Auth domain of the project. + * + * @type {string} + */ +firebaseui.auth.CIAPHandlerConfig.prototype.authDomain; + + +/** + * The display mode for tenant selection flow. This could be 'optionFirst' or + * 'identifierFirst', defaults to 'optionFirst'. + * + * @type {string|undefined} + */ +firebaseui.auth.CIAPHandlerConfig.prototype.displayMode; + + +/** + * The terms of service URL/callback for tenant selection UI. + * + * @type {string|!function()|undefined} + */ +firebaseui.auth.CIAPHandlerConfig.prototype.tosUrl; + + +/** + * The privacy policy URL/callback for tenant selection UI. + * + * @type {string|!function()|undefined} + */ +firebaseui.auth.CIAPHandlerConfig.prototype.privacyPolicyUrl; + + +/** + * The CIAP flow related callbacks. + * + * @type {!firebaseui.auth.CIAPCallbacks|undefined} + */ +firebaseui.auth.CIAPHandlerConfig.prototype.callbacks; + + +/** + * The tenant level configurations keyed by tenant ID or '_' for top-level + * project. + * + * @type {!Object} + */ +firebaseui.auth.CIAPHandlerConfig.prototype.tenants; + + +/** + * Initializes a CIAP AuthenticationHandler with the configuration provided. + * + * @param {string|!Element} element The container element or the query selector. + * @param {!Object} configs + * The configuration of the handler keyed by API key. + * @constructor + */ +firebaseui.auth.FirebaseUiHandler = function(element, configs) {}; + + +/** + * Selects a tenant from the given tenant IDs. Returns the tenant ID of the + * selected tenant and the underlying matching providers. + * @param {!firebaseui.auth.ProjectConfig} projectConfig The configuration + * object used to identify the project. + * @param {!Array} tenantIds The IDs of the tenants to select from. + * @return {!Promise} The matching tenant + * and providers enabled for the tenant. + */ +firebaseui.auth.FirebaseUiHandler.prototype.selectTenant = + function(projectConfig, tenantIds) {}; + + +/** + * Returns the Auth instance for the corresponding project/tenant pair. + * + * @param {string} apiKey The API key. + * @param {?string} tenantId The tenant ID, null for agent flow. + * @return {!firebase.auth.Auth} The Auth instance for the given API key and + * tenant ID. + */ +firebaseui.auth.FirebaseUiHandler.prototype.getAuth = + function(apiKey, tenantId) {}; + + +/** + * Starts sign in with the corresponding Auth instance. The sign in options + * used are based on auth.tenantId. + * + * @param {!firebase.auth.Auth} auth The Auth instance. + * @param {!firebaseui.auth.SelectedTenantInfo=} tenantInfo The optional + * selected tenant and the matching providers. + * @return {!Promise} + */ +firebaseui.auth.FirebaseUiHandler.prototype.startSignIn = + function(auth, tenantInfo) {}; + + +/** + * Resets the FirebaseUI handler and deletes the underlying FirebaseUI instance. + * Calling startSignIn after reset should rerender the UI successfully. + * + * @return {!Promise} The promise that resolves when the instance + * is successfully deleted. + */ +firebaseui.auth.FirebaseUiHandler.prototype.reset = function() {}; + + +/** + * Renders progress bar in the container if hidden. + */ +firebaseui.auth.FirebaseUiHandler.prototype.showProgressBar = function() {}; + + +/** + * Hides progress bar if visible. + */ +firebaseui.auth.FirebaseUiHandler.prototype.hideProgressBar = function() {}; + + +/** + * Renders the UI after user is signed out. + * @return {!Promise} + */ +firebaseui.auth.FirebaseUiHandler.prototype.completeSignOut = function() {}; + + +/** + * Displays the error message to the end users and provides the ability to retry + * for recoverable error. + * @param {!Error|!firebaseui.auth.CIAPError|!firebaseui.auth.CIAPRetryError} + * error The error from CIAP. + */ +firebaseui.auth.FirebaseUiHandler.prototype.handleError = + function(error) {}; + + +/** + * Handles additional processing on the user if callback is provided by the + * developer. + * @param {!firebase.User} user The signed in user to be processed. + * @return {!Promise} A promise that resolves when the + * processing is finished. + */ +firebaseui.auth.FirebaseUiHandler.prototype.processUser = function(user) {}; + + +/** + * The language code of the handler. + * @type {?string} + */ +firebaseui.auth.FirebaseUiHandler.prototype.languageCode; + + /** * FirebaseUI related error typically returned via `signInFailure` callback. * @@ -196,6 +514,17 @@ firebaseui.auth.Config.prototype.callbacks; */ firebaseui.auth.Config.prototype.credentialHelper; +/** + * Whether to immediately redirect to the provider's site or instead show the + * default 'Sign in with Provider' button when there is only a single federated + * provider in signInOptions. In order for this option to take effect, the + * signInOptions must only hold a single federated provider (like 'google.com') + * and signInFlow must be set to 'redirect'. The default is false. + * + * @type {boolean|undefined} + */ +firebaseui.auth.Config.prototype.immediateFederatedRedirect; + /** * Whether to open the sign-in widget in a popup when `signIn` is called. The * default is false. @@ -272,6 +601,40 @@ firebaseui.auth.Config.prototype.privacyPolicyUrl; firebaseui.auth.Config.prototype.widgetUrl; +/** + * The tenant level CIAP configuration settings. + * + * @interface + * @extends {firebaseui.auth.Config} + */ +firebaseui.auth.TenantConfig = function() {}; + + +/** + * The tenant display name of the tenant selection button for the option first + * flow. + * + * @type {string|undefined} + */ +firebaseui.auth.TenantConfig.prototype.displayName; + + +/** + * The color of the tenant selection button for the option first flow. + * + * @type {string|undefined} + */ +firebaseui.auth.TenantConfig.prototype.buttonColor; + + +/** + * The URL of the icon in tenant selection button for the option first flow. + * + * @type {string|undefined} + */ +firebaseui.auth.TenantConfig.prototype.iconUrl; + + /** * Defines all the FirebaseUI callbacks that can be passed to a * `firebaseui.auth.Config` object. @@ -327,6 +690,16 @@ firebaseui.auth.SignInOption = function() {}; */ firebaseui.auth.SignInOption.prototype.provider; + +/** + * The hosted domain used to match the user’s email domain with the tenant + * providers for the identifier first flow. + * + * @type {string|!RegExp|undefined} + */ +firebaseui.auth.SignInOption.prototype.hd; + + /** * Defines the sign-in option needed to configure the FirebaseUI federated * sign-in widget. diff --git a/firebaseuihandler/README.md b/firebaseuihandler/README.md new file mode 100644 index 00000000..1cc677b6 --- /dev/null +++ b/firebaseuihandler/README.md @@ -0,0 +1,444 @@ +# IAP External Identities Support with FirebaseUI + +To use external identities with +[IAP](https://cloud.google.com/iap/docs/external-identities), your app needs a +page for authenticating users. IAP will redirect any unauthenticated +requests to this page. + +The `FirebaseUiHandler` API in `firebaseui` module provides simple, customizable +elements that help reduce boilerplate code when building the authentication UI. +It handles the UI flows for tenant selection, user authentication, +token refreshes and sign-out. + +FirebaseUiHandler implements the +[`AuthenticationHandler`](https://cloud.google.com/iap/docs/create-custom-auth-ui) +interface that +[`gcip-iap`](https://github.com/GoogleCloudPlatform/iap-gcip-web-toolkit) +defines. You just need to provide a UI configuration object and pass it to a +`gcip-iap` Authentication instance. + +## Table of Contents + +1. [Prerequisite](#Prerequisite) +2. [Installation](#installation) +3. [Configuring your authentication UI](#configuring-your-authentication-ui) +4. [API references](#api-reference) +5. [Configuring Authentication URL](#configuring-authentication-url) + +## Prerequisite + +A [Google Cloud Identity Platform](https://cloud.google.com/identity-platform/) +project is required. Firebase projects can upgrade to GCIP projects through the +[Cloud Console](https://console.cloud.google.com/customer-identity). + +Ensure you've configured IAP to use external identities and set up providers +with Identity Platform. See +[Enabling external identities](https://cloud.google.com/iap/docs/enable-external-identities) +to learn how. + +## Installation + +Install `gcip-iap`, `firebaseui` and its peer-dependency firebase via npm using +the following commands: + +```bash +$ npm install firebase --save +$ npm install firebaseui --save +$ npm install gcip-iap --save +``` + +The `gcip-iap` module is not available via CDN. + +You can then `import` the following modules within your source files: + +```javascript +// Import firebase modules. +import * as firebase from "firebase/app"; +import "firebase/auth"; +// Import firebaseui module. +import * as firebaseui from 'firebaseui' +// Import gcip-iap module. +import * as ciap from 'gcip-iap'; +``` + +Or use cjs `require` to include the modules: + +```javascript +// Require firebase modules. +var firebase = require('firebase/app'); +require('firebase/auth'); +// Require firebaseui module. +var firebaseui = require('firebaseui'); +// Require gcip-iap module. +var ciap = require('gcip-iap'); +``` + +Localization is supported by FirebaseUI. You need to build and import the +localized versions following the +[instructions](https://github.com/firebase/firebaseui-web#building-firebaseui). + + +## Configuring your authentication UI +`FirebaseUiHandler` takes a configuration object that specifies the tenants and +providers to use for authentication. A full configuration can be very long, +and might look something like this: +```javascript +// The project configurations. +const configs = { + // Configuration for project identified by API key API_KEY1. + API_KEY1: { + authDomain: 'project-id1.firebaseapp.com', + // Decide whether to ask user for identifier to figure out what tenant to + // select or whether to present all the tenants to select from. + displayMode: 'optionFirst', // Or identifierFirst + callbacks: { + // The callback to trigger when the selection tenant page + // or enter email for tenant matching page is shown. + selectTenantUiShown: () => { + // Show title and additional display info. + }, + // The callback to trigger when the selection tenant page + // or enter email for tenant matching page is hidden. + selectTenantUihidden: () => { + // Hide title and additional display info. + }, + // The callback to trigger when the sign-in page + // is shown. + signInUiShown: (tenantId) => { + // Show tenant title and additional display info. + }, + beforeSignInSuccess: (user) => { + // Do additional processing on user before sign-in is + // complete. + return Promise.resolve(user); + } + }, + tenants: { + // Tenant configuration for tenant ID tenantId1. + tenantId1: { + // Display name, button color and icon URL of the + // tenant selection button. + displayName: 'ACME', + buttonColor: '#2F2F2F', + iconUrl: '', + // Sign-in providers enabled for tenantId1. + signInOptions: [ + // Microsoft sign-in. + { + provider: 'microsoft.com', + }, + // Email/password sign-in. + { + provider: 'password', + // Do not require display name on sign up. + requireDisplayName: false + }, + // SAML provider. + { + provider: 'saml.my-provider1', + providerName: 'SAML provider', + buttonColor: '#4666FF', + iconUrl: 'https://www.example.com/photos/my_idp/saml.png' + }, + ], + signInFlow: 'redirect', // Or popup + }, + // Tenant configuration for tenant ID tenantId2. + tenantId2: { + displayName: 'OCP', + buttonColor: '#2F2F2F', + iconUrl: '', + // Tenant2 supports SAML, OIDC sign-in. + signInOptions: [ + // SAML provider. (multiple SAML providers can be passed) + { + provider: 'saml.my-provider2', + providerName: 'SAML provider', + buttonColor: '#4666FF', + iconUrl: 'https://www.example.com/photos/my_idp/saml.png' + }, + // OIDC provider. (multiple OIDC providers can be passed) + { + provider: 'oidc.my-provider1', + providerName: 'OIDC provider', + buttonColor: '#4666FF', + iconUrl: 'https://www.example.com/photos/my_idp/oidc.png' + }, + ], + }, + }, + }, +}; + +// Create a FirebaseUiHandler instance. +const handler = new firebaseui.auth.FirebaseUiHandler( + '#firebaseui-auth-container', configs); +// Initialize a ciap.Authentication instance using the FirebaseUiHandler +// instance. +const ciapInstance = new ciap.Authentication(handler); + +// Start the authentication flow. +ciapInstance.start(); +``` + +### Getting the authentication domain +Set the `authdomain` field to the domain provisioned to facilitate federated +sign-in. You can retrieve this field from the +[Identity Platform page](https://console.cloud.google.com/customer-identity) +in the Cloud Console. + +### Configuring tenants and providers +A configuration requires a list of tenants and providers that users can +authenticate with. Each tenant has a list of providers; these are specified in +the `signInOptions` field. See +[Configuring sign-in providers in the FirebaseUI](https://github.com/firebase/firebaseui-web#configuring-sign-in-providers) +to learn how to configure providers. + +### Choosing a tenant selection mode +Users can select a tenant in two ways: `optionsFirst` mode, or +`identifierFirst` mode. + +In options mode, the user begins by selecting a tenant from a list. +In identifier mode, the user enters their email first. The system then +automatically selects the first tenant with an identity provider matching the +email's domain. + +To use options mode, set `displayMode` to `optionFirst`. You'll then need to +provide configuration information for each tenant's button, including +`displayName`, `buttonColor`, and `iconUrl`. + +The following is an example of a tenant configured to use options mode: + +```javascript +tenantId1: { + displayName: 'ACME', + buttonColor: '#2F2F2F', + iconUrl: '', + // ... +``` + +To use identifier mode, each sign-in option must specify an `hd` field +indicating what domain it supports. This can be either a regex +(such as `/@example\.com$/`) or the domain string (e.g., `example.com`). + +The code below shows a tenant configured to use identifier mode: + +```javascript +tenantId1: { + signInOptions: [ + // Email/password sign-in. + { + hd: 'acme.com', // using regex: /@acme\.com$/ + // ... + }, +``` + +### Setting up callbacks +The configuration object contains a set of optional callbacks that are invoked +at various points during the authentication flow. This allows you to +additionally customize the UI. The following hooks are available: + +|Callback | Description | +|------------------|------------------------------------------------------| +|selectTenantUiShown()|Triggered when the UI to select a tenant is shown. Use this if you want to modify the UI with a customized title or theme.| +|selectTenantUiHidden()|Triggered when the UI to select a tenant is hidden, which is always before `signInUiShown` is triggered (if applicable). Use this if you want to hide the UI displayed in `selectTenantUiShown`.| +|signInUiShown(tenantId)|Triggered when a tenant is selected and the UI for the user to enter their credentials is shown. Use this if you want to modify the UI with a customized title or theme.| +|beforeSignInSuccess(user)|Triggered before sign-in completes. Use this to modify a signed in user before redirecting back to the IAP resource.| + +The following example code shows how you might implement these callbacks: + +```javascript +callbacks: { + selectTenantUiShown: () => { + // Show info of the IAP resource. + showUiTitle( + 'Select your employer to access your Health Benefits'); + }, + selectTenantUiHidden: () => { + // Hide the previous displayed title. + hideUiTitle(); + }, + signInUiShown: (tenantId) => { + // Show tenant title and additional display info. + const tenantName = getTenantNameFromId(tenantId); + showUiTitle(`Sign in to access your ${tenantName} Health Benefits`); + }, + beforeSignInSuccess: (user) => { + // Do additional processing on user before sign-in is + // complete. + // For example update the user profile. + return user.updateProfile({ + photoURL: 'https://example.com/profile/1234/photo.png', + }).then(function() { + // To reflect updated photoURL in the ID token, force token + // refresh. + return user.getIdToken(true); + }).then(function() { + return user; + }); + } +} +``` + +### Configuring a single federated provider +If you only have one tenant/top-level project configured for the IAP resouce and +only one provider enabled for the tenant/project, FirebaseUI will act as a proxy +between the IAP resource and the federated IdP without showing any UI before +redirecting to the IdP sign-in page: + +```javascript +// The project configurations for a single Facebook identity provider. +const configs = { + // Configuration for project identified by API key API_KEY1. + API_KEY1: { + authDomain: 'project-id1.firebaseapp.com', + tenants: { + // The '*' key is used as a fallback when the tenant ID is not found. So + // you can use it for single tenant/top-level project flow to replace the + // hardcoded tenant ID or '_'. + '*': { + displayName: 'My Organization', + signInOptions: [ + // Replace Facebook with another provider ID or provider configuration + // object if you want to use a different IdP. + firebase.auth.FacebookAuthProvider.PROVIDER_ID, + ], + immediateFederatedRedirect: true, + }, + }, + }, +}; +``` + +### Configuring providers for multiple projects +In most cases, you only need to specify a single API key. However, if you want +to use a single authentication URL across multiple projects, you can include +multiple API keys in a single configuration object. + +```javascript +const configs = { + API_KEY1: { + // Config goes here + }, + API_KEY2: { + // Config goes here + }, +} +``` + +### API reference + +```typescript +// The type of the configuration object for FirebaseUiHandler. The key of the +// object is the API key of the project. +type CIAPHandlerConfig = {[key: string]: GCIPProjectConfig}; + +// Interface that represents the project-level configuration. +interface GCIPProjectConfig { + // The Auth domain of the project. Retrieve it from + // https://console.cloud.google.com/customer-identity + authDomain: string; + // The display mode of for tenant selection: `optionFirst` or + // `identifierFirst`. Default: `optionFirst` + displayMode?: string; + // The URL of the Terms of Service page or a callback function to be invoked + // when Terms of Service link is clicked in tenant selection page for + // optionFirst mode or enter email for tenant matching page for + // identifierFirst mode. + tosUrl?: (() => void) | string; + // The URL of the Privacy Policy page or a callback function to be invoked + // when Privacy Policy link is clicked in tenant selection page for + // optionFirst mode or enter email for tenant matching page for + // identifierFirst mode. + privacyPolicyUrl?: (() => void) | string; + // The object of developers callbacks after specific events. + callbacks?: Callbacks; + // The tenant-level configurations object. The key of the object is the + // tenant ID. If using non-tenant flow, use `_` for the key. + tenants: {[key: string]: TenantConfig}; +} + +// Interface that represents the available developer callbacks. +interface Callbacks { + // The callback to trigger when the tenant selection page for optionFirst + // mode or enter email for tenant matching page for identifierFirst mode + // is shown. + selectTenantUiShown?(): void; + // The callback to trigger when the tenant selection page for optionFirst + // mode or enter email for tenant matching page for identifierFirst mode + // is hidden. + selectTenantUiHidden?(): void; + // The callback to trigger when the sign-in page is shown. The tenant ID is + // passed to the callback. + signInUiShown?(string): void; + // The callback to triggered before sign-in completes. The signed in user is + // passed to the callback. + beforeSignInSuccess?(currentUser: firebase.User): Promise; +} + +// Interface that represents the tenant-level configurations. +interface TenantConfig { + // The display name for tenant in the tenant selection button. Only needed if + // you are using the option first mode. + displayName?: string; + // The color of the tenant selection button. Only needed if you are + // using the option first mode. + buttonColor?: string; + // The URL of the icon for tenant selection button. Only needed + // if you are using the option first mode. + iconUrl?: string; + // A boolean which determines whether to immediately redirect to the + // provider's site or instead show the default 'Sign in with Provider' button + // when there is only a single federated provider in signInOptions. In order + // for this option to take effect, the signInOptions must only hold a single + // federated provider (like 'google.com') and signInFlow must be set to + // `redirect`. + immediateFederatedRedirect?: boolean; + // The sign-in flow to use for IDP providers: `redirect` or `popup`. + // Default: `redirect` + signInFlow?: string; + // The list of providers enabled for signing into your app. The order you + // specify them will be the order they are displayed on the sign-in provider + // selection screen. + signInOptions: Array; + // The URL of the Terms of Service page or a callback function to be invoked + // when Terms of Service link is clicked after the tenant is determind. + tosUrl?: (() => void) | string; + // The URL of the Terms of Service page or a callback function to be invoked + // when Terms of Service link is clicked after the tenant is determind. + privacyPolicyUrl?: (() => void) | string; +} + +// Interface the represent the sign-in provider configuration. Additional +// configurations might be available according to different providers. +interface SignInOption { + // The provider ID. + provider: string; + // The domain string or a regex to match email address. Only needed if you + // are using the identifier first mode. + hd?: string; + // The provider name displayed to end users (sign in button/linking prompt). + providerName?: string; + // The color of sign in button. + buttonColor?: string; + // The URL of the Identity Provider's icon. This will be displayed on the + // provider's sign-in button, etc. + iconUrl?: string; +} + +// The FirebaseUI handler to handle the UI flows for tenant selection, +// user authentication, token refreshes and sign-out. +class FirebaseUiHandler { + constructor( + element: Element|string, + configs: {[key: string]: firebaseui.auth.CIAPHandlerConfig}); +} +``` + +## Configuring Authentication URL +You can host your authentication UI using Firebase Hosting or other web hosting +services. Once it's done, register the URL of the authentication UI in the +side panel of [IAP page](https://console.cloud.google.com/security/iap) +in the Cloud Console. +You need to select whether to use `project providers` or `tenants`, and check +the boxes of the providers or tenants to enable. diff --git a/gulpfile.js b/gulpfile.js index 2339b8bc..d9f6d540 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -60,14 +60,14 @@ const MDL_COMPONENTS = [ 'progress/progress', 'spinner/spinner', 'textfield/textfield' -] +]; // The external dependencies needed by FirebaseUI as ES module imports. const ESM_DEPS = [ 'import * as firebase from \'firebase/app\'', 'import \'firebase/auth\'', 'import dialogPolyfill from \'dialog-polyfill\'', -].concat(MDL_COMPONENTS.map(component => `import \'material-design-lite/src/${component}\'`)) +].concat(MDL_COMPONENTS.map(component => `import \'material-design-lite/src/${component}\'`)); // The external dependencies needed by FirebaseUI as CommonJS modules. const CJS_DEPS = [ @@ -229,6 +229,11 @@ function buildFirebaseUiJs(locale) { only_closure_dependencies: true, output_wrapper: OUTPUT_WRAPPER, + // This is required to support @export annotation to expose external + // properties. + export_local_property_definitions: true, + generate_exports: true, + // This is required to match XTB IDs to the JS/Soy messages. translations_project: 'FirebaseUI' }; @@ -250,7 +255,7 @@ function buildFirebaseUiJs(locale) { * @param {string} locale The desired FirebaseUI locale. * @param {string} outBaseName The prefix of the output file name. * @param {string} outputWrapper A wrapper with which to wrap the output JS. - * @param {string[]} dependencies The dependencies to concatenate. + * @param {?Array=} dependencies The dependencies to concatenate. * @return {*} A stream that ends when compilation finishes. */ function concatWithDeps(locale, outBaseName, outputWrapper, dependencies = []) { diff --git a/image/microsoft.svg b/image/microsoft.svg index 1f739764..e8f27e44 100644 --- a/image/microsoft.svg +++ b/image/microsoft.svg @@ -1 +1 @@ -MS-SymbolLockup \ No newline at end of file +MS-SymbolLockup diff --git a/image/oidc.svg b/image/oidc.svg new file mode 100644 index 00000000..a385c556 --- /dev/null +++ b/image/oidc.svg @@ -0,0 +1,11 @@ + + + + icon-oidc-24 + Created with Sketch. + + + + + + diff --git a/image/saml.svg b/image/saml.svg new file mode 100644 index 00000000..75b62417 --- /dev/null +++ b/image/saml.svg @@ -0,0 +1 @@ + diff --git a/javascript/data/country.js b/javascript/data/country.js index 779626a4..79e5d1d5 100644 --- a/javascript/data/country.js +++ b/javascript/data/country.js @@ -26,80 +26,82 @@ goog.require('goog.structs.Trie'); /** - * Defines a prefix tree for storing all the country codes to facilate phone + * Defines a prefix tree for storing all the country codes to facilitate phone * number country code lookup. - * @param {!Array} countries The list of - * countries to construct a prefix tree for. - * @constructor */ -firebaseui.auth.data.country.LookupTree = function(countries) { +firebaseui.auth.data.country.LookupTree = class { /** - * @private {!Array} The list of - * countries to construct a prefix tree for. + * @param {!Array} countries The list + * of countries to construct a prefix tree for. */ - this.countries_ = countries; - /** - * @private { - * !goog.structs.Trie>} The - * prefix tree. - */ - this.trie_ = new goog.structs.Trie(); - // Initialize prefix tree like structure. - this.init_(); -}; - + constructor(countries) { + /** + * @private {!Array} The list of + * countries to construct a prefix tree for. + */ + this.countries_ = countries; + /** + * @private { + * !goog.structs.Trie>} The + * prefix tree. + */ + this.trie_ = new goog.structs.Trie(); + // Initialize prefix tree like structure. + this.init_(); + } -/** - * Populates the prefix tree structure. - * @private - */ -firebaseui.auth.data.country.LookupTree.prototype.init_ = function() { - // Populate the prefix tree. - for (var i = 0; i < this.countries_.length; i++) { - // Construct key. - var key = '+' + this.countries_[i].e164_cc; - // Check if key exists. - var nodeValue = this.trie_.get(key); - if (nodeValue) { - // If so, add country object to its array. - nodeValue.push(this.countries_[i]); - } else { - // Else add that key/value. - this.trie_.add('+' + this.countries_[i].e164_cc, [this.countries_[i]]); + /** + * Populates the prefix tree structure. + * @private + */ + init_() { + // Populate the prefix tree. + for (var i = 0; i < this.countries_.length; i++) { + // Construct key. + var key = '+' + this.countries_[i].e164_cc; + // Check if key exists. + var nodeValue = this.trie_.get(key); + if (nodeValue) { + // If so, add country object to its array. + nodeValue.push(this.countries_[i]); + } else { + // Else add that key/value. + this.trie_.add('+' + this.countries_[i].e164_cc, [this.countries_[i]]); + } } } -}; -/** - * Looks up the country that matches the code's prefix. - * @param {string} code The string that could contain a country code prefix. - * @return {!Array} The country objects - * that match the prefix of the code provided, empty array if not found. - */ -firebaseui.auth.data.country.LookupTree.prototype.search = function(code) { - // Get all keys and prefixes. - var keyAndPrefixes = this.trie_.getKeyAndPrefixes(code); - // Get matching key and prefixes. - for (var key in keyAndPrefixes) { - if (keyAndPrefixes.hasOwnProperty(key)) { - // Pick first one. There should always be one as country codes can't be - // prefixes of each other. - return keyAndPrefixes[key]; + /** + * Looks up the country that matches the code's prefix. + * @param {string} code The string that could contain a country code prefix. + * @return {!Array} The country objects + * that match the prefix of the code provided, empty array if not found. + */ + search(code) { + // Get all keys and prefixes. + var keyAndPrefixes = this.trie_.getKeyAndPrefixes(code); + // Get matching key and prefixes. + for (var key in keyAndPrefixes) { + if (keyAndPrefixes.hasOwnProperty(key)) { + // Pick first one. There should always be one as country codes can't be + // prefixes of each other. + return keyAndPrefixes[key]; + } } + return []; } - return []; -}; -/** - * Gets the list of counties used to build up the tree. - * @return {!Array} The list of - * countries to construct the prefix tree for. - */ -firebaseui.auth.data.country.LookupTree.prototype.getCountries = function() { - return this.countries_; + /** + * Gets the list of counties used to build up the tree. + * @return {!Array} The list of + * countries to construct the prefix tree for. + */ + getCountries() { + return this.countries_; + } }; diff --git a/javascript/testing/acclient.js b/javascript/testing/acclient.js index 606690bd..45e0befb 100644 --- a/javascript/testing/acclient.js +++ b/javascript/testing/acclient.js @@ -28,187 +28,165 @@ var acClient = goog.require('firebaseui.auth.acClient'); /** * Fake accountchooser.com client class. - * @constructor - * @extends {Disposable} */ -var FakeAcClient = function() { - this.selectTried_ = false; - this.skipSelect_ = false; - this.preSkip_ = null; - this.available_ = true; - this.localAccounts_ = null; - this.acResult_ = null; - this.initialized_ = false; - this.callbackUrl_ = null; - this.onEmptyResponse_ = null; -}; -goog.inherits(FakeAcClient, Disposable); - - -/** - * Installs the fake accountchooser.com client. - * @return {!FakeAcClient} The fake accountchooser.com - * client. - */ -FakeAcClient.prototype.install = function() { - var r = this.replacer_ = new PropertyReplacer(); - r.set(acClient, 'isInitialized', - goog.bind(this.isInitialized_, this)); - r.set(acClient, 'init', goog.bind(this.init_, this)); - r.set( - acClient, - 'trySelectAccount', - goog.bind(this.trySelectAccount_, this)); - return this; -}; - - -/** Removes the fake accountchooser.com client hooks. */ -FakeAcClient.prototype.uninstall = function() { - this.localAccounts_ = null; - this.acResult_ = null; - this.selectTried_ = false; - this.initialized_ = false; - this.callbackUrl_ = null; - this.onEmptyResponse_ = null; - if (this.replacer_) { - this.replacer_.reset(); - this.replacer_ = null; +class FakeAcClient extends Disposable { + constructor() { + super(); + this.selectTried_ = false; + this.skipSelect_ = false; + this.preSkip_ = null; + this.available_ = true; + this.localAccounts_ = null; + this.acResult_ = null; + this.initialized_ = false; + this.callbackUrl_ = null; + this.onEmptyResponse_ = null; } -}; - - -/** @override */ -FakeAcClient.prototype.disposeInternal = function() { - this.uninstall(); - FakeAcClient.base(this, 'disposeInternal'); -}; - - -/** - * Sets whether accountchooser.com is available or not. - * @param {boolean} available Whether accountchooser.com is available. - */ -FakeAcClient.prototype.setAvailability = - function(available) { - this.available_ = available; -}; - -/** - * Sets whether to skip selecting an account. - * @param {boolean} skip Whether to skip selecting an account. - * @param {function()=} opt_preSkip Callback to invoke before onSkip callback. - */ -FakeAcClient.prototype.setSkipSelect = function(skip, opt_preSkip) { - this.skipSelect_ = skip; - if (skip) { - this.preSkip_ = opt_preSkip || null; + /** + * Installs the fake accountchooser.com client. + * @return {!FakeAcClient} The fake accountchooser.com + * client. + */ + install() { + var r = this.replacer_ = new PropertyReplacer(); + r.set(acClient, 'isInitialized', goog.bind(this.isInitialized_, this)); + r.set(acClient, 'init', goog.bind(this.init_, this)); + r.set( + acClient, 'trySelectAccount', goog.bind(this.trySelectAccount_, this)); + return this; } -}; - - -/** - * Sets the response from accountchooser.com to a selected account. - * @param {!Account} account The selected account. - */ -FakeAcClient.prototype.setSelectedAccount = - function(account) { - this.acResult_ = account; -}; - - -/** Sets the response from accountchooser.com as add account. */ -FakeAcClient.prototype.setAddAccount = function() { - this.acResult_ = 'Add account'; -}; + /** Removes the fake accountchooser.com client hooks. */ + uninstall() { + this.localAccounts_ = null; + this.acResult_ = null; + this.selectTried_ = false; + this.initialized_ = false; + this.callbackUrl_ = null; + this.onEmptyResponse_ = null; + if (this.replacer_) { + this.replacer_.reset(); + this.replacer_ = null; + } + } -/** - * @see firebaseui.auth.acClient.isInitialized - * @return {boolean} Whether accountchooser.com API client is initialized. - * @private - */ -FakeAcClient.prototype.isInitialized_ = function() { - return this.initialized_; -}; + /** @override */ + disposeInternal() { + this.uninstall(); + super.disposeInternal(); + } + /** + * Sets whether accountchooser.com is available or not. + * @param {boolean} available Whether accountchooser.com is available. + */ + setAvailability(available) { + this.available_ = available; + } -/** - * Simulates when accountchooser.com is initially called and an empty response - * is returned. - */ -FakeAcClient.prototype.forceOnEmpty = function() { - if (this.onEmptyResponse_) { - this.onEmptyResponse_(); + /** + * Sets whether to skip selecting an account. + * @param {boolean} skip Whether to skip selecting an account. + * @param {function()=} opt_preSkip Callback to invoke before onSkip callback. + */ + setSkipSelect(skip, opt_preSkip) { + this.skipSelect_ = skip; + if (skip) { + this.preSkip_ = opt_preSkip || null; + } } -}; + /** + * Sets the response from accountchooser.com to a selected account. + * @param {!Account} account The selected account. + */ + setSelectedAccount(account) { + this.acResult_ = account; + } -/** - * @see firebaseui.auth.acClient.init - * @private - */ -FakeAcClient.prototype.init_ = function( - opt_onEmptyResponse, - opt_onAccountSelected, - opt_onAddAccount, - opt_providers, - opt_language, - opt_uiConfig) { - this.onEmptyResponse_ = opt_onEmptyResponse; - this.initialized_ = true; - if (this.acResult_ == null) { - opt_onEmptyResponse(); - } else if (this.acResult_ instanceof Account) { - opt_onAccountSelected( - /** @type {!Account} */(this.acResult_)); - } else if (this.acResult_ == 'Add account') { - opt_onAddAccount(this.available_); - } else { - throw new Error('unknown accountchooser result.'); + /** Sets the response from accountchooser.com as add account. */ + setAddAccount() { + this.acResult_ = 'Add account'; } -}; + /** + * @see firebaseui.auth.acClient.isInitialized + * @return {boolean} Whether accountchooser.com API client is initialized. + * @private + */ + isInitialized_() { + return this.initialized_; + } -/** - * @see firebaseui.auth.acClient.trySelectAccount - * @private - */ -FakeAcClient.prototype.trySelectAccount_ = function( - onSkipSelect, opt_localAccounts, opt_callbackUrl) { - this.selectTried_ = true; - this.localAccounts_ = opt_localAccounts; - this.callbackUrl_ = opt_callbackUrl; - if (this.skipSelect_) { - if (this.preSkip_) { - this.preSkip_(); + /** + * Simulates when accountchooser.com is initially called and an empty response + * is returned. + */ + forceOnEmpty() { + if (this.onEmptyResponse_) { + this.onEmptyResponse_(); } - onSkipSelect(this.available_); } -}; + /** + * @see firebaseui.auth.acClient.init + * @private + */ + init_( + opt_onEmptyResponse, opt_onAccountSelected, opt_onAddAccount, + opt_providers, opt_language, opt_uiConfig) { + this.onEmptyResponse_ = opt_onEmptyResponse; + this.initialized_ = true; + if (this.acResult_ == null) { + opt_onEmptyResponse(); + } else if (this.acResult_ instanceof Account) { + opt_onAccountSelected( + /** @type {!Account} */ (this.acResult_)); + } else if (this.acResult_ == 'Add account') { + opt_onAddAccount(this.available_); + } else { + throw new Error('unknown accountchooser result.'); + } + } -/** - * Asserts the client has tried to select an account from accountchooser.com. - * @param {?Array=} opt_localAccounts Local account - * list. - * @param {string=} opt_callbackUrl The URL to return to when the flow finishes. - * The default is current URL. - */ -FakeAcClient.prototype.assertTrySelectAccount = - function(opt_localAccounts, opt_callbackUrl) { - assertTrue(this.selectTried_); - if (opt_localAccounts) { - assertArrayEquals(opt_localAccounts, this.localAccounts_); - } else { - assertTrue(this.localAccounts_ == null); + /** + * @see firebaseui.auth.acClient.trySelectAccount + * @private + */ + trySelectAccount_(onSkipSelect, opt_localAccounts, opt_callbackUrl) { + this.selectTried_ = true; + this.localAccounts_ = opt_localAccounts; + this.callbackUrl_ = opt_callbackUrl; + if (this.skipSelect_) { + if (this.preSkip_) { + this.preSkip_(); + } + onSkipSelect(this.available_); + } } - if (opt_callbackUrl) { - assertEquals(opt_callbackUrl, this.callbackUrl_); - } else { - assertTrue(this.callbackUrl_ == null); + + /** + * Asserts the client has tried to select an account from accountchooser.com. + * @param {?Array=} opt_localAccounts Local account + * list. + * @param {string=} opt_callbackUrl The URL to return to when the flow + * finishes. The default is current URL. + */ + assertTrySelectAccount(opt_localAccounts, opt_callbackUrl) { + assertTrue(this.selectTried_); + if (opt_localAccounts) { + assertArrayEquals(opt_localAccounts, this.localAccounts_); + } else { + assertTrue(this.localAccounts_ == null); + } + if (opt_callbackUrl) { + assertEquals(opt_callbackUrl, this.callbackUrl_); + } else { + assertTrue(this.callbackUrl_ == null); + } } -}; +} + exports = FakeAcClient; diff --git a/javascript/testing/appclient.js b/javascript/testing/appclient.js index 07eed6ef..c636d6c2 100644 --- a/javascript/testing/appclient.js +++ b/javascript/testing/appclient.js @@ -20,42 +20,44 @@ goog.module('firebaseui.auth.testing.FakeAppClient'); goog.module.declareLegacyNamespace(); goog.setTestOnly(); -var Disposable = goog.require('goog.Disposable'); -var FakeAuthClient = goog.require('firebaseui.auth.testing.FakeAuthClient'); -var GoogPromise = goog.require('goog.Promise'); - +const Disposable = goog.require('goog.Disposable'); +const FakeAuthClient = goog.require('firebaseui.auth.testing.FakeAuthClient'); +const GoogPromise = goog.require('goog.Promise'); /** * Fake App client class. - * @param {!Object} options The app configuration. - * @param {?string=} opt_name The optional app name. - * @constructor - * @extends {Disposable} - */ -var FakeAppClient = function(options, opt_name) { - this['options'] = options || {}; - this['name'] = opt_name || '[DEFAULT]'; - // Initialize a fake auth client instance. - this.auth_ = new FakeAuthClient(this); -}; - - -/** - * @return {!FakeAuthClient} The associated fake Auth - * client instance. */ -FakeAppClient.prototype.auth = function() { - return this.auth_; -}; - +class FakeAppClient extends Disposable { + /** + * @param {!Object} options The app configuration. + * @param {?string=} name The optional app name. + */ + constructor(options, name) { + super(); + this.options = options || {}; + this.name = name || '[DEFAULT]'; + // Initialize a fake auth client instance. + this.auth_ = new FakeAuthClient(this); + }; + + + /** + * @return {!FakeAuthClient} The associated fake Auth client instance. + */ + auth() { + return this.auth_; + } + + + /** + * Dummy app delete method. + * @return {!GoogPromise} The promise that resolves upon deletion. + */ + delete() { + return GoogPromise.resolve(); + } +} -/** - * Dummy app delete method. - * @return {!GoogPromise} The promise that resolves upon deletion. - */ -FakeAppClient.prototype.delete = function() { - return GoogPromise.resolve(); -}; exports = FakeAppClient; diff --git a/javascript/testing/auth.js b/javascript/testing/auth.js index ebfb9b15..a6f4bdad 100644 --- a/javascript/testing/auth.js +++ b/javascript/testing/auth.js @@ -26,159 +26,152 @@ var array = goog.require('goog.array'); /** * Fake Auth API client class. - * @param {!firebaseui.auth.testing.FakeAppClient} app The app instance - * associated with the fake Auth client. - * @constructor - * @extends {MockHelper} */ -var FakeAuthClient = function(app) { - this.user_ = {}; - this['app'] = app; - this['currentUser'] = null; - var asyncMethods = {}; - // Populate auth async methods. - for (var key in FakeAuthClient.AuthAsyncMethod) { - asyncMethods[key] = { - 'context': this, - 'name': FakeAuthClient.AuthAsyncMethod[key] - }; - } - // Populate user async methods. - for (var key in FakeAuthClient.UserAsyncMethod) { - asyncMethods[key] = { - 'context': this.user_, - 'name': FakeAuthClient.UserAsyncMethod[key] +class FakeAuthClient extends MockHelper { + /** + * @param {!firebaseui.auth.testing.FakeAppClient} app The app instance + * associated with the fake Auth client. + */ + constructor(app) { + super(); + this.user_ = {}; + this['app'] = app; + this['currentUser'] = null; + var asyncMethods = {}; + // Populate auth async methods. + for (var key in FakeAuthClient.AuthAsyncMethod) { + asyncMethods[key] = { + 'context': this, + 'name': FakeAuthClient.AuthAsyncMethod[key] + }; + } + // Populate user async methods. + for (var key in FakeAuthClient.UserAsyncMethod) { + asyncMethods[key] = { + 'context': this.user_, + 'name': FakeAuthClient.UserAsyncMethod[key] + }; + } + // Pass async methods enum to base class. + this.asyncMethods = asyncMethods; + this.authObservers_ = []; + this.idTokenObservers_ = []; + /** @private {!Array} The list of frameworks logged. */ + this.frameworks_ = []; + var self = this; + this['INTERNAL'] = { + logFramework: function(framework) { + self.frameworks_.push(framework); + } }; } - FakeAuthClient.base(this, 'constructor', asyncMethods); - this.authObservers_ = []; - this.idTokenObservers_ = []; - /** @private {!Array} The list of frameworks logged. */ - this.frameworks_ = []; - var self = this; - this['INTERNAL'] = { - logFramework: function(framework) { - self.frameworks_.push(framework); - } - }; -}; -goog.inherits(FakeAuthClient, MockHelper); - - -/** - * Asserts the expected list of frameworks IDs are logged. - * @param {!Array} expectedFrameworks The expected list of frameworks - * logged. - */ -FakeAuthClient.prototype.assertFrameworksLogged = function(expectedFrameworks) { - assertArrayEquals(expectedFrameworks, this.frameworks_); -}; + /** + * Asserts the expected list of frameworks IDs are logged. + * @param {!Array} expectedFrameworks The expected list of frameworks + * logged. + */ + assertFrameworksLogged(expectedFrameworks) { + assertArrayEquals(expectedFrameworks, this.frameworks_); + } -/** - * Updates the user with selected properties. - * @param {!Object} props The updated user properties, if null, user is - * nullified. - */ -FakeAuthClient.prototype.setUser = function(props) { - if (props) { - // User is logged in. - for (var key in FakeAuthClient.UserProperty) { - var prop = FakeAuthClient.UserProperty[key]; - this.user_[prop] = props[prop]; + /** + * Updates the user with selected properties. + * @param {!Object} props The updated user properties, if null, user is + * nullified. + */ + setUser(props) { + if (props) { + // User is logged in. + for (var key in FakeAuthClient.UserProperty) { + var prop = FakeAuthClient.UserProperty[key]; + this.user_[prop] = props[prop]; + } + this['currentUser'] = this.user_; + } else { + // User is logged out. + this['currentUser'] = null; } - this['currentUser'] = this.user_; - } else { - // User is logged out. - this['currentUser'] = null; } -}; - -/** - * Triggers all registered auth state change listeners. - */ -FakeAuthClient.prototype.runAuthChangeHandler = - function() { - for (var i = 0; i < this.authObservers_.length; i++) { - this.authObservers_[i](this['currentUser']); + /** + * Triggers all registered auth state change listeners. + */ + runAuthChangeHandler() { + for (var i = 0; i < this.authObservers_.length; i++) { + this.authObservers_[i](this['currentUser']); + } } -}; - -/** - * Adds an observer for auth state user changes. - * @param {!Object|function(?Object)} - * nextOrObserver An observer object or a function triggered on change. - * @param {function(!Object)=} opt_error Optional A function - * triggered on auth error. - * @param {function()=} opt_completed Optional A function triggered when the - * observer is removed. - * @return {!function()} The unsubscribe function for the observer. - */ -FakeAuthClient.prototype.onAuthStateChanged = function( - nextOrObserver, opt_error, opt_completed) { - // Simplified version of the observer for testing purpose only. - var observer = (goog.isFunction(nextOrObserver) && nextOrObserver) || - nextOrObserver['next']; - if (!observer) { - throw 'onAuthStateChanged must be called with an Observer or up to three ' + - 'functions: next, error and completed.'; + /** + * Adds an observer for auth state user changes. + * @param {!Object|function(?Object)} + * nextOrObserver An observer object or a function triggered on change. + * @param {function(!Object)=} opt_error Optional A function + * triggered on auth error. + * @param {function()=} opt_completed Optional A function triggered when the + * observer is removed. + * @return {function()} The unsubscribe function for the observer. + */ + onAuthStateChanged(nextOrObserver, opt_error, opt_completed) { + // Simplified version of the observer for testing purpose only. + var observer = (goog.isFunction(nextOrObserver) && nextOrObserver) || + nextOrObserver['next']; + if (!observer) { + throw 'onAuthStateChanged must be called with an Observer or up to three ' + + 'functions: next, error and completed.'; + } + this.authObservers_.push(observer); + var self = this; + // This will allow the subscriber to remove the observer. + var unsubscribe = function() { + // Removes the observer. + array.removeAllIf(self.authObservers_, function(obs) { + return obs == observer; + }); + }; + return unsubscribe; } - this.authObservers_.push(observer); - var self = this; - // This will allow the subscriber to remove the observer. - var unsubscribe = function() { - // Removes the observer. - array.removeAllIf(self.authObservers_, function(obs) { - return obs == observer; - }); - }; - return unsubscribe; -}; - -/** - * Triggers all registered ID token change listeners. - */ -FakeAuthClient.prototype.runIdTokenChangeHandler = - function() { - for (var i = 0; i < this.idTokenObservers_.length; i++) { - this.idTokenObservers_[i](this['currentUser']); + /** + * Triggers all registered ID token change listeners. + */ + runIdTokenChangeHandler() { + for (var i = 0; i < this.idTokenObservers_.length; i++) { + this.idTokenObservers_[i](this['currentUser']); + } } -}; - -/** - * Adds an observer for ID token state changes. - * @param {!Object|function(?Object)} - * nextOrObserver An observer object or a function triggered on change. - * @param {function(!Object)=} opt_error Optional A function - * triggered on auth error. - * @param {function()=} opt_completed Optional A function triggered when the - * observer is removed. - * @return {!function()} The unsubscribe function for the observer. - */ -FakeAuthClient.prototype.onIdTokenChanged = function( - nextOrObserver, opt_error, opt_completed) { - // Simplified version of the observer for testing purpose only. - var observer = (goog.isFunction(nextOrObserver) && nextOrObserver) || - nextOrObserver['next']; - if (!observer) { - throw 'onIdTokenChanged must be called with an Observer or up to three ' + - 'functions: next, error and completed.'; + /** + * Adds an observer for ID token state changes. + * @param {!Object|function(?Object)} + * nextOrObserver An observer object or a function triggered on change. + * @param {function(!Object)=} opt_error Optional A function + * triggered on auth error. + * @param {function()=} opt_completed Optional A function triggered when the + * observer is removed. + * @return {function()} The unsubscribe function for the observer. + */ + onIdTokenChanged(nextOrObserver, opt_error, opt_completed) { + // Simplified version of the observer for testing purpose only. + var observer = (goog.isFunction(nextOrObserver) && nextOrObserver) || + nextOrObserver['next']; + if (!observer) { + throw 'onIdTokenChanged must be called with an Observer or up to three ' + + 'functions: next, error and completed.'; + } + this.idTokenObservers_.push(observer); + var self = this; + // This will allow the subscriber to remove the observer. + var unsubscribe = function() { + // Removes the observer. + array.removeAllIf(self.idTokenObservers_, function(obs) { + return obs == observer; + }); + }; + return unsubscribe; } - this.idTokenObservers_.push(observer); - var self = this; - // This will allow the subscriber to remove the observer. - var unsubscribe = function() { - // Removes the observer. - array.removeAllIf(self.idTokenObservers_, function(obs) { - return obs == observer; - }); - }; - return unsubscribe; -}; +} /** diff --git a/javascript/testing/auth_test.js b/javascript/testing/auth_test.js index f2a27600..83cacbc0 100644 --- a/javascript/testing/auth_test.js +++ b/javascript/testing/auth_test.js @@ -19,6 +19,7 @@ goog.provide('firebaseui.auth.AuthTest'); goog.require('firebaseui.auth.testing.FakeAppClient'); +goog.require('goog.Promise'); goog.require('goog.testing.AsyncTestCase'); goog.require('goog.testing.jsunit'); diff --git a/javascript/testing/cookiestorage.js b/javascript/testing/cookiestorage.js index f89fd079..747ea176 100644 --- a/javascript/testing/cookiestorage.js +++ b/javascript/testing/cookiestorage.js @@ -20,83 +20,80 @@ goog.module('firebaseui.auth.testing.FakeCookieStorage'); goog.module.declareLegacyNamespace(); goog.setTestOnly(); -var Disposable = goog.require('goog.Disposable'); -var PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); -var cookies = goog.require('goog.net.cookies'); +const Disposable = goog.require('goog.Disposable'); +const PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); +const cookies = goog.require('goog.net.cookies'); /** * Fake cookie storage client class. This makes it safe to isolate cookies * between tests and doesn't require manual cleaning each time. It also makes it * easy to test cookie expiration with MockClock. - * @constructor - * @extends {Disposable} */ -var FakeCookieStorage = function() { - /** - * @private {!Object} The mock - * cookie storage hash map. - */ - this.mockCookieStorage_ = {}; -}; -goog.inherits(FakeCookieStorage, Disposable); +class FakeCookieStorage extends Disposable { + constructor() { + super(); + /** + * @private {!Object} The mock + * cookie storage hash map. + */ + this.mockCookieStorage_ = {}; + /** + * @private {?PropertyReplacer} + */ + this.replacer_ = null; + } -/** - * Installs the fake cookie storage utility. - * @return {!FakeCookieStorage} The fake cookie storage utility. - */ -FakeCookieStorage.prototype.install = function() { - // Note that this mock cookie storage does not take into account path, domain - // and secure flag when manipulating cookies. - var self = this; - var r = this.replacer_ = new PropertyReplacer(); - r.replace( - cookies, - 'set', - function(key, value, maxAge, path, domain, secure) { - self.mockCookieStorage_[key] = { - 'value': value, - 'expiration': goog.now() + maxAge * 1000 - }; - }); - r.replace( - cookies, - 'get', - function(key) { - // Make sure entry exist and is not expired. - if (self.mockCookieStorage_[key] && - self.mockCookieStorage_[key].expiration > goog.now()) { - return self.mockCookieStorage_[key].value; - } else { - delete self.mockCookieStorage_[key]; - return null; - } - }); - r.replace( - cookies, - 'remove', - function(key, path, domain) { + /** + * Installs the fake cookie storage utility. + * @return {!FakeCookieStorage} The fake cookie storage utility. + */ + install() { + // Note that this mock cookie storage does not take into account path, + // domain and secure flag when manipulating cookies. + var self = this; + var r = this.replacer_ = new PropertyReplacer(); + r.replace( + cookies, 'set', function(key, value, maxAge, path, domain, secure) { + self.mockCookieStorage_[key] = { + 'value': value, + 'expiration': goog.now() + maxAge * 1000 + }; + }); + r.replace(cookies, 'get', function(key) { + // Make sure entry exist and is not expired. + if (self.mockCookieStorage_[key] && + self.mockCookieStorage_[key].expiration > goog.now()) { + return self.mockCookieStorage_[key].value; + } else { delete self.mockCookieStorage_[key]; - }); - return this; -}; + return null; + } + }); + r.replace(cookies, 'remove', function(key, path, domain) { + delete self.mockCookieStorage_[key]; + }); + return this; + } -/** Removes the fake cookie storage utility hooks. */ -FakeCookieStorage.prototype.uninstall = function() { - this.mockCookieStorage_ = {}; - if (this.replacer_) { - this.replacer_.reset(); - this.replacer_ = null; + /** Removes the fake cookie storage utility hooks. */ + uninstall() { + this.mockCookieStorage_ = {}; + if (this.replacer_) { + this.replacer_.reset(); + this.replacer_ = null; + } } -}; -/** @override */ -FakeCookieStorage.prototype.disposeInternal = function() { - this.uninstall(); - FakeCookieStorage.base(this, 'disposeInternal'); -}; + /** @override */ + disposeInternal() { + this.uninstall(); + super.disposeInternal(); + } +} + exports = FakeCookieStorage; diff --git a/javascript/testing/mockhelper.js b/javascript/testing/mockhelper.js index 7f36137f..54594965 100644 --- a/javascript/testing/mockhelper.js +++ b/javascript/testing/mockhelper.js @@ -21,219 +21,219 @@ goog.module('firebaseui.auth.testing.MockHelper'); goog.module.declareLegacyNamespace(); goog.setTestOnly(); -var Disposable = goog.require('goog.Disposable'); -var GoogPromise = goog.require('goog.Promise'); +const Disposable = goog.require('goog.Disposable'); +const GoogPromise = goog.require('goog.Promise'); /** * Creates a mock helper. - * @param {!Object} asyncMethods The async - * methods enums. - * @constructor - * @extends {Disposable} + * @unrestricted */ -var MockHelper = function(asyncMethods) { - MockHelper.base(this, 'constructor'); +class MockHelper extends Disposable { /** - * @private {!Object>} The actual calls made with the - * async method enum keys as keys here too. + * @param {?Object=} asyncMethods The + * optional async methods enums. */ - this.actualCalls_ = {}; + constructor(asyncMethods) { + super(); + /** + * @private {!Object>} The actual calls made with + * the async method enum keys as keys here too. + */ + this.actualCalls_ = {}; + /** + * @protected {!Object} The async + * methods enums. + */ + this.asyncMethods = asyncMethods || {}; + } + /** - * @private {!Object} The async methods - * enums. + * Installs the fake client hooks. + * @return {!MockHelper} The mock helper instance itself. */ - this.asyncMethods_ = asyncMethods; -}; -goog.inherits(MockHelper, Disposable); - - -/** - * Information related to AsyncMethod. This includes the context on which it is - * called and the public API name. - * @typedef {{ - * context: !Object, - * name: string - * }} - */ -MockHelper.AsyncMethodInfo; - - -/** - * Installs the fake client hooks. - * @return {!MockHelper} The mock helper instance itself. - */ -MockHelper.prototype.install = function() { - this.init_(); - return this; -}; + install() { + this.init_(); + return this; + } -/** - * Removes the fake client hooks. - * @return {!GoogPromise} - */ -MockHelper.prototype.uninstall = function() { - var self = this; - return this.process().then(function() { - var unused = []; - // Check for any unexpected API requests and fail if any detected. - for (var methodName in self.actualCalls_) { - if (self.actualCalls_[methodName].length) { - unused.push(methodName); + /** + * Removes the fake client hooks. + * @return {!GoogPromise} + */ + uninstall() { + var self = this; + return this.process().then(function() { + var unused = []; + // Check for any unexpected API requests and fail if any detected. + for (var methodName in self.actualCalls_) { + if (self.actualCalls_[methodName].length) { + unused.push(methodName); + } } - } - if (unused.length) { - throw new Error('unexpected API request(s): ' + unused.join(', ')); - } - // Reset actual and expected calls. - self.actualCalls_ = {}; - self.expectedCalls_ = []; - }); -}; + if (unused.length) { + throw new Error('unexpected API request(s): ' + unused.join(', ')); + } + // Reset actual and expected calls. + self.actualCalls_ = {}; + self.expectedCalls_ = []; + }); + } -/** @override */ -MockHelper.prototype.disposeInternal = function() { - this.uninstall(); - MockHelper.base(this, 'disposeInternal'); -}; + /** @override */ + disposeInternal() { + this.uninstall(); + super.disposeInternal(); + } -/** - * Initializes the API listeners and creates all the simulation methods and - * their recorders. In addition, creates all the methods to assert and simulate - * their responses. - * @private - */ -MockHelper.prototype.init_ = function() { - var self = this; /** - * @private {!Array} The array of expected calls made. + * Initializes the API listeners and creates all the simulation methods and + * their recorders. In addition, creates all the methods to assert and + * simulate their responses. + * @private */ - this.expectedCalls_ = []; - var assertCb = - function(methodName, context, self, data, opt_resp, opt_error) { - var p = new GoogPromise(function(resolve, reject) { - self.expectedCalls_.push({ - 'methodName': methodName, - 'data': data, - 'resp': opt_resp, - 'error': opt_error, - 'resolveAssert': resolve + init_() { + var self = this; + /** + * @private {!Array} The array of expected calls made. + */ + this.expectedCalls_ = []; + var assertCb = function( + methodName, context, self, data, opt_resp, opt_error) { + var p = new GoogPromise(function(resolve, reject) { + self.expectedCalls_.push({ + 'methodName': methodName, + 'data': data, + 'resp': opt_resp, + 'error': opt_error, + 'resolveAssert': resolve + }); + // This API has already been called but not processed yet. Resolve + // returned promise. + if (self.actualCalls_[methodName] && + self.actualCalls_[methodName].length > 0) { + resolve(); + } }); - // This API has already been called but not processed yet. Resolve - // returned promise. - if (self.actualCalls_[methodName] && - self.actualCalls_[methodName].length > 0) { - resolve(); + // Returns a promise that resolves when this API is called. + // This is useful if this API is called asynchronously. + return p; + }; + var cb = function(var_args) { + var methodName = arguments[0]; + var context = arguments[1]; + var self = arguments[2]; + var data = []; + for (var i = 3; i < arguments.length; i++) { + data.push(arguments[i]); } - }); - // Returns a promise that resolves when this API is called. - // This is useful if this API is called asynchronously. - return p; - }; - var cb = function(var_args) { - var methodName = arguments[0]; - var context = arguments[1]; - var self = arguments[2]; - var data = []; - for (var i = 3; i < arguments.length; i++) { - data.push(arguments[i]); - } - // Resolve assert returned promise on the first matching API call. - for (var i = 0; i < self.expectedCalls_.length; i++) { - if (self.expectedCalls_[i]['methodName'] === methodName) { - self.expectedCalls_[i]['resolveAssert'](); - break; + // Resolve assert returned promise on the first matching API call. + for (var i = 0; i < self.expectedCalls_.length; i++) { + if (self.expectedCalls_[i]['methodName'] === methodName) { + self.expectedCalls_[i]['resolveAssert'](); + break; + } } - } - var p = new GoogPromise(function(resolve, reject) { - self.actualCalls_[methodName].push({ - 'data': data, - 'resolve': resolve, - 'reject': reject, - 'context': context + var p = new GoogPromise(function(resolve, reject) { + self.actualCalls_[methodName].push({ + 'data': data, + 'resolve': resolve, + 'reject': reject, + 'context': context + }); + }).thenCatch(function(e) { + // If cancelled, remove from actual calls. + if (e && e.name == 'cancel') { + // Do not count this anymore. + self.actualCalls_[methodName].shift(); + } + throw e; }); - }).thenCatch(function(e) { - // If cancelled, remove from actual calls. - if (e && e.name == 'cancel') { - // Do not count this anymore. - self.actualCalls_[methodName].shift(); - } - throw e; - }); - var index = self.actualCalls_[methodName].length - 1; - self.actualCalls_[methodName][index]['promise'] = p; - return p; - }; - for (var key in this.asyncMethods_) { - var methodName = this.asyncMethods_[key]['name']; - var context = this.asyncMethods_[key]['context']; - this.actualCalls_[methodName] = []; - context[methodName] = goog.partial(cb, methodName, context, self); - var assertMethodName = - 'assert' + MockHelper.capitalize_(methodName); - context[assertMethodName] = goog.partial( - assertCb, methodName, context, self); + var index = self.actualCalls_[methodName].length - 1; + self.actualCalls_[methodName][index]['promise'] = p; + return p; + }; + for (var key in this.asyncMethods) { + var methodName = this.asyncMethods[key]['name']; + var context = this.asyncMethods[key]['context']; + this.actualCalls_[methodName] = []; + context[methodName] = goog.partial(cb, methodName, context, self); + var assertMethodName = 'assert' + MockHelper.capitalize_(methodName); + context[assertMethodName] = + goog.partial(assertCb, methodName, context, self); + } } -}; -/** - * Processes all pending asserts. - * @return {!GoogPromise} - */ -MockHelper.prototype.process = function() { - var self = this; - return GoogPromise.resolve().then(function() { - if (self.expectedCalls_.length) { - var assertReq = self.expectedCalls_.shift(); - var methodName = assertReq['methodName']; - var data = assertReq['data']; - var resp = assertReq['resp']; - var error = assertReq['error']; - var resolveAssert = assertReq['resolveAssert']; - var req = self.actualCalls_[methodName].shift(); - if (!req) { - // Fail quickly when expected API request is not called. - throw new Error('missing API request: ' + methodName); - } - assertArrayEquals(data, req['data']); - var continueProcess = function() { - if (self.expectedCalls_.length) { - return self.process(); - } - }; - // Resolve API promise if not already resolved. - resolveAssert(); - if (error) { - if (goog.isFunction(error)) { - req['reject'](error()); - } else { - req['reject'](error); + /** + * Processes all pending asserts. + * @return {!GoogPromise} + */ + process() { + var self = this; + return GoogPromise.resolve().then(function() { + if (self.expectedCalls_.length) { + var assertReq = self.expectedCalls_.shift(); + var methodName = assertReq['methodName']; + var data = assertReq['data']; + var resp = assertReq['resp']; + var error = assertReq['error']; + var resolveAssert = assertReq['resolveAssert']; + var req = self.actualCalls_[methodName].shift(); + if (!req) { + // Fail quickly when expected API request is not called. + throw new Error('missing API request: ' + methodName); } - } else { - if (goog.isFunction(resp)) { - req['resolve'](resp()); + assertArrayEquals(data, req['data']); + var continueProcess = function() { + if (self.expectedCalls_.length) { + return self.process(); + } + }; + // Resolve API promise if not already resolved. + resolveAssert(); + if (error) { + if (goog.isFunction(error)) { + req['reject'](error()); + } else { + req['reject'](error); + } } else { - req['resolve'](resp); + if (goog.isFunction(resp)) { + req['resolve'](resp()); + } else { + req['resolve'](resp); + } } + return req['promise'].then(continueProcess, continueProcess); } - return req['promise'].then(continueProcess, continueProcess); - } - }); -}; + }); + } + /** + * @param {string} str String value whose first character is to be + * capitalized. + * @return {string} String value with first letter in uppercase. + * @private + */ + static capitalize_(str) { + return str.charAt(0).toUpperCase() + str.substr(1); + } +} + /** - * @param {string} str String value whose first character is to be capitalized. - * @return {string} String value with first letter in uppercase. - * @private + * Information related to AsyncMethod. This includes the context on which it is + * called and the public API name. + * @typedef {{ + * context: !Object, + * name: string + * }} */ -MockHelper.capitalize_ = function(str) { - return str.charAt(0).toUpperCase() + str.substr(1); -}; +MockHelper.AsyncMethodInfo; exports = MockHelper; diff --git a/javascript/testing/recaptchaverifier.js b/javascript/testing/recaptchaverifier.js index 712065c3..8ffb6bbd 100644 --- a/javascript/testing/recaptchaverifier.js +++ b/javascript/testing/recaptchaverifier.js @@ -25,7 +25,6 @@ var MockHelper = goog.require('firebaseui.auth.testing.MockHelper'); var object = goog.require('goog.object'); - /** * Creates the fake firebase reCAPTCHA app verifier for the Firebase app * provided. @@ -39,92 +38,89 @@ var object = goog.require('goog.object'); * recaptchaVerifierInstance.install(); * return recaptchaVerifierInstance; * }; - * - * @param {!Element|string} container The reCAPTCHA container parameter. This - * has different meaning depending on whether the reCAPTCHA is hidden or - * visible. - * @param {?Object=} opt_parameters The optional reCAPTCHA parameters. - * @param {?firebaseui.auth.testing.FakeAppClient=} opt_app The app instance - * associated with the fake Auth client. - * @constructor - * @extends {MockHelper} */ -var RecaptchaVerifier = function(container, opt_parameters, opt_app) { - this['type'] = 'recaptcha'; - /** @private {!Element|string} The reCAPTCHA container parameter. */ - this.container_ = container; - /** @private {?Object|undefined} The optional reCAPTCHA parameters. */ - this.parameters_ = opt_parameters; +class RecaptchaVerifier extends MockHelper { /** - * @private {?firebaseui.auth.testing.FakeAppClient|undefined} The app - * instance associated with the fake Auth client. + * @param {(!Element|string)} container The reCAPTCHA container parameter. + * This has different meaning depending on whether the reCAPTCHA is hidden + * or visible. + * @param {?Object=} opt_parameters The optional reCAPTCHA parameters. + * @param {?firebaseui.auth.testing.FakeAppClient=} opt_app The app instance + * associated with the fake Auth client. */ - this.app_ = opt_app; - /** @private {boolean} Whether the reCAPTCHA is cleared. */ - this.cleared_ = false; - var asyncMethods = {}; - for (var key in RecaptchaVerifier.AsyncMethod) { - asyncMethods[key] = { - 'context': this, - 'name': RecaptchaVerifier.AsyncMethod[key] - }; + constructor(container, opt_parameters, opt_app) { + super(); + this['type'] = 'recaptcha'; + /** @private {!Element|string} The reCAPTCHA container parameter. */ + this.container_ = container; + /** @private {?Object|undefined} The optional reCAPTCHA parameters. */ + this.parameters_ = opt_parameters; + /** + * @private {?firebaseui.auth.testing.FakeAppClient|undefined} The app + * instance associated with the fake Auth client. + */ + this.app_ = opt_app; + /** @private {boolean} Whether the reCAPTCHA is cleared. */ + this.cleared_ = false; + var asyncMethods = {}; + for (var key in RecaptchaVerifier.AsyncMethod) { + asyncMethods[key] = { + 'context': this, + 'name': RecaptchaVerifier.AsyncMethod[key] + }; + } + // Pass async methods enum to base class. + this.asyncMethods = asyncMethods; } - RecaptchaVerifier.base(this, 'constructor', asyncMethods); -}; -goog.inherits(RecaptchaVerifier, MockHelper); - - -/** - * Asserts the reCAPTCHA verifier initialized with the expected parameters. - * @param {!Element|string} container The expected reCAPTCHA container - * parameter. This has different meaning depending on whether the reCAPTCHA - * is hidden or visible. - * @param {?Object=} opt_parameters The expected optional reCAPTCHA parameters. - * @param {?firebaseui.auth.testing.FakeAppClient=} opt_app The expected app - * instance associated with the fake Auth client. - */ -RecaptchaVerifier.prototype.assertInitializedWithParameters = - function(container, opt_parameters, opt_app) { - assertEquals(container, this.container_); - // Confirm everything except for the callbacks. - // It is not useful to assert the callbacks as they are not provided - // externally and are injected in the process of rendering a reCAPTCHA. - var filteredParams = object.clone(this.parameters_); - delete filteredParams['callback']; - delete filteredParams['expired-callback']; - assertObjectEquals(opt_parameters, filteredParams); - assertEquals(opt_app, this.app_); -}; - - -/** - * @return {?Object|undefined} The optional reCAPTCHA parameters. - */ -RecaptchaVerifier.prototype.getParameters = function() { - return this.parameters_; -}; - -/** Clears the reCAPTCHA from the DOM. */ -RecaptchaVerifier.prototype.clear = function() { - this.cleared_ = true; -}; + /** + * Asserts the reCAPTCHA verifier initialized with the expected parameters. + * @param {!Element|string} container The expected reCAPTCHA container + * parameter. This has different meaning depending on whether the + * reCAPTCHA is hidden or visible. + * @param {?Object=} opt_parameters The expected optional reCAPTCHA + * parameters. + * @param {?firebaseui.auth.testing.FakeAppClient=} opt_app The expected app + * instance associated with the fake Auth client. + */ + assertInitializedWithParameters(container, opt_parameters, opt_app) { + assertEquals(container, this.container_); + // Confirm everything except for the callbacks. + // It is not useful to assert the callbacks as they are not provided + // externally and are injected in the process of rendering a reCAPTCHA. + var filteredParams = object.clone(this.parameters_); + delete filteredParams['callback']; + delete filteredParams['expired-callback']; + assertObjectEquals(opt_parameters, filteredParams); + assertEquals(opt_app, this.app_); + } + /** + * @return {?Object|undefined} The optional reCAPTCHA parameters. + */ + getParameters() { + return this.parameters_; + } -/** Asserts reCAPTCHA is cleared. */ -RecaptchaVerifier.prototype.assertClear = function() { - if (!this.cleared_) { - throw new Error('reCAPTCHA verifier not cleared!'); + /** Clears the reCAPTCHA from the DOM. */ + clear() { + this.cleared_ = true; } -}; + /** Asserts reCAPTCHA is cleared. */ + assertClear() { + if (!this.cleared_) { + throw new Error('reCAPTCHA verifier not cleared!'); + } + } -/** Asserts reCAPTCHA is not cleared. */ -RecaptchaVerifier.prototype.assertNotClear = function() { - if (this.cleared_) { - throw new Error('reCAPTCHA verifier cleared!'); + /** Asserts reCAPTCHA is not cleared. */ + assertNotClear() { + if (this.cleared_) { + throw new Error('reCAPTCHA verifier cleared!'); + } } -}; +} /** diff --git a/javascript/testing/util.js b/javascript/testing/util.js index dfa36a4a..1677bbf1 100644 --- a/javascript/testing/util.js +++ b/javascript/testing/util.js @@ -27,174 +27,161 @@ var util = goog.require('firebaseui.auth.util'); /** * Fake utils class. - * @constructor - * @extends {Disposable} */ -var FakeUtil = function() { - FakeUtil.base(this, 'constructor'); - this.hasOpener_ = false; - this.goToUrl_ = null; - this.openerGoToUrl_ = null; - this.closedWindow_ = null; - this.popupUrl_ = null; - this.openUrl_ = null; - this.windowName_ = null; - this.lastHistoryState_ = null; - this.lastHistoryTitle_ = null; - this.lastHistoryUrl_ = null; -}; -goog.inherits(FakeUtil, Disposable); - - -/** - * Installs the fake utils. - * @return {!FakeUtil} The fake util. - */ -FakeUtil.prototype.install = function() { - var r = this.replacer_ = new PropertyReplacer(); - var self = this; - r.set(util, 'goTo', function(url) { - self.goToUrl_ = url; - }); - r.set(util, 'openerGoTo', function(url) { - self.openerGoToUrl_ = url; - }); - r.set(util, 'hasOpener', function() { - return !!self.hasOpener_; - }); - r.set(util, 'close', function(window) { - self.closedWindow_ = window; - }); - r.set(util, 'popup', function(url) { - self.popupUrl_ = url; - }); - r.set(util, 'open', function(url, windowName) { - self.openUrl_ = url; - self.windowName_ = windowName; - }); - r.set(util, 'replaceHistoryState', function(state, title, url) { - self.lastHistoryState_ = state; - self.lastHistoryTitle_ = title; - self.lastHistoryUrl_ = url; - }); - return this; -}; - - -/** Removes the fake utils hooks. */ -FakeUtil.prototype.uninstall = function() { - assertNull('unexpected goTo', this.goToUrl_); - assertNull('unexpected openerGoTo', this.openerGoToUrl_); - assertNull('unexpected window close', this.closedWindow_); - assertNull('unexpected popup window', this.popupUrl_); - assertNull('unexpected replaceHistoryState', this.lastHistoryUrl_); - this.hasOpener_ = null; - this.goToUrl_ = null; - this.openerGoToUrl_ = null; - this.closedWindow_ = null; - this.popupUrl_ = null; - if (this.replacer_) { - this.replacer_.reset(); - this.replacer_ = null; +class FakeUtil extends Disposable { + constructor() { + super(); + this.hasOpener_ = false; + this.goToUrl_ = null; + this.openerGoToUrl_ = null; + this.closedWindow_ = null; + this.popupUrl_ = null; + this.openUrl_ = null; + this.windowName_ = null; + this.lastHistoryState_ = null; + this.lastHistoryTitle_ = null; + this.lastHistoryUrl_ = null; } - this.lastHistoryState_ = null; - this.lastHistoryTitle_ = null; - this.lastHistoryUrl_ = null; -}; - - -/** @override */ -FakeUtil.prototype.disposeInternal = function() { - this.uninstall(); - FakeUtil.base(this, 'disposeInternal'); -}; - - -/** - * @param {boolean} hasOpener Whether the current window has an opener. - */ -FakeUtil.prototype.setHasOpener = function(hasOpener) { - this.hasOpener_ = hasOpener; -}; - - -/** - * Asserts the window location is set to the given URL. - * @param {string} url The current location URL. - */ -FakeUtil.prototype.assertGoTo = function(url) { - assertEquals(url, this.goToUrl_); - this.goToUrl_ = null; -}; + /** + * Installs the fake utils. + * @return {!FakeUtil} The fake util. + */ + install() { + var r = this.replacer_ = new PropertyReplacer(); + var self = this; + r.set(util, 'goTo', function(url) { + self.goToUrl_ = url; + }); + r.set(util, 'openerGoTo', function(url) { + self.openerGoToUrl_ = url; + }); + r.set(util, 'hasOpener', function() { + return !!self.hasOpener_; + }); + r.set(util, 'close', function(window) { + self.closedWindow_ = window; + }); + r.set(util, 'popup', function(url) { + self.popupUrl_ = url; + }); + r.set(util, 'open', function(url, windowName) { + self.openUrl_ = url; + self.windowName_ = windowName; + }); + r.set(util, 'replaceHistoryState', function(state, title, url) { + self.lastHistoryState_ = state; + self.lastHistoryTitle_ = title; + self.lastHistoryUrl_ = url; + }); + return this; + } -/** - * Asserts the opener window location is set to the given URL. - * @param {string} url The current location URL. - */ -FakeUtil.prototype.assertOpenerGoTo = function(url) { - assertEquals(url, this.openerGoToUrl_); - this.openerGoToUrl_ = null; -}; + /** Removes the fake utils hooks. */ + uninstall() { + assertNull('unexpected goTo', this.goToUrl_); + assertNull('unexpected openerGoTo', this.openerGoToUrl_); + assertNull('unexpected window close', this.closedWindow_); + assertNull('unexpected popup window', this.popupUrl_); + assertNull('unexpected replaceHistoryState', this.lastHistoryUrl_); + this.hasOpener_ = null; + this.goToUrl_ = null; + this.openerGoToUrl_ = null; + this.closedWindow_ = null; + this.popupUrl_ = null; + if (this.replacer_) { + this.replacer_.reset(); + this.replacer_ = null; + } + this.lastHistoryState_ = null; + this.lastHistoryTitle_ = null; + this.lastHistoryUrl_ = null; + } + /** @override */ + disposeInternal() { + this.uninstall(); + super.disposeInternal(); + } -/** - * Asserts the given URL is loaded in the given window. - * @param {string} url The URL to be loaded. - * @param {string} windowName The window name to be loaded into. - */ -FakeUtil.prototype.assertOpen = function(url, windowName) { - assertEquals(url, this.openUrl_); - assertEquals(windowName, this.windowName_); - this.openUrl_ = null; - this.windowName_ = null; -}; + /** + * @param {boolean} hasOpener Whether the current window has an opener. + */ + setHasOpener(hasOpener) { + this.hasOpener_ = hasOpener; + } + /** + * Asserts the window location is set to the given URL. + * @param {string} url The current location URL. + */ + assertGoTo(url) { + assertEquals(url, this.goToUrl_); + this.goToUrl_ = null; + } -/** - * Asserts a window is closed. - * @param {!Window} window The closed window. - */ -FakeUtil.prototype.assertWindowClosed = - function(window) { - assertEquals(window, this.closedWindow_); - this.closedWindow_ = null; -}; + /** + * Asserts the opener window location is set to the given URL. + * @param {string} url The current location URL. + */ + assertOpenerGoTo(url) { + assertEquals(url, this.openerGoToUrl_); + this.openerGoToUrl_ = null; + } + /** + * Asserts the given URL is loaded in the given window. + * @param {string} url The URL to be loaded. + * @param {string} windowName The window name to be loaded into. + */ + assertOpen(url, windowName) { + assertEquals(url, this.openUrl_); + assertEquals(windowName, this.windowName_); + this.openUrl_ = null; + this.windowName_ = null; + } -/** - * Asserts a window is not closed. - * @param {!Window} window The still open window. - */ -FakeUtil.prototype.assertWindowNotClosed = - function(window) { - assertNotEquals(window, this.closedWindow_); -}; + /** + * Asserts a window is closed. + * @param {!Window} window The closed window. + */ + assertWindowClosed(window) { + assertEquals(window, this.closedWindow_); + this.closedWindow_ = null; + } + /** + * Asserts a window is not closed. + * @param {!Window} window The still open window. + */ + assertWindowNotClosed(window) { + assertNotEquals(window, this.closedWindow_); + } -/** - * Asserts a popup window is opened, and the location is set to the URL. - * @param {string} url The URL of the popup window. - */ -FakeUtil.prototype.assertPopupWindow = function(url) { - assertEquals(url, this.popupUrl_); - this.popupUrl_ = null; -}; + /** + * Asserts a popup window is opened, and the location is set to the URL. + * @param {string} url The URL of the popup window. + */ + assertPopupWindow(url) { + assertEquals(url, this.popupUrl_); + this.popupUrl_ = null; + } + /** + * Asserts replaceHistoryState is called with provided history state. + * @param {!Object} state The last history state to assert. + * @param {string} title The associated document title. + * @param {string} url The associated URL. + */ + assertReplaceHistoryState(state, title, url) { + assertObjectEquals(state, this.lastHistoryState_); + assertEquals(title, this.lastHistoryTitle_); + assertEquals(url, this.lastHistoryUrl_); + this.lastHistoryState_ = null; + this.lastHistoryTitle_ = null; + this.lastHistoryUrl_ = null; + } +} -/** - * Asserts replaceHistoryState is called with provided history state. - * @param {!Object} state The last history state to assert. - * @param {string} title The associated document title. - * @param {string} url The associated URL. - */ -FakeUtil.prototype.assertReplaceHistoryState = function(state, title, url) { - assertObjectEquals(state, this.lastHistoryState_); - assertEquals(title, this.lastHistoryTitle_); - assertEquals(url, this.lastHistoryUrl_); - this.lastHistoryState_ = null; - this.lastHistoryTitle_ = null; - this.lastHistoryUrl_ = null; -}; exports = FakeUtil; diff --git a/javascript/ui/element/email.js b/javascript/ui/element/email.js index 141a6be9..814dfd48 100644 --- a/javascript/ui/element/email.js +++ b/javascript/ui/element/email.js @@ -20,8 +20,9 @@ goog.provide('firebaseui.auth.ui.element.email'); goog.require('firebaseui.auth.soy2.strings'); goog.require('firebaseui.auth.ui.element'); -goog.require('goog.dom'); +goog.require('goog.asserts'); goog.require('goog.format.EmailAddress'); +goog.require('goog.string'); goog.require('goog.ui.Component'); diff --git a/javascript/ui/element/emailtesthelper.js b/javascript/ui/element/emailtesthelper.js index a4745d20..9b2e357d 100644 --- a/javascript/ui/element/emailtesthelper.js +++ b/javascript/ui/element/emailtesthelper.js @@ -24,14 +24,12 @@ goog.require('firebaseui.auth.ui.element.ElementTestHelper'); goog.require('goog.dom.forms'); goog.require('goog.events.KeyCodes'); goog.require('goog.testing.events'); -goog.require('goog.ui.Component'); goog.scope(function() { var element = firebaseui.auth.ui.element; - /** @constructor */ element.EmailTestHelper = function() { element.EmailTestHelper.base(this, 'constructor', 'Email'); diff --git a/javascript/ui/page/anonymoususermismatch.js b/javascript/ui/page/anonymoususermismatch.js index 7877464a..8e523da3 100644 --- a/javascript/ui/page/anonymoususermismatch.js +++ b/javascript/ui/page/anonymoususermismatch.js @@ -24,54 +24,41 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Anonymous user mismatch error UI component. - * @param {function()} onDismissClick Callback to invoke when dismiss button - * is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.AnonymousUserMismatch = function( - onDismissClick, - opt_domHelper) { - firebaseui.auth.ui.page.AnonymousUserMismatch.base( - this, - 'constructor', - firebaseui.auth.soy2.page.anonymousUserMismatch, - undefined, - opt_domHelper, - 'anonymousUserMismatch'); - this.onDismissClick_ = onDismissClick; -}; -goog.inherits( - firebaseui.auth.ui.page.AnonymousUserMismatch, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.AnonymousUserMismatch.prototype.enterDocument = - function() { - var self = this; - // Handle action event for dismiss button. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getSecondaryLinkElement(), function(e) { - self.onDismissClick_(); - }); - // Set initial focus on the dismiss button. - this.getSecondaryLinkElement().focus(); - firebaseui.auth.ui.page.AnonymousUserMismatch.base( - this, 'enterDocument'); -}; +firebaseui.auth.ui.page.AnonymousUserMismatch = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onDismissClick Callback to invoke when dismiss button + * is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(onDismissClick, opt_domHelper) { + super( + firebaseui.auth.soy2.page.anonymousUserMismatch, undefined, + opt_domHelper, 'anonymousUserMismatch'); + this.onDismissClick_ = onDismissClick; + } + /** @override */ + enterDocument() { + var self = this; + // Handle action event for dismiss button. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getSecondaryLinkElement(), function(e) { + self.onDismissClick_(); + }); + // Set initial focus on the dismiss button. + this.getSecondaryLinkElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.AnonymousUserMismatch.prototype.disposeInternal = - function() { - this.onDismissClick_ = null; - firebaseui.auth.ui.page.AnonymousUserMismatch.base( - this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onDismissClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/base.js b/javascript/ui/page/base.js index 22bc6342..deff396b 100644 --- a/javascript/ui/page/base.js +++ b/javascript/ui/page/base.js @@ -38,7 +38,8 @@ goog.require('goog.ui.Component'); /** * @define {string} The base URL of images. */ -goog.define('firebaseui.auth.ui.page.IMAGE_BASE', +firebaseui.auth.ui.page.IMAGE_BASE = goog.define( + 'firebaseui.auth.ui.page.IMAGE_BASE', 'https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/'); @@ -65,6 +66,8 @@ firebaseui.auth.ui.page.DEFAULT_ICON_URLS_ = { 'microsoft.com': firebaseui.auth.ui.page.IMAGE_BASE + 'microsoft.svg', 'yahoo.com': firebaseui.auth.ui.page.IMAGE_BASE + 'yahoo.svg', 'apple.com': firebaseui.auth.ui.page.IMAGE_BASE + 'apple.png', + 'saml': firebaseui.auth.ui.page.IMAGE_BASE + 'saml.svg', + 'oidc': firebaseui.auth.ui.page.IMAGE_BASE + 'oidc.svg', }; @@ -84,6 +87,8 @@ firebaseui.auth.ui.page.DEFAULT_BUTTON_COLORS_ = { 'microsoft.com': '#2F2F2F', 'yahoo.com': '#720E9E', 'apple.com': '#000000', + 'saml': '#007bff', + 'oidc': '#007bff', }; diff --git a/javascript/ui/page/blank.js b/javascript/ui/page/blank.js index b6333b1f..29acc575 100644 --- a/javascript/ui/page/blank.js +++ b/javascript/ui/page/blank.js @@ -22,20 +22,14 @@ goog.require('firebaseui.auth.soy2.page'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Blank page UI componenet. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.Blank = function(opt_domHelper) { - firebaseui.auth.ui.page.Blank.base( - this, - 'constructor', - firebaseui.auth.soy2.page.blank, - undefined, - opt_domHelper, - 'blank'); +firebaseui.auth.ui.page.Blank = class extends firebaseui.auth.ui.page.Base { + /** + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(opt_domHelper) { + super(firebaseui.auth.soy2.page.blank, undefined, opt_domHelper, 'blank'); + } }; -goog.inherits(firebaseui.auth.ui.page.Blank, firebaseui.auth.ui.page.Base); diff --git a/javascript/ui/page/callback.js b/javascript/ui/page/callback.js index ba4bab68..152d806f 100644 --- a/javascript/ui/page/callback.js +++ b/javascript/ui/page/callback.js @@ -22,37 +22,30 @@ goog.require('firebaseui.auth.soy2.page'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Callback page UI componenet. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} - */ -firebaseui.auth.ui.page.Callback = function(opt_domHelper) { - firebaseui.auth.ui.page.Callback.base( - this, - 'constructor', - firebaseui.auth.soy2.page.callback, - undefined, - opt_domHelper, - 'callback'); -}; -goog.inherits(firebaseui.auth.ui.page.Callback, firebaseui.auth.ui.page.Base); - - -/** - * Executes an API promise based request. This page already has a progress bar, - * no need to show another progress bar when executing a promise. - * @param {function(...):!goog.Promise} executor The request executor. - * @param {!Array} parameters The API request array of parameters. - * @param {function(*)} onSuccess The response handling success callback. - * @param {function(*)} onError The response handling error callback. - * @return {?goog.Promise} The pending promise. - * @override */ -firebaseui.auth.ui.page.Callback.prototype.executePromiseRequest = - function(executor, parameters, onSuccess, onError) { - return executor.apply(null, parameters) - .then(onSuccess, onError); +firebaseui.auth.ui.page.Callback = class extends firebaseui.auth.ui.page.Base { + /** + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(opt_domHelper) { + super( + firebaseui.auth.soy2.page.callback, undefined, opt_domHelper, + 'callback'); + } + + /** + * Executes an API promise based request. This page already has a progress + * bar, no need to show another progress bar when executing a promise. + * @param {function(...):!goog.Promise} executor The request executor. + * @param {!Array} parameters The API request array of parameters. + * @param {function(*)} onSuccess The response handling success callback. + * @param {function(*)} onError The response handling error callback. + * @return {?goog.Promise} The pending promise. + * @override + */ + executePromiseRequest(executor, parameters, onSuccess, onError) { + return executor.apply(null, parameters).then(onSuccess, onError); + } }; diff --git a/javascript/ui/page/differentdeviceerror.js b/javascript/ui/page/differentdeviceerror.js index 5d64e896..fc67b410 100644 --- a/javascript/ui/page/differentdeviceerror.js +++ b/javascript/ui/page/differentdeviceerror.js @@ -24,54 +24,41 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Different device error UI component. - * @param {function()} onDismissClick Callback to invoke when dismiss button - * is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.DifferentDeviceError = function( - onDismissClick, - opt_domHelper) { - firebaseui.auth.ui.page.DifferentDeviceError.base( - this, - 'constructor', - firebaseui.auth.soy2.page.differentDeviceError, - undefined, - opt_domHelper, - 'differentDeviceError'); - this.onDismissClick_ = onDismissClick; -}; -goog.inherits( - firebaseui.auth.ui.page.DifferentDeviceError, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.DifferentDeviceError.prototype.enterDocument = - function() { - var self = this; - // Handle action event for dismiss button. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getSecondaryLinkElement(), function(e) { - self.onDismissClick_(); - }); - // Set initial focus on the dismiss button. - this.getSecondaryLinkElement().focus(); - firebaseui.auth.ui.page.DifferentDeviceError.base( - this, 'enterDocument'); -}; +firebaseui.auth.ui.page.DifferentDeviceError = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onDismissClick Callback to invoke when dismiss button + * is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(onDismissClick, opt_domHelper) { + super( + firebaseui.auth.soy2.page.differentDeviceError, undefined, + opt_domHelper, 'differentDeviceError'); + this.onDismissClick_ = onDismissClick; + } + /** @override */ + enterDocument() { + var self = this; + // Handle action event for dismiss button. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getSecondaryLinkElement(), function(e) { + self.onDismissClick_(); + }); + // Set initial focus on the dismiss button. + this.getSecondaryLinkElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.DifferentDeviceError.prototype.disposeInternal = - function() { - this.onDismissClick_ = null; - firebaseui.auth.ui.page.DifferentDeviceError.base( - this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onDismissClick_ = null; + super.disposeInternal(); + } }; @@ -85,4 +72,3 @@ goog.mixin( getSecondaryLinkElement: firebaseui.auth.ui.element.form.getSecondaryLinkElement }); - diff --git a/javascript/ui/page/emailchangerevoke.js b/javascript/ui/page/emailchangerevoke.js index 406b6150..b16a0c3f 100644 --- a/javascript/ui/page/emailchangerevoke.js +++ b/javascript/ui/page/emailchangerevoke.js @@ -24,73 +24,56 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Email change revocation UI component. - * @param {string} email The original email to revert back to. - * @param {function()} onResetPasswordClick Callback to invoke when the reset - * password link is clicked. - * @param {function()=} opt_onContinueClick Callback to invoke when the continue - * button is clicked. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailChangeRevoke = function( - email, - onResetPasswordClick, - opt_onContinueClick, - opt_domHelper) { - firebaseui.auth.ui.page.EmailChangeRevoke.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailChangeRevokeSuccess, - { - email: email, - allowContinue: !!opt_onContinueClick - }, - opt_domHelper, - 'emailChangeRevoke'); - this.onResetPasswordClick_ = onResetPasswordClick; - this.onContinueClick_ = opt_onContinueClick || null; -}; -goog.inherits(firebaseui.auth.ui.page.EmailChangeRevoke, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.EmailChangeRevoke.prototype.enterDocument = function() { - var self = this; - // Handle action event for 'change your password immediately' link. - firebaseui.auth.ui.element.listenForActionEvent( - this, - this.getResetPasswordElement(), - function(e) { - self.onResetPasswordClick_(); - }); - if (this.onContinueClick_) { - this.initFormElement(this.onContinueClick_); - this.getSubmitElement().focus(); +firebaseui.auth.ui.page.EmailChangeRevoke = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The original email to revert back to. + * @param {function()} onResetPasswordClick Callback to invoke when the reset + * password link is clicked. + * @param {function()=} opt_onContinueClick Callback to invoke when the + * continue button is clicked. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(email, onResetPasswordClick, opt_onContinueClick, opt_domHelper) { + super( + firebaseui.auth.soy2.page.emailChangeRevokeSuccess, + {email: email, allowContinue: !!opt_onContinueClick}, opt_domHelper, + 'emailChangeRevoke'); + this.onResetPasswordClick_ = onResetPasswordClick; + this.onContinueClick_ = opt_onContinueClick || null; } - firebaseui.auth.ui.page.EmailChangeRevoke.base(this, 'enterDocument'); -}; - -/** @override */ -firebaseui.auth.ui.page.EmailChangeRevoke.prototype.disposeInternal = - function() { - this.onContinueClick_ = null; - this.onResetPasswordClick_ = null; - firebaseui.auth.ui.page.EmailChangeRevoke.base(this, 'disposeInternal'); -}; + /** @override */ + enterDocument() { + var self = this; + // Handle action event for 'change your password immediately' link. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getResetPasswordElement(), function(e) { + self.onResetPasswordClick_(); + }); + if (this.onContinueClick_) { + this.initFormElement(this.onContinueClick_); + this.getSubmitElement().focus(); + } + super.enterDocument(); + } + /** @override */ + disposeInternal() { + this.onContinueClick_ = null; + this.onResetPasswordClick_ = null; + super.disposeInternal(); + } -/** - * @return {Element} The reset password link. - */ -firebaseui.auth.ui.page.EmailChangeRevoke.prototype.getResetPasswordElement = - function() { - return this.getElementByClass('firebaseui-id-reset-password-link'); + /** + * @return {Element} The reset password link. + */ + getResetPasswordElement() { + return this.getElementByClass('firebaseui-id-reset-password-link'); + } }; diff --git a/javascript/ui/page/emaillinksigninconfirmation.js b/javascript/ui/page/emaillinksigninconfirmation.js index 693e54ad..5a3ee983 100644 --- a/javascript/ui/page/emaillinksigninconfirmation.js +++ b/javascript/ui/page/emaillinksigninconfirmation.js @@ -26,83 +26,63 @@ goog.require('firebaseui.auth.ui.page.Base'); goog.require('goog.dom.selection'); - /** * Email link sign in confirmation UI component. - * @param {function()} onEmailEnter Callback to invoke when enter key (or its - * equivalent) is detected. - * @param {function()} onCancelClick Callback to invoke when cancel button - * is clicked. - * @param {string=} opt_email The email to prefill. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailLinkSignInConfirmation = function( - onEmailEnter, - onCancelClick, - opt_email, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.EmailLinkSignInConfirmation.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailLinkSignInConfirmation, - { - email: opt_email - }, - opt_domHelper, - 'emailLinkSignInConfirmation', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onEmailEnter_ = onEmailEnter; - this.onCancelClick_ = onCancelClick; -}; -goog.inherits( - firebaseui.auth.ui.page.EmailLinkSignInConfirmation, - firebaseui.auth.ui.page.Base); +firebaseui.auth.ui.page.EmailLinkSignInConfirmation = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onEmailEnter Callback to invoke when enter key (or its + * equivalent) is detected. + * @param {function()} onCancelClick Callback to invoke when cancel button + * is clicked. + * @param {string=} opt_email The email to prefill. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + onEmailEnter, onCancelClick, opt_email, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + super( + firebaseui.auth.soy2.page.emailLinkSignInConfirmation, + {email: opt_email}, opt_domHelper, 'emailLinkSignInConfirmation', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onEmailEnter_ = onEmailEnter; + this.onCancelClick_ = onCancelClick; + } + /** @override */ + enterDocument() { + // Handle on enter event for email element. + this.initEmailElement(this.onEmailEnter_); + // Handle a click on the submit button or cancel button. + this.initFormElement(this.onEmailEnter_, this.onCancelClick_); + this.setupFocus_(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInConfirmation.prototype.enterDocument = - function() { - // Handle on enter event for email element. - this.initEmailElement(this.onEmailEnter_); - // Handle a click on the submit button or cancel button. - this.initFormElement(this.onEmailEnter_, this.onCancelClick_); - this.setupFocus_(); - firebaseui.auth.ui.page.EmailLinkSignInConfirmation.base( - this, 'enterDocument'); -}; + /** @override */ + disposeInternal() { + this.onEmailEnter_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } - -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInConfirmation.prototype.disposeInternal = - function() { - this.onEmailEnter_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.EmailLinkSignInConfirmation.base( - this, 'disposeInternal'); -}; - - -/** - * Sets up the focus order and auto focus. - * @private - */ -firebaseui.auth.ui.page.EmailLinkSignInConfirmation.prototype.setupFocus_ = - function() { - // Auto focus the email input and put the cursor at the end. - this.getEmailElement().focus(); - goog.dom.selection.setCursorPosition( - this.getEmailElement(), (this.getEmailElement().value || '').length); + /** + * Sets up the focus order and auto focus. + * @private + */ + setupFocus_() { + // Auto focus the email input and put the cursor at the end. + this.getEmailElement().focus(); + goog.dom.selection.setCursorPosition( + this.getEmailElement(), (this.getEmailElement().value || '').length); + } }; diff --git a/javascript/ui/page/emaillinksigninlinking.js b/javascript/ui/page/emaillinksigninlinking.js index affc354f..7d3bec4b 100644 --- a/javascript/ui/page/emaillinksigninlinking.js +++ b/javascript/ui/page/emaillinksigninlinking.js @@ -25,61 +25,48 @@ goog.require('firebaseui.auth.ui.page.Base'); /** * Email link sign in linking UI component. - * @param {string} email The user's email. - * @param {?Object} providerConfig The provider config of the IdP we should use - * for sign in. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailLinkSignInLinking = function( - email, - providerConfig, - onSubmitClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - // Extend base page class and render email link sign in linking soy template. - firebaseui.auth.ui.page.EmailLinkSignInLinking.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailLinkSignInLinking, - { - email: email, - providerConfig: providerConfig - }, - opt_domHelper, - 'emailLinkSignInLinking', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; -}; -goog.inherits(firebaseui.auth.ui.page.EmailLinkSignInLinking, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInLinking.prototype.enterDocument = - function() { - this.initFormElement(this.onSubmitClick_); - this.getSubmitElement().focus(); - firebaseui.auth.ui.page.EmailLinkSignInLinking.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.EmailLinkSignInLinking = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The user's email. + * @param {?Object} providerConfig The provider config of the IdP we should + * use for sign in. + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + email, providerConfig, onSubmitClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + // Extend base page class and render email link sign in linking soy + // template. + super( + firebaseui.auth.soy2.page.emailLinkSignInLinking, + {email: email, providerConfig: providerConfig}, opt_domHelper, + 'emailLinkSignInLinking', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + } + /** @override */ + enterDocument() { + this.initFormElement(this.onSubmitClick_); + this.getSubmitElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInLinking.prototype.disposeInternal = - function() { - this.onSubmitClick_ = null; - firebaseui.auth.ui.page.EmailLinkSignInLinking.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js b/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js index 3eb2b1cb..49dfb0a0 100644 --- a/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js +++ b/javascript/ui/page/emaillinksigninlinkingdifferentdevice.js @@ -26,61 +26,47 @@ goog.require('firebaseui.auth.ui.page.Base'); /** * Email link sign in linking different device UI component. - * @param {?Object} providerConfig The provider config of the IdP we should use - * for sign in. - * @param {function()} onContinueClick Callback to invoke when the continue - * button is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice = function( - providerConfig, - onContinueClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - // Extend base page class and render Email link sign in linking different - // device soy template. - firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailLinkSignInLinkingDifferentDevice, - { - providerConfig: providerConfig - }, - opt_domHelper, - 'emailLinkSignInLinkingDifferentDevice', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onContinueClick_ = onContinueClick; -}; -goog.inherits(firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice.prototype - .enterDocument = function() { - this.initFormElement(this.onContinueClick_); - this.getSubmitElement().focus(); - firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice.base( - this, 'enterDocument'); -}; +firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {?Object} providerConfig The provider config of the IdP we should + * use for sign in. + * @param {function()} onContinueClick Callback to invoke when the continue + * button is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + providerConfig, onContinueClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + // Extend base page class and render Email link sign in linking different + // device soy template. + super( + firebaseui.auth.soy2.page.emailLinkSignInLinkingDifferentDevice, + {providerConfig: providerConfig}, opt_domHelper, + 'emailLinkSignInLinkingDifferentDevice', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onContinueClick_ = onContinueClick; + } + /** @override */ + enterDocument() { + this.initFormElement(this.onContinueClick_); + this.getSubmitElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice.prototype - .disposeInternal = function() { - this.onContinueClick_ = null; - firebaseui.auth.ui.page.EmailLinkSignInLinkingDifferentDevice.base( - this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onContinueClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/emaillinksigninsent.js b/javascript/ui/page/emaillinksigninsent.js index cbfe692e..365ff885 100644 --- a/javascript/ui/page/emaillinksigninsent.js +++ b/javascript/ui/page/emaillinksigninsent.js @@ -24,85 +24,68 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Email link sign in sent UI component. - * @param {string} email The email formerly used to sign in. - * @param {function()} onTroubleGetingEmailLinkClick Callback to invoke when - * the trouble getting email link is clicked. - * @param {function()} onCancelClick Callback to invoke when the back button is - * clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailLinkSignInSent = function( - email, - onTroubleGetingEmailLinkClick, - onCancelClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - // Extend base page class and render email link sign in sent soy template. - firebaseui.auth.ui.page.EmailLinkSignInSent.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailLinkSignInSent, - { - email: email - }, - opt_domHelper, - 'emailLinkSignInSent', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onTroubleGetingEmailLinkClick_ = onTroubleGetingEmailLinkClick; - this.onCancelClick_ = onCancelClick; -}; -goog.inherits(firebaseui.auth.ui.page.EmailLinkSignInSent, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInSent.prototype.enterDocument = - function() { - var self = this; - // Handle action event for cancel button. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getSecondaryLinkElement(), function(e) { - self.onCancelClick_(); - }); - // Handle action event for trouble getting email link. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getTroubleGettingEmailLink(), function(e) { - self.onTroubleGetingEmailLinkClick_(); - }); - // Set initial focus on the cancel button. - this.getSecondaryLinkElement().focus(); - firebaseui.auth.ui.page.EmailLinkSignInSent.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.EmailLinkSignInSent = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The email formerly used to sign in. + * @param {function()} onTroubleGetingEmailLinkClick Callback to invoke when + * the trouble getting email link is clicked. + * @param {function()} onCancelClick Callback to invoke when the back button + * is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + email, onTroubleGetingEmailLinkClick, onCancelClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + // Extend base page class and render email link sign in sent soy template. + super( + firebaseui.auth.soy2.page.emailLinkSignInSent, {email: email}, + opt_domHelper, 'emailLinkSignInSent', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onTroubleGetingEmailLinkClick_ = onTroubleGetingEmailLinkClick; + this.onCancelClick_ = onCancelClick; + } + /** @override */ + enterDocument() { + var self = this; + // Handle action event for cancel button. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getSecondaryLinkElement(), function(e) { + self.onCancelClick_(); + }); + // Handle action event for trouble getting email link. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getTroubleGettingEmailLink(), function(e) { + self.onTroubleGetingEmailLinkClick_(); + }); + // Set initial focus on the cancel button. + this.getSecondaryLinkElement().focus(); + super.enterDocument(); + } -/** - * @return {?Element} The trouble getting email link. - */ -firebaseui.auth.ui.page.EmailLinkSignInSent.prototype - .getTroubleGettingEmailLink = function() { - return this.getElementByClass('firebaseui-id-trouble-getting-email-link'); -}; - + /** + * @return {?Element} The trouble getting email link. + */ + getTroubleGettingEmailLink() { + return this.getElementByClass('firebaseui-id-trouble-getting-email-link'); + } -/** @override */ -firebaseui.auth.ui.page.EmailLinkSignInSent.prototype.disposeInternal = - function() { - this.onTroubleGetingEmailLinkClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.EmailLinkSignInSent.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onTroubleGetingEmailLinkClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/emailmismatch.js b/javascript/ui/page/emailmismatch.js index 83b62478..2a03b907 100644 --- a/javascript/ui/page/emailmismatch.js +++ b/javascript/ui/page/emailmismatch.js @@ -23,68 +23,56 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Email mismatch UI component. - * @param {string} userEmail The email returned by identity provider. - * @param {string} pendingEmail The email formerly used to sign in. - * @param {function()} onContinueClick Callback to invoke when the continue - * button is clicked. - * @param {function()} onCancelClick Callback to invoke when the cancel - * button is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailMismatch = function( - userEmail, - pendingEmail, - onContinueClick, - onCancelClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - // Extend base page class and render email mismatch soy template. - firebaseui.auth.ui.page.EmailMismatch.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailMismatch, - { - userEmail: userEmail, - pendingEmail: pendingEmail - }, - opt_domHelper, - 'emailMismatch', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onContinueClick_ = onContinueClick; - this.onCancelClick_ = onCancelClick; -}; -goog.inherits(firebaseui.auth.ui.page.EmailMismatch, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.EmailMismatch.prototype.enterDocument = function() { - // Initialize form elements with their click handlers. - this.initFormElement(this.onContinueClick_, this.onCancelClick_); - // Set initial focus on the submit button. - this.getSubmitElement().focus(); - firebaseui.auth.ui.page.EmailMismatch.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.EmailMismatch = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} userEmail The email returned by identity provider. + * @param {string} pendingEmail The email formerly used to sign in. + * @param {function()} onContinueClick Callback to invoke when the continue + * button is clicked. + * @param {function()} onCancelClick Callback to invoke when the cancel + * button is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + userEmail, pendingEmail, onContinueClick, onCancelClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + // Extend base page class and render email mismatch soy template. + super( + firebaseui.auth.soy2.page.emailMismatch, + {userEmail: userEmail, pendingEmail: pendingEmail}, opt_domHelper, + 'emailMismatch', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onContinueClick_ = onContinueClick; + this.onCancelClick_ = onCancelClick; + /** @type {?} */ + this.onSubmitClick_; + } + /** @override */ + enterDocument() { + // Initialize form elements with their click handlers. + this.initFormElement(this.onContinueClick_, this.onCancelClick_); + // Set initial focus on the submit button. + this.getSubmitElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.EmailMismatch.prototype.disposeInternal = function() { - this.onSubmitClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.EmailMismatch.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/emailnotreceived.js b/javascript/ui/page/emailnotreceived.js index 99defa8d..128cc266 100644 --- a/javascript/ui/page/emailnotreceived.js +++ b/javascript/ui/page/emailnotreceived.js @@ -24,80 +24,67 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Email not received UI component. - * @param {function()} onResendClick Callback to invoke when the resend link - * is clicked. - * @param {function()} onCancelClick Callback to invoke when the cancel button - * is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.EmailNotReceived = function( - onResendClick, - onCancelClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - // Extend base page class and render email not received soy template. - firebaseui.auth.ui.page.EmailNotReceived.base( - this, - 'constructor', - firebaseui.auth.soy2.page.emailNotReceived, - undefined, - opt_domHelper, - 'emailNotReceived', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onResendClick_ = onResendClick; - this.onCancelClick_ = onCancelClick; -}; -goog.inherits(firebaseui.auth.ui.page.EmailNotReceived, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.EmailNotReceived.prototype.enterDocument = function() { - var self = this; - // Handle action event for cancel button. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getSecondaryLinkElement(), function(e) { - self.onCancelClick_(); - }); - // Handle action event for resend link. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getResendLink(), function(e) { - self.onResendClick_(); - }); - // Set initial focus on the cancel button. - this.getSecondaryLinkElement().focus(); - firebaseui.auth.ui.page.EmailNotReceived.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.EmailNotReceived = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onResendClick Callback to invoke when the resend link + * is clicked. + * @param {function()} onCancelClick Callback to invoke when the cancel button + * is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + onResendClick, onCancelClick, opt_tosCallback, opt_privacyPolicyCallback, + opt_domHelper) { + // Extend base page class and render email not received soy template. + super( + firebaseui.auth.soy2.page.emailNotReceived, undefined, opt_domHelper, + 'emailNotReceived', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onResendClick_ = onResendClick; + this.onCancelClick_ = onCancelClick; + } + /** @override */ + enterDocument() { + var self = this; + // Handle action event for cancel button. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getSecondaryLinkElement(), function(e) { + self.onCancelClick_(); + }); + // Handle action event for resend link. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getResendLink(), function(e) { + self.onResendClick_(); + }); + // Set initial focus on the cancel button. + this.getSecondaryLinkElement().focus(); + super.enterDocument(); + } -/** - * @return {?Element} The resend email link. - */ -firebaseui.auth.ui.page.EmailNotReceived.prototype.getResendLink = - function() { - return this.getElementByClass('firebaseui-id-resend-email-link'); -}; - + /** + * @return {?Element} The resend email link. + */ + getResendLink() { + return this.getElementByClass('firebaseui-id-resend-email-link'); + } -/** @override */ -firebaseui.auth.ui.page.EmailNotReceived.prototype.disposeInternal = - function() { - this.onResendClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.EmailNotReceived.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onResendClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/federatedlinking.js b/javascript/ui/page/federatedlinking.js index eb5ad0aa..af9a8fc0 100644 --- a/javascript/ui/page/federatedlinking.js +++ b/javascript/ui/page/federatedlinking.js @@ -24,62 +24,48 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Federated account linking UI component. - * @param {string} email The user's email. - * @param {?Object} providerConfig The provider config of the IdP we should use - * for sign in. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.FederatedLinking = function( - email, - providerConfig, - onSubmitClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.FederatedLinking.base( - this, - 'constructor', - firebaseui.auth.soy2.page.federatedLinking, - { - email: email, - providerConfig: providerConfig - }, - opt_domHelper, - 'federatedLinking', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; -}; -goog.inherits(firebaseui.auth.ui.page.FederatedLinking, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.FederatedLinking.prototype.enterDocument = function() { - this.initFormElement(this.onSubmitClick_); - this.getSubmitElement().focus(); - firebaseui.auth.ui.page.FederatedLinking.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.FederatedLinking = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The user's email. + * @param {?Object} providerConfig The provider config of the IdP we should + * use for sign in. + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + email, providerConfig, onSubmitClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + super( + firebaseui.auth.soy2.page.federatedLinking, + {email: email, providerConfig: providerConfig}, opt_domHelper, + 'federatedLinking', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + } + /** @override */ + enterDocument() { + this.initFormElement(this.onSubmitClick_); + this.getSubmitElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.FederatedLinking.prototype.disposeInternal = - function() { - this.onSubmitClick_ = null; - firebaseui.auth.ui.page.FederatedLinking.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/notice.js b/javascript/ui/page/notice.js index 6bbe821b..b35bfd3e 100644 --- a/javascript/ui/page/notice.js +++ b/javascript/ui/page/notice.js @@ -25,7 +25,12 @@ goog.provide('firebaseui.auth.ui.page.PasswordChangeSuccess'); goog.provide('firebaseui.auth.ui.page.PasswordRecoveryEmailSent'); goog.provide('firebaseui.auth.ui.page.PasswordResetFailure'); goog.provide('firebaseui.auth.ui.page.PasswordResetSuccess'); +goog.provide('firebaseui.auth.ui.page.RecoverableError'); +goog.provide('firebaseui.auth.ui.page.RevertSecondFactorAdditionFailure'); +goog.provide('firebaseui.auth.ui.page.SignOut'); goog.provide('firebaseui.auth.ui.page.UnrecoverableError'); +goog.provide('firebaseui.auth.ui.page.VerifyAndChangeEmailFailure'); +goog.provide('firebaseui.auth.ui.page.VerifyAndChangeEmailSuccess'); goog.require('firebaseui.auth.soy2.page'); goog.require('firebaseui.auth.ui.element.form'); @@ -186,6 +191,103 @@ goog.inherits(firebaseui.auth.ui.page.EmailVerificationFailure, firebaseui.auth.ui.page.Notice); +/** + * Verify and change email success notice UI component. + * @param {string} email The email being verified and to be updated to. + * @param {?function(): undefined=} onContinueClick Callback to invoke when the + * continue button is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + * @constructor + * @extends {firebaseui.auth.ui.page.Notice} + */ +firebaseui.auth.ui.page.VerifyAndChangeEmailSuccess = function( + email, onContinueClick, domHelper) { + firebaseui.auth.ui.page.VerifyAndChangeEmailSuccess.base( + this, + 'constructor', + firebaseui.auth.soy2.page.verifyAndChangeEmailSuccess, + { + email: email, + allowContinue: !!onContinueClick + }, + onContinueClick, + domHelper, + 'verifyAndChangeEmailSuccess'); +}; +goog.inherits(firebaseui.auth.ui.page.VerifyAndChangeEmailSuccess, + firebaseui.auth.ui.page.Notice); + + +/** + * Verify and change email failure notice UI component. + * @param {?function(): undefined=} onContinueClick Callback to invoke when the + * continue button is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + * @constructor + * @extends {firebaseui.auth.ui.page.Notice} + */ +firebaseui.auth.ui.page.VerifyAndChangeEmailFailure = function( + onContinueClick, domHelper) { + firebaseui.auth.ui.page.VerifyAndChangeEmailFailure.base( + this, + 'constructor', + firebaseui.auth.soy2.page.verifyAndChangeEmailFailure, + { + allowContinue: !!onContinueClick + }, + onContinueClick, + domHelper, + 'verifyAndChangeEmailFailure'); +}; +goog.inherits(firebaseui.auth.ui.page.VerifyAndChangeEmailFailure, + firebaseui.auth.ui.page.Notice); + + +/** + * Revert second factor addition failure notice UI component. + * @param {?function(): undefined=} onContinueClick Callback to invoke when the + * continue button is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + * @constructor + * @extends {firebaseui.auth.ui.page.Notice} + */ +firebaseui.auth.ui.page.RevertSecondFactorAdditionFailure = function( + onContinueClick, domHelper) { + firebaseui.auth.ui.page.RevertSecondFactorAdditionFailure.base( + this, + 'constructor', + firebaseui.auth.soy2.page.revertSecondFactorAdditionFailure, + { + allowContinue: !!onContinueClick + }, + onContinueClick, + domHelper, + 'revertSecondFactorAdditionFailure'); +}; +goog.inherits(firebaseui.auth.ui.page.RevertSecondFactorAdditionFailure, + firebaseui.auth.ui.page.Notice); + + +/** + * Sign out notice UI component. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @constructor + * @extends {firebaseui.auth.ui.page.Notice} + */ +firebaseui.auth.ui.page.SignOut = function(opt_domHelper) { + firebaseui.auth.ui.page.SignOut.base( + this, + 'constructor', + firebaseui.auth.soy2.page.signOut, + undefined, + undefined, + opt_domHelper, + 'signOut'); +}; +goog.inherits(firebaseui.auth.ui.page.SignOut, + firebaseui.auth.ui.page.Notice); + + /** * Password reset success notice UI component. @@ -263,6 +365,32 @@ goog.inherits(firebaseui.auth.ui.page.EmailChangeRevokeFailure, firebaseui.auth.ui.page.Notice); +/** + * Recoverable error notice UI component. + * @param {string} errorMessage The detailed error message to be displayed. + * @param {?function()=} opt_onRetryClick Callback to invoke when the + * retry button is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @constructor + * @extends {firebaseui.auth.ui.page.Notice} + */ +firebaseui.auth.ui.page.RecoverableError = + function(errorMessage, opt_onRetryClick, opt_domHelper) { + firebaseui.auth.ui.page.RecoverableError.base( + this, + 'constructor', + firebaseui.auth.soy2.page.recoverableError, + { + errorMessage: errorMessage, + allowRetry: !!opt_onRetryClick + }, + opt_onRetryClick, + opt_domHelper, + 'recoverableError'); +}; +goog.inherits(firebaseui.auth.ui.page.RecoverableError, + firebaseui.auth.ui.page.Notice); + /** * Unrecoverable error notice UI component. diff --git a/javascript/ui/page/notice_test.js b/javascript/ui/page/notice_test.js index 05a12cab..4be4de1b 100644 --- a/javascript/ui/page/notice_test.js +++ b/javascript/ui/page/notice_test.js @@ -29,7 +29,12 @@ goog.require('firebaseui.auth.ui.page.PageTestHelper'); goog.require('firebaseui.auth.ui.page.PasswordRecoveryEmailSent'); goog.require('firebaseui.auth.ui.page.PasswordResetFailure'); goog.require('firebaseui.auth.ui.page.PasswordResetSuccess'); +goog.require('firebaseui.auth.ui.page.RecoverableError'); +goog.require('firebaseui.auth.ui.page.RevertSecondFactorAdditionFailure'); +goog.require('firebaseui.auth.ui.page.SignOut'); goog.require('firebaseui.auth.ui.page.UnrecoverableError'); +goog.require('firebaseui.auth.ui.page.VerifyAndChangeEmailFailure'); +goog.require('firebaseui.auth.ui.page.VerifyAndChangeEmailSuccess'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.testing.MockClock'); @@ -182,6 +187,40 @@ function testEmailChangeRevokeFailure_getPageId() { } +function testVerifyAndChangeEmailSuccess_getPageId() { + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.VerifyAndChangeEmailSuccess( + 'user@example.com', + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + assertEquals('verifyAndChangeEmailSuccess', component.getPageId()); +} + + +function testVerifyAndChangeEmailFailure_getPageId() { + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.VerifyAndChangeEmailFailure( + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + assertEquals('verifyAndChangeEmailFailure', component.getPageId()); +} + + +function testRevertSecondFactorAdditionFailure_getPageId() { + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.RevertSecondFactorAdditionFailure( + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + assertEquals('revertSecondFactorAdditionFailure', component.getPageId()); +} + + function testUnrecoverableError_getPageId() { component.dispose(); // Initialize component. @@ -189,3 +228,41 @@ function testUnrecoverableError_getPageId() { 'Error occurred!'); assertEquals('unrecoverableError', component.getPageId()); } + + +function testSignOut_getPageId() { + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.SignOut(); + assertEquals('signOut', component.getPageId()); +} + + +function testRecoverableError_getPageId() { + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.RecoverableError( + 'Error occurred!'); + assertEquals('recoverableError', component.getPageId()); +} + + +function testRecoverableError_InitialFocus() { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + return; + } + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.RecoverableError( + 'Error occurred!', + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + component.render(root); + formTestHelper.setComponent(component); + // Reset previous state of form helper. + formTestHelper.resetState(); + assertEquals( + component.getSubmitElement(), + goog.dom.getActiveElement(document)); +} diff --git a/javascript/ui/page/passwordlinking.js b/javascript/ui/page/passwordlinking.js index 0ec9fe94..3447c8a6 100644 --- a/javascript/ui/page/passwordlinking.js +++ b/javascript/ui/page/passwordlinking.js @@ -26,74 +26,57 @@ goog.require('firebaseui.auth.ui.page.Base'); goog.require('goog.asserts'); - /** * Password linking UI component. - * @param {string} email The user's email. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {function()} onForgotClick Callback to invoke when the forgot password - * link is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PasswordLinking = function( - email, - onSubmitClick, - onForgotClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.PasswordLinking.base( - this, - 'constructor', - firebaseui.auth.soy2.page.passwordLinking, - { - email: email - }, - opt_domHelper, - 'passwordLinking', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; - this.onForgotClick_ = onForgotClick; -}; -goog.inherits(firebaseui.auth.ui.page.PasswordLinking, - firebaseui.auth.ui.page.Base); +firebaseui.auth.ui.page.PasswordLinking = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The user's email. + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {function()} onForgotClick Callback to invoke when the forgot + * password link is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + email, onSubmitClick, onForgotClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + super( + firebaseui.auth.soy2.page.passwordLinking, {email: email}, + opt_domHelper, 'passwordLinking', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + this.onForgotClick_ = onForgotClick; + } + /** @override */ + enterDocument() { + this.initPasswordElement(); + this.initFormElement(this.onSubmitClick_, this.onForgotClick_); + // Submit on enter in password element. + this.submitOnEnter(this.getPasswordElement(), this.onSubmitClick_); + this.getPasswordElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.PasswordLinking.prototype.enterDocument = function() { - this.initPasswordElement(); - this.initFormElement(this.onSubmitClick_, this.onForgotClick_); - // Submit on enter in password element. - this.submitOnEnter( - this.getPasswordElement(), - this.onSubmitClick_); - this.getPasswordElement().focus(); - firebaseui.auth.ui.page.PasswordLinking.base(this, 'enterDocument'); -}; - - -/** @override */ -firebaseui.auth.ui.page.PasswordLinking.prototype.disposeInternal = function() { - this.onSubmitClick_ = null; - firebaseui.auth.ui.page.PasswordLinking.base(this, 'disposeInternal'); -}; - + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + super.disposeInternal(); + } -/** @return {string} The email address of the account. */ -firebaseui.auth.ui.page.PasswordLinking.prototype.checkAndGetEmail = - function() { - return goog.asserts.assertString(firebaseui.auth.ui.element.getInputValue( - this.getElementByClass('firebaseui-id-email'))); + /** @return {string} The email address of the account. */ + checkAndGetEmail() { + return goog.asserts.assertString(firebaseui.auth.ui.element.getInputValue( + this.getElementByClass('firebaseui-id-email'))); + } }; diff --git a/javascript/ui/page/passwordrecovery.js b/javascript/ui/page/passwordrecovery.js index 48f5ea35..1d0c4134 100644 --- a/javascript/ui/page/passwordrecovery.js +++ b/javascript/ui/page/passwordrecovery.js @@ -27,65 +27,52 @@ goog.require('firebaseui.auth.ui.page.Base'); /** * Password recovery UI component. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {function()=} opt_onCancelClick Callback to invoke when the cancel - * button is clicked. - * @param {string=} opt_email The email to prefill. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PasswordRecovery = function( - onSubmitClick, - opt_onCancelClick, - opt_email, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.PasswordRecovery.base( - this, - 'constructor', - firebaseui.auth.soy2.page.passwordRecovery, - { - email: opt_email, - allowCancel: !!opt_onCancelClick - }, - opt_domHelper, - 'passwordRecovery', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; - this.onCancelClick_ = opt_onCancelClick; -}; -goog.inherits(firebaseui.auth.ui.page.PasswordRecovery, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.PasswordRecovery.prototype.enterDocument = function() { - this.initEmailElement(); - this.initFormElement(this.onSubmitClick_, this.onCancelClick_); - if (!firebaseui.auth.ui.element.getInputValue(this.getEmailElement())) { - this.getEmailElement().focus(); +firebaseui.auth.ui.page.PasswordRecovery = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {function()=} opt_onCancelClick Callback to invoke when the cancel + * button is clicked. + * @param {string=} opt_email The email to prefill. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + onSubmitClick, opt_onCancelClick, opt_email, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + super( + firebaseui.auth.soy2.page.passwordRecovery, + {email: opt_email, allowCancel: !!opt_onCancelClick}, opt_domHelper, + 'passwordRecovery', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + this.onCancelClick_ = opt_onCancelClick; } - this.submitOnEnter(this.getEmailElement(), this.onSubmitClick_); - firebaseui.auth.ui.page.PasswordRecovery.base(this, 'enterDocument'); -}; + /** @override */ + enterDocument() { + this.initEmailElement(); + this.initFormElement(this.onSubmitClick_, this.onCancelClick_); + if (!firebaseui.auth.ui.element.getInputValue(this.getEmailElement())) { + this.getEmailElement().focus(); + } + this.submitOnEnter(this.getEmailElement(), this.onSubmitClick_); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.PasswordRecovery.prototype.disposeInternal = - function() { - this.onSubmitClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.PasswordRecovery.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/passwordreset.js b/javascript/ui/page/passwordreset.js index e48e1020..5841549a 100644 --- a/javascript/ui/page/passwordreset.js +++ b/javascript/ui/page/passwordreset.js @@ -24,45 +24,38 @@ goog.require('firebaseui.auth.ui.element.newPassword'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Password reset UI component. - * @param {string} email The email to prefill. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PasswordReset = function(email, onSubmitClick, - opt_domHelper) { - firebaseui.auth.ui.page.PasswordReset.base( - this, - 'constructor', - firebaseui.auth.soy2.page.passwordReset, - {email: email}, - opt_domHelper, - 'passwordReset'); - this.onSubmitClick_ = onSubmitClick; -}; -goog.inherits(firebaseui.auth.ui.page.PasswordReset, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.PasswordReset.prototype.enterDocument = function() { - this.initNewPasswordElement(); - this.initFormElement(this.onSubmitClick_); - this.submitOnEnter(this.getNewPasswordElement(), this.onSubmitClick_); - this.getNewPasswordElement().focus(); - firebaseui.auth.ui.page.PasswordReset.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.PasswordReset = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The email to prefill. + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(email, onSubmitClick, opt_domHelper) { + super( + firebaseui.auth.soy2.page.passwordReset, {email: email}, opt_domHelper, + 'passwordReset'); + this.onSubmitClick_ = onSubmitClick; + } + /** @override */ + enterDocument() { + this.initNewPasswordElement(); + this.initFormElement(this.onSubmitClick_); + this.submitOnEnter(this.getNewPasswordElement(), this.onSubmitClick_); + this.getNewPasswordElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.PasswordReset.prototype.disposeInternal = function() { - this.onSubmitClick_ = null; - firebaseui.auth.ui.page.PasswordReset.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/passwordsignin.js b/javascript/ui/page/passwordsignin.js index 4171abe8..6b951344 100644 --- a/javascript/ui/page/passwordsignin.js +++ b/javascript/ui/page/passwordsignin.js @@ -26,76 +26,64 @@ goog.require('firebaseui.auth.ui.element.password'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Password sign-in UI component. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {function()} onForgotClick Callback to invoke when the forgot password - * link is clicked. - * @param {string=} opt_email The email to prefill. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full - * message of Term of Service and Privacy Policy. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PasswordSignIn = function( - onSubmitClick, - onForgotClick, - opt_email, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_displayFullTosPpMessage, - opt_domHelper) { - firebaseui.auth.ui.page.PasswordSignIn.base( - this, - 'constructor', - firebaseui.auth.soy2.page.passwordSignIn, - { - email: opt_email, - displayFullTosPpMessage: !!opt_displayFullTosPpMessage - }, - opt_domHelper, - 'passwordSignIn', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; - this.onForgotClick_ = onForgotClick; -}; -goog.inherits(firebaseui.auth.ui.page.PasswordSignIn, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.PasswordSignIn.prototype.enterDocument = function() { - this.initEmailElement(); - this.initPasswordElement(); - this.initFormElement(this.onSubmitClick_, this.onForgotClick_); - this.focusToNextOnEnter(this.getEmailElement(), this.getPasswordElement()); - // Submit if enter pressed in password element. - this.submitOnEnter(this.getPasswordElement(), this.onSubmitClick_); - // Auto focus. - if (!firebaseui.auth.ui.element.getInputValue(this.getEmailElement())) { - this.getEmailElement().focus(); - } else { - this.getPasswordElement().focus(); +firebaseui.auth.ui.page.PasswordSignIn = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {function()} onForgotClick Callback to invoke when the forgot + * password link is clicked. + * @param {string=} opt_email The email to prefill. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full + * message of Term of Service and Privacy Policy. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + onSubmitClick, onForgotClick, opt_email, opt_tosCallback, + opt_privacyPolicyCallback, opt_displayFullTosPpMessage, opt_domHelper) { + super( + firebaseui.auth.soy2.page.passwordSignIn, { + email: opt_email, + displayFullTosPpMessage: !!opt_displayFullTosPpMessage + }, + opt_domHelper, 'passwordSignIn', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + this.onForgotClick_ = onForgotClick; } - firebaseui.auth.ui.page.PasswordSignIn.base(this, 'enterDocument'); -}; + /** @override */ + enterDocument() { + this.initEmailElement(); + this.initPasswordElement(); + this.initFormElement(this.onSubmitClick_, this.onForgotClick_); + this.focusToNextOnEnter(this.getEmailElement(), this.getPasswordElement()); + // Submit if enter pressed in password element. + this.submitOnEnter(this.getPasswordElement(), this.onSubmitClick_); + // Auto focus. + if (!firebaseui.auth.ui.element.getInputValue(this.getEmailElement())) { + this.getEmailElement().focus(); + } else { + this.getPasswordElement().focus(); + } + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.PasswordSignIn.prototype.disposeInternal = function() { - this.onSubmitClick_ = null; - this.onForgotClick_ = null; - firebaseui.auth.ui.page.PasswordSignIn.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + this.onForgotClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/ui/page/passwordsignup.js b/javascript/ui/page/passwordsignup.js index aa65bbf5..914b408a 100644 --- a/javascript/ui/page/passwordsignup.js +++ b/javascript/ui/page/passwordsignup.js @@ -27,112 +27,99 @@ goog.require('firebaseui.auth.ui.element.newPassword'); goog.require('firebaseui.auth.ui.page.Base'); - /** * Password sign-up UI component. - * @param {boolean} requireDisplayName Whether to show the display name. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {function()=} opt_onCancelClick Callback to invoke when the cancel - * button is clicked. - * @param {string=} opt_email The email to prefill. - * @param {string=} opt_name The name to prefill. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full - * message of Term of Service and Privacy Policy. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PasswordSignUp = function( - requireDisplayName, - onSubmitClick, - opt_onCancelClick, - opt_email, - opt_name, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_displayFullTosPpMessage, - opt_domHelper) { - firebaseui.auth.ui.page.PasswordSignUp.base( - this, - 'constructor', - firebaseui.auth.soy2.page.passwordSignUp, - { - email: opt_email, - requireDisplayName: requireDisplayName, - name: opt_name, - allowCancel: !!opt_onCancelClick, - displayFullTosPpMessage: !!opt_displayFullTosPpMessage - }, - opt_domHelper, - 'passwordSignUp', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; - this.onCancelClick_ = opt_onCancelClick; - this.requireDisplayName_ = requireDisplayName; -}; -goog.inherits(firebaseui.auth.ui.page.PasswordSignUp, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.PasswordSignUp.prototype.enterDocument = function() { - this.initEmailElement(); - if (this.requireDisplayName_) { - this.initNameElement(); +firebaseui.auth.ui.page.PasswordSignUp = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {boolean} requireDisplayName Whether to show the display name. + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {function()=} opt_onCancelClick Callback to invoke when the cancel + * button is clicked. + * @param {string=} opt_email The email to prefill. + * @param {string=} opt_name The name to prefill. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full + * message of Term of Service and Privacy Policy. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + requireDisplayName, onSubmitClick, opt_onCancelClick, opt_email, opt_name, + opt_tosCallback, opt_privacyPolicyCallback, opt_displayFullTosPpMessage, + opt_domHelper) { + super( + firebaseui.auth.soy2.page.passwordSignUp, { + email: opt_email, + requireDisplayName: requireDisplayName, + name: opt_name, + allowCancel: !!opt_onCancelClick, + displayFullTosPpMessage: !!opt_displayFullTosPpMessage + }, + opt_domHelper, 'passwordSignUp', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + this.onCancelClick_ = opt_onCancelClick; + this.requireDisplayName_ = requireDisplayName; } - this.initNewPasswordElement(); - this.initFormElement(this.onSubmitClick_, this.onCancelClick_); - this.setupFocus_(); - firebaseui.auth.ui.page.PasswordSignUp.base(this, 'enterDocument'); -}; - -/** @override */ -firebaseui.auth.ui.page.PasswordSignUp.prototype.disposeInternal = function() { - this.onSubmitClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.PasswordSignUp.base(this, 'disposeInternal'); -}; - - -/** - * Sets up the focus order and auto focus. - * @private - */ -firebaseui.auth.ui.page.PasswordSignUp.prototype.setupFocus_ = function() { - // Focus order. - if (this.requireDisplayName_) { - this.focusToNextOnEnter(this.getEmailElement(), this.getNameElement()); - this.focusToNextOnEnter( - this.getNameElement(), this.getNewPasswordElement()); - } else { - this.focusToNextOnEnter( - this.getEmailElement(), this.getNewPasswordElement()); + /** @override */ + enterDocument() { + this.initEmailElement(); + if (this.requireDisplayName_) { + this.initNameElement(); + } + this.initNewPasswordElement(); + this.initFormElement(this.onSubmitClick_, this.onCancelClick_); + this.setupFocus_(); + super.enterDocument(); } - // On enter in password element. - // TODO: Investigate why the compiler complains about onSubmitClick_ being - // nullable here but not in any other file. - if (this.onSubmitClick_) { - this.submitOnEnter(this.getNewPasswordElement(), this.onSubmitClick_); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); } - // Auto focus. - if (!firebaseui.auth.ui.element.getInputValue(this.getEmailElement())) { - this.getEmailElement().focus(); - } else if (this.requireDisplayName_ && - !firebaseui.auth.ui.element.getInputValue(this.getNameElement())) { - this.getNameElement().focus(); - } else { - this.getNewPasswordElement().focus(); + /** + * Sets up the focus order and auto focus. + * @private + */ + setupFocus_() { + // Focus order. + if (this.requireDisplayName_) { + this.focusToNextOnEnter(this.getEmailElement(), this.getNameElement()); + this.focusToNextOnEnter( + this.getNameElement(), this.getNewPasswordElement()); + } else { + this.focusToNextOnEnter( + this.getEmailElement(), this.getNewPasswordElement()); + } + + // On enter in password element. + // TODO: Investigate why the compiler complains about onSubmitClick_ being + // nullable here but not in any other file. + if (this.onSubmitClick_) { + this.submitOnEnter(this.getNewPasswordElement(), this.onSubmitClick_); + } + + // Auto focus. + if (!firebaseui.auth.ui.element.getInputValue(this.getEmailElement())) { + this.getEmailElement().focus(); + } else if ( + this.requireDisplayName_ && + !firebaseui.auth.ui.element.getInputValue(this.getNameElement())) { + this.getNameElement().focus(); + } else { + this.getNewPasswordElement().focus(); + } } }; diff --git a/javascript/ui/page/phonesigninfinish.js b/javascript/ui/page/phonesigninfinish.js index b804e814..3d9df3fa 100644 --- a/javascript/ui/page/phonesigninfinish.js +++ b/javascript/ui/page/phonesigninfinish.js @@ -26,144 +26,134 @@ goog.require('firebaseui.auth.ui.element.form'); goog.require('firebaseui.auth.ui.element.phoneConfirmationCode'); goog.require('firebaseui.auth.ui.element.resend'); goog.require('firebaseui.auth.ui.page.Base'); +goog.require('goog.Timer'); +goog.require('goog.events'); /** * UI component for the user to enter a phone confirmation code. - * @param {function()} onChangePhoneNumberClick Callback to invoke when change - * phone number link is clicked. - * @param {function()} onSubmitClick Callback to invoke when enter key (or its - * equivalent) is detected on submission. - * @param {function()} onCancelClick Callback to invoke when cancel button - * is clicked. - * @param {function()} onResendClick Callback to invoke when resend link - * is clicked. - * @param {string} phoneNumber the phone number to confirm. - * @param {number} resendDelay The resend delay. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PhoneSignInFinish = function( - onChangePhoneNumberClick, onSubmitClick, onCancelClick, onResendClick, - phoneNumber, resendDelay, opt_tosCallback, opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.PhoneSignInFinish.base( - this, 'constructor', firebaseui.auth.soy2.page.phoneSignInFinish, - { - phoneNumber: phoneNumber - }, - opt_domHelper, - 'phoneSignInFinish', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - /** @private {string} the phone number to confirm. */ - this.phoneNumber_ = phoneNumber; - /** @private {number} The resend delay. */ - this.resendDelay_ = resendDelay; - /** @private {?goog.Timer} A resend timer with a one-second interval. */ - this.resendTimer_ = new goog.Timer(1000); - /** @private {number} The seconds remaining before enabling resend. */ - this.secondsRemaining_ = resendDelay; - /** @private {?function()} On edit click callback. */ - this.onChangePhoneNumberClick_ = onChangePhoneNumberClick; - /** @private {?function()} On submit click callback. */ - this.onSubmitClick_ = onSubmitClick; - /** @private {?function()} On cancel click callback. */ - this.onCancelClick_ = onCancelClick; - /** @private {?function()} On resend click callback. */ - this.onResendClick_ = onResendClick; -}; -goog.inherits( - firebaseui.auth.ui.page.PhoneSignInFinish, firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.PhoneSignInFinish.prototype.enterDocument = function() { - var self = this; - // Init countdown. - this.updateResendCountdown(this.resendDelay_); - goog.events.listen( - this.resendTimer_, goog.Timer.TICK, this.handleTickEvent_, false, this); - this.resendTimer_.start(); - // Handle change phone number click. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getChangePhoneNumberElement(), function(e) { - self.onChangePhoneNumberClick_(); - }); - // Handle resend click. - firebaseui.auth.ui.element.listenForActionEvent( - this, this.getResendLink(), function(e) { - self.onResendClick_(); - }); - // Submit if user taps enter while confirmation code element has focus. - this.initPhoneConfirmationCodeElement( - /** @type {function()} */ (this.onSubmitClick_)); - // Handle a click on the submit button or cancel button. - this.initFormElement( - /** @type {function()} */ (this.onSubmitClick_), - /** @type {function()} */ (this.onCancelClick_)); - this.setupFocus_(); - firebaseui.auth.ui.page.PhoneSignInFinish.base(this, 'enterDocument'); -}; - - -/** @override */ -firebaseui.auth.ui.page.PhoneSignInFinish.prototype.disposeInternal = - function() { - this.onChangePhoneNumberClick_ = null; - this.onSubmitClick_ = null; - this.onCancelClick_ = null; - this.onResendClick_ = null; - // Dispose of countdown. - this.resendTimer_.stop(); - goog.events.unlisten( - this.resendTimer_, goog.Timer.TICK, this.handleTickEvent_); - this.resendTimer_ = null; - firebaseui.auth.ui.page.PhoneSignInFinish.base(this, 'disposeInternal'); -}; +firebaseui.auth.ui.page.PhoneSignInFinish = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onChangePhoneNumberClick Callback to invoke when change + * phone number link is clicked. + * @param {function()} onSubmitClick Callback to invoke when enter key (or its + * equivalent) is detected on submission. + * @param {function()} onCancelClick Callback to invoke when cancel button + * is clicked. + * @param {function()} onResendClick Callback to invoke when resend link + * is clicked. + * @param {string} phoneNumber the phone number to confirm. + * @param {number} resendDelay The resend delay. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + onChangePhoneNumberClick, onSubmitClick, onCancelClick, onResendClick, + phoneNumber, resendDelay, opt_tosCallback, opt_privacyPolicyCallback, + opt_domHelper) { + super( + firebaseui.auth.soy2.page.phoneSignInFinish, {phoneNumber: phoneNumber}, + opt_domHelper, 'phoneSignInFinish', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + /** @private {string} the phone number to confirm. */ + this.phoneNumber_ = phoneNumber; + /** @private {number} The resend delay. */ + this.resendDelay_ = resendDelay; + /** @private {?goog.Timer} A resend timer with a one-second interval. */ + this.resendTimer_ = new goog.Timer(1000); + /** @private {number} The seconds remaining before enabling resend. */ + this.secondsRemaining_ = resendDelay; + /** @private {?function()} On edit click callback. */ + this.onChangePhoneNumberClick_ = onChangePhoneNumberClick; + /** @private {?function()} On submit click callback. */ + this.onSubmitClick_ = onSubmitClick; + /** @private {?function()} On cancel click callback. */ + this.onCancelClick_ = onCancelClick; + /** @private {?function()} On resend click callback. */ + this.onResendClick_ = onResendClick; + } + /** @override */ + enterDocument() { + var self = this; + // Init countdown. + this.updateResendCountdown(this.resendDelay_); + goog.events.listen( + this.resendTimer_, goog.Timer.TICK, this.handleTickEvent_, false, this); + this.resendTimer_.start(); + // Handle change phone number click. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getChangePhoneNumberElement(), function(e) { + self.onChangePhoneNumberClick_(); + }); + // Handle resend click. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getResendLink(), function(e) { + self.onResendClick_(); + }); + // Submit if user taps enter while confirmation code element has focus. + this.initPhoneConfirmationCodeElement( + /** @type {function()} */ (this.onSubmitClick_)); + // Handle a click on the submit button or cancel button. + this.initFormElement( + /** @type {function()} */ (this.onSubmitClick_), + /** @type {function()} */ (this.onCancelClick_)); + this.setupFocus_(); + super.enterDocument(); + } -/** - * Handles clock tick events. - * @private - */ -firebaseui.auth.ui.page.PhoneSignInFinish.prototype.handleTickEvent_ = - function() { - this.secondsRemaining_ -= 1; - if (this.secondsRemaining_ > 0) { - this.updateResendCountdown(this.secondsRemaining_); - } else { + /** @override */ + disposeInternal() { + this.onChangePhoneNumberClick_ = null; + this.onSubmitClick_ = null; + this.onCancelClick_ = null; + this.onResendClick_ = null; + // Dispose of countdown. this.resendTimer_.stop(); goog.events.unlisten( this.resendTimer_, goog.Timer.TICK, this.handleTickEvent_); - this.hideResendCountdown(); - this.showResendLink(); + this.resendTimer_ = null; + super.disposeInternal(); } -}; - -/** - * Sets up the focus order and auto focus. - * @private - */ -firebaseui.auth.ui.page.PhoneSignInFinish.prototype.setupFocus_ = function() { - this.getPhoneConfirmationCodeElement().focus(); -}; + /** + * Handles clock tick events. + * @private + */ + handleTickEvent_() { + this.secondsRemaining_ -= 1; + if (this.secondsRemaining_ > 0) { + this.updateResendCountdown(this.secondsRemaining_); + } else { + this.resendTimer_.stop(); + goog.events.unlisten( + this.resendTimer_, goog.Timer.TICK, this.handleTickEvent_); + this.hideResendCountdown(); + this.showResendLink(); + } + } + /** + * Sets up the focus order and auto focus. + * @private + */ + setupFocus_() { + this.getPhoneConfirmationCodeElement().focus(); + } -/** - * @return {?Element} The change phone number link. - */ -firebaseui.auth.ui.page.PhoneSignInFinish.prototype - .getChangePhoneNumberElement = function() { - return this.getElementByClass('firebaseui-id-change-phone-number-link'); + /** + * @return {?Element} The change phone number link. + */ + getChangePhoneNumberElement() { + return this.getElementByClass('firebaseui-id-change-phone-number-link'); + } }; diff --git a/javascript/ui/page/phonesigninstart.js b/javascript/ui/page/phonesigninstart.js index 98486f87..0733cd50 100644 --- a/javascript/ui/page/phonesigninstart.js +++ b/javascript/ui/page/phonesigninstart.js @@ -27,119 +27,105 @@ goog.require('firebaseui.auth.ui.page.Base'); goog.require('goog.dom.selection'); - /** * UI component for the user to enter their phone number. - * @param {function(?)} onSubmitClick Callback to invoke when enter key (or its - * equivalent) is detected on submission. - * @param {boolean} enableVisibleRecaptcha Whether to enable visible reCAPTCHA. - * @param {?function(?)=} opt_onCancelClick Callback to invoke when cancel - * button is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full - * message of Term of Service and Privacy Policy. - * @param {?firebaseui.auth.data.country.LookupTree=} opt_lookupTree The country - * lookup prefix tree to search country code with. - * @param {?string=} opt_countryId The ID (e164_key) of the country to - * pre-select. - * @param {?string=} opt_nationalNumber The national number to pre-fill. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.PhoneSignInStart = function( - onSubmitClick, - enableVisibleRecaptcha, - opt_onCancelClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_displayFullTosPpMessage, - opt_lookupTree, - opt_countryId, - opt_nationalNumber, - opt_domHelper) { - var nationalNumber = opt_nationalNumber || null; - firebaseui.auth.ui.page.PhoneSignInStart.base( - this, - 'constructor', - firebaseui.auth.soy2.page.phoneSignInStart, - { - enableVisibleRecaptcha: enableVisibleRecaptcha, - nationalNumber: nationalNumber, - displayCancelButton: !!opt_onCancelClick, - displayFullTosPpMessage: !!opt_displayFullTosPpMessage - }, - opt_domHelper, - 'phoneSignInStart', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - /** @private @const {?string} The default country to select. */ - this.countryId_ = opt_countryId || null; - /** @private {boolean} Whether to enable visible reCAPTCHA. */ - this.enableVisibleRecaptcha_ = enableVisibleRecaptcha; - /** @private {?function(?)} On submit click callback. */ - this.onSubmitClick_ = onSubmitClick; - /** @private {?function(?)} On cancel click callback. */ - this.onCancelClick_ = opt_onCancelClick || null; +firebaseui.auth.ui.page.PhoneSignInStart = + class extends firebaseui.auth.ui.page.Base { /** - * @private {?firebaseui.auth.data.country.LookupTree} The country - * lookup prefix tree to search country code with. + * @param {function(?)} onSubmitClick Callback to invoke when enter key (or + * its equivalent) is detected on submission. + * @param {boolean} enableVisibleRecaptcha Whether to enable visible + * reCAPTCHA. + * @param {?function(?)=} opt_onCancelClick Callback to invoke when cancel + * button is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full + * message of Term of Service and Privacy Policy. + * @param {?firebaseui.auth.data.country.LookupTree=} opt_lookupTree The + * country lookup prefix tree to search country code with. + * @param {?string=} opt_countryId The ID (e164_key) of the country to + * pre-select. + * @param {?string=} opt_nationalNumber The national number to pre-fill. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. */ - this.lookupTree_ = opt_lookupTree || null; -}; -goog.inherits( - firebaseui.auth.ui.page.PhoneSignInStart, firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.PhoneSignInStart.prototype.enterDocument = function() { - this.initPhoneNumberElement(this.lookupTree_, this.countryId_); - // Handle a click on the submit button or cancel button. - this.initFormElement( - /** @type {function(?)} */ (this.onSubmitClick_), - this.onCancelClick_ || undefined); - this.setupFocus_(); - firebaseui.auth.ui.page.PhoneSignInStart.base(this, 'enterDocument'); -}; - - -/** @override */ -firebaseui.auth.ui.page.PhoneSignInStart.prototype.disposeInternal = - function() { - this.onSubmitClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.PhoneSignInStart.base(this, 'disposeInternal'); -}; + constructor( + onSubmitClick, enableVisibleRecaptcha, opt_onCancelClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_displayFullTosPpMessage, opt_lookupTree, + opt_countryId, opt_nationalNumber, opt_domHelper) { + var nationalNumber = opt_nationalNumber || null; + super( + firebaseui.auth.soy2.page.phoneSignInStart, { + enableVisibleRecaptcha: enableVisibleRecaptcha, + nationalNumber: nationalNumber, + displayCancelButton: !!opt_onCancelClick, + displayFullTosPpMessage: !!opt_displayFullTosPpMessage + }, + opt_domHelper, 'phoneSignInStart', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + /** @private @const {?string} The default country to select. */ + this.countryId_ = opt_countryId || null; + /** @private {boolean} Whether to enable visible reCAPTCHA. */ + this.enableVisibleRecaptcha_ = enableVisibleRecaptcha; + /** @private {?function(?)} On submit click callback. */ + this.onSubmitClick_ = onSubmitClick; + /** @private {?function(?)} On cancel click callback. */ + this.onCancelClick_ = opt_onCancelClick || null; + /** + * @private {?firebaseui.auth.data.country.LookupTree} The country + * lookup prefix tree to search country code with. + */ + this.lookupTree_ = opt_lookupTree || null; + } + /** @override */ + enterDocument() { + this.initPhoneNumberElement(this.lookupTree_, this.countryId_); + // Handle a click on the submit button or cancel button. + this.initFormElement( + /** @type {function(?)} */ (this.onSubmitClick_), + this.onCancelClick_ || undefined); + this.setupFocus_(); + super.enterDocument(); + } -/** - * Sets up the focus order and auto focus. - * @private - */ -firebaseui.auth.ui.page.PhoneSignInStart.prototype.setupFocus_ = function() { - // Focus order. - if (!this.enableVisibleRecaptcha_) { - // When reCAPTCHA is not visible shift focus to submit button. - this.focusToNextOnEnter( - this.getPhoneNumberElement(), this.getSubmitElement()); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); } - // Otherwise, can't force focus on visible reCAPTCHA. - // Do not submit directly on phone input enter since an invisible reCAPTCHA - // must be triggered by a button click, otherwise it may force a visible - // challenge. - this.submitOnEnter( - this.getSubmitElement(), /** @type {function()} */ (this.onSubmitClick_)); - // Auto focus the phone input and put the cursor at the end. - this.getPhoneNumberElement().focus(); - goog.dom.selection.setCursorPosition( - this.getPhoneNumberElement(), - (this.getPhoneNumberElement().value || '').length); + /** + * Sets up the focus order and auto focus. + * @private + */ + setupFocus_() { + // Focus order. + if (!this.enableVisibleRecaptcha_) { + // When reCAPTCHA is not visible shift focus to submit button. + this.focusToNextOnEnter( + this.getPhoneNumberElement(), this.getSubmitElement()); + } + // Otherwise, can't force focus on visible reCAPTCHA. + + // Do not submit directly on phone input enter since an invisible reCAPTCHA + // must be triggered by a button click, otherwise it may force a visible + // challenge. + this.submitOnEnter( + this.getSubmitElement(), + /** @type {function()} */ (this.onSubmitClick_)); + // Auto focus the phone input and put the cursor at the end. + this.getPhoneNumberElement().focus(); + goog.dom.selection.setCursorPosition( + this.getPhoneNumberElement(), + (this.getPhoneNumberElement().value || '').length); + } }; diff --git a/javascript/ui/page/providermatchbyemail.js b/javascript/ui/page/providermatchbyemail.js new file mode 100644 index 00000000..8f5bbc25 --- /dev/null +++ b/javascript/ui/page/providermatchbyemail.js @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @fileoverview UI component for matching providers by the given email. */ + +goog.module('firebaseui.auth.ui.page.ProviderMatchByEmail'); +goog.module.declareLegacyNamespace(); + +const Base = goog.require('firebaseui.auth.ui.page.Base'); +const email = goog.require('firebaseui.auth.ui.element.email'); +const form = goog.require('firebaseui.auth.ui.element.form'); +const page = goog.require('firebaseui.auth.soy2.page'); +const selection = goog.require('goog.dom.selection'); + +/** + * UI component to match a user's inputted email with the associated sign-in + * methods. + */ +class ProviderMatchByEmail extends Base { + /** + * @param {function()} onEmailEnter Callback to invoke when enter key + * (or its equivalent) is detected. + * @param {?function()=} tosCallback The optional callback to invoke when the + * ToS link is clicked. + * @param {?function()=} privacyPolicyCallback The optional callback to + * invoke when the Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + */ + constructor( + onEmailEnter, tosCallback = undefined, privacyPolicyCallback = undefined, + domHelper = undefined) { + super( + page.providerMatchByEmail, + undefined, domHelper, 'providerMatchByEmail', { + tosCallback: tosCallback, + privacyPolicyCallback: privacyPolicyCallback, + }); + this.onEmailEnter_ = onEmailEnter; + } + + /** @override */ + enterDocument() { + this.initEmailElement(this.onEmailEnter_); + // Handle a click on the submit button button. + this.initFormElement(this.onEmailEnter_); + this.setupFocus_(); + super.enterDocument(); + } + + /** @override */ + disposeInternal() { + this.onEmailEnter_ = null; + super.disposeInternal(); + } + + /** + * Sets up the focus order and auto focus. + * @private + */ + setupFocus_() { + // Auto focus the email input and put the cursor at the end. + this.getEmailElement().focus(); + selection.setCursorPosition( + this.getEmailElement(), (this.getEmailElement().value || '').length); + } +} + +goog.mixin( + ProviderMatchByEmail.prototype, + /** @lends {ProviderMatchByEmail.prototype} */ + { + // For email. + getEmailElement: email.getEmailElement, + getEmailErrorElement: email.getEmailErrorElement, + initEmailElement: email.initEmailElement, + getEmail: email.getEmail, + checkAndGetEmail: email.checkAndGetEmail, + + // For form. + getSubmitElement: form.getSubmitElement, + initFormElement: form.initFormElement, + }); + +exports = ProviderMatchByEmail; + diff --git a/javascript/ui/page/providermatchbyemail_test.js b/javascript/ui/page/providermatchbyemail_test.js new file mode 100644 index 00000000..dc58ec60 --- /dev/null +++ b/javascript/ui/page/providermatchbyemail_test.js @@ -0,0 +1,150 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Tests for the provider match by email page. + */ + +goog.provide('firebaseui.auth.ui.page.ProviderMatchByEmailTest'); +goog.setTestOnly('firebaseui.auth.ui.page.ProviderMatchByEmailTest'); + +goog.require('firebaseui.auth.ui.element'); +goog.require('firebaseui.auth.ui.element.EmailTestHelper'); +goog.require('firebaseui.auth.ui.element.InfoBarTestHelper'); +goog.require('firebaseui.auth.ui.element.TosPpTestHelper'); +goog.require('firebaseui.auth.ui.page.PageTestHelper'); +goog.require('firebaseui.auth.ui.page.ProviderMatchByEmail'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events.KeyCodes'); +goog.require('goog.testing.MockClock'); +goog.require('goog.testing.events'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); + + +var mockClock; +var root; +var component; +var tosCallback; +var privacyPolicyCallback; +var emailTestHelper = + new firebaseui.auth.ui.element.EmailTestHelper().registerTests(); +var infoBarTestHelper = + new firebaseui.auth.ui.element.InfoBarTestHelper().registerTests(); +var tosPpTestHelper = + new firebaseui.auth.ui.element.TosPpTestHelper().registerTests(); +var pageTestHelper = + new firebaseui.auth.ui.page.PageTestHelper().registerTests(); + + +function setUp() { + // Set up clock. + mockClock = new goog.testing.MockClock(); + mockClock.install(); + tosCallback = goog.bind( + firebaseui.auth.ui.element.TosPpTestHelper.prototype.onTosLinkClick, + tosPpTestHelper); + privacyPolicyCallback = goog.bind( + firebaseui.auth.ui.element.TosPpTestHelper.prototype.onPpLinkClick, + tosPpTestHelper); + root = goog.dom.createDom(goog.dom.TagName.DIV); + document.body.appendChild(root); + component = new firebaseui.auth.ui.page.ProviderMatchByEmail( + goog.bind( + firebaseui.auth.ui.element.EmailTestHelper.prototype.onEnter, + emailTestHelper), + tosCallback, + privacyPolicyCallback); + component.render(root); + emailTestHelper.setComponent(component); + infoBarTestHelper.setComponent(component); + tosPpTestHelper.setComponent(component); + // Reset previous state of tosPp helper. + tosPpTestHelper.resetState(); + pageTestHelper.setClock(mockClock).setComponent(component); +} + + +function tearDown() { + // Tear down clock. + mockClock.tick(Infinity); + mockClock.reset(); + component.dispose(); + goog.dom.removeNode(root); +} + + +function testInitialFocus() { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + return; + } + assertEquals( + component.getEmailElement(), + goog.dom.getActiveElement(document)); +} + + +function testEmail_onEnter() { + emailTestHelper.resetState(); + assertFalse(emailTestHelper.enterPressed_); + goog.testing.events.fireKeySequence( + component.getEmailElement(), goog.events.KeyCodes.ENTER); + assertTrue(emailTestHelper.enterPressed_); +} + + +function testNextButton_onClick() { + emailTestHelper.resetState(); + assertFalse(emailTestHelper.enterPressed_); + goog.testing.events.fireClickSequence(component.getSubmitElement()); + assertTrue(emailTestHelper.enterPressed_); +} + + +function testProviderMatchByEmail_fullMessage() { + tosPpTestHelper.assertFullMessage(tosCallback, privacyPolicyCallback); +} + + +function testProviderMatchByEmail_noTosPp() { + component.dispose(); + component = new firebaseui.auth.ui.page.ProviderMatchByEmail( + goog.bind( + firebaseui.auth.ui.element.EmailTestHelper.prototype.onEnter, + emailTestHelper)); + component.render(root); + tosPpTestHelper.setComponent(component); + tosPpTestHelper.assertFullMessage(null, null); +} + + +function testProviderMatchByEmail_pageEvents() { + // Run page event tests. + // Dispose previously created container since test must run before rendering + // the component in docoument. + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.ProviderMatchByEmail( + goog.bind( + firebaseui.auth.ui.element.EmailTestHelper.prototype.onEnter, + emailTestHelper)); + // Run all page helper tests. + pageTestHelper.runTests(component, root); +} + + +function testProviderMatchByEmail_getPageId() { + assertEquals('providerMatchByEmail', component.getPageId()); +} diff --git a/javascript/ui/page/providersignin.js b/javascript/ui/page/providersignin.js index d4754b47..74ed4fa8 100644 --- a/javascript/ui/page/providersignin.js +++ b/javascript/ui/page/providersignin.js @@ -12,76 +12,59 @@ * limitations under the License. */ -/** - * @fileoverview UI component for the list of supported identity providers. - */ - -goog.provide('firebaseui.auth.ui.page.ProviderSignIn'); - -goog.require('firebaseui.auth.soy2.page'); -goog.require('firebaseui.auth.ui.element.idps'); -goog.require('firebaseui.auth.ui.page.Base'); - +/** @fileoverview UI component for the list of supported identity providers. */ +goog.module('firebaseui.auth.ui.page.ProviderSignIn'); +goog.module.declareLegacyNamespace(); -/** - * UI component that displays a list of supported identity providers. - * @param {function(string)} onIdpClick Callback to invoke when the user clicks - * one IdP button. - * @param {!Array} providerConfigs The provider configs of the IdPs to - * display. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} - */ -firebaseui.auth.ui.page.ProviderSignIn = function( - onIdpClick, - providerConfigs, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.ProviderSignIn.base( - this, - 'constructor', - firebaseui.auth.soy2.page.providerSignIn, - { - providerConfigs: providerConfigs - }, - opt_domHelper, - 'providerSignIn', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onIdpClick_ = onIdpClick; -}; -goog.inherits(firebaseui.auth.ui.page.ProviderSignIn, - firebaseui.auth.ui.page.Base); - +const Base = goog.require('firebaseui.auth.ui.page.Base'); +const idps = goog.require('firebaseui.auth.ui.element.idps'); +const page = goog.require('firebaseui.auth.soy2.page'); -/** @override */ -firebaseui.auth.ui.page.ProviderSignIn.prototype.enterDocument = function() { - this.initIdpList(this.onIdpClick_); - firebaseui.auth.ui.page.ProviderSignIn.base(this, 'enterDocument'); -}; +/** UI component that displays a list of supported identity providers. */ +class ProviderSignIn extends Base { + /** + * @param {function(string)} onIdpClick Callback to invoke when the user + * clicks one IdP button. + * @param {!Array} providerConfigs The provider configs of the IdPs + * to display. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when + * the Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + */ + constructor( + onIdpClick, providerConfigs, opt_tosCallback, opt_privacyPolicyCallback, + domHelper = undefined) { + super( + page.providerSignIn, + {providerConfigs: providerConfigs}, domHelper, 'providerSignIn', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback, + }); + this.onIdpClick_ = onIdpClick; + } + /** @override */ + enterDocument() { + this.initIdpList(this.onIdpClick_); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.ProviderSignIn.prototype.disposeInternal = function() { - this.onIdpClick_ = null; - firebaseui.auth.ui.page.ProviderSignIn.base(this, 'disposeInternal'); -}; - + /** @override */ + disposeInternal() { + this.onIdpClick_ = null; + super.disposeInternal(); + } +} goog.mixin( - firebaseui.auth.ui.page.ProviderSignIn.prototype, - /** @lends {firebaseui.auth.ui.page.ProviderSignIn.prototype} */ + ProviderSignIn.prototype, + /** @lends {ProviderSignIn.prototype} */ { // For idps. - initIdpList: - firebaseui.auth.ui.element.idps.initIdpList + initIdpList: idps.initIdpList, }); + +exports = ProviderSignIn; diff --git a/javascript/ui/page/providersignin_test.js b/javascript/ui/page/providersignin_test.js index 2c6d4c96..3befc306 100644 --- a/javascript/ui/page/providersignin_test.js +++ b/javascript/ui/page/providersignin_test.js @@ -17,95 +17,95 @@ * providers. */ -goog.provide('firebaseui.auth.ui.page.ProviderSignInTest'); -goog.setTestOnly('firebaseui.auth.ui.page.ProviderSignInTest'); +goog.module('firebaseui.auth.ui.page.ProviderSignInTest'); +goog.setTestOnly(); -goog.require('firebaseui.auth.ui.element.IdpsTestHelper'); -goog.require('firebaseui.auth.ui.element.InfoBarTestHelper'); -goog.require('firebaseui.auth.ui.element.TosPpTestHelper'); -goog.require('firebaseui.auth.ui.page.PageTestHelper'); -goog.require('firebaseui.auth.ui.page.ProviderSignIn'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.testing.MockClock'); -goog.require('goog.testing.jsunit'); +const IdpsTestHelper = + goog.require('firebaseui.auth.ui.element.IdpsTestHelper'); +const InfoBarTestHelper = + goog.require('firebaseui.auth.ui.element.InfoBarTestHelper'); +const MockClock = goog.require('goog.testing.MockClock'); +const PageTestHelper = goog.require('firebaseui.auth.ui.page.PageTestHelper'); +const ProviderSignIn = goog.require('firebaseui.auth.ui.page.ProviderSignIn'); +const TagName = goog.require('goog.dom.TagName'); +const TosPpTestHelper = + goog.require('firebaseui.auth.ui.element.TosPpTestHelper'); +const dom = goog.require('goog.dom'); +const testSuite = goog.require('goog.testing.testSuite'); +let mockClock; +let root; +let component; +const idpsTestHelper = + new IdpsTestHelper().registerTests(); +const infoBarTestHelper = + new InfoBarTestHelper().registerTests(); +const tosPpTestHelper = + new TosPpTestHelper().registerTests(); +const pageTestHelper = + new PageTestHelper().registerTests(); -var mockClock; -var root; -var component; -var idpsTestHelper = - new firebaseui.auth.ui.element.IdpsTestHelper().registerTests(); -var infoBarTestHelper = - new firebaseui.auth.ui.element.InfoBarTestHelper().registerTests(); -var tosPpTestHelper = - new firebaseui.auth.ui.element.TosPpTestHelper().registerTests(); -var pageTestHelper = - new firebaseui.auth.ui.page.PageTestHelper().registerTests(); +testSuite({ + setUp() { + // Set up clock. + mockClock = new MockClock(); + mockClock.install(); + root = dom.createDom(TagName.DIV); + document.body.appendChild(root); + component = new ProviderSignIn( + goog.bind( + IdpsTestHelper.prototype.onClick, + idpsTestHelper), + [{ + providerId: 'google.com', + }, + { + providerId: 'password', + }], + goog.bind( + TosPpTestHelper.prototype.onTosLinkClick, + tosPpTestHelper), + goog.bind( + TosPpTestHelper.prototype.onPpLinkClick, + tosPpTestHelper)); + component.render(root); + idpsTestHelper.setComponent(component); + infoBarTestHelper.setComponent(component); + tosPpTestHelper.setComponent(component); + // Reset previous state of tosPp helper. + tosPpTestHelper.resetState(); + pageTestHelper.setClock(mockClock).setComponent(component); + }, + tearDown() { + // Tear down clock. + mockClock.tick(Infinity); + mockClock.reset(); + component.dispose(); + dom.removeNode(root); + }, -function setUp() { - // Set up clock. - mockClock = new goog.testing.MockClock(); - mockClock.install(); - root = goog.dom.createDom(goog.dom.TagName.DIV); - document.body.appendChild(root); - component = new firebaseui.auth.ui.page.ProviderSignIn( - goog.bind( - firebaseui.auth.ui.element.IdpsTestHelper.prototype.onClick, - idpsTestHelper), - [{ - providerId: 'google.com' - }, - { - providerId: 'password' - }], - goog.bind( - firebaseui.auth.ui.element.TosPpTestHelper.prototype.onTosLinkClick, - tosPpTestHelper), - goog.bind( - firebaseui.auth.ui.element.TosPpTestHelper.prototype.onPpLinkClick, - tosPpTestHelper)); - component.render(root); - idpsTestHelper.setComponent(component); - infoBarTestHelper.setComponent(component); - tosPpTestHelper.setComponent(component); - // Reset previous state of tosPp helper. - tosPpTestHelper.resetState(); - pageTestHelper.setClock(mockClock).setComponent(component); -} + testProviderSignIn_pageEvents() { + // Run page event tests. + // Dispose previously created container since test must run before rendering + // the component in docoument. + component.dispose(); + // Initialize component. + component = new ProviderSignIn( + goog.bind( + IdpsTestHelper.prototype.onClick, + idpsTestHelper), + [{ + providerId: 'facebook.com', + }, + { + providerId: 'password', + }]); + // Run all page helper tests. + pageTestHelper.runTests(component, root); + }, - -function tearDown() { - // Tear down clock. - mockClock.tick(Infinity); - mockClock.reset(); - component.dispose(); - goog.dom.removeNode(root); -} - - -function testProviderSignIn_pageEvents() { - // Run page event tests. - // Dispose previously created container since test must run before rendering - // the component in docoument. - component.dispose(); - // Initialize component. - component = new firebaseui.auth.ui.page.ProviderSignIn( - goog.bind( - firebaseui.auth.ui.element.IdpsTestHelper.prototype.onClick, - idpsTestHelper), - [{ - providerId: 'facebook.com' - }, - { - providerId: 'password' - }]); - // Run all page helper tests. - pageTestHelper.runTests(component, root); -} - - -function testProviderSignIn_getPageId() { - assertEquals('providerSignIn', component.getPageId()); -} + testProviderSignIn_getPageId() { + assertEquals('providerSignIn', component.getPageId()); + }, +}); diff --git a/javascript/ui/page/revertsecondfactoradditionsuccess.js b/javascript/ui/page/revertsecondfactoradditionsuccess.js new file mode 100644 index 00000000..3a0077e2 --- /dev/null +++ b/javascript/ui/page/revertsecondfactoradditionsuccess.js @@ -0,0 +1,101 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview UI component for the revert second factor addition success + * page. + */ + +goog.provide('firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess'); + +goog.require('firebaseui.auth.soy2.page'); +goog.require('firebaseui.auth.ui.element'); +goog.require('firebaseui.auth.ui.element.form'); +goog.require('firebaseui.auth.ui.page.Base'); + + +/** + * Revert second factor addition success UI component. + */ +firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} factorId The factor ID of the second factor. + * @param {function()} onResetPasswordClick Callback to invoke when the reset + * password link is clicked. + * @param {?string=} phoneNumber The second factor phone number. + * @param {function(): undefined=} onContinueClick Callback to invoke when the + * continue button is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + */ + constructor(factorId, onResetPasswordClick, phoneNumber, onContinueClick, + domHelper) { + super( + firebaseui.auth.soy2.page.revertSecondFactorAdditionSuccess, + { + factorId: factorId, + phoneNumber: phoneNumber || null, + allowContinue: !!onContinueClick + }, + domHelper, + 'revertSecondFactorAdditionSuccess'); + this.onResetPasswordClick_ = onResetPasswordClick; + this.onContinueClick_ = onContinueClick || null; + } + + /** @override */ + enterDocument() { + // Handle action event for 'change your password immediately' link. + firebaseui.auth.ui.element.listenForActionEvent( + this, this.getResetPasswordElement(), (e) => { + this.onResetPasswordClick_(); + }); + if (this.onContinueClick_) { + this.initFormElement(this.onContinueClick_); + this.getSubmitElement().focus(); + } + super.enterDocument(); + } + + /** @override */ + disposeInternal() { + this.onContinueClick_ = null; + this.onResetPasswordClick_ = null; + super.disposeInternal(); + } + + /** + * @return {?Element} The reset password link. + */ + getResetPasswordElement() { + return this.getElementByClass('firebaseui-id-reset-password-link'); + } +}; + + +goog.mixin( + firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess.prototype, + /** + * @lends + * {firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess.prototype} + */ + { + // For form. + getSubmitElement: + firebaseui.auth.ui.element.form.getSubmitElement, + getSecondaryLinkElement: + firebaseui.auth.ui.element.form.getSecondaryLinkElement, + initFormElement: + firebaseui.auth.ui.element.form.initFormElement + }); diff --git a/javascript/ui/page/revertsecondfactoradditionsuccess_test.js b/javascript/ui/page/revertsecondfactoradditionsuccess_test.js new file mode 100644 index 00000000..4a9b7940 --- /dev/null +++ b/javascript/ui/page/revertsecondfactoradditionsuccess_test.js @@ -0,0 +1,146 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Tests for the revert second factor addition success page. + */ + +goog.provide('firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccessTest'); +goog.setTestOnly( + 'firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccessTest'); + +goog.require('firebaseui.auth.ui.element.FormTestHelper'); +goog.require('firebaseui.auth.ui.element.InfoBarTestHelper'); +goog.require('firebaseui.auth.ui.page.PageTestHelper'); +goog.require('firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.testing.MockClock'); +goog.require('goog.testing.events'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); + + +let mockClock; +let root; +let component; +const formTestHelper = new firebaseui.auth.ui.element.FormTestHelper(). + excludeTests('testOnLinkClick_', 'testOnLinkEnter_'). + registerTests(); +const infoBarTestHelper = + new firebaseui.auth.ui.element.InfoBarTestHelper().registerTests(); +const pageTestHelper = + new firebaseui.auth.ui.page.PageTestHelper().registerTests(); + +let updateClicked; +let onClick; + + +function setUp() { + // Set up clock. + mockClock = new goog.testing.MockClock(); + mockClock.install(); + updateClicked = false; + onClick = () => { + updateClicked = true; + }; + root = goog.dom.createDom(goog.dom.TagName.DIV); + document.body.appendChild(root); + component = new firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess( + 'phone', + onClick, + '+*******1234', + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + component.render(root); + formTestHelper.setComponent(component); + // Reset previous state of form helper. + formTestHelper.resetState(); + infoBarTestHelper.setComponent(component); + pageTestHelper.setClock(mockClock).setComponent(component); +} + + +function tearDown() { + // Tear down clock. + mockClock.tick(Infinity); + mockClock.reset(); + component.dispose(); + goog.dom.removeNode(root); +} + + +function testInitialFocus() { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + return; + } + assertEquals( + component.getSubmitElement(), + goog.dom.getActiveElement(document)); +} + + +function testRevertSecondFactorAdditionSuccess_resetPassword() { + const link = component.getResetPasswordElement(); + assertNotNull(link); + assertFalse(updateClicked); + goog.testing.events.fireClickSequence(link); + assertTrue(updateClicked); +} + + +function testRevertSecondFactorAdditionSuccess_pageEvents() { + // Run page event tests. + // Dispose previously created container since test must run before rendering + // the component in document. + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess( + 'phone', + onClick, + '+*******1234', + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + // Run all page helper tests. + pageTestHelper.runTests(component, root); +} + + +function testRevertSecondFactorAdditionSuccess_unknownFactorId() { + // Run page event tests. + // Dispose previously created container since test must run before rendering + // the component in document. + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.RevertSecondFactorAdditionSuccess( + 'unknown', + onClick, + undefined, + goog.bind( + firebaseui.auth.ui.element.FormTestHelper.prototype.onSubmit, + formTestHelper)); + component.render(root); + const link = component.getResetPasswordElement(); + assertNotNull(link); + assertFalse(updateClicked); + goog.testing.events.fireClickSequence(link); + assertTrue(updateClicked); +} + + +function testRevertSecondFactorAdditionSuccess_getPageId() { + assertEquals('revertSecondFactorAdditionSuccess', component.getPageId()); +} diff --git a/javascript/ui/page/selecttenant.js b/javascript/ui/page/selecttenant.js new file mode 100644 index 00000000..83649ee9 --- /dev/null +++ b/javascript/ui/page/selecttenant.js @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @fileoverview UI component for the list of tenants to select from. */ + +goog.module('firebaseui.auth.ui.page.SelectTenant'); +goog.module.declareLegacyNamespace(); + +const Base = goog.require('firebaseui.auth.ui.page.Base'); +const dataset = goog.require('goog.dom.dataset'); +const element = goog.require('firebaseui.auth.ui.element'); +const page = goog.require('firebaseui.auth.soy2.page'); + +/** UI component that displays a list of tenants to select from. */ +class SelectTenant extends Base { + /** + * @param {function(?string)} onTenantClick Callback to invoke when the user + * clicks one tenant selection button. + * @param {!Array} tenantConfigs The button configs of the tenants + * to display. + * @param {?function()=} tosCallback Optional callback to invoke when the + * ToS link is clicked. + * @param {?function()=} privacyPolicyCallback Optional callback to invoke + * when the Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} domHelper Optional DOM helper. + */ + constructor( + onTenantClick, tenantConfigs, tosCallback = undefined, + privacyPolicyCallback = undefined, domHelper = undefined) { + super( + page.selectTenant, + { + tenantConfigs: tenantConfigs, + }, + domHelper, + 'selectTenant', + { + tosCallback: tosCallback, + privacyPolicyCallback: privacyPolicyCallback, + }); + this.onTenantClick_ = onTenantClick; + } + + /** @override */ + enterDocument() { + this.initTenantList_(this.onTenantClick_); + super.enterDocument(); + } + + /** @override */ + disposeInternal() { + this.onTenantClick_ = null; + super.disposeInternal(); + } + + /** + * Initializes tenant selection menu buttons. + * @param {function(?string)} onClick Callback to invoke when the user clicks + * one tenant selection button. + * @private + */ + initTenantList_(onClick) { + const buttons = + this.getElementsByClass('firebaseui-id-tenant-selection-button'); + const cb = (tenantId, e) => { + onClick(tenantId); + }; + for (let i = 0; i < buttons.length; i++) { + const button = buttons[i]; + const tenantId = dataset.get(button, 'tenantId'); + element.listenForActionEvent(this, button, goog.partial(cb, tenantId)); + } + } +} + +exports = SelectTenant; diff --git a/javascript/ui/page/selecttenant_test.js b/javascript/ui/page/selecttenant_test.js new file mode 100644 index 00000000..056a8551 --- /dev/null +++ b/javascript/ui/page/selecttenant_test.js @@ -0,0 +1,158 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Tests for the page displaying a list of tenants to select from. + */ + +goog.module('firebaseui.auth.ui.page.SelectTenantTest'); +goog.setTestOnly(); + + +const KeyCodes = goog.require('goog.events.KeyCodes'); +const MockClock = goog.require('goog.testing.MockClock'); +const PageTestHelper = goog.require('firebaseui.auth.ui.page.PageTestHelper'); +const SelectTenant = goog.require('firebaseui.auth.ui.page.SelectTenant'); +const TagName = goog.require('goog.dom.TagName'); +const TosPpTestHelper = + goog.require('firebaseui.auth.ui.element.TosPpTestHelper'); +const dom = goog.require('goog.dom'); +const events = goog.require('goog.testing.events'); +const testSuite = goog.require('goog.testing.testSuite'); + +let mockClock; +let root; +let component; +const tosPpTestHelper = new TosPpTestHelper().registerTests(); +const pageTestHelper = new PageTestHelper().registerTests(); + +// The tenant button click callback. +let onTenantSelect; +let selectedTenant; + +testSuite({ + setUp() { + // Set up clock. + mockClock = new MockClock(); + mockClock.install(); + root = dom.createDom(TagName.DIV); + document.body.appendChild(root); + // Set up the tenant button click callback. + selectedTenant = null; + onTenantSelect = (tenantId) => { + selectedTenant = tenantId; + }; + + component = new SelectTenant( + onTenantSelect, + [{ + tenantId: 'TENANT_ID', + displayName: 'Contractor A', + buttonColor: '#FFB6C1', + iconUrl: 'icon-url', + }, + { + tenantId: null, + displayName: 'ACME', + buttonColor: '#53B2BF', + iconUrl: 'icon-url', + }], + goog.bind( + TosPpTestHelper.prototype.onTosLinkClick, + tosPpTestHelper), + goog.bind( + TosPpTestHelper.prototype.onPpLinkClick, + tosPpTestHelper)); + component.render(root); + tosPpTestHelper.setComponent(component); + // Reset previous state of tosPp helper. + tosPpTestHelper.resetState(); + pageTestHelper.setClock(mockClock).setComponent(component); + }, + + tearDown() { + // Tear down clock. + mockClock.tick(Infinity); + mockClock.reset(); + component.dispose(); + dom.removeNode(root); + }, + + testSelectTenant_onClick_tenant() { + // Test that the correct tenant ID is passed to the callback on button + // clicked. + const tenantButtons = + component.getElementsByClass('firebaseui-id-tenant-selection-button'); + events.fireClickSequence(tenantButtons[0]); + assertEquals('TENANT_ID', selectedTenant); + }, + + testSelectTenant_onEnter_tenant() { + // Test that the correct tenant ID is passed to the callback on enter + // pressed. + const tenantButtons = + component.getElementsByClass('firebaseui-id-tenant-selection-button'); + events.fireKeySequence(tenantButtons[0], KeyCodes.ENTER); + assertEquals('TENANT_ID', selectedTenant); + }, + + testSelectTenant_onClick_topLevelProject() { + // Test that null tenant ID is passed to the callback on button clicked + // for top-level project. + const tenantButtons = + component.getElementsByClass('firebaseui-id-tenant-selection-button'); + events.fireClickSequence(tenantButtons[1]); + assertNull(selectedTenant); + }, + + testSelectTenant_onEnter_topLevelProject() { + // Test that null tenant ID is passed to the callback on enter pressed + // for top-level project. + const tenantButtons = + component.getElementsByClass('firebaseui-id-tenant-selection-button'); + events.fireKeySequence(tenantButtons[1], KeyCodes.ENTER); + assertNull(selectedTenant); + }, + + testSelectTenant_pageEvents() { + component.dispose(); + // Initialize component. + component = new SelectTenant( + onTenantSelect, + [{ + tenantId: 'TENANT_ID', + displayName: 'Contractor A', + buttonColor: '#FFB6C1', + iconUrl: 'icon-url', + }, + { + tenantId: null, + displayName: 'ACME', + buttonColor: '#53B2BF', + iconUrl: 'icon-url', + }], + goog.bind( + TosPpTestHelper.prototype.onTosLinkClick, + tosPpTestHelper), + goog.bind( + TosPpTestHelper.prototype.onPpLinkClick, + tosPpTestHelper)); + // Run all page helper tests. + pageTestHelper.runTests(component, root); + }, + + testSelectTenant_getPageId() { + assertEquals('selectTenant', component.getPageId()); + }, +}); diff --git a/javascript/ui/page/signin.js b/javascript/ui/page/signin.js index 0ac6bd45..1f999f2a 100644 --- a/javascript/ui/page/signin.js +++ b/javascript/ui/page/signin.js @@ -26,80 +26,67 @@ goog.require('firebaseui.auth.ui.page.Base'); goog.require('goog.dom.selection'); - /** * UI component for the user to enter their email. - * @param {function()} onEmailEnter Callback to invoke when enter key (or its - * equivalent) is detected. - * @param {?function()=} opt_onCancelClick Callback to invoke when cancel button - * is clicked. - * @param {string=} opt_email The email to prefill. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full - * message of Term of Service and Privacy Policy. - * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.SignIn = function( - onEmailEnter, - opt_onCancelClick, - opt_email, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_displayFullTosPpMessage, - opt_domHelper) { - firebaseui.auth.ui.page.SignIn.base( - this, - 'constructor', - firebaseui.auth.soy2.page.signIn, - { - email: opt_email, - displayCancelButton: !!opt_onCancelClick, - displayFullTosPpMessage: !!opt_displayFullTosPpMessage - }, - opt_domHelper, - 'signIn', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onEmailEnter_ = onEmailEnter; - this.onCancelClick_ = opt_onCancelClick; -}; -goog.inherits(firebaseui.auth.ui.page.SignIn, firebaseui.auth.ui.page.Base); +firebaseui.auth.ui.page.SignIn = class extends firebaseui.auth.ui.page.Base { + /** + * @param {function()} onEmailEnter Callback to invoke when enter key (or its + * equivalent) is detected. + * @param {?function()=} opt_onCancelClick Callback to invoke when cancel + * button is clicked. + * @param {string=} opt_email The email to prefill. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {boolean=} opt_displayFullTosPpMessage Whether to display the full + * message of Term of Service and Privacy Policy. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + onEmailEnter, opt_onCancelClick, opt_email, opt_tosCallback, + opt_privacyPolicyCallback, opt_displayFullTosPpMessage, opt_domHelper) { + super( + firebaseui.auth.soy2.page.signIn, { + email: opt_email, + displayCancelButton: !!opt_onCancelClick, + displayFullTosPpMessage: !!opt_displayFullTosPpMessage + }, + opt_domHelper, 'signIn', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onEmailEnter_ = onEmailEnter; + this.onCancelClick_ = opt_onCancelClick; + } + /** @override */ + enterDocument() { + this.initEmailElement(this.onEmailEnter_); + // Handle a click on the submit button or cancel button. + this.initFormElement(this.onEmailEnter_, this.onCancelClick_ || undefined); + this.setupFocus_(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.SignIn.prototype.enterDocument = function() { - this.initEmailElement(this.onEmailEnter_); - // Handle a click on the submit button or cancel button. - this.initFormElement(this.onEmailEnter_, this.onCancelClick_ || undefined); - this.setupFocus_(); - firebaseui.auth.ui.page.SignIn.base(this, 'enterDocument'); -}; + /** @override */ + disposeInternal() { + this.onEmailEnter_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } - -/** @override */ -firebaseui.auth.ui.page.SignIn.prototype.disposeInternal = function() { - this.onEmailEnter_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.SignIn.base(this, 'disposeInternal'); -}; - - -/** - * Sets up the focus order and auto focus. - * @private - */ -firebaseui.auth.ui.page.SignIn.prototype.setupFocus_ = function() { - // Auto focus the email input and put the cursor at the end. - this.getEmailElement().focus(); - goog.dom.selection.setCursorPosition( - this.getEmailElement(), (this.getEmailElement().value || '').length); + /** + * Sets up the focus order and auto focus. + * @private + */ + setupFocus_() { + // Auto focus the email input and put the cursor at the end. + this.getEmailElement().focus(); + goog.dom.selection.setCursorPosition( + this.getEmailElement(), (this.getEmailElement().value || '').length); + } }; diff --git a/javascript/ui/page/spinner.js b/javascript/ui/page/spinner.js new file mode 100644 index 00000000..8c7b4555 --- /dev/null +++ b/javascript/ui/page/spinner.js @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview UI component for the spinner page. + */ + +goog.provide('firebaseui.auth.ui.page.Spinner'); + +goog.require('firebaseui.auth.soy2.page'); +goog.require('firebaseui.auth.ui.page.Base'); + + +/** + * Spinner page UI componenet. + */ +firebaseui.auth.ui.page.Spinner = class extends firebaseui.auth.ui.page.Base { + /** + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor(opt_domHelper) { + super( + firebaseui.auth.soy2.page.spinner, undefined, opt_domHelper, 'spinner'); + } +}; diff --git a/javascript/ui/page/spinner_test.js b/javascript/ui/page/spinner_test.js new file mode 100644 index 00000000..0a1b9fc0 --- /dev/null +++ b/javascript/ui/page/spinner_test.js @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Tests for the spinner page. + */ + +goog.provide('firebaseui.auth.ui.page.SpinnerTest'); +goog.setTestOnly('firebaseui.auth.ui.page.SpinnerTest'); + +goog.require('firebaseui.auth.ui.page.PageTestHelper'); +goog.require('firebaseui.auth.ui.page.Spinner'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.testing.MockClock'); +goog.require('goog.testing.jsunit'); + + +var mockClock; +var root; +var component; +var pageTestHelper = new firebaseui.auth.ui.page.PageTestHelper() + // Spinner already has a progress bar. No need to use + // executePromiseRequest. + .excludeTests('testExecutePromiseRequest_') + .registerTests(); + + +function setUp() { + // Set up clock. + mockClock = new goog.testing.MockClock(); + mockClock.install(); + root = goog.dom.createDom(goog.dom.TagName.DIV); + document.body.appendChild(root); + component = new firebaseui.auth.ui.page.Spinner(); + component.render(root); + pageTestHelper.setClock(mockClock).setComponent(component); +} + + +function tearDown() { + // Tear down clock. + mockClock.tick(Infinity); + mockClock.reset(); + component.dispose(); + goog.dom.removeNode(root); +} + + +function testSpinner_getPageId() { + assertEquals('spinner', component.getPageId()); +} + + +function testSpinner_pageEvents() { + // Run page event tests. + // Dispose previously created container since test must run before rendering + // the component in document. + component.dispose(); + // Initialize component. + component = new firebaseui.auth.ui.page.Spinner(); + // Run all page helper tests. + pageTestHelper.runTests(component, root); +} diff --git a/javascript/ui/page/unsupportedprovider.js b/javascript/ui/page/unsupportedprovider.js index beca22a3..9e309b91 100644 --- a/javascript/ui/page/unsupportedprovider.js +++ b/javascript/ui/page/unsupportedprovider.js @@ -26,60 +26,47 @@ goog.require('firebaseui.auth.ui.page.Base'); /** * Unsupported provider UI component. - * @param {string} email The user's email. - * @param {function()} onSubmitClick Callback to invoke when the submit button - * is clicked. - * @param {function()} onCancelClick Callback to invoke when the cancel button - * is clicked. - * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link - * is clicked. - * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the - * Privacy Policy link is clicked. - * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. - * @constructor - * @extends {firebaseui.auth.ui.page.Base} */ -firebaseui.auth.ui.page.UnsupportedProvider = function( - email, - onSubmitClick, - onCancelClick, - opt_tosCallback, - opt_privacyPolicyCallback, - opt_domHelper) { - firebaseui.auth.ui.page.UnsupportedProvider.base( - this, - 'constructor', - firebaseui.auth.soy2.page.unsupportedProvider, - { - email: email - }, - opt_domHelper, - 'unsupportedProvider', - { - tosCallback: opt_tosCallback, - privacyPolicyCallback: opt_privacyPolicyCallback - }); - this.onSubmitClick_ = onSubmitClick; - this.onCancelClick_ = onCancelClick; -}; -goog.inherits(firebaseui.auth.ui.page.UnsupportedProvider, - firebaseui.auth.ui.page.Base); - - -/** @override */ -firebaseui.auth.ui.page.UnsupportedProvider.prototype.enterDocument = function() { - this.initFormElement(this.onSubmitClick_, this.onCancelClick_); - this.getSubmitElement().focus(); - firebaseui.auth.ui.page.UnsupportedProvider.base(this, 'enterDocument'); -}; +firebaseui.auth.ui.page.UnsupportedProvider = + class extends firebaseui.auth.ui.page.Base { + /** + * @param {string} email The user's email. + * @param {function()} onSubmitClick Callback to invoke when the submit button + * is clicked. + * @param {function()} onCancelClick Callback to invoke when the cancel button + * is clicked. + * @param {?function()=} opt_tosCallback Callback to invoke when the ToS link + * is clicked. + * @param {?function()=} opt_privacyPolicyCallback Callback to invoke when the + * Privacy Policy link is clicked. + * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + */ + constructor( + email, onSubmitClick, onCancelClick, opt_tosCallback, + opt_privacyPolicyCallback, opt_domHelper) { + super( + firebaseui.auth.soy2.page.unsupportedProvider, {email: email}, + opt_domHelper, 'unsupportedProvider', { + tosCallback: opt_tosCallback, + privacyPolicyCallback: opt_privacyPolicyCallback + }); + this.onSubmitClick_ = onSubmitClick; + this.onCancelClick_ = onCancelClick; + } + /** @override */ + enterDocument() { + this.initFormElement(this.onSubmitClick_, this.onCancelClick_); + this.getSubmitElement().focus(); + super.enterDocument(); + } -/** @override */ -firebaseui.auth.ui.page.UnsupportedProvider.prototype.disposeInternal = - function() { - this.onSubmitClick_ = null; - this.onCancelClick_ = null; - firebaseui.auth.ui.page.UnsupportedProvider.base(this, 'disposeInternal'); + /** @override */ + disposeInternal() { + this.onSubmitClick_ = null; + this.onCancelClick_ = null; + super.disposeInternal(); + } }; diff --git a/javascript/utils/acclient.js b/javascript/utils/acclient.js index 84d3d068..3890770e 100644 --- a/javascript/utils/acclient.js +++ b/javascript/utils/acclient.js @@ -25,7 +25,7 @@ goog.require('goog.asserts'); /** - * @type {accountchooser.Api} + * @type {?accountchooser.Api} * @private */ firebaseui.auth.acClient.api_ = null; @@ -215,130 +215,127 @@ firebaseui.auth.acClient.tryStoreAccount = * A dummy accountchooser.com API implmentation which is used if * accountchooser.com is not available, for instance, the user agent doesn't * support SNI. - * - * @param {Object} config The configuration. - * @constructor * @implements {accountchooser.Api} */ -firebaseui.auth.acClient.DummyApi = function(config) { - this.config_ = config; - this.config_['callbacks'] = this.config_['callbacks'] || {}; -}; +firebaseui.auth.acClient.DummyApi = class { + /** + * @param {Object} config The configuration. + */ + constructor(config) { + this.config_ = config; + this.config_['callbacks'] = this.config_['callbacks'] || {}; + } -/** - * Triggers the onEmpty callback. - */ -firebaseui.auth.acClient.DummyApi.prototype.fireOnEmpty = function() { - if (goog.isFunction(this.config_['callbacks']['empty'])) { - this.config_['callbacks']['empty'](); + /** + * Triggers the onEmpty callback. + */ + fireOnEmpty() { + if (goog.isFunction(this.config_['callbacks']['empty'])) { + this.config_['callbacks']['empty'](); + } } -}; -/** - * The accountchooser.com service unavailable error. - * @const {Object} - * @private - */ -firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_ = { - 'code': -32000, - 'message': 'Service unavailable', - 'data': 'Service is unavailable.' -}; + /** + * Stores the accounts. The callback is always invoked with a service + * unavailable error. + * + * @param {Array} accounts The accounts to store. + * @param {Object=} opt_config The optional client configuration. + */ + store(accounts, opt_config) { + if (goog.isFunction(this.config_['callbacks']['store'])) { + this.config_['callbacks']['store']( + undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + } + } -/** - * Stores the accounts. The callback is always invoked with a service - * unavailable error. - * - * @param {Array} accounts The accounts to store. - * @param {Object=} opt_config The optional client configuration. - */ -firebaseui.auth.acClient.DummyApi.prototype.store = - function(accounts, opt_config) { - if (goog.isFunction(this.config_['callbacks']['store'])) { - this.config_['callbacks']['store']( - undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + /** + * Selects account. The callback is always invoked with a service unavailable + * error. + * + * @param {Array} accounts The local accounts to select. + * @param {Object=} opt_config The optional client configuration. + */ + select(accounts, opt_config) { + if (goog.isFunction(this.config_['callbacks']['select'])) { + this.config_['callbacks']['select']( + undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + } } -}; -/** - * Selects account. The callback is always invoked with a service unavailable - * error. - * - * @param {Array} accounts The local accounts to select. - * @param {Object=} opt_config The optional client configuration. - */ -firebaseui.auth.acClient.DummyApi.prototype.select = - function(accounts, opt_config) { - if (goog.isFunction(this.config_['callbacks']['select'])) { - this.config_['callbacks']['select']( - undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + /** + * Updates the account. The callback is always invoked with a service + * unavailable error. + * + * @param {Object} account The account to update. + * @param {Object=} opt_config The optional client configuration. + */ + update(account, opt_config) { + if (goog.isFunction(this.config_['callbacks']['update'])) { + this.config_['callbacks']['update']( + undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + } } -}; -/** - * Updates the account. The callback is always invoked with a service - * unavailable error. - * - * @param {Object} account The account to update. - * @param {Object=} opt_config The optional client configuration. - */ -firebaseui.auth.acClient.DummyApi.prototype.update = - function(account, opt_config) { - if (goog.isFunction(this.config_['callbacks']['update'])) { - this.config_['callbacks']['update']( - undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + /** + * Checkes if accountchooser.com is disabled. The callback is always invoked + * with a `true`. + * + * @param {function(boolean=, Object=)} callback The callback function. + */ + checkDisabled(callback) { + callback(true); } -}; -/** - * Checkes if accountchooser.com is disabled. The callback is always invoked - * with a `true`. - * - * @param {function(boolean=, Object=)} callback The callback function. - */ -firebaseui.auth.acClient.DummyApi.prototype.checkDisabled = function(callback) { - callback(true); -}; + /** + * Checkes if the accountchooser.com is empty. The callback is always invoked + * with a service unavailable error. + * + * @param {function(boolean=, Object=)} callback The callback function. + */ + checkEmpty(callback) { + callback(undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + } -/** - * Checkes if the accountchooser.com is empty. The callback is always invoked - * with a service unavailable error. - * - * @param {function(boolean=, Object=)} callback The callback function. - */ -firebaseui.auth.acClient.DummyApi.prototype.checkEmpty = function(callback) { - callback(undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); -}; + /** + * Checkes if the account is in accountchooser.com. The callback is always + * invoked with a service unavailable error. + * + * @param {Object} account The account to check. + * @param {function(boolean=, Object=)} callback The callback function. + */ + checkAccountExist(account, callback) { + callback(undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + } -/** - * Checkes if the account is in accountchooser.com. The callback is always - * invoked with a service unavailable error. - * - * @param {Object} account The account to check. - * @param {function(boolean=, Object=)} callback The callback function. - */ -firebaseui.auth.acClient.DummyApi.prototype.checkAccountExist = - function(account, callback) { - callback(undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + /** + * Checkes if the account should be updated. The callback is always invoked + * with a service unavailable error. + * + * @param {Object} account The account to check. + * @param {function(boolean=, Object=)} callback The callback function. + */ + checkShouldUpdate(account, callback) { + callback(undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); + } }; /** - * Checkes if the account should be updated. The callback is always invoked with - * a service unavailable error. - * - * @param {Object} account The account to check. - * @param {function(boolean=, Object=)} callback The callback function. + * The accountchooser.com service unavailable error. + * @const {Object} + * @private */ -firebaseui.auth.acClient.DummyApi.prototype.checkShouldUpdate = - function(account, callback) { - callback(undefined, firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_); +firebaseui.auth.acClient.DummyApi.UNAVAILABLE_ERROR_ = { + 'code': -32000, + 'message': 'Service unavailable', + 'data': 'Service is unavailable.', }; diff --git a/javascript/utils/account.js b/javascript/utils/account.js index 18fde0a1..24b713df 100644 --- a/javascript/utils/account.js +++ b/javascript/utils/account.js @@ -19,81 +19,78 @@ goog.provide('firebaseui.auth.Account'); - -/** - * @param {string} email The email address. - * @param {?string=} opt_displayName The display name, defaulting to null. - * @param {?string=} opt_photoUrl The profile photo URL, defaulting to null. - * @param {?string=} opt_providerId The identity provider ID, defaulting to - * null. - * @constructor - */ -firebaseui.auth.Account = function( - email, opt_displayName, opt_photoUrl, opt_providerId) { - this.email_ = email; - this.displayName_ = opt_displayName || null; - this.photoUrl_ = opt_photoUrl || null; - this.providerId_ = opt_providerId || null; -}; +firebaseui.auth.Account = class { + /** + * @param {string} email The email address. + * @param {?string=} displayName The display name, defaulting to null. + * @param {?string=} photoUrl The profile photo URL, defaulting to null. + * @param {?string=} providerId The identity provider ID, defaulting to + * null. + */ + constructor(email, displayName, photoUrl, providerId) { + this.email_ = email; + this.displayName_ = displayName || null; + this.photoUrl_ = photoUrl || null; + this.providerId_ = providerId || null; + } -/** @return {string} The email address. */ -firebaseui.auth.Account.prototype.getEmail = function() { - return this.email_; -}; + /** @return {string} The email address. */ + getEmail() { + return this.email_; + } -/** @return {?string} The displayName. */ -firebaseui.auth.Account.prototype.getDisplayName = function() { - return this.displayName_ || null; -}; + /** @return {?string} The displayName. */ + getDisplayName() { + return this.displayName_ || null; + } -/** @return {?string} The profile photo URL. */ -firebaseui.auth.Account.prototype.getPhotoUrl = function() { - return this.photoUrl_ || null; -}; + /** @return {?string} The profile photo URL. */ + getPhotoUrl() { + return this.photoUrl_ || null; + } -/** @return {?string} The identity provider ID. */ -firebaseui.auth.Account.prototype.getProviderId = function() { - return this.providerId_ || null; -}; + /** @return {?string} The identity provider ID. */ + getProviderId() { + return this.providerId_ || null; + } -/** - * @return {{ - * email: string, - * displayName: (null|string|undefined), - * photoUrl: (null|string|undefined), - * providerId: (null|string|undefined) - * }} The plain object representation for the account. - */ -firebaseui.auth.Account.prototype.toPlainObject = function() { - return { - 'email': this.email_, - 'displayName': this.displayName_, - 'photoUrl': this.photoUrl_, - 'providerId': this.providerId_ - }; -}; + /** + * @return {{ + * email: string, + * displayName: ?string, + * photoUrl: ?string, + * providerId: ?string + * }} The plain object representation for the account. + */ + toPlainObject() { + return { + 'email': this.email_, + 'displayName': this.displayName_, + 'photoUrl': this.photoUrl_, + 'providerId': this.providerId_ + }; + } -/** - * Converts a plain account object to `firebaseui.auth.Account`. - * @param {!Object} account The plain object representation of an account. - * @return {firebaseui.auth.Account} The account. - */ -firebaseui.auth.Account.fromPlainObject = function(account) { - // TODO: Remove this filter once accountchooser.com supports non-email - // accounts. We will also have to figure out how to choose a sign-in method, - // since fetchProvidersForEmail won't work. - if (account['email']) { - return new firebaseui.auth.Account( - account['email'], - account['displayName'], - account['photoUrl'], - account['providerId']); + /** + * Converts a plain account object to `firebaseui.auth.Account`. + * @param {!Object} account The plain object representation of an account. + * @return {?firebaseui.auth.Account} The account. + */ + static fromPlainObject(account) { + // TODO: Remove this filter once accountchooser.com supports non-email + // accounts. We will also have to figure out how to choose a sign-in method, + // since fetchProvidersForEmail won't work. + if (account['email']) { + return new firebaseui.auth.Account( + account['email'], account['displayName'], account['photoUrl'], + account['providerId']); + } + return null; } - return null; }; diff --git a/javascript/utils/actioncodeurlbuilder.js b/javascript/utils/actioncodeurlbuilder.js index 026f5200..10a05ca3 100644 --- a/javascript/utils/actioncodeurlbuilder.js +++ b/javascript/utils/actioncodeurlbuilder.js @@ -29,187 +29,187 @@ goog.require('goog.Uri'); * continue URL and then parsing them back when the code is applied. * This utility helps abstract the underlying parameters used to store all * parameters of interest and how they are set on the URL. - * - * @param {string} url This is the continue URL passed to the ActionCodeSettings - * object and which on code application can be retrieved. The URL cannot be - * a relative URL. - * @constructor */ -firebaseui.auth.ActionCodeUrlBuilder = function(url) { - /** @const @private {!goog.Uri} The corresponding parsed URI. */ - this.uri_ = goog.Uri.parse(url); -}; +firebaseui.auth.ActionCodeUrlBuilder = class { + /** + * @param {string} url This is the continue URL passed to the + * ActionCodeSettings object and which on code application can be + * retrieved. The URL cannot be a relative URL. + */ + constructor(url) { + /** @const @private {!goog.Uri} The corresponding parsed URI. */ + this.uri_ = goog.Uri.parse(url); + } -/** Clears all email link sign-in related parameters. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.clearState = function() { - // Remove all related query parameters. Keep all other parameters. - for (var key in firebaseui.auth.ActionCodeUrlBuilder.Parameter) { - if (firebaseui.auth.ActionCodeUrlBuilder.Parameter.hasOwnProperty(key)) { - this.uri_.removeParameter( - firebaseui.auth.ActionCodeUrlBuilder.Parameter[key]); + /** Clears all email link sign-in related parameters. */ + clearState() { + // Remove all related query parameters. Keep all other parameters. + for (const key in firebaseui.auth.ActionCodeUrlBuilder.Parameter) { + if (firebaseui.auth.ActionCodeUrlBuilder.Parameter.hasOwnProperty(key)) { + this.uri_.removeParameter( + firebaseui.auth.ActionCodeUrlBuilder.Parameter[key]); + } } } -}; -/** - * The action code URL builder reserved parameters used by FirebaseUI. - * - * @enum {string} - */ -firebaseui.auth.ActionCodeUrlBuilder.Parameter = { - ANONYMOUS_UID: 'ui_auid', - API_KEY: 'apiKey', - FORCE_SAME_DEVICE: 'ui_sd', - MODE: 'mode', - OOB_CODE: 'oobCode', - PROVIDER_ID: 'ui_pid', - SESSION_ID: 'ui_sid', - TENANT_ID: 'tenantId' -}; + /** + * Sets the session ID on the URL. + * @param {?string} sid The session identifier. + */ + setSessionId(sid) { + if (sid) { + this.uri_.setParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.SESSION_ID, sid); + } else { + this.uri_.removeParameter( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.SESSION_ID); + } + } -/** - * Sets the session ID on the URL. - * @param {?string} sid The session identifier. - */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.setSessionId = function(sid) { - if (sid) { - this.uri_.setParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.SESSION_ID, sid); - } else { - this.uri_.removeParameter( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.SESSION_ID); + /** @return {?string} The session ID. */ + getSessionId() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.SESSION_ID) || null; } -}; -/** @return {?string} The session ID. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getSessionId = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.SESSION_ID) || null; -}; + /** @return {?string} The OOB code if available. */ + getOobCode() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.OOB_CODE) || null; + } -/** @return {?string} The OOB code if available. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getOobCode = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.OOB_CODE) || null; -}; + /** @return {?string} The email action mode if available. */ + getMode() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.MODE) || null; + } -/** @return {?string} The email action mode if available. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getMode = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.MODE) || null; -}; + /** @return {?string} The API key if available. */ + getApiKey() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.API_KEY) || null; + } -/** @return {?string} The API key if available. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getApiKey = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.API_KEY) || null; -}; + /** + * Sets the tenant ID of the tenant project. + * @param {?string} tenantId The tenant ID to be set. + */ + setTenantId(tenantId) { + if (tenantId) { + this.uri_.setParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID, tenantId); + } else { + this.uri_.removeParameter( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID); + } + } -/** - * Sets the tenant ID of the tenant project. - * @param {?string} tenantId The tenant ID to be set. - */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.setTenantId = - function(tenantId) { - if (tenantId) { - this.uri_.setParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID, - tenantId); - } else { - this.uri_.removeParameter( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID); + /** @return {?string} The tenant ID if available. */ + getTenantId() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID) || null; } -}; -/** @return {?string} The tenant ID if available. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getTenantId = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID) || null; -}; + /** + * Defines whether to force same device flow. + * @param {?boolean} forceSameDevice Whether to force same device flow. + */ + setForceSameDevice(forceSameDevice) { + if (forceSameDevice !== null) { + this.uri_.setParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.FORCE_SAME_DEVICE, + forceSameDevice ? '1' : '0'); + } else { + this.uri_.removeParameter( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.FORCE_SAME_DEVICE); + } + } -/** - * Defines whether to force same device flow. - * @param {?boolean} forceSameDevice Whether to force same device flow. - */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.setForceSameDevice = - function(forceSameDevice) { - if (forceSameDevice !== null) { - this.uri_.setParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.FORCE_SAME_DEVICE, - forceSameDevice ? '1': '0'); - } else { - this.uri_.removeParameter( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.FORCE_SAME_DEVICE); + /** @return {boolean} Whether to force same device flow. */ + getForceSameDevice() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.FORCE_SAME_DEVICE) === + '1'; } -}; -/** @return {boolean} Whether to force same device flow. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getForceSameDevice = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.FORCE_SAME_DEVICE) === '1'; -}; + /** + * Sets the UID of the anonymous user to upgrade. + * @param {?string} anonymousUid The anonymous user to upgrade. + */ + setAnonymousUid(anonymousUid) { + if (anonymousUid) { + this.uri_.setParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.ANONYMOUS_UID, + anonymousUid); + } else { + this.uri_.removeParameter( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.ANONYMOUS_UID); + } + } -/** - * Sets the UID of the anonymous user to upgrade. - * @param {?string} anonymousUid The anonymous user to upgrade. - */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.setAnonymousUid = - function(anonymousUid) { - if (anonymousUid) { - this.uri_.setParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.ANONYMOUS_UID, - anonymousUid); - } else { - this.uri_.removeParameter( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.ANONYMOUS_UID); + /** @return {?string} The anonymous UID of the user to upgrade. */ + getAnonymousUid() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.ANONYMOUS_UID) || null; } -}; -/** @return {?string} The anonymous UID of the user to upgrade. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getAnonymousUid = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.ANONYMOUS_UID)|| null; -}; + /** + * Sets the provider ID of the credential to link. + * @param {?string} providerId The provider ID of the credential to link. + */ + setProviderId(providerId) { + if (providerId) { + this.uri_.setParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.PROVIDER_ID, + providerId); + } else { + this.uri_.removeParameter( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.PROVIDER_ID); + } + } -/** - * Sets the provider ID of the credential to link. - * @param {?string} providerId The provider ID of the credential to link. - */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.setProviderId = - function(providerId) { - if (providerId) { - this.uri_.setParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.PROVIDER_ID, - providerId); - } else { - this.uri_.removeParameter( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.PROVIDER_ID); + /** + * @return {?string} The provider ID of the credential to link if available. + */ + getProviderId() { + return this.uri_.getParameterValue( + firebaseui.auth.ActionCodeUrlBuilder.Parameter.PROVIDER_ID) || null; } -}; -/** @return {?string} The provider ID of the credential to link if available. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.getProviderId = function() { - return this.uri_.getParameterValue( - firebaseui.auth.ActionCodeUrlBuilder.Parameter.PROVIDER_ID) || null; + /** @return {string} The URL string representation. */ + toString() { + return this.uri_.toString(); + } }; -/** @return {string} The URL string representation. */ -firebaseui.auth.ActionCodeUrlBuilder.prototype.toString = function() { - return this.uri_.toString(); +/** + * The action code URL builder reserved parameters used by FirebaseUI. + * + * @enum {string} + */ +firebaseui.auth.ActionCodeUrlBuilder.Parameter = { + ANONYMOUS_UID: 'ui_auid', + API_KEY: 'apiKey', + FORCE_SAME_DEVICE: 'ui_sd', + MODE: 'mode', + OOB_CODE: 'oobCode', + PROVIDER_ID: 'ui_pid', + SESSION_ID: 'ui_sid', + TENANT_ID: 'tenantId', }; diff --git a/javascript/utils/config.js b/javascript/utils/config.js index 05a0fe8d..0c6b5351 100644 --- a/javascript/utils/config.js +++ b/javascript/utils/config.js @@ -18,109 +18,107 @@ goog.provide('firebaseui.auth.Config'); -goog.require('goog.Uri'); - - /** * Structure for defining and manipulating configuration fields. - * @constructor */ -firebaseui.auth.Config = function() { +firebaseui.auth.Config = class { + constructor() { + /** + * The instance for storing all configurations. + * @type {Object} + * @private + */ + this.instance_ = {}; + } + + /** - * The instance for storing all configurations. - * @type {Object} + * @param {string} name The name of the configuration. + * @return {boolean} Whether the configuration is defined. * @private */ - this.instance_ = {}; -}; - - -/** - * @param {string} name The name of the configuration. - * @return {boolean} Whether the configuration is defined. - * @private - */ -firebaseui.auth.Config.prototype.has_ = function(name) { - return name.toLowerCase() in this.instance_; -}; + has_(name) { + return name.toLowerCase() in this.instance_; + } -/** - * @param {string} name The name of the configuration. - * @return {*|undefined} The configuration value. - * @private - */ -firebaseui.auth.Config.prototype.get_ = function(name) { - return this.instance_[name.toLowerCase()]; -}; + /** + * @param {string} name The name of the configuration. + * @return {*|undefined} The configuration value. + * @private + */ + get_(name) { + return this.instance_[name.toLowerCase()]; + } -/** - * Sets the information for a configuration. - * - * @param {string} name The name of the configuration. - * @param {*|undefined} value The configuration value. - * @private - */ -firebaseui.auth.Config.prototype.set_ = function(name, value) { - this.instance_[name.toLowerCase()] = value; -}; + /** + * Sets the information for a configuration. + * + * @param {string} name The name of the configuration. + * @param {*|undefined} value The configuration value. + * @private + */ + set_(name, value) { + this.instance_[name.toLowerCase()] = value; + } -/** - * Defines a configuration with the given name and value. - * - * @param {string} name The name of the configuration. - * @param {*=} opt_value The value of the configuration. - */ -firebaseui.auth.Config.prototype.define = function(name, opt_value) { - if (this.has_(name)) { - throw new Error('Configuration ' + name + ' has already been defined.'); + /** + * Defines a configuration with the given name and value. + * + * @param {string} name The name of the configuration. + * @param {*=} value The value of the configuration. + */ + define(name, value) { + if (this.has_(name)) { + throw new Error('Configuration ' + name + ' has already been defined.'); + } + this.set_(name, value); } - this.set_(name, opt_value); -}; -/** - * Updates the configuration and its descendants with the given value. - * - * @param {string} name The name of the configuration. - * @param {*} value The value of the configuration. - */ -firebaseui.auth.Config.prototype.update = function(name, value) { - if (!this.has_(name)) { - throw new Error('Configuration ' + name + ' is not defined.'); + /** + * Updates the configuration and its descendants with the given value. + * + * @param {string} name The name of the configuration. + * @param {*} value The value of the configuration. + */ + update(name, value) { + if (!this.has_(name)) { + throw new Error('Configuration ' + name + ' is not defined.'); + } + this.set_(name, value); } - this.set_(name, value); -}; -/** - * Gets the configuration value for the given name. If an unrecognized name is - * specified, an `Error` is thrown. - * - * @param {string} name The name of the configuration. - * @return {*|undefined} The configuration value. - */ -firebaseui.auth.Config.prototype.get = function(name) { - if (!this.has_(name)) { - throw new Error('Configuration ' + name + ' is not defined.'); + /** + * Gets the configuration value for the given name. If an unrecognized name is + * specified, an `Error` is thrown. + * + * @param {string} name The name of the configuration. + * @return {*|undefined} The configuration value. + */ + get(name) { + if (!this.has_(name)) { + throw new Error('Configuration ' + name + ' is not defined.'); + } + return this.get_(name); } - return this.get_(name); -}; -/** - * Gets the configuration value for the given name. If an unrecognized name is - * specified or the value is not provided, an `Error` is thrown. - * - * @param {string} name The name of the configuration. - * @return {*} The configuration value. - */ -firebaseui.auth.Config.prototype.getRequired = function(name) { - var value = this.get(name); - if (!value) { - throw new Error('Configuration ' + name + ' is required.'); + /** + * Gets the configuration value for the given name. If an unrecognized name is + * specified or the value is not provided, an `Error` is thrown. + * + * @param {string} name The name of the configuration. + * @return {*} The configuration value. + */ + getRequired(name) { + var value = this.get(name); + if (!value) { + throw new Error('Configuration ' + name + ' is required.'); + } + return value; } - return value; }; diff --git a/javascript/utils/cookiemechanism.js b/javascript/utils/cookiemechanism.js index 717c1e3e..a36c7392 100644 --- a/javascript/utils/cookiemechanism.js +++ b/javascript/utils/cookiemechanism.js @@ -26,72 +26,66 @@ goog.require('goog.storage.mechanism.Mechanism'); /** * Defines a goog.storage.mechanism.Mechanism implementation for storing * cookies. - * @param {?number=} opt_maxAge The max age in seconds (from now). Use -1 to - * set a session cookie. If not provided, the default is -1 - * (i.e. set a session cookie). - * @param {?string=} opt_path The path of the cookie. If not present then this - * uses the full request path. - * @param {?string=} opt_domain The domain of the cookie, or null to not - * specify a domain attribute (browser will use the full request host name). - * If not provided, the default is null (i.e. let browser use full request - * host name). - * @param {boolean=} opt_secure Whether the cookie should only be sent over - * a secure channel. - * @constructor - * @extends {goog.storage.mechanism.Mechanism} */ firebaseui.auth.CookieMechanism = - function(opt_maxAge, opt_path, opt_domain, opt_secure) { - /** @const @private {number} The cookie max age in seconds. */ - this.maxAge_ = typeof opt_maxAge !== 'undefined' && opt_maxAge !== null ? - opt_maxAge : -1; - /** @const @private {?string} The cookie path. */ - this.path_ = opt_path || null; - /** @const @private {?string} The cookie domain policy. */ - this.domain_ = opt_domain || null; - /** @const @private {boolean} The cookie secure policy. */ - this.secure_ = !!opt_secure; -}; -goog.inherits( - firebaseui.auth.CookieMechanism, goog.storage.mechanism.Mechanism); - + class extends goog.storage.mechanism.Mechanism { + /** + * @param {?number=} opt_maxAge The max age in seconds (from now). Use -1 to + * set a session cookie. If not provided, the default is -1 + * (i.e. set a session cookie). + * @param {?string=} opt_path The path of the cookie. If not present then + * this uses the full request path. + * @param {?string=} opt_domain The domain of the cookie, or null to not + * specify a domain attribute (browser will use the full request host + * name). If not provided, the default is null (i.e. let browser use full + * request host name). + * @param {boolean=} opt_secure Whether the cookie should only be sent over + * a secure channel. + */ + constructor(opt_maxAge, opt_path, opt_domain, opt_secure) { + super(); + /** @const @private {number} The cookie max age in seconds. */ + this.maxAge_ = typeof opt_maxAge !== 'undefined' && opt_maxAge !== null ? + opt_maxAge : + -1; + /** @const @private {?string} The cookie path. */ + this.path_ = opt_path || null; + /** @const @private {?string} The cookie domain policy. */ + this.domain_ = opt_domain || null; + /** @const @private {boolean} The cookie secure policy. */ + this.secure_ = !!opt_secure; + } -/** - * Set a value for a key. - * - * @param {string} key The key to set. - * @param {string} value The string to save. - * @override - */ -firebaseui.auth.CookieMechanism.prototype.set = function(key, value) { - goog.net.cookies.set( - key, - value, - this.maxAge_, - this.path_, - this.domain_, - this.secure_); -}; + /** + * Set a value for a key. + * + * @param {string} key The key to set. + * @param {string} value The string to save. + * @override + */ + set(key, value) { + goog.net.cookies.set( + key, value, this.maxAge_, this.path_, this.domain_, this.secure_); + } + /** + * Get the value stored under a key. + * + * @param {string} key The key to get. + * @return {?string} The corresponding value, null if not found. + * @override + */ + get(key) { + return goog.net.cookies.get(key) || null; + } -/** - * Get the value stored under a key. - * - * @param {string} key The key to get. - * @return {?string} The corresponding value, null if not found. - * @override - */ -firebaseui.auth.CookieMechanism.prototype.get = function(key) { - return goog.net.cookies.get(key) || null; -}; - - -/** - * Remove a key and its value. - * - * @param {string} key The key to remove. - * @override - */ -firebaseui.auth.CookieMechanism.prototype.remove = function(key) { - goog.net.cookies.remove(key, this.path_, this.domain_); + /** + * Remove a key and its value. + * + * @param {string} key The key to remove. + * @override + */ + remove(key) { + goog.net.cookies.remove(key, this.path_, this.domain_); + } }; diff --git a/javascript/utils/eventregister.js b/javascript/utils/eventregister.js index 8f333642..1a14b3aa 100644 --- a/javascript/utils/eventregister.js +++ b/javascript/utils/eventregister.js @@ -20,10 +20,10 @@ goog.provide('firebaseui.auth.EventDispatcher'); goog.provide('firebaseui.auth.EventRegister'); +goog.require('goog.array'); goog.require('goog.events.EventTarget'); - /** * @private {!Object.>} The map * of elements to their corresponding event dispatchers. @@ -119,44 +119,41 @@ firebaseui.auth.EventRegister.getKey_ = function(el) { }; - /** * An event dispatcher wrapper for an element. Anytime an event is to be * triggered on the provided element, it is to be dispatched via event register * and if a listener is set, then its callback will be called. - * @param {Element} el The element on which events will be dispatched. - * @constructor - * @extends {goog.events.EventTarget} */ -firebaseui.auth.EventDispatcher = function(el) { - if (!el) { - throw new Error('Event target element must be provided!'); +firebaseui.auth.EventDispatcher = class extends goog.events.EventTarget { + /** + * @param {Element} el The element on which events will be dispatched. + */ + constructor(el) { + if (!el) { + throw new Error('Event target element must be provided!'); + } + super(); + this.el_ = el; } - this.el_ = el; - firebaseui.auth.EventDispatcher.base(this, 'constructor'); -}; -goog.inherits(firebaseui.auth.EventDispatcher, goog.events.EventTarget); - - -/** - * @return {Element} The element corresponding to the event dispatcher. - */ -firebaseui.auth.EventDispatcher.prototype.getElement = function() { - return this.el_; -}; - -/** - * Registers the event dispatcher object. - */ -firebaseui.auth.EventDispatcher.prototype.register = function() { - firebaseui.auth.EventRegister.register(this); -}; + /** + * @return {Element} The element corresponding to the event dispatcher. + */ + getElement() { + return this.el_; + } + /** + * Registers the event dispatcher object. + */ + register() { + firebaseui.auth.EventRegister.register(this); + } -/** - * Unregisters the event dispatcher object. - */ -firebaseui.auth.EventDispatcher.prototype.unregister = function() { - firebaseui.auth.EventRegister.unregister(this); + /** + * Unregisters the event dispatcher object. + */ + unregister() { + firebaseui.auth.EventRegister.unregister(this); + } }; diff --git a/javascript/utils/eventregister_test.js b/javascript/utils/eventregister_test.js index cd4c4ec7..8ff8b006 100644 --- a/javascript/utils/eventregister_test.js +++ b/javascript/utils/eventregister_test.js @@ -19,11 +19,11 @@ goog.provide('firebaseui.auth.EventRegisterTest'); goog.setTestOnly('firebaseui.auth.EventRegisterTest'); +goog.require('firebaseui.auth.EventDispatcher'); goog.require('firebaseui.auth.EventRegister'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); -goog.require('goog.testing.events'); goog.require('goog.testing.jsunit'); diff --git a/javascript/utils/googleyolo.js b/javascript/utils/googleyolo.js index d9bc19ca..f071b1fc 100644 --- a/javascript/utils/googleyolo.js +++ b/javascript/utils/googleyolo.js @@ -30,26 +30,167 @@ goog.require('goog.string.Const'); /** * The One-Tap sign-up API wrapper. - * @param {?SmartLockApi=} opt_googleyolo The optional googleyolo reference. If - * no reference provided, the global googleyolo instance is used. If that is - * not available, all supported operations will be no-ops. - * @constructor */ -firebaseui.auth.GoogleYolo = function(opt_googleyolo) { +firebaseui.auth.GoogleYolo = class { /** - * @private {?SmartLockApi|undefined} The One-Tap instance reference. If no - * reference is available, googleyolo will be lazy loaded dynamically. + * @param {?SmartLockApi=} googleyolo The optional googleyolo reference. + * If no reference provided, the global googleyolo instance is used. If + * that is not available, all supported operations will be no-ops. */ - this.googleyolo_ = opt_googleyolo || - goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]; - /** @private {?Promise} The last cancellation promise. */ - this.lastCancel_ = null; - /** @private {boolean} Whether googleyolo UI has already been initialized. */ - this.initialized_ = false; + constructor(googleyolo) { + /** + * @private {?SmartLockApi|undefined} The One-Tap instance reference. If no + * reference is available, googleyolo will be lazy loaded dynamically. + */ + this.googleyolo_ = + googleyolo || goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]; + /** + * @private {?Promise} The last cancellation promise. + */ + this.lastCancel_ = null; + /** + * @private {boolean} Whether googleyolo UI has already been initialized. + */ + this.initialized_ = false; + } + + + /** Cancels any pending One-Tap operation if available. */ + cancel() { + // Call underlying googleyolo API if supported and previously initialized. + // There is also a current issue with One-Tap. It will always fail with + // noCredentialsAvailable error if cancelLastOperation is called before + // rendering. + // If googleyolo is not yet loaded, there is no need to run cancel even + // after loading since there is nothing to cancel. + if (this.googleyolo_ && this.initialized_) { + this.lastCancel_ = + this.googleyolo_.cancelLastOperation().catch(function(error) { + // Suppress error. + }); + } + } + + + /** + * Shows the One-Tap UI if available and returns a promise that resolves with + * the selected googleyolo credential or null if not available. + * @param {?SmartLockRequestOptions} config The One-Tap configuration if + * available. + * @param {boolean} autoSignInDisabled Whether auto sign-in is disabled. + * @return {!Promise} A promise that resolves when + * One-Tap sign in is dismissed or resolved. A googleyolo credential is + * returned in the process. + */ + show(config, autoSignInDisabled) { + var self = this; + // Configuration available and googleyolo is available. + if (this.googleyolo_ && config) { + // One-Tap UI renderer. + var render = function() { + // UI initialized, it is OK to cancel last operation. + self.initialized_ = true; + // retrieve is only called if auto sign-in is enabled. Otherwise, it + // will get skipped. + var retrieveCredential = Promise.resolve(null); + if (!autoSignInDisabled) { + retrieveCredential = + self.googleyolo_ + .retrieve( + /** @type {!SmartLockRequestOptions} */ (config)) + .catch(function(error) { + // For user cancellation or concurrent request pass down. + // Otherwise suppress and run hint. + if (error.type === + firebaseui.auth.GoogleYolo.Error.USER_CANCELED || + error.type === + firebaseui.auth.GoogleYolo.Error + .CONCURRENT_REQUEST) { + throw error; + } + // Ignore all other errors to give hint a chance to run + // next. + return null; + }); + } + // Check if a credential is already available (previously signed in + // with). + return retrieveCredential + .then(function(credential) { + if (!credential) { + // Auto sign-in not complete. + // Show account selector. + return self.googleyolo_.hint( + /** @type {!SmartLockHintOptions} */ (config)); + } + // Credential already available from the retrieve call. Pass it + // through. + return credential; + }) + .catch(function(error) { + // When user cancels the flow, reset the lastCancel promise and + // resolve with false. + if (error.type === + firebaseui.auth.GoogleYolo.Error.USER_CANCELED) { + self.lastCancel_ = Promise.resolve(); + } else if ( + error.type === + firebaseui.auth.GoogleYolo.Error.CONCURRENT_REQUEST) { + // Only one UI can be rendered at a time, cancel existing UI + // and try again. + self.cancel(); + return self.show(config, autoSignInDisabled); + } + // Return null as no credential is available. + return null; + }); + }; + // If there is a pending cancel operation, wait for it to complete. + // Otherwise, an error will be thrown. + if (this.lastCancel_) { + // render always catches the error. + return this.lastCancel_.then(render); + } else { + // No pending cancel operation. Render UI directly. + return render(); + } + } else if (config) { + // Try to dynamically load googleyolo dependencies. + // If multiple calls of show are triggered successively, they would all + // share the same loader pending promise. They would then cancel each + // other successively due to concurrent requests. Only the last call will + // succeed. + var p = firebaseui.auth.GoogleYolo.Loader.getInstance() + .load() + .then(function() { + // Set googleyolo to prevent reloading again for future show + // calls. + self.googleyolo_ = + goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]; + // On success, retry to show. + return self.show(config, autoSignInDisabled); + }) + .thenCatch(function(error) { + // On failure, resolve with null. + return null; + }); + // Cast from goog.Promise to native Promise. + return Promise.resolve(p); + } + // no-op operation, resolve with null. + if (typeof Promise !== 'undefined') { + // typecast added to bypass weird compiler issue. + return Promise.resolve(/** @type {?SmartLockCredential} */ (null)); + } + // API not supported on older browsers. + throw new Error('One-Tap sign in not supported in the current browser!'); + } }; + goog.addSingletonGetter(firebaseui.auth.GoogleYolo); + /** * The One-Tap sign-up namespace. * @const {string} @@ -95,190 +236,67 @@ firebaseui.auth.GoogleYolo.Error = { }; -/** Cancels any pending One-Tap operation if available. */ -firebaseui.auth.GoogleYolo.prototype.cancel = function() { - // Call underlying googleyolo API if supported and previously initialized. - // There is also a current issue with One-Tap. It will always fail with - // noCredentialsAvailable error if cancelLastOperation is called before - // rendering. - // If googleyolo is not yet loaded, there is no need to run cancel even after - // loading since there is nothing to cancel. - if (this.googleyolo_ && this.initialized_) { - this.lastCancel_ = this.googleyolo_.cancelLastOperation() - .catch(function(error) { - // Suppress error. - }); - } -}; - - /** - * Shows the One-Tap UI if available and returns a promise that resolves with - * the selected googleyolo credential or null if not available. - * @param {?SmartLockRequestOptions} config The One-Tap configuration if - * available. - * @param {boolean} autoSignInDisabled Whether auto sign-in is disabled. - * @return {!Promise} A promise that resolves when One-Tap - * sign in is dismissed or resolved. A googleyolo credential is returned in - * the process. + * Googleyolo loader utility which will dynamically load one-tap sign-up + * dependencies if not already loaded. */ -firebaseui.auth.GoogleYolo.prototype.show = - function(config, autoSignInDisabled) { - var self = this; - // Configuration available and googleyolo is available. - if (this.googleyolo_ && config) { - // One-Tap UI renderer. - var render = function() { - // UI initialized, it is OK to cancel last operation. - self.initialized_ = true; - // retrieve is only called if auto sign-in is enabled. Otherwise, it will - // get skipped. - var retrieveCredential = Promise.resolve(null); - if (!autoSignInDisabled) { - retrieveCredential = - self.googleyolo_.retrieve( - /** @type {!SmartLockRequestOptions} */ (config)) - .catch(function(error) { - // For user cancellation or concurrent request pass down. - // Otherwise suppress and run hint. - if (error.type === - firebaseui.auth.GoogleYolo.Error.USER_CANCELED || - error.type === - firebaseui.auth.GoogleYolo.Error.CONCURRENT_REQUEST) { - throw error; - } - // Ignore all other errors to give hint a chance to run next. - return null; - }); - } - // Check if a credential is already available (previously signed in with). - return retrieveCredential - .then(function(credential) { - if (!credential) { - // Auto sign-in not complete. - // Show account selector. - return self.googleyolo_.hint( - /** @type {!SmartLockHintOptions} */ (config)); - } - // Credential already available from the retrieve call. Pass it - // through. - return credential; - }) - .catch(function(error) { - // When user cancels the flow, reset the lastCancel promise and - // resolve with false. - if (error.type === firebaseui.auth.GoogleYolo.Error.USER_CANCELED) { - self.lastCancel_ = Promise.resolve(); - } else if (error.type === - firebaseui.auth.GoogleYolo.Error.CONCURRENT_REQUEST) { - // Only one UI can be rendered at a time, cancel existing UI - // and try again. - self.cancel(); - return self.show(config, autoSignInDisabled); - } - // Return null as no credential is available. - return null; - }); - }; - // If there is a pending cancel operation, wait for it to complete. - // Otherwise, an error will be thrown. - if (this.lastCancel_) { - // render always catches the error. - return this.lastCancel_.then(render); - } else { - // No pending cancel operation. Render UI directly. - return render(); - } - } else if (config) { - // Try to dynamically load googleyolo dependencies. - // If multiple calls of show are triggered successively, they would all - // share the same loader pending promise. They would then cancel each other - // successively due to concurrent requests. Only the last call will succeed. - var p = firebaseui.auth.GoogleYolo.Loader.getInstance().load() - .then(function() { - // Set googleyolo to prevent reloading again for future show calls. - self.googleyolo_ = goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]; - // On success, retry to show. - return self.show(config, autoSignInDisabled); - }) - .thenCatch(function(error) { - // On failure, resolve with null. - return null; - }); - // Cast from goog.Promise to native Promise. - return Promise.resolve(p); - } - // no-op operation, resolve with null. - if (typeof Promise !== 'undefined') { - // typecast added to bypass weird compiler issue. - return Promise.resolve(/** @type {?SmartLockCredential} */ (null)); +firebaseui.auth.GoogleYolo.Loader = class { + constructor() { + /** + * @private {?goog.Promise} A promise that resolves when the + * googleyolo library is loaded. + */ + this.loader_ = null; } - // API not supported on older browsers. - throw new Error('One-Tap sign in not supported in the current browser!'); -}; -/** - * Googleyolo loader utility which will dynamically load one-tap sign-up - * dependencies if not already loaded. - * @constructor - */ -firebaseui.auth.GoogleYolo.Loader = function() { /** - * @private {?goog.Promise} A promise that resolves when the googleyolo - * library is loaded. + * @return {!goog.Promise} A promise that resolves when googleyolo is + * loaded. */ - this.loader_ = null; -}; -goog.addSingletonGetter(firebaseui.auth.GoogleYolo.Loader); - - -/** - * @return {!goog.Promise} A promise that resolves when googleyolo is - * loaded. - */ -firebaseui.auth.GoogleYolo.Loader.prototype.load = function() { - var self = this; - // Return any pending or resolved loader promise. - if (this.loader_) { - return this.loader_; - } - var url = goog.html.TrustedResourceUrl.fromConstant( - firebaseui.auth.GoogleYolo.GOOGLE_YOLO_SRC_); - if (!goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]) { - // Wait for DOM to be ready. - this.loader_ = firebaseui.auth.util.onDomReady() - .then(function() { - // In case it was still being loaded while DOM was not ready. - // Resolve immediately. - if (goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]) { - return; - } - return new goog.Promise(function(resolve, reject) { - // Timeout after a certain delay. - var timer = setTimeout(function() { - // On error, nullify loader to allow retrial. - self.loader_ = null; - reject(new Error('Network error!')); - }, firebaseui.auth.GoogleYolo.LOAD_TIMEOUT_MS_); - // On googleyolo load callback, clear timeout and resolve loader. - goog.global[firebaseui.auth.GoogleYolo.CALLBACK_] = function() { - clearTimeout(timer); - resolve(); - }; - // Load googleyolo dependency. - goog.Promise.resolve(goog.net.jsloader.safeLoad(url)) - .thenCatch(function(error) { - // On error, clear timer and nullify loader to allow retrial. - clearTimeout(timer); - self.loader_ = null; - reject(error); - }); - }); + load() { + var self = this; + // Return any pending or resolved loader promise. + if (this.loader_) { + return this.loader_; + } + var url = goog.html.TrustedResourceUrl.fromConstant( + firebaseui.auth.GoogleYolo.GOOGLE_YOLO_SRC_); + if (!goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]) { + // Wait for DOM to be ready. + this.loader_ = firebaseui.auth.util.onDomReady().then(function() { + // In case it was still being loaded while DOM was not ready. + // Resolve immediately. + if (goog.global[firebaseui.auth.GoogleYolo.NAMESPACE_]) { + return; + } + return new goog.Promise(function(resolve, reject) { + // Timeout after a certain delay. + var timer = setTimeout(function() { + // On error, nullify loader to allow retrial. + self.loader_ = null; + reject(new Error('Network error!')); + }, firebaseui.auth.GoogleYolo.LOAD_TIMEOUT_MS_); + // On googleyolo load callback, clear timeout and resolve loader. + goog.global[firebaseui.auth.GoogleYolo.CALLBACK_] = function() { + clearTimeout(timer); + resolve(); + }; + // Load googleyolo dependency. + goog.Promise.resolve(goog.net.jsloader.safeLoad(url)) + .thenCatch(function(error) { + // On error, clear timer and nullify loader to allow retrial. + clearTimeout(timer); + self.loader_ = null; + reject(error); + }); }); - return this.loader_; + }); + return this.loader_; + } + // Already available, resolve immediately. + return goog.Promise.resolve(); } - // Already available, resolve immediately. - return goog.Promise.resolve(); }; +goog.addSingletonGetter(firebaseui.auth.GoogleYolo.Loader); diff --git a/javascript/utils/idp.js b/javascript/utils/idp.js index e2ff1c00..cdf5f91e 100644 --- a/javascript/utils/idp.js +++ b/javascript/utils/idp.js @@ -69,6 +69,7 @@ firebaseui.auth.idp.NonFederatedSignInMethods = [ 'phone' ]; + /** * Supported IdP auth provider. * @package {Object} @@ -124,8 +125,8 @@ firebaseui.auth.idp.isFederatedSignInMethod = function(providerId) { /** - * Returns the provider by provider ID. If the provider ID is neither built-in - * provider or SAML provder, it will be considered as generic OAuth provider. + * Returns the provider by provider ID. If the provider ID is neither a built-in + * provider or SAML provider, it will be considered as a generic OAuth provider. * @param {string} providerId * @return {!firebase.auth.AuthProvider} The IdP. */ diff --git a/javascript/utils/pendingemailcredential.js b/javascript/utils/pendingemailcredential.js index d66ff4e8..57446293 100644 --- a/javascript/utils/pendingemailcredential.js +++ b/javascript/utils/pendingemailcredential.js @@ -22,47 +22,50 @@ goog.provide('firebaseui.auth.PendingEmailCredential'); - /** * The pending email credential. - * @param {string} email The pending email. - * @param {?firebase.auth.AuthCredential=} opt_credential The pending auth - * credential. - * @constructor @struct */ -firebaseui.auth.PendingEmailCredential = function(email, opt_credential) { - /** @const @private {string} The pending email. */ - this.email_ = email; - /** @const @private {?firebase.auth.AuthCredential} The pending credential. */ - this.credential_ = opt_credential || null; -}; +firebaseui.auth.PendingEmailCredential = class { + /** + * @param {string} email The pending email. + * @param {?firebase.auth.AuthCredential=} credential The pending auth + * credential. + */ + constructor(email, credential) { + /** @const @private {string} The pending email. */ + this.email_ = email; + /** + * @const @private {?firebase.auth.AuthCredential} The pending credential. + */ + this.credential_ = credential || null; + } -/** @return {string} The pending email. */ -firebaseui.auth.PendingEmailCredential.prototype.getEmail = function() { - return this.email_; -}; + /** @return {string} The pending email. */ + getEmail() { + return this.email_; + } -/** @return {?firebase.auth.AuthCredential} The pending credential. */ -firebaseui.auth.PendingEmailCredential.prototype.getCredential = function() { - return this.credential_; -}; + /** @return {?firebase.auth.AuthCredential} The pending credential. */ + getCredential() { + return this.credential_; + } -/** - * @return {!Object} The plain object representation of a pending email - * credential. - */ -firebaseui.auth.PendingEmailCredential.prototype.toPlainObject = function() { - return { - 'email': this.email_, - 'credential': this.credential_ && this.credential_['toJSON']() - }; + /** + * @return {!Object} The plain object representation of a pending email + * credential. + */ + toPlainObject() { + return { + 'email': this.email_, + 'credential': this.credential_ && this.credential_['toJSON']() + }; + } }; - /** * @param {?Object} response The plain object presentation of a potential * pending email credential object. diff --git a/javascript/utils/phoneauthresult.js b/javascript/utils/phoneauthresult.js index c67c48ee..677dc79f 100644 --- a/javascript/utils/phoneauthresult.js +++ b/javascript/utils/phoneauthresult.js @@ -1,15 +1,17 @@ /* * Copyright 2018 Google Inc. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ /** @@ -24,48 +26,45 @@ goog.require('goog.Promise'); /** * Wrapper object for firebase.auth.ConfirmationResult with additional error * handler for confirm method. - * @param {!firebase.auth.ConfirmationResult} confirmationResult The - * confirmation result from phone Auth. - * @param {(function(!Error):!goog.Promise)=} opt_errorHandler The error handler - * for confirm method. - * @constructor */ -firebaseui.auth.PhoneAuthResult = function( - confirmationResult, opt_errorHandler) { +firebaseui.auth.PhoneAuthResult = class { /** - * @const @private {!firebase.auth.ConfirmationResult} The confirmation result - * from a phone number sign-in or link. + * @param {!firebase.auth.ConfirmationResult} confirmationResult The + * confirmation result from phone Auth. + * @param {(function(!Error):!goog.Promise)=} errorHandler The error + * handler for confirm method. */ - this.confirmationResult_ = confirmationResult; - /** - * @const @private {function(*):*} The error handler for confirm method. - * If not provided, the error will be rethrown. - */ - this.errorHandler_ = opt_errorHandler || function(error) {throw error;}; - /** @const {string} The verification ID in confirmation result. */ - this.verificationId = confirmationResult['verificationId']; + constructor(confirmationResult, errorHandler) { + /** + * @const @private {!firebase.auth.ConfirmationResult} The confirmation + * result from a phone number sign-in or link. + */ + this.confirmationResult_ = confirmationResult; + /** + * @const @private {function(*):*} The error handler for confirm method. + * If not provided, the error will be rethrown. + */ + this.errorHandler_ = errorHandler || (error => {throw error;}); + /** @const {string} The verification ID in confirmation result. */ + this.verificationId = confirmationResult['verificationId']; + } -}; - -/** - * @param {string} verificationCode The verification code. - * @return {!goog.Promise} The user credential. - */ -firebaseui.auth.PhoneAuthResult.prototype.confirm = function(verificationCode) { - return goog.Promise.resolve( - this.confirmationResult_.confirm(verificationCode)) - .thenCatch(this.errorHandler_); -}; + /** + * @param {string} verificationCode The verification code. + * @return {!goog.Promise} The user credential. + */ + confirm(verificationCode) { + return goog.Promise + .resolve(this.confirmationResult_.confirm(verificationCode)) + .thenCatch(this.errorHandler_); + } -/** - * @return {!firebase.auth.ConfirmationResult} The confirmation result. - */ -firebaseui.auth.PhoneAuthResult.prototype.getConfirmationResult = function() { - return this.confirmationResult_; + /** + * @return {!firebase.auth.ConfirmationResult} The confirmation result. + */ + getConfirmationResult() { + return this.confirmationResult_; + } }; - - - - diff --git a/javascript/utils/phonenumber.js b/javascript/utils/phonenumber.js index 25930abe..58ebdbe3 100644 --- a/javascript/utils/phonenumber.js +++ b/javascript/utils/phonenumber.js @@ -20,76 +20,79 @@ goog.require('goog.string'); /** * Represents a phone number. - * @param {string} countryId The ID of the phone number's country. - * @param {string} nationalNumber The phone number without country code. - * @constructor @final @struct + * @final */ -firebaseui.auth.PhoneNumber = function(countryId, nationalNumber) { - /** @const {string} */ - this.countryId = countryId; - /** @const {string} */ - this.nationalNumber = nationalNumber; -}; +firebaseui.auth.PhoneNumber = class { + /** + * @param {string} countryId The ID of the phone number's country. + * @param {string} nationalNumber The phone number without country code. + */ + constructor(countryId, nationalNumber) { + /** @const {string} */ + this.countryId = countryId; + /** @const {string} */ + this.nationalNumber = nationalNumber; + } -/** @const {string} The ID of the default country (currently USA). */ -firebaseui.auth.PhoneNumber.DEFAULT_COUNTRY_ID = '1-US-0'; + /** + * Converts a phone number string to a firebaseui.auth.PhoneNumber object. + * Returns null if invalid. + * @param {string} phoneNumberStr The full phone number string. + * @return {?firebaseui.auth.PhoneNumber} The corresponding + * `firebaseui.auth.PhoneNumber` representation. + */ + static fromString(phoneNumberStr) { + // Ideally libPhoneNumber should be used to parse the phone number string + // but that dependency is too large to bundle with FirebaseUI-web, so we + // will attempt a best effort approach to parse the 2 components. + var trimmedPhoneNumber = goog.string.trim(phoneNumberStr); + // Get matching countries if national number countains it. + var countries = + firebaseui.auth.data.country.LOOKUP_TREE.search(trimmedPhoneNumber); + if (countries.length > 0) { + var countryId; + // Parse the country ID and national number components. + // If the country code is +1, use US as default code. + // Otherwise, just pick the first country. + if (countries[0].e164_cc == '1') { + countryId = firebaseui.auth.PhoneNumber.DEFAULT_COUNTRY_ID; + } else { + countryId = countries[0].e164_key; + } + // Get the national number. Add the + char to the e164_cc string. + var nationalNumber = + trimmedPhoneNumber.substr(countries[0].e164_cc.length + 1); + // Return the phone number object. + return new firebaseui.auth.PhoneNumber( + countryId, goog.string.trim(nationalNumber)); + } + return null; + } -/** - * Converts a phone number string to a firebaseui.auth.PhoneNumber object. - * Returns null if invalid. - * @param {string} phoneNumberStr The full phone number string. - * @return {?firebaseui.auth.PhoneNumber} The corresponding - * `firebaseui.auth.PhoneNumber` representation. - */ -firebaseui.auth.PhoneNumber.fromString = function(phoneNumberStr) { - // Ideally libPhoneNumber should be used to parse the phone number string but - // that dependency is too large to bundle with FirebaseUI-web, so we will - // attempt a best effort approach to parse the 2 components. - var trimmedPhoneNumber = goog.string.trim(phoneNumberStr); - // Get matching countries if national number countains it. - var countries = firebaseui.auth.data.country.LOOKUP_TREE.search( - trimmedPhoneNumber); - if (countries.length > 0) { - var countryId; - // Parse the country ID and national number components. - // If the country code is +1, use US as default code. - // Otherwise, just pick the first country. - if (countries[0].e164_cc == '1') { - countryId = firebaseui.auth.PhoneNumber.DEFAULT_COUNTRY_ID; - } else { - countryId = countries[0].e164_key; + /** + * @return {string} The full phone number. + */ + getPhoneNumber() { + var countryData = + firebaseui.auth.data.country.getCountryByKey(this.countryId); + if (!countryData) { + throw new Error('Country ID ' + this.countryId + ' not found.'); } - // Get the national number. Add the + char to the e164_cc string. - var nationalNumber = - trimmedPhoneNumber.substr(countries[0].e164_cc.length + 1); - // Return the phone number object. - return new firebaseui.auth.PhoneNumber( - countryId, goog.string.trim(nationalNumber)); + return '+' + countryData.e164_cc + this.nationalNumber; } - return null; -}; -/** - * @return {string} The full phone number. - */ -firebaseui.auth.PhoneNumber.prototype.getPhoneNumber = function() { - var countryData = firebaseui.auth.data.country.getCountryByKey( - this.countryId); - if (!countryData) { - throw new Error('Country ID ' + this.countryId + ' not found.'); + /** + * @return {?firebaseui.auth.data.country.Country} The country corresponding + * to the phone number's country ID. + */ + getCountry() { + return firebaseui.auth.data.country.getCountryByKey(this.countryId); } - return '+' + countryData.e164_cc + this.nationalNumber; }; -/** - * @return {?firebaseui.auth.data.country.Country} The country corresponding to - * the phone number's country ID. - */ -firebaseui.auth.PhoneNumber.prototype.getCountry = function() { - return firebaseui.auth.data.country.getCountryByKey( - this.countryId); -}; +/** @const {string} The ID of the default country (currently USA). */ +firebaseui.auth.PhoneNumber.DEFAULT_COUNTRY_ID = '1-US-0'; diff --git a/javascript/utils/redirectstatus.js b/javascript/utils/redirectstatus.js index d26883fd..a641c337 100644 --- a/javascript/utils/redirectstatus.js +++ b/javascript/utils/redirectstatus.js @@ -22,44 +22,46 @@ goog.provide('firebaseui.auth.RedirectStatus'); - /** * The redirect status. It indicates there is a pending redirect operation to be * resolved. - * @param {?string=} opt_tenantId The optional tenant ID. - * @constructor */ -firebaseui.auth.RedirectStatus = function(opt_tenantId) { - /** @const @private {?string} The tenant ID. */ - this.tenantId_ = opt_tenantId || null; -}; +firebaseui.auth.RedirectStatus = class { + /** + * The redirect status. It indicates there is a pending redirect operation to + * be resolved. + * @param {?string=} tenantId The optional tenant ID. + */ + constructor(tenantId) { + /** @const @private {?string} The tenant ID. */ + this.tenantId_ = tenantId || null; + } -/** @return {?string} The tenant ID. */ -firebaseui.auth.RedirectStatus.prototype.getTenantId = function() { - return this.tenantId_; -}; + /** @return {?string} The tenant ID. */ + getTenantId() { + return this.tenantId_; + } -/** - * @return {!Object} The plain object representation of redirect status. - */ -firebaseui.auth.RedirectStatus.prototype.toPlainObject = function() { - return { - 'tenantId': this.tenantId_ - }; -}; + /** + * @return {!Object} The plain object representation of redirect status. + */ + toPlainObject() { + return {'tenantId': this.tenantId_}; + } -/** - * @param {?Object} response The plain object presentation of a potential - * redirect status object. - * @return {?firebaseui.auth.RedirectStatus} The redirect status representation - * of the provided object. - */ -firebaseui.auth.RedirectStatus.fromPlainObject = function(response) { - if (response && typeof response['tenantId'] !== 'undefined') { - return new firebaseui.auth.RedirectStatus(response['tenantId']); + /** + * @param {?Object} response The plain object presentation of a potential + * redirect status object. + * @return {?firebaseui.auth.RedirectStatus} The redirect status + * representation of the provided object. + */ + static fromPlainObject(response) { + if (response && typeof response['tenantId'] !== 'undefined') { + return new firebaseui.auth.RedirectStatus(response['tenantId']); + } + return null; } - return null; }; diff --git a/javascript/utils/sni.js b/javascript/utils/sni.js index 5882b53a..00583dcb 100644 --- a/javascript/utils/sni.js +++ b/javascript/utils/sni.js @@ -105,60 +105,62 @@ firebaseui.auth.sni.REGEX_MOBILE_MSIE_UA_ = /MSIE ([\d.]+).*Windows Phone OS ([\d.]+)/; - /** * Represents a version number for the user agent and platform. - * - * @param {string} version The version string. - * @param {string=} opt_delimiter The delimiter which separates each components - * of the version. Default is a dot '.'. - * @constructor */ -firebaseui.auth.sni.Version = function(version, opt_delimiter) { - this.version_ = version; - var parts = version.split(opt_delimiter || '.'); - this.components_ = []; - for (var i = 0; i < parts.length; i++) { - this.components_.push(parseInt(parts[i], 10)); +firebaseui.auth.sni.Version = class { + /** + * @param {string} version The version string. + * @param {string=} opt_delimiter The delimiter which separates each + * components of the version. Default is a dot '.'. + */ + constructor(version, opt_delimiter) { + this.version_ = version; + var parts = version.split(opt_delimiter || '.'); + this.components_ = []; + for (var i = 0; i < parts.length; i++) { + this.components_.push(parseInt(parts[i], 10)); + } } -}; -/** - * Compares the version with another one. - * - * @param {firebaseui.auth.sni.Version|string} version The version to compare. - * @return {number} -1, 0 or 1 if it's less than, equal to or greater than the - * other. - */ -firebaseui.auth.sni.Version.prototype.compare = function(version) { - if (!(version instanceof firebaseui.auth.sni.Version)) { - version = new firebaseui.auth.sni.Version(String(version)); - } - var maxLength = Math.max(this.components_.length, version.components_.length); - for (var i = 0; i < maxLength; i++) { - var num1 = this.components_[i]; - var num2 = version.components_[i]; - if (num1 !== undefined && num2 !== undefined && num1 !== num2) { - return num1 - num2; - } else if (num1 === undefined) { - return -1; - } else if (num2 === undefined) { - return 1; + /** + * Compares the version with another one. + * + * @param {firebaseui.auth.sni.Version|string} version The version to compare. + * @return {number} -1, 0 or 1 if it's less than, equal to or greater than the + * other. + */ + compare(version) { + if (!(version instanceof firebaseui.auth.sni.Version)) { + version = new firebaseui.auth.sni.Version(String(version)); } + var maxLength = + Math.max(this.components_.length, version.components_.length); + for (var i = 0; i < maxLength; i++) { + var num1 = this.components_[i]; + var num2 = version.components_[i]; + if (num1 !== undefined && num2 !== undefined && num1 !== num2) { + return num1 - num2; + } else if (num1 === undefined) { + return -1; + } else if (num2 === undefined) { + return 1; + } + } + return 0; } - return 0; -}; -/** - * Checks the version is equal to or greater than another one. - * - * @param {firebaseui.auth.sni.Version|string} version The version to compare. - * @return {boolean} `true` if it's equal to or greater than the other. - */ -firebaseui.auth.sni.Version.prototype.ge = function(version) { - return this.compare(version) >= 0; + /** + * Checks the version is equal to or greater than another one. + * + * @param {firebaseui.auth.sni.Version|string} version The version to compare. + * @return {boolean} `true` if it's equal to or greater than the other. + */ + ge(version) { + return this.compare(version) >= 0; + } }; diff --git a/javascript/utils/storage.js b/javascript/utils/storage.js index f8afecc1..26010b0c 100644 --- a/javascript/utils/storage.js +++ b/javascript/utils/storage.js @@ -290,7 +290,7 @@ storage.getRememberedAccounts = function(opt_id) { var accounts = goog.array.map(rawAccounts, function(element) { return firebaseui.auth.Account.fromPlainObject(element); }); - return goog.array.filter(accounts, goog.isDefAndNotNull); + return goog.array.filter(accounts, x => x != null); }; diff --git a/javascript/utils/util.js b/javascript/utils/util.js index 775cc1da..6491a1ea 100644 --- a/javascript/utils/util.js +++ b/javascript/utils/util.js @@ -226,7 +226,7 @@ firebaseui.auth.util.popup = * @param {string|Element} element The element or the query selector. * @param {string=} opt_notFoundDesc Error description when element not * found. - * @return {Element} The HTML element. + * @return {!Element} The HTML element. */ firebaseui.auth.util.getElement = function(element, opt_notFoundDesc) { element = goog.dom.isElement(element) ? @@ -237,7 +237,7 @@ firebaseui.auth.util.getElement = function(element, opt_notFoundDesc) { var notFoundDesc = opt_notFoundDesc || 'Cannot find element.'; throw new Error(notFoundDesc); } - return /** @type {Element} */ (element); + return /** @type {!Element} */ (element); }; @@ -317,3 +317,22 @@ firebaseui.auth.util.generateRandomAlphaNumericString = function(numOfChars) { } return chars.join(''); }; + + +/** + * Copies and filters an object by provided keys. + * @param {!Object} source The object to be copied and filtered. + * @param {!Array} keys The keys used to filter the object. + * @param {!Object=} initialValue The base object to be extended when generating + * the target object. + * @return {!Object} The target object. + */ +firebaseui.auth.util.filterProperties = + function(source, keys, initialValue = {}) { + return Object.keys(source) + .filter(key => keys.includes(key)) + .reduce((obj, key) => { + obj[key] = source[key]; + return obj; + }, initialValue); +}; diff --git a/javascript/utils/util_test.js b/javascript/utils/util_test.js index 8dfb4e1b..caa36c36 100644 --- a/javascript/utils/util_test.js +++ b/javascript/utils/util_test.js @@ -216,3 +216,102 @@ function testGenerateRandomAlphaNumericString() { i, firebaseui.auth.util.generateRandomAlphaNumericString(i).length); } } + + +function testFilterProperties() { + // Test that only expected properties are copied over to the new object. + const expectedObj = { + property1: 'a', + property2: 'b', + property3: 'c', + }; + // The keys used to filter the source object. + const keys = Object.keys(expectedObj); + // Source object contains additional properties. + const sourceObj = Object.assign({property4: 'd'}, expectedObj); + + // The additional properties on the source object should be removed. + assertObjectEquals( + expectedObj, + firebaseui.auth.util.filterProperties(sourceObj, keys)); +} + + +function testFilterProperties_noMatchingKeys() { + // Test when the keys do not match with any properties in the source object. + const sourceObj = { + property1: 'a', + property2: 'b', + property3: 'c', + }; + // The keys used to filter the source object. + const keys = ['property4', 'property5']; + + // Empty object should be returned since there are no properties matching the + // provided keys. + assertObjectEquals( + {}, + firebaseui.auth.util.filterProperties(sourceObj, keys)); +} + + +function testFilterProperties_initialValue() { + // Test that expected properties and properties on initial value are + // copied over to the new object. + const expectedObj = { + property1: 'a', + property2: 'b', + property3: 'c', + }; + // The keys used to filter the source object. + const keys = Object.keys(expectedObj); + // Source object contains additional properties. + const sourceObj = Object.assign({property4: 'd'}, expectedObj); + + // The additional properties should be removed, the properties on initial + // value shoud be copied over. + assertObjectEquals( + { + property1: 'a', + property2: 'b', + property3: 'c', + property5: 'e', + }, + firebaseui.auth.util.filterProperties(sourceObj, keys, { + property5: 'e', + })); +} + + +function testFilterProperties_initialValueOverridden() { + // Test that expected properties are copied over and properties on initial + // value are overriden if present on the source object. + const expectedObj = { + property1: 'a', + property2: 'b', + property3: 'c', + }; + // The keys used to filter the source object. + const keys = Object.keys(expectedObj); + // Source object contains additional property. + const sourceObj = Object.assign({property4: 'd'}, expectedObj); + + // The value of property1 should be the one on source object rather than the + // one on initial object. + assertObjectEquals( + expectedObj, + firebaseui.auth.util.filterProperties(sourceObj, keys, { + property1: 'z', + })); +} + + +function testFilterProperties_emptySourceObject() { + // Test when the source object is empty. + // The keys used to filter the source object. + const keys = ['property1', 'property2']; + + assertObjectEquals( + {}, + firebaseui.auth.util.filterProperties({}, keys)); +} diff --git a/javascript/widgets/authui.js b/javascript/widgets/authui.js index 6c120997..6f17ebcb 100644 --- a/javascript/widgets/authui.js +++ b/javascript/widgets/authui.js @@ -74,6 +74,8 @@ goog.require('firebaseui.auth.widget.handler.handlePhoneSignInFinish'); /** @suppress {extraRequire} */ goog.require('firebaseui.auth.widget.handler.handlePhoneSignInStart'); /** @suppress {extraRequire} */ +goog.require('firebaseui.auth.widget.handler.handlePrefilledEmailSignIn'); +/** @suppress {extraRequire} */ goog.require('firebaseui.auth.widget.handler.handleProviderSignIn'); /** @suppress {extraRequire} */ goog.require('firebaseui.auth.widget.handler.handleSendEmailLinkForSignIn'); @@ -93,7 +95,7 @@ goog.require('goog.events.EventType'); /** * @param {!firebase.auth.Auth} auth The Firebase Auth instance. * @param {string=} opt_appId The optional app id. - * @constructor + * @constructor @struct */ firebaseui.auth.AuthUI = function(auth, opt_appId) { /** @private {boolean} Whether the current instance is deleted. */ @@ -138,6 +140,10 @@ firebaseui.auth.AuthUI = function(auth, opt_appId) { this.appId_ = opt_appId; /** @private {!firebaseui.auth.widget.Config} The AuthUI configuration. */ this.config_ = new firebaseui.auth.widget.Config(); + /** + * @private {?Object} The sign-in hint. Only supports email hint for now. + */ + this.signInHint_ = null; /** * @private {?firebaseui.auth.EventDispatcher} The event dispatcher for * triggering custom events on DOM objects. @@ -416,6 +422,17 @@ firebaseui.auth.AuthUI.prototype.getAppId = function() { }; +/** + * Returns the email hint for sign-in. + * @return {string|undefined} The sign-in email hint. + */ +firebaseui.auth.AuthUI.prototype.getSignInEmailHint = function() { + // Check if instance is already destroyed. + this.checkIfDestroyed_(); + return this.signInHint_ ? this.signInHint_['emailHint'] : undefined; +}; + + /** * Returns true if there is any pending operations to be resolved by the widget. * It is required in some cases like linking flows, where the user can become @@ -471,6 +488,18 @@ firebaseui.auth.AuthUI.prototype.isEmailLinkSignInUrl_ = function(url) { * @param {!Object} config The configuration for sign-in button. */ firebaseui.auth.AuthUI.prototype.start = function(element, config) { + this.startWithSignInHint(element, config); +}; + + +/** + * Start sign-in operation with sign-in hint. Not exposed to public for now. + * @param {string|!Element} element The container element or the query selector. + * @param {!Object} config The configuration for the sign-in button. + * @param {?Object=} signInHint The optional sign-in hint. + */ +firebaseui.auth.AuthUI.prototype.startWithSignInHint = + function(element, config, signInHint = undefined) { // Check if instance is already destroyed. this.checkIfDestroyed_(); var self = this; @@ -497,6 +526,7 @@ firebaseui.auth.AuthUI.prototype.start = function(element, config) { // These changes will be ignored as only the first accountchooser.com related // config will be applied. this.setConfig(config); + this.signInHint_ = signInHint || null; // Checks if there is pending internal Auth signOut promise. If yes, wait // until it resolved and then initElement. var doc = goog.global.document; @@ -569,11 +599,20 @@ firebaseui.auth.AuthUI.prototype.initElement_ = function(element) { this.initPageChangeListener_(container); // Document already loaded, render on demand. firebaseui.auth.widget.dispatcher.dispatchOperation(this, element); + // If the current page is blank and immediateFederatedRedirect is set to true, + // the widget is in the middle of federated redirecting and about to redirect + // to IdPs. + const isCurrentlyRedirecting = this.currentComponent_ && + this.currentComponent_.getPageId() == 'blank' && + this.getConfig().federatedProviderShouldImmediatelyRedirect(); // Removes pending status of previous redirect operations including redirect // back from accountchooser.com and federated sign in. // Remove status after dispatchOperation completes as that operation depends // on this information. - if (firebaseui.auth.storage.hasRedirectStatus(this.getAppId())) { + if (firebaseui.auth.storage.hasRedirectStatus(this.getAppId()) && + // Do not clear redirect status if the widget is undergoing immediate + // redirect, which is before redirecting to IdPs. + !isCurrentlyRedirecting) { var redirectStatus = firebaseui.auth.storage.getRedirectStatus(this.getAppId()); this.setTenantId(redirectStatus.getTenantId()); @@ -760,6 +799,7 @@ firebaseui.auth.AuthUI.prototype.reset = function() { this.widgetEventDispatcher_.unregister(); } this.revertLanguageCode(); + this.signInHint_ = null; // Clear email link sign-in state from URL if needed. this.clearEmailSignInState(); // Removes pending status of previous redirect operations including redirect @@ -1169,7 +1209,7 @@ firebaseui.auth.AuthUI.prototype.signInWithEmailLink = function( // Sign in with email link credential on internal instance. var p = this.getAuth().signInWithEmailLink(email, link) .then(function(userCredential) { - authResult = /** @type {!firebaseui.auth.AuthResult} */ ({ + authResult = /** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ 'user': userCredential['user'], // Email link credential is not re-usable. 'credential': null, @@ -1532,8 +1572,9 @@ firebaseui.auth.AuthUI.prototype.startSignInAnonymously = function() { /** * Finishes FirebaseUI login with the given 3rd party credentials. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result. - * @return {!firebase.Promise} + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result. + * @return {!firebase.Promise} */ firebaseui.auth.AuthUI.prototype.finishSignInAndRetrieveDataWithAuthResult = function(authResult) { @@ -1557,7 +1598,11 @@ firebaseui.auth.AuthUI.prototype.finishSignInAndRetrieveDataWithAuthResult = // with error credential already in use. // There are cases where this is required. For example, when email // mismatch occurs and the user continues with the new account. - return /** @type {!firebase.Promise} */ ( + return ( + /** + * @type {!firebase.Promise< + * !firebaseui.auth.widget.Config.AuthResult>} + */ ( self.clearTempAuthState().then(function() { return user.linkWithCredential( authResult['credential']); @@ -1578,7 +1623,7 @@ firebaseui.auth.AuthUI.prototype.finishSignInAndRetrieveDataWithAuthResult = } // For all other errors, run onUpgrade check. return self.onUpgradeError(error, authResult['credential']); - })); + }))); } else { // Finishes sign in with the supplied credential on the developer provided // Auth instance. On completion, this will redirect to signInSuccessUrl or diff --git a/javascript/widgets/authui_test.js b/javascript/widgets/authui_test.js index f26e402f..cadfa19e 100644 --- a/javascript/widgets/authui_test.js +++ b/javascript/widgets/authui_test.js @@ -21,7 +21,6 @@ goog.provide('firebaseui.auth.AuthUITest'); goog.require('firebaseui.auth.ActionCodeUrlBuilder'); goog.require('firebaseui.auth.AuthUI'); goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.CredentialHelper'); goog.require('firebaseui.auth.GoogleYolo'); goog.require('firebaseui.auth.PendingEmailCredential'); goog.require('firebaseui.auth.RedirectStatus'); @@ -176,6 +175,17 @@ function assertHasCssClass(container, cssName) { } +/** + * Asserts that two errors are equivalent. Plain assertObjectEquals cannot be + * used as Internet Explorer adds the stack trace as a property of the object. + * @param {!firebaseui.auth.AuthUIError} expected + * @param {!firebaseui.auth.AuthUIError} actual + */ +function assertErrorEquals(expected, actual) { + assertObjectEquals(expected.toPlainObject(), actual.toPlainObject()); +} + + function setUp() { testCookieStorage = new firebaseui.auth.testing.FakeCookieStorage().install(); mockClock.install(); @@ -844,6 +854,77 @@ function testStart() { } +function testStart_immediateFederatedRedirect_startRedirect() { + // Verify when immediateFederatedRedirect is enabled, redirect status is set + // correctly before redirecting to IdPs. + createAndInstallTestInstances(); + testStubs.reset(); + asyncTestCase.waitForSignals(1); + + assertFalse(firebaseui.auth.storage.hasRedirectStatus(app1.getAppId())); + // Enable immediateFederatedRedirect and start sign-in. + app1.start(container1, { + 'immediateFederatedRedirect': true, + 'signInOptions': [{ + 'provider': 'google.com', + }], + 'signInFlow':'redirect' + }); + + app1.getExternalAuth().runAuthChangeHandler(); + app1.getAuth().assertSignInWithRedirect([expectedProvider]); + app1.getExternalAuth().process().then(() => { + return app1.getAuth().process(); + }).then(() => { + // Federated redirect page should be rendered. + assertHasCssClass(container1, 'firebaseui-id-page-blank'); + // Redirect status should be set correctly. + assertTrue(firebaseui.auth.storage.hasRedirectStatus(app1.getAppId())); + asyncTestCase.signal(); + }); +} + + +function testStart_immediateFederatedRedirect_finishRedirect() { + // Verify when immediateFederatedRedirect is enabled, redirect status is + // cleared after sign-in is completed. + createAndInstallTestInstances(); + testStubs.reset(); + asyncTestCase.waitForSignals(1); + // Mock widget coming back from IdP page where the redirect status is set. + const redirectStatus = new firebaseui.auth.RedirectStatus(); + firebaseui.auth.storage.setRedirectStatus(redirectStatus, app1.getAppId()); + assertTrue(firebaseui.auth.storage.hasRedirectStatus(app1.getAppId())); + + // Enable immediateFederatedRedirect and start sign-in. + app1.start(container1, { + 'immediateFederatedRedirect': true, + 'signInOptions': [{ + 'provider': 'google.com', + }], + 'signInFlow':'redirect' + }); + + app1.getExternalAuth().runAuthChangeHandler(); + app1.getAuth().assertGetRedirectResult( + [], + function() { + app1.getAuth().setUser(expectedUser); + return expectedUserCredential; + }); + app1.getExternalAuth().process().then(() => { + return app1.getAuth().process(); + }).then(() => { + // Callback page should be rendered to handle redirecting back. + assertHasCssClass(container1, 'firebaseui-id-page-callback'); + // Redirect status should be cleared after sign-in is finished. + assertFalse(firebaseui.auth.storage.hasRedirectStatus(app1.getAppId())); + app1.getAuth().assertSignOut([]); + asyncTestCase.signal(); + }); +} + + function testSetLang() { testStubs.replace(goog, 'LOCALE', 'de'); // Language code of auth instance is set to goog.LOCALE at initialization. @@ -979,6 +1060,66 @@ function testStart_redirect_tenantId() { } +function testStartWithSignInHint() { + // Test startWithSignInHint. + testStubs.reset(); + testStubs.set( + firebaseui.auth.widget.handler.common, + 'handleSignInStart', + goog.testing.recordFunction()); + createAndInstallTestInstances(); + const emailHint = 'user@example.com'; + + // Start sign-in with signInHint. + app1.startWithSignInHint(container1, config1, { + emailHint, + }); + + // Verify that the correct email hint is returned. + assertEquals(emailHint, app1.getSignInEmailHint()); + // Verify that email hint is passed to handleSignInStart. + /** @suppress {missingRequire} */ + assertEquals( + 1, + firebaseui.auth.widget.handler.common.handleSignInStart.getCallCount()); + assertEquals( + app1, + firebaseui.auth.widget.handler.common.handleSignInStart.getLastCall() + .getArgument(0)); + assertEquals( + container1, + firebaseui.auth.widget.handler.common.handleSignInStart.getLastCall() + .getArgument(1)); + assertEquals( + emailHint, + firebaseui.auth.widget.handler.common.handleSignInStart.getLastCall() + .getArgument(2)); + + // Start sign-in again without signInHint. + app1.startWithSignInHint(container1, config1); + // Verify that email hint is undefined. + assertUndefined(app1.getSignInEmailHint()); + // Verify that no email hint is passed to handleSignInStart. + /** @suppress {missingRequire} */ + assertEquals( + 2, + firebaseui.auth.widget.handler.common.handleSignInStart.getCallCount()); + assertEquals( + app1, + firebaseui.auth.widget.handler.common.handleSignInStart.getLastCall() + .getArgument(0)); + assertEquals( + container1, + firebaseui.auth.widget.handler.common.handleSignInStart.getLastCall() + .getArgument(1)); + /** @suppress {missingRequire} */ + assertUndefined( + firebaseui.auth.widget.handler.common.handleSignInStart.getLastCall() + .getArgument(2)); + +} + + function testStart_elementNotFound() { // Test widget start method with missing element. // Test correct error message thrown when widget element not found. @@ -1033,7 +1174,7 @@ function testUiChangedCallback() { 'callbacks': { 'uiChanged': uiChangedCallback }, - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, }; // Initialize app and pass configuration. // Make sure internal and external instances installed. @@ -1456,7 +1597,7 @@ function testAuthUi_oneTapSignIn_disabled() { 'clientId': '1234567890.apps.googleusercontent.com' } ], - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE }; // Expected googyolo config is null. var googYoloConfig = null; @@ -1510,7 +1651,8 @@ function testAuthUi_oneTapSignIn_autoSignInDisabled() { 'clientId': '1234567890.apps.googleusercontent.com' } ], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }; // Expected googyolo config corresponding to the above FirebaseUI config. var googYoloConfig = { @@ -1573,7 +1715,8 @@ function testAuthUi_oneTapSignIn_noCurrentComponent() { 'clientId': '1234567890.apps.googleusercontent.com' } ], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }; // Expected googyolo config corresponding to the above FirebaseUI config. var googYoloConfig = { @@ -1633,7 +1776,8 @@ function testAuthUi_oneTapSignIn_autoSignInEnabled() { 'clientId': '1234567890.apps.googleusercontent.com' } ], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }; // Expected googyolo config corresponding to the above FirebaseUI config. var googYoloConfig = { @@ -2973,7 +3117,7 @@ function testStartSignInWithCredential_upgradeAnonymous_nonAnonymous_success() { testAuth.setUser(expectedUser); // Trigger initial onAuthStateChanged listener. app.getExternalAuth().runAuthChangeHandler(); - // signInAndRetrieveDataWithCredential called on internal Auth instance as no + // signInWithCredential called on internal Auth instance as no // anonymous user available. app.getAuth().assertSignInWithCredential( [expectedCredential], @@ -3004,7 +3148,7 @@ function testStartSignInWithCredential_upgradeAnonymous_noUser_success() { testAuth.setUser(null); // Trigger initial onAuthStateChanged listener. app.getExternalAuth().runAuthChangeHandler(); - // signInAndRetrieveDataWithCredential called on internal Auth instance as no + // signInWithCredential called on internal Auth instance as no // user available. app.getAuth().assertSignInWithCredential( [expectedCredential], @@ -5295,7 +5439,7 @@ function testOnUpgradeError_credentialAlreadyInUseError() { // Confirm signInFailure called with expected UI error. assertEquals( 1, anonymousUpgradeConfig.callbacks.signInFailure.getCallCount()); - assertObjectEquals( + assertErrorEquals( expectedMergeError, anonymousUpgradeConfig.callbacks.signInFailure.getLastCall() .getArgument(0)); @@ -5339,7 +5483,7 @@ function testOnUpgradeError_emailAlreadyInUseError() { // Confirm signInFailure called with expected UI error. assertEquals( 1, anonymousUpgradeConfig.callbacks.signInFailure.getCallCount()); - assertObjectEquals( + assertErrorEquals( expectedMergeError, anonymousUpgradeConfig.callbacks.signInFailure.getLastCall() .getArgument(0)); diff --git a/javascript/widgets/authuierror.js b/javascript/widgets/authuierror.js index c7225c53..039cc426 100644 --- a/javascript/widgets/authuierror.js +++ b/javascript/widgets/authuierror.js @@ -12,76 +12,72 @@ * limitations under the License. */ -/** - * @fileoverview FirebaseUI error. - */ - -goog.provide('firebaseui.auth.AuthUIError'); +/** @fileoverview FirebaseUI error. */ -goog.require('firebaseui.auth.soy2.strings'); +goog.module('firebaseui.auth.AuthUIError'); +goog.module.declareLegacyNamespace(); +const strings = goog.require('firebaseui.auth.soy2.strings'); -/** - * Error that can be returned to the developer. - * @param {!firebaseui.auth.AuthUIError.Error} code The short error code. - * @param {?string=} opt_message The human-readable message. - * @param {?firebase.auth.AuthCredential=} opt_credential The Auth credential - * that failed to link to the anonymous user. - * @constructor - * @extends {Error} - */ -firebaseui.auth.AuthUIError = function(code, opt_message, opt_credential) { - this['code'] = firebaseui.auth.AuthUIError.ERROR_CODE_PREFIX + code; - this['message'] = opt_message || - firebaseui.auth.AuthUIError.getDefaultErrorMessage_(this['code']) || ''; - this['credential'] = opt_credential || null; -}; -goog.inherits(firebaseui.auth.AuthUIError, Error); +/** Error that can be returned to the developer. */ +class AuthUIError extends Error { + /** + * @param {!AuthUIError.Error} code The short error code. + * @param {?string=} opt_message The human-readable message. + * @param {?firebase.auth.AuthCredential=} opt_credential The Auth credential + * that failed to link to the anonymous user. + */ + constructor(code, opt_message, opt_credential) { + super(); + /** @export */ + this.code = AuthUIError.ERROR_CODE_PREFIX + code; + /** @export */ + this.message = opt_message || + AuthUIError.getDefaultErrorMessage_(this.code) || ''; + /** @export */ + this.credential = opt_credential || null; + } + /** @return {!Object} The plain object form of the error. */ + toPlainObject() { + return { + 'code': this.code, + 'message': this.message, + }; + } -/** - * @return {!Object} The plain object form of the error. - */ -firebaseui.auth.AuthUIError.prototype.toPlainObject = function() { - return { - 'code': this['code'], - 'message': this['message'], - }; -}; + /** + * @return {!Object} The plain object form of the error. This is used by + * JSON.stringify() to return the stringified representation of the error. + * @override + */ + toJSON() { + return this.toPlainObject(); + } + /** + * Maps the error code to the default error message. + * @param {string} code The error code. + * @return {string} The display error message. + * @private + */ + static getDefaultErrorMessage_(code) { + return strings.errorAuthUI({code: code}).toString(); + } +} /** - * @return {!Object} The plain object form of the error. This is used by - * JSON.stringify() to return the stringified representation of the error. - * @override - */ -firebaseui.auth.AuthUIError.prototype.toJSON = function() { - return this.toPlainObject(); -}; - - -/** - * The error prefix for firebaseui.auth.AuthUIError. + * The error prefix for AuthUIError. * @protected {string} */ -firebaseui.auth.AuthUIError.ERROR_CODE_PREFIX = 'firebaseui/'; - +AuthUIError.ERROR_CODE_PREFIX = 'firebaseui/'; /** * Developer facing FirebaseUI error codes. * @enum {string} */ -firebaseui.auth.AuthUIError.Error = { - MERGE_CONFLICT: 'anonymous-upgrade-merge-conflict' +AuthUIError.Error = { + MERGE_CONFLICT: 'anonymous-upgrade-merge-conflict', }; - -/** - * Maps the error code to the default error message. - * @param {string} code The error code. - * @return {string} The display error message. - * @private - */ -firebaseui.auth.AuthUIError.getDefaultErrorMessage_ = function(code) { - return firebaseui.auth.soy2.strings.errorAuthUI({code: code}).toString(); -}; +exports = AuthUIError; diff --git a/javascript/widgets/authuierror_test.js b/javascript/widgets/authuierror_test.js index bcb4f6e6..e12dd958 100644 --- a/javascript/widgets/authuierror_test.js +++ b/javascript/widgets/authuierror_test.js @@ -12,53 +12,50 @@ * limitations under the License. */ -/** - * @fileoverview Tests for authuierror.js - */ - -goog.provide('firebaseui.auth.AuthUIErrorTest'); - -goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.soy2.strings'); -goog.require('goog.testing.jsunit'); +/** @fileoverview Tests for authuierror.js */ +goog.module('firebaseui.auth.AuthUIErrorTest'); goog.setTestOnly(); - -function testAuthUIError() { - var authCredential = {'accessToken': 'googleAccessToken', - 'providerId': 'google.com'}; - var error = new firebaseui.auth.AuthUIError( - firebaseui.auth.AuthUIError.Error.MERGE_CONFLICT, undefined, - authCredential); - assertEquals('firebaseui/anonymous-upgrade-merge-conflict', error['code']); - assertEquals( - firebaseui.auth.soy2.strings.errorAuthUI( - {code: error['code']}).toString(), - error['message']); - // Test toJSON(). Do not expose credential in JSON object. - assertObjectEquals({ - code: error['code'], - message: error['message'], - }, error.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); -} - - -function testAuthUIError_customMessage() { - var authCredential = {'accessToken': 'googleAccessToken', - 'providerId': 'google.com'}; - var error = new firebaseui.auth.AuthUIError( - firebaseui.auth.AuthUIError.Error.MERGE_CONFLICT, 'merge conflict error', - authCredential); - assertEquals('firebaseui/anonymous-upgrade-merge-conflict', error['code']); - assertEquals('merge conflict error', error['message']); - // Test toJSON(). Do not expose credential in JSON object. - assertObjectEquals({ - code: error['code'], - message: error['message'], - }, error.toJSON()); - // Make sure JSON.stringify works and uses underlying toJSON. - assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); -} +const AuthUIError = goog.require('firebaseui.auth.AuthUIError'); +const strings = goog.require('firebaseui.auth.soy2.strings'); +const testSuite = goog.require('goog.testing.testSuite'); + +testSuite({ + testAuthUIError() { + const authCredential = {'accessToken': 'googleAccessToken', + 'providerId': 'google.com'}; + const error = new AuthUIError( + AuthUIError.Error.MERGE_CONFLICT, undefined, + authCredential); + assertEquals('firebaseui/anonymous-upgrade-merge-conflict', error.code); + assertEquals( + strings.errorAuthUI( + {code: error.code}).toString(), + error.message); + // Test toJSON(). Do not expose credential in JSON object. + assertObjectEquals({ + code: error.code, + message: error.message, + }, error.toJSON()); + // Make sure JSON.stringify works and uses underlying toJSON. + assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); + }, + + testAuthUIError_customMessage() { + const authCredential = {'accessToken': 'googleAccessToken', + 'providerId': 'google.com'}; + const error = new AuthUIError( + AuthUIError.Error.MERGE_CONFLICT, 'merge conflict error', + authCredential); + assertEquals('firebaseui/anonymous-upgrade-merge-conflict', error.code); + assertEquals('merge conflict error', error.message); + // Test toJSON(). Do not expose credential in JSON object. + assertObjectEquals({ + code: error.code, + message: error.message, + }, error.toJSON()); + // Make sure JSON.stringify works and uses underlying toJSON. + assertEquals(JSON.stringify(error), JSON.stringify(error.toJSON())); + }, +}); diff --git a/javascript/widgets/config.js b/javascript/widgets/config.js index 584cf2d1..fcc21be6 100644 --- a/javascript/widgets/config.js +++ b/javascript/widgets/config.js @@ -12,1052 +12,962 @@ * limitations under the License. */ -/** - * @fileoverview Defines all configurations used by FirebaseUI widget. - */ - -goog.provide('firebaseui.auth.AnonymousAuthProvider'); -goog.provide('firebaseui.auth.CredentialHelper'); -goog.provide('firebaseui.auth.callback.signInFailure'); -goog.provide('firebaseui.auth.callback.signInSuccess'); -goog.provide('firebaseui.auth.callback.signInSuccessWithAuthResult'); -goog.provide('firebaseui.auth.widget.Config'); - -goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.Config'); -goog.require('firebaseui.auth.PhoneNumber'); -goog.require('firebaseui.auth.data.country'); -goog.require('firebaseui.auth.idp'); -goog.require('firebaseui.auth.log'); -goog.require('firebaseui.auth.util'); -goog.require('goog.Uri'); -goog.require('goog.array'); -goog.require('goog.object'); -goog.require('goog.uri.utils'); - -goog.forwardDeclare('firebaseui.auth.AuthResult'); +/** @fileoverview Defines all configurations used by FirebaseUI widget. */ + +goog.module('firebaseui.auth.widget.Config'); +goog.module.declareLegacyNamespace(); + +const AuthConfig = goog.require('firebaseui.auth.Config'); +const AuthUIError = goog.require('firebaseui.auth.AuthUIError'); +const PhoneNumber = goog.require('firebaseui.auth.PhoneNumber'); +const Uri = goog.require('goog.Uri'); +const country = goog.require('firebaseui.auth.data.country'); +const googArray = goog.require('goog.array'); +const googObject = goog.require('goog.object'); +const idp = goog.require('firebaseui.auth.idp'); +const log = goog.require('firebaseui.auth.log'); +const util = goog.require('firebaseui.auth.util'); +const utils = goog.require('goog.uri.utils'); + + +/** Application configuration settings. */ +class Config { + constructor() { + /** @const @private {!AuthConfig} The AuthUI config object. */ + this.config_ = new AuthConfig(); + // Define FirebaseUI widget configurations and convenient getters. + this.config_.define('acUiConfig'); + this.config_.define('autoUpgradeAnonymousUsers'); + this.config_.define('callbacks'); + /** + * Determines which credential helper to use. Currently, only + * accountchooser.com is available and it is set by default. + */ + this.config_.define( + 'credentialHelper', + Config.CredentialHelper.ACCOUNT_CHOOSER_COM); + /** + * Determines whether to immediately redirect to the provider's site or + * instead show the default 'Sign in with Provider' button when there + * is only a single federated provider in signInOptions. In order for this + * option to take effect, the signInOptions must only hold a single + * federated provider (like 'google.com') and signInFlow must be set to + * 'redirect'. + */ + this.config_.define('immediateFederatedRedirect', false); + this.config_.define('popupMode', false); + this.config_.define('privacyPolicyUrl'); + /** Determines the redirect URL query key. */ + this.config_.define( + 'queryParameterForSignInSuccessUrl', 'signInSuccessUrl'); + this.config_.define('queryParameterForWidgetMode', 'mode'); + /** + * Determines the sign-in flow, 'popup' or 'redirect'. The former will use + * signInWithPopup where as the latter will use the default + * signInWithRedirect when a federated sign-in is triggered. + */ + this.config_.define('signInFlow'); + /** + * Determines the list of IdPs for handling federated sign-in as well as + * password account sign-up explicitly. The developer can also request + * additional scopes. + */ + this.config_.define('signInOptions'); + this.config_.define('signInSuccessUrl'); + this.config_.define('siteName'); + this.config_.define('tosUrl'); + this.config_.define('widgetUrl'); + } + /** @return {?Object} The UI configuration for accountchooser.com. */ + getAcUiConfig() { + return /** @type {?Object} */ (this.config_.get('acUiConfig') || null); + } -/** - * Application configuration settings. - * @constructor - */ -firebaseui.auth.widget.Config = function() { - this.config_ = new firebaseui.auth.Config(); - // Define FirebaseUI widget configurations and convenient getters. - this.config_.define('acUiConfig'); - this.config_.define('autoUpgradeAnonymousUsers'); - this.config_.define('callbacks'); - /** - * Determines which credential helper to use. Currently, only - * accountchooser.com is available and it is set by default. - */ - this.config_.define( - 'credentialHelper', - firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM); /** - * Determines whether to immediately redirect to the provider's site or - * instead show the default 'Sign in with Provider' button when there - * is only a single federated provider in signInOptions. In order for this - * option to take effect, the signInOptions must only hold a single federated - * provider (like 'google.com') and signInFlow must be set to 'redirect'. + * Gets the widget URL for a specific mode. + * The 'widgetUrl' configuration is required for this method. + * @param {?Config.WidgetMode=} mode The mode for the widget. + * @return {string} The URL of the callback widget. */ - this.config_.define('immediateFederatedRedirect', false); - this.config_.define('popupMode', false); - this.config_.define('privacyPolicyUrl'); + getRequiredWidgetUrl(mode = undefined) { + const url = /** @type {string} */ (this.config_.getRequired('widgetUrl')); + return this.widgetUrlForMode_(url, mode); + } + /** - * Determines the redirect URL query key. + * Gets the widget URL for a specific mode. + * If the 'widgetUrl' configuration is not set, current URL is used as the + * base URL for the widget. + * @param {?Config.WidgetMode=} mode The mode for the widget. + * @return {string} The URL of the callback widget. */ - this.config_.define('queryParameterForSignInSuccessUrl', 'signInSuccessUrl'); - this.config_.define('queryParameterForWidgetMode', 'mode'); + getWidgetUrl(mode = undefined) { + const url = /** @type {string|undefined} */ ( + this.config_.get('widgetUrl')) || + // If no widget URL is provided, use the current one. + util.getCurrentUrl(); + return this.widgetUrlForMode_(url, mode); + } + /** - * Determines the sign-in flow, 'popup' or 'redirect'. The former will use - * signInWithPopup where as the latter will use the default signInWithRedirect - * when a federated sign-in is triggered. + * Gets the callback URL for IdP. It always returns an absolute URL. + * @return {string} The callback URL. */ - this.config_.define('signInFlow'); + getIdpCallbackUrl() { + return Uri.resolve( + window.location.href, this.getWidgetUrl()).toString(); + } + /** - * Determines the list of IdPs for handling federated sign-in as well as - * password account sign-up explicitly. The developer can also request - * additional scopes. + * @param {string} baseUrl The base URL of the widget. + * @param {?Config.WidgetMode=} mode The mode for the widget. + * @return {string} The URL of the widget for a specific mode. + * @private */ - this.config_.define('signInOptions'); - this.config_.define('signInSuccessUrl'); - this.config_.define('siteName'); - this.config_.define('tosUrl'); - this.config_.define('widgetUrl'); -}; - - -/** - * The different credentials helper available. - * - * @enum {string} - */ -firebaseui.auth.CredentialHelper = { - ACCOUNT_CHOOSER_COM: 'accountchooser.com', - GOOGLE_YOLO: 'googleyolo', - NONE: 'none' -}; - - -/** - * Provider ID for continue as guest sign in option. - * - * @const {string} - */ -firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID = 'anonymous'; - - -/** - * The configuration sign-in success callback. - * @typedef {function( - * !firebase.User, ?firebase.auth.AuthCredential=, string=): boolean} - */ -firebaseui.auth.callback.signInSuccess; - - -/** - * The configuration sign-in success callback which takes AuthResult as input. - * @typedef {function( - * !firebaseui.auth.AuthResult, string=): boolean} - */ -firebaseui.auth.callback.signInSuccessWithAuthResult; - - -/** - * The configuration sign-in failure callback. - * @typedef {function(!firebaseui.auth.AuthUIError): (!Promise|void)} - */ -firebaseui.auth.callback.signInFailure; - - -/** - * The accountchooser.com result codes. - * - * @enum {string} - */ -firebaseui.auth.widget.Config.AccountChooserResult = { - EMPTY: 'empty', - UNAVAILABLE: 'unavailable', - ACCOUNT_SELECTED: 'accountSelected', - ADD_ACCOUNT: 'addAccount' -}; - - -/** - * The type of sign-in flow. - * - * @enum {string} - */ -firebaseui.auth.widget.Config.SignInFlow = { - POPUP: 'popup', - REDIRECT: 'redirect' -}; - - -/** - * The provider config object for generic providers. - * providerId: The provider ID. - * providerName: The display name of the provider. - * buttonColor: The color of the sign in button. - * iconUrl: The URL of the icon on sign in button. - * loginHintKey: The name to use for the optional login hint parameter. - * - * @typedef {{ - * providerId: string, - * providerName: (?string|undefined), - * buttonColor: (?string|undefined), - * iconUrl: (?string|undefined), - * loginHintKey: (?string|undefined) - * }} - */ -firebaseui.auth.widget.Config.ProviderConfig; - - -/** @return {?Object} The UI configuration for accountchooser.com. */ -firebaseui.auth.widget.Config.prototype.getAcUiConfig = function() { - return /** @type {?Object} */ (this.config_.get('acUiConfig') || null); -}; - - -/** - * Enums for callback widget mode. Please alphabetize by names. - * @enum {string} - */ -firebaseui.auth.widget.Config.WidgetMode = { - CALLBACK: 'callback', - RECOVER_EMAIL: 'recoverEmail', - RESET_PASSWORD: 'resetPassword', - SELECT: 'select', - SIGN_IN: 'signIn', - VERIFY_EMAIL: 'verifyEmail' -}; - - -/** - * FirebaseUI supported providers in sign in option. - * @const @private {!Array} - */ -firebaseui.auth.widget.Config.UI_SUPPORTED_PROVIDERS_ = ['anonymous']; - - -/** - * @const @private {!Array} List of blacklisted reCAPTCHA parameter - * keys. - */ -firebaseui.auth.widget.Config.BLACKLISTED_RECAPTCHA_KEYS_ = [ - 'sitekey', 'tabindex', 'callback', 'expired-callback']; - - -/** - * Gets the widget URL for a specific mode. - * The 'widgetUrl' configuration is required for this method. - * - * @param {?firebaseui.auth.widget.Config.WidgetMode=} opt_mode The mode for the - * widget. - * @return {string} The URL of the callback widget. - */ -firebaseui.auth.widget.Config.prototype.getRequiredWidgetUrl = - function(opt_mode) { - var url = /** @type {string} */ (this.config_.getRequired('widgetUrl')); - return this.widgetUrlForMode_(url, opt_mode); -}; - - -/** - * Gets the widget URL for a specific mode. - * If the 'widgetUrl' configuration is not set, current URL is used as the - * base URL for the widget. - * - * @param {?firebaseui.auth.widget.Config.WidgetMode=} opt_mode The mode for the - * widget. - * @return {string} The URL of the callback widget. - */ -firebaseui.auth.widget.Config.prototype.getWidgetUrl = function(opt_mode) { - var url = /** @type {string|undefined} */ (this.config_.get('widgetUrl')) || - // If no widget URL is provided, use the current one. - firebaseui.auth.util.getCurrentUrl(); - return this.widgetUrlForMode_(url, opt_mode); -}; - - -/** - * Gets the callback URL for IdP. It always returns an absolute URL. - * @return {string} The callback URL. - */ -firebaseui.auth.widget.Config.prototype.getIdpCallbackUrl = function() { - return goog.Uri.resolve( - window.location.href, this.getWidgetUrl()).toString(); -}; - - -/** - * @param {string} baseUrl The base URL of the widget. - * @param {?firebaseui.auth.widget.Config.WidgetMode=} opt_mode The mode for the - * widget. - * @return {string} The URL of the widget for a specific mode. - * @private - */ -firebaseui.auth.widget.Config.prototype.widgetUrlForMode_ = function(baseUrl, - opt_mode) { - if (opt_mode) { - var key = this.getQueryParameterForWidgetMode(); - return goog.uri.utils.setParam(baseUrl, key, opt_mode); - } else { - return baseUrl; + widgetUrlForMode_(baseUrl, mode = undefined) { + if (mode) { + const key = this.getQueryParameterForWidgetMode(); + return utils.setParam(baseUrl, key, mode); + } else { + return baseUrl; + } } -}; - -/** @return {string} The sign-in URL of the site. */ -firebaseui.auth.widget.Config.prototype.getSignInSuccessUrl = function() { - return /** @type {string} */ (this.config_.get('signInSuccessUrl')); -}; - - -/** @return {boolean} Whether to auto upgrade anonymous users. */ -firebaseui.auth.widget.Config.prototype.autoUpgradeAnonymousUsers = function() { - var autoUpgradeAnonymousUsers = - !!this.config_.get('autoUpgradeAnonymousUsers'); - // Confirm signInFailure callback is provided when anonymous upgrade is - // enabled. This is required to provide a means of recovery for merge conflict - // flows. - if (autoUpgradeAnonymousUsers && !this.getSignInFailureCallback()) { - firebaseui.auth.log.error('Missing "signInFailure" callback: ' + - '"signInFailure" callback needs to be provided when ' + - '"autoUpgradeAnonymousUsers" is set to true.'); + /** @return {string} The sign-in URL of the site. */ + getSignInSuccessUrl() { + return /** @type {string} */ (this.config_.get('signInSuccessUrl')); } - return autoUpgradeAnonymousUsers; -}; - -/** - * Returns the normalized list of valid user-enabled IdPs. - * - * The user may specify each IdP as just a provider ID or as an object - * containing provider ID and additional scopes; this method converts all - * entries to the object format and filters out entries with invalid providers. - * - * @return {!Array} The normalized sign-in options. - * @private - */ -firebaseui.auth.widget.Config.prototype.getSignInOptions_ = function() { - var signInOptions = this.config_.get('signInOptions') || []; - var normalizedOptions = []; - for (var i = 0; i < signInOptions.length; i++) { - var providerConfig = signInOptions[i]; - - // If the config is not in object format, convert to object format. - var normalizedConfig = goog.isObject(providerConfig) ? - providerConfig : {'provider': providerConfig}; - - if (normalizedConfig['provider']) { - normalizedOptions.push(normalizedConfig); + /** @return {boolean} Whether to auto upgrade anonymous users. */ + autoUpgradeAnonymousUsers() { + const autoUpgradeAnonymousUsers = + !!this.config_.get('autoUpgradeAnonymousUsers'); + // Confirm signInFailure callback is provided when anonymous upgrade is + // enabled. This is required to provide a means of recovery for merge + // conflict flows. + if (autoUpgradeAnonymousUsers && !this.getSignInFailureCallback()) { + log.error('Missing "signInFailure" callback: ' + + '"signInFailure" callback needs to be provided when ' + + '"autoUpgradeAnonymousUsers" is set to true.'); } + return autoUpgradeAnonymousUsers; } - return normalizedOptions; -}; - -/** - * Returns the normalized signInOptions for the specified provider. - * - * @param {string} providerId The provider id whose signInOptions are to be - * returned. - * @return {?Object} The normalized sign-in options for the specified provider. - * @private - */ -firebaseui.auth.widget.Config.prototype.getSignInOptionsForProvider_ = - function(providerId) { - var signInOptions = this.getSignInOptions_(); - // For each sign-in option. - for (var i = 0; i < signInOptions.length; i++) { - // Check if current option matches provider ID. - if (signInOptions[i]['provider'] === providerId) { - return signInOptions[i]; + /** + * Returns the normalized list of valid user-enabled IdPs. + * The user may specify each IdP as just a provider ID or as an object + * containing provider ID and additional scopes; this method converts all + * entries to the object format and filters out entries with invalid + * providers. + * @return {!Array} The normalized sign-in options. + * @private + */ + getSignInOptions_() { + const signInOptions = this.config_.get('signInOptions') || []; + const normalizedOptions = []; + for (let i = 0; i < signInOptions.length; i++) { + const providerConfig = signInOptions[i]; + + // If the config is not in object format, convert to object format. + const normalizedConfig = goog.isObject(providerConfig) ? + providerConfig : {'provider': providerConfig}; + + if (normalizedConfig['provider']) { + normalizedOptions.push(normalizedConfig); + } } + return normalizedOptions; } - return null; -}; - -/** - * @return {!Array} The list of supported IdPs including password - * special IdP. - */ -firebaseui.auth.widget.Config.prototype.getProviders = function() { - return goog.array.map(this.getSignInOptions_(), function(option) { - return option['provider']; - }); -}; - - -/** - * @param {string} providerId The provider id whose sign in provider config is - * to be returned. - * @return {?firebaseui.auth.widget.Config.ProviderConfig} The list of sign in - * provider configs for supported IdPs. - */ -firebaseui.auth.widget.Config.prototype.getConfigForProvider = - function(providerId) { - var providerConfigs = this.getProviderConfigs(); - for (var i = 0; i < providerConfigs.length; i++) { - // Check if current option matches provider ID. - if (providerConfigs[i]['providerId'] === providerId) { - return providerConfigs[i]; + /** + * Returns the normalized signInOptions for the specified provider. + * @param {string} providerId The provider id whose signInOptions are to be + * returned. + * @return {?Object} The normalized sign-in options for the specified + * provider. + * @private + */ + getSignInOptionsForProvider_(providerId) { + const signInOptions = this.getSignInOptions_(); + // For each sign-in option. + for (let i = 0; i < signInOptions.length; i++) { + // Check if current option matches provider ID. + if (signInOptions[i]['provider'] === providerId) { + return signInOptions[i]; + } } + return null; } - return null; -}; + /** + * @return {!Array} The list of supported IdPs including password + * special IdP. + */ + getProviders() { + return googArray.map( + this.getSignInOptions_(), (option) => option['provider']); + } -/** - * Returns all available provider configs. For built-in providers, provider - * display name, button color and icon URL are fixed and cannot be overridden. - * - * @return {!Array} The list of - * supported IdP configs. - */ -firebaseui.auth.widget.Config.prototype.getProviderConfigs = function() { - return goog.array.map(this.getSignInOptions_(), function(option) { - if (firebaseui.auth.idp.isSupportedProvider(option['provider']) || - goog.array.contains( - firebaseui.auth.widget.Config.UI_SUPPORTED_PROVIDERS_, - option['provider'])) { - // For built-in providers, provider display name, button color and - // icon URL are fixed. The login hint key is also automatically set for - // built-in providers that support it. - return { - providerId: option['provider'] - }; - } else { - return { - providerId: option['provider'], - providerName: option['providerName'] || null, - buttonColor: option['buttonColor'] || null, - iconUrl: option['iconUrl'] ? - firebaseui.auth.util.sanitizeUrl(option['iconUrl']) : null, - loginHintKey: option['loginHintKey'] || null - }; + /** + * @param {string} providerId The provider id whose sign in provider config + * is to be returned. + * @return {?Config.ProviderConfig} The list of sign in provider configs for + * supported IdPs. + */ + getConfigForProvider(providerId) { + const providerConfigs = this.getProviderConfigs(); + for (let i = 0; i < providerConfigs.length; i++) { + // Check if current option matches provider ID. + if (providerConfigs[i]['providerId'] === providerId) { + return providerConfigs[i]; + } } - }); -}; + return null; + } + /** + * Returns all available provider configs. For built-in providers, provider + * display name, button color and icon URL are fixed and cannot be overridden. + * @return {!Array} The list of supported IdP configs. + */ + getProviderConfigs() { + return googArray.map(this.getSignInOptions_(), (option) => { + if (idp.isSupportedProvider(option['provider']) || + googArray.contains( + UI_SUPPORTED_PROVIDERS, + option['provider'])) { + // For built-in providers, provider display name, button color and + // icon URL are fixed. The login hint key is also automatically set for + // built-in providers that support it. + return { + providerId: option['provider'], + }; + } else { + return { + providerId: option['provider'], + providerName: option['providerName'] || null, + buttonColor: option['buttonColor'] || null, + iconUrl: option['iconUrl'] ? + util.sanitizeUrl(option['iconUrl']) : null, + loginHintKey: option['loginHintKey'] || null, + }; + } + }); + } -/** - * @return {?SmartLockRequestOptions} The googleyolo configuration options if - * available. - */ -firebaseui.auth.widget.Config.prototype.getGoogleYoloConfig = function() { - var supportedAuthMethods = []; - var supportedIdTokenProviders = []; - goog.array.forEach(this.getSignInOptions_(), function(option) { - if (option['authMethod']) { - supportedAuthMethods.push(option['authMethod']); - if (option['clientId']) { - supportedIdTokenProviders.push({ - 'uri': option['authMethod'], - 'clientId': option['clientId'] - }); + /** + * @return {?SmartLockRequestOptions} The googleyolo configuration options + * if available. + */ + getGoogleYoloConfig() { + const supportedAuthMethods = []; + const supportedIdTokenProviders = []; + googArray.forEach(this.getSignInOptions_(), (option) => { + if (option['authMethod']) { + supportedAuthMethods.push(option['authMethod']); + if (option['clientId']) { + supportedIdTokenProviders.push({ + 'uri': option['authMethod'], + 'clientId': option['clientId'], + }); + } } + }); + let config = null; + // Ensure configuration is not empty. At least one supportedIdTokenProviders + // or supportedAuthMethods needs to be provided. + if (this.getCredentialHelper() === + Config.CredentialHelper.GOOGLE_YOLO && + // googleyolo will enforce that clientId is present. Delegate the check + // to it. Errors will be surfaced in the console. + supportedAuthMethods.length) { + config = { + 'supportedIdTokenProviders': supportedIdTokenProviders, + 'supportedAuthMethods': supportedAuthMethods, + }; } - }); - var config = null; - // Ensure configuration is not empty. At least one supportedIdTokenProviders - // or supportedAuthMethods needs to be provided. - if (this.getCredentialHelper() === - firebaseui.auth.CredentialHelper.GOOGLE_YOLO && - // googleyolo will enforce that clientId is present. Delegate the check - // to it. Errors will be surfaced in the console. - supportedAuthMethods.length) { - config = { - 'supportedIdTokenProviders': supportedIdTokenProviders, - 'supportedAuthMethods': supportedAuthMethods - }; + return config; } - return config; -}; + /** + * @return {boolean} Whether the user should be prompted to select an + * account. + */ + isAccountSelectionPromptEnabled() { + // This is only applicable to Google. If prompt is set, googleyolo retrieve + // is disabled. Auto sign-in should be manually disabled. + // Get Google custom parameters. + const googleCustomParameters = this.getProviderCustomParameters( + firebase.auth.GoogleAuthProvider.PROVIDER_ID); + // Google custom parameters must have prompt set to select_account, + // otherwise account selection prompt is considered disabled. + return !!(googleCustomParameters && + googleCustomParameters['prompt'] === 'select_account'); + } -/** - * @return {boolean} Whether the user should be prompted to select an account. - */ -firebaseui.auth.widget.Config.prototype.isAccountSelectionPromptEnabled = - function() { - // This is only applicable to Google. If prompt is set, googleyolo retrieve is - // disabled. Auto sign-in should be manually disabled. - // Get Google custom parameters. - var googleCustomParameters = this.getProviderCustomParameters( - firebase.auth.GoogleAuthProvider.PROVIDER_ID); - // Google custom parameters must have prompt set to select_account, otherwise - // account selection prompt is considered disabled. - return !!(googleCustomParameters && - googleCustomParameters['prompt'] === 'select_account'); -}; - + /** + * Returns the corresponding Firebase Auth provider ID for the googleyolo + * authMethod provided. + * @param {?string} authMethod The googleyolo authMethod whose corresponding + * Firebase provider ID is to be returned. + * @return {?string} The corresponding Firebase provider ID if available. + */ + getProviderIdFromAuthMethod(authMethod) { + let providerId = null; + // For each supported provider. + googArray.forEach(this.getSignInOptions_(), (option) => { + // Check for matching authMethod. + if (option['authMethod'] === authMethod) { + // Get the providerId for that provider. + providerId = option['provider']; + } + }); + // Return the corresponding provider ID. + return providerId; + } -/** - * Returns the corresponding Firebase Auth provider ID for the googleyolo - * authMethod provided. - * @param {?string} authMethod The googleyolo authMethod whose corresponding - * Firebase provider ID is to be returned. - * @return {?string} The corresponding Firebase provider ID if available. - */ -firebaseui.auth.widget.Config.prototype.getProviderIdFromAuthMethod = - function(authMethod) { - var providerId = null; - // For each supported provider. - goog.array.forEach(this.getSignInOptions_(), function(option) { - // Check for matching authMethod. - if (option['authMethod'] === authMethod) { - // Get the providerId for that provider. - providerId = option['provider']; + /** + * @return {?Object} The filtered reCAPTCHA parameters used when + * phone auth provider is enabled. If none provided, null is returned. + */ + getRecaptchaParameters() { + let recaptchaParameters = null; + googArray.forEach(this.getSignInOptions_(), (option) => { + if (option['provider'] == + firebase.auth.PhoneAuthProvider.PROVIDER_ID && + // Confirm valid object. + goog.isObject(option['recaptchaParameters']) && + !goog.isArray(option['recaptchaParameters'])) { + // Clone original object. + recaptchaParameters = googObject.clone(option['recaptchaParameters']); + } + }); + if (recaptchaParameters) { + // Keep track of all blacklisted keys passed by the developer. + const blacklistedKeys = []; + // Go over all blacklisted keys and remove them from the original object. + googArray.forEach( + BLACKLISTED_RECAPTCHA_KEYS, + (key) => { + if (typeof recaptchaParameters[key] !== 'undefined') { + blacklistedKeys.push(key); + delete recaptchaParameters[key]; + } + }); + // Log a warning for invalid keys. + // This will show on each call. + if (blacklistedKeys.length) { + log.warning( + 'The following provided "recaptchaParameters" keys are not ' + + 'allowed: ' + blacklistedKeys.join(', ')); + } } - }); - // Return the corresponding provider ID. - return providerId; -}; + return recaptchaParameters; + } + /** + * @param {string} providerId The provider id whose additional scopes are to + * be returned. + * @return {!Array} The list of additional scopes for specified + * provider. + */ + getProviderAdditionalScopes(providerId) { + // Get provided sign-in options for specified provider. + const signInOptions = this.getSignInOptionsForProvider_(providerId); + const scopes = signInOptions && signInOptions['scopes']; + return goog.isArray(scopes) ? scopes : []; + } -/** - * @return {?Object} The filtered reCAPTCHA parameters used when - * phone auth provider is enabled. If none provided, null is returned. - */ -firebaseui.auth.widget.Config.prototype.getRecaptchaParameters = function() { - var recaptchaParameters = null; - goog.array.forEach(this.getSignInOptions_(), function(option) { - if (option['provider'] == - firebase.auth.PhoneAuthProvider.PROVIDER_ID && - // Confirm valid object. - goog.isObject(option['recaptchaParameters']) && - !goog.isArray(option['recaptchaParameters'])) { - // Clone original object. - recaptchaParameters = goog.object.clone(option['recaptchaParameters']); - } - }); - if (recaptchaParameters) { - // Keep track of all blacklisted keys passed by the developer. - var blacklistedKeys = []; - // Go over all blacklisted keys and remove them from the original object. - goog.array.forEach( - firebaseui.auth.widget.Config.BLACKLISTED_RECAPTCHA_KEYS_, - function(key) { - if (typeof recaptchaParameters[key] !== 'undefined') { - blacklistedKeys.push(key); - delete recaptchaParameters[key]; - } - }); - // Log a warning for invalid keys. - // This will show on each call. - if (blacklistedKeys.length) { - firebaseui.auth.log.warning( - 'The following provided "recaptchaParameters" keys are not ' + - 'allowed: ' + blacklistedKeys.join(', ')); + /** + * @param {string} providerId The provider id whose custom parameters are to + * be returned. + * @return {?Object} The custom parameters for the current provider. + */ + getProviderCustomParameters(providerId) { + // Get provided sign-in options for specified provider. + const signInOptions = this.getSignInOptionsForProvider_(providerId); + // Get customParameters for that provider if available. + const customParameters = signInOptions && signInOptions['customParameters']; + // Custom parameters must be an object. + if (goog.isObject(customParameters)) { + // Clone original custom parameters. + const clonedCustomParameters = googObject.clone(customParameters); + // Delete login_hint from Google provider as it could break the flow. + if (providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID) { + delete clonedCustomParameters['login_hint']; + } + // Delete login from GitHub provider as it could break the flow. + if (providerId === firebase.auth.GithubAuthProvider.PROVIDER_ID) { + delete clonedCustomParameters['login']; + } + return clonedCustomParameters; } + return null; } - return recaptchaParameters; -}; + /** + * Returns the default country to select for phone authentication. + * @return {?string} The default naional number, or null if phone auth is not + * enabled. + */ + getPhoneAuthDefaultNationalNumber() { + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.PhoneAuthProvider.PROVIDER_ID); + // Check if loginHint passed. If so, get the national number from there. + // If no defaultNationalNumber passed, use this value instead. + let defaultPhoneNumber = null; + if (signInOptions && typeof (signInOptions['loginHint']) === 'string') { + defaultPhoneNumber = PhoneNumber.fromString( + /** @type {string} */ (signInOptions['loginHint'])); + } + return (signInOptions && signInOptions['defaultNationalNumber']) || + (defaultPhoneNumber && defaultPhoneNumber.nationalNumber) || null; + } -/** - * @param {string} providerId The provider id whose additional scopes are to be - * returned. - * @return {!Array} The list of additional scopes for specified - * provider. - */ -firebaseui.auth.widget.Config.prototype.getProviderAdditionalScopes = - function(providerId) { - // Get provided sign-in options for specified provider. - var signInOptions = this.getSignInOptionsForProvider_(providerId); - var scopes = signInOptions && signInOptions['scopes']; - return goog.isArray(scopes) ? scopes : []; -}; - + /** + * Returns the default country to select for phone authentication. + * @return {?country.Country} The default country, or null if phone auth is + * not enabled or the country is not found. + */ + getPhoneAuthDefaultCountry() { + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.PhoneAuthProvider.PROVIDER_ID); + const iso2 = signInOptions && signInOptions['defaultCountry'] || null; + const countries = iso2 && country.getCountriesByIso2(iso2); + // Check if loginHint passed. If so, get the country ID from there. + // If no defaultCountry passed, use this value instead. + let defaultPhoneNumber = null; + if (signInOptions && typeof (signInOptions['loginHint']) === 'string') { + defaultPhoneNumber = PhoneNumber.fromString( + /** @type {string} */ (signInOptions['loginHint'])); + } + // If there are multiple entries, pick the first one. + return (countries && countries[0]) || + (defaultPhoneNumber && defaultPhoneNumber.getCountry()) || null; + } -/** - * @param {string} providerId The provider id whose custom parameters are to be - * returned. - * @return {?Object} The custom parameters for the current provider. - */ -firebaseui.auth.widget.Config.prototype.getProviderCustomParameters = - function(providerId) { - // Get provided sign-in options for specified provider. - var signInOptions = this.getSignInOptionsForProvider_(providerId); - // Get customParameters for that provider if available. - var customParameters = signInOptions && signInOptions['customParameters']; - // Custom parameters must be an object. - if (goog.isObject(customParameters)) { - // Clone original custom parameters. - var clonedCustomParameters = goog.object.clone(customParameters); - // Delete login_hint from Google provider as it could break the flow. - if (providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID) { - delete clonedCustomParameters['login_hint']; + /** + * Returns the available countries for phone authentication. + * @return {?Array} The available country list, or null if + * phone Auth is not enabled. + */ + getPhoneAuthAvailableCountries() { + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.PhoneAuthProvider.PROVIDER_ID); + if (!signInOptions) { + return null; + } + const whitelistedCountries = signInOptions['whitelistedCountries']; + const blacklistedCountries = signInOptions['blacklistedCountries']; + // First validate the input. + if (typeof whitelistedCountries !== 'undefined' && + (!goog.isArray(whitelistedCountries) || + whitelistedCountries.length == 0)) { + throw new Error('WhitelistedCountries must be a non-empty array.'); + } + if (typeof blacklistedCountries !== 'undefined' && + (!goog.isArray(blacklistedCountries))) { + throw new Error('BlacklistedCountries must be an array.'); + } + // If both whitelist and blacklist are provided, throw error. + if (whitelistedCountries && blacklistedCountries) { + throw new Error( + 'Both whitelistedCountries and blacklistedCountries are provided.'); + } + // If no whitelist or blacklist provided, return all available countries. + if (!whitelistedCountries && !blacklistedCountries) { + return country.COUNTRY_LIST; } - // Delete login from GitHub provider as it could break the flow. - if (providerId === firebase.auth.GithubAuthProvider.PROVIDER_ID) { - delete clonedCustomParameters['login']; + let countries = []; + const availableCountries = []; + if (whitelistedCountries) { + // Whitelist is provided. + const whitelistedCountryMap = {}; + for (let i = 0; i < whitelistedCountries.length; i++) { + countries = country + .getCountriesByE164OrIsoCode(whitelistedCountries[i]); + // Remove duplicate and overlaps by putting into a map. + for (let j = 0; j < countries.length; j++) { + whitelistedCountryMap[countries[j].e164_key] = countries[j]; + } + } + for (let countryKey in whitelistedCountryMap) { + if (whitelistedCountryMap.hasOwnProperty(countryKey)) { + availableCountries.push(whitelistedCountryMap[countryKey]); + } + } + return availableCountries; + } else { + const blacklistedCountryMap = {}; + for (let i = 0; i < blacklistedCountries.length; i++) { + countries = country + .getCountriesByE164OrIsoCode(blacklistedCountries[i]); + // Remove duplicate and overlaps by putting into a map. + for (let j = 0; j < countries.length; j++) { + blacklistedCountryMap[countries[j].e164_key] = countries[j]; + } + } + for (let k = 0; k < country.COUNTRY_LIST.length; k++) { + if (!googObject.containsKey( + blacklistedCountryMap, + country.COUNTRY_LIST[k].e164_key)) { + availableCountries.push(country.COUNTRY_LIST[k]); + } + } + return availableCountries; } - return clonedCustomParameters; } - return null; -}; - -/** - * Returns the default country to select for phone authentication. - * @return {?string} The default naional number, or null if phone auth is not - * enabled. - */ -firebaseui.auth.widget.Config.prototype.getPhoneAuthDefaultNationalNumber = - function() { - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.PhoneAuthProvider.PROVIDER_ID); - // Check if loginHint passed. If so, get the national number from there. - // If no defaultNationalNumber passed, use this value instead. - var defaultPhoneNumber = null; - if (signInOptions && goog.isString(signInOptions['loginHint'])) { - defaultPhoneNumber = firebaseui.auth.PhoneNumber.fromString( - /** @type {string} */ (signInOptions['loginHint'])); + /** @return {string} The query parameter name for widget mode. */ + getQueryParameterForWidgetMode() { + return /** @type {string} */ ( + this.config_.getRequired('queryParameterForWidgetMode')); } - return (signInOptions && signInOptions['defaultNationalNumber']) || - (defaultPhoneNumber && defaultPhoneNumber.nationalNumber) || null; -}; - -/** - * Returns the default country to select for phone authentication. - * @return {?firebaseui.auth.data.country.Country} The default country, or null - * if phone auth is not enabled or the country is not found. - */ -firebaseui.auth.widget.Config.prototype.getPhoneAuthDefaultCountry = - function() { - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.PhoneAuthProvider.PROVIDER_ID); - var iso2 = signInOptions && signInOptions['defaultCountry'] || null; - var countries = iso2 && firebaseui.auth.data.country.getCountriesByIso2(iso2); - // Check if loginHint passed. If so, get the country ID from there. - // If no defaultCountry passed, use this value instead. - var defaultPhoneNumber = null; - if (signInOptions && goog.isString(signInOptions['loginHint'])) { - defaultPhoneNumber = firebaseui.auth.PhoneNumber.fromString( - /** @type {string} */ (signInOptions['loginHint'])); + /** @return {string} The redirect URL query parameter. */ + getQueryParameterForSignInSuccessUrl() { + return /** @type {string} */ ( + this.config_.getRequired('queryParameterForSignInSuccessUrl')); } - // If there are multiple entries, pick the first one. - return (countries && countries[0]) || - (defaultPhoneNumber && defaultPhoneNumber.getCountry()) || null; -}; - -/** - * Returns the available countries for phone authentication. - * @return {?Array} The available - * country list, or null if phone Auth is not enabled. - */ -firebaseui.auth.widget.Config.prototype.getPhoneAuthAvailableCountries = - function() { - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.PhoneAuthProvider.PROVIDER_ID); - if (!signInOptions) { - return null; - } - var whitelistedCountries = signInOptions['whitelistedCountries']; - var blacklistedCountries = signInOptions['blacklistedCountries']; - // First validate the input. - if (typeof whitelistedCountries !== 'undefined' && - (!goog.isArray(whitelistedCountries) || - whitelistedCountries.length == 0)) { - throw new Error('WhitelistedCountries must be a non-empty array.'); + /** @return {string} The name of the website. */ + getSiteName() { + return /** @type {string} */ (this.config_.getRequired('siteName')); } - if (typeof blacklistedCountries !== 'undefined' && - (!goog.isArray(blacklistedCountries))) { - throw new Error('BlacklistedCountries must be an array.'); - } - // If both whitelist and blacklist are provided, throw error. - if (whitelistedCountries && blacklistedCountries) { - throw new Error( - 'Both whitelistedCountries and blacklistedCountries are provided.'); - } - // If no whitelist or blacklist provided, return all available countries. - if (!whitelistedCountries && !blacklistedCountries) { - return firebaseui.auth.data.country.COUNTRY_LIST; - } - var countries = []; - var availableCountries = []; - if (whitelistedCountries) { - // Whitelist is provided. - var whitelistedCountryMap = {}; - for (var i = 0; i < whitelistedCountries.length; i++) { - countries = firebaseui.auth.data.country - .getCountriesByE164OrIsoCode(whitelistedCountries[i]); - // Remove duplicate and overlaps by putting into a map. - for (var j = 0; j < countries.length; j++) { - whitelistedCountryMap[countries[j].e164_key] = countries[j]; - } - } - for (var countryKey in whitelistedCountryMap) { - if (whitelistedCountryMap.hasOwnProperty(countryKey)) { - availableCountries.push(whitelistedCountryMap[countryKey]); - } + + /** + * @return {?function()} The ToS callback for the site. If URL is provided, + * wraps the URL with a callback function. + */ + getTosUrl() { + const tosUrl = this.config_.get('tosUrl') || null; + const privacyPolicyUrl = this.config_.get('privacyPolicyUrl') || null; + if (tosUrl && !privacyPolicyUrl) { + log.warning( + 'Privacy Policy URL is missing, the link will not be displayed.'); } - return availableCountries; - } else { - var blacklistedCountryMap = {}; - for (var i = 0; i < blacklistedCountries.length; i++) { - countries = firebaseui.auth.data.country - .getCountriesByE164OrIsoCode(blacklistedCountries[i]); - // Remove duplicate and overlaps by putting into a map. - for (var j = 0; j < countries.length; j++) { - blacklistedCountryMap[countries[j].e164_key] = countries[j]; + if (tosUrl && privacyPolicyUrl) { + if (goog.isFunction(tosUrl)) { + return /** @type {function()} */ (tosUrl); + } else if (typeof tosUrl === 'string') { + return () => { + util.open( + /** @type {string} */ (tosUrl), + util.isCordovaInAppBrowserInstalled() ? + '_system' : '_blank'); + }; } } - for (var k = 0; k < firebaseui.auth.data.country.COUNTRY_LIST.length; k++) { - if (!goog.object.containsKey( - blacklistedCountryMap, - firebaseui.auth.data.country.COUNTRY_LIST[k].e164_key)) { - availableCountries.push(firebaseui.auth.data.country.COUNTRY_LIST[k]); + return null; + } + + /** + * @return {?function()} The Privacy Policy callback for the site. If URL is + * provided, wraps the URL with a callback function. + */ + getPrivacyPolicyUrl() { + const tosUrl = this.config_.get('tosUrl') || null; + const privacyPolicyUrl = this.config_.get('privacyPolicyUrl') || null; + if (privacyPolicyUrl && !tosUrl) { + log.warning( + 'Term of Service URL is missing, the link will not be displayed.'); + } + if (tosUrl && privacyPolicyUrl) { + if (goog.isFunction(privacyPolicyUrl)) { + return /** @type {function()} */ (privacyPolicyUrl); + } else if (typeof privacyPolicyUrl === 'string') { + return () => { + util.open( + /** @type {string} */ (privacyPolicyUrl), + util.isCordovaInAppBrowserInstalled() ? + '_system' : '_blank'); + }; } } - return availableCountries; + return null; } -}; - -/** @return {string} The query parameter name for widget mode. */ -firebaseui.auth.widget.Config.prototype.getQueryParameterForWidgetMode = - function() { - return /** @type {string} */ ( - this.config_.getRequired('queryParameterForWidgetMode')); -}; + /** + * @return {boolean} Whether the display name should be displayed. Defaults + * to true. + */ + isDisplayNameRequired() { + // Get provided sign-in options for specified provider. + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.EmailAuthProvider.PROVIDER_ID); + if (signInOptions && + typeof signInOptions['requireDisplayName'] !== 'undefined') { + return /** @type {boolean} */ (!!signInOptions['requireDisplayName']); + } + return true; + } -/** @return {string} The redirect URL query parameter. */ -firebaseui.auth.widget.Config.prototype.getQueryParameterForSignInSuccessUrl = - function() { - return /** @type {string} */ ( - this.config_.getRequired('queryParameterForSignInSuccessUrl')); -}; + /** + * @return {boolean} Whether email link sign-in is allowed. Defaults to false. + */ + isEmailLinkSignInAllowed() { + // Get provided sign-in options for specified provider. + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.EmailAuthProvider.PROVIDER_ID); + return !!(signInOptions && signInOptions['signInMethod'] === + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD); + } -/** @return {string} The name of the website. */ -firebaseui.auth.widget.Config.prototype.getSiteName = function() { - return /** @type {string} */ (this.config_.getRequired('siteName')); -}; + /** + * @return {boolean} Whether password sign-in is allowed. Defaults to true. + */ + isEmailPasswordSignInAllowed() { + return !this.isEmailLinkSignInAllowed(); + } + /** @return {boolean} Whether same device is forced for email link sign-in. */ + isEmailLinkSameDeviceForced() { + // Get provided sign-in options for specified provider. + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.EmailAuthProvider.PROVIDER_ID); -/** - * @return {?function()} The ToS callback for the site. If URL is provided, - * wraps the URL with a callback function. - */ -firebaseui.auth.widget.Config.prototype.getTosUrl = function() { - var tosUrl = this.config_.get('tosUrl') || null; - var privacyPolicyUrl = this.config_.get('privacyPolicyUrl') || null; - if (tosUrl && !privacyPolicyUrl) { - firebaseui.auth.log.warning('Privacy Policy URL is missing, ' + - 'the link will not be displayed.'); + return !!(signInOptions && signInOptions['forceSameDevice']); } - if (tosUrl && privacyPolicyUrl) { - if (goog.isFunction(tosUrl)) { - return /** @type {function()} */ (tosUrl); - } else if (goog.isString(tosUrl)) { - return function() { - firebaseui.auth.util.open( - /** @type {string} */ (tosUrl), - firebaseui.auth.util.isCordovaInAppBrowserInstalled() ? - '_system' : '_blank'); + + /** + * @return {?firebase.auth.ActionCodeSettings} The ActionCodeSettings if + * email link sign-in is enabled. Null is returned otherwise. + */ + getEmailLinkSignInActionCodeSettings() { + if (this.isEmailLinkSignInAllowed()) { + const actionCodeSettings = { + 'url': util.getCurrentUrl(), + 'handleCodeInApp': true, }; + // Get provided sign-in options for specified provider. + const signInOptions = this.getSignInOptionsForProvider_( + firebase.auth.EmailAuthProvider.PROVIDER_ID); + if (signInOptions && + typeof signInOptions['emailLinkSignIn'] === 'function') { + googObject.extend( + actionCodeSettings, + signInOptions['emailLinkSignIn']()); + } + // URL could be provided using a relative path. + actionCodeSettings['url'] = Uri.resolve( + util.getCurrentUrl(), + actionCodeSettings['url']).toString(); + return actionCodeSettings; } + return null; } - return null; -}; + /** @return {boolean} Whether to prefer popup mode. */ + getPopupMode() { + return !!this.config_.get('popupMode'); + } -/** - * @return {?function()} The Privacy Policy callback for the site. If - * URL is provided, wraps the URL with a callback function. - */ -firebaseui.auth.widget.Config.prototype.getPrivacyPolicyUrl = function() { - var tosUrl = this.config_.get('tosUrl') || null; - var privacyPolicyUrl = this.config_.get('privacyPolicyUrl') || null; - if (privacyPolicyUrl && !tosUrl) { - firebaseui.auth.log.warning('Term of Service URL is missing, ' + - 'the link will not be displayed.'); + /** + * Determines whether to show the 'nascar' sign-in buttons screen or + * immediately redirect to the provider's site when there is only a single + * federated provider in signInOptions. In order for this option to take + * effect, the signInOptions must only hold a single federated provider (like + * 'google.com') and signInFlow must be set to 'redirect'. + * @return {boolean} Whether to skip the 'nascar' screen or not. + */ + federatedProviderShouldImmediatelyRedirect() { + const immediateFederatedRedirect = !!this.config_.get( + 'immediateFederatedRedirect'); + const providers = this.getProviders(); + const signInFlow = this.getSignInFlow(); + return immediateFederatedRedirect && + providers.length == 1 && + idp.isFederatedSignInMethod(providers[0]) && + signInFlow == Config.SignInFlow.REDIRECT; } - if (tosUrl && privacyPolicyUrl) { - if (goog.isFunction(privacyPolicyUrl)) { - return /** @type {function()} */ (privacyPolicyUrl); - } else if (goog.isString(privacyPolicyUrl)) { - return function() { - firebaseui.auth.util.open( - /** @type {string} */ (privacyPolicyUrl), - firebaseui.auth.util.isCordovaInAppBrowserInstalled() ? - '_system' : '_blank'); - }; + + /** @return {!Config.SignInFlow} The current sign-in flow. */ + getSignInFlow() { + const signInFlow = this.config_.get('signInFlow'); + // Make sure the select flow is a valid one. + for (let key in Config.SignInFlow) { + if (Config.SignInFlow[key] == signInFlow) { + // Return valid flow. + return Config.SignInFlow[key]; + } } + // Default to redirect flow. + return Config.SignInFlow.REDIRECT; } - return null; -}; - -/** - * @return {boolean} Whether the display name should be displayed. - * Defaults to true. - */ -firebaseui.auth.widget.Config.prototype.isDisplayNameRequired = function() { - // Get provided sign-in options for specified provider. - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.EmailAuthProvider.PROVIDER_ID); - - if (signInOptions && - typeof signInOptions['requireDisplayName'] !== 'undefined') { - return /** @type {boolean} */ (!!signInOptions['requireDisplayName']); + /** + * @return {?function()} The callback to invoke when the widget UI is shown. + */ + getUiShownCallback() { + return /** @type {?function()} */ ( + this.getCallbacks_()['uiShown'] || null); } - return true; -}; - -/** - * @return {boolean} Whether email link sign-in is allowed. Defaults to false. - */ -firebaseui.auth.widget.Config.prototype.isEmailLinkSignInAllowed = function() { - // Get provided sign-in options for specified provider. - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.EmailAuthProvider.PROVIDER_ID); + /** + * @return {?function(?string, ?string)} The callback to invoke when the + * widget UI is changed. Two parameters are passed, the from page + * identifier and the to page identifier. + */ + getUiChangedCallback() { + return /** @type {?function(?string, ?string)} */ ( + this.getCallbacks_()['uiChanged'] || null); + } - return !!(signInOptions && signInOptions['signInMethod'] === - firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD); -}; + /** + * @return {?function(?function())} The callback to invoke right when + * accountchooser.com is triggered, a continue function is passed and + * this should be called when the callback is completed, typically + * asynchronously to proceed to accountchooser.com. + */ + getAccountChooserInvokedCallback() { + return /** @type {?function(?function())} */ ( + this.getCallbacks_()['accountChooserInvoked'] || null); + } + /** + * @return {?function(?Config.AccountChooserResult, ?function())} The callback + * to invoke on return from accountchooser.com invocation. The code + * result string is passed. + */ + getAccountChooserResultCallback() { + /** + * @type {?function(?Config.AccountChooserResult, ?function())} + */ + const callback = this.getCallbacks_()['accountChooserResult'] || null; + return callback; + } -/** - * @return {boolean} Whether password sign-in is allowed. Defaults to true. - */ -firebaseui.auth.widget.Config.prototype.isEmailPasswordSignInAllowed = - function() { - return !this.isEmailLinkSignInAllowed(); -}; + /** + * @return {?Config.signInSuccessCallback} The callback to invoke when the + * user signs in successfully. The signed in firebase user is passed + * into the callback. A second parameter, the Auth credential is also + * returned if available from the sign in with redirect response. An + * optional third parameter, the redirect URL, is also returned if that + * value is set in storage. If it returns `true`, the widget will + * continue to redirect the page to `signInSuccessUrl`. Otherwise, the + * widget stops after it returns. + */ + getSignInSuccessCallback() { + return /** @type {?Config.signInSuccessCallback} */ ( + this.getCallbacks_()['signInSuccess'] || null); + } + /** + * @return {?Config.signInSuccessWithAuthResultCallback} The callback to + * invoke when the user signs in successfully. The Auth result is passed + * into the callback, which includes current user, credential to sign in + * to external Auth instance, additional user info and operation type. + * An optional second parameter, the redirect URL, is also returned if + * that value is set in storage. If it returns `true`, the widget will + * continue to redirect the page to `signInSuccessUrl`. Otherwise, the + * widget stops after it returns. + */ + getSignInSuccessWithAuthResultCallback() { + return ( + /** @type {?Config.signInSuccessWithAuthResultCallback} */ ( + this.getCallbacks_()['signInSuccessWithAuthResult'] || null)); + } -/** @return {boolean} Whether same device is forced for email link sign-in. */ -firebaseui.auth.widget.Config.prototype.isEmailLinkSameDeviceForced = - function() { - // Get provided sign-in options for specified provider. - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.EmailAuthProvider.PROVIDER_ID); + /** + * @return {?Config.signInFailureCallback} The callback to invoke when the + * user fails to sign in. + */ + getSignInFailureCallback() { + return /** @type {?Config.signInFailureCallback} */ ( + this.getCallbacks_()['signInFailure'] || null); + } - return !!(signInOptions && signInOptions['forceSameDevice']); -}; + /** + * @return {!Object} The callback configuration. + * @private + */ + getCallbacks_() { + return /** @type {!Object} */ (this.config_.get('callbacks') || {}); + } + /** + * TODO: for now, only accountchooser.com is available and all logic related + * to credential helper relies on it, so this method is provided for ease of + * use. It should be removed in the future when FirebaseUI supports several + * credential helpers. + * @return {boolean} Whether accountchooser.com is enabled. + */ + isAccountChooserEnabled() { + return this.getCredentialHelper() == + Config.CredentialHelper.ACCOUNT_CHOOSER_COM; + } -/** - * @return {?firebase.auth.ActionCodeSettings} The ActionCodeSettings if email - * link sign-in is enabled. Null is returned otherwise. - */ -firebaseui.auth.widget.Config.prototype.getEmailLinkSignInActionCodeSettings = - function() { - if (this.isEmailLinkSignInAllowed()) { - var actionCodeSettings = { - 'url': firebaseui.auth.util.getCurrentUrl(), - 'handleCodeInApp': true - }; - // Get provided sign-in options for specified provider. - var signInOptions = this.getSignInOptionsForProvider_( - firebase.auth.EmailAuthProvider.PROVIDER_ID); - if (signInOptions && - typeof signInOptions['emailLinkSignIn'] === 'function') { - goog.object.extend( - actionCodeSettings, - signInOptions['emailLinkSignIn']()); + /** + * @return {!Config.CredentialHelper} The credential helper to use. + */ + getCredentialHelper() { + // Always use none for non http or https environment. + // This could change when we support other credential helpers. This is + // unlikely though as smartlock also checks the domain and will not work in + // such environments. + if (!util.isHttpOrHttps()) { + return Config.CredentialHelper.NONE; } - // URL could be provided using a relative path. - actionCodeSettings['url'] = goog.Uri.resolve( - firebaseui.auth.util.getCurrentUrl(), - actionCodeSettings['url']).toString(); - return actionCodeSettings; + const credentialHelper = this.config_.get('credentialHelper'); + // Make sure the credential helper is valid. + for (let key in Config.CredentialHelper) { + if (Config.CredentialHelper[key] == + credentialHelper) { + // Return valid flow. + return Config.CredentialHelper[key]; + } + } + // Default to using accountchooser.com. + return Config.CredentialHelper.ACCOUNT_CHOOSER_COM; } - return null; -}; - -/** @return {boolean} Whether to prefer popup mode. */ -firebaseui.auth.widget.Config.prototype.getPopupMode = function() { - return !!this.config_.get('popupMode'); -}; - - -/** - * Determines whether to show the 'nascar' sign-in buttons screen or - * immediately redirect to the provider's site when there is only a single - * federated provider in signInOptions. In order for this option to take - * effect, the signInOptions must only hold a single federated provider (like - * 'google.com') and signInFlow must be set to 'redirect'. - * @return {boolean} Whether to skip the 'nascar' screen or not. - */ -firebaseui.auth.widget.Config.prototype. - federatedProviderShouldImmediatelyRedirect = function() { - var immediateFederatedRedirect = !!this.config_.get( - 'immediateFederatedRedirect'); - var providers = this.getProviders(); - var signInFlow = this.getSignInFlow(); - return immediateFederatedRedirect && - providers.length == 1 && - firebaseui.auth.idp.isFederatedSignInMethod(providers[0]) && - signInFlow == firebaseui.auth.widget.Config.SignInFlow.REDIRECT; -}; - - -/** - * @return {!firebaseui.auth.widget.Config.SignInFlow} The current sign-in - * flow. - */ -firebaseui.auth.widget.Config.prototype.getSignInFlow = function() { - var signInFlow = this.config_.get('signInFlow'); - // Make sure the select flow is a valid one. - for (var key in firebaseui.auth.widget.Config.SignInFlow) { - if (firebaseui.auth.widget.Config.SignInFlow[key] == signInFlow) { - // Return valid flow. - return firebaseui.auth.widget.Config.SignInFlow[key]; + /** + * Resolves configurations that are implied/restricted by other configs. + * @private + */ + resolveImplicitConfig_() { + if (util.isMobileBrowser()) { + // On mobile we should not use popup + this.config_.update('popupMode', false); } } - // Default to redirect flow. - return firebaseui.auth.widget.Config.SignInFlow.REDIRECT; -}; + /** + * Sets the configurations. + * @param {Object} config The configurations. + */ + setConfig(config) { + for (let name in config) { + try { + this.config_.update(name, config[name]); + } catch (e) { + log.error(`Invalid config: "${name}"`); + } + } + this.resolveImplicitConfig_(); + this.getPhoneAuthAvailableCountries(); + } -/** @return {?function()} The callback to invoke when the widget UI is shown. */ -firebaseui.auth.widget.Config.prototype.getUiShownCallback = function() { - return /** @type {?function()} */ ( - this.getCallbacks_()['uiShown'] || null); -}; - + /** + * Updates the configuration and its descendants with the given value. + * @param {string} name The name of the configuration. + * @param {*} value The value of the configuration. + */ + update(name, value) { + this.config_.update(name, value); + this.getPhoneAuthAvailableCountries(); + } +} /** - * @return {?function(?string, ?string)} The callback to invoke when the widget - * UI is changed. Two parameters are passed, the from page identifier and - * the to page identifier. + * The different credentials helper available. + * @enum {string} */ -firebaseui.auth.widget.Config.prototype.getUiChangedCallback = function() { - return /** @type {?function(?string, ?string)} */ ( - this.getCallbacks_()['uiChanged'] || null); +Config.CredentialHelper = { + ACCOUNT_CHOOSER_COM: 'accountchooser.com', + GOOGLE_YOLO: 'googleyolo', + NONE: 'none', }; - /** - * @return {?function(?function())} The callback to invoke right when - * accountchooser.com is triggered, a continue function is passed and this - * should be called when the callback is completed, typically asynchronously - * to proceed to accountchooser.com. + * Provider ID for continue as guest sign in option. + * @const {string} */ -firebaseui.auth.widget.Config.prototype.getAccountChooserInvokedCallback = - function() { - return /** @type {?function(?function())} */ ( - this.getCallbacks_()['accountChooserInvoked'] || null); -}; - +Config.ANONYMOUS_PROVIDER_ID = 'anonymous'; /** - * @return {?function(?firebaseui.auth.widget.Config.AccountChooserResult, - * ?function())} The callback to invoke on return from accountchooser.com - * invocation. The code result string is passed. + * @typedef {{ + * user: (?firebase.User), + * credential: (?firebase.auth.AuthCredential), + * operationType: (?string|undefined), + * additionalUserInfo: (?firebase.auth.AdditionalUserInfo|undefined) + * }} */ -firebaseui.auth.widget.Config.prototype.getAccountChooserResultCallback = - function() { - /** - * @type {?function(?firebaseui.auth.widget.Config.AccountChooserResult, - * ?function())} - */ - var callback = this.getCallbacks_()['accountChooserResult'] || null; - return callback; -}; - +Config.AuthResult; /** - * @return {?firebaseui.auth.callback.signInSuccess} The callback to invoke when - * the user signs in successfully. The signed in firebase user is passed - * into the callback. A second parameter, the Auth credential is also - * returned if available from the sign in with redirect response. - * An optional third parameter, the redirect URL, is also returned if that - * value is set in storage. If it returns `true`, the widget will - * continue to redirect the page to `signInSuccessUrl`. Otherwise, the - * widget stops after it returns. + * The configuration sign-in success callback. + * @typedef {function( + * !firebase.User, ?firebase.auth.AuthCredential=, string=): boolean} */ -firebaseui.auth.widget.Config.prototype.getSignInSuccessCallback = function() { - return /** @type {?firebaseui.auth.callback.signInSuccess} */ ( - this.getCallbacks_()['signInSuccess'] || null); -}; - +Config.signInSuccessCallback; /** - * @return {?firebaseui.auth.callback.signInSuccessWithAuthResult} The callback - * to invoke when the user signs in successfully. The Auth result is passed - * into the callback, which includes current user, credential to sign in to - * external Auth instance, additional user info and operation type. - * An optional second parameter, the redirect URL, is also returned if that - * value is set in storage. If it returns `true`, the widget will - * continue to redirect the page to `signInSuccessUrl`. Otherwise, the - * widget stops after it returns. + * The configuration sign-in success callback which takes AuthResult as input. + * @typedef {function(!Config.AuthResult, string=): boolean} */ -firebaseui.auth.widget.Config.prototype.getSignInSuccessWithAuthResultCallback = - function() { - return /** @type {?firebaseui.auth.callback.signInSuccessWithAuthResult} */ ( - this.getCallbacks_()['signInSuccessWithAuthResult'] || null); -}; - +Config.signInSuccessWithAuthResultCallback; /** - * @return {?firebaseui.auth.callback.signInFailure} The callback to invoke when - * the user fails to sign in. + * The configuration sign-in failure callback. + * @typedef {function(!AuthUIError): (!Promise|void)} */ -firebaseui.auth.widget.Config.prototype.getSignInFailureCallback = function() { - return /** @type {?firebaseui.auth.callback.signInFailure} */ ( - this.getCallbacks_()['signInFailure'] || null); -}; - +Config.signInFailureCallback; /** - * @return {!Object} The callback configuration. - * @private + * The accountchooser.com result codes. + * @enum {string} */ -firebaseui.auth.widget.Config.prototype.getCallbacks_ = function() { - return /** @type {!Object} */ (this.config_.get('callbacks') || {}); +Config.AccountChooserResult = { + EMPTY: 'empty', + UNAVAILABLE: 'unavailable', + ACCOUNT_SELECTED: 'accountSelected', + ADD_ACCOUNT: 'addAccount', }; - /** - * TODO: for now, only accountchooser.com is available and all logic related to - * credential helper relies on it, so this method is provided for ease of use. - * It should be removed in the future when FirebaseUI supports several - * credential helpers. - * - * @return {boolean} Whether accountchooser.com is enabled. + * The type of sign-in flow. + * @enum {string} */ -firebaseui.auth.widget.Config.prototype.isAccountChooserEnabled = function() { - return this.getCredentialHelper() == - firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM; +Config.SignInFlow = { + POPUP: 'popup', + REDIRECT: 'redirect', }; /** - * @return {!firebaseui.auth.CredentialHelper} The credential helper to use. + * The provider config object for generic providers. + * providerId: The provider ID. + * providerName: The display name of the provider. + * buttonColor: The color of the sign in button. + * iconUrl: The URL of the icon on sign in button. + * loginHintKey: The name to use for the optional login hint parameter. + * @typedef {{ + * providerId: string, + * providerName: (?string|undefined), + * buttonColor: (?string|undefined), + * iconUrl: (?string|undefined), + * loginHintKey: (?string|undefined) + * }} */ -firebaseui.auth.widget.Config.prototype.getCredentialHelper = function() { - // Always use none for non http or https environment. - // This could change when we support other credential helpers. This is - // unlikely though as smartlock also checks the domain and will not work in - // such environments. - if (!firebaseui.auth.util.isHttpOrHttps()) { - return firebaseui.auth.CredentialHelper.NONE; - } - var credentialHelper = this.config_.get('credentialHelper'); - // Make sure the credential helper is valid. - for (var key in firebaseui.auth.CredentialHelper) { - if (firebaseui.auth.CredentialHelper[key] == credentialHelper) { - // Return valid flow. - return firebaseui.auth.CredentialHelper[key]; - } - } - // Default to using accountchooser.com. - return firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM; -}; - +Config.ProviderConfig; /** - * Resolves configurations that are implied/restricted by other configs. - * - * @private + * Enums for callback widget mode. Please alphabetize by names. + * @enum {string} */ -firebaseui.auth.widget.Config.prototype.resolveImplicitConfig_ = function() { - if (firebaseui.auth.util.isMobileBrowser()) { - // On mobile we should not use popup - this.config_.update('popupMode', false); - } +Config.WidgetMode = { + CALLBACK: 'callback', + RECOVER_EMAIL: 'recoverEmail', + RESET_PASSWORD: 'resetPassword', + SELECT: 'select', + SIGN_IN: 'signIn', + VERIFY_EMAIL: 'verifyEmail', }; - /** - * Sets the configurations. - * - * @param {Object} config The configurations. + * FirebaseUI supported providers in sign in option. + * @const {!Array} */ -firebaseui.auth.widget.Config.prototype.setConfig = function(config) { - for (var name in config) { - try { - this.config_.update(name, config[name]); - } catch (e) { - firebaseui.auth.log.error('Invalid config: "' + name + '"'); - } - } - this.resolveImplicitConfig_(); - this.getPhoneAuthAvailableCountries(); -}; - +const UI_SUPPORTED_PROVIDERS = ['anonymous']; /** - * Updates the configuration and its descendants with the given value. - * - * @param {string} name The name of the configuration. - * @param {*} value The value of the configuration. + * @const @type {!Array} List of blacklisted reCAPTCHA parameter + * keys. */ -firebaseui.auth.widget.Config.prototype.update = function(name, value) { - this.config_.update(name, value); - this.getPhoneAuthAvailableCountries(); -}; +const BLACKLISTED_RECAPTCHA_KEYS = [ + 'sitekey', 'tabindex', 'callback', 'expired-callback']; + +exports = Config; diff --git a/javascript/widgets/config_test.js b/javascript/widgets/config_test.js index 9d00b75f..559bc73b 100644 --- a/javascript/widgets/config_test.js +++ b/javascript/widgets/config_test.js @@ -12,1641 +12,1561 @@ * limitations under the License. */ -/** - * @fileoverview Tests for config.js - */ +/** @fileoverview Tests for config.js */ + +goog.module('firebaseui.auth.widget.ConfigTest'); +goog.setTestOnly(); + +const Config = goog.require('firebaseui.auth.widget.Config'); +const FakeUtil = goog.require('firebaseui.auth.testing.FakeUtil'); +const PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); +const googArray = goog.require('goog.array'); +const log = goog.require('firebaseui.auth.log'); +const testSuite = goog.require('goog.testing.testSuite'); +const testing = goog.require('goog.testing'); +const util = goog.require('firebaseui.auth.util'); + +let config; +const stub = new PropertyReplacer(); +let testUtil; +let errorLogMessages = []; +let warningLogMessages = []; + +testSuite({ + setUp() { + config = new Config(); + // Remember error log messages. + stub.replace(log, 'error', (msg) => { + errorLogMessages.push(msg); + }); + // Remember error warning messages. + stub.replace(log, 'warning', (msg) => { + warningLogMessages.push(msg); + }); + goog.global.firebase = {}; + const firebase = goog.global.firebase; + firebase.auth = { + GoogleAuthProvider: {PROVIDER_ID: 'google.com'}, + GithubAuthProvider: {PROVIDER_ID: 'github.com'}, + FacebookAuthProvider: {PROVIDER_ID: 'facebook.com'}, + EmailAuthProvider: { + EMAIL_LINK_SIGN_IN_METHOD: 'emailLink', + EMAIL_PASSWORD_SIGN_IN_METHOD: 'password', + PROVIDER_ID: 'password', + }, + PhoneAuthProvider: {PROVIDER_ID: 'phone'}, + }; + testUtil = new FakeUtil().install(); + }, + + tearDown() { + errorLogMessages = []; + warningLogMessages = []; + stub.reset(); + }, + + testGetAcUiConfig() { + assertNull(config.getAcUiConfig()); + const ui = {favicon: 'http://localhost/favicon.ico'}; + config.update('acUiConfig', ui); + assertObjectEquals(ui, config.getAcUiConfig()); + }, + + testGetQueryParameterForSignInSuccessUrl() { + // Confirm default value for query parameter for sign-in success URL. + assertEquals( + 'signInSuccessUrl', + config.getQueryParameterForSignInSuccessUrl()); + // Update query parameter. + config.update('queryParameterForSignInSuccessUrl', 'continue'); + // Confirm value changed. + assertEquals( + 'continue', + config.getQueryParameterForSignInSuccessUrl()); + }, + + testGetRequiredWidgetUrl() { + assertThrows(() => {config.getRequiredWidgetUrl();}); + config.update('widgetUrl', 'http://localhost/callback'); + + let widgetUrl = config.getRequiredWidgetUrl(); + assertEquals('http://localhost/callback', widgetUrl); + widgetUrl = config.getRequiredWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals('http://localhost/callback?mode=select', widgetUrl); + + config.update('queryParameterForWidgetMode', 'mode2'); + widgetUrl = config.getRequiredWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals('http://localhost/callback?mode2=select', widgetUrl); + }, + + testFederatedProviderShouldImmediatelyRedirect() { + // Returns true when immediateFederatedRedirect is set, there is + // only one federated provider and the signInFlow is set to redirect. + config.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], + 'signInFlow': Config.SignInFlow.REDIRECT, + }); + assertTrue(config.federatedProviderShouldImmediatelyRedirect()); -goog.provide('firebaseui.auth.widget.ConfigTest'); - -goog.require('firebaseui.auth.CredentialHelper'); -goog.require('firebaseui.auth.log'); -goog.require('firebaseui.auth.testing.FakeUtil'); -goog.require('firebaseui.auth.util'); -goog.require('firebaseui.auth.widget.Config'); -goog.require('goog.array'); -goog.require('goog.testing'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); - -goog.setTestOnly('firebaseui.auth.widget.ConfigTest'); - -var config; -var stub = new goog.testing.PropertyReplacer(); -var testUtil; -var errorLogMessages = []; -var warningLogMessages = []; -var firebase = {}; - - -function setUp() { - config = new firebaseui.auth.widget.Config(); - // Remember error log messages. - stub.replace(firebaseui.auth.log, 'error', function(msg) { - errorLogMessages.push(msg); - }); - // Remember error warning messages. - stub.replace(firebaseui.auth.log, 'warning', function(msg) { - warningLogMessages.push(msg); - }); - firebase.auth = { - GoogleAuthProvider: {PROVIDER_ID: 'google.com'}, - GithubAuthProvider: {PROVIDER_ID: 'github.com'}, - FacebookAuthProvider: {PROVIDER_ID: 'facebook.com'}, - EmailAuthProvider: { - EMAIL_LINK_SIGN_IN_METHOD: 'emailLink', - EMAIL_PASSWORD_SIGN_IN_METHOD: 'password', - PROVIDER_ID: 'password', - }, - PhoneAuthProvider: {PROVIDER_ID: 'phone'} - }; - testUtil = new firebaseui.auth.testing.FakeUtil().install(); -} - - -function tearDown() { - errorLogMessages = []; - warningLogMessages = []; - stub.reset(); -} - - -function testGetAcUiConfig() { - assertNull(config.getAcUiConfig()); - var ui = {favicon: 'http://localhost/favicon.ico'}; - config.update('acUiConfig', ui); - assertObjectEquals(ui, config.getAcUiConfig()); -} - - -function testGetQueryParameterForSignInSuccessUrl() { - // Confirm default value for query parameter for sign-in success URL. - assertEquals( - 'signInSuccessUrl', - config.getQueryParameterForSignInSuccessUrl()); - // Update query parameter. - config.update('queryParameterForSignInSuccessUrl', 'continue'); - // Confirm value changed. - assertEquals( - 'continue', - config.getQueryParameterForSignInSuccessUrl()); -} - - -function testGetRequiredWidgetUrl() { - assertThrows(function() {config.getRequiredWidgetUrl();}); - config.update('widgetUrl', 'http://localhost/callback'); - - var widgetUrl = config.getRequiredWidgetUrl(); - assertEquals('http://localhost/callback', widgetUrl); - widgetUrl = config.getRequiredWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals('http://localhost/callback?mode=select', widgetUrl); - - config.update('queryParameterForWidgetMode', 'mode2'); - widgetUrl = config.getRequiredWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals('http://localhost/callback?mode2=select', widgetUrl); -} - - -function testFederatedProviderShouldImmediatelyRedirect() { - // Returns true when immediateFederatedRedirect is set, there is - // only one federated provider and the signInFlow is set to redirect. - config.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT - }); - assertTrue(config.federatedProviderShouldImmediatelyRedirect()); - - // Returns false if the immediateFederatedRedirect option is false. - config.setConfig({ - 'immediateFederatedRedirect': false, - 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT - }); - assertFalse(config.federatedProviderShouldImmediatelyRedirect()); - - // Returns false if the provider is not a federated provider. - config.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT - }); - assertFalse(config.federatedProviderShouldImmediatelyRedirect()); - - // Returns false if there is more than one federated provider. - config.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [ - firebase.auth.GoogleAuthProvider.PROVIDER_ID, - firebase.auth.FacebookAuthProvider.PROVIDER_ID - ], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT - }); - assertFalse(config.federatedProviderShouldImmediatelyRedirect()); - - // Returns false if there is more than one provider of any kind. - config.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [ - firebase.auth.GoogleAuthProvider.PROVIDER_ID, - firebase.auth.EmailAuthProvider.PROVIDER_ID - ], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT - }); - assertFalse(config.federatedProviderShouldImmediatelyRedirect()); - - // Returns false if signInFlow is using a popup. - config.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.POPUP - }); - assertFalse(config.federatedProviderShouldImmediatelyRedirect()); -} - - -function testGetSignInFlow() { - // Confirm default value for sign-in flow - assertEquals( - 'redirect', - config.getSignInFlow()); - // Update sign-in flow parameter to popup flow. - config.update('signInFlow', 'popup'); - // Confirm value changed. - assertEquals( - 'popup', - config.getSignInFlow()); - // Use an invalid option. Redirect should be returned. - // Update sign-in flow parameter. - config.update('signInFlow', 'continue'); - assertEquals( - 'redirect', - config.getSignInFlow()); -} - - -function testGetWidgetUrl_notSpecified() { - var widgetUrl = config.getWidgetUrl(); - assertEquals(window.location.href, widgetUrl); - widgetUrl = config.getWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals(window.location.href + '?mode=select', widgetUrl); - - config.update('queryParameterForWidgetMode', 'mode2'); - widgetUrl = config.getWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals(window.location.href + '?mode2=select', widgetUrl); -} - - -function testGetWidgetUrl_notSpecified_withQueryAndFragment() { - // Simulate current URL has mode/mode2 queries, other query parameters and a - // fragment. - stub.replace( - firebaseui.auth.util, - 'getCurrentUrl', - function() { - return 'http://www.example.com/path/?mode=foo&mode2=bar#a=1'; - }); - var widgetUrl = config.getWidgetUrl(); - // The same current URL should be returned. - assertEquals( - firebaseui.auth.util.getCurrentUrl(), widgetUrl); - // Only the mode query param should be overwritten. - widgetUrl = config.getWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals( - 'http://www.example.com/path/?mode2=bar&mode=select#a=1', widgetUrl); - - // Only the mode2 query param should be overwritten. - config.update('queryParameterForWidgetMode', 'mode2'); - widgetUrl = config.getWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals( - 'http://www.example.com/path/?mode=foo&mode2=select#a=1', widgetUrl); -} - - -function testGetWidgetUrl_specified() { - config.update('widgetUrl', 'http://localhost/callback'); - var widgetUrl = config.getWidgetUrl(); - assertEquals('http://localhost/callback', widgetUrl); - widgetUrl = config.getWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals('http://localhost/callback?mode=select', widgetUrl); - - config.update('queryParameterForWidgetMode', 'mode2'); - widgetUrl = config.getWidgetUrl( - firebaseui.auth.widget.Config.WidgetMode.SELECT); - assertEquals('http://localhost/callback?mode2=select', widgetUrl); -} - - -function testGetSignInSuccessUrl() { - assertUndefined(config.getSignInSuccessUrl()); - config.update('signInSuccessUrl', 'http://localhost/home'); - assertEquals( - 'http://localhost/home', config.getSignInSuccessUrl()); -} - - -function testGetProviders_providerIds() { - assertArrayEquals([], config.getProviders()); - config.update('signInOptions', ['google.com', 'github.com', 'twitter.com']); - // Check that predefined OAuth providers are included in the list in the - // correct order. - assertArrayEquals( - ['google.com', 'github.com', 'twitter.com'], - config.getProviders()); - - // Test when password accounts are to be enabled. - config.update('signInOptions', ['google.com', 'password']); - // Check that password accounts are included in the list in the correct - // order. - assertArrayEquals(['google.com', 'password'], config.getProviders()); - - // Test when phone accounts are to be enabled. - config.update('signInOptions', ['google.com', 'phone']); - // Check that phone accounts are included in the list in the correct - // order. - assertArrayEquals(['google.com', 'phone'], config.getProviders()); - - // Test when anonymous provider is to be enabled. - config.update('signInOptions', ['google.com', 'anonymous']); - // Check that anonymous provider is included in the list in the correct - // order. - assertArrayEquals(['google.com', 'anonymous'], config.getProviders()); - - // Test when generic provider is to be enabled. - config.update('signInOptions', - [ - 'google.com', - { - 'provider': 'microsoft.com', - 'providerName': 'Microsoft', - 'buttonColor': '#FFB6C1', - 'iconUrl': '' - } - ]); - // Check that generic provider is included in the list in the correct - // order. - assertArrayEquals(['google.com', 'microsoft.com'], config.getProviders()); -} - - -function testGetProviders_fullConfig() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'scopes': ['foo', 'bar'] - }, - {'provider': 'github.com'}, - 'facebook.com', - { - 'provider': 'microsoft.com', - 'providerName': 'Microsoft', - 'buttonColor': '#FFB6C1', - 'iconUrl': '' - - }, - {'not a': 'valid config'}, - {'provider': 'phone', 'recaptchaParameters': {'size': 'invisible'}}, - {'provider': 'anonymous'} - ]); - // Check that invalid configs are not included. - assertArrayEquals( - ['google.com', 'github.com', 'facebook.com', 'microsoft.com', 'phone', - 'anonymous'], - config.getProviders()); -} - - -function testGetProviderConfigs() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'scopes': ['foo', 'bar'], - // providerName, buttonColor and iconUrl should be override with null. - 'providerName': 'Google', - 'buttonColor': '#FFB6C1', - 'iconUrl': '' - }, - 'facebook.com', - { - 'provider': 'microsoft.com', - 'providerName': 'Microsoft', - 'buttonColor': '#FFB6C1', - 'iconUrl': '', - 'loginHintKey': 'login_hint' - }, - {'not a': 'valid config'}, - { - 'provider': 'yahoo.com', - } - ]); - var providerConfigs = config.getProviderConfigs(); - assertEquals(4, providerConfigs.length); - assertObjectEquals({ - providerId: 'google.com', - }, providerConfigs[0]); - assertObjectEquals({ - providerId: 'facebook.com', - }, providerConfigs[1]); - assertObjectEquals({ - providerId: 'microsoft.com', - providerName: 'Microsoft', - buttonColor: '#FFB6C1', - iconUrl: '', - loginHintKey: 'login_hint' - }, providerConfigs[2]); - assertObjectEquals({ - providerId: 'yahoo.com', - providerName: null, - buttonColor: null, - iconUrl: null, - loginHintKey: null - }, providerConfigs[3]); -} - - -function testGetConfigForProvider() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'scopes': ['foo', 'bar'], - // providerName, buttonColor and iconUrl should be override with null. - 'providerName': 'Google', - 'buttonColor': '#FFB6C1', - 'iconUrl': '' - }, - 'facebook.com', - { + // Returns false if the immediateFederatedRedirect option is false. + config.setConfig({ + 'immediateFederatedRedirect': false, + 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], + 'signInFlow': Config.SignInFlow.REDIRECT, + }); + assertFalse(config.federatedProviderShouldImmediatelyRedirect()); + + // Returns false if the provider is not a federated provider. + config.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], + 'signInFlow': Config.SignInFlow.REDIRECT, + }); + assertFalse(config.federatedProviderShouldImmediatelyRedirect()); + + // Returns false if there is more than one federated provider. + config.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [ + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.FacebookAuthProvider.PROVIDER_ID, + ], + 'signInFlow': Config.SignInFlow.REDIRECT, + }); + assertFalse(config.federatedProviderShouldImmediatelyRedirect()); + + // Returns false if there is more than one provider of any kind. + config.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [ + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.EmailAuthProvider.PROVIDER_ID, + ], + 'signInFlow': Config.SignInFlow.REDIRECT, + }); + assertFalse(config.federatedProviderShouldImmediatelyRedirect()); + + // Returns false if signInFlow is using a popup. + config.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], + 'signInFlow': Config.SignInFlow.POPUP, + }); + assertFalse(config.federatedProviderShouldImmediatelyRedirect()); + }, + + testGetSignInFlow() { + // Confirm default value for sign-in flow + assertEquals( + 'redirect', + config.getSignInFlow()); + // Update sign-in flow parameter to popup flow. + config.update('signInFlow', 'popup'); + // Confirm value changed. + assertEquals( + 'popup', + config.getSignInFlow()); + // Use an invalid option. Redirect should be returned. + // Update sign-in flow parameter. + config.update('signInFlow', 'continue'); + assertEquals( + 'redirect', + config.getSignInFlow()); + }, + + testGetWidgetUrl_notSpecified() { + let widgetUrl = config.getWidgetUrl(); + assertEquals(window.location.href, widgetUrl); + widgetUrl = config.getWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals(window.location.href + '?mode=select', widgetUrl); + + config.update('queryParameterForWidgetMode', 'mode2'); + widgetUrl = config.getWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals(window.location.href + '?mode2=select', widgetUrl); + }, + + testGetWidgetUrl_notSpecified_withQueryAndFragment() { + // Simulate current URL has mode/mode2 queries, other query parameters and a + // fragment. + stub.replace( + util, + 'getCurrentUrl', + () => 'http://www.example.com/path/?mode=foo&mode2=bar#a=1'); + let widgetUrl = config.getWidgetUrl(); + // The same current URL should be returned. + assertEquals( + util.getCurrentUrl(), widgetUrl); + // Only the mode query param should be overwritten. + widgetUrl = config.getWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals( + 'http://www.example.com/path/?mode2=bar&mode=select#a=1', widgetUrl); + + // Only the mode2 query param should be overwritten. + config.update('queryParameterForWidgetMode', 'mode2'); + widgetUrl = config.getWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals( + 'http://www.example.com/path/?mode=foo&mode2=select#a=1', widgetUrl); + }, + + testGetWidgetUrl_specified() { + config.update('widgetUrl', 'http://localhost/callback'); + let widgetUrl = config.getWidgetUrl(); + assertEquals('http://localhost/callback', widgetUrl); + widgetUrl = config.getWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals('http://localhost/callback?mode=select', widgetUrl); + + config.update('queryParameterForWidgetMode', 'mode2'); + widgetUrl = config.getWidgetUrl( + Config.WidgetMode.SELECT); + assertEquals('http://localhost/callback?mode2=select', widgetUrl); + }, + + testGetSignInSuccessUrl() { + assertUndefined(config.getSignInSuccessUrl()); + config.update('signInSuccessUrl', 'http://localhost/home'); + assertEquals( + 'http://localhost/home', config.getSignInSuccessUrl()); + }, + + testGetProviders_providerIds() { + assertArrayEquals([], config.getProviders()); + config.update('signInOptions', ['google.com', 'github.com', 'twitter.com']); + // Check that predefined OAuth providers are included in the list in the + // correct order. + assertArrayEquals( + ['google.com', 'github.com', 'twitter.com'], + config.getProviders()); + + // Test when password accounts are to be enabled. + config.update('signInOptions', ['google.com', 'password']); + // Check that password accounts are included in the list in the correct + // order. + assertArrayEquals(['google.com', 'password'], config.getProviders()); + + // Test when phone accounts are to be enabled. + config.update('signInOptions', ['google.com', 'phone']); + // Check that phone accounts are included in the list in the correct + // order. + assertArrayEquals(['google.com', 'phone'], config.getProviders()); + + // Test when anonymous provider is to be enabled. + config.update('signInOptions', ['google.com', 'anonymous']); + // Check that anonymous provider is included in the list in the correct + // order. + assertArrayEquals(['google.com', 'anonymous'], config.getProviders()); + + // Test when generic provider is to be enabled. + config.update('signInOptions', + [ + 'google.com', + { + 'provider': 'microsoft.com', + 'providerName': 'Microsoft', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + }, + ]); + // Check that generic provider is included in the list in the correct + // order. + assertArrayEquals(['google.com', 'microsoft.com'], config.getProviders()); + }, + + testGetProviders_fullConfig() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'scopes': ['foo', 'bar'], + }, + {'provider': 'github.com'}, + 'facebook.com', + { + 'provider': 'microsoft.com', + 'providerName': 'Microsoft', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + + }, + {'not a': 'valid config'}, + {'provider': 'phone', 'recaptchaParameters': {'size': 'invisible'}}, + {'provider': 'anonymous'}, + ]); + // Check that invalid configs are not included. + assertArrayEquals( + ['google.com', 'github.com', 'facebook.com', 'microsoft.com', 'phone', + 'anonymous'], + config.getProviders()); + }, + + testGetProviderConfigs() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'scopes': ['foo', 'bar'], + // providerName, buttonColor and iconUrl should be override with null. + 'providerName': 'Google', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + }, + 'facebook.com', + { + 'provider': 'microsoft.com', + 'providerName': 'Microsoft', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + 'loginHintKey': 'login_hint', + }, + {'not a': 'valid config'}, + { + 'provider': 'yahoo.com', + }, + ]); + const providerConfigs = config.getProviderConfigs(); + assertEquals(4, providerConfigs.length); + assertObjectEquals({ + providerId: 'google.com', + }, providerConfigs[0]); + assertObjectEquals({ + providerId: 'facebook.com', + }, providerConfigs[1]); + assertObjectEquals({ + providerId: 'microsoft.com', + providerName: 'Microsoft', + buttonColor: '#FFB6C1', + iconUrl: '', + loginHintKey: 'login_hint', + }, providerConfigs[2]); + assertObjectEquals({ + providerId: 'yahoo.com', + providerName: null, + buttonColor: null, + iconUrl: null, + loginHintKey: null, + }, providerConfigs[3]); + }, + + testGetConfigForProvider() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'scopes': ['foo', 'bar'], + // providerName, buttonColor and iconUrl should be override with null. + 'providerName': 'Google', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + }, + 'facebook.com', + { + 'provider': 'microsoft.com', + 'providerName': 'Microsoft', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + 'loginHintKey': 'login_hint', + }, + {'not a': 'valid config'}, + { + 'provider': 'yahoo.com', + 'providerName': 'Yahoo', + 'buttonColor': '#FFB6C1', + 'iconUrl': 'javascript:doEvilStuff()', + }, + ]); + assertObjectEquals({ + providerId: 'google.com', + }, config.getConfigForProvider('google.com')); + assertObjectEquals({ + providerId: 'facebook.com', + }, config.getConfigForProvider('facebook.com')); + assertObjectEquals({ + providerId: 'microsoft.com', + providerName: 'Microsoft', + buttonColor: '#FFB6C1', + iconUrl: '', + loginHintKey: 'login_hint', + }, config.getConfigForProvider('microsoft.com')); + assertNull(config.getConfigForProvider('INVALID_ID')); + assertObjectEquals({ + providerId: 'yahoo.com', + providerName: 'Yahoo', + buttonColor: '#FFB6C1', + iconUrl: 'about:invalid#zClosurez', + loginHintKey: null, + }, config.getConfigForProvider('yahoo.com')); + }, + + testGetRecaptchaParameters() { + // Empty config. + assertNull(config.getRecaptchaParameters()); + + // No phone provider config. + config.update('signInOptions', [{'provider': 'google.com'}]); + assertNull(config.getRecaptchaParameters()); + + // Phone config with no additional parameters. + config.update( + 'signInOptions', + ['github.com', {'provider': 'google.com'}, {'provider': 'phone'}]); + assertNull(config.getRecaptchaParameters()); + + // Phone config with invalid reCAPTCHA parameters. + config.update( + 'signInOptions', + ['github.com', {'provider': 'google.com'}, + {'provider': 'phone', 'recaptchaParameters': [1, true]}, 'password']); + assertNull(config.getRecaptchaParameters()); + + // Phone config with an empty object reCAPTCHA parameters. + config.update( + 'signInOptions', + ['github.com', {'provider': 'google.com'}, + {'provider': 'phone', 'recaptchaParameters': {}}, 'password']); + assertObjectEquals({}, config.getRecaptchaParameters()); + + // Confirm no warning logged so far. + assertArrayEquals([], warningLogMessages); + + // Phone config with blacklisted reCAPTCHA parameters. + const blacklist = { + 'sitekey': 'SITEKEY', + 'tabindex': 0, + 'callback': function(token) {}, + 'expired-callback': function() {}, + }; + config.update( + 'signInOptions', + ['github.com', {'provider': 'google.com'}, + {'provider': 'phone', 'recaptchaParameters': blacklist}, 'password']); + assertObjectEquals({}, config.getRecaptchaParameters()); + // Expected warning should be logged. + assertArrayEquals( + [ + 'The following provided "recaptchaParameters" keys are not ' + + 'allowed: sitekey, tabindex, callback, expired-callback', + ], warningLogMessages); + // Reset warnings. + warningLogMessages = []; + + // Phone config with blacklisted, valid and invalid reCAPTCHA parameters. + const mixed = { + 'sitekey': 'SITEKEY', + 'tabindex': 0, + 'callback': function(token) {}, + 'expired-callback': function() {}, + 'type': 'audio', + 'size': 'invisible', + 'badge': 'bottomleft', + 'theme': 'dark', + 'foo': 'bar', + }; + const expectedParameters = { + 'type': 'audio', + 'size': 'invisible', + 'badge': 'bottomleft', + 'theme': 'dark', + 'foo': 'bar', + }; + config.update( + 'signInOptions', + ['github.com', {'provider': 'google.com'}, + {'provider': 'phone', 'recaptchaParameters': mixed}, 'password']); + assertObjectEquals(expectedParameters, config.getRecaptchaParameters()); + // Expected warning should be logged. + assertArrayEquals( + [ + 'The following provided "recaptchaParameters" keys are not ' + + 'allowed: sitekey, tabindex, callback, expired-callback', + ], warningLogMessages); + + // No error should be logged. + assertArrayEquals([], errorLogMessages); + }, + + testGetProviderCustomParameters_noSignInOptions() { + config.update('signInOptions', null); + assertNull(config.getProviderCustomParameters('google.com')); + }, + + testGetProviderCustomParameters_genericProvider() { + config.update('signInOptions', [{ 'provider': 'microsoft.com', 'providerName': 'Microsoft', 'buttonColor': '#FFB6C1', 'iconUrl': '', - 'loginHintKey': 'login_hint' - }, - {'not a': 'valid config'}, - { - 'provider': 'yahoo.com', - 'providerName': 'Yahoo', - 'buttonColor': '#FFB6C1', - 'iconUrl': 'javascript:doEvilStuff()' - } - ]); - assertObjectEquals({ - providerId: 'google.com' - }, config.getConfigForProvider('google.com')); - assertObjectEquals({ - providerId: 'facebook.com' - }, config.getConfigForProvider('facebook.com')); - assertObjectEquals({ - providerId: 'microsoft.com', - providerName: 'Microsoft', - buttonColor: '#FFB6C1', - iconUrl: '', - loginHintKey: 'login_hint' - }, config.getConfigForProvider('microsoft.com')); - assertNull(config.getConfigForProvider('INVALID_ID')); - assertObjectEquals({ - providerId: 'yahoo.com', - providerName: 'Yahoo', - buttonColor: '#FFB6C1', - iconUrl: 'about:invalid#zClosurez', - loginHintKey: null - }, config.getConfigForProvider('yahoo.com')); -} - - -function testGetRecaptchaParameters() { - // Empty config. - assertNull(config.getRecaptchaParameters()); - - // No phone provider config. - config.update('signInOptions', [{'provider': 'google.com'}]); - assertNull(config.getRecaptchaParameters()); - - // Phone config with no additional parameters. - config.update( - 'signInOptions', - ['github.com', {'provider': 'google.com'}, {'provider': 'phone'}]); - assertNull(config.getRecaptchaParameters()); - - // Phone config with invalid reCAPTCHA parameters. - config.update( - 'signInOptions', - ['github.com', {'provider': 'google.com'}, - {'provider': 'phone', 'recaptchaParameters': [1, true]}, 'password']); - assertNull(config.getRecaptchaParameters()); - - // Phone config with an empty object reCAPTCHA parameters. - config.update( - 'signInOptions', - ['github.com', {'provider': 'google.com'}, - {'provider': 'phone', 'recaptchaParameters': {}}, 'password']); - assertObjectEquals({}, config.getRecaptchaParameters()); - - // Confirm no warning logged so far. - assertArrayEquals([], warningLogMessages); - - // Phone config with blacklisted reCAPTCHA parameters. - var blacklist = { - 'sitekey': 'SITEKEY', - 'tabindex': 0, - 'callback': function(token) {}, - 'expired-callback': function() {} - }; - config.update( - 'signInOptions', - ['github.com', {'provider': 'google.com'}, - {'provider': 'phone', 'recaptchaParameters': blacklist}, 'password']); - assertObjectEquals({}, config.getRecaptchaParameters()); - // Expected warning should be logged. - assertArrayEquals( - [ - 'The following provided "recaptchaParameters" keys are not allowed: ' + - 'sitekey, tabindex, callback, expired-callback' - ], warningLogMessages); - // Reset warnings. - warningLogMessages = []; - - // Phone config with blacklisted, valid and invalid reCAPTCHA parameters. - var mixed = { - 'sitekey': 'SITEKEY', - 'tabindex': 0, - 'callback': function(token) {}, - 'expired-callback': function() {}, - 'type': 'audio', - 'size': 'invisible', - 'badge': 'bottomleft', - 'theme': 'dark', - 'foo': 'bar' - }; - var expectedParameters = { - 'type': 'audio', - 'size': 'invisible', - 'badge': 'bottomleft', - 'theme': 'dark', - 'foo': 'bar' - }; - config.update( - 'signInOptions', - ['github.com', {'provider': 'google.com'}, - {'provider': 'phone', 'recaptchaParameters': mixed}, 'password']); - assertObjectEquals(expectedParameters, config.getRecaptchaParameters()); - // Expected warning should be logged. - assertArrayEquals( - [ - 'The following provided "recaptchaParameters" keys are not allowed: ' + - 'sitekey, tabindex, callback, expired-callback' - ], warningLogMessages); - - // No error should be logged. - assertArrayEquals([], errorLogMessages); -} - - -function testGetProviderCustomParameters_noSignInOptions() { - config.update('signInOptions', null); - assertNull(config.getProviderCustomParameters('google.com')); -} - - -function testGetProviderCustomParameters_genericProvider() { - config.update('signInOptions', [{ - 'provider': 'microsoft.com', - 'providerName': 'Microsoft', - 'buttonColor': '#FFB6C1', - 'iconUrl': '', - 'customParameters': {'foo': 'bar'} - }]); - assertObjectEquals({'foo': 'bar'}, - config.getProviderCustomParameters('microsoft.com')); -} - - -function testGetProviderCustomParameters_missingCustomParameters() { - config.update('signInOptions', [{ - 'provider': 'google.com', - }]); - assertNull(config.getProviderCustomParameters('google.com')); -} - - -function testGetProviderCustomParameters_multipleIdp() { - config.update('signInOptions', [ - { + 'customParameters': {'foo': 'bar'}, + }]); + assertObjectEquals({'foo': 'bar'}, + config.getProviderCustomParameters('microsoft.com')); + }, + + testGetProviderCustomParameters_missingCustomParameters() { + config.update('signInOptions', [{ 'provider': 'google.com', - 'scopes': ['google1', 'google2'], - 'customParameters': { - 'prompt': 'select_account', - 'login_hint': 'user@example.com' - } - }, - { - 'provider': 'facebook.com', - 'scopes': ['facebook1', 'facebook2'], - 'customParameters': { - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', - } - }, - 'github.com' - ]); - assertObjectEquals( - {'prompt': 'select_account'}, - config.getProviderCustomParameters('google.com')); - assertObjectEquals( + }]); + assertNull(config.getProviderCustomParameters('google.com')); + }, + + testGetProviderCustomParameters_multipleIdp() { + config.update('signInOptions', [ { - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', + 'provider': 'google.com', + 'scopes': ['google1', 'google2'], + 'customParameters': { + 'prompt': 'select_account', + 'login_hint': 'user@example.com', + }, }, - config.getProviderCustomParameters('facebook.com')); - assertNull(config.getProviderCustomParameters('github.com')); - assertNull(config.getProviderCustomParameters('twitter.com')); -} - - -function testGetProviderCustomParameters_github() { - config.update('signInOptions', [ - { - 'provider': 'github.com', - 'customParameters': { - 'allow_signup': 'false', - 'login': 'user@example.com' - } - } - ]); - // login custom parameter should be deleted. - assertObjectEquals( { - 'allow_signup': 'false' + 'provider': 'facebook.com', + 'scopes': ['facebook1', 'facebook2'], + 'customParameters': { + 'display': 'popup', + 'auth_type': 'rerequest', + 'locale': 'pt_BR', + }, + }, + 'github.com', + ]); + assertObjectEquals( + {'prompt': 'select_account'}, + config.getProviderCustomParameters('google.com')); + assertObjectEquals( + { + 'display': 'popup', + 'auth_type': 'rerequest', + 'locale': 'pt_BR', + }, + config.getProviderCustomParameters('facebook.com')); + assertNull(config.getProviderCustomParameters('github.com')); + assertNull(config.getProviderCustomParameters('twitter.com')); + }, + + testGetProviderCustomParameters_github() { + config.update('signInOptions', [ + { + 'provider': 'github.com', + 'customParameters': { + 'allow_signup': 'false', + 'login': 'user@example.com', + }, }, - config.getProviderCustomParameters('github.com')); -} + ]); + // login custom parameter should be deleted. + assertObjectEquals( + { + 'allow_signup': 'false', + }, + config.getProviderCustomParameters('github.com')); + }, + testIsAccountSelectionPromptEnabled_googleLoginHint() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'customParameters': { + 'prompt': 'select_account', + 'login_hint': 'user@example.com', + }, + }, + { + 'provider': 'facebook.com', + 'customParameters': { + 'display': 'popup', + 'auth_type': 'rerequest', + 'locale': 'pt_BR', + }, + }, + 'github.com', + ]); + assertTrue(config.isAccountSelectionPromptEnabled()); + }, -function testIsAccountSelectionPromptEnabled_googleLoginHint() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'customParameters': { - 'prompt': 'select_account', - 'login_hint': 'user@example.com' - } - }, - { - 'provider': 'facebook.com', - 'customParameters': { - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', - } - }, - 'github.com' - ]); - assertTrue(config.isAccountSelectionPromptEnabled()); -} - - -function testIsAccountSelectionPromptEnabled_nonGoogleLoginHint() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'customParameters': { - 'prompt': 'none', - 'login_hint': 'user@example.com' - } - }, - { - 'provider': 'facebook.com', - 'customParameters': { - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', - // This does nothing. - 'prompt': 'select_account' - } - }, - 'github.com', - 'password' - ]); - assertFalse(config.isAccountSelectionPromptEnabled()); -} - - -function testIsAccountSelectionPromptEnabled_noCustomParameter() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'scopes': ['google1', 'google2'] - }, - 'github.com' - ]); - assertFalse(config.isAccountSelectionPromptEnabled()); -} - - -function testIsAccountSelectionPromptEnabled_noAdditionalParameters() { - config.update('signInOptions', [ - 'google.com' - ]); - assertFalse(config.isAccountSelectionPromptEnabled()); -} - - -function testIsAccountSelectionPromptEnabled_noOAuthProvider() { - config.update('signInOptions', [ - 'phone', - 'password' - ]); - assertFalse(config.isAccountSelectionPromptEnabled()); -} - - -function testGetProviderIdFromAuthMethod() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'customParameters': { - 'prompt': 'none' + testIsAccountSelectionPromptEnabled_nonGoogleLoginHint() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'customParameters': { + 'prompt': 'none', + 'login_hint': 'user@example.com', + }, }, - 'authMethod': 'https://accounts.google.com', - 'clientId': '1234567890.apps.googleusercontent.com' - }, - { - 'provider': 'password', - 'authMethod': 'googleyolo://id-and-password' - }, - { - 'authMethod': 'unknown' - } - ]); - assertEquals( - firebase.auth.GoogleAuthProvider.PROVIDER_ID, - config.getProviderIdFromAuthMethod('https://accounts.google.com')); - assertEquals( - firebase.auth.EmailAuthProvider.PROVIDER_ID, - config.getProviderIdFromAuthMethod('googleyolo://id-and-password')); - // Test with authMethod that is not provided in the configuration. - assertNull(config.getProviderIdFromAuthMethod('https://www.facebook.com')); - // Test with null authMethod. - assertNull(config.getProviderIdFromAuthMethod(null)); - // Test with authMethod that does not have a provider ID in the configuration. - assertNull(config.getProviderIdFromAuthMethod('unknown')); -} - - -function testGetGoogleYoloConfig_availableAndEnabled() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'customParameters': { - 'prompt': 'none' + { + 'provider': 'facebook.com', + 'customParameters': { + 'display': 'popup', + 'auth_type': 'rerequest', + 'locale': 'pt_BR', + // This does nothing. + 'prompt': 'select_account', + }, }, - 'authMethod': 'https://accounts.google.com', - 'clientId': '1234567890.apps.googleusercontent.com' - }, - { - 'provider': 'password', - 'authMethod': 'googleyolo://id-and-password' - }, - { - 'authMethod': 'unknown' - }, - { - 'provider': 'facebook.com', - // authMethod is required. - 'clientId': 'CLIENT_ID' - } - ]); - // GOOGLE_YOLO credentialHelper must be selected. - config.update( - 'credentialHelper', firebaseui.auth.CredentialHelper.GOOGLE_YOLO); - var expectedConfig = { - 'supportedAuthMethods': [ - 'https://accounts.google.com', - 'googleyolo://id-and-password' - ], - 'supportedIdTokenProviders': [ + 'github.com', + 'password', + ]); + assertFalse(config.isAccountSelectionPromptEnabled()); + }, + + testIsAccountSelectionPromptEnabled_noCustomParameter() { + config.update('signInOptions', [ { - 'uri': 'https://accounts.google.com', - 'clientId': '1234567890.apps.googleusercontent.com' - } - ] - }; - assertObjectEquals(expectedConfig, config.getGoogleYoloConfig()); -} - - -function testGetGoogleYoloConfig_notEnabled() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'customParameters': { - 'prompt': 'none' + 'provider': 'google.com', + 'scopes': ['google1', 'google2'], }, - 'authMethod': 'https://accounts.google.com', - 'clientId': '1234567890.apps.googleusercontent.com' - }, - { - 'provider': 'password', - 'authMethod': 'googleyolo://id-and-password' - }, - { - 'authMethod': 'unknown' - }, - { - 'provider': 'facebook.com', - // authMethod is required. - 'clientId': 'CLIENT_ID' - } - ]); - // GOOGLE_YOLO credentialHelper not selected. - config.update( - 'credentialHelper', firebaseui.auth.CredentialHelper.NONE); - assertNull(config.getGoogleYoloConfig()); -} + 'github.com', + ]); + assertFalse(config.isAccountSelectionPromptEnabled()); + }, + + testIsAccountSelectionPromptEnabled_noAdditionalParameters() { + config.update('signInOptions', [ + 'google.com', + ]); + assertFalse(config.isAccountSelectionPromptEnabled()); + }, + + testIsAccountSelectionPromptEnabled_noOAuthProvider() { + config.update('signInOptions', [ + 'phone', + 'password', + ]); + assertFalse(config.isAccountSelectionPromptEnabled()); + }, + + testGetProviderIdFromAuthMethod() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'customParameters': { + 'prompt': 'none', + }, + 'authMethod': 'https://accounts.google.com', + 'clientId': '1234567890.apps.googleusercontent.com', + }, + { + 'provider': 'password', + 'authMethod': 'googleyolo://id-and-password', + }, + { + 'authMethod': 'unknown', + }, + ]); + assertEquals( + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + config.getProviderIdFromAuthMethod('https://accounts.google.com')); + assertEquals( + firebase.auth.EmailAuthProvider.PROVIDER_ID, + config.getProviderIdFromAuthMethod('googleyolo://id-and-password')); + // Test with authMethod that is not provided in the configuration. + assertNull(config.getProviderIdFromAuthMethod('https://www.facebook.com')); + // Test with null authMethod. + assertNull(config.getProviderIdFromAuthMethod(null)); + // Test with authMethod that does not have a provider ID in the + // configuration. + assertNull(config.getProviderIdFromAuthMethod('unknown')); + }, + + testGetGoogleYoloConfig_availableAndEnabled() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'customParameters': { + 'prompt': 'none', + }, + 'authMethod': 'https://accounts.google.com', + 'clientId': '1234567890.apps.googleusercontent.com', + }, + { + 'provider': 'password', + 'authMethod': 'googleyolo://id-and-password', + }, + { + 'authMethod': 'unknown', + }, + { + 'provider': 'facebook.com', + // authMethod is required. + 'clientId': 'CLIENT_ID', + }, + ]); + // GOOGLE_YOLO credentialHelper must be selected. + config.update( + 'credentialHelper', + Config.CredentialHelper.GOOGLE_YOLO); + const expectedConfig = { + 'supportedAuthMethods': [ + 'https://accounts.google.com', + 'googleyolo://id-and-password', + ], + 'supportedIdTokenProviders': [ + { + 'uri': 'https://accounts.google.com', + 'clientId': '1234567890.apps.googleusercontent.com', + }, + ], + }; + assertObjectEquals(expectedConfig, config.getGoogleYoloConfig()); + }, + + testGetGoogleYoloConfig_notEnabled() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'customParameters': { + 'prompt': 'none', + }, + 'authMethod': 'https://accounts.google.com', + 'clientId': '1234567890.apps.googleusercontent.com', + }, + { + 'provider': 'password', + 'authMethod': 'googleyolo://id-and-password', + }, + { + 'authMethod': 'unknown', + }, + { + 'provider': 'facebook.com', + // authMethod is required. + 'clientId': 'CLIENT_ID', + }, + ]); + // GOOGLE_YOLO credentialHelper not selected. + config.update( + 'credentialHelper', Config.CredentialHelper.NONE); + assertNull(config.getGoogleYoloConfig()); + }, + + testGetGoogleYoloConfig_notAvailable() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'customParameters': { + 'prompt': 'none', + 'login_hint': 'user@example.com', + }, + }, + { + 'provider': 'facebook.com', + 'customParameters': { + 'display': 'popup', + 'auth_type': 'rerequest', + 'locale': 'pt_BR', + }, + }, + 'github.com', + 'password', + // authMethod with no provider. + { + 'authMethod': 'unknown', + }, + // clientId with no authMethod. + { + 'provider': 'facebook.com', + // authMethod is required. + 'clientId': 'CLIENT_ID', + }, + ]); + // GOOGLE_YOLO credentialHelper is selected. + config.update( + 'credentialHelper', + Config.CredentialHelper.GOOGLE_YOLO); + assertNull(config.getGoogleYoloConfig()); + }, + + testGetPhoneAuthDefaultCountry() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'defaultCountry': 'gb', + }]); + assertEquals('United Kingdom', config.getPhoneAuthDefaultCountry().name); + assertEquals('44', config.getPhoneAuthDefaultCountry().e164_cc); + }, + + testGetPhoneAuthDefaultCountry_defaultCountryAndLoginHint() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'defaultCountry': 'gb', + // loginHint will be ignored in favor of the above. + 'loginHint': '+112345678890', + }]); + assertEquals('United Kingdom', config.getPhoneAuthDefaultCountry().name); + assertEquals('44', config.getPhoneAuthDefaultCountry().e164_cc); + }, + + testGetPhoneAuthDefaultCountry_loginHintOnly() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'loginHint': '+4412345678890', + }]); + assertEquals('Guernsey', config.getPhoneAuthDefaultCountry().name); + assertEquals('44', config.getPhoneAuthDefaultCountry().e164_cc); + }, + + testGetPhoneAuthDefaultCountry_null() { + config.update('signInOptions', null); + assertNull(config.getPhoneAuthDefaultCountry()); + }, + testGetPhoneAuthDefaultCountry_noCountrySpecified() { + config.update('signInOptions', [{ + 'provider': 'phone', + }]); + assertNull(config.getPhoneAuthDefaultCountry()); + }, -function testGetGoogleYoloConfig_notAvailable() { - config.update('signInOptions', [ - { + testGetPhoneAuthDefaultCountry_invalidIdp() { + config.update('signInOptions', [{ 'provider': 'google.com', - 'customParameters': { - 'prompt': 'none', - 'login_hint': 'user@example.com' - } - }, - { - 'provider': 'facebook.com', - 'customParameters': { - 'display': 'popup', - 'auth_type': 'rerequest', - 'locale': 'pt_BR', - } - }, - 'github.com', - 'password', - // authMethod with no provider. - { - 'authMethod': 'unknown' - }, - // clientId with no authMethod. - { - 'provider': 'facebook.com', - // authMethod is required. - 'clientId': 'CLIENT_ID' - } - ]); - // GOOGLE_YOLO credentialHelper is selected. - config.update( - 'credentialHelper', firebaseui.auth.CredentialHelper.GOOGLE_YOLO); - assertNull(config.getGoogleYoloConfig()); -} - - -function testGetPhoneAuthDefaultCountry() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'defaultCountry': 'gb' - }]); - assertEquals('United Kingdom', config.getPhoneAuthDefaultCountry().name); - assertEquals('44', config.getPhoneAuthDefaultCountry().e164_cc); -} - - -function testGetPhoneAuthDefaultCountry_defaultCountryAndLoginHint() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'defaultCountry': 'gb', - // loginHint will be ignored in favor of the above. - 'loginHint': '+112345678890' - }]); - assertEquals('United Kingdom', config.getPhoneAuthDefaultCountry().name); - assertEquals('44', config.getPhoneAuthDefaultCountry().e164_cc); -} - - -function testGetPhoneAuthDefaultCountry_loginHintOnly() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'loginHint': '+4412345678890' - }]); - assertEquals('Guernsey', config.getPhoneAuthDefaultCountry().name); - assertEquals('44', config.getPhoneAuthDefaultCountry().e164_cc); -} - - -function testGetPhoneAuthDefaultCountry_null() { - config.update('signInOptions', null); - assertNull(config.getPhoneAuthDefaultCountry()); -} - - -function testGetPhoneAuthDefaultCountry_noCountrySpecified() { - config.update('signInOptions', [{ - 'provider': 'phone' - }]); - assertNull(config.getPhoneAuthDefaultCountry()); -} - - -function testGetPhoneAuthDefaultCountry_invalidIdp() { - config.update('signInOptions', [{ - 'provider': 'google.com', - 'defaultCountry': 'gb', - 'loginHint': '+112345678890' - }]); - assertNull(config.getPhoneAuthDefaultCountry()); -} - - -function testGetPhoneAuthDefaultCountry_invalidCountry() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'defaultCountry': 'zz' - }]); - assertNull(config.getPhoneAuthDefaultCountry()); -} - - -function testGetPhoneAuthDefaultCountry_invalidDefaultCountry_loginHint() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'defaultCountry': 'zz', - 'loginHint': '+112345678890' - }]); - // Since the defaultCountry is invalid, loginHint will be used instead. - assertEquals('United States', config.getPhoneAuthDefaultCountry().name); - assertEquals('1', config.getPhoneAuthDefaultCountry().e164_cc); -} - - -function testGetPhoneAuthDefaultNationalNumber() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'defaultCountry': 'us', - 'defaultNationalNumber': '1234567890' - }]); - assertEquals('1234567890', config.getPhoneAuthDefaultNationalNumber()); -} - - -function testGetPhoneAuthDefaultNationalNumber_defaultNationalNumberAndHint() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'defaultNationalNumber': '1234567890', - // loginHint will be ignored in favor of the above. - 'loginHint': '+12223334444' - }]); - assertEquals('1234567890', config.getPhoneAuthDefaultNationalNumber()); -} - - -function testGetPhoneAuthDefaultNationalNumber_loginHintOnly() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'loginHint': '+12223334444' - }]); - assertEquals('2223334444', config.getPhoneAuthDefaultNationalNumber()); -} - - -function testGetPhoneAuthDefaultNationalNumber_null() { - config.update('signInOptions', null); - assertNull(config.getPhoneAuthDefaultNationalNumber()); -} - - -function testGetPhoneAuthDefaultNationalNumber_noNationalNumberSpecified() { - config.update('signInOptions', [{ - 'provider': 'phone' - }]); - assertNull(config.getPhoneAuthDefaultNationalNumber()); -} - - -function testGetPhoneAuthDefaultNationalNumber_invalidIdp() { - config.update('signInOptions', [{ - 'provider': 'google.com', - 'defaultCountry': 'ca', - 'defaultNationalNumber': '1234567890', - 'loginHint': '+12223334444' - }]); - assertNull(config.getPhoneAuthDefaultNationalNumber()); -} - - -function testGetPhoneAuthSelectedCountries_whitelist() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'whitelistedCountries': ['+44', 'us'] - }]); - var countries = config.getPhoneAuthAvailableCountries(); - var actualKeys = goog.array.map(countries, function(country) { - return country.e164_key; - }); - assertSameElements( - ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0', '1-US-0'], actualKeys); -} - - -function testGetPhoneAuthSelectedCountries_whitelist_overlap() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'whitelistedCountries': ['+44', 'GB'] - }]); - var countries = config.getPhoneAuthAvailableCountries(); - var actualKeys = goog.array.map(countries, function(country) { - return country.e164_key; - }); - assertSameElements(['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0'], actualKeys); -} - - -function testGetPhoneAuthSelectedCountries_blacklist() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'blacklistedCountries': ['+44', 'US'] - }]); - var countries = config.getPhoneAuthAvailableCountries(); - // BlacklistedCountries should not appear in the available countries list. - var blacklistedKeys = ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0', '1-US-0']; - for (var i = 0; i < countries.length; i++) { - assertNotContains(countries[i].e164_key, blacklistedKeys); - } -} - - -function testGetPhoneAuthSelectedCountries_blacklist_overlap() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'blacklistedCountries': ['+44', 'GB'] - }]); - var countries = config.getPhoneAuthAvailableCountries(); - // BlacklistedCountries should not appear in the available countries list. - var blacklistedKeys = ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0']; - for (var i = 0; i < countries.length; i++) { - assertNotContains(countries[i].e164_key, blacklistedKeys); - } -} - - -function testGetPhoneAuthSelectedCountries_noBlackOrWhiteListProvided() { - config.update('signInOptions', [{ - 'provider': 'phone' - }]); - var countries = config.getPhoneAuthAvailableCountries(); - assertSameElements(firebaseui.auth.data.country.COUNTRY_LIST, countries); -} - - -function testGetPhoneAuthSelectedCountries_emptyBlacklist() { - config.update('signInOptions', [{ - 'provider': 'phone', - 'blacklistedCountries': [] - }]); - var countries = config.getPhoneAuthAvailableCountries(); - assertSameElements(firebaseui.auth.data.country.COUNTRY_LIST, countries); -} - - -function testUpdateConfig_phoneSignInOption_error() { - // Tests when both whitelist and blacklist are provided. - var error = assertThrows(function() { + 'defaultCountry': 'gb', + 'loginHint': '+112345678890', + }]); + assertNull(config.getPhoneAuthDefaultCountry()); + }, + + testGetPhoneAuthDefaultCountry_invalidCountry() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'defaultCountry': 'zz', + }]); + assertNull(config.getPhoneAuthDefaultCountry()); + }, + + testGetPhoneAuthDefaultCountry_invalidDefaultCountry_loginHint() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'defaultCountry': 'zz', + 'loginHint': '+112345678890', + }]); + // Since the defaultCountry is invalid, loginHint will be used instead. + assertEquals('United States', config.getPhoneAuthDefaultCountry().name); + assertEquals('1', config.getPhoneAuthDefaultCountry().e164_cc); + }, + + testGetPhoneAuthDefaultNationalNumber() { config.update('signInOptions', [{ 'provider': 'phone', - 'blacklistedCountries': ['+44'], - 'whitelistedCountries': ['+1'] + 'defaultCountry': 'us', + 'defaultNationalNumber': '1234567890', }]); - }); - assertEquals( - 'Both whitelistedCountries and blacklistedCountries are provided.', - error.message); - // Tests when empty whitelist is provided. - error = assertThrows(function() { + assertEquals('1234567890', config.getPhoneAuthDefaultNationalNumber()); + }, + + testGetPhoneAuthDefaultNationalNumber_defaultNationalNumberAndHint() { config.update('signInOptions', [{ 'provider': 'phone', - 'whitelistedCountries': [] + 'defaultNationalNumber': '1234567890', + // loginHint will be ignored in favor of the above. + 'loginHint': '+12223334444', }]); - }); - assertEquals( - 'WhitelistedCountries must be a non-empty array.', - error.message); - // Tests string is provided as whitelistedCountries. - error = assertThrows(function() { + assertEquals('1234567890', config.getPhoneAuthDefaultNationalNumber()); + }, + + testGetPhoneAuthDefaultNationalNumber_loginHintOnly() { config.update('signInOptions', [{ 'provider': 'phone', - 'whitelistedCountries': 'US' + 'loginHint': '+12223334444', }]); - }); - assertEquals( - 'WhitelistedCountries must be a non-empty array.', - error.message); - // Tests falsy value is provided as whitelistedCountries. - error = assertThrows(function() { + assertEquals('2223334444', config.getPhoneAuthDefaultNationalNumber()); + }, + + testGetPhoneAuthDefaultNationalNumber_null() { + config.update('signInOptions', null); + assertNull(config.getPhoneAuthDefaultNationalNumber()); + }, + + testGetPhoneAuthDefaultNationalNumber_noNationalNumberSpecified() { config.update('signInOptions', [{ 'provider': 'phone', - 'whitelistedCountries': 0 }]); - }); - assertEquals( - 'WhitelistedCountries must be a non-empty array.', - error.message); - // Tests string is provided as blacklistedCountries. - error = assertThrows(function() { + assertNull(config.getPhoneAuthDefaultNationalNumber()); + }, + + testGetPhoneAuthDefaultNationalNumber_invalidIdp() { + config.update('signInOptions', [{ + 'provider': 'google.com', + 'defaultCountry': 'ca', + 'defaultNationalNumber': '1234567890', + 'loginHint': '+12223334444', + }]); + assertNull(config.getPhoneAuthDefaultNationalNumber()); + }, + + testGetPhoneAuthSelectedCountries_whitelist() { config.update('signInOptions', [{ 'provider': 'phone', - 'blacklistedCountries': 'US' + 'whitelistedCountries': ['+44', 'us'], }]); - }); - assertEquals( - 'BlacklistedCountries must be an array.', - error.message); - // Tests falsy value is provided as blacklistedCountries. - error = assertThrows(function() { + const countries = config.getPhoneAuthAvailableCountries(); + const actualKeys = googArray.map(countries, (country) => country.e164_key); + assertSameElements( + ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0', '1-US-0'], actualKeys); + }, + + testGetPhoneAuthSelectedCountries_whitelist_overlap() { config.update('signInOptions', [{ 'provider': 'phone', - 'blacklistedCountries': 0 + 'whitelistedCountries': ['+44', 'GB'], }]); - }); - assertEquals( - 'BlacklistedCountries must be an array.', - error.message); -} + const countries = config.getPhoneAuthAvailableCountries(); + const actualKeys = googArray.map(countries, (country) => country.e164_key); + assertSameElements( + ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0'], actualKeys); + }, + testGetPhoneAuthSelectedCountries_blacklist() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'blacklistedCountries': ['+44', 'US'], + }]); + const countries = config.getPhoneAuthAvailableCountries(); + // BlacklistedCountries should not appear in the available countries list. + const blacklistedKeys = + ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0', '1-US-0']; + for (let i = 0; i < countries.length; i++) { + assertNotContains(countries[i].e164_key, blacklistedKeys); + } + }, -function testSetConfig_phoneSignInOption_error() { - // Tests when both whitelist and blacklist are provided. - var error = assertThrows(function() { - config.setConfig({ - 'signInOptions': [{ + testGetPhoneAuthSelectedCountries_blacklist_overlap() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'blacklistedCountries': ['+44', 'GB'], + }]); + const countries = config.getPhoneAuthAvailableCountries(); + // BlacklistedCountries should not appear in the available countries list. + const blacklistedKeys = ['44-GG-0', '44-IM-0', '44-JE-0', '44-GB-0']; + for (let i = 0; i < countries.length; i++) { + assertNotContains(countries[i].e164_key, blacklistedKeys); + } + }, + + testGetPhoneAuthSelectedCountries_noBlackOrWhiteListProvided() { + config.update('signInOptions', [{ + 'provider': 'phone', + }]); + const countries = config.getPhoneAuthAvailableCountries(); + assertSameElements(firebaseui.auth.data.country.COUNTRY_LIST, countries); + }, + + testGetPhoneAuthSelectedCountries_emptyBlacklist() { + config.update('signInOptions', [{ + 'provider': 'phone', + 'blacklistedCountries': [], + }]); + const countries = config.getPhoneAuthAvailableCountries(); + assertSameElements(firebaseui.auth.data.country.COUNTRY_LIST, countries); + }, + + testUpdateConfig_phoneSignInOption_error() { + // Tests when both whitelist and blacklist are provided. + let error = assertThrows(() => { + config.update('signInOptions', [{ 'provider': 'phone', 'blacklistedCountries': ['+44'], - 'whitelistedCountries': ['+1'] - }] + 'whitelistedCountries': ['+1'], + }]); }); - }); - assertEquals( - 'Both whitelistedCountries and blacklistedCountries are provided.', - error.message); - // Tests when empty whitelist is provided. - error = assertThrows(function() { - config.setConfig({ - 'signInOptions': [{ + assertEquals( + 'Both whitelistedCountries and blacklistedCountries are provided.', + error.message); + // Tests when empty whitelist is provided. + error = assertThrows(() => { + config.update('signInOptions', [{ 'provider': 'phone', - 'whitelistedCountries': [] - }] + 'whitelistedCountries': [], + }]); }); - }); - assertEquals( - 'WhitelistedCountries must be a non-empty array.', - error.message); - // Tests string is provided as whitelistedCountries. - error = assertThrows(function() { - config.setConfig({ - 'signInOptions': [ - { - 'provider': 'phone', - 'whitelistedCountries': 'US' - }] + assertEquals( + 'WhitelistedCountries must be a non-empty array.', + error.message); + // Tests string is provided as whitelistedCountries. + error = assertThrows(() => { + config.update('signInOptions', [{ + 'provider': 'phone', + 'whitelistedCountries': 'US', + }]); }); - }); - assertEquals( - 'WhitelistedCountries must be a non-empty array.', - error.message); - // Tests falsy value is provided as whitelistedCountries. - error = assertThrows(function() { - config.setConfig({ - 'signInOptions': [ - { - 'provider': 'phone', - 'whitelistedCountries': 0 - }] + assertEquals( + 'WhitelistedCountries must be a non-empty array.', + error.message); + // Tests falsy value is provided as whitelistedCountries. + error = assertThrows(() => { + config.update('signInOptions', [{ + 'provider': 'phone', + 'whitelistedCountries': 0, + }]); }); - }); - assertEquals( - 'WhitelistedCountries must be a non-empty array.', - error.message); - // Tests string is provided as blacklistedCountries. - error = assertThrows(function() { - config.setConfig({ - 'signInOptions': [ - { + assertEquals( + 'WhitelistedCountries must be a non-empty array.', + error.message); + // Tests string is provided as blacklistedCountries. + error = assertThrows(() => { + config.update('signInOptions', [{ + 'provider': 'phone', + 'blacklistedCountries': 'US', + }]); + }); + assertEquals( + 'BlacklistedCountries must be an array.', + error.message); + // Tests falsy value is provided as blacklistedCountries. + error = assertThrows(() => { + config.update('signInOptions', [{ + 'provider': 'phone', + 'blacklistedCountries': 0, + }]); + }); + assertEquals( + 'BlacklistedCountries must be an array.', + error.message); + }, + + testSetConfig_phoneSignInOption_error() { + // Tests when both whitelist and blacklist are provided. + let error = assertThrows(() => { + config.setConfig({ + 'signInOptions': [{ 'provider': 'phone', - 'blacklistedCountries': 'US' - }] + 'blacklistedCountries': ['+44'], + 'whitelistedCountries': ['+1'], + }], + }); }); - }); - assertEquals( - 'BlacklistedCountries must be an array.', - error.message); - // Tests falsy value is provided as blacklistedCountries. - error = assertThrows(function() { - config.setConfig({ - 'signInOptions': [ - { + assertEquals( + 'Both whitelistedCountries and blacklistedCountries are provided.', + error.message); + // Tests when empty whitelist is provided. + error = assertThrows(() => { + config.setConfig({ + 'signInOptions': [{ 'provider': 'phone', - 'blacklistedCountries': 0 - }] + 'whitelistedCountries': [], + }], + }); }); - }); - assertEquals( - 'BlacklistedCountries must be an array.', - error.message); -} - - -function testGetProviderAdditionalScopes_noSignInOptions() { - config.update('signInOptions', null); - assertArrayEquals([], config.getProviderAdditionalScopes('google.com')); -} - - -function testGetProviderAdditionalScopes_genericProvider() { - config.update('signInOptions', [{ - 'provider': 'microsoft.com', - 'providerName': 'Microsoft', - 'buttonColor': '#FFB6C1', - 'iconUrl': '', - 'scopes': ['foo', 'bar'] - }]); - assertArrayEquals(['foo', 'bar'], - config.getProviderAdditionalScopes('microsoft.com')); -} - - -function testGetProviderAdditionalScopes_missingScopes() { - config.update('signInOptions', [{ - 'provider': 'google.com', - }]); - assertArrayEquals([], config.getProviderAdditionalScopes('google.com')); -} - - -function testGetProviderAdditionalScopes_multipleIdp() { - config.update('signInOptions', [ - { - 'provider': 'google.com', - 'scopes': ['google1', 'google2'] - }, - { - 'provider': 'github.com', - 'scopes': ['github1', 'github2'] - }, - 'facebook.com' - ]); - assertArrayEquals( - ['google1', 'google2'], - config.getProviderAdditionalScopes('google.com')); - assertArrayEquals( - ['github1', 'github2'], - config.getProviderAdditionalScopes('github.com')); - assertArrayEquals( - [], - config.getProviderAdditionalScopes('facebook.com')); - assertArrayEquals( - [], - config.getProviderAdditionalScopes('twitter.com')); -} - - -function testGetQueryParameterForWidgetMode() { - assertEquals('mode', config.getQueryParameterForWidgetMode()); - config.update('queryParameterForWidgetMode', 'mode2'); - assertEquals('mode2', config.getQueryParameterForWidgetMode()); -} - - -function testGetTosUrl() { - assertNull(config.getTosUrl()); - config.update('tosUrl', 'http://localhost/tos'); - assertNull(config.getTosUrl()); - // Expected warning should be logged. - assertArrayEquals( - [ - 'Privacy Policy URL is missing, the link will not be displayed.' - ], warningLogMessages); - config.update('privacyPolicyUrl', 'http://localhost/privacy_policy'); - var tosCallback = config.getTosUrl(); - tosCallback(); - testUtil.assertOpen('http://localhost/tos', '_blank'); - // No additional warning logged. - assertArrayEquals( - [ - 'Privacy Policy URL is missing, the link will not be displayed.' - ], warningLogMessages); - // Mock that Cordova InAppBrowser plugin is installed. - stub.replace( - firebaseui.auth.util, - 'isCordovaInAppBrowserInstalled', - function() { - return true; + assertEquals( + 'WhitelistedCountries must be a non-empty array.', + error.message); + // Tests string is provided as whitelistedCountries. + error = assertThrows(() => { + config.setConfig({ + 'signInOptions': [ + { + 'provider': 'phone', + 'whitelistedCountries': 'US', + }], + }); + }); + assertEquals( + 'WhitelistedCountries must be a non-empty array.', + error.message); + // Tests falsy value is provided as whitelistedCountries. + error = assertThrows(() => { + config.setConfig({ + 'signInOptions': [ + { + 'provider': 'phone', + 'whitelistedCountries': 0, + }], }); - tosCallback = config.getTosUrl(); - tosCallback(); - // Target should be _system if Cordova InAppBrowser plugin is installed. - testUtil.assertOpen('http://localhost/tos', '_system'); - // Tests if callback function is passed to tosUrl config. - tosCallback = function() {}; - config.update('tosUrl', tosCallback); - assertEquals(tosCallback, config.getTosUrl()); - // Tests if invalid tyoe is passed to tosUrl config. - config.update('tosUrl', 123456); - assertNull(config.getTosUrl()); -} - - -function testGetPrivacyPolicyUrl() { - assertNull(config.getPrivacyPolicyUrl()); - config.update('privacyPolicyUrl', 'http://localhost/privacy_policy'); - assertNull(config.getPrivacyPolicyUrl()); - // Expected warning should be logged. - assertArrayEquals( - [ - 'Term of Service URL is missing, the link will not be displayed.' - ], warningLogMessages); - config.update('tosUrl', 'http://localhost/tos'); - var privacyPolicyCallback = config.getPrivacyPolicyUrl(); - privacyPolicyCallback(); - testUtil.assertOpen('http://localhost/privacy_policy', '_blank'); - // No additional warning logged. - assertArrayEquals( - [ - 'Term of Service URL is missing, the link will not be displayed.' - ], warningLogMessages); - // Mock that Cordova InAppBrowser plugin is installed. - stub.replace( - firebaseui.auth.util, - 'isCordovaInAppBrowserInstalled', - function() { - return true; + }); + assertEquals( + 'WhitelistedCountries must be a non-empty array.', + error.message); + // Tests string is provided as blacklistedCountries. + error = assertThrows(() => { + config.setConfig({ + 'signInOptions': [ + { + 'provider': 'phone', + 'blacklistedCountries': 'US', + }], }); - privacyPolicyCallback = config.getPrivacyPolicyUrl(); - privacyPolicyCallback(); - // Target should be _system if Cordova InAppBrowser plugin is installed. - testUtil.assertOpen('http://localhost/privacy_policy', '_system'); - // Tests if callback function is passed to privacyPolicyUrl config. - privacyPolicyCallback = function() {}; - config.update('privacyPolicyUrl', privacyPolicyCallback); - assertEquals(privacyPolicyCallback, config.getPrivacyPolicyUrl()); - // Tests if invalid tyoe is passed to tosUrl config. - config.update('privacyPolicyUrl', 123456); - assertNull(config.getPrivacyPolicyUrl()); -} - - -function testRequireDisplayName_shouldBeTrueByDefault() { - assertTrue(config.isDisplayNameRequired()); -} - - -function testRequireDisplayName_canBeSet() { - config.update('signInOptions', [ - { - 'provider': 'password', - 'requireDisplayName': true - } - ]); - assertTrue(config.isDisplayNameRequired()); + }); + assertEquals( + 'BlacklistedCountries must be an array.', + error.message); + // Tests falsy value is provided as blacklistedCountries. + error = assertThrows(() => { + config.setConfig({ + 'signInOptions': [ + { + 'provider': 'phone', + 'blacklistedCountries': 0, + }], + }); + }); + assertEquals( + 'BlacklistedCountries must be an array.', + error.message); + }, - config.update('signInOptions', [ - { - 'provider': 'password', - 'requireDisplayName': false - } - ]); - assertFalse(config.isDisplayNameRequired()); -} + testGetProviderAdditionalScopes_noSignInOptions() { + config.update('signInOptions', null); + assertArrayEquals([], config.getProviderAdditionalScopes('google.com')); + }, + testGetProviderAdditionalScopes_genericProvider() { + config.update('signInOptions', [{ + 'provider': 'microsoft.com', + 'providerName': 'Microsoft', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + 'scopes': ['foo', 'bar'], + }]); + assertArrayEquals(['foo', 'bar'], + config.getProviderAdditionalScopes('microsoft.com')); + }, -function testRequireDisplayName_isTrueWithNonBooleanArgs() { - config.update('signInOptions', [ - { - 'provider': 'password', - 'requireDisplayName': 'a string' - } - ]); - assertTrue(config.isDisplayNameRequired()); -} + testGetProviderAdditionalScopes_missingScopes() { + config.update('signInOptions', [{ + 'provider': 'google.com', + }]); + assertArrayEquals([], config.getProviderAdditionalScopes('google.com')); + }, + testGetProviderAdditionalScopes_multipleIdp() { + config.update('signInOptions', [ + { + 'provider': 'google.com', + 'scopes': ['google1', 'google2'], + }, + { + 'provider': 'github.com', + 'scopes': ['github1', 'github2'], + }, + 'facebook.com', + ]); + assertArrayEquals( + ['google1', 'google2'], + config.getProviderAdditionalScopes('google.com')); + assertArrayEquals( + ['github1', 'github2'], + config.getProviderAdditionalScopes('github.com')); + assertArrayEquals( + [], + config.getProviderAdditionalScopes('facebook.com')); + assertArrayEquals( + [], + config.getProviderAdditionalScopes('twitter.com')); + }, + + testGetQueryParameterForWidgetMode() { + assertEquals('mode', config.getQueryParameterForWidgetMode()); + config.update('queryParameterForWidgetMode', 'mode2'); + assertEquals('mode2', config.getQueryParameterForWidgetMode()); + }, + + testGetTosUrl() { + assertNull(config.getTosUrl()); + config.update('tosUrl', 'http://localhost/tos'); + assertNull(config.getTosUrl()); + // Expected warning should be logged. + assertArrayEquals( + [ + 'Privacy Policy URL is missing, the link will not be displayed.', + ], warningLogMessages); + config.update('privacyPolicyUrl', 'http://localhost/privacy_policy'); + let tosCallback = config.getTosUrl(); + tosCallback(); + testUtil.assertOpen('http://localhost/tos', '_blank'); + // No additional warning logged. + assertArrayEquals( + [ + 'Privacy Policy URL is missing, the link will not be displayed.', + ], warningLogMessages); + // Mock that Cordova InAppBrowser plugin is installed. + stub.replace( + util, + 'isCordovaInAppBrowserInstalled', + () => true); + tosCallback = config.getTosUrl(); + tosCallback(); + // Target should be _system if Cordova InAppBrowser plugin is installed. + testUtil.assertOpen('http://localhost/tos', '_system'); + // Tests if callback function is passed to tosUrl config. + tosCallback = () => {}; + config.update('tosUrl', tosCallback); + assertEquals(tosCallback, config.getTosUrl()); + // Tests if invalid tyoe is passed to tosUrl config. + config.update('tosUrl', 123456); + assertNull(config.getTosUrl()); + }, + + testGetPrivacyPolicyUrl() { + assertNull(config.getPrivacyPolicyUrl()); + config.update('privacyPolicyUrl', 'http://localhost/privacy_policy'); + assertNull(config.getPrivacyPolicyUrl()); + // Expected warning should be logged. + assertArrayEquals( + [ + 'Term of Service URL is missing, the link will not be displayed.', + ], warningLogMessages); + config.update('tosUrl', 'http://localhost/tos'); + let privacyPolicyCallback = config.getPrivacyPolicyUrl(); + privacyPolicyCallback(); + testUtil.assertOpen('http://localhost/privacy_policy', '_blank'); + // No additional warning logged. + assertArrayEquals( + [ + 'Term of Service URL is missing, the link will not be displayed.', + ], warningLogMessages); + // Mock that Cordova InAppBrowser plugin is installed. + stub.replace( + util, + 'isCordovaInAppBrowserInstalled', + () => true); + privacyPolicyCallback = config.getPrivacyPolicyUrl(); + privacyPolicyCallback(); + // Target should be _system if Cordova InAppBrowser plugin is installed. + testUtil.assertOpen('http://localhost/privacy_policy', '_system'); + // Tests if callback function is passed to privacyPolicyUrl config. + privacyPolicyCallback = () => {}; + config.update('privacyPolicyUrl', privacyPolicyCallback); + assertEquals(privacyPolicyCallback, config.getPrivacyPolicyUrl()); + // Tests if invalid tyoe is passed to tosUrl config. + config.update('privacyPolicyUrl', 123456); + assertNull(config.getPrivacyPolicyUrl()); + }, + + testRequireDisplayName_shouldBeTrueByDefault() { + assertTrue(config.isDisplayNameRequired()); + }, + + testRequireDisplayName_canBeSet() { + config.update('signInOptions', [ + { + 'provider': 'password', + 'requireDisplayName': true, + }, + ]); + assertTrue(config.isDisplayNameRequired()); -function testEmailProviderConfig_passwordAllowed() { - config.update('signInOptions', [ - { - 'provider': 'password' - } - ]); - assertTrue(config.isEmailPasswordSignInAllowed()); - assertFalse(config.isEmailLinkSignInAllowed()); - assertFalse(config.isEmailLinkSameDeviceForced()); - assertNull(config.getEmailLinkSignInActionCodeSettings()); - - // Even if emailLinkSignIn is provided, it should still be ignored. - config.update('signInOptions', [ - { - 'provider': 'password', - 'emailLinkSignIn': function() { - return { - 'url': firebaseui.auth.util.getCurrentUrl() - }; - } - } - ]); - assertTrue(config.isEmailPasswordSignInAllowed()); - assertFalse(config.isEmailLinkSignInAllowed()); - assertFalse(config.isEmailLinkSameDeviceForced()); - assertNull(config.getEmailLinkSignInActionCodeSettings()); -} - - -function testEmailProviderConfig_emailLinkAllowed() { - stub.replace( - firebaseui.auth.util, - 'getCurrentUrl', - function() { - return 'https://www.example.com/path/?mode=foo&mode2=bar#a=1'; - }); - var originalActionCodeSettings = { - 'url': 'https://other.com/handleSignIn', - 'dynamicLinkDomain': 'example.page.link', - 'iOS': { - 'bundleId': 'com.example.ios' - }, - 'android': { - 'packageName': 'com.example.android', - 'installApp': true, - 'minimumVersion': '12' - } - }; - var expectedActionCodeSettings = { - 'url': 'https://other.com/handleSignIn', - 'handleCodeInApp': true, - 'dynamicLinkDomain': 'example.page.link', - 'iOS': { - 'bundleId': 'com.example.ios' - }, - 'android': { - 'packageName': 'com.example.android', - 'installApp': true, - 'minimumVersion': '12' - } - }; + config.update('signInOptions', [ + { + 'provider': 'password', + 'requireDisplayName': false, + }, + ]); + assertFalse(config.isDisplayNameRequired()); + }, - config.update('signInOptions', [ - { - 'provider': 'password', - 'signInMethod': firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD - } - ]); - assertFalse(config.isEmailPasswordSignInAllowed()); - assertTrue(config.isEmailLinkSignInAllowed()); - assertFalse(config.isEmailLinkSameDeviceForced()); - assertObjectEquals( + testRequireDisplayName_isTrueWithNonBooleanArgs() { + config.update('signInOptions', [ { - 'url': firebaseui.auth.util.getCurrentUrl(), - 'handleCodeInApp': true + 'provider': 'password', + 'requireDisplayName': 'a string', }, - config.getEmailLinkSignInActionCodeSettings()); - - // Same device flow and explicit actionCodeUrl. - config.update('signInOptions', [ - { - 'provider': 'password', - 'signInMethod': firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD, - 'forceSameDevice': true, - 'emailLinkSignIn': function() { - return originalActionCodeSettings; - } - } - ]); - assertFalse(config.isEmailPasswordSignInAllowed()); - assertTrue(config.isEmailLinkSignInAllowed()); - assertTrue(config.isEmailLinkSameDeviceForced()); - assertObjectEquals( - expectedActionCodeSettings, - config.getEmailLinkSignInActionCodeSettings()); - - // Relative URL in actionCodeUrl. - config.update('signInOptions', [ - { - 'provider': 'password', - 'signInMethod': firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD, - 'forceSameDevice': true, - 'emailLinkSignIn': function() { - return { - // Relative path will be resolved relative to current URL. - 'url': '../completeSignIn?a=1#b=2' - }; - } - } - ]); - assertFalse(config.isEmailPasswordSignInAllowed()); - assertTrue(config.isEmailLinkSignInAllowed()); - assertTrue(config.isEmailLinkSameDeviceForced()); - assertObjectEquals( + ]); + assertTrue(config.isDisplayNameRequired()); + }, + + testEmailProviderConfig_passwordAllowed() { + config.update('signInOptions', [ { - 'url': 'https://www.example.com/completeSignIn?a=1#b=2', - 'handleCodeInApp': true + 'provider': 'password', }, - config.getEmailLinkSignInActionCodeSettings()); -} - - -function testSetConfig() { - config.setConfig({ - tosUrl: 'www.testUrl1.com', - privacyPolicyUrl: 'www.testUrl2.com' - }); - var tosCallback = config.getTosUrl(); - tosCallback(); - testUtil.assertOpen('www.testUrl1.com', '_blank'); - var privacyPolicyCallback = config.getPrivacyPolicyUrl(); - privacyPolicyCallback(); - testUtil.assertOpen('www.testUrl2.com', '_blank'); -} - - -function testSetConfig_nonDefined() { - config.setConfig({test1: 1, test2: 2}); - assertArrayEquals( - [ - 'Invalid config: "test1"', - 'Invalid config: "test2"' - ], errorLogMessages); -} - - -function testPopupInMobileBrowser() { - goog.testing.createMethodMock(firebaseui.auth.util, 'isMobileBrowser'); - firebaseui.auth.util.isMobileBrowser().$returns(true); - firebaseui.auth.util.isMobileBrowser().$replay(); - config.setConfig({ - popupMode: true}); - - assertFalse(config.getPopupMode()); - firebaseui.auth.util.isMobileBrowser.$tearDown(); -} - - -function testGetCallbacks() { - var uiShownCallback = function() {}; - var signInSuccessCallback = function() { return true; }; - var signInSuccessWithAuthResultCallback = function() { return true; }; - var uiChangedCallback = function() {}; - var accountChooserInvokedCallback = function() {}; - var accountChooserResultCallback = function() {}; - var signInFailureCallback = function() {}; - assertNull(config.getUiShownCallback()); - assertNull(config.getSignInSuccessCallback()); - assertNull(config.getSignInSuccessWithAuthResultCallback()); - assertNull(config.getUiChangedCallback()); - assertNull(config.getAccountChooserInvokedCallback()); - assertNull(config.getAccountChooserResultCallback()); - assertNull(config.getAccountChooserResultCallback()); - config.update('callbacks', { - 'uiShown': uiShownCallback, - 'signInSuccess': signInSuccessCallback, - 'signInSuccessWithAuthResult': signInSuccessWithAuthResultCallback, - 'uiChanged': uiChangedCallback, - 'accountChooserInvoked': accountChooserInvokedCallback, - 'accountChooserResult': accountChooserResultCallback, - 'signInFailure': signInFailureCallback - }); - assertEquals(uiShownCallback, config.getUiShownCallback()); - assertEquals( - signInSuccessCallback, config.getSignInSuccessCallback()); - assertEquals( - signInSuccessWithAuthResultCallback, - config.getSignInSuccessWithAuthResultCallback()); - assertEquals(uiChangedCallback, config.getUiChangedCallback()); - assertEquals( - accountChooserInvokedCallback, - config.getAccountChooserInvokedCallback()); - assertEquals( - accountChooserResultCallback, - config.getAccountChooserResultCallback()); - assertEquals( - signInFailureCallback, config.getSignInFailureCallback()); -} - - -function testAutoUpgradeAnonymousUsers() { - var expectedErrorLogMessage = 'Missing "signInFailure" callback: ' + - '"signInFailure" callback needs to be provided when ' + - '"autoUpgradeAnonymousUsers" is set to true.'; - assertFalse(config.autoUpgradeAnonymousUsers()); - - config.update('autoUpgradeAnonymousUsers', ''); - assertFalse(config.autoUpgradeAnonymousUsers()); - - config.update('autoUpgradeAnonymousUsers', null); - assertFalse(config.autoUpgradeAnonymousUsers()); - - config.update('autoUpgradeAnonymousUsers', false); - assertFalse(config.autoUpgradeAnonymousUsers()); - - // No error or warning should be logged. - assertArrayEquals([], errorLogMessages); - assertArrayEquals([], warningLogMessages); - - // Set autoUpgradeAnonymousUsers to true without providing a signInFailure - // callback. - config.update('autoUpgradeAnonymousUsers', true); - assertTrue(config.autoUpgradeAnonymousUsers()); - // Error should be logged. - assertArrayEquals([expectedErrorLogMessage], errorLogMessages); - assertArrayEquals([], warningLogMessages); - - // Provide the signInFailure callback. - config.update('callbacks', { - 'signInFailure': goog.nullFunction - }); - config.update('autoUpgradeAnonymousUsers', 'true'); - assertTrue(config.autoUpgradeAnonymousUsers()); - config.update('autoUpgradeAnonymousUsers', 1); - assertTrue(config.autoUpgradeAnonymousUsers()); - // No additional error logged. - assertArrayEquals([expectedErrorLogMessage], errorLogMessages); - assertArrayEquals([], warningLogMessages); -} - - -function testGetCredentialHelper_httpOrHttps() { - // Test credential helper configuration setting, as well as the - // accountchooser.com enabled helper method, in a HTTP or HTTPS environment. - // Simulate HTTP or HTTPS environment. - stub.replace( - firebaseui.auth.util, - 'isHttpOrHttps', - function() { - return true; - }); - // Default is accountchooser.com. - assertEquals('accountchooser.com', config.getCredentialHelper()); - assertTrue(config.isAccountChooserEnabled()); - - // Use an invalid credential helper. - config.update('credentialHelper', 'invalid'); - assertEquals('accountchooser.com', config.getCredentialHelper()); - assertTrue(config.isAccountChooserEnabled()); - - // Explicitly disable credential helper. - config.update('credentialHelper', 'none'); - assertEquals('none', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); - - // Explicitly enable accountchooser.com. - config.update('credentialHelper', 'accountchooser.com'); - assertEquals('accountchooser.com', config.getCredentialHelper()); - assertTrue(config.isAccountChooserEnabled()); - - // Explicitly enable googleyolo. - config.update('credentialHelper', 'googleyolo'); - assertEquals('googleyolo', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); -} - - -function testGetCredentialHelper_nonHttpOrHttps() { - // Test credential helper configuration setting, as well as the - // accountchooser.com enabled helper method, in a non HTTP or HTTPS - // environment. This could be a Cordova file environment. - // Simulate non HTTP or HTTPS environment. - stub.replace( - firebaseui.auth.util, - 'isHttpOrHttps', - function() { - return false; - }); - // All should resolve to none. - // Default is accountchooser.com. - assertEquals('none', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); - - // Use an invalid credential helper. - config.update('credentialHelper', 'invalid'); - assertEquals('none', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); - - // Explicitly disable credential helper. - config.update('credentialHelper', 'none'); - assertEquals('none', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); - - // Explicitly enable accountchooser.com. - config.update('credentialHelper', 'accountchooser.com'); - assertEquals('none', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); - - // Explicitly enable googleyolo. - config.update('credentialHelper', 'googleyolo'); - assertEquals('none', config.getCredentialHelper()); - assertFalse(config.isAccountChooserEnabled()); -} + ]); + assertTrue(config.isEmailPasswordSignInAllowed()); + assertFalse(config.isEmailLinkSignInAllowed()); + assertFalse(config.isEmailLinkSameDeviceForced()); + assertNull(config.getEmailLinkSignInActionCodeSettings()); + + // Even if emailLinkSignIn is provided, it should still be ignored. + config.update('signInOptions', [ + { + 'provider': 'password', + 'emailLinkSignIn': function() { + return { + 'url': util.getCurrentUrl(), + }; + }, + }, + ]); + assertTrue(config.isEmailPasswordSignInAllowed()); + assertFalse(config.isEmailLinkSignInAllowed()); + assertFalse(config.isEmailLinkSameDeviceForced()); + assertNull(config.getEmailLinkSignInActionCodeSettings()); + }, + + testEmailProviderConfig_emailLinkAllowed() { + stub.replace( + util, + 'getCurrentUrl', + () => 'https://www.example.com/path/?mode=foo&mode2=bar#a=1'); + const originalActionCodeSettings = { + 'url': 'https://other.com/handleSignIn', + 'dynamicLinkDomain': 'example.page.link', + 'iOS': { + 'bundleId': 'com.example.ios', + }, + 'android': { + 'packageName': 'com.example.android', + 'installApp': true, + 'minimumVersion': '12', + }, + }; + const expectedActionCodeSettings = { + 'url': 'https://other.com/handleSignIn', + 'handleCodeInApp': true, + 'dynamicLinkDomain': 'example.page.link', + 'iOS': { + 'bundleId': 'com.example.ios', + }, + 'android': { + 'packageName': 'com.example.android', + 'installApp': true, + 'minimumVersion': '12', + }, + }; + + config.update('signInOptions', [ + { + 'provider': 'password', + 'signInMethod': + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD, + }, + ]); + assertFalse(config.isEmailPasswordSignInAllowed()); + assertTrue(config.isEmailLinkSignInAllowed()); + assertFalse(config.isEmailLinkSameDeviceForced()); + assertObjectEquals( + { + 'url': util.getCurrentUrl(), + 'handleCodeInApp': true, + }, + config.getEmailLinkSignInActionCodeSettings()); + + // Same device flow and explicit actionCodeUrl. + config.update('signInOptions', [ + { + 'provider': 'password', + 'signInMethod': + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD, + 'forceSameDevice': true, + 'emailLinkSignIn': function() { + return originalActionCodeSettings; + }, + }, + ]); + assertFalse(config.isEmailPasswordSignInAllowed()); + assertTrue(config.isEmailLinkSignInAllowed()); + assertTrue(config.isEmailLinkSameDeviceForced()); + assertObjectEquals( + expectedActionCodeSettings, + config.getEmailLinkSignInActionCodeSettings()); + + // Relative URL in actionCodeUrl. + config.update('signInOptions', [ + { + 'provider': 'password', + 'signInMethod': + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD, + 'forceSameDevice': true, + 'emailLinkSignIn': function() { + return { + // Relative path will be resolved relative to current URL. + 'url': '../completeSignIn?a=1#b=2', + }; + }, + }, + ]); + assertFalse(config.isEmailPasswordSignInAllowed()); + assertTrue(config.isEmailLinkSignInAllowed()); + assertTrue(config.isEmailLinkSameDeviceForced()); + assertObjectEquals( + { + 'url': 'https://www.example.com/completeSignIn?a=1#b=2', + 'handleCodeInApp': true, + }, + config.getEmailLinkSignInActionCodeSettings()); + }, + + testSetConfig() { + config.setConfig({ + tosUrl: 'www.testUrl1.com', + privacyPolicyUrl: 'www.testUrl2.com', + }); + const tosCallback = config.getTosUrl(); + tosCallback(); + testUtil.assertOpen('www.testUrl1.com', '_blank'); + const privacyPolicyCallback = config.getPrivacyPolicyUrl(); + privacyPolicyCallback(); + testUtil.assertOpen('www.testUrl2.com', '_blank'); + }, + + testSetConfig_nonDefined() { + config.setConfig({test1: 1, test2: 2}); + assertArrayEquals( + [ + 'Invalid config: "test1"', + 'Invalid config: "test2"', + ], errorLogMessages); + }, + + testPopupInMobileBrowser() { + testing.createMethodMock(util, 'isMobileBrowser'); + util.isMobileBrowser().$returns(true); + util.isMobileBrowser().$replay(); + config.setConfig({ + popupMode: true}); + + assertFalse(config.getPopupMode()); + util.isMobileBrowser.$tearDown(); + }, + + testGetCallbacks() { + const uiShownCallback = () => {}; + const signInSuccessCallback = () => true; + const signInSuccessWithAuthResultCallback = () => true; + const uiChangedCallback = () => {}; + const accountChooserInvokedCallback = () => {}; + const accountChooserResultCallback = () => {}; + const signInFailureCallback = () => {}; + assertNull(config.getUiShownCallback()); + assertNull(config.getSignInSuccessCallback()); + assertNull(config.getSignInSuccessWithAuthResultCallback()); + assertNull(config.getUiChangedCallback()); + assertNull(config.getAccountChooserInvokedCallback()); + assertNull(config.getAccountChooserResultCallback()); + assertNull(config.getAccountChooserResultCallback()); + config.update('callbacks', { + 'uiShown': uiShownCallback, + 'signInSuccess': signInSuccessCallback, + 'signInSuccessWithAuthResult': signInSuccessWithAuthResultCallback, + 'uiChanged': uiChangedCallback, + 'accountChooserInvoked': accountChooserInvokedCallback, + 'accountChooserResult': accountChooserResultCallback, + 'signInFailure': signInFailureCallback, + }); + assertEquals(uiShownCallback, config.getUiShownCallback()); + assertEquals( + signInSuccessCallback, config.getSignInSuccessCallback()); + assertEquals( + signInSuccessWithAuthResultCallback, + config.getSignInSuccessWithAuthResultCallback()); + assertEquals(uiChangedCallback, config.getUiChangedCallback()); + assertEquals( + accountChooserInvokedCallback, + config.getAccountChooserInvokedCallback()); + assertEquals( + accountChooserResultCallback, + config.getAccountChooserResultCallback()); + assertEquals( + signInFailureCallback, config.getSignInFailureCallback()); + }, + + testAutoUpgradeAnonymousUsers() { + const expectedErrorLogMessage = 'Missing "signInFailure" callback: ' + + '"signInFailure" callback needs to be provided when ' + + '"autoUpgradeAnonymousUsers" is set to true.'; + assertFalse(config.autoUpgradeAnonymousUsers()); + + config.update('autoUpgradeAnonymousUsers', ''); + assertFalse(config.autoUpgradeAnonymousUsers()); + + config.update('autoUpgradeAnonymousUsers', null); + assertFalse(config.autoUpgradeAnonymousUsers()); + + config.update('autoUpgradeAnonymousUsers', false); + assertFalse(config.autoUpgradeAnonymousUsers()); + + // No error or warning should be logged. + assertArrayEquals([], errorLogMessages); + assertArrayEquals([], warningLogMessages); + + // Set autoUpgradeAnonymousUsers to true without providing a signInFailure + // callback. + config.update('autoUpgradeAnonymousUsers', true); + assertTrue(config.autoUpgradeAnonymousUsers()); + // Error should be logged. + assertArrayEquals([expectedErrorLogMessage], errorLogMessages); + assertArrayEquals([], warningLogMessages); + + // Provide the signInFailure callback. + config.update('callbacks', { + 'signInFailure': goog.nullFunction, + }); + config.update('autoUpgradeAnonymousUsers', 'true'); + assertTrue(config.autoUpgradeAnonymousUsers()); + config.update('autoUpgradeAnonymousUsers', 1); + assertTrue(config.autoUpgradeAnonymousUsers()); + // No additional error logged. + assertArrayEquals([expectedErrorLogMessage], errorLogMessages); + assertArrayEquals([], warningLogMessages); + }, + + testGetCredentialHelper_httpOrHttps() { + // Test credential helper configuration setting, as well as the + // accountchooser.com enabled helper method, in a HTTP or HTTPS environment. + // Simulate HTTP or HTTPS environment. + stub.replace( + util, + 'isHttpOrHttps', + () => true); + // Default is accountchooser.com. + assertEquals('accountchooser.com', config.getCredentialHelper()); + assertTrue(config.isAccountChooserEnabled()); + + // Use an invalid credential helper. + config.update('credentialHelper', 'invalid'); + assertEquals('accountchooser.com', config.getCredentialHelper()); + assertTrue(config.isAccountChooserEnabled()); + + // Explicitly disable credential helper. + config.update('credentialHelper', 'none'); + assertEquals('none', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + + // Explicitly enable accountchooser.com. + config.update('credentialHelper', 'accountchooser.com'); + assertEquals('accountchooser.com', config.getCredentialHelper()); + assertTrue(config.isAccountChooserEnabled()); + + // Explicitly enable googleyolo. + config.update('credentialHelper', 'googleyolo'); + assertEquals('googleyolo', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + }, + + testGetCredentialHelper_nonHttpOrHttps() { + // Test credential helper configuration setting, as well as the + // accountchooser.com enabled helper method, in a non HTTP or HTTPS + // environment. This could be a Cordova file environment. + // Simulate non HTTP or HTTPS environment. + stub.replace( + util, + 'isHttpOrHttps', + () => false); + // All should resolve to none. + // Default is accountchooser.com. + assertEquals('none', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + + // Use an invalid credential helper. + config.update('credentialHelper', 'invalid'); + assertEquals('none', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + + // Explicitly disable credential helper. + config.update('credentialHelper', 'none'); + assertEquals('none', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + + // Explicitly enable accountchooser.com. + config.update('credentialHelper', 'accountchooser.com'); + assertEquals('none', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + + // Explicitly enable googleyolo. + config.update('credentialHelper', 'googleyolo'); + assertEquals('none', config.getCredentialHelper()); + assertFalse(config.isAccountChooserEnabled()); + }, +}); diff --git a/javascript/widgets/dispatcher.js b/javascript/widgets/dispatcher.js index d30abf57..28fbc61b 100644 --- a/javascript/widgets/dispatcher.js +++ b/javascript/widgets/dispatcher.js @@ -16,47 +16,68 @@ * @fileoverview Dispatches FirebaseUI widget operation to the correct handler. */ -goog.provide('firebaseui.auth.widget.dispatcher'); +goog.module('firebaseui.auth.widget.dispatcher'); +goog.module.declareLegacyNamespace(); -goog.require('firebaseui.auth.acClient'); -goog.require('firebaseui.auth.soy2.strings'); -goog.require('firebaseui.auth.storage'); -goog.require('firebaseui.auth.util'); -goog.require('firebaseui.auth.widget.Config'); -goog.require('firebaseui.auth.widget.HandlerName'); -goog.require('firebaseui.auth.widget.handler'); -goog.require('firebaseui.auth.widget.handler.common'); -goog.require('goog.asserts'); -goog.require('goog.uri.utils'); -goog.forwardDeclare('firebaseui.auth.AuthUI'); +const AuthUI = goog.forwardDeclare('firebaseui.auth.AuthUI'); +const Config = goog.require('firebaseui.auth.widget.Config'); +const HandlerName = goog.require('firebaseui.auth.widget.HandlerName'); +const acClient = goog.require('firebaseui.auth.acClient'); +const asserts = goog.require('goog.asserts'); +const common = goog.require('firebaseui.auth.widget.handler.common'); +const handler = goog.require('firebaseui.auth.widget.handler'); +const storage = goog.require('firebaseui.auth.storage'); +const strings = goog.require('firebaseui.auth.soy2.strings'); +const util = goog.require('firebaseui.auth.util'); +const utils = goog.require('goog.uri.utils'); +/** + * The object used to put the public functions on and then export it. All the + * public functions have to be exported from this object so that we can use. + * property replacer to mock the function. For example: + * dispatcher.foo = function() { + * dispatcher.bar(); + * }; + * exports = dispatcher; + * So that in other files, you can replace dispatcher.bar property. + * + * Otherwise, if we do: + * foo = function() { + * bar(); + * }; + * exports = { foo, bar }; + * In other files, replacing bar() on exported object will not change the + * reference to the bar() used in this file. + * + * @const {!Object} + */ +const dispatcher = {}; + /** * The description of the error message raised when the widget element is not * found during initialization. * @const {string} - * @private */ -firebaseui.auth.widget.dispatcher.ELEMENT_NOT_FOUND_ = 'Could not find the ' + +const ELEMENT_NOT_FOUND = 'Could not find the ' + 'FirebaseUI widget element on the page.'; /** * Gets the widget mode from the given URL. If no URL is provided, the one for * the current page is used. - * - * @param {!firebaseui.auth.AuthUI} app The FirebaseUI instance. + * @param {!AuthUI} app The FirebaseUI instance. * @param {?string=} opt_url The URL from which to extract the mode. - * @return {?firebaseui.auth.widget.Config.WidgetMode} The widget mode. + * @return {?Config.WidgetMode} The widget mode. */ -firebaseui.auth.widget.dispatcher.getMode = function(app, opt_url) { - var url = opt_url || firebaseui.auth.util.getCurrentUrl(); - var modeParam = app.getConfig().getQueryParameterForWidgetMode(); - var modeString = goog.uri.utils.getParamValue(url, modeParam) || ''; +dispatcher.getMode = function(app, opt_url) { + const url = opt_url || util.getCurrentUrl(); + const modeParam = app.getConfig().getQueryParameterForWidgetMode(); + const modeString = utils.getParamValue(url, modeParam) || ''; // Normalize the mode. - var WidgetMode = firebaseui.auth.widget.Config.WidgetMode; - for (var k in WidgetMode) { + const WidgetMode = Config.WidgetMode; + for (let k in WidgetMode) { if (WidgetMode[k].toLowerCase() == modeString.toLowerCase()) { return WidgetMode[k]; } @@ -65,55 +86,68 @@ firebaseui.auth.widget.dispatcher.getMode = function(app, opt_url) { return WidgetMode.CALLBACK; }; - /** * Gets the redirect URL from the given URL. If no URL is provided, the one for * the current page is used. - * - * @param {!firebaseui.auth.AuthUI} app The FirebaseUI instance. + * @param {!AuthUI} app The FirebaseUI instance. * @param {?string=} opt_url the URL from which to extract the redirect URL. * @return {?string} The current redirect URL if available in the URL. */ -firebaseui.auth.widget.dispatcher.getRedirectUrl = function(app, opt_url) { - var url = opt_url || firebaseui.auth.util.getCurrentUrl(); - var queryParameterForSignInSuccessUrl = +dispatcher.getRedirectUrl = function (app, opt_url) { + const url = opt_url || util.getCurrentUrl(); + const queryParameterForSignInSuccessUrl = app.getConfig().getQueryParameterForSignInSuccessUrl(); // Return the value of sign-in success URL from parsed url. - var redirectUrl = - goog.uri.utils.getParamValue(url, queryParameterForSignInSuccessUrl); - return redirectUrl ? firebaseui.auth.util.sanitizeUrl(redirectUrl) : null; + const redirectUrl = + utils.getParamValue(url, queryParameterForSignInSuccessUrl); + return redirectUrl ? util.sanitizeUrl(redirectUrl) : null; }; +/** + * Dispatches the operation to the corresponding handler. + * @param {!AuthUI} app The FirebaseUI instance. + * @param {string|!Element} e The container element or the query selector. + */ +dispatcher.dispatchOperation = function (app, e) { + // Check that web storage is available otherwise issue an error and exit. + // Some browsers like safari private mode disable web storage. + if (storage.isAvailable()) { + doDispatchOperation(app, e); + } else { + // Web storage not supported, display appropriate message. + // Get container element. + const container = util.getElement( + e, ELEMENT_NOT_FOUND); + // Show unrecoverable error message. + common.handleUnrecoverableError( + app, + container, + strings.errorNoWebStorage().toString()); + } +}; /** * Gets the URL param identified by name from the given URL. If no URL is * provided, the one for the current page is used. - * * @param {string} paramName The name of the URL param. - * @param {?string=} opt_url The URL from which to extract the app ID. + * @param {?string=} url The URL from which to extract the app ID. * @return {string} The param value. - * @private */ -firebaseui.auth.widget.dispatcher.getRequiredUrlParam_ = function(paramName, - opt_url) { - return goog.asserts.assertString(goog.uri.utils.getParamValue( - opt_url || firebaseui.auth.util.getCurrentUrl(), paramName)); -}; - +function getRequiredUrlParam(paramName, url = undefined) { + return asserts.assertString(utils.getParamValue( + url || util.getCurrentUrl(), paramName)); +} /** * Gets the action code from the given URL. If no URL is provided, the one for * the current page is used. - * - * @param {?string=} opt_url The URL from which to extract the app ID. + * @param {?string=} url The URL from which to extract the app ID. * @return {string} The action code. - * @private */ -firebaseui.auth.widget.dispatcher.getActionCode_ = function(opt_url) { - return firebaseui.auth.widget.dispatcher.getRequiredUrlParam_('oobCode', - opt_url); -}; - +function getActionCode(url = undefined) { + return getRequiredUrlParam('oobCode', + url); +} /** * Gets the action code continue callback from the given URL. If no URL is @@ -121,149 +155,117 @@ firebaseui.auth.widget.dispatcher.getActionCode_ = function(opt_url) { * go back to the application. This could open an FDL link which redirects to a * mobile app or to a web page. If no continue URL is available, no button is * shown. - * - * @param {?string=} opt_url The URL from which to extract the continue URL. + * @param {?string=} url The URL from which to extract the continue URL. * @return {?function()} The continue callback that will redirect the page back * to the app. If none available, null is returned. - * @private */ -firebaseui.auth.widget.dispatcher.getContinueCallback_ = function(opt_url) { - var continueUrl = goog.uri.utils.getParamValue( - opt_url || firebaseui.auth.util.getCurrentUrl(), 'continueUrl'); +function getContinueCallback(url = undefined) { + const continueUrl = utils.getParamValue( + url || util.getCurrentUrl(), 'continueUrl'); // If continue URL detected, return a callback URL to redirect to it. if (continueUrl) { - return function() { - firebaseui.auth.util.goTo(/** @type {string} */ (continueUrl)); + return () => { + util.goTo(/** @type {string} */ (continueUrl)); }; } return null; -}; - +} /** * Gets the provider ID from the given URL. If no URL is provided, the one for * the current page is used. - * - * @param {?string=} opt_url The URL from which to extract the app ID. + * @param {?string=} url The URL from which to extract the app ID. * @return {string} The provider ID. - * @private */ -firebaseui.auth.widget.dispatcher.getProviderId_ = function(opt_url) { - return firebaseui.auth.widget.dispatcher.getRequiredUrlParam_('providerId', - opt_url); -}; - +function getProviderId(url = undefined) { + return getRequiredUrlParam('providerId', + url); +} /** - * Dispatches the operation to the corresponding handler. - * - * @param {!firebaseui.auth.AuthUI} app The FirebaseUI instance. + * @param {!AuthUI} app The FirebaseUI instance. * @param {string|!Element} e The container element or the query selector. */ -firebaseui.auth.widget.dispatcher.dispatchOperation = function(app, e) { - // Check that web storage is available otherwise issue an error and exit. - // Some browsers like safari private mode disable web storage. - if (firebaseui.auth.storage.isAvailable()) { - firebaseui.auth.widget.dispatcher.doDispatchOperation_(app, e); - } else { - // Web storage not supported, display appropriate message. - // Get container element. - var container = firebaseui.auth.util.getElement( - e, firebaseui.auth.widget.dispatcher.ELEMENT_NOT_FOUND_); - // Show unrecoverable error message. - firebaseui.auth.widget.handler.common.handleUnrecoverableError( - app, - container, - firebaseui.auth.soy2.strings.errorNoWebStorage().toString()); - } -}; - - -/** - * @param {!firebaseui.auth.AuthUI} app The FirebaseUI instance. - * @param {string|!Element} e The container element or the query selector. - * @private - */ -firebaseui.auth.widget.dispatcher.doDispatchOperation_ = function(app, e) { - var container = firebaseui.auth.util.getElement( - e, firebaseui.auth.widget.dispatcher.ELEMENT_NOT_FOUND_); - +function doDispatchOperation(app, e) { + const container = util.getElement( + e, ELEMENT_NOT_FOUND); // TODO: refactor dispatcher to simplify and move logic externally. - switch (firebaseui.auth.widget.dispatcher.getMode(app)) { - case firebaseui.auth.widget.Config.WidgetMode.CALLBACK: + const redirectUrl = dispatcher.getRedirectUrl(app); + switch (dispatcher.getMode(app)) { + case Config.WidgetMode.CALLBACK: // If redirect URL available, save in non persistent storage. // Developer could directly go to // http://www.widgetpage.com/?signInSuccessUrl=http%3A%2F%2Fwww.google.com // On success this should redirect to google.com the same as when // mode=select is passed in query parameters. - var redirectUrl = firebaseui.auth.widget.dispatcher.getRedirectUrl(app); if (redirectUrl) { - firebaseui.auth.storage.setRedirectUrl(redirectUrl, app.getAppId()); + storage.setRedirectUrl(redirectUrl, app.getAppId()); } // Avoid UI flicker if there is no pending redirect. if (app.isPendingRedirect()) { - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.CALLBACK, app, container); + handler.handle( + HandlerName.CALLBACK, app, container); } else { // No pending redirect. Skip callback screen. - firebaseui.auth.widget.handler.common.handleSignInStart( + common.handleSignInStart( app, - container); + container, + // Pass sign-in email hint if available. + app.getSignInEmailHint()); } break; - case firebaseui.auth.widget.Config.WidgetMode.RESET_PASSWORD: - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.PASSWORD_RESET, + case Config.WidgetMode.RESET_PASSWORD: + handler.handle( + HandlerName.PASSWORD_RESET, app, container, - firebaseui.auth.widget.dispatcher.getActionCode_(), + getActionCode(), // Check if continue URL is available. if so, display a button to // redirect to it. - firebaseui.auth.widget.dispatcher.getContinueCallback_()); + getContinueCallback()); break; - case firebaseui.auth.widget.Config.WidgetMode.RECOVER_EMAIL: - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.EMAIL_CHANGE_REVOCATION, + case Config.WidgetMode.RECOVER_EMAIL: + handler.handle( + HandlerName.EMAIL_CHANGE_REVOCATION, app, container, - firebaseui.auth.widget.dispatcher.getActionCode_()); + getActionCode()); break; - case firebaseui.auth.widget.Config.WidgetMode.VERIFY_EMAIL: - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION, + case Config.WidgetMode.VERIFY_EMAIL: + handler.handle( + HandlerName.EMAIL_VERIFICATION, app, container, - firebaseui.auth.widget.dispatcher.getActionCode_(), + getActionCode(), // Check if continue URL is available. if so, display a button to // redirect to it. - firebaseui.auth.widget.dispatcher.getContinueCallback_()); + getContinueCallback()); break; - case firebaseui.auth.widget.Config.WidgetMode.SIGN_IN: + case Config.WidgetMode.SIGN_IN: // Complete signin. - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.EMAIL_LINK_SIGN_IN_CALLBACK, + handler.handle( + HandlerName.EMAIL_LINK_SIGN_IN_CALLBACK, app, container, - firebaseui.auth.util.getCurrentUrl()); + util.getCurrentUrl()); // Clear URL from email sign-in related query parameters to avoid // re-running on reload. app.clearEmailSignInState(); break; - case firebaseui.auth.widget.Config.WidgetMode.SELECT: + case Config.WidgetMode.SELECT: // If redirect URL available, save in non-persistent storage. - var redirectUrl = firebaseui.auth.widget.dispatcher.getRedirectUrl(app); if (redirectUrl) { - firebaseui.auth.storage.setRedirectUrl(redirectUrl, app.getAppId()); + storage.setRedirectUrl(redirectUrl, app.getAppId()); } - if (firebaseui.auth.acClient.isInitialized()) { + if (acClient.isInitialized()) { // Renders provider sign-in or simulates sign in with email click. - firebaseui.auth.widget.handler.common.handleSignInStart( + common.handleSignInStart( app, container); break; @@ -271,10 +273,10 @@ firebaseui.auth.widget.dispatcher.doDispatchOperation_ = function(app, e) { // Even if accountchooser.com is unavailable as a credential helper, // force UI shown callback since this is the first page to display. If // empty, render callback handler and do not try to select an account. - firebaseui.auth.widget.handler.common.loadAccountchooserJs( + common.loadAccountchooserJs( app, - function() { - firebaseui.auth.widget.handler.common.selectFromAccountChooser( + () => { + common.selectFromAccountChooser( app.getAuthUiGetter(), container, true); @@ -292,8 +294,10 @@ firebaseui.auth.widget.dispatcher.doDispatchOperation_ = function(app, e) { throw new Error('Unhandled widget operation.'); } // By default, UI is shown so invoke the uiShown callback. - var uiShownCallback = app.getConfig().getUiShownCallback(); + const uiShownCallback = app.getConfig().getUiShownCallback(); if (uiShownCallback) { uiShownCallback(); } -}; +} + +exports = dispatcher; diff --git a/javascript/widgets/dispatcher_test.js b/javascript/widgets/dispatcher_test.js index e20052ce..25385a4d 100644 --- a/javascript/widgets/dispatcher_test.js +++ b/javascript/widgets/dispatcher_test.js @@ -12,177 +12,56 @@ * limitations under the License. */ -/** - * @fileoverview Tests for dispatcher.js - */ - -goog.provide('firebaseui.auth.widget.dispatcherTest'); - -goog.require('firebaseui.auth.AuthUI'); -goog.require('firebaseui.auth.CredentialHelper'); -goog.require('firebaseui.auth.RedirectStatus'); -goog.require('firebaseui.auth.idp'); -goog.require('firebaseui.auth.storage'); -goog.require('firebaseui.auth.testing.FakeAcClient'); -goog.require('firebaseui.auth.testing.FakeAppClient'); -goog.require('firebaseui.auth.testing.FakeUtil'); -goog.require('firebaseui.auth.widget.Config'); -goog.require('firebaseui.auth.widget.dispatcher'); -goog.require('firebaseui.auth.widget.handler'); -goog.require('firebaseui.auth.widget.handler.common'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.testing.PropertyReplacer'); -goog.require('goog.testing.jsunit'); -goog.require('goog.testing.recordFunction'); - -goog.setTestOnly('firebaseui.auth.widget.dispatcherTest'); - - -var app; -var appId = 'glowing-heat-3485'; -var accountchooser = {}; -var stub = new goog.testing.PropertyReplacer(); -var testAc; -var testUtil; -var uiShownCallbackCount = 0; -var expectedSessionId = 'SESSION_ID_STRING'; -var getApp; -var externalAuthApp; -var testAuth; -var firebase = {}; -var testUtil; - +/** @fileoverview Tests for dispatcher.js */ + +goog.module('firebaseui.auth.widget.dispatcherTest'); +goog.setTestOnly(); + +const AuthUI = goog.require('firebaseui.auth.AuthUI'); +const Config = goog.require('firebaseui.auth.widget.Config'); +const FakeAcClient = goog.require('firebaseui.auth.testing.FakeAcClient'); +const FakeAppClient = goog.require('firebaseui.auth.testing.FakeAppClient'); +const FakeUtil = goog.require('firebaseui.auth.testing.FakeUtil'); +const PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); +const RedirectStatus = goog.require('firebaseui.auth.RedirectStatus'); +const asserts = goog.require('goog.asserts'); +const common = goog.require('firebaseui.auth.widget.handler.common'); +const dispatcher = goog.require('firebaseui.auth.widget.dispatcher'); +const dom = goog.require('goog.dom'); +const idp = goog.require('firebaseui.auth.idp'); +const recordFunction = goog.require('goog.testing.recordFunction'); +const storage = goog.require('firebaseui.auth.storage'); +const testSuite = goog.require('goog.testing.testSuite'); +const widgetHandler = goog.require('firebaseui.auth.widget.handler'); + +let app; +const appId = 'glowing-heat-3485'; +const stub = new PropertyReplacer(); +let testAc; +let testUtil; +let uiShownCallbackCount = 0; +let externalAuthApp; +let testAuth; /** Callback for tracking uiShown calls. */ function uiShownCallback() { uiShownCallbackCount++; } - -function setUp() { - // Used to initialize internal Auth instance. - firebase = {}; - firebase.initializeApp = function(options, name) { - return new firebaseui.auth.testing.FakeAppClient(options, name); - }; - // Build mock auth providers. - firebase['auth'] = {}; - for (var key in firebaseui.auth.idp.AuthProviders) { - firebase['auth'][firebaseui.auth.idp.AuthProviders[key]] = function() { - this.scopes = []; - this.customParameters = {}; - }; - firebase['auth'][firebaseui.auth.idp.AuthProviders[key]].PROVIDER_ID = key; - for (var method in firebaseui.auth.idp.SignInMethods[key]) { - firebase['auth'][firebaseui.auth.idp.AuthProviders[key]][method] = - firebaseui.auth.idp.SignInMethods[key][method]; - } - if (key != 'twitter.com' && key != 'password') { - firebase['auth'][firebaseui.auth.idp.AuthProviders[key]] - .prototype.addScope = function(scope) { - this.scopes.push(scope); - }; - } - if (key != 'password') { - // Record setCustomParameters for all OAuth providers. - firebase['auth'][firebaseui.auth.idp.AuthProviders[key]] - .prototype.setCustomParameters = function(customParameters) { - this.customParameters = customParameters; - }; - } - } - // Initialize external Firebase app. - externalAuthApp = new firebaseui.auth.testing.FakeAppClient(); - // Pass installed external Firebase Auth instance. - app = new firebaseui.auth.AuthUI(externalAuthApp.auth().install(), appId); - // Install internal instance. - testAuth = app.getAuth().install(); - uiShownCallbackCount = 0; - app.setConfig({ - queryParameterForWidgetMode: 'mode', - widgetUrl: 'http://localhost/firebase', - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM - }); - // Record all selectFromAccountChooser calls. - stub.set( - firebaseui.auth.widget.handler.common, - 'selectFromAccountChooser', - goog.testing.recordFunction( - firebaseui.auth.widget.handler.common.selectFromAccountChooser)); - testAc = new firebaseui.auth.testing.FakeAcClient().install(); - testUtil = new firebaseui.auth.testing.FakeUtil().install(); - // Record all widget handler calls. - for (var handlerName in firebaseui.auth.widget.HandlerName) { - stub.set( - firebaseui.auth.widget.handlers_, - firebaseui.auth.widget.HandlerName[handlerName], - goog.testing.recordFunction()); - } - // Remove redirect URL from storage. - firebaseui.auth.storage.removeRedirectUrl(app.getAppId()); - // Reset query parameter for sign-in success URL to default. - app.updateConfig( - 'queryParameterForSignInSuccessUrl', 'signInSuccessUrl'); - // Reset accountchooser.com force UI shown flag. - firebaseui.auth.widget.handler.common.acForceUiShown_ = false; - // Assume widget already rendered and Auth UI global reference set. - stub.replace( - firebaseui.auth.AuthUI, - 'getAuthUi', - function() { - return app; - }); - // Simulate accountchooser.com client loaded. - stub.set( - firebaseui.auth.widget.handler.common, - 'loadAccountchooserJs', - function(app, callback, opt_forceUiShownCallback) { - firebaseui.auth.widget.handler.common.acForceUiShown_ = - !!opt_forceUiShownCallback; - callback(); - }); - // Current rendered FirebaseUI getter. - getApp = function() { - return app; - }; -} - - -function tearDown() { - stub.reset(); - if (testAc) { - testAc.uninstall(); - } - // Uninstall internal and external Auth instance. - externalAuthApp.auth().uninstall(); - if (testAuth) { - testAuth.uninstall(); - } - if (testUtil) { - testUtil.uninstall(); - } - // Reset AuthUI internals. - firebaseui.auth.AuthUI.resetAllInternals(); - testUtil.uninstall(); -} - - /** * Test helper used to check that selectFromAccountChooser was called with the * expected parameters. - * - * @param {!firebaseui.auth.AuthUI} app The FirebaseUI app instance. + * @param {!AuthUI} app The FirebaseUI app instance. * @param {!Element} container The container DOM element for the handler. - * @param {boolean=} opt_disableSelectOnEmpty Whether to disable selecting an + * @param {boolean=} disableSelectOnEmpty Whether to disable selecting an * account when there are no pending results. - * @param {string=} opt_callbackUrl The URL to return to when the flow finishes. + * @param {string=} callbackUrl The URL to return to when the flow finishes. * The default is current URL. */ function assertSelectFromAccountChooserInvoked( - app, container, opt_disableSelectOnEmpty, opt_callbackUrl) { - var selectFromAccountChooser = - firebaseui.auth.widget.handler.common.selectFromAccountChooser; + app, container, disableSelectOnEmpty = undefined, callbackUrl = undefined) { + const selectFromAccountChooser = + common.selectFromAccountChooser; assertEquals( 1, selectFromAccountChooser.getCallCount()); @@ -193,14 +72,13 @@ function assertSelectFromAccountChooserInvoked( container, selectFromAccountChooser.getLastCall().getArgument(1)); assertEquals( - opt_disableSelectOnEmpty, + disableSelectOnEmpty, selectFromAccountChooser.getLastCall().getArgument(2)); assertEquals( - opt_callbackUrl, + callbackUrl, selectFromAccountChooser.getLastCall().getArgument(3)); } - /** * Asserts correct handler with correct parameter called. * @param {!firebaseui.auth.widget.HandlerName} handlerName The handler name @@ -208,628 +86,750 @@ function assertSelectFromAccountChooserInvoked( * @param {...*} var_args Additional arguments to assert, relevant to handler. */ function assertHandlerInvoked(handlerName, var_args) { - var handler = + const handler = firebaseui.auth.widget.handlers_[handlerName]; // Assert expected handler called. assertEquals( 1, handler.getCallCount()); // Assert expected parameters used for handler. - for (var i = 0; i < arguments.length - 1; i++) { + for (let i = 0; i < arguments.length - 1; i++) { assertEquals( arguments[i + 1], handler.getLastCall().getArgument(i)); } } - -function testGetMode() { - var url = 'http://localhost/callback?mode=select'; - assertEquals( - firebaseui.auth.widget.Config.WidgetMode.SELECT, - firebaseui.auth.widget.dispatcher.getMode(app, url)); -} - - -function testGetRedirectUrl() { - var redirectUrl = 'http://www.example.com'; - // No redirect URL available. - var url = 'http://localhost/callback?mode=select'; - assertEquals(null, - firebaseui.auth.widget.dispatcher.getRedirectUrl(app, url)); - // Set current page URL to include a redirect URL. - url = 'http://localhost/callback?mode=select&signInSuccessUrl=' + - encodeURIComponent(redirectUrl); - // Check that the redirect URL is successfully retrieved. - assertEquals( - redirectUrl, - firebaseui.auth.widget.dispatcher.getRedirectUrl(app, url)); - - // Update the query parameter for redirect URL. - url = 'http://localhost/callback?mode=select&signInSuccessUrl=' + - encodeURIComponent('javascript:doEvilStuff()'); - // Confirm redirect URL is successfully sanitized. - assertEquals( - 'about:invalid#zClosurez', - firebaseui.auth.widget.dispatcher.getRedirectUrl(app, url)); - - // Change the query parameter for redirect URL. - app.updateConfig( - 'queryParameterForSignInSuccessUrl', 'continue'); - // Confirm that previous redirect URL no longer valid. - assertEquals(null, - firebaseui.auth.widget.dispatcher.getRedirectUrl(app, url)); - // Update the query parameter for redirect URL. - url = 'http://localhost/callback?mode=select&continue=' + - encodeURIComponent(redirectUrl); - // Confirm redirect URL using new query parameter is successfully retrieved. - assertEquals( - redirectUrl, - firebaseui.auth.widget.dispatcher.getRedirectUrl(app, url)); - - // Update the query parameter for redirect URL. - url = 'http://localhost/callback?mode=select&continue=' + - encodeURIComponent('javascript:doEvilStuff()'); - // Confirm redirect URL is successfully sanitized. - assertEquals( - 'about:invalid#zClosurez', - firebaseui.auth.widget.dispatcher.getRedirectUrl(app, url)); -} - - -function testGetMode_noMode() { - // No fragment, no query, should return callback mode. - var url = 'http://localhost/callback'; - assertEquals( - firebaseui.auth.widget.Config.WidgetMode.CALLBACK, - firebaseui.auth.widget.dispatcher.getMode(app, url)); - // Unsupported query but no mode, should return callback. - url = 'http://localhost/callback?query=value'; - assertEquals( - firebaseui.auth.widget.Config.WidgetMode.CALLBACK, - firebaseui.auth.widget.dispatcher.getMode(app, url)); - // No query but fragment provided should return callback. - url = 'http://localhost/callback#fragment'; - assertEquals( - firebaseui.auth.widget.Config.WidgetMode.CALLBACK, - firebaseui.auth.widget.dispatcher.getMode(app, url)); -} - - -function testGetMode_unrecognizedMode() { - var url = 'http://localhost/callback?mode=What'; - assertEquals( - firebaseui.auth.widget.Config.WidgetMode.CALLBACK, - firebaseui.auth.widget.dispatcher.getMode(app, url)); -} - - -function testGetMode_nonDefaultModeParameter() { - app.setConfig({ - queryParameterForWidgetMode: 'action' - }); - var url = 'http://localhost/callback?action=resetPassword'; - assertEquals( - firebaseui.auth.widget.Config.WidgetMode.RESET_PASSWORD, - firebaseui.auth.widget.dispatcher.getMode(app, url)); -} - - -function testGetActionCode() { - var url = 'http://location/callback?mode=forgot&oobCode=12345'; - assertEquals('12345', firebaseui.auth.widget.dispatcher.getActionCode_(url)); -} - - /** * @param {string} mode The dispatcher mode to simulate. * @param {?Object=} opt_params The parameters in the URL to simulate. */ function setModeAndUrlParams(mode, opt_params) { - var params = opt_params || {}; + const params = opt_params || {}; stub.replace( firebaseui.auth.util, 'getCurrentUrl', - function() { - var currentUrl = 'https://www.example.com/'; + () => { + let currentUrl = 'https://www.example.com/'; if (mode) { currentUrl += '?mode=' + encodeURIComponent(mode); - for (var name in params) { - currentUrl += '&' + name + '=' + - encodeURIComponent(goog.asserts.assertString(params[name])); + for (let name in params) { + currentUrl += `&${name}=` + + encodeURIComponent(asserts.assertString(params[name])); } } return currentUrl; }); } +testSuite({ + setUp() { + goog.global.firebase = {}; + const firebase = goog.global.firebase; + // Used to initialize internal Auth instance. + firebase.initializeApp = + (options, name) => new FakeAppClient(options, name); + // Build mock auth providers. + firebase['auth'] = {}; + for (let key in idp.AuthProviders) { + firebase['auth'][idp.AuthProviders[key]] = function() { + this.scopes = []; + this.customParameters = {}; + }; + firebase['auth'][idp.AuthProviders[key]].PROVIDER_ID = key; + for (let method in idp.SignInMethods[key]) { + firebase['auth'][idp.AuthProviders[key]][method] = + idp.SignInMethods[key][method]; + } + if (key != 'twitter.com' && key != 'password') { + firebase['auth'][idp.AuthProviders[key]] + .prototype.addScope = function(scope) { + this.scopes.push(scope); + }; + } + if (key != 'password') { + // Record setCustomParameters for all OAuth providers. + firebase['auth'][idp.AuthProviders[key]] + .prototype.setCustomParameters = function(customParameters) { + this.customParameters = customParameters; + }; + } + } + // Initialize external Firebase app. + externalAuthApp = new FakeAppClient(); + // Pass installed external Firebase Auth instance. + app = new AuthUI(externalAuthApp.auth().install(), appId); + // Install internal instance. + testAuth = app.getAuth().install(); + uiShownCallbackCount = 0; + app.setConfig({ + queryParameterForWidgetMode: 'mode', + widgetUrl: 'http://localhost/firebase', + 'credentialHelper': + Config.CredentialHelper.ACCOUNT_CHOOSER_COM, + }); + // Record all selectFromAccountChooser calls. + stub.set( + common, + 'selectFromAccountChooser', + recordFunction( + common.selectFromAccountChooser)); + testAc = new FakeAcClient().install(); + testUtil = new FakeUtil().install(); + // Record all widget handler calls. + for (let handlerName in firebaseui.auth.widget.HandlerName) { + stub.set( + firebaseui.auth.widget.handlers_, + firebaseui.auth.widget.HandlerName[handlerName], + recordFunction()); + } + // Remove redirect URL from storage. + storage.removeRedirectUrl(app.getAppId()); + // Reset query parameter for sign-in success URL to default. + app.updateConfig( + 'queryParameterForSignInSuccessUrl', 'signInSuccessUrl'); + // Reset accountchooser.com force UI shown flag. + common.acForceUiShown_ = false; + // Assume widget already rendered and Auth UI global reference set. + stub.replace( + AuthUI, + 'getAuthUi', + () => app); + // Simulate accountchooser.com client loaded. + stub.set( + common, + 'loadAccountchooserJs', + (app, callback, opt_forceUiShownCallback) => { + common.acForceUiShown_ = + !!opt_forceUiShownCallback; + callback(); + }); + }, + + tearDown() { + stub.reset(); + if (testAc) { + testAc.uninstall(); + } + // Uninstall internal and external Auth instance. + externalAuthApp.auth().uninstall(); + if (testAuth) { + testAuth.uninstall(); + } + if (testUtil) { + testUtil.uninstall(); + } + // Reset AuthUI internals. + AuthUI.resetAllInternals(); + testUtil.uninstall(); + }, -function testDispatchOperation_noStorageSupport_elementNotFound() { - // Test dispatchOperation with missing element. - // Simulate web storage unavailability. - stub.set(firebaseui.auth.storage, 'isAvailable', function() {return false;}); - // Test correct error message thrown when widget element not found. - try { - firebaseui.auth.widget.dispatcher.dispatchOperation(app, '#notFound'); - fails('Should have thrown an error!'); - } catch (e) { - // Confirm correct error message thrown. + testGetMode() { + const url = 'http://localhost/callback?mode=select'; assertEquals( - e.message, - 'Could not find the FirebaseUI widget element on the page.'); - } -} - - -function testDispatchOperation_withStorageSupport_elementNotFound() { - // Test dispatchOperation with missing element. - // Test correct error message thrown when widget element not found. - try { - firebaseui.auth.widget.dispatcher.dispatchOperation(app, '#notFound'); - fails('Should have thrown an error!'); - } catch (e) { - // Confirm correct error message thrown. + Config.WidgetMode.SELECT, + dispatcher.getMode(app, url)); + }, + + testGetRedirectUrl() { + const redirectUrl = 'http://www.example.com'; + // No redirect URL available. + let url = 'http://localhost/callback?mode=select'; + assertEquals(null, + dispatcher.getRedirectUrl(app, url)); + // Set current page URL to include a redirect URL. + url = 'http://localhost/callback?mode=select&signInSuccessUrl=' + + encodeURIComponent(redirectUrl); + // Check that the redirect URL is successfully retrieved. assertEquals( - e.message, - 'Could not find the FirebaseUI widget element on the page.'); - } -} + redirectUrl, + dispatcher.getRedirectUrl(app, url)); + // Update the query parameter for redirect URL. + url = 'http://localhost/callback?mode=select&signInSuccessUrl=' + + encodeURIComponent('javascript:doEvilStuff()'); + // Confirm redirect URL is successfully sanitized. + assertEquals( + 'about:invalid#zClosurez', + dispatcher.getRedirectUrl(app, url)); + + // Change the query parameter for redirect URL. + app.updateConfig( + 'queryParameterForSignInSuccessUrl', 'continue'); + // Confirm that previous redirect URL no longer valid. + assertEquals(null, + dispatcher.getRedirectUrl(app, url)); + // Update the query parameter for redirect URL. + url = 'http://localhost/callback?mode=select&continue=' + + encodeURIComponent(redirectUrl); + // Confirm redirect URL using new query parameter is successfully retrieved. + assertEquals( + redirectUrl, + dispatcher.getRedirectUrl(app, url)); -function testDispatchOperation_noStorageSupport() { - var element = goog.dom.createElement('div'); - var expectedErrorMessage = 'The browser you are using does not support Web ' + - 'Storage. Please try again in a different browser.'; - // Detect call to handle unrecoverable error, and confirm container and error - // message passed. - var called = false; - stub.set( - firebaseui.auth.widget.handler.common, - 'handleUnrecoverableError', - function(app, container, errorMessage) { - called = true; - assertEquals(element, container); - assertEquals(expectedErrorMessage, errorMessage); - }); - // Simulate web storage unavailability. - stub.set(firebaseui.auth.storage, 'isAvailable', function() {return false;}); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertTrue(called); -} + // Update the query parameter for redirect URL. + url = 'http://localhost/callback?mode=select&continue=' + + encodeURIComponent('javascript:doEvilStuff()'); + // Confirm redirect URL is successfully sanitized. + assertEquals( + 'about:invalid#zClosurez', + dispatcher.getRedirectUrl(app, url)); + }, + testGetMode_noMode() { + // No fragment, no query, should return callback mode. + let url = 'http://localhost/callback'; + assertEquals( + Config.WidgetMode.CALLBACK, + dispatcher.getMode(app, url)); + // Unsupported query but no mode, should return callback. + url = 'http://localhost/callback?query=value'; + assertEquals( + Config.WidgetMode.CALLBACK, + dispatcher.getMode(app, url)); + // No query but fragment provided should return callback. + url = 'http://localhost/callback#fragment'; + assertEquals( + Config.WidgetMode.CALLBACK, + dispatcher.getMode(app, url)); + }, -function testDispatchOperation_select() { - app.setConfig({ - 'callbacks': { - 'uiShown': uiShownCallback + testGetMode_unrecognizedMode() { + const url = 'http://localhost/callback?mode=What'; + assertEquals( + Config.WidgetMode.CALLBACK, + dispatcher.getMode(app, url)); + }, + + testGetMode_nonDefaultModeParameter() { + app.setConfig({ + queryParameterForWidgetMode: 'action', + }); + const url = 'http://localhost/callback?action=resetPassword'; + assertEquals( + Config.WidgetMode.RESET_PASSWORD, + dispatcher.getMode(app, url)); + }, + + testDispatchOperation_noStorageSupport_elementNotFound() { + // Test dispatchOperation with missing element. + // Simulate web storage unavailability. + stub.set(storage, 'isAvailable', () => false); + // Test correct error message thrown when widget element not found. + try { + dispatcher.dispatchOperation(app, '#notFound'); + fails('Should have thrown an error!'); + } catch (e) { + // Confirm correct error message thrown. + assertEquals( + e.message, + 'Could not find the FirebaseUI widget element on the page.'); } - }); - assertEquals(uiShownCallbackCount, 0); - var element = goog.dom.createElement('div'); - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.SELECT); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertSelectFromAccountChooserInvoked(app, element, true, undefined); - assertEquals(uiShownCallbackCount, 1); - // Force UI shown callback should be set to true. - assertTrue(firebaseui.auth.widget.handler.common.acForceUiShown_); - // Callback handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); - - // accountchooser.com client should be initialized at this point. - // Call dispatchOperation again. - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Provider sign-in invoked directly. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, element); - // UI shown callback should be triggered again. - assertEquals(uiShownCallbackCount, 2); -} - - -function testDispatchOperation_acDisabled() { - // Disable credential helpers and add uiShownCallback. - app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, - 'callbacks': { - 'uiShown': uiShownCallback + }, + + testDispatchOperation_withStorageSupport_elementNotFound() { + // Test dispatchOperation with missing element. + // Test correct error message thrown when widget element not found. + try { + dispatcher.dispatchOperation(app, '#notFound'); + fails('Should have thrown an error!'); + } catch (e) { + // Confirm correct error message thrown. + assertEquals( + e.message, + 'Could not find the FirebaseUI widget element on the page.'); } - }); - // Skip select. - testAc.setSkipSelect(true); - testAc.setAvailability(false); - assertEquals(uiShownCallbackCount, 0); - var element = goog.dom.createElement('div'); - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.SELECT); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertSelectFromAccountChooserInvoked(app, element, true, undefined); - // Callback handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); - assertEquals(uiShownCallbackCount, 1); - // Force UI shown callback should be set to true. - assertTrue(firebaseui.auth.widget.handler.common.acForceUiShown_); - - // accountchooser.com client should be initialized at this point. - // Call dispatchOperation again. - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Provider sign-in invoked directly. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, element); - // UI shown callback should be triggered again. - assertEquals(uiShownCallbackCount, 2); -} - - -function testDispatchOperation_selectWithRedirectUrl() { - var element = goog.dom.createElement('div'); - // Redirect URL. - var redirectUrl = 'http://www.example.com'; - // Simulate redirect URL above being available in URL. - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.SELECT, { - 'signInSuccessUrl': redirectUrl - }); - // No redirect URL. - assertFalse(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertSelectFromAccountChooserInvoked(app, element, true, undefined); - // Redirect URL should be set now in storage. - assertTrue(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - // Confirm it is the correct value. - assertEquals( - redirectUrl, - firebaseui.auth.storage.getRedirectUrl(app.getAppId())); - // Force UI shown callback should be set to true. - assertTrue(firebaseui.auth.widget.handler.common.acForceUiShown_); - // Callback handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); -} - - -function testDispatchOperation_selectWithUnsafeRedirectUrl() { - var element = goog.dom.createElement('div'); - // Unsafe redirect URL. - var redirectUrl = 'javascript:doEvilStuff()'; - // Simulate unsafe redirect URL above being available in URL. - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.SELECT, { - 'signInSuccessUrl': redirectUrl - }); - // No redirect URL. - assertFalse(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertSelectFromAccountChooserInvoked(app, element, true, undefined); - // Redirect URL should be set now in storage. - assertTrue(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - // Confirm the sanitized value is returned. - assertEquals( - 'about:invalid#zClosurez', - firebaseui.auth.storage.getRedirectUrl(app.getAppId())); - // Force UI shown callback should be set to true. - assertTrue(firebaseui.auth.widget.handler.common.acForceUiShown_); - // Callback handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); -} - - -function testDispatchOperation_callbackWithRedirectUrl() { - var element = goog.dom.createElement('div'); - // Redirect URL. - var redirectUrl = 'http://www.example.com'; - // Set current mode to callback mode. - // Simulate redirect URL above being available in URL. - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK, { - 'signInSuccessUrl': redirectUrl - }); - // Simulate app returning from redirect sign-in operation. - var redirectStatus = new firebaseui.auth.RedirectStatus(); - firebaseui.auth.storage.setRedirectStatus(redirectStatus, app.getAppId()); - // No redirect URL. - assertFalse(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Redirect URL should be set now in storage. - assertTrue(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - // Confirm it is the correct value. - assertEquals( - redirectUrl, - firebaseui.auth.storage.getRedirectUrl(app.getAppId())); - // Callback handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); -} - - -function testDispatchOperation_callbackWithRedirectUrl_noPendingRedirect() { - var element = goog.dom.createElement('div'); - // Redirect URL. - var redirectUrl = 'http://www.example.com'; - // Set current mode to callback mode. - // Simulate redirect URL above being available in URL. - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK, { - 'signInSuccessUrl': redirectUrl - }); - // Simulate app not returning from redirect sign-in operation. - firebaseui.auth.storage.removeRedirectStatus(app.getAppId()); - // No redirect URL. - assertFalse(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Redirect URL should be set now in storage. - assertTrue(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - // Confirm it is the correct value. - assertEquals( - redirectUrl, - firebaseui.auth.storage.getRedirectUrl(app.getAppId())); - // Provider sign in handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, element); -} - - -function testDispatchOperation_callbackWithUnsafeRedirectUrl() { - var element = goog.dom.createElement('div'); - // Unsafe redirect URL. - var redirectUrl = 'javascript:doEvilStuff()'; - // Simulate unsafe redirect URL above being available in URL. - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.SELECT, { - 'signInSuccessUrl': redirectUrl - }); - // Simulate app returning from redirect sign-in operation. - var redirectStatus = new firebaseui.auth.RedirectStatus(); - firebaseui.auth.storage.setRedirectStatus(redirectStatus, app.getAppId()); - // No redirect URL. - assertFalse(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Redirect URL should be set now in storage. - assertTrue(firebaseui.auth.storage.hasRedirectUrl(app.getAppId())); - // Confirm the sanitized value is returned. - assertEquals( - 'about:invalid#zClosurez', - firebaseui.auth.storage.getRedirectUrl(app.getAppId())); - // Callback handler should be invoked. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); -} - - -function testDispatchOperation_noMode_providerFirst() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams(null); - // Simulate app returning from redirect sign-in operation. - var redirectStatus = new firebaseui.auth.RedirectStatus(); - firebaseui.auth.storage.setRedirectStatus(redirectStatus, app.getAppId()); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Callback handler should be invoked since no mode will result with CALLBACK - // mode. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, app, element); -} - - -function testDispatchOperation_callback() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK); - // Simulate app returning from redirect sign-in operation. - var redirectStatus = new firebaseui.auth.RedirectStatus(); - firebaseui.auth.storage.setRedirectStatus(redirectStatus, app.getAppId()); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.CALLBACK, - app, - element); -} - - -function testDispatchOperation_callback_noPendingRedirect() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK); - // Simulate app not returning from redirect sign-in operation. - firebaseui.auth.storage.removeRedirectStatus(app.getAppId()); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // Provider sign in handler should be rendered. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, - app, - element); -} - - -function testDispatchOperation_callback_canSkipNascarScreen() { - // Checks to make sure that when immediateFederatedRedirect is true - // and all the correct options are set, the 'nascar' sign-in screen will - // be skipped. - var element = goog.dom.createElement('div'); - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK); - // Simulate app not returning from redirect sign-in operation. - firebaseui.auth.storage.removeRedirectStatus(app.getAppId()); - // In order for an immediate redirect to succeed all of the following - // options must be set: - app.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT - }); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // The federated redirect handler should trigger. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.FEDERATED_REDIRECT, - app, - element); -} - - -function testDispatchOperation_callback_canShowNascarScreen() { - // Checks to make sure that even if immediateFederatedRedirect is true - // unless all the correct options are set, the 'nascar' sign-in screen will - // not be skipped. - var element = goog.dom.createElement('div'); - setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK); - // Simulate app not returning from redirect sign-in operation. - firebaseui.auth.storage.removeRedirectStatus(app.getAppId()); - // The immediate redirect should not be triggered (since there is more - // than one federated provider and it is using a popup). - app.setConfig({ - 'immediateFederatedRedirect': true, - 'signInOptions': [ - firebase.auth.GoogleAuthProvider.PROVIDER_ID, - firebase.auth.FacebookAuthProvider.PROVIDER_ID], - 'signInFlow': firebaseui.auth.widget.Config.SignInFlow.POPUP - }); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - // The normal provider sign in handler 'nascar' screen should be rendered. - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, - app, - element); -} - - -function testDispatchOperation_revokeChangeEmail() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams( - firebaseui.auth.widget.Config.WidgetMode.RECOVER_EMAIL, - {'oobCode': 'ACTION_CODE'}); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.EMAIL_CHANGE_REVOCATION, - app, - element, - 'ACTION_CODE'); -} - - -function testDispatchOperation_verifyEmail() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams( - firebaseui.auth.widget.Config.WidgetMode.VERIFY_EMAIL, - {'oobCode': 'ACTION_CODE'}); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION, - app, - element, - 'ACTION_CODE'); -} - - -function testDispatchOperation_emailLinkSignIn() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams( - firebaseui.auth.widget.Config.WidgetMode.SIGN_IN, - {'oobCode': 'ACTION_CODE', 'lang': 'en'}); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.EMAIL_LINK_SIGN_IN_CALLBACK, - app, - element, - 'https://www.example.com/?mode=signIn&oobCode=ACTION_CODE&lang=en'); - // Confirm history state replaced. - testUtil.assertReplaceHistoryState( - { - 'state': 'signIn', - 'mode': 'emailLink', - 'operation': 'clear' + }, + + testDispatchOperation_noStorageSupport() { + const element = dom.createElement('div'); + const expectedErrorMessage = + 'The browser you are using does not support Web ' + + 'Storage. Please try again in a different browser.'; + // Detect call to handle unrecoverable error, and confirm container and + // error message passed. + let called = false; + stub.set( + common, + 'handleUnrecoverableError', + (app, container, errorMessage) => { + called = true; + assertEquals(element, container); + assertEquals(expectedErrorMessage, errorMessage); + }); + // Simulate web storage unavailability. + stub.set(storage, 'isAvailable', () => false); + dispatcher.dispatchOperation(app, element); + assertTrue(called); + }, + + testDispatchOperation_select() { + app.setConfig({ + 'callbacks': { + 'uiShown': uiShownCallback, }, - // Same document title should be kept. - document.title, - // URL should be cleared from email sign-in related query params. - 'https://www.example.com/?lang=en'); -} - - -function testDispatchOperation_emailLinkSignIn_tenantId() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams( - firebaseui.auth.widget.Config.WidgetMode.SIGN_IN, - {'oobCode': 'ACTION_CODE', 'lang': 'en', 'tenantId': 'TENANT_ID'}); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.EMAIL_LINK_SIGN_IN_CALLBACK, - app, - element, - 'https://www.example.com/?mode=signIn&oobCode=ACTION_CODE&lang=en&' + - 'tenantId=TENANT_ID'); - // Confirm history state replaced. - testUtil.assertReplaceHistoryState( - { - 'state': 'signIn', - 'mode': 'emailLink', - 'operation': 'clear' + }); + assertEquals(uiShownCallbackCount, 0); + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.SELECT); + dispatcher.dispatchOperation(app, element); + assertSelectFromAccountChooserInvoked(app, element, true, undefined); + assertEquals(uiShownCallbackCount, 1); + // Force UI shown callback should be set to true. + assertTrue(common.acForceUiShown_); + // Callback handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + + // accountchooser.com client should be initialized at this point. + // Call dispatchOperation again. + dispatcher.dispatchOperation(app, element); + // Provider sign-in invoked directly. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, element); + // UI shown callback should be triggered again. + assertEquals(uiShownCallbackCount, 2); + }, + + testDispatchOperation_acDisabled() { + // Disable credential helpers and add uiShownCallback. + app.setConfig({ + 'credentialHelper': Config.CredentialHelper.NONE, + 'callbacks': { + 'uiShown': uiShownCallback, }, - // Same document title should be kept. - document.title, - // URL should be cleared from email sign-in related query params. - 'https://www.example.com/?lang=en'); - assertNull(app.getTenantId()); assertNull(app.getTenantId()); -} - - -function testDispatchOperation_verifyEmail_continueUrl() { - var element = goog.dom.createElement('div'); - var continueUrl = 'http://www.example.com/path/page?a=1#b=2'; - stub.replace( - firebaseui.auth.util, - 'getCurrentUrl', - function() { - return 'http://example.firebaseapp.com/__/auth/action?mode=' + - 'verifyEmail&apiKey=API_KEY&oobCode=ACTION_CODE&continueUrl=' + - encodeURIComponent(continueUrl); - }); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION, - app, - element, - 'ACTION_CODE'); - // Get callback passed to verify email handler and confirm it redirects to - // continue URL. - var handler = - firebaseui.auth.widget.handlers_[ - firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION]; - var continueCallback = handler.getLastCall().getArgument(3); - continueCallback(); - testUtil.assertGoTo(continueUrl); -} - - -function testDispatchOperation_resetPassword() { - var element = goog.dom.createElement('div'); - setModeAndUrlParams( - firebaseui.auth.widget.Config.WidgetMode.RESET_PASSWORD, - {'oobCode': 'ACTION_CODE'}); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PASSWORD_RESET, - app, - element, - 'ACTION_CODE'); -} + }); + // Skip select. + testAc.setSkipSelect(true); + testAc.setAvailability(false); + assertEquals(uiShownCallbackCount, 0); + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.SELECT); + dispatcher.dispatchOperation(app, element); + assertSelectFromAccountChooserInvoked(app, element, true, undefined); + // Callback handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + assertEquals(uiShownCallbackCount, 1); + // Force UI shown callback should be set to true. + assertTrue(common.acForceUiShown_); + + // accountchooser.com client should be initialized at this point. + // Call dispatchOperation again. + dispatcher.dispatchOperation(app, element); + // Provider sign-in invoked directly. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, element); + // UI shown callback should be triggered again. + assertEquals(uiShownCallbackCount, 2); + }, + + testDispatchOperation_selectWithRedirectUrl() { + const element = dom.createElement('div'); + // Redirect URL. + const redirectUrl = 'http://www.example.com'; + // Simulate redirect URL above being available in URL. + setModeAndUrlParams(Config.WidgetMode.SELECT, { + 'signInSuccessUrl': redirectUrl, + }); + // No redirect URL. + assertFalse(storage.hasRedirectUrl(app.getAppId())); + dispatcher.dispatchOperation(app, element); + assertSelectFromAccountChooserInvoked(app, element, true, undefined); + // Redirect URL should be set now in storage. + assertTrue(storage.hasRedirectUrl(app.getAppId())); + // Confirm it is the correct value. + assertEquals( + redirectUrl, + storage.getRedirectUrl(app.getAppId())); + // Force UI shown callback should be set to true. + assertTrue(common.acForceUiShown_); + // Callback handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + }, + + testDispatchOperation_selectWithUnsafeRedirectUrl() { + const element = dom.createElement('div'); + // Unsafe redirect URL. + const redirectUrl = 'javascript:doEvilStuff()'; + // Simulate unsafe redirect URL above being available in URL. + setModeAndUrlParams(Config.WidgetMode.SELECT, { + 'signInSuccessUrl': redirectUrl, + }); + // No redirect URL. + assertFalse(storage.hasRedirectUrl(app.getAppId())); + dispatcher.dispatchOperation(app, element); + assertSelectFromAccountChooserInvoked(app, element, true, undefined); + // Redirect URL should be set now in storage. + assertTrue(storage.hasRedirectUrl(app.getAppId())); + // Confirm the sanitized value is returned. + assertEquals( + 'about:invalid#zClosurez', + storage.getRedirectUrl(app.getAppId())); + // Force UI shown callback should be set to true. + assertTrue(common.acForceUiShown_); + // Callback handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + }, + + testDispatchOperation_callbackWithRedirectUrl() { + const element = dom.createElement('div'); + // Redirect URL. + const redirectUrl = 'http://www.example.com'; + // Set current mode to callback mode. + // Simulate redirect URL above being available in URL. + setModeAndUrlParams(Config.WidgetMode.CALLBACK, { + 'signInSuccessUrl': redirectUrl, + }); + // Simulate app returning from redirect sign-in operation. + const redirectStatus = new RedirectStatus(); + storage.setRedirectStatus(redirectStatus, app.getAppId()); + // No redirect URL. + assertFalse(storage.hasRedirectUrl(app.getAppId())); + dispatcher.dispatchOperation(app, element); + // Redirect URL should be set now in storage. + assertTrue(storage.hasRedirectUrl(app.getAppId())); + // Confirm it is the correct value. + assertEquals( + redirectUrl, + storage.getRedirectUrl(app.getAppId())); + // Callback handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + }, + + testDispatchOperation_callbackWithRedirectUrl_noPendingRedirect() { + const element = dom.createElement('div'); + // Redirect URL. + const redirectUrl = 'http://www.example.com'; + // Set current mode to callback mode. + // Simulate redirect URL above being available in URL. + setModeAndUrlParams(Config.WidgetMode.CALLBACK, { + 'signInSuccessUrl': redirectUrl, + }); + // Simulate app not returning from redirect sign-in operation. + storage.removeRedirectStatus(app.getAppId()); + // No redirect URL. + assertFalse(storage.hasRedirectUrl(app.getAppId())); + dispatcher.dispatchOperation(app, element); + // Redirect URL should be set now in storage. + assertTrue(storage.hasRedirectUrl(app.getAppId())); + // Confirm it is the correct value. + assertEquals( + redirectUrl, + storage.getRedirectUrl(app.getAppId())); + // Provider sign in handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, element); + }, + + testDispatchOperation_callbackWithUnsafeRedirectUrl() { + const element = dom.createElement('div'); + // Unsafe redirect URL. + const redirectUrl = 'javascript:doEvilStuff()'; + // Simulate unsafe redirect URL above being available in URL. + setModeAndUrlParams(Config.WidgetMode.SELECT, { + 'signInSuccessUrl': redirectUrl, + }); + // Simulate app returning from redirect sign-in operation. + const redirectStatus = new RedirectStatus(); + storage.setRedirectStatus(redirectStatus, app.getAppId()); + // No redirect URL. + assertFalse(storage.hasRedirectUrl(app.getAppId())); + dispatcher.dispatchOperation(app, element); + // Redirect URL should be set now in storage. + assertTrue(storage.hasRedirectUrl(app.getAppId())); + // Confirm the sanitized value is returned. + assertEquals( + 'about:invalid#zClosurez', + storage.getRedirectUrl(app.getAppId())); + // Callback handler should be invoked. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + }, + + testDispatchOperation_noMode_providerFirst() { + const element = dom.createElement('div'); + setModeAndUrlParams(null); + // Simulate app returning from redirect sign-in operation. + const redirectStatus = new RedirectStatus(); + storage.setRedirectStatus(redirectStatus, app.getAppId()); + dispatcher.dispatchOperation(app, element); + // Callback handler should be invoked since no mode will result with + // CALLBACK mode. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, app, element); + }, + + testDispatchOperation_callback() { + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.CALLBACK); + // Simulate app returning from redirect sign-in operation. + const redirectStatus = new RedirectStatus(); + storage.setRedirectStatus(redirectStatus, app.getAppId()); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.CALLBACK, + app, + element); + }, + + testDispatchOperation_callback_noPendingRedirect() { + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.CALLBACK); + // Simulate app not returning from redirect sign-in operation. + storage.removeRedirectStatus(app.getAppId()); + dispatcher.dispatchOperation(app, element); + // Provider sign in handler should be rendered. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, + app, + element); + }, + + testDispatchOperation_callback_signInHint() { + // Test CALLBACK operation with signInHint. + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.CALLBACK); + // Simulate app not returning from redirect sign-in operation. + storage.removeRedirectStatus(app.getAppId()); + const signInHint = { + emailHint: 'user@example.com', + }; + // dispatchOperation is called underneath. Call startWithSignInHint in order + // to pass signInHint. + app.startWithSignInHint(element, {}, signInHint); + // Provider sign in handler should be rendered with email hint. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, + app, + element, + undefined, + signInHint['emailHint']); + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }, + + testDispatchOperation_callback_signInHint_emailProvider() { + // Test CALLBACK operation with signInHint when only email Auth provider is + // enabled. + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.CALLBACK); + // Simulate app not returning from redirect sign-in operation. + storage.removeRedirectStatus(app.getAppId()); + const signInHint = { + emailHint: 'user@example.com', + }; -function testDispatchOperation_resetPassword_continueUrl() { - var element = goog.dom.createElement('div'); - var continueUrl = 'http://www.example.com/path/page?a=1#b=2'; - stub.replace( - firebaseui.auth.util, - 'getCurrentUrl', - function() { - return 'http://example.firebaseapp.com/__/auth/action?mode=' + - 'resetPassword&apiKey=API_KEY&oobCode=ACTION_CODE&continueUrl=' + - encodeURIComponent(continueUrl); - }); - firebaseui.auth.widget.dispatcher.dispatchOperation(app, element); - assertHandlerInvoked( - firebaseui.auth.widget.HandlerName.PASSWORD_RESET, - app, - element, - 'ACTION_CODE'); - // Get callback passed to password reset handler and confirm it redirects to - // continue URL. - /** @suppress {missingRequire} */ - var handler = - firebaseui.auth.widget.handlers_[ - firebaseui.auth.widget.HandlerName.PASSWORD_RESET]; - var continueCallback = handler.getLastCall().getArgument(3); - continueCallback(); - testUtil.assertGoTo(continueUrl); -} + // dispatchOperation is called underneath. Call startWithSignInHint in order + // to pass signInHint. + app.startWithSignInHint( + element, + { + signInOptions: ['password'], + credentialHelper: Config.CredentialHelper.NONE, + }, + signInHint); + // Prefilled email sign in handler should be rendered with email hint. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PREFILLED_EMAIL_SIGN_IN, + app, + element, + signInHint['emailHint']); + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }, + + testDispatchOperation_callback_canSkipNascarScreen() { + // Checks to make sure that when immediateFederatedRedirect is true + // and all the correct options are set, the 'nascar' sign-in screen will + // be skipped. + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.CALLBACK); + // Simulate app not returning from redirect sign-in operation. + storage.removeRedirectStatus(app.getAppId()); + // In order for an immediate redirect to succeed all of the following + // options must be set: + app.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID], + 'signInFlow': Config.SignInFlow.REDIRECT, + }); + dispatcher.dispatchOperation(app, element); + // The federated redirect handler should trigger. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.FEDERATED_REDIRECT, + app, + element); + }, + + testDispatchOperation_callback_canShowNascarScreen() { + // Checks to make sure that even if immediateFederatedRedirect is true + // unless all the correct options are set, the 'nascar' sign-in screen will + // not be skipped. + const element = dom.createElement('div'); + setModeAndUrlParams(Config.WidgetMode.CALLBACK); + // Simulate app not returning from redirect sign-in operation. + storage.removeRedirectStatus(app.getAppId()); + // The immediate redirect should not be triggered (since there is more + // than one federated provider and it is using a popup). + app.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [ + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.FacebookAuthProvider.PROVIDER_ID], + 'signInFlow': Config.SignInFlow.POPUP, + }); + dispatcher.dispatchOperation(app, element); + // The normal provider sign in handler 'nascar' screen should be rendered. + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, + app, + element); + }, + + testDispatchOperation_revokeChangeEmail() { + const element = dom.createElement('div'); + setModeAndUrlParams( + Config.WidgetMode.RECOVER_EMAIL, + {'oobCode': 'ACTION_CODE'}); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.EMAIL_CHANGE_REVOCATION, + app, + element, + 'ACTION_CODE'); + }, + + testDispatchOperation_verifyEmail() { + const element = dom.createElement('div'); + setModeAndUrlParams( + Config.WidgetMode.VERIFY_EMAIL, + {'oobCode': 'ACTION_CODE'}); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION, + app, + element, + 'ACTION_CODE'); + }, + + testDispatchOperation_emailLinkSignIn() { + const element = dom.createElement('div'); + setModeAndUrlParams( + Config.WidgetMode.SIGN_IN, + {'oobCode': 'ACTION_CODE', 'lang': 'en'}); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.EMAIL_LINK_SIGN_IN_CALLBACK, + app, + element, + 'https://www.example.com/?mode=signIn&oobCode=ACTION_CODE&lang=en'); + // Confirm history state replaced. + testUtil.assertReplaceHistoryState( + { + 'state': 'signIn', + 'mode': 'emailLink', + 'operation': 'clear', + }, + // Same document title should be kept. + document.title, + // URL should be cleared from email sign-in related query params. + 'https://www.example.com/?lang=en'); + }, + + testDispatchOperation_emailLinkSignIn_tenantId() { + const element = dom.createElement('div'); + setModeAndUrlParams( + Config.WidgetMode.SIGN_IN, + {'oobCode': 'ACTION_CODE', 'lang': 'en', 'tenantId': 'TENANT_ID'}); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.EMAIL_LINK_SIGN_IN_CALLBACK, + app, + element, + 'https://www.example.com/?mode=signIn&oobCode=ACTION_CODE&lang=en&' + + 'tenantId=TENANT_ID'); + // Confirm history state replaced. + testUtil.assertReplaceHistoryState( + { + 'state': 'signIn', + 'mode': 'emailLink', + 'operation': 'clear', + }, + // Same document title should be kept. + document.title, + // URL should be cleared from email sign-in related query params. + 'https://www.example.com/?lang=en'); + assertNull(app.getTenantId()); assertNull(app.getTenantId()); + }, + + testDispatchOperation_verifyEmail_continueUrl() { + const element = dom.createElement('div'); + const continueUrl = 'http://www.example.com/path/page?a=1#b=2'; + stub.replace( + firebaseui.auth.util, + 'getCurrentUrl', + () => 'http://example.firebaseapp.com/__/auth/action?mode=' + + 'verifyEmail&apiKey=API_KEY&oobCode=ACTION_CODE&continueUrl=' + + encodeURIComponent(continueUrl)); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION, + app, + element, + 'ACTION_CODE'); + // Get callback passed to verify email handler and confirm it redirects to + // continue URL. + const handler = + firebaseui.auth.widget.handlers_[ + firebaseui.auth.widget.HandlerName.EMAIL_VERIFICATION]; + const continueCallback = handler.getLastCall().getArgument(3); + continueCallback(); + testUtil.assertGoTo(continueUrl); + }, + + testDispatchOperation_resetPassword() { + const element = dom.createElement('div'); + setModeAndUrlParams( + Config.WidgetMode.RESET_PASSWORD, + {'oobCode': 'ACTION_CODE'}); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PASSWORD_RESET, + app, + element, + 'ACTION_CODE'); + }, + + testDispatchOperation_resetPassword_continueUrl() { + const element = dom.createElement('div'); + const continueUrl = 'http://www.example.com/path/page?a=1#b=2'; + stub.replace( + firebaseui.auth.util, + 'getCurrentUrl', + () => 'http://example.firebaseapp.com/__/auth/action?mode=' + + 'resetPassword&apiKey=API_KEY&oobCode=ACTION_CODE&continueUrl=' + + encodeURIComponent(continueUrl)); + dispatcher.dispatchOperation(app, element); + assertHandlerInvoked( + firebaseui.auth.widget.HandlerName.PASSWORD_RESET, + app, + element, + 'ACTION_CODE'); + // Get callback passed to password reset handler and confirm it redirects to + // continue URL. + /** @suppress {missingRequire} */ + const handler = + firebaseui.auth.widget.handlers_[ + firebaseui.auth.widget.HandlerName.PASSWORD_RESET]; + const continueCallback = handler.getLastCall().getArgument(3); + continueCallback(); + testUtil.assertGoTo(continueUrl); + }, +}); diff --git a/javascript/widgets/exports_app.js b/javascript/widgets/exports_app.js index 048110b1..46668fd2 100644 --- a/javascript/widgets/exports_app.js +++ b/javascript/widgets/exports_app.js @@ -14,11 +14,42 @@ goog.provide('firebaseui.auth.exports'); -goog.require('firebaseui.auth.AnonymousAuthProvider'); goog.require('firebaseui.auth.AuthUI'); goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.CredentialHelper'); +goog.require('firebaseui.auth.FirebaseUiHandler'); +goog.require('firebaseui.auth.widget.Config'); +goog.require('goog.Promise'); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler', + firebaseui.auth.FirebaseUiHandler); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.selectTenant', + firebaseui.auth.FirebaseUiHandler.prototype.selectTenant); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.getAuth', + firebaseui.auth.FirebaseUiHandler.prototype.getAuth); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.startSignIn', + firebaseui.auth.FirebaseUiHandler.prototype.startSignIn); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.reset', + firebaseui.auth.FirebaseUiHandler.prototype.reset); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.showProgressBar', + firebaseui.auth.FirebaseUiHandler.prototype.showProgressBar); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.hideProgressBar', + firebaseui.auth.FirebaseUiHandler.prototype.hideProgressBar); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.completeSignOut', + firebaseui.auth.FirebaseUiHandler.prototype.completeSignOut); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.handleError', + firebaseui.auth.FirebaseUiHandler.prototype.handleError); +goog.exportSymbol( + 'firebaseui.auth.FirebaseUiHandler.prototype.processUser', + firebaseui.auth.FirebaseUiHandler.prototype.processUser); goog.exportSymbol('firebaseui.auth.AuthUI', firebaseui.auth.AuthUI); goog.exportSymbol( 'firebaseui.auth.AuthUI.getInstance', @@ -50,13 +81,17 @@ goog.exportSymbol( firebaseui.auth.AuthUIError.prototype.toJSON); goog.exportSymbol( 'firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM', - firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM); + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM); goog.exportSymbol( 'firebaseui.auth.CredentialHelper.GOOGLE_YOLO', - firebaseui.auth.CredentialHelper.GOOGLE_YOLO); + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO); goog.exportSymbol( 'firebaseui.auth.CredentialHelper.NONE', - firebaseui.auth.CredentialHelper.NONE); + firebaseui.auth.widget.Config.CredentialHelper.NONE); goog.exportSymbol( 'firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID', - firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID); + firebaseui.auth.widget.Config.ANONYMOUS_PROVIDER_ID); +goog.exportProperty( + goog.Promise.prototype, 'catch', goog.Promise.prototype.thenCatch); +goog.exportProperty( + goog.Promise.prototype, 'finally', goog.Promise.prototype.thenAlways); diff --git a/javascript/widgets/firebaseuihandler.js b/javascript/widgets/firebaseuihandler.js new file mode 100644 index 00000000..4bf2561f --- /dev/null +++ b/javascript/widgets/firebaseuihandler.js @@ -0,0 +1,647 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview The authentication handler that implements the interface used + * for IAP integration. + */ + +goog.module('firebaseui.auth.FirebaseUiHandler'); +goog.module.declareLegacyNamespace(); + +const AuthUI = goog.require('firebaseui.auth.AuthUI'); +const Base = goog.require('firebaseui.auth.ui.page.Base'); +const GoogPromise = goog.require('goog.Promise'); +const ProviderMatchByEmail = + goog.require('firebaseui.auth.ui.page.ProviderMatchByEmail'); +const RecoverableError = + goog.require('firebaseui.auth.ui.page.RecoverableError'); +const SelectTenant = goog.require('firebaseui.auth.ui.page.SelectTenant'); +const SignOut = goog.require('firebaseui.auth.ui.page.SignOut'); +const Spinner = goog.require('firebaseui.auth.ui.page.Spinner'); +const UiHandlerConfig = goog.require('firebaseui.auth.widget.UiHandlerConfig'); +const dom = goog.require('goog.dom'); +const element = goog.require('firebaseui.auth.ui.element'); +const strings = goog.require('firebaseui.auth.soy2.strings'); +const util = goog.require('firebaseui.auth.util'); + +/** + * The interface that represents the Authentication Handler. + * @interface + */ +class AuthenticationHandler { + /** + * Selects a tenant from the given tenant IDs. Returns the tenant ID of the + * selected tenant and the underlying matching providers. + * @param {!ProjectConfig} projectConfig The config object used to identify + * the project. + * @param {!Array} tenantIds The IDs of the tenants to select from. + * @return {!GoogPromise} The selected tenant and + * providers enabled for the tenant. + */ + selectTenant(projectConfig, tenantIds) {} + + /** + * Returns the Auth instance for the corresponding project/tenant pair. + * @param {string} apiKey The API key. + * @param {?string} tenantId The tenant ID, null for top-level project flow. + * @return {!firebase.auth.Auth} The Auth instance for the given API key and + * tenant ID. + */ + getAuth(apiKey, tenantId) {} + + /** + * Starts sign in with the corresponding Auth instance. The sign in options + * used are based on auth.tenantId. + * @param {!firebase.auth.Auth} auth The Auth instance. + * @param {!SelectedTenantInfo=} tenantInfo The optional selected tenant and + * the matching providers. + * @return {!GoogPromise} + */ + startSignIn(auth, tenantInfo) {} + + /** + * Renders the UI after user is signed out. + * @return {!GoogPromise} + */ + completeSignOut() {} + + /** + * Renders progress bar in the container if hidden. + */ + showProgressBar() {} + + /** + * Hides progress bar if visible. + */ + hideProgressBar() {} + + /** + * Displays the error message to the end users and provides tha ability to + * retry for recoverable error. + * @param {!Error|!CIAPError|!CIAPRetryError} error The error from CIAP. + */ + handleError(error) {} + + /** + * Handles additional processing on the user if callback is provided by the + * developer. + * @param {!firebase.User} user The signed in user to be processed. + * @return {!GoogPromise} A promise that resolves when the + * processing is finished. + */ + processUser(user) {} +} + + +/** + * The CIAP Error interface. If the error is recoverable, it will have a retry + * callback on the object. + * @interface + */ +class CIAPError { + constructor() { + /** + * The short error code. + * @type {string} + */ + this.code; + /** + * The human-readable error message. + * @type {string} + */ + this.message; + /** + * The HTTP error code number. + * @type {number|undefined} + */ + this.httpErrorCode; + /** + * The underlying reason error if available. + * @type {!Error|undefined} + */ + this.reason; + } + + /** + * Returns a JSON-serializable representation of the error. + * @return {!Object} The plain object representation of the error. + */ + toJSON() {} +} + + +/** + * The CIAP recoverable error interface. + * @extends {CIAPError} + * @interface + */ +class CIAPRetryError { + /** + * The retry callback to recover from error. + * @return {!Promise} A promise that resolves on retry completion. + */ + retry() {} +} + + +/** + * Initializes an CIAP AuthenticationHandler with the Auth configuration and + * UI configurations provided. + * @implements {AuthenticationHandler} + */ +class FirebaseUiHandler { + /** + * @param {string|!Element} element The container element or the query + * selector. + * @param {!Object} configs The + * configuration of the handler keyed by API key. + */ + constructor(element, configs) { + /** @private {!Element} The container element. */ + this.container_ = util.getElement(element); + /** + * @private {!Object} The configuration of the + * UI handler keyed by API keys. + */ + this.configs_ = {}; + Object.keys(configs).forEach((apiKey) => { + this.configs_[apiKey] = new UiHandlerConfig(configs[apiKey]); + }); + /** @private {?AuthUI} The FirebaseUI instance if available. */ + this.ui_ = null; + /** + * @private {?firebase.auth.Auth} The Auth instance used to sign in with. + * This is used to keep track of the API key being used, which is + * needed for multi-project support. + */ + this.signedInAuth_ = null; + /** @private {?Spinner} The progress bar component. */ + this.progressBar_ = null; + /** @private {?number} The ID of show progress bar timeout. */ + this.showProcessingTimeout_ = null; + /** @private {?Base} The current UI component. */ + this.currentComponent_ = null; + /** @private {?string} The handler's language code. */ + this.languageCode_ = null; + Object.defineProperty( + /** @type {!Object} */ (this), + 'languageCode', + { + /** + * @return {?string} The current language code. + * @this {!Object} + */ + get() { + return this.languageCode_; + }, + /** + * @param {?string} value The new language code. + * @this {!Object} + */ + set(value) { + this.languageCode_ = value || null; + }, + enumerable: false, + }); + } + + /** + * Selects a tenant from the given tenant IDs. Returns the tenant ID of the + * selected tenant and the underlying matching providers. + * @param {!ProjectConfig} projectConfig The config object used to identify + * the project. + * @param {!Array} tenantIds The IDs of the tenants to select from. + * @return {!GoogPromise} The selected tenant and + * providers enabled for the tenant. + */ + selectTenant(projectConfig, tenantIds) { + this.disposeCurrentComponent_(); + const apiKey = projectConfig['apiKey']; + return new GoogPromise((resolve, reject) => { + if (!this.configs_.hasOwnProperty(apiKey)) { + const error = + new Error('Invalid project configuration: API key is invalid!'); + // Add error code for localization. + error['code'] = 'invalid-configuration'; + this.handleError(error); + reject(error); + return; + } + const selectTenantUiHidden = + this.configs_[apiKey].getSelectTenantUiHiddenCallback(); + // Option first flow. + if (this.configs_[apiKey].getDisplayMode() === + UiHandlerConfig.DisplayMode.OPTION_FIRST) { + // Get the button configurations based on the given tenant IDs. + const tenantButtonConfigs = []; + tenantIds.forEach((tenantId) => { + const buttonConfig = + this.configs_[apiKey].getSelectionButtonConfigForTenant( + tenantId || UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY); + if (buttonConfig) { + tenantButtonConfigs.push(buttonConfig); + } + }); + // Resolver to return the SelectedTenantInfo based on the tenantId. + const resolveWithTenantInfo = (tenantId) => { + const selectedTenantInfo = { + 'tenantId': tenantId, + 'providerIds': + this.configs_[apiKey].getProvidersForTenant( + tenantId || + UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY), + }; + resolve(selectedTenantInfo); + }; + // If only one tenant is available, do not show the select tenant page. + // Resolve with the only tenant and return immediately. + if (tenantButtonConfigs.length === 1) { + const tenantId = tenantButtonConfigs[0].tenantId; + resolveWithTenantInfo(tenantId); + return; + } else { + const onTenantClick = (tenantId) => { + this.disposeCurrentComponent_(); + // Trigger the selectTenantUiHidden callback. + if (selectTenantUiHidden) { + selectTenantUiHidden(); + } + resolveWithTenantInfo(tenantId); + }; + this.currentComponent_ = + new SelectTenant(onTenantClick, tenantButtonConfigs, + this.configs_[apiKey].getTosUrl(), + this.configs_[apiKey].getPrivacyPolicyUrl()); + } + } else { + // Identifier first flow. + const onEmailEnter = () => { + const email = this.currentComponent_.checkAndGetEmail(); + if (!email) { + return; + } + for (let i = 0; i < tenantIds.length; i++) { + const providers = + this.configs_[apiKey].getProvidersForTenant( + tenantIds[i] || + UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY, + email); + // Resolve with the first matching tenant with available providers. + if (providers.length !== 0) { + const selectedTenantInfo = { + 'tenantId': tenantIds[i], + 'providerIds': providers, + 'email': email, + }; + this.disposeCurrentComponent_(); + // Trigger the selectTenantUiHidden callback. + if (selectTenantUiHidden) { + selectTenantUiHidden(); + } + resolve(selectedTenantInfo); + return; + } + } + // If no matching tenant found, show error message in info bar. + this.currentComponent_.showInfoBar( + getLocalizedErrorMessage('no-matching-tenant-for-email')); + }; + this.currentComponent_ = new ProviderMatchByEmail( + onEmailEnter, + this.configs_[apiKey].getTosUrl(), + this.configs_[apiKey].getPrivacyPolicyUrl()); + } + this.currentComponent_.render(this.container_); + // Trigger the selectTenantUiShown callback. + const selectTenantUiShown = + this.configs_[apiKey].getSelectTenantUiShownCallback(); + if (selectTenantUiShown) { + selectTenantUiShown(); + } + }); + } + + /** + * Returns the Auth instance for the corresponding project/tenant pair. + * @param {string} apiKey The API key. + * @param {?string} tenantId The tenant ID, null for top-level project flow. + * @return {!firebase.auth.Auth} The Auth instance for the given API key and + * tenant ID. + * @override + */ + getAuth(apiKey, tenantId) { + if (!this.configs_.hasOwnProperty(apiKey)) { + throw new Error('Invalid project configuration: API key is invalid!'); + } + // The name of the firebase app. For tenant flow, use tenant ID, for + // top-level project flow, use the default name "[DEFAULT]". + const appName = tenantId || undefined; + // Validates that the UI configuration is available. If tenant ID is null, + // the top-level project UI configuration key TOP_LEVEL_CONFIG_KEY should + // be available. + this.configs_[apiKey].validateTenantId( + tenantId || UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY); + try { + this.signedInAuth_ = firebase.app(appName).auth(); + } catch (e) { + const options = { + 'apiKey': apiKey, + 'authDomain': this.configs_[apiKey].getAuthDomain(), + }; + const app = firebase.initializeApp(options, appName); + app.auth()['tenantId'] = tenantId; + this.signedInAuth_ = app.auth(); + } + return this.signedInAuth_; + } + + /** + * Starts sign in with the corresponding Auth instance. The sign in options + * used are based on auth.tenantId. + * @param {!firebase.auth.Auth} auth The Auth instance. + * @param {!SelectedTenantInfo=} tenantInfo The optional selected tenant and + * the matching providers. + * @return {!GoogPromise} + * @override + */ + startSignIn(auth, tenantInfo = undefined) { + return new GoogPromise((resolve, reject) => { + const apiKey = auth['app']['options']['apiKey']; + if (!this.configs_.hasOwnProperty(apiKey)) { + reject( + new Error('Invalid project configuration: API key is invalid!')); + } + const signInConfig = + this.configs_[apiKey].getSignInConfigForTenant( + auth['tenantId'] || + UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY, + tenantInfo && tenantInfo['providerIds']); + this.disposeCurrentComponent_(); + // Passes the sign-in related callbacks to FirebaseUI. + const signInCallbacks = {}; + signInCallbacks['signInSuccessWithAuthResult'] = (userCredential) => { + resolve(userCredential); + return false; + }; + const signInUiShownCallback = + this.configs_[apiKey].getSignInUiShownCallback(); + let uiShown = false; + signInCallbacks['uiChanged'] = (fromPageId, toPageId) => { + // Processing redirect result. + if (fromPageId === null && toPageId === 'callback') { + // Hide callback page if available. + const callbackElement = dom.getElementByClass( + 'firebaseui-id-page-callback', this.container_); + if (callbackElement) { + element.hide(callbackElement); + } + // Show spinner. This will trigger null -> spinner uiChanged. + this.progressBar_ = new Spinner(); + this.progressBar_.render(this.container_); + } else if (!uiShown && + !(fromPageId === null && toPageId === 'spinner') && + // Do not trigger callback for immediate federated redirect + // to IdP page. + (toPageId !== 'blank')) { + // Remove spinner if still showing. + if (this.progressBar_) { + this.progressBar_.dispose(); + this.progressBar_ = null; + } + // Trigger the signInUiShown callback. This should be triggered once. + uiShown = true; + if (signInUiShownCallback) { + signInUiShownCallback(auth['tenantId']); + } + } + }; + signInConfig['callbacks'] = signInCallbacks; + // Do not support `credentialHelper` for sign-in flow. + signInConfig['credentialHelper'] = 'none'; + let signInHint; + if (tenantInfo && tenantInfo['email']) { + signInHint = { + 'emailHint': tenantInfo['email'], + }; + } + const startAuthUi = (signInConfig, signInHint) => { + this.ui_ = new AuthUI(auth); + this.ui_.startWithSignInHint(this.container_, signInConfig, signInHint); + }; + // If the AuthUI instance is not null, delete it before re-initialization. + if (this.ui_) { + this.ui_.delete().then(() => { + startAuthUi(signInConfig, signInHint); + }); + } else { + startAuthUi(signInConfig, signInHint); + } + }); + } + + /** + * Resets the FirebaseUI handler and deletes the underlying FirebaseUI + * instance. Calling startSignIn after reset should rerender the UI + * successfully. + * @return {!GoogPromise} The promise that resolves when the instance is + * successfully reset. + */ + reset() { + return GoogPromise.resolve().then(() => { + if (this.ui_) { + this.ui_.delete(); + } + }).then(() => { + this.ui_ = null; + this.disposeCurrentComponent_(); + }); + } + + /** + * Returns the current FirebaseUI instance used for sign-in if available. + * @return {?AuthUI} + */ + getCurrentAuthUI() { + return this.ui_; + } + + /** + * Renders progress bar in the container if hidden. + * @override + */ + showProgressBar() { + if (this.progressBar_ || this.showProcessingTimeout_) { + return; + } + this.showProcessingTimeout_ = window.setTimeout(() => { + this.disposeCurrentComponent_(); + this.progressBar_ = new Spinner(); + this.currentComponent_ = this.progressBar_; + this.progressBar_.render(this.container_); + this.showProcessingTimeout_ = null; + }, SHOW_PROCESSING_DELAY); + } + + /** + * Hides progress bar if visible. + * @override + */ + hideProgressBar() { + window.clearTimeout(this.showProcessingTimeout_); + this.showProcessingTimeout_ = null; + if (this.progressBar_) { + this.progressBar_.dispose(); + this.progressBar_ = null; + } + } + + /** + * Renders the UI after user is signed out. + * @return {!GoogPromise} + * @override + */ + completeSignOut() { + this.disposeCurrentComponent_(); + this.currentComponent_ = new SignOut(); + this.currentComponent_.render(this.container_); + return GoogPromise.resolve(); + } + + /** + * Removes the rendered UI component from display. + * @private + */ + disposeCurrentComponent_() { + if (this.ui_) { + this.ui_.reset(); + } + this.hideProgressBar(); + if (this.currentComponent_) { + this.currentComponent_.dispose(); + } + } + + /** + * Displays the error message to the end users and provides the ability to + * retry for recoverable error. + * @param {!Error|!CIAPError|!CIAPRetryError} error The error from CIAP. + * @override @suppress {checkTypes} Suppress [] access error for now, will + * need to define CIAPError extern so that dot access will not be renamed. + */ + handleError(error) { + const message = getLocalizedErrorMessage(error['code']) || error['message']; + this.disposeCurrentComponent_(); + let onRetryClick; + if (error['retry'] && goog.isFunction(error['retry'])) { + onRetryClick = () => { + this.reset(); + error['retry'](); + }; + } + this.currentComponent_ = + new RecoverableError(message, onRetryClick); + this.currentComponent_.render(this.container_); + } + + /** + * Handles additional processing on the user if callback is provided by the + * developer. + * @param {!firebase.User} user The signed in user to be processed. + * @return {!GoogPromise} A promise that resolves when the + * processing is finished. + * @override + */ + processUser(user) { + return GoogPromise.resolve().then(() => { + const apiKey = this.signedInAuth_ && + this.signedInAuth_['app']['options']['apiKey']; + if (!this.configs_.hasOwnProperty(apiKey)) { + throw new Error('Invalid project configuration: API key is invalid!'); + } + this.configs_[apiKey].validateTenantId( + user['tenantId'] || UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY); + if (!this.signedInAuth_['currentUser'] || + (this.signedInAuth_['currentUser']['uid'] !== user['uid'])) { + throw new Error( + 'The user being processed does not match the signed in user!'); + } + const beforeSignInSuccessCallback = + this.configs_[apiKey].getBeforeSignInSuccessCallback(); + return beforeSignInSuccessCallback ? + beforeSignInSuccessCallback(user) : user; + }).then((processedUser) => { + // Checks that the user returned in callback has to be same as the + // original one. + if (processedUser['uid'] !== user['uid']) { + throw new Error('User with mismatching UID returned.'); + } + return processedUser; + }); + } +} + +/** + * Returns the user-facing localized error message from the given error code. + * Returns empty string if no error message is available for the given code. + * @param {string} code The error code. + * @return {string} The localized user-facing error message. + */ +function getLocalizedErrorMessage(code) { + return strings.errorCIAP({code: code}).toString(); +} + +/** + * The project level configuration object. + * authDomain: The Auth domain. + * tenants: The tenant level configuations keyed by tenant ID or '_' for + * top-level project config. + * @typedef {{ + * authDomain: string, + * tenants: !Object + * }} + */ +let CIAPHandlerConfig; + +/** + * The object used to identify the project. + * projectId: The project ID. + * apiKey: The API key. + * @typedef {{ + * projectId: string, + * apiKey: string, + * }} + */ +let ProjectConfig; + +/** + * The matching tenant and providers enabled for the tenant. + * email: The email being used to select the tenant. + * tenantId: The ID of the tenant being selected. + * providerIds: The providers available for the tenant. + * @typedef {{ + * email: (string|undefined), + * tenantId: ?string, + * providerIds: !Array, + * }} + */ +let SelectedTenantInfo; + +/** + * @const {number} The delay, in milliseconds, before the progress bar + * is shown. + */ +const SHOW_PROCESSING_DELAY = 500; + +exports = FirebaseUiHandler; diff --git a/javascript/widgets/firebaseuihandler_test.js b/javascript/widgets/firebaseuihandler_test.js new file mode 100644 index 00000000..9b9e9d8a --- /dev/null +++ b/javascript/widgets/firebaseuihandler_test.js @@ -0,0 +1,2468 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @fileoverview Tests for firebaseuihandler.js */ + +goog.module('firebaseui.auth.FirebaseUiHandlerTest'); +goog.setTestOnly(); + +const AuthUI = goog.require('firebaseui.auth.AuthUI'); +const FakeAppClient = goog.require('firebaseui.auth.testing.FakeAppClient'); +const FirebaseUiHandler = goog.require('firebaseui.auth.FirebaseUiHandler'); +const GoogPromise = goog.require('goog.Promise'); +const KeyCodes = goog.require('goog.events.KeyCodes'); +const MockClock = goog.require('goog.testing.MockClock'); +const PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); +const RedirectStatus = goog.require('firebaseui.auth.RedirectStatus'); +const TagName = goog.require('goog.dom.TagName'); +const dataset = goog.require('goog.dom.dataset'); +const dom = goog.require('goog.dom'); +const forms = goog.require('goog.dom.forms'); +const idp = goog.require('firebaseui.auth.idp'); +const recordFunction = goog.require('goog.testing.recordFunction'); +const storage = goog.require('firebaseui.auth.storage'); +const strings = goog.require('firebaseui.auth.soy2.strings'); +const testSuite = goog.require('goog.testing.testSuite'); +const testingEvents = goog.require('goog.testing.events'); +const userAgent = goog.require('goog.userAgent'); + +const testStubs = new PropertyReplacer(); +const mockClock = new MockClock(); +let container; +let configs; +let projectConfig; +let handler; +let auth; +let auth1; +let auth2; +let selectTenantUiShownCallback; +let selectTenantUiHiddenCallback; +let signInUiShownCallback; + +/** + * Asserts the progress bar is visible. + * @param {!Element} container The html element container of the widget. + */ +function assertProgressBarVisible(container) { + const elements = + dom.getElementsByClass('firebaseui-id-page-spinner', container); + assertEquals(1, elements.length); +} + +/** + * Asserts the callback page is in the dom but hidden. + * @param {!Element} container The html element container of the widget. + */ +function assertCallbackPageDomHidden(container) { + const element = dom.getElementByClass( + 'firebaseui-id-page-callback', container); + assertNotNull(element); + assertTrue(dom.classlist.contains(element, 'firebaseui-hidden')); +} + +/** + * Asserts the callback page is not in the dom. + * @param {!Element} container The html element container of the widget. + */ +function assertCallbackPageHidden(container) { + assertNull(dom.getElementByClass('firebaseui-id-page-callback', container)); +} + +/** + * Asserts the blank page is visible. + * @param {!Element} container The html element container of the widget. + */ +function assertBlankPageVisible(container) { + assertNotNull(dom.getElementByClass('firebaseui-id-page-blank', container)); +} + + +/** + * Asserts the busy indicator is after a short delay. + * @param {!Element} container The html element container of the widget. + */ +function delayForBusyIndicatorAndAssertIndicatorShown(container) { + mockClock.tick(500); + const element = + dom.getElementByClass('firebaseui-id-busy-indicator', container); + assertNotNull(element); +} + +/** + * Asserts the provider sign in page is displayed. + * @param {!Element} container The html element container of the widget. + */ +function assertProviderSignInPageVisible(container) { + const element = dom.getElementByClass( + 'firebaseui-id-page-provider-sign-in', container); + assertNotNull(element); +} + +/** + * Asserts the progress bar is hidden. + * @param {!Element} container The html element container of the widget. + */ +function assertProgressBarHidden(container) { + const elements = + dom.getElementsByClass('firebaseui-id-page-spinner', container); + assertEquals(0, elements.length); +} + +/** + * Asserts the select tenant page is displayed. + * @param {!Element} container The html element container of the widget. + */ +function assertSelectTenantPageVisible(container) { + const element = + dom.getElementByClass('firebaseui-id-page-select-tenant', container); + assertNotNull(element); +} + +/** + * Asserts the select tenant page is hidden. + * @param {!Element} container The html element container of the widget. + */ +function assertSelectTenantPageHidden(container) { + const element = + dom.getElementByClass('firebaseui-id-page-select-tenant', container); + assertNull(element); +} + +/** + * Asserts the provider match by email page is displayed. + * @param {!Element} container The html element container of the widget. + */ +function assertProviderMatchByEmailPageVisible(container) { + const element = dom.getElementByClass( + 'firebaseui-id-page-provider-match-by-email', container); + assertNotNull(element); +} + +/** + * Asserts the provider match by email page is hidden. + * @param {!Element} container The html element container of the widget. + */ +function assertProviderMatchByEmailPageHidden(container) { + const element = dom.getElementByClass( + 'firebaseui-id-page-provider-match-by-email', container); + assertNull(element); +} + +/** + * Asserts the complete sign out page is displayed. + * @param {!Element} container The html element container of the widget. + */ +function assertCompleteSignOutPageVisible(container) { + const element = + dom.getElementByClass('firebaseui-id-page-sign-out', container); + assertNotNull(element); +} + +/** + * Asserts the complete sign out page is hidden. + * @param {!Element} container The html element container of the widget. + */ +function assertCompleteSignOutPageHidden(container) { + const element = + dom.getElementByClass('firebaseui-id-page-sign-out', container); + assertNull(element); +} + +/** + * Asserts the error page is visible and it contains the correct error message. + * @param {!Element} container The html element container of the widget. + * @param {string} message The error message. + * @param {boolean} recoverable Whether the error is recoverable. + */ +function assertErrorPageVisible(container, message, recoverable) { + const element = dom.getElementByClass( + 'firebaseui-id-page-recoverable-error', container); + assertNotNull(element); + const msgContainer = dom.getElementByClass('firebaseui-text', container); + assertContains(message, dom.getTextContent(msgContainer)); + const retryButton = getRetryButton(container); + if (recoverable) { + assertNotNull(retryButton); + } else { + assertNull(retryButton); + } +} + +/** + * Asserts the error page is hidden. + * @param {!Element} container The html element container of the widget. + */ +function assertErrorPageHidden(container) { + const element = dom.getElementByClass( + 'firebaseui-id-page-recoverable-error', container); + assertNull(element); +} + +/** + * Asserts the info bar is displayed with the correct message. + * @param {string} message The message displayed in info bar. + * @param {!Element} container The html element container of the widget. + */ +function assertInfoBarMessage(message, container) { + const element = dom.getElementByClass('firebaseui-id-info-bar', container); + assertContains(message, dom.getTextContent(element)); +} + +/** + * Asserts the email error is displayed with the correct message. + * @param {string} message The email error message. + * @param {!Element} container The html element container of the widget. + */ +function assertEmailErrorMessage(message, container) { + const element = dom.getElementByClass('firebaseui-id-email-error', container); + assertContains(message, dom.getTextContent(element)); +} + +/** + * Returns the retry button element. + * @param {!Element} container The html element container of the widget. + * @return {?Element} + */ +function getRetryButton(container) { + return dom.getElementByClass('firebaseui-id-submit', container); +} + +/** + * Returns mock CIAP error. + * @param {string} code The error code. + * @param {string} message The error message. + * @param {function()=} retry The optional retry callback. + * @return {!Error} + */ +function mockCIAPError(code, message, retry = undefined) { + const error = new Error(message); + error['code'] = code; + error['retry'] = retry; + return error; +} + +testSuite({ + setUp() { + mockClock.install(); + goog.global.firebase = {}; + const firebase = goog.global.firebase; + firebase.instances_ = {}; + firebase.initializeApp = (options, name) => { + // Throw an error if a FirebaseApp already exists for the specified name. + const key = name || '[DEFAULT]'; + if (firebase.instances_[key]) { + throw new Error(`An app instance already exists for ${key}`); + } else { + firebase.instances_[key] = + new FakeAppClient(options, name); + } + const firebaseApp = firebase.instances_[key]; + // Make sure Auth instance is installed. + firebaseApp.auth().install(); + return firebaseApp; + }; + firebase.app = (name) => { + const key = name || '[DEFAULT]'; + if (typeof firebase.instances_[key] === 'undefined') { + throw new Error('app/no-app'); + } + return firebase.instances_[key]; + }; + // On FirebaseApp deletion, confirm instance not already deleted and then + // remove it from firebase.instances_. + testStubs.replace( + FakeAppClient.prototype, + 'delete', + function() { + // Already deleted. + if (!firebase.instances_[this['name']]) { + throw new Error(`Instance ${key} already deleted!`); + } + delete firebase.instances_[this['name']]; + return GoogPromise.resolve(); + }); + // Define firebase.auth.Auth.Persistence enum. + firebase.auth = firebase.auth || {}; + firebase.auth.Auth = firebase.auth.Auth || {}; + firebase.auth.Auth.Persistence = firebase.auth.Auth.Persistence || { + LOCAL: 'local', + NONE: 'none', + SESSION: 'session', + }; + // Build mock auth providers. + for (let key in idp.AuthProviders) { + firebase['auth'][idp.AuthProviders[key]] = function() { + this.scopes = []; + this.customParameters = {}; + }; + firebase['auth'][idp.AuthProviders[key]].PROVIDER_ID = key; + if (key != 'twitter.com' && key != 'password') { + firebase['auth'][idp.AuthProviders[key]] + .prototype.addScope = function(scope) { + this.scopes.push(scope); + return this; + }; + } + if (key != 'password') { + // Record setCustomParameters for all OAuth providers. + firebase['auth'][idp.AuthProviders[key]] + .prototype.setCustomParameters = function(customParameters) { + this.customParameters = customParameters; + return this; + }; + } + if (key == 'password') { + // Mock credential initializer for Email/password credentials. + firebase['auth'][idp.AuthProviders[key]]['credential'] = + (email, password) => ({ + 'email': email, + 'password': password, + 'providerId': 'password', + }); + firebase.auth.EmailAuthProvider.credentialWithLink = + (email, link) => ({ + email: email, + link: link, + providerId: 'password', + signInMethod: 'emailLink', + }); + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD = 'emailLink'; + firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD = + 'password'; + } else if (key == 'facebook.com') { + // Mock credential initializer for Facebook credentials. + firebase['auth'][idp.AuthProviders[key]]['credential'] = + (accessToken) => ({ + 'accessToken': accessToken, + 'providerId': 'facebook.com', + 'signInMethod': 'facebook.com', + 'toJSON': function() { + return { + 'accessToken': accessToken, + 'providerId': 'facebook.com', + 'signInMethod': 'facebook.com', + }; + }, + }); + } + } + + selectTenantUiShownCallback = recordFunction(); + selectTenantUiHiddenCallback = recordFunction(); + signInUiShownCallback = recordFunction(); + container = dom.createDom(TagName.DIV, {'id': 'element'}); + configs = { + 'API_KEY': { + 'authDomain': 'subdomain.firebaseapp.com', + 'displayMode': 'optionsFirst', + 'callbacks': { + 'selectTenantUiShown': selectTenantUiShownCallback, + 'selectTenantUiHidden': selectTenantUiHiddenCallback, + 'signInUiShown': signInUiShownCallback, + }, + 'tenants': { + // The top-level project UI configuration. + '_': { + 'displayName': 'ACME', + 'buttonColor': '#FFB6C1', + 'iconUrl': '', + 'signInOptions': [ + { + 'provider': 'password', + 'hd': 'acme.com', + }, + { + 'provider': 'saml.provider', + 'providerName': 'SAML Provider', + 'buttonColor': '#2F2F2F', + 'iconUrl': '', + 'hd': 'acme.com', + }], + 'tosUrl': 'http://localhost/tos', + 'privacyPolicyUrl': 'http://localhost/privacy_policy', + }, + 'tenant1': { + 'displayName': 'Contractor A', + 'buttonColor': '#ADF7B2', + 'iconUrl': '', + 'signInOptions': [ + { + 'provider': 'google.com', + 'hd': 'acme.com', + }, + { + 'provider': 'facebook.com', + 'hd': 'acme.com', + }, + { + 'provider': 'password', + 'hd': 'acme.com', + }, + { + 'provider': 'saml.provider', + 'providerName': 'SAML Provider', + 'buttonColor': '#2F2F2F', + 'iconUrl': '', + 'hd': 'acme-supplier.com', + }], + 'tosUrl': 'http://localhost/tos', + 'privacyPolicyUrl': 'http://localhost/privacy_policy', + }, + 'tenant2': { + 'displayName': 'Contractor B', + 'buttonColor': '#EAC9A1', + 'iconUrl': '', + 'signInOptions': ['google.com', 'facebook.com', 'password', + 'github.com', + { + 'provider': 'saml.provider', + 'providerName': 'SAML Provider', + 'buttonColor': '#2F2F2F', + 'iconUrl': '', + }], + 'tosUrl': 'http://localhost/tos', + 'privacyPolicyUrl': 'http://localhost/privacy_policy', + }, + }, + }, + 'API_KEY2': { + 'authDomain': 'subdomain2.firebaseapp.com', + 'tenants': { + 'tenant3': { + 'signInOptions': ['google.com', 'facebook.com', 'password'], + 'tosUrl': 'http://localhost/tos', + 'privacyPolicyUrl': 'http://localhost/privacy_policy', + }, + }, + }, + }; + projectConfig = { + apiKey: 'API_KEY', + projectId: 'PROJECT_ID', + }; + }, + + tearDown() { + if (handler) { + handler.reset(); + // Wait for reset promise to be resolved. + mockClock.tick(); + } + if (auth) { + auth.uninstall(); + } + if (auth1) { + auth1.uninstall(); + } + if (auth2) { + auth2.uninstall(); + } + dom.removeNode(container); + mockClock.reset(); + mockClock.uninstall(); + testStubs.reset(); + }, + + testSelectTenant_optionFirst() { + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant1', 'tenant2']); + + // The select tenant page should be shown. + assertSelectTenantPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + const buttons = dom.getElementsByClass( + 'firebaseui-id-tenant-selection-button', container); + // Two tenants should be available to be selected from. + const expectedTenants = ['tenant1', 'tenant2']; + assertEquals(expectedTenants.length, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(expectedTenants[i], + dataset.get(buttons[i], 'tenantId')); + } + + // Click the tenant1's button. + testingEvents.fireClickSequence(buttons[0]); + return selectPromise.then((selectedTenantInfo) => { + // The select tenant page should be hidden. + assertSelectTenantPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: + ['google.com', 'facebook.com', 'password', 'saml.provider'], + }, selectedTenantInfo); + + // Test startSignIn is called with the returned selectedTenantInfo after + // selectTenant is called. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1, selectedTenantInfo); + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals( + 'tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + // The provider sign-in page is shown with matching providers. + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + assertEquals(selectedTenantInfo.providerIds.length, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(selectedTenantInfo.providerIds[i], + dataset.get(buttons[i], 'providerId')); + } + }); + }, + + testSelectTenant_optionFirst_skipSelection() { + // Test when there is only one tenant available. + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant(projectConfig, ['tenant1']); + + // The select tenant page should be skipped. + assertSelectTenantPageHidden(container); + // selectTenantUiShownCallback should not be triggered. + assertEquals(0, selectTenantUiShownCallback.getCallCount()); + + return selectPromise.then((selectedTenantInfo) => { + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: + ['google.com', 'facebook.com', 'password', 'saml.provider'], + }, selectedTenantInfo); + }); + }, + + testSelectTenant_optionFirst_topLevelProject() { + // Test selectTenant for top-level project flow. + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant(projectConfig, [null]); + + // The select tenant page should be skipped. + assertSelectTenantPageHidden(container); + // selectTenantUiShownCallback should not be triggered. + assertEquals(0, selectTenantUiShownCallback.getCallCount()); + + return selectPromise.then((selectedTenantInfo) => { + // The select tenant page should be hidden. + assertSelectTenantPageHidden(container); + // selectTenantUiShownCallback should not be triggered. + assertEquals(0, selectTenantUiShownCallback.getCallCount()); + assertObjectEquals({ + tenantId: null, + providerIds: ['password', 'saml.provider'], + }, selectedTenantInfo); + }); + }, + + testSelectTenant_optionFirst_tenantNotConfigured() { + // Test when one of the tenants being passed is not configured in + // FirebaseUiHandler. The tenant button should not be shown. + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + // invalid_tenant is not configured. + ['tenant1', 'tenant2', 'invalid_tenant']); + + assertSelectTenantPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + const buttons = dom.getElementsByClass( + 'firebaseui-id-tenant-selection-button', container); + // Only two tenants should be available to be selected from. + const expectedTenants = ['tenant1', 'tenant2']; + assertEquals(expectedTenants.length, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(expectedTenants[i], + dataset.get(buttons[i], 'tenantId')); + } + + testingEvents.fireClickSequence(buttons[1]); + return selectPromise.then((selectedTenantInfo) => { + assertSelectTenantPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant2', + providerIds: + ['google.com', 'facebook.com', 'password', 'github.com', + 'saml.provider'], + }, selectedTenantInfo); + }); + }, + + testSelectTenant_optionFirst_invalidApiKey() { + // Test when invalid API key is passed in projectConfig. + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + { + apiKey: 'INVALID_API_KEY', + projectId: 'PROJECT_ID', + }, + ['tenant1', 'tenant2']); + assertSelectTenantPageHidden(container); + // selectTenantUiShownCallback should not be triggered. + assertEquals(0, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + return selectPromise.then(fail, (error) => { + let errorMessage = + strings.errorCIAP({code: 'invalid-configuration'}).toString(); + assertErrorPageVisible(container, errorMessage, false); + }); + }, + + testSelectTenant_identifierFirst() { + // Test selectTenant for identifier first flow. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant1', 'tenant2']); + + // The provider match for email page should be shown. + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter the email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@acme.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: ['google.com', 'facebook.com', 'password'], + email: 'user@acme.com', + }, selectedTenantInfo); + + // Test startSignIn is called with the returned selectedTenantInfo after + // selectTenant is called. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1, selectedTenantInfo); + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals( + 'tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + // The provider sign-in page is shown with matching providers. + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + assertEquals(selectedTenantInfo.providerIds.length, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(selectedTenantInfo.providerIds[i], + dataset.get(buttons[i], 'providerId')); + } + }); + }, + + testSelectTenant_identifierFirst_multipleMatchingTenants() { + // Test selectTenant for identifier first flow when user enters an email + // that matches with multiple tenants. The first matching tenant should be + // returned with the providers. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant1', null]); + + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter an email that matches with both tenant1 and top-level project + // and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@acme.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + // The first matching tenant should be returned. + tenantId: 'tenant1', + providerIds: ['google.com', 'facebook.com', 'password'], + email: 'user@acme.com', + }, selectedTenantInfo); + }); + }, + + testSelectTenant_identifierFirst_noMatchingTenant() { + // Test selectTenant for identifier first flow when user enters an email + // that does not match with any providers. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant1']); + + // The provider match for email page should be shown. + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter an email with no matching providers and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@nomatching.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + // The error should be displayed in info bar. + let errorMessage = + strings.errorCIAP({code: 'no-matching-tenant-for-email'}).toString(); + assertInfoBarMessage(errorMessage, container); + // The provider match for email page should still be shown. + assertProviderMatchByEmailPageVisible(container); + + forms.setValue(emailInput, 'user@acme-supplier.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: ['saml.provider'], + email: 'user@acme-supplier.com', + }, selectedTenantInfo); + }); + }, + + testSelectTenant_identifierFirst_hdNotConfigured() { + // Test selectTenant identifier first flow for providers that do not have + // hd configured. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant2']); + + // The provider match for email page should be shown. + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter an arbitrary email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@arbitraryemail.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant2', + // All providers without hd configured should be returned. + providerIds: ['google.com', 'facebook.com', 'password', + 'github.com', 'saml.provider'], + email: 'user@arbitraryemail.com', + }, selectedTenantInfo); + }); + }, + + testSelectTenant_identifierFirst_topLevelProject() { + // Test selectTenant identifier first flow for top-level project. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + [null]); + + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter the email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@acme.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + // tenant ID should be null for top-level project. + tenantId: null, + providerIds: ['password', 'saml.provider'], + email: 'user@acme.com', + }, selectedTenantInfo); + }); + }, + + testSelectTenant_identifierFirst_tenantNotConfigured() { + // Test selectTenant identifier first flow when tenant ID being passed + // is not configured in UI handler. The tenant should be skipped with no + // error being thrown. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + // invalid_tenant is not configured. It should be skipped and no error + // should be thrown. + const selectPromise = handler.selectTenant( + projectConfig, + ['invalid_tenant', 'tenant1']); + + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter the email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@acme.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: ['google.com', 'facebook.com', 'password'], + email: 'user@acme.com', + }, selectedTenantInfo); + }); + }, + + testSelectTenant_identifierFirst_invalidEmail() { + // Test selectTenant for identifier first flow when user enters an + // invalid email. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant1', 'tenant2']); + + // The provider match for email page should be shown. + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + // Enter an empty email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, ''); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + // The missing email error should be displayed. + assertEmailErrorMessage(strings.errorMissingEmail().toString(), container); + // The provider match for email page should still be shown. + assertProviderMatchByEmailPageVisible(container); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + + // Enter an invalid email and click enter. + forms.setValue(emailInput, '12345678@'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + // The invalid email error should be displayed. + assertEmailErrorMessage(strings.errorInvalidEmail().toString(), container); + // The provider match for email page should still be shown. + assertProviderMatchByEmailPageVisible(container); + + // Enter a valid email and click enter. + forms.setValue(emailInput, 'user@acme.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + return selectPromise.then((selectedTenantInfo) => { + // The provider match for email page should be hidden. + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: ['google.com', 'facebook.com', 'password'], + email: 'user@acme.com', + }, selectedTenantInfo); + }); + }, + + testSelectTenant_identifierFirst_invalidApiKey() { + // Test when invalid API key is passed in projectConfig. + configs['API_KEY']['displayMode'] = 'identifierFirst'; + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + { + apiKey: 'INVALID_API_KEY', + projectId: 'PROJECT_ID', + }, + ['tenant1', 'tenant2']); + assertProviderMatchByEmailPageHidden(container); + // selectTenantUiShownCallback should not be triggered. + assertEquals(0, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + return selectPromise.then(fail, (error) => { + assertProviderMatchByEmailPageHidden(container); + let errorMessage = + strings.errorCIAP({code: 'invalid-configuration'}).toString(); + assertErrorPageVisible(container, errorMessage, false); + }); + }, + + testSelectTenant_twice() { + // Test selectTenant is called and then followed by startSignIn. And + // then selectTenant is called again while startSignIn is still pending. + handler = new FirebaseUiHandler(container, configs); + const selectPromise = handler.selectTenant( + projectConfig, + ['tenant1', 'tenant2']); + + // The select tenant page should be shown. + assertSelectTenantPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(1, selectTenantUiShownCallback.getCallCount()); + // selectTenantUiHiddenCallback should not be triggered. + assertEquals(0, selectTenantUiHiddenCallback.getCallCount()); + const buttons = dom.getElementsByClass( + 'firebaseui-id-tenant-selection-button', container); + // Two tenants should be available to be selected from. + const expectedTenants = ['tenant1', 'tenant2']; + assertEquals(expectedTenants.length, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(expectedTenants[i], + dataset.get(buttons[i], 'tenantId')); + } + + // Click the tenant1's button. + testingEvents.fireClickSequence(buttons[0]); + return selectPromise.then((selectedTenantInfo) => { + // The select tenant page should be hidden. + assertSelectTenantPageHidden(container); + // selectTenantUiHiddenCallback should be triggered. + assertEquals(1, selectTenantUiHiddenCallback.getCallCount()); + assertObjectEquals({ + tenantId: 'tenant1', + providerIds: + ['google.com', 'facebook.com', 'password', 'saml.provider'], + }, selectedTenantInfo); + + // startSignIn with the selected tenant. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1, selectedTenantInfo); + + // Calling selectTenant again while sign-in is still pending should + // not throw errors. + assertNotThrows(() => { + handler.selectTenant(projectConfig, ['tenant1', 'tenant2']); + // The select tenant page should be shown. + assertSelectTenantPageVisible(container); + // selectTenantUiShownCallback should be triggered. + assertEquals(2, selectTenantUiShownCallback.getCallCount()); + }); + }); + }, + + testGetAuth() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals('tenant1', auth1.tenantId); + // Test that passing the same tenant ID will return the same Auth instance. + const sameAuth = handler.getAuth('API_KEY', 'tenant1'); + assertEquals(sameAuth, auth1); + assertEquals('tenant1', sameAuth.tenantId); + + auth2 = handler.getAuth('API_KEY', 'tenant2'); + assertEquals('tenant2', auth2.tenantId); + // Verify that a new Auth instance is created. + assertNotEquals(auth2, auth1); + + // Verify that invalid API key error is thrown. + let error = assertThrows(() => { + handler.getAuth('INVALID_API_KEY', 'tenant1'); + }); + assertEquals( + 'Invalid project configuration: API key is invalid!', + error.message); + + // Verify that invalid tenant ID error is thrown. + error = assertThrows(() => { + handler.getAuth('API_KEY', 'tenant3'); + }); + assertEquals('Invalid tenant configuration!', error.message); + }, + + testGetAuth_topLevelProject() { + // Test top-level project flow where tenant ID is null. + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', null); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is null on Auth instances for top-level project + // flow. + assertNull(auth1.tenantId); + // Test that the same Auth instance will be returned if no tenant ID is + // provided. + const sameAuth = handler.getAuth('API_KEY', null); + assertEquals(sameAuth, auth1); + assertNull(sameAuth.tenantId); + // Verify that the default Auth instance will be returned. + const defaultAuth = firebase.app('[DEFAULT]').auth(); + assertEquals(defaultAuth, auth1); + }, + + testGetAuth_noTopLevelProjectConfigError() { + const configWithoutTopLevelProject = { + 'API_KEY': { + 'authDomain': 'subdomain.firebaseapp.com', + 'tenants': { + 'tenant': { + 'signInOptions': ['google.com', 'facebook.com', 'password'], + 'tosUrl': 'http://localhost/tos', + 'privacyPolicyUrl': 'http://localhost/privacy_policy', + }, + }, + }, + }; + handler = new FirebaseUiHandler( + container, configWithoutTopLevelProject); + // Verify that error is thrown if no top-level project config is available + // when getting the Auth instance. + const error = assertThrows(() => { + handler.getAuth('API_KEY', null); + }); + assertEquals('Invalid tenant configuration!', error.message); + }, + + testStartSignIn() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const startSignInPromise = handler.startSignIn(auth1); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + + // This is complicated by IE delaying focus events firing until this + // function returns. + // http://stackoverflow.com/questions/5900288/ie-focus-event-handler-delay + if (userAgent.IE) { + return; + } + + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals('tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + // Click sign in with email button. + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + testingEvents.fireClickSequence(buttons[2]); + // Enter the email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@example.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + internalAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + ['password']); + internalAuth.process().then(() => { + // Enter password and click submit. + const passwordElement = dom.getElementByClass( + 'firebaseui-id-password', container); + forms.setValue(passwordElement, '123'); + const submit = dom.getElementByClass('firebaseui-id-submit', container); + testingEvents.fireClickSequence(submit); + internalAuth.assertSignInWithEmailAndPassword( + ['user@example.com', '123'], + () => { + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }); + return { + 'user': internalAuth.currentUser, + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'password', + 'isNewUser': false, + }, + }; + }); + return internalAuth.process(); + }).then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + const expectedAuthResult = { + 'user': externalAuth.currentUser, + // Password credential should not be exposed to callback. + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_twice() { + // Test that calling startSignIn again while the first call is still pending + // does not throw an error. + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + auth2 = handler.getAuth('API_KEY', 'tenant2'); + + // startSignIn with auth1. + handler.startSignIn(auth1); + // Keep track of the AuthUI instance being used. + const authUI1 = handler.getCurrentAuthUI(); + assertEquals(auth1, handler.getCurrentAuthUI().getExternalAuth()); + + assertNotThrows(() => { + // startSignIn with auth2. + handler.startSignIn(auth2); + mockClock.tick(); + assertEquals(auth2, handler.getCurrentAuthUI().getExternalAuth()); + // The AuthUI should be re-initialized in the second sign-in operation. + assertNotEquals(authUI1, handler.getCurrentAuthUI()); + }); + mockClock.tick(); + }, + + testStartSignIn_selectedTenantInfo() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Mock that the preferred providers are passed to startSignIn. + 'providerIds': ['password', 'saml.provider'], + }; + const startSignInPromise = handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + + // This is complicated by IE delaying focus events firing until this + // function returns. + // http://stackoverflow.com/questions/5900288/ie-focus-event-handler-delay + if (userAgent.IE) { + return; + } + + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals('tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + // Verify that only the matched providers are enabled. + assertEquals(2, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(selectedTenantInfo.providerIds[i], + dataset.get(buttons[i], 'providerId')); + } + // Click sign in with email button. + testingEvents.fireClickSequence(buttons[0]); + + internalAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + ['password']); + internalAuth.process().then(() => { + // Enter password and click submit. + const passwordElement = dom.getElementByClass( + 'firebaseui-id-password', container); + forms.setValue(passwordElement, '123'); + const submit = dom.getElementByClass('firebaseui-id-submit', container); + testingEvents.fireClickSequence(submit); + internalAuth.assertSignInWithEmailAndPassword( + ['user@example.com', '123'], + () => { + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }); + return { + 'user': internalAuth.currentUser, + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'password', + 'isNewUser': false, + }, + }; + }); + return internalAuth.process(); + }).then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + const expectedAuthResult = { + 'user': externalAuth.currentUser, + // Password credential should not be exposed to callback. + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_selectedTenantInfo_passwordOnly() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Mock that the preferred provider is passed to startSignIn. + 'providerIds': ['password'], + }; + const startSignInPromise = handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + + // This is complicated by IE delaying focus events firing until this + // function returns. + // http://stackoverflow.com/questions/5900288/ie-focus-event-handler-delay + if (userAgent.IE) { + return; + } + + internalAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + ['password']); + internalAuth.process().then(() => { + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals( + 'tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + + // Enter password and click submit. + const passwordElement = dom.getElementByClass( + 'firebaseui-id-password', container); + forms.setValue(passwordElement, '123'); + const submit = dom.getElementByClass('firebaseui-id-submit', container); + testingEvents.fireClickSequence(submit); + internalAuth.assertSignInWithEmailAndPassword( + ['user@example.com', '123'], + () => { + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }); + return { + 'user': internalAuth.currentUser, + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'password', + 'isNewUser': false, + }, + }; + }); + return internalAuth.process(); + }).then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + const expectedAuthResult = { + 'user': externalAuth.currentUser, + // Password credential should not be exposed to callback. + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_selectedTenantInfo_idp_popup() { + // Test sign in with IdP for popup flow when selectedTenantInfo is passed + // with email. + configs['API_KEY']['tenants']['tenant1']['signInFlow'] = 'popup'; + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Mock that the preferred providers are passed to startSignIn. + 'providerIds': ['google.com', 'password'], + }; + const expectedProvider = idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({ + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + const startSignInPromise = handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals('tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + // Verify that only the matched providers are enabled. + assertEquals(2, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(selectedTenantInfo.providerIds[i], + dataset.get(buttons[i], 'providerId')); + } + // Click sign in with Google button. + testingEvents.fireClickSequence(buttons[0]); + + // User should be signed in. + internalAuth.setUser({ + 'email': prefilledEmail, + }); + const cred = { + 'providerId': 'google.com', + 'accessToken': 'ACCESS_TOKEN' + }; + // Sign in with popup triggered. + internalAuth.assertSignInWithPopup( + [expectedProvider], + { + 'user': internalAuth.currentUser, + 'credential': cred, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'google.com', + 'isNewUser': false, + }, + }); + internalAuth.process().then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + const expectedAuthResult = { + 'user': externalAuth.currentUser, + 'credential': cred, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'google.com', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_selectedTenantInfo_idp_immediateRedirect_success() { + // Test sign in with IdP for redirect flow when selectedTenantInfo is passed + // with email and only one provider is enabled. + configs['API_KEY']['tenants']['tenant1']['immediateFederatedRedirect'] = + true; + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Only one federated IdP is available. + 'providerIds': ['google.com'], + }; + const expectedProvider = idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({ + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + // signInUiShownCallback should not be triggered before immediate redirect. + assertEquals(0, signInUiShownCallback.getCallCount()); + + // Should immediately redirect to IdP without button click. + internalAuth.assertSignInWithRedirect([expectedProvider]); + // Blank page should be displayed with progress bar. + assertBlankPageVisible(container); + delayForBusyIndicatorAndAssertIndicatorShown(container); + return internalAuth.process().then(() => { + // signInUiShownCallback should still not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + }); + }, + + testStartSignIn_selectedTenantInfo_idp_immediateRedirect_redirectStatus() { + // Test sign in with IdP for redirect flow when selectedTenantInfo is passed + // with email and only one provider is enabled and redirect status is set. + // This is a special case where user exits during the middle of redirect + // sign-in so that the redirect status is not cleared. + // Set redirect status with the tenant ID used to start the flow. + const redirectStatus = new RedirectStatus('tenant1'); + storage.setRedirectStatus(redirectStatus); + configs['API_KEY']['tenants']['tenant1']['immediateFederatedRedirect'] = + true; + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Only one federated IdP is available. + 'providerIds': ['google.com'], + }; + const expectedProvider = idp.getAuthProvider('google.com'); + + handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + + // Should immediately redirect to IdP without button click. + internalAuth.assertGetRedirectResult( + [], + function() { + // Spinner progress bar should be visible. + assertProgressBarVisible(container); + // Callback page should be hidden in DOM. + assertCallbackPageDomHidden(container); + // signInUiShownCallback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + return { + 'user': null, + }; + }); + + return internalAuth.process().then(() => { + // signInUiShownCallback should not be triggered before immediate + // redirect. + assertEquals(0, signInUiShownCallback.getCallCount()); + internalAuth.assertSignInWithRedirect([expectedProvider]); + // Blank page should be displayed with progress bar. + assertBlankPageVisible(container); + delayForBusyIndicatorAndAssertIndicatorShown(container); + return internalAuth.process(); + }).then(() => { + // signInUiShownCallback should still not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + }); + }, + + testStartSignIn_selectedTenantInfo_idp_immediateRedirect_error() { + // Test sign in with IdP error case for redirect flow when + // selectedTenantInfo is passed with email and only one provider is enabled. + const networkError = { + 'code': 'auth/network-request-failed', + 'message': 'A network error has occurred.', + }; + configs['API_KEY']['tenants']['tenant1']['immediateFederatedRedirect'] = + true; + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Only one federated IdP is available. + 'providerIds': ['google.com'], + }; + const expectedProvider = idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({ + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + // signInUiShownCallback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + + // Should immediately redirect to IdP without button click. + internalAuth.assertSignInWithRedirect( + [expectedProvider], + null, + () => { + // signInUiShown callback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + // Simulate error. + return networkError; + }); + return internalAuth.process() + .then(() => { + // signInUiShown callback should be triggered with expected tenant ID. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals( + 'tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + // Provider sign-in page should be displayed with error in info bar. + assertInfoBarMessage( + strings.error({'code': networkError.code}).toString(), container); + assertProviderSignInPageVisible(container); + }); + }, + + testStartSignIn_selectedTenantInfo_idp_redirect() { + // Test sign in with IdP for redirect flow when selectedTenantInfo is passed + // with email. + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const prefilledEmail = 'user@example.com'; + const selectedTenantInfo = { + 'email': prefilledEmail, + 'tenantId': 'tenant1', + // Mock that the preferred providers are passed to startSignIn. + 'providerIds': ['google.com', 'password'], + }; + const expectedProvider = idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({ + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + handler.startSignIn(auth1, selectedTenantInfo); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals('tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + // Verify that only the matched providers are enabled. + assertEquals(2, buttons.length); + for (let i = 0; i < buttons.length; i++) { + assertEquals(selectedTenantInfo.providerIds[i], + dataset.get(buttons[i], 'providerId')); + } + // Click sign in with Google button. + testingEvents.fireClickSequence(buttons[0]); + + internalAuth.assertSignInWithRedirect([expectedProvider]); + return internalAuth.process(); + }, + + testStartSignIn_topLevelProject() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', null); + const startSignInPromise = handler.startSignIn(auth1); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is null on Auth instances for top-level project + // flow. + assertEquals(auth1, externalAuth); + assertNull(externalAuth.tenantId); + assertNull(internalAuth.tenantId); + + // This is complicated by IE delaying focus events firing until this + // function returns. + // http://stackoverflow.com/questions/5900288/ie-focus-event-handler-delay + if (userAgent.IE) { + return; + } + + // signInUiShownCallback should be triggered. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertNull(signInUiShownCallback.getLastCall().getArgument(0)); + // Click sign in with email button. + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + testingEvents.fireClickSequence(buttons[0]); + // Enter the email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@example.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + internalAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + ['password']); + internalAuth.process().then(() => { + // Enter password and click submit. + const passwordElement = dom.getElementByClass( + 'firebaseui-id-password', container); + forms.setValue(passwordElement, '123'); + const submit = dom.getElementByClass('firebaseui-id-submit', container); + testingEvents.fireClickSequence(submit); + internalAuth.assertSignInWithEmailAndPassword( + ['user@example.com', '123'], + () => { + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': null, + }); + return { + 'user': internalAuth.currentUser, + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'password', + 'isNewUser': false, + }, + }; + }); + return internalAuth.process(); + }).then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + const expectedAuthResult = { + 'user': externalAuth.currentUser, + // Password credential should not be exposed to callback. + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_multiProject() { + // Test the flows with a different API_KEY to verify the multi-project + // support. + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY2', 'tenant3'); + const startSignInPromise = handler.startSignIn(auth1); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that the right API key and Auth domain are used. + assertEquals('API_KEY2', auth1['app']['options']['apiKey']); + assertEquals( + 'subdomain2.firebaseapp.com', auth1['app']['options']['authDomain']); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant3', externalAuth.tenantId); + assertEquals('tenant3', internalAuth.tenantId); + + // This is complicated by IE delaying focus events firing until this + // function returns. + // http://stackoverflow.com/questions/5900288/ie-focus-event-handler-delay + if (userAgent.IE) { + return; + } + + // signInUiShownCallback configured for project with API_KEY1 should not be + // triggered for project with API_KEY2. + assertEquals(0, signInUiShownCallback.getCallCount()); + // Click sign in with email button. + const buttons = dom.getElementsByClass( + 'firebaseui-id-idp-button', container); + testingEvents.fireClickSequence(buttons[2]); + // Enter the email and click enter. + const emailInput = dom.getElementByClass('firebaseui-id-email', container); + forms.setValue(emailInput, 'user@example.com'); + testingEvents.fireKeySequence(emailInput, KeyCodes.ENTER); + + internalAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + ['password']); + internalAuth.process().then(() => { + // Enter password and click submit. + const passwordElement = dom.getElementByClass( + 'firebaseui-id-password', container); + forms.setValue(passwordElement, '123'); + const submit = dom.getElementByClass('firebaseui-id-submit', container); + testingEvents.fireClickSequence(submit); + internalAuth.assertSignInWithEmailAndPassword( + ['user@example.com', '123'], + () => { + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant3', + }); + return { + 'user': internalAuth.currentUser, + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'password', + 'isNewUser': false, + }, + }; + }); + return internalAuth.process(); + }).then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + const expectedAuthResult = { + 'user': externalAuth.currentUser, + // Password credential should not be exposed to callback. + 'credential': null, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'password', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_pendingRedirect_success() { + // Set redirect status with the tenant ID used to start the flow. + const redirectStatus = new RedirectStatus('tenant1'); + storage.setRedirectStatus(redirectStatus); + + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + const startSignInPromise = handler.startSignIn(auth1); + + // signInUiShownCallback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + const cred = { + 'providerId': 'google.com', + 'accessToken': 'ACCESS_TOKEN', + }; + // User should be signed in at this point. + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }); + internalAuth.assertGetRedirectResult( + [], + function() { + // Spinner progress bar should be visible. + assertProgressBarVisible(container); + // Callback page should be hidden in DOM. + assertCallbackPageDomHidden(container); + return { + 'user': internalAuth.currentUser, + 'credential': cred, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'google.com', + 'isNewUser': false, + }, + }; + }); + internalAuth.process().then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + assertEquals(0, signInUiShownCallback.getCallCount()); + const expectedAuthResult = { + 'user': externalAuth.currentUser, + 'credential': cred, + 'operationType': 'signIn', + 'additionalUserInfo': {'providerId': 'google.com', 'isNewUser': false}, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_pendingRedirect_error() { + const networkError = { + 'code': 'auth/network-request-failed', + 'message': 'A network error has occurred.', + }; + // Set redirect status with the tenant ID used to start the flow. + const redirectStatus = new RedirectStatus('tenant1'); + storage.setRedirectStatus(redirectStatus); + + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1); + + // signInUiShownCallback should not be immediately triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that tenant ID is set on Auth instances. + assertEquals(auth1, externalAuth); + assertEquals('tenant1', externalAuth.tenantId); + assertEquals('tenant1', internalAuth.tenantId); + internalAuth.assertGetRedirectResult( + [], + null, + function() { + // signInUiShown callback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + // Spinner progress bar should be visible. + assertProgressBarVisible(container); + // Callback page should be hidden in DOM. + assertCallbackPageDomHidden(container); + // Simulate error. + return networkError; + }); + return internalAuth.process() + .then(() => { + // signInUiShown callback should be triggered with expected tenant ID. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertEquals( + 'tenant1', signInUiShownCallback.getLastCall().getArgument(0)); + // No progress bar should be shown. + assertProgressBarHidden(container); + // Provider sign in page should be shown with the expected error + // message. + assertCallbackPageHidden(container); + assertInfoBarMessage( + strings.error({'code': networkError.code}).toString(), container); + assertProviderSignInPageVisible(container); + }); + }, + + testStartSignIn_pendingRedirect_topLevelProject_success() { + // Set redirect status without the tenant ID. + const redirectStatus = new RedirectStatus(); + storage.setRedirectStatus(redirectStatus); + + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', null); + const startSignInPromise = handler.startSignIn(auth1); + + // signInUiShownCallback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that tenant ID is null on Auth instances. + assertEquals(auth1, externalAuth); + assertNull(externalAuth.tenantId); + assertNull(internalAuth.tenantId); + const cred = { + 'providerId': 'saml.provider', + 'pendingToken': 'PENDING_TOKEN', + }; + // User should be signed in at this point. + internalAuth.setUser({ + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': null, + }); + internalAuth.assertGetRedirectResult( + [], + function() { + // Spinner progress bar should be visible. + assertProgressBarVisible(container); + // Callback page should be hidden in DOM. + assertCallbackPageDomHidden(container); + return { + 'user': internalAuth.currentUser, + 'credential': cred, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'saml.provider', + 'isNewUser': false, + }, + }; + }); + internalAuth.process().then(() => { + internalAuth.assertSignOut([]); + return internalAuth.process(); + }).then(() => { + externalAuth.assertUpdateCurrentUser( + [internalAuth.currentUser], + () => { + externalAuth.setUser(internalAuth.currentUser); + }); + return externalAuth.process(); + }); + return startSignInPromise.then((userCredential) => { + assertEquals(0, signInUiShownCallback.getCallCount()); + const expectedAuthResult = { + 'user': externalAuth.currentUser, + 'credential': cred, + 'operationType': 'signIn', + 'additionalUserInfo': { + 'providerId': 'saml.provider', + 'isNewUser': false, + }, + }; + assertObjectEquals(expectedAuthResult, userCredential); + }); + }, + + testStartSignIn_pendingRedirect_topLevelProject_error() { + const networkError = { + 'code': 'auth/network-request-failed', + 'message': 'A network error has occurred.', + }; + // Set redirect status without the tenant ID. + const redirectStatus = new RedirectStatus(); + storage.setRedirectStatus(redirectStatus); + + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', null); + handler.startSignIn(auth1); + + // signInUiShownCallback should not be immediately triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + + const internalAuth = handler.getCurrentAuthUI().getAuth(); + const externalAuth = handler.getCurrentAuthUI().getExternalAuth(); + // Verify that tenant ID is null on Auth instances. + assertEquals(auth1, externalAuth); + assertNull(externalAuth.tenantId); + assertNull(internalAuth.tenantId); + internalAuth.assertGetRedirectResult( + [], + null, + function() { + // signInUiShown callback should not be triggered. + assertEquals(0, signInUiShownCallback.getCallCount()); + // Spinner progress bar should be visible. + assertProgressBarVisible(container); + // Callback page should be hidden in DOM. + assertCallbackPageDomHidden(container); + // Simulate error. + return networkError; + }); + return internalAuth.process() + .then(() => { + // signInUiShown callback should be triggered with null. + assertEquals(1, signInUiShownCallback.getCallCount()); + assertNull(signInUiShownCallback.getLastCall().getArgument(0)); + // No progress bar should be shown. + assertProgressBarHidden(container); + // Provider sign in page should be shown with the expected error + // message. + assertCallbackPageHidden(container); + assertInfoBarMessage( + strings.error({'code': networkError.code}).toString(), container); + assertProviderSignInPageVisible(container); + }); + }, + + testStartSignIn_projectConfigError() { + handler = new FirebaseUiHandler(container, configs); + auth = firebase.initializeApp({ + 'apiKey': 'INVALID_API_KEY', + 'authDomain': 'subdomain.firebaseapp.com', + }).auth(); + return handler.startSignIn(auth).then(fail, (error) => { + assertEquals( + 'Invalid project configuration: API key is invalid!', + error.message); + }); + }, + + testStartSignIn_invalidTenantIdError() { + handler = new FirebaseUiHandler(container, configs); + auth = firebase.initializeApp({ + 'apiKey': 'API_KEY', + 'authDomain': 'subdomain.firebaseapp.com', + }).auth(); + auth.tenantId = 'INVALID_TENANT_ID'; + return handler.startSignIn(auth).then(fail, (error) => { + assertEquals('Invalid tenant configuration!', error.message); + // signInUiShownCallback should not be triggered if error occurs before + // sign-in UI is shown. + assertEquals(0, signInUiShownCallback.getCallCount()); + }); + }, + + testStartSignIn_invalidTopLevelProjectConfigError() { + const configWithoutTopLevelProject = { + 'API_KEY': { + 'authDomain': 'subdomain.firebaseapp.com', + 'tenants': { + 'tenant': { + 'signInOptions': ['google.com', 'facebook.com', 'password'], + 'tosUrl': 'http://localhost/tos', + 'privacyPolicyUrl': 'http://localhost/privacy_policy', + }, + }, + }, + }; + handler = new FirebaseUiHandler( + container, configWithoutTopLevelProject); + auth = firebase.initializeApp({ + 'apiKey': 'API_KEY', + 'authDomain': 'subdomain.firebaseapp.com', + }).auth(); + auth.tenantId = null; + return handler.startSignIn(auth).then(fail, (error) => { + assertEquals('Invalid tenant configuration!', error.message); + }); + }, + + testStartSignIn_uiReset() { + testStubs.set( + AuthUI.prototype, + 'reset', + recordFunction(AuthUI.prototype.reset)); + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1); + mockClock.tick(); + assertEquals(0, handler.getCurrentAuthUI().reset.getCallCount()); + return handler.completeSignOut().then(() => { + // Rendering other screen should reset the AuthUI instance. + assertEquals(1, handler.getCurrentAuthUI().reset.getCallCount()); + assertCompleteSignOutPageVisible(container); + }); + }, + + testReset() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1); + + assertNotNull(AuthUI.getInstance()); + assertNotNull(handler.getCurrentAuthUI()); + return handler.reset().then(() => { + // Verify that the AuthUI instance is deleted and the reference to the + // instance is set to null. + assertNull(AuthUI.getInstance()); + assertNull(handler.getCurrentAuthUI()); + // Calling startSignIn again should not throw. + handler.startSignIn(auth1); + }); + }, + + testReset_progressBar() { + handler = new FirebaseUiHandler(container, configs); + handler.showProgressBar(); + mockClock.tick(500); + assertProgressBarVisible(container); + return handler.reset().then(() => { + assertProgressBarHidden(container); + }); + }, + + testProgressBar() { + handler = new FirebaseUiHandler(container, configs); + handler.showProgressBar(); + assertProgressBarHidden(container); + // Progress bar should be visible after 500ms. + mockClock.tick(500); + assertProgressBarVisible(container); + // signInUiShownCallback should not be triggered by progress bar shown. + assertEquals(0, signInUiShownCallback.getCallCount()); + handler.hideProgressBar(); + assertProgressBarHidden(container); + + // Verify that hideProgressBar will cancel the showProgressBar timeout. + handler.showProgressBar(); + mockClock.tick(); + handler.hideProgressBar(); + assertProgressBarHidden(container); + mockClock.tick(500); + assertProgressBarHidden(container); + + // Verify that calling showProgressBar twice won't render two progress bars. + handler.showProgressBar(); + mockClock.tick(500); + assertProgressBarVisible(container); + handler.showProgressBar(); + mockClock.tick(500); + assertProgressBarVisible(container); + + // Verify that calling completeSignOut will remove the progress bar. + return handler.completeSignOut().then(() => { + assertProgressBarHidden(container); + assertCompleteSignOutPageVisible(container); + handler.hideProgressBar(); + assertCompleteSignOutPageVisible(container); + // Calling showProgressBar will remove the sign out page. + handler.showProgressBar(); + assertCompleteSignOutPageVisible(container); + mockClock.tick(500); + assertCompleteSignOutPageHidden(container); + assertProgressBarVisible(container); + }); + }, + + testProgressBar_pending() { + handler = new FirebaseUiHandler(container, configs); + handler.showProgressBar(); + assertProgressBarHidden(container); + + // Verify that the delayed progress bar will be cancelled. + return handler.completeSignOut().then(() => { + assertCompleteSignOutPageVisible(container); + mockClock.tick(500); + assertProgressBarHidden(container); + }); + }, + + testCompleteSignOut() { + handler = new FirebaseUiHandler(container, configs); + return handler.completeSignOut().then(() => { + assertCompleteSignOutPageVisible(container); + // signInUiShownCallback should not be triggered by complete sign out UI + // shown. + assertEquals(0, signInUiShownCallback.getCallCount()); + return handler.reset(); + }).then(() => { + assertCompleteSignOutPageHidden(container); + }); + }, + + testHandleError() { + handler = new FirebaseUiHandler(container, configs); + // CIAP error with default error message. + let code = 'invalid-argument'; + let errorMessage = + strings.errorCIAP({code: code}).toString(); + let ciapError = mockCIAPError(code, errorMessage); + handler.handleError(ciapError); + assertErrorPageVisible(container, errorMessage, false); + // signInUiShownCallback should not be triggered by error UI shown. + assertEquals(0, signInUiShownCallback.getCallCount()); + + // CIAP error with custom error message. The custom message should be + // overridden by the default one. + ciapError = mockCIAPError(code, 'custom message'); + handler.handleError(ciapError); + assertErrorPageVisible(container, errorMessage, false); + + // Auth error with error message. + code = 'auth/user-disabled'; + errorMessage = + strings.errorCIAP({code: code}).toString(); + ciapError = mockCIAPError(code, errorMessage); + handler.handleError(ciapError); + assertErrorPageVisible(container, errorMessage, false); + + // GCIP error with error message. + code = 'restart-process'; + errorMessage = + strings.errorCIAP({code: code}).toString(); + ciapError = mockCIAPError(code, errorMessage); + handler.handleError(ciapError); + assertErrorPageVisible(container, errorMessage, false); + + // Error code not defined in FirebaseUI. Message in error will be displayed. + errorMessage = 'Not defined error message.'; + ciapError = mockCIAPError('undefined-error-code', errorMessage); + handler.handleError(ciapError); + assertErrorPageVisible(container, errorMessage, false); + }, + + testHandleError_retry() { + const code = 'auth/network-request-failed'; + const errorMessage = + strings.errorCIAP({code: code}).toString(); + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + handler.startSignIn(auth1); + const onRetryClick = recordFunction(); + // Recoverable error with retry callback. + const ciapError = mockCIAPError(code, errorMessage, onRetryClick); + + handler.handleError(ciapError); + assertErrorPageVisible(container, errorMessage, true); + const retryButton = getRetryButton(container); + // Click the retry button should reset the UI and trigger the retry + // callback. + testingEvents.fireClickSequence(retryButton); + mockClock.tick(); + // Error page should be hidden. + assertErrorPageHidden(container); + // UI should be reset. + assertNull(AuthUI.getInstance()); + assertNull(handler.getCurrentAuthUI()); + // Retry callback should be triggered. + assertEquals(1, onRetryClick.getCallCount()); + }, + + testLanguageCode() { + handler = new FirebaseUiHandler(container, configs); + assertNull(handler.languageCode); + handler.languageCode = 'en'; + assertEquals('en', handler.languageCode); + handler.languageCode = null; + assertNull(handler.languageCode); + }, + + testProcessUser() { + const userCallback = recordFunction((user) => { + // Mock updating displayName. + user.displayName = 'Processed User'; + return GoogPromise.resolve(user); + }); + // Set the beforeSignInSuccess callback in UI config. + configs['API_KEY']['callbacks'] = { + 'beforeSignInSuccess': userCallback, + }; + handler = new FirebaseUiHandler(container, configs); + // This should always be called before the signed in user is determined. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + // Mock the user to be processed is from tenant project tenant1. + const user = { + 'uid': 'UID', + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }; + // Mock the user is signed in on Auth instance. + auth1.setUser(user); + return handler.processUser(user).then((processedUser) => { + // Verify that the user reference passed to provided callback is the + // same as the one being processed. + assertEquals(userCallback.getLastCall().getArgument(0), user); + assertEquals(1, userCallback.getCallCount()); + // Verify that the user reference returned from provided callback is the + // same as the one being processed. + assertEquals(user, processedUser); + assertEquals('Processed User', processedUser.displayName); + }); + }, + + testProcessUser_noCallback() { + // Test the user is returned when no beforeSignInSuccess callback is + // provided. + handler = new FirebaseUiHandler(container, configs); + // This should always be called before the signed in user is determined. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + // Mock the user to be processed is from tenant project tenant1, in which + // beforeSignInSuccess callback is not provided. + const user = { + 'uid': 'UID', + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }; + // Mock the user is signed in on Auth instance. + auth1.setUser(user); + return handler.processUser(user).then((processedUser) => { + // Verify that the user reference returned from provided callback is the + // same as the one being processed. + assertEquals(user, processedUser); + assertObjectEquals(user, processedUser); + }); + }, + + testProcessUser_reject() { + const expectedError = new Error('error thrown in callback'); + const userCallback = recordFunction( + (user) => GoogPromise.reject(expectedError)); + // Set the beforeSignInSuccess callback in UI config. + configs['API_KEY']['callbacks'] = { + 'beforeSignInSuccess': userCallback, + }; + handler = new FirebaseUiHandler(container, configs); + // This should always be called before the signed in user is determined. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + // Mock the user to be processed is from tenant project tenant1. + const user = { + 'uid': 'UID', + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }; + // Mock the user is signed in on Auth instance. + auth1.setUser(user); + return handler.processUser(user).then(fail, (error) => { + // Verify that the user reference passed to provided callback is the + // same as the one being processed. + assertEquals(userCallback.getLastCall().getArgument(0), user); + assertEquals(1, userCallback.getCallCount()); + // Verify promise is rejected with the same error thrown in callback. + assertEquals(expectedError, error); + }); + }, + + testProcessUser_userMismatch() { + // Test that if different user is returned in callback, error is thrown. + const userCallback = recordFunction((user) => { + const anotherUser = { + 'uid': 'MISMATCH_UID', + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }; + return GoogPromise.resolve(anotherUser); + }); + // Set the beforeSignInSuccess callback in UI config. + configs['API_KEY']['callbacks'] = { + 'beforeSignInSuccess': userCallback, + }; + handler = new FirebaseUiHandler(container, configs); + // This should always be called before the signed in user is determined. + auth1 = handler.getAuth('API_KEY', 'tenant1'); + // Mock the user to be processed is from tenant project tenant1. + const user = { + 'uid': 'UID', + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'tenant1', + }; + // Mock the user is signed in on Auth instance. + auth1.setUser(user); + return handler.processUser(user).then(fail, (error) => { + // Verify that the user reference passed to provided callback is the + // same as the one being processed. + assertEquals(userCallback.getLastCall().getArgument(0), user); + assertEquals(1, userCallback.getCallCount()); + assertEquals('User with mismatching UID returned.', error.message); + }); + }, + + testProcessUser_invalidConfigError() { + handler = new FirebaseUiHandler(container, configs); + auth1 = handler.getAuth('API_KEY', 'tenant1'); + // Mock the user to be processed is from tenant project invalid_tenant. + const user = { + 'uid': 'UID', + 'email': 'user@example.com', + 'displayName':'Sample User', + 'tenantId': 'invalid_tenant', + }; + return handler.processUser(user).then(fail, (error) => { + assertEquals('Invalid tenant configuration!', error.message); + }); + }, +}); diff --git a/javascript/widgets/handler/callback.js b/javascript/widgets/handler/callback.js index 2716828f..8b014227 100644 --- a/javascript/widgets/handler/callback.js +++ b/javascript/widgets/handler/callback.js @@ -133,7 +133,7 @@ firebaseui.auth.widget.handler.handleCallback = firebaseui.auth.widget.handler.handleCallbackResult_ = function(app, component, result) { if (result['user']) { - var authResult = /** @type {!firebaseui.auth.AuthResult} */ ({ + var authResult = /** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ 'user': result['user'], 'credential': result['credential'], 'operationType': result['operationType'], @@ -160,22 +160,23 @@ firebaseui.auth.widget.handler.handleCallbackResult_ = if (pendingCredential) { // Check if there is a pending auth credential. If so, complete the link // process and delete the pending credential. - app.registerPending(result['user'].linkWithCredential( - pendingCredential) + app.registerPending(result['user'].linkWithCredential(pendingCredential) .then(function(userCredential) { // Linking successful, complete sign in, pass pending credentials // as the developer originally expected them in the sign in // attempt that triggered the link. - authResult = /** @type {!firebaseui.auth.AuthResult} */ ({ - 'user': userCredential['user'], - 'credential': pendingCredential, - // Even though the operation type returned here is always 'link', - // we will sign in again on external Auth instance with this - // credential returning 'signIn' or 'link' in case of anonymous - // upgrade through finishSignInAndRetrieveDataWithAuthResult. - 'operationType': userCredential['operationType'], - 'additionalUserInfo': userCredential['additionalUserInfo'] - }); + authResult = ( + /** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ + 'user': userCredential['user'], + 'credential': pendingCredential, + // Even though the operation type returned here is always + // 'link', we will sign in again on external Auth instance + // with this credential returning 'signIn' or 'link' in case + // of anonymous upgrade through + // finishSignInAndRetrieveDataWithAuthResult. + 'operationType': userCredential['operationType'], + 'additionalUserInfo': userCredential['additionalUserInfo'] + })); firebaseui.auth.widget.handler.handleCallbackSuccess_( app, component, authResult); }, @@ -209,9 +210,9 @@ firebaseui.auth.widget.handler.handleCallbackResult_ = * configuration is used. * @param {!firebaseui.auth.ui.page.Base} component The current UI component if * present. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result, which - * includes current user, credential to sign in on external Auth instance, - * additional user info and operation type. + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result, which includes current user, credential to sign in on external + * Auth instance, additional user info and operation type. * @private */ firebaseui.auth.widget.handler.handleCallbackSuccess_ = @@ -330,7 +331,8 @@ firebaseui.auth.widget.handler.handleCallbackLinking_ = * @param {!firebaseui.auth.AuthUI} app The current FirebaseUI instance whose * configuration is used. * @param {!firebaseui.auth.ui.page.Base} component The current UI component. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result object. + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result object. * @private */ firebaseui.auth.widget.handler.handleCallbackEmailMismatch_ = diff --git a/javascript/widgets/handler/callback_test.js b/javascript/widgets/handler/callback_test.js index b1ee8846..22db7927 100644 --- a/javascript/widgets/handler/callback_test.js +++ b/javascript/widgets/handler/callback_test.js @@ -20,7 +20,6 @@ goog.provide('firebaseui.auth.widget.handler.CallbackTest'); goog.setTestOnly('firebaseui.auth.widget.handler.CallbackTest'); goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.CredentialHelper'); goog.require('firebaseui.auth.PendingEmailCredential'); goog.require('firebaseui.auth.idp'); goog.require('firebaseui.auth.soy2.strings'); @@ -1224,7 +1223,8 @@ function testHandleCallback_redirectUser_pendingCredential_err_emailAuthOnly() { // Test when single email auth provider is used. asyncTestCase.waitForSignals(1); app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); var cred = firebaseui.auth.idp.getAuthCredential({ @@ -1278,7 +1278,8 @@ function testHandleCallback_signedInUser_pendingCred_err_emailAuthOnly_popup() { asyncTestCase.waitForSignals(1); app.updateConfig('signInFlow', 'popup'); app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); var cred = firebaseui.auth.idp.getAuthCredential({ @@ -1376,7 +1377,8 @@ function testHandleCallback_redirectError_noLinking_emailAuthOnly() { // Test when single email auth provider is used. asyncTestCase.waitForSignals(1); app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); // Callback rendered. @@ -1404,7 +1406,8 @@ function testHandleCallback_signInError_noLinking_emailAuthOnly_popup() { asyncTestCase.waitForSignals(1); app.setConfig({ 'signInFlow': 'popup', - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); // Popup result: reject with a generic error. @@ -1794,7 +1797,8 @@ function testHandleCallback_redirectError_linkingRequired_err_emailAuthOnly() { // Fetch Provider Email request fails in this case. // Test when single email auth provider is used. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); asyncTestCase.waitForSignals(1); @@ -1838,7 +1842,8 @@ function testHandleCallback_signInErr_linkRequired_err_emailAuthOnly_popup() { // Fetch Provider Email request fails in this case. // Test when single email auth provider is used. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); asyncTestCase.waitForSignals(1); @@ -2004,7 +2009,8 @@ function testHandleCallback_nullUser_emailAuthOnly_acEnabled() { // Test when no previous sign-in with redirect is detected and the sign-in // page is rendered (email auth provider only, accountchooser.com is enabled). app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); asyncTestCase.waitForSignals(1); @@ -2047,7 +2053,8 @@ function testHandleCallback_nullUser_emailAuthOnly_acEnabled_newAcctSelect() { // page is rendered (email auth provider only, accountchooser.com is enabled). // Simulate new account selected. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], 'callbacks': { 'accountChooserResult': accountChooserResultCallback, @@ -2113,7 +2120,8 @@ function testHandleCallback_nullUser_emailAuthOnly_acEnabled_existingAccount() { // page is rendered (email auth provider only, accountchooser.com is enabled). // Simulate existing password account selected. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], 'callbacks': { 'accountChooserResult': accountChooserResultCallback, @@ -2174,7 +2182,8 @@ function testHandleCallback_nullUser_emailAuthOnly_acEnabled_addAccount() { // page is rendered (email auth provider only, accountchooser.com is enabled). // Simulate "Add Account" selected. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], 'callbacks': { 'accountChooserResult': accountChooserResultCallback, @@ -2226,7 +2235,7 @@ function testHandleCallback_nullUser_emailAuthOnly_acDisabled() { // page is rendered (email auth provider only, credential helpers are // disabled). app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], 'callbacks': { 'accountChooserResult': accountChooserResultCallback, @@ -2277,7 +2286,8 @@ function testHandleCallback_nullUser_emailAuthOnly_acUnavailable() { // page is rendered (email auth provider only, accountchooser.com is // unavailable). app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], 'callbacks': { 'accountChooserResult': accountChooserResultCallback, @@ -2444,7 +2454,7 @@ function testHandleCallback_operationNotSupported_passwordOnly_acDisabled() { // Set password only provider. // Test with accountchooser.com disabled. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); // Callback rendered. @@ -2472,7 +2482,8 @@ function testHandleCallback_operationNotSupported_passwordOnly_acEnabled() { // Set password only provider. // Test with accountchooser.com enabled. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); // Simulate empty response from accountchooser.com click. diff --git a/javascript/widgets/handler/common.js b/javascript/widgets/handler/common.js index 130b4cbb..085b3e71 100644 --- a/javascript/widgets/handler/common.js +++ b/javascript/widgets/handler/common.js @@ -16,7 +16,6 @@ * @fileoverview Common functions shared by handlers. */ -goog.provide('firebaseui.auth.AuthResult'); goog.provide('firebaseui.auth.OAuthResponse'); goog.provide('firebaseui.auth.widget.handler.common'); @@ -58,17 +57,6 @@ goog.forwardDeclare('firebaseui.auth.AuthUI'); firebaseui.auth.OAuthResponse; -/** - * @typedef {{ - * user: (?firebase.User), - * credential: (?firebase.auth.AuthCredential), - * operationType: (?string|undefined), - * additionalUserInfo: (?firebase.auth.AdditionalUserInfo|undefined) - * }} - */ -firebaseui.auth.AuthResult; - - /** * @define {string} The accountchooser.com client library URL. */ @@ -414,9 +402,9 @@ firebaseui.auth.widget.handler.common.selectFromAccountChooser = function( * @param {firebaseui.auth.AuthUI} app The current FirebaseUI instance whose * configuration is used and that has a user signed in. * @param {firebaseui.auth.ui.page.Base} component The UI component. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result, which - * includes current user, credential to sign in to external Auth instance, - * additional user info and operation type. + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result, which includes current user, credential to sign in to external + * Auth instance, additional user info and operation type. * @param {boolean=} opt_alreadySignedIn Whether user already signed in on * external Auth instance. If true, current user on external Auth instance * should be passed in from Auth result. Should be true for anonymous @@ -614,9 +602,9 @@ firebaseui.auth.widget.handler.common.setUserLoggedInExternal_ = * @param {firebaseui.auth.AuthUI} app The current FirebaseUI instance whose * configuration is used and that has a user signed in. * @param {firebaseui.auth.ui.page.Base} component The UI component. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result, which - * includes current user, credential to sign in to external Auth instance, - * additional user info and operation type. + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result, which includes current user, credential to sign in to external + * Auth instance, additional user info and operation type. * @private */ firebaseui.auth.widget.handler.common.setUserLoggedInExternalWithAuthResult_ = @@ -861,7 +849,7 @@ firebaseui.auth.widget.handler.common.federatedSignIn = function( } firebaseui.auth.log.error('signInWithRedirect: ' + error['code']); var errorMessage = firebaseui.auth.widget.handler.common.getErrorMessage( - error); + error); // If the page was previously blank because the 'nascar' screen is being // skipped, then the provider sign-in 'nascar' screen needs to be shown // along with the error message. Otherwise, the error message can simply @@ -998,7 +986,8 @@ firebaseui.auth.widget.handler.common.handleSignInAnonymously = function( return firebaseui.auth.widget.handler.common.setLoggedInWithAuthResult( app, component, - /** @type {!firebaseui.auth.AuthResult} */(userCredential), + /** @type {!firebaseui.auth.widget.Config.AuthResult} */( + userCredential), true); }, function(error) { @@ -1179,14 +1168,15 @@ firebaseui.auth.widget.handler.common.verifyPassword = goog.bind(app.startSignInWithEmailAndPassword, app)), [email, password], function(userCredential) { - var authResult = /** @type {!firebaseui.auth.AuthResult} */ ({ - 'user': userCredential['user'], - // Password credential is needed to complete sign-in to the original - // Auth instance. - 'credential': emailPassCred, - 'operationType': userCredential['operationType'], - 'additionalUserInfo': userCredential['additionalUserInfo'] - }); + var authResult = ( + /** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ + 'user': userCredential['user'], + // Password credential is needed to complete sign-in to the + // original Auth instance. + 'credential': emailPassCred, + 'operationType': userCredential['operationType'], + 'additionalUserInfo': userCredential['additionalUserInfo'] + })); return firebaseui.auth.widget.handler.common.setLoggedInWithAuthResult( app, component, authResult); }, @@ -1267,34 +1257,34 @@ firebaseui.auth.widget.handler.common.isPhoneProviderOnly = function(app) { /** * Calls the appropriate sign-in start handler depending on display mode. * - * @param {firebaseui.auth.AuthUI} app The current FirebaseUI instance whose + * @param {?firebaseui.auth.AuthUI} app The current FirebaseUI instance whose * configuration is used. - * @param {Element} container The container DOM element. - * @param {string=} opt_email The email to prefill. - * @param {string=} opt_infoBarMessage The message to show on info bar. + * @param {?Element} container The container DOM element. + * @param {string=} email The optional email to prefill. + * @param {string=} infoBarMessage The optional message to show on info bar. */ firebaseui.auth.widget.handler.common.handleSignInStart = function( - app, container, opt_email, opt_infoBarMessage) { + app, container, email = undefined, infoBarMessage = undefined) { if (firebaseui.auth.widget.handler.common.isPasswordProviderOnly(app)) { // If info bar message is available, do not go to accountchooser.com since // this is a result of some error in the flow and the error message must be // displayed. - if (opt_infoBarMessage) { + if (infoBarMessage) { firebaseui.auth.widget.handler.handle( firebaseui.auth.widget.HandlerName.SIGN_IN, app, container, - opt_email, - opt_infoBarMessage); + email, + infoBarMessage); } else { // Email auth provider is the only option, trigger that flow immediately // instead of just showing a single sign-in with email button. firebaseui.auth.widget.handler.common.handleSignInWithEmail( - app, container, opt_email); + app, container, email); } } else if ( app && firebaseui.auth.widget.handler.common.isPhoneProviderOnly(app) && - !opt_infoBarMessage) { + !infoBarMessage) { // Avoid an infinite loop by only skipping to phone auth if there's no // error on phone auth rendering, eg recaptcha error when network down, // which would trigger an info bar message. @@ -1302,13 +1292,14 @@ firebaseui.auth.widget.handler.common.handleSignInStart = function( firebaseui.auth.widget.HandlerName.PHONE_SIGN_IN_START, app, container); } else if (app && app.getConfig().federatedProviderShouldImmediatelyRedirect() && - !opt_infoBarMessage) { + !infoBarMessage) { // If there is an info bar message available, the 'nascar' screen cannot be // skipped since the message or error must be shown to the user. firebaseui.auth.widget.handler.handle( firebaseui.auth.widget.HandlerName.FEDERATED_REDIRECT, app, - container); + container, + email); } else { // For all other cases, show the provider sign-in screen along with any // info bar messages that need to be shown to the user. @@ -1316,7 +1307,8 @@ firebaseui.auth.widget.handler.common.handleSignInStart = function( firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN, app, container, - opt_infoBarMessage); + infoBarMessage, + email); } }; @@ -1466,6 +1458,7 @@ firebaseui.auth.widget.handler.common.handleSignInFetchSignInMethodsForEmail = email); } } + }; @@ -1527,12 +1520,19 @@ firebaseui.auth.widget.handler.common.handleSignInWithEmail = firebaseui.auth.widget.Config.AccountChooserResult.UNAVAILABLE, function() { // If not available, go to the sign-in screen and no UI - // shown callback. - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.SIGN_IN, - app, - container, - opt_email); + // shown callback. If email is prefilled, skip the sign-in screen. + if (opt_email) { + firebaseui.auth.widget.handler.handle( + firebaseui.auth.widget.HandlerName.PREFILLED_EMAIL_SIGN_IN, + app, + container, + opt_email); + } else { + firebaseui.auth.widget.handler.handle( + firebaseui.auth.widget.HandlerName.SIGN_IN, + app, + container); + } }); }; // Handle accountchooser.com invoked callback, pass continue callback @@ -1581,12 +1581,21 @@ firebaseui.auth.widget.handler.common.handleSignInWithEmail = AccountChooserResult.UNAVAILABLE, function() { // If not available, go to the sign-in screen and no - // UI shown callback. - firebaseui.auth.widget.handler.handle( - firebaseui.auth.widget.HandlerName.SIGN_IN, - app, - container, - opt_email); + // UI shown callback. If email is prefilled, + // skip the sign-in screen. + if (opt_email) { + firebaseui.auth.widget.handler.handle( + firebaseui.auth.widget.HandlerName + .PREFILLED_EMAIL_SIGN_IN, + app, + container, + opt_email); + } else { + firebaseui.auth.widget.handler.handle( + firebaseui.auth.widget.HandlerName.SIGN_IN, + app, + container); + } }); }, firebaseui.auth.storage.getRememberedAccounts(app.getAppId()), diff --git a/javascript/widgets/handler/common_test.js b/javascript/widgets/handler/common_test.js index 1900baac..7629d6fb 100644 --- a/javascript/widgets/handler/common_test.js +++ b/javascript/widgets/handler/common_test.js @@ -21,7 +21,6 @@ goog.setTestOnly('firebaseui.auth.widget.handler.CommonTest'); goog.require('firebaseui.auth.Account'); goog.require('firebaseui.auth.AuthUI'); goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.CredentialHelper'); goog.require('firebaseui.auth.PendingEmailCredential'); goog.require('firebaseui.auth.RedirectStatus'); goog.require('firebaseui.auth.idp'); @@ -1504,7 +1503,7 @@ function testSelectFromAccountChooser_acCallbacks_unavailable() { // accountchooser.com callbacks provided. // Test when accountchooser.com is unavailable. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, 'callbacks': { 'accountChooserResult': accountChooserResultCallback, 'accountChooserInvoked': accountChooserInvokedCallback @@ -1881,7 +1880,7 @@ function testHandleSignInWithEmail_acNotEnabled() { 'setRedirectStatus', goog.testing.recordFunction()); app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE }); firebaseui.auth.widget.handler.common.acForceUiShown_ = true; firebaseui.auth.widget.handler.common.handleSignInWithEmail(app, container); @@ -1893,6 +1892,39 @@ function testHandleSignInWithEmail_acNotEnabled() { assertFalse(firebaseui.auth.widget.handler.common.acForceUiShown_); } + +function testHandleSignInWithEmail_prefillEmail() { + const prefilledEmail = 'user@example'; + testStubs.replace( + firebaseui.auth.storage, + 'setRedirectStatus', + goog.testing.recordFunction()); + app.setConfig({ + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE + }); + firebaseui.auth.widget.handler.common.acForceUiShown_ = true; + firebaseui.auth.widget.handler.common.handleSignInWithEmail( + app, container, prefilledEmail); + assertBlankPage(); + /** @suppress {missingRequire} */ + assertEquals(0, + firebaseui.auth.storage.setRedirectStatus.getCallCount()); + assertFalse(firebaseui.auth.storage.hasRememberAccount(app.getAppId())); + assertFalse(firebaseui.auth.widget.handler.common.acForceUiShown_); + + testAuth.assertFetchSignInMethodsForEmail( + [prefilledEmail], + []); + return testAuth.process().then(() => { + // Password sign-up page should be shown. + assertPasswordSignUpPage(); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + return testAuth.process(); + }); +} + + function testLoadAccountchooserJs_externallyLoaded() { // Test accountchooser.com client loading when already loaded. // Reset loadAccountchooserJs stubs. @@ -2077,6 +2109,28 @@ function testFederatedSignIn_success_redirectMode() { // Confirm signInWithRedirect called underneath. testAuth.assertSignInWithRedirect([expectedProvider]); testAuth.process(); + +} + + +function testFederatedSignIn_success_redirectMode_tenantId() { + app.setTenantId('TENANT_ID'); + var expectedProvider = firebaseui.auth.idp.getAuthProvider('google.com'); + var component = new firebaseui.auth.ui.page.ProviderSignIn( + goog.nullFunction(), []); + component.render(container); + assertFalse(firebaseui.auth.storage.hasRedirectStatus(app.getAppId())); + // This will trigger a signInWithRedirect using the expected provider. + firebaseui.auth.widget.handler.common.federatedSignIn( + app, component, 'google.com'); + assertTrue(firebaseui.auth.storage.hasRedirectStatus(app.getAppId())); + assertEquals( + 'TENANT_ID', + firebaseui.auth.storage.getRedirectStatus(app.getAppId()).getTenantId()); + assertProviderSignInPage(); + // Confirm signInWithRedirect called underneath. + testAuth.assertSignInWithRedirect([expectedProvider]); + testAuth.process(); } @@ -2126,7 +2180,7 @@ function testFederatedSignIn_error_redirectMode() { function testFederatedSignIn_success_cordova() { simulateCordovaEnvironment(); - var cred = firebaseui.auth.idp.getAuthCredential({ + var cred = createMockCredential({ 'providerId': 'google.com', 'accessToken': 'ACCESS_TOKEN' }); @@ -2314,7 +2368,7 @@ function testFederatedSignIn_anonymousUpgrade_success_cordova() { simulateCordovaEnvironment(); app.updateConfig('autoUpgradeAnonymousUsers', true); externalAuth.setUser(anonymousUser); - var cred = firebaseui.auth.idp.getAuthCredential({ + var cred = createMockCredential({ 'providerId': 'google.com', 'accessToken': 'ACCESS_TOKEN' }); @@ -2366,7 +2420,7 @@ function testFederatedSignIn_anonymousUpgrade_credInUse_error_cordova() { simulateCordovaEnvironment(); app.updateConfig('autoUpgradeAnonymousUsers', true); externalAuth.setUser(anonymousUser); - var cred = firebaseui.auth.idp.getAuthCredential({ + var cred = createMockCredential({ 'providerId': 'google.com', 'accessToken': 'ACCESS_TOKEN' }); @@ -2417,7 +2471,7 @@ function testFederatedSignIn_anonymousUpgrade_emailInUse_error_cordova() { simulateCordovaEnvironment(); app.updateConfig('autoUpgradeAnonymousUsers', true); externalAuth.setUser(anonymousUser); - var cred = firebaseui.auth.idp.getAuthCredential({ + var cred = createMockCredential({ 'providerId': 'google.com', 'accessToken': 'ACCESS_TOKEN' }); @@ -2560,7 +2614,8 @@ function testHandleGoogleYoloCredential_handledSuccessfully_withScopes() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var component = new firebaseui.auth.ui.page.ProviderSignIn( goog.nullFunction(), []); @@ -2590,7 +2645,8 @@ function testHandleGoogleYoloCredential_handledSuccessfully_withoutScopes() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var component = new firebaseui.auth.ui.page.ProviderSignIn( goog.nullFunction(), []); @@ -2654,7 +2710,8 @@ function testHandleGoogleYoloCredential_unhandled_withoutScopes() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var component = new firebaseui.auth.ui.page.ProviderSignIn( goog.nullFunction(), []); @@ -2696,7 +2753,8 @@ function testHandleGoogleYoloCredential_cancelled_withoutScopes() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var component = new firebaseui.auth.ui.page.ProviderSignIn( goog.nullFunction(), []); @@ -2727,7 +2785,8 @@ function testHandleGoogleYoloCredential_unsupportedCredential() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var component = new firebaseui.auth.ui.page.ProviderSignIn( goog.nullFunction(), []); @@ -2760,7 +2819,8 @@ function testHandleGoogleYoloCredential_upgradeAnonymous_noScopes() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var expectedCredential = firebase.auth.GoogleAuthProvider.credential( googleYoloIdTokenCredential.idToken); @@ -2820,7 +2880,8 @@ function testHandleGoogleYoloCredential_upgradeAnonymous_credentialInUse() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var expectedCredential = firebase.auth.GoogleAuthProvider.credential( googleYoloIdTokenCredential.idToken); @@ -2876,7 +2937,8 @@ function testHandleGoogleYoloCredential_upgradeAnonymous_fedEmailInUse() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var expectedCredential = firebase.auth.GoogleAuthProvider.credential( googleYoloIdTokenCredential.idToken); @@ -2943,7 +3005,8 @@ function testHandleGoogleYoloCredential_upgradeAnonymous_passEmailInUse() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var expectedCredential = firebase.auth.GoogleAuthProvider.credential( googleYoloIdTokenCredential.idToken); @@ -3020,7 +3083,8 @@ function testHandleGoogleYoloCredential_upgradeAnonymous_withScopes() { 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' }, 'facebook.com', 'password', 'phone'], - 'credentialHelper': firebaseui.auth.CredentialHelper.GOOGLE_YOLO + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO }); var component = new firebaseui.auth.ui.page.ProviderSignIn( goog.nullFunction(), []); diff --git a/javascript/widgets/handler/emailmismatch.js b/javascript/widgets/handler/emailmismatch.js index fd899a1c..af0313f2 100644 --- a/javascript/widgets/handler/emailmismatch.js +++ b/javascript/widgets/handler/emailmismatch.js @@ -60,7 +60,8 @@ goog.require('firebaseui.auth.widget.handler.common'); * @param {!firebaseui.auth.AuthUI} app The current Firebase UI instance whose * configuration is used. * @param {!Element} container The container DOM element. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result object. + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result object. */ firebaseui.auth.widget.handler.handleEmailMismatch = function( app, container, authResult) { @@ -104,7 +105,8 @@ firebaseui.auth.widget.handler.handleEmailMismatch = function( * @param {!firebaseui.auth.AuthUI} app The current Firebase UI instance whose * configuration is used. * @param {!firebaseui.auth.ui.page.Base} component The current UI component. - * @param {!firebaseui.auth.AuthResult} authResult The Auth result object. + * @param {!firebaseui.auth.widget.Config.AuthResult} authResult The Auth + * result object. * @private */ firebaseui.auth.widget.handler.handleEmailMismatchContinue_ = diff --git a/javascript/widgets/handler/federatedredirect.js b/javascript/widgets/handler/federatedredirect.js index 73719e29..dcd5afb2 100644 --- a/javascript/widgets/handler/federatedredirect.js +++ b/javascript/widgets/handler/federatedredirect.js @@ -35,12 +35,14 @@ goog.require('goog.asserts'); * @param {?firebaseui.auth.AuthUI} app The current Firebase UI instance whose * configuration is used. * @param {?Element} container The container DOM element. + * @param {string=} email The optional prefilled email to pass to IdPs. * @throws {!goog.asserts.AssertionError} Thrown if there is more than one * provider. */ firebaseui.auth.widget.handler.handleFederatedRedirect = function( app, - container) { + container, + email = undefined) { var component = new firebaseui.auth.ui.page.Blank(); component.render(container); // Set current UI component. @@ -54,7 +56,8 @@ firebaseui.auth.widget.handler.handleFederatedRedirect = function( firebaseui.auth.widget.handler.common.federatedSignIn( /** @type {!firebaseui.auth.AuthUI} */ (app), component, - providerId); + providerId, + email); }; diff --git a/javascript/widgets/handler/federatedredirect_test.js b/javascript/widgets/handler/federatedredirect_test.js index 19f262f7..fa731c08 100644 --- a/javascript/widgets/handler/federatedredirect_test.js +++ b/javascript/widgets/handler/federatedredirect_test.js @@ -55,6 +55,64 @@ function testHandleFederatedRedirect() { } +function testHandleFederatedSignIn_signInHint() { + // Test handleFederatedRedirect when signInHint is passed in start. + const prefilledEmail = 'user@example.com'; + const expectedProvider = firebaseui.auth.idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({'login_hint': prefilledEmail}); + + app.startWithSignInHint( + container, + { + signInOptions: ['google.com'], + credentialHelper: 'none', + immediateFederatedRedirect: true, + }, + { + emailHint: prefilledEmail, + }); + + assertBlankPage(); + testAuth.assertSignInWithRedirect([expectedProvider]); + return testAuth.process().then(() => { + // Clean up the AuthUI instance. + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }); +} + + +function testHandleFederatedRedirect_prefilledEmail() { + // Test handleFederatedRedirect with prefilled email. + app.setConfig({ + 'immediateFederatedRedirect': true, + 'signInOptions': [{ + 'provider': 'google.com', + 'scopes': ['googl1', 'googl2'], + 'customParameters': {'prompt': 'select_account'} + }], + 'signInFlow':'redirect' + }); + const prefilledEmail = 'user@example.com'; + const expectedProvider = firebaseui.auth.idp.getAuthProvider('google.com'); + expectedProvider.addScope('googl1'); + expectedProvider.addScope('googl2'); + expectedProvider.setCustomParameters({ + 'prompt': 'select_account', + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + // Call the handler and expect a blank page and the redirect to start. + firebaseui.auth.widget.handler.handleFederatedRedirect( + app, container, prefilledEmail); + assertBlankPage(); + testAuth.assertSignInWithRedirect([expectedProvider]); + return testAuth.process(); +} + + function testHandleFederatedRedirect_error() { // Add additional scopes to test they are properly passed to the redirect // method. diff --git a/javascript/widgets/handler/federatedsignin.js b/javascript/widgets/handler/federatedsignin.js index 27134b8d..405f30d5 100644 --- a/javascript/widgets/handler/federatedsignin.js +++ b/javascript/widgets/handler/federatedsignin.js @@ -26,7 +26,6 @@ goog.require('firebaseui.auth.widget.handler'); goog.require('firebaseui.auth.widget.handler.common'); - /** * Handles the case where the user had previously signed in with a federated IdP * but is now trying to sign in with email/password. diff --git a/javascript/widgets/handler/federatedsignin_test.js b/javascript/widgets/handler/federatedsignin_test.js index dd8a657c..875e09d8 100644 --- a/javascript/widgets/handler/federatedsignin_test.js +++ b/javascript/widgets/handler/federatedsignin_test.js @@ -34,6 +34,7 @@ goog.require('firebaseui.auth.widget.handler.handleProviderSignIn'); goog.require('firebaseui.auth.widget.handler.testHelper'); + function testHandleFederatedSignIn() { // Add additional scopes to test they are properly passed to sign-in method. var expectedProvider = getExpectedProviderWithScopes({ diff --git a/javascript/widgets/handler/handler.js b/javascript/widgets/handler/handler.js index 7d2e451a..4829fe2e 100644 --- a/javascript/widgets/handler/handler.js +++ b/javascript/widgets/handler/handler.js @@ -25,7 +25,6 @@ goog.require('goog.asserts'); goog.forwardDeclare('firebaseui.auth.AuthUI'); - /** * Handler names. * @enum {string} @@ -56,6 +55,7 @@ firebaseui.auth.widget.HandlerName = { PASSWORD_RESET: 'passwordReset', EMAIL_MISMATCH: 'emailMismatch', PROVIDER_SIGN_IN: 'providerSignIn', + PREFILLED_EMAIL_SIGN_IN: 'prefilledEmailSignIn', UNSUPPORTED_PROVIDER: 'unsupportedProvider' }; diff --git a/javascript/widgets/handler/handler_test.js b/javascript/widgets/handler/handler_test.js index a194130b..df452ae2 100644 --- a/javascript/widgets/handler/handler_test.js +++ b/javascript/widgets/handler/handler_test.js @@ -126,6 +126,9 @@ function testHandlerRegistration() { assertEquals( firebaseui.auth.widget.handler.handleProviderSignIn, firebaseui.auth.widget.handlers_[HandlerName.PROVIDER_SIGN_IN]); + assertEquals( + firebaseui.auth.widget.handler.handlePrefilledEmailSignIn, + firebaseui.auth.widget.handlers_[HandlerName.PREFILLED_EMAIL_SIGN_IN]); assertEquals( firebaseui.auth.widget.handler.handleEmailLinkSignInCallback, firebaseui.auth.widget.handlers_[ diff --git a/javascript/widgets/handler/passwordlinking.js b/javascript/widgets/handler/passwordlinking.js index 72de1967..af0a214b 100644 --- a/javascript/widgets/handler/passwordlinking.js +++ b/javascript/widgets/handler/passwordlinking.js @@ -114,7 +114,7 @@ firebaseui.auth.widget.handler.onPasswordLinkingSubmit_ = pendingCredential) .then(function(linkedUserCredential) { var linkedAuthResult = - /** @type {!firebaseui.auth.AuthResult} */ ({ + /** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ 'user': linkedUserCredential['user'], 'credential': pendingCredential, 'operationType': linkedUserCredential['operationType'], diff --git a/javascript/widgets/handler/passwordsignup.js b/javascript/widgets/handler/passwordsignup.js index a4f93a64..70e8e8ef 100644 --- a/javascript/widgets/handler/passwordsignup.js +++ b/javascript/widgets/handler/passwordsignup.js @@ -115,13 +115,13 @@ firebaseui.auth.widget.handler.onSignUpSubmit_ = function(app, component) { ), [email, password], function(userCredential) { - var authResult = /** @type {!firebaseui.auth.AuthResult} */ ({ + var authResult = (/** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ 'user': userCredential['user'], // Password credential is needed for signing in on external instance. 'credential': emailPassCred, 'operationType': userCredential['operationType'], 'additionalUserInfo': userCredential['additionalUserInfo'] - }); + })); if (requireDisplayName) { // Sign up successful. We can now set the name. var p = userCredential['user'].updateProfile({'displayName': name}) diff --git a/javascript/widgets/handler/phonesigninfinish.js b/javascript/widgets/handler/phonesigninfinish.js index bee1ffcd..94149c2e 100644 --- a/javascript/widgets/handler/phonesigninfinish.js +++ b/javascript/widgets/handler/phonesigninfinish.js @@ -149,14 +149,15 @@ firebaseui.auth.widget.handler.onPhoneSignInFinishSubmit_ = function( // Dismiss dialog and dispose of component before completing sign-in. component.dismissDialog(); component.dispose(); - var authResult = /** @type {!firebaseui.auth.AuthResult} */ ({ - // User already signed on external instance. - 'user': app.getExternalAuth().currentUser, - // Phone Auth operations do not return a credential. - 'credential': null, - 'operationType': userCredential['operationType'], - 'additionalUserInfo': userCredential['additionalUserInfo'] - }); + var authResult = ( + /** @type {!firebaseui.auth.widget.Config.AuthResult} */ ({ + // User already signed on external instance. + 'user': app.getExternalAuth().currentUser, + // Phone Auth operations do not return a credential. + 'credential': null, + 'operationType': userCredential['operationType'], + 'additionalUserInfo': userCredential['additionalUserInfo'] + })); firebaseui.auth.widget.handler.common.setLoggedInWithAuthResult( app, component, authResult, true); }, firebaseui.auth.widget.handler.CODE_SUCCESS_DIALOG_DELAY); diff --git a/javascript/widgets/handler/prefilledemailsignin.js b/javascript/widgets/handler/prefilledemailsignin.js new file mode 100644 index 00000000..8cc52287 --- /dev/null +++ b/javascript/widgets/handler/prefilledemailsignin.js @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Prefilled email sign in handler. + */ + +goog.provide('firebaseui.auth.widget.handler.handlePrefilledEmailSignIn'); + +goog.require('firebaseui.auth.ui.page.Blank'); +goog.require('firebaseui.auth.widget.Handler'); +goog.require('firebaseui.auth.widget.HandlerName'); +goog.require('firebaseui.auth.widget.handler'); +goog.require('firebaseui.auth.widget.handler.common'); + +/** + * Handles sign-in with prefilled email flow. It's triggered when an email hint + * is provided for sign-in. In this case, the sign-in screen is skipped. + * + * @param {?firebaseui.auth.AuthUI} app The current Firebase UI instance whose + * configuration is used. + * @param {?Element} container The container DOM element. + * @param {string} email The prefilled email to pass to IdPs. + */ +firebaseui.auth.widget.handler.handlePrefilledEmailSignIn = function( + app, + container, + email) { + const component = new firebaseui.auth.ui.page.Blank(); + component.render(container); + // Set current UI component. + app.setCurrentComponent(component); + app.registerPending(component.executePromiseRequest( + /** @type {function (): !goog.Promise} */ ( + goog.bind(app.getAuth().fetchSignInMethodsForEmail, app.getAuth())), + [email], + (signInMethods) => { + signInMethods = /** @type {!Array} */ (signInMethods); + component.dispose(); + const displayFullTosPpMessage = !!( + firebaseui.auth.widget.handler.common.isPasswordProviderOnly(app) && + app.getSignInEmailHint()); + firebaseui.auth.widget.handler.common + .handleSignInFetchSignInMethodsForEmail( + app, + container, + signInMethods, + email, + undefined, + undefined, + displayFullTosPpMessage); + }, + (error) => { + // The email provided could be an invalid one or some other error + // could occur. + const errorMessage = + firebaseui.auth.widget.handler.common.getErrorMessage( + error); + component.dispose(); + firebaseui.auth.widget.handler.handle( + firebaseui.auth.widget.HandlerName.SIGN_IN, + app, + container, + email, + errorMessage); + } + )); +}; + +// Register handler. +firebaseui.auth.widget.handler.register( + firebaseui.auth.widget.HandlerName.PREFILLED_EMAIL_SIGN_IN, + /** @type {!firebaseui.auth.widget.Handler} */ + (firebaseui.auth.widget.handler.handlePrefilledEmailSignIn)); diff --git a/javascript/widgets/handler/prefilledemailsignin_test.js b/javascript/widgets/handler/prefilledemailsignin_test.js new file mode 100644 index 00000000..1a1bbd27 --- /dev/null +++ b/javascript/widgets/handler/prefilledemailsignin_test.js @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Test for prefilled email sign in handler. + */ + +goog.provide('firebaseui.auth.widget.handler.PrefilledEmailSignInTest'); +goog.setTestOnly('firebaseui.auth.widget.handler.PrefilledEmailSignInTest'); + +goog.require('firebaseui.auth.widget.handler'); +goog.require('firebaseui.auth.widget.handler.common'); +goog.require('firebaseui.auth.widget.handler.handlePrefilledEmailSignIn'); +/** @suppress {extraRequire} */ +goog.require('firebaseui.auth.widget.handler.testHelper'); + + +function testHandlePrefilledEmailSignIn_paaswordSignUp() { + // Test password sign-up with prefilled email flow. + const prefilledEmail = 'user@example.com'; + firebaseui.auth.widget.handler.handlePrefilledEmailSignIn( + app, container, prefilledEmail); + + assertBlankPage(); + testAuth.assertFetchSignInMethodsForEmail( + [prefilledEmail], + []); + return testAuth.process().then(() => { + // Password sign-up page should be shown. + assertPasswordSignUpPage(); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + assertTosPpFooter(tosCallback, 'http://localhost/privacy_policy'); + return testAuth.process(); + }); +} + + +function testHandlePrefilledEmailSignIn_passwordSignIn() { + // Test password sign-in with prefilled email flow. + const prefilledEmail = 'user@example.com'; + firebaseui.auth.widget.handler.handlePrefilledEmailSignIn( + app, container, prefilledEmail); + + assertBlankPage(); + testAuth.assertFetchSignInMethodsForEmail( + [prefilledEmail], + ['password']); + return testAuth.process().then(() => { + // Password sign-in page should be shown. + assertPasswordSignInPage(); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + assertTosPpFooter(tosCallback, 'http://localhost/privacy_policy'); + return testAuth.process(); + }); +} + + +function testHandlePrefilledEmailSignIn_federatedSignIn() { + // Test federated sign-in with prefilled email flow. + const prefilledEmail = 'user@example.com'; + firebaseui.auth.widget.handler.handlePrefilledEmailSignIn( + app, container, prefilledEmail); + + assertBlankPage(); + testAuth.assertFetchSignInMethodsForEmail( + [prefilledEmail], + ['google.com']); + return testAuth.process().then(() => { + // Federated linking page should be shown. + assertFederatedLinkingPage(); + assertTosPpFooter(tosCallback, 'http://localhost/privacy_policy'); + return testAuth.process(); + }); +} + + +function testHandlePrefilledEmailSignIn_error() { + // Test prefilled email error flow. + const prefilledEmail = 'user@example.com'; + firebaseui.auth.widget.handler.handlePrefilledEmailSignIn( + app, container, prefilledEmail); + + assertBlankPage(); + testAuth.assertFetchSignInMethodsForEmail( + [prefilledEmail], + null, + internalError); + return testAuth.process().then(() => { + // Sign in page should be displayed with error message. + assertSignInPage(); + // On error, expect the info bar to show a message. + assertInfoBarMessage( + firebaseui.auth.widget.handler.common.getErrorMessage(internalError)); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + return testAuth.process(); + }); +} + + +function testHandleSignIn_signInHint() { + // Test handleSignIn with signInHint. + const prefilledEmail = 'user@example.com'; + app.startWithSignInHint( + container, + { + signInOptions: ['password'], + credentialHelper: 'none', + }, + { + emailHint: prefilledEmail, + }); + + assertBlankPage(); + testAuth.assertFetchSignInMethodsForEmail( + [prefilledEmail], + ['password']); + return testAuth.process().then(() => { + // Password sign-in page should be shown. + assertPasswordSignInPage(); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + assertTosPpFullMessage(tosCallback, 'http://localhost/privacy_policy'); + // Clean up the AuthUI instance. + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }); +} diff --git a/javascript/widgets/handler/providersignin.js b/javascript/widgets/handler/providersignin.js index 0427211b..6de1de33 100644 --- a/javascript/widgets/handler/providersignin.js +++ b/javascript/widgets/handler/providersignin.js @@ -18,8 +18,8 @@ goog.provide('firebaseui.auth.widget.handler.handleProviderSignIn'); -goog.require('firebaseui.auth.AnonymousAuthProvider'); goog.require('firebaseui.auth.ui.page.ProviderSignIn'); +goog.require('firebaseui.auth.widget.Config'); goog.require('firebaseui.auth.widget.Handler'); goog.require('firebaseui.auth.widget.HandlerName'); goog.require('firebaseui.auth.widget.handler'); @@ -29,15 +29,17 @@ goog.require('firebaseui.auth.widget.handler.common'); /** * Handles provider sign in. * - * @param {firebaseui.auth.AuthUI} app The current Firebase UI instance whose + * @param {?firebaseui.auth.AuthUI} app The current Firebase UI instance whose * configuration is used. - * @param {Element} container The container DOM element. - * @param {string=} opt_infoBarMessage The message to show on info bar. + * @param {?Element} container The container DOM element. + * @param {string=} infoBarMessage The optional message to show on info bar. + * @param {string=} email The optional email to prefill. */ firebaseui.auth.widget.handler.handleProviderSignIn = function( app, container, - opt_infoBarMessage) { + infoBarMessage = undefined, + email = undefined) { var component = new firebaseui.auth.ui.page.ProviderSignIn( function(providerId) { // TODO: Consider deleting pending credentials on new sign in. @@ -46,7 +48,7 @@ firebaseui.auth.widget.handler.handleProviderSignIn = function( component.dispose(); // Handle sign in with email. firebaseui.auth.widget.handler.common.handleSignInWithEmail( - app, container); + app, container, email); } else if (providerId == firebase.auth.PhoneAuthProvider.PROVIDER_ID) { // User clicks sign in with phone number button. @@ -56,7 +58,7 @@ firebaseui.auth.widget.handler.handleProviderSignIn = function( firebaseui.auth.widget.HandlerName.PHONE_SIGN_IN_START, app, container); } else if (providerId == - firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID) { + firebaseui.auth.widget.Config.ANONYMOUS_PROVIDER_ID) { // User clicks continue as guest button. firebaseui.auth.widget.handler.common.handleSignInAnonymously( /** @type {!firebaseui.auth.AuthUI} */ (app), component); @@ -65,7 +67,8 @@ firebaseui.auth.widget.handler.handleProviderSignIn = function( firebaseui.auth.widget.handler.common.federatedSignIn( /** @type {!firebaseui.auth.AuthUI} */ (app), component, - providerId); + providerId, + email); } // Cancel One-Tap on any button click. app.cancelOneTapSignIn(); @@ -77,8 +80,8 @@ firebaseui.auth.widget.handler.handleProviderSignIn = function( // Set current UI component. app.setCurrentComponent(component); // Show info bar if necessary. - if (opt_infoBarMessage) { - component.showInfoBar(opt_infoBarMessage); + if (infoBarMessage) { + component.showInfoBar(infoBarMessage); } // Show One-Tap UI if available. app.showOneTapSignIn( diff --git a/javascript/widgets/handler/providersignin_test.js b/javascript/widgets/handler/providersignin_test.js index f14229c0..23e65fbb 100644 --- a/javascript/widgets/handler/providersignin_test.js +++ b/javascript/widgets/handler/providersignin_test.js @@ -21,7 +21,6 @@ goog.setTestOnly('firebaseui.auth.widget.handler.ProviderSignInTest'); goog.require('firebaseui.auth.AuthUI'); goog.require('firebaseui.auth.AuthUIError'); -goog.require('firebaseui.auth.CredentialHelper'); goog.require('firebaseui.auth.PendingEmailCredential'); goog.require('firebaseui.auth.acClient'); goog.require('firebaseui.auth.idp'); @@ -64,18 +63,20 @@ var signInOptions; /** * Sets up the provider sign-in page and verifies it was rendered correctly. * @param {!firebaseui.auth.widget.Config.SignInFlow} flow The sign-in flow. - * @param {boolean=} opt_ignoreConfig Whether to ignore config setting assuming + * @param {boolean=} ignoreConfig Whether to ignore config setting assuming * it was set externally. - * @param {boolean=} opt_enableOneTap Whether One-Tap sign-in is enabled. - * @param {boolean=} opt_disableAdditionalScopes Whether to disable additional + * @param {boolean=} enableOneTap Whether One-Tap sign-in is enabled. + * @param {boolean=} disableAdditionalScopes Whether to disable additional * scopes. + * @param {string=} email The optional email to prefill. */ function setupProviderSignInPage( - flow, opt_ignoreConfig, opt_enableOneTap, opt_disableAdditionalScopes) { + flow, ignoreConfig = false, enableOneTap = false, + disableAdditionalScopes = false, email = undefined) { // Test provider sign-in handler. signInOptions = [{ 'provider': 'google.com', - 'scopes': !!opt_disableAdditionalScopes ? [] : ['googl1', 'googl2'], + 'scopes': !!disableAdditionalScopes ? [] : ['googl1', 'googl2'], 'customParameters': {'prompt': 'select_account'}, 'authMethod': 'https://accounts.google.com', 'clientId': '1234567890.apps.googleusercontent.com' @@ -87,18 +88,19 @@ function setupProviderSignInPage( 'scopes': ['scope1', 'scope2'], 'customParameters': {'param': 'value'}, }]; - if (!opt_ignoreConfig) { + if (!ignoreConfig) { app.setConfig({ 'signInOptions': signInOptions, 'signInFlow': flow, - 'credentialHelper': !!opt_enableOneTap ? - firebaseui.auth.CredentialHelper.GOOGLE_YOLO : - firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM + 'credentialHelper': !!enableOneTap ? + firebaseui.auth.widget.Config.CredentialHelper.GOOGLE_YOLO : + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM }); } // Set sign-in options. app.updateConfig('signInOptions', signInOptions); - firebaseui.auth.widget.handler.handleProviderSignIn(app, container); + firebaseui.auth.widget.handler.handleProviderSignIn( + app, container, undefined, email); assertProviderSignInPage(); buttons = getIdpButtons(); assertEquals(signInOptions.length, buttons.length); @@ -718,6 +720,62 @@ function testHandleProviderSignIn_popup_success() { } +function testHandleProviderSignIn_popup_success_prefilledEmail() { + // Test successful provider sign-in with popup with prefilled email. + // Add additional scopes to test that they are properly passed to the sign-in + // method. + const prefilledEmail = federatedAccount.getEmail(); + const expectedProvider = firebaseui.auth.idp.getAuthProvider('google.com'); + expectedProvider.addScope('googl1'); + expectedProvider.addScope('googl2'); + expectedProvider.setCustomParameters({ + 'prompt': 'select_account', + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + // Render the provider sign-in page and confirm it was rendered correctly. + setupProviderSignInPage('popup', false, false, false, prefilledEmail); + // Click the first button, which is Google IdP. + goog.testing.events.fireClickSequence(buttons[0]); + + // User should be signed in. + testAuth.setUser({ + 'email': federatedAccount.getEmail(), + 'displayName': federatedAccount.getDisplayName() + }); + const cred = { + 'providerId': 'google.com', + 'accessToken': 'ACCESS_TOKEN' + }; + // Sign in with popup triggered. + testAuth.assertSignInWithPopup( + [expectedProvider], + { + 'user': testAuth.currentUser, + 'credential': cred + }); + // Sign out from internal instance and then sign in with passed credential to + // external instance. + return testAuth.process().then(function() { + testAuth.assertSignOut([]); + return testAuth.process(); + }).then(function() { + externalAuth.assertUpdateCurrentUser( + [testAuth.currentUser], + function() { + externalAuth.setUser(testAuth.currentUser); + }); + return externalAuth.process(); + }).then(function() { + // Pending credential and email should be cleared from storage. + assertFalse(firebaseui.auth.storage.hasPendingEmailCredential( + app.getAppId())); + // User should be redirected to success URL. + testUtil.assertGoTo('http://localhost/home'); + }); +} + + function testHandleProviderSignIn_popup_success_oidc() { // Test successful OIDC provider sign-in with popup. // Add additional scopes to test that they are properly passed to the sign-in @@ -1086,10 +1144,12 @@ function testHandleProviderSignIn_reset() { // Test reset after provider sign-in handler called. firebaseui.auth.widget.handler.handleProviderSignIn(app, container); assertProviderSignInPage(); + testAuth.assertSignOut([]); // Reset current rendered widget page. app.reset(); // Container should be cleared. assertComponentDisposed(); + return testAuth.process(); } @@ -1265,7 +1325,7 @@ function testHandleProviderSignIn_acCallbacks_unavailable() { // Set accountchooser.com callbacks. app.setConfig({ 'signInOptions': signInOptions, - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, 'callbacks': { 'accountChooserResult': accountChooserResultCallback, 'accountChooserInvoked': accountChooserInvokedCallback @@ -1660,6 +1720,27 @@ function testHandleProviderSignIn_signInWithIdp() { } +function testHandleProviderSignIn_signInWithIdp_redirect_prefilledEmail() { + // Test provider sign-in handler when sign in with IdP is clicked with + // prefilled email. + const prefilledEmail = 'user@example.com'; + // Add additional scopes to test that they are properly passed to the sign-in + // method. + const expectedProvider = getExpectedProviderWithScopes({ + 'prompt': 'select_account', + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + // Render the provider sign-in page and confirm it was rendered correctly. + setupProviderSignInPage('redirect', false, false, false, prefilledEmail); + // Click the first button, which is sign in with Google button. + goog.testing.events.fireClickSequence(buttons[0]); + + testAuth.assertSignInWithRedirect([expectedProvider]); + return testAuth.process(); +} + + function testHandleProviderSignIn_signInWithIdp_cordova() { // Test provider sign-in handler when sign in with IdP clicked in a Cordova // environment. @@ -1775,7 +1856,7 @@ function testHandleProviderSignIn_signInWithEmail_acDisabled() { // Disable any credential helper. app.setConfig({ 'signInOptions': signInOptions, - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, 'callbacks': { 'uiShown': uiShownCallback } @@ -1795,6 +1876,183 @@ function testHandleProviderSignIn_signInWithEmail_acDisabled() { } +function testHandleProviderSignIn_signInWithEmail_prefilledEmail() { + // Test provider sign-in handler when sign in with email is clicked with + // prefilled email. + // Disable any credential helper. + app.setConfig({ + 'signInOptions': signInOptions, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, + }); + const prefilledEmail = 'user@example.com'; + // Render provider sign-in using previous config. + setupProviderSignInPage('redirect', true, false, false, prefilledEmail); + // Click the third button, which is sign in with email button. + goog.testing.events.fireClickSequence(buttons[2]); + + testAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + []); + return testAuth.process().then(() => { + // Password sign-up page should be shown. + assertPasswordSignUpPage(); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + return testAuth.process(); + }); +} + + +function testHandleProviderSignIn_signInHint_signInWithEmail() { + // Test sign in with email in provider sign-in handler with signInHint. + const prefilledEmail = 'user@example.com'; + app.startWithSignInHint( + container, + { + signInOptions: ['google.com', 'password'], + credentialHelper: firebaseui.auth.widget.Config.CredentialHelper.NONE, + }, + { + emailHint: prefilledEmail, + }); + + assertProviderSignInPage(); + const buttons = getIdpButtons(); + assertEquals(2, buttons.length); + assertEquals('google.com', goog.dom.dataset.get(buttons[0], 'providerId')); + assertEquals('password', goog.dom.dataset.get(buttons[1], 'providerId')); + + // Click the sign in with email button. + goog.testing.events.fireClickSequence(buttons[1]); + + testAuth.assertFetchSignInMethodsForEmail( + ['user@example.com'], + ['password']); + return testAuth.process().then(() => { + // Password sign-in page should be shown. + assertPasswordSignInPage(); + // The prefilled email should be populated in the email entry. + assertEquals(prefilledEmail, getEmailElement().value); + // Clean up the AuthUI instance. + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }); +} + + +function testHandleProviderSignIn_signInHint_signInWithIdp_popup() { + // Test sign in with IdP popup mode in provider sign-in handler with + // signInHint. + const prefilledEmail = 'user@example.com'; + const expectedProvider = firebaseui.auth.idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({ + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + app.startWithSignInHint( + container, + { + signInOptions: ['google.com', 'password'], + credentialHelper: firebaseui.auth.widget.Config.CredentialHelper.NONE, + signInFlow: 'popup', + }, + { + emailHint: prefilledEmail, + }); + + assertProviderSignInPage(); + const buttons = getIdpButtons(); + assertEquals(2, buttons.length); + assertEquals('google.com', goog.dom.dataset.get(buttons[0], 'providerId')); + assertEquals('password', goog.dom.dataset.get(buttons[1], 'providerId')); + + // Click the sign in with IdP button. + goog.testing.events.fireClickSequence(buttons[0]); + + // User should be signed in. + testAuth.setUser({ + 'email': prefilledEmail, + 'displayName': federatedAccount.getDisplayName() + }); + const cred = { + 'providerId': 'google.com', + 'accessToken': 'ACCESS_TOKEN' + }; + // Sign in with popup triggered. + testAuth.assertSignInWithPopup( + [expectedProvider], + { + 'user': testAuth.currentUser, + 'credential': cred + }); + // Sign out from internal instance and then sign in with passed credential to + // external instance. + return testAuth.process().then(function() { + testAuth.assertSignOut([]); + return testAuth.process(); + }).then(function() { + externalAuth.assertUpdateCurrentUser( + [testAuth.currentUser], + function() { + externalAuth.setUser(testAuth.currentUser); + }); + return externalAuth.process(); + }).then(function() { + // Pending credential and email should be cleared from storage. + assertFalse(firebaseui.auth.storage.hasPendingEmailCredential( + app.getAppId())); + // User should be redirected to success URL. + testUtil.assertGoTo('http://localhost/home'); + + // Clean up the AuthUI instance. + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }); +} + + +function testHandleProviderSignIn_signInHint_signInWithIdp_redirect() { + // Test sign in with IdP redirect mode in provider sign-in handler with + // signInHint. + const prefilledEmail = 'user@example.com'; + const expectedProvider = firebaseui.auth.idp.getAuthProvider('google.com'); + expectedProvider.setCustomParameters({ + // The prefilled email should be passed to IdP as login_hint. + 'login_hint': prefilledEmail, + }); + + app.startWithSignInHint( + container, + { + signInOptions: ['google.com', 'password'], + credentialHelper: firebaseui.auth.widget.Config.CredentialHelper.NONE, + }, + { + emailHint: prefilledEmail, + }); + + assertProviderSignInPage(); + const buttons = getIdpButtons(); + assertEquals(2, buttons.length); + assertEquals('google.com', goog.dom.dataset.get(buttons[0], 'providerId')); + assertEquals('password', goog.dom.dataset.get(buttons[1], 'providerId')); + + // Click the sign in with IdP button. + goog.testing.events.fireClickSequence(buttons[0]); + + testAuth.assertSignInWithRedirect([expectedProvider]); + return testAuth.process().then(() => { + // Clean up the AuthUI instance. + testAuth.assertSignOut([]); + app.delete(); + return testAuth.process(); + }); +} + + function testHandleProviderSignIn_accountChooserSelect_appChange() { // Test when selectAccountChooser is first called with a different app, // then a new app is initialized, and try select is called. The second app's @@ -1833,7 +2091,8 @@ function testHandleProviderSignIn_accountChooserSelect_appChange() { 'siteName': 'Test Site', 'popupMode': false, 'tosUrl': 'http://localhost/tos', - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM }); app.getExternalAuth().runAuthChangeHandler(); // Callback page should be rendered. @@ -1889,9 +2148,13 @@ function testHandleProviderSignIn_accountChooserSelect_appChange() { // app2. assertPasswordSignInPage(); assertTrue(firebaseui.auth.storage.isRememberAccount(app2.getAppId())); - // Uninstall internal and external auth instances. - app2.getAuth().uninstall(); - app2.getExternalAuth().uninstall(); + app2.getAuth().assertSignOut([]); + app2.delete(); + return testAuth2.process(); + }).then(function() { + // Uninstall internal auth instances. + testAuth2.uninstall(); + return testAuth.process(); }); } diff --git a/javascript/widgets/handler/signin_test.js b/javascript/widgets/handler/signin_test.js index 1b9d9be6..025e41ca 100644 --- a/javascript/widgets/handler/signin_test.js +++ b/javascript/widgets/handler/signin_test.js @@ -19,8 +19,8 @@ goog.provide('firebaseui.auth.widget.handler.SignInTest'); goog.setTestOnly('firebaseui.auth.widget.handler.SignInTest'); -goog.require('firebaseui.auth.CredentialHelper'); goog.require('firebaseui.auth.storage'); +goog.require('firebaseui.auth.widget.Config'); /** @suppress {extraRequire} Required for page navigation to work. */ goog.require('firebaseui.auth.widget.handler.handleFederatedSignIn'); goog.require('firebaseui.auth.widget.handler.handlePasswordRecovery'); @@ -123,7 +123,7 @@ function testHandleSignIn_acDisabled_emailProviderOnly() { // Simulate accountchooser.com disabled and email provider only. // No cancel button should be displayed. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.NONE, + 'credentialHelper': firebaseui.auth.widget.Config.CredentialHelper.NONE, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID], 'tosUrl': undefined, 'privacyPolicyUrl': undefined @@ -141,7 +141,8 @@ function testHandleSignIn_acEnabled_emailProviderOnly() { // Simulate accountchooser.com enabled and email provider only. // Cancel button should still be displayed. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID] }); firebaseui.auth.widget.handler.handleSignIn( @@ -157,7 +158,8 @@ function testHandleSignIn_acDisabled_multiProviders() { // Simulate accountchooser.com disabled and multiple auth providers. // Cancel button should still be displayed. app.setConfig({ - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'signInOptions': [ firebase.auth.EmailAuthProvider.PROVIDER_ID, firebase.auth.GoogleAuthProvider.PROVIDER_ID @@ -238,7 +240,8 @@ function testHandleSignIn_federatedSignIn() { function testHandleSignIn_accountChooserDisabled() { // Test that when credential helpers are disabled, remember account is not // shown and is disabled. - app.updateConfig('credentialHelper', firebaseui.auth.CredentialHelper.NONE); + app.updateConfig('credentialHelper', + firebaseui.auth.widget.Config.CredentialHelper.NONE); firebaseui.auth.widget.handler.handleSignIn(app, container); assertSignInPage(); var emailInput = getEmailElement(); diff --git a/javascript/widgets/handler/testhelper.js b/javascript/widgets/handler/testhelper.js index 05fb1532..b6e33a48 100644 --- a/javascript/widgets/handler/testhelper.js +++ b/javascript/widgets/handler/testhelper.js @@ -24,11 +24,9 @@ goog.setTestOnly('firebaseui.auth.widget.handler.testHelper'); goog.require('firebaseui.auth.Account'); goog.require('firebaseui.auth.ActionCodeUrlBuilder'); goog.require('firebaseui.auth.AuthUI'); -goog.require('firebaseui.auth.CredentialHelper'); goog.require('firebaseui.auth.EventDispatcher'); goog.require('firebaseui.auth.OAuthResponse'); goog.require('firebaseui.auth.PendingEmailCredential'); -goog.require('firebaseui.auth.callback.signInSuccess'); goog.require('firebaseui.auth.idp'); goog.require('firebaseui.auth.soy2.strings'); goog.require('firebaseui.auth.storage'); @@ -378,7 +376,8 @@ function setUp() { 'popupMode': false, 'tosUrl': tosCallback, 'privacyPolicyUrl': 'http://localhost/privacy_policy', - 'credentialHelper': firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM, + 'credentialHelper': + firebaseui.auth.widget.Config.CredentialHelper.ACCOUNT_CHOOSER_COM, 'callbacks': { 'signInFailure': signInFailureCallback }, @@ -1343,8 +1342,8 @@ function assertPage_(container, idClass) { * @param {boolean} redirect The return status of the success callback. * @param {boolean=} opt_manualRedirect Whether the developer manually * redirects to redirectUrl. - * @return {!firebaseui.auth.callback.signInSuccess} sign in success callback - * function. + * @return {!firebaseui.auth.widget.Config.signInSuccessCallback} sign in + * success callback function. */ function signInSuccessCallback(redirect, opt_manualRedirect) { return function(user, credential, redirectUrl) { @@ -1378,8 +1377,8 @@ function assertSignInSuccessCallbackInvoked( * @param {boolean} redirect The return status of the success callback. * @param {boolean=} opt_manualRedirect Whether the developer manually * redirects to redirectUrl. - * @return {!firebaseui.auth.callback.signInSuccessWithAuthResult} sign in - * success with auth result callback function. + * @return {!firebaseui.auth.widget.Config.signInSuccessWithAuthResultCallback} + * sign in success with auth result callback function. */ function signInSuccessWithAuthResultCallback(redirect, opt_manualRedirect) { return function(authResult, redirectUrl) { @@ -1501,8 +1500,11 @@ function assertResendCountdown(timeRemaining) { function assertSignInFailure(expectedError) { // Confirm signInFailure callback triggered with expected argument. assertEquals(1, signInFailureCallback.getCallCount()); + // Plain assertObjectEquals cannot be used as Internet Explorer adds the + // stack trace as a property of the object. assertObjectEquals( - expectedError, signInFailureCallback.getLastCall().getArgument(0)); + expectedError.toPlainObject(), + signInFailureCallback.getLastCall().getArgument(0).toPlainObject()); // Sign in success should not be called. assertUndefined(signInCallbackUser); } diff --git a/javascript/widgets/handler/unsupportedprovider.js b/javascript/widgets/handler/unsupportedprovider.js index 5b13a5e0..bb79eb63 100644 --- a/javascript/widgets/handler/unsupportedprovider.js +++ b/javascript/widgets/handler/unsupportedprovider.js @@ -67,4 +67,3 @@ firebaseui.auth.widget.handler.register( firebaseui.auth.widget.HandlerName.UNSUPPORTED_PROVIDER, /** @type {!firebaseui.auth.widget.Handler} */ (firebaseui.auth.widget.handler.handleUnsupportedProvider)); - \ No newline at end of file diff --git a/javascript/widgets/uihandlerconfig.js b/javascript/widgets/uihandlerconfig.js new file mode 100644 index 00000000..f14666ac --- /dev/null +++ b/javascript/widgets/uihandlerconfig.js @@ -0,0 +1,390 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @fileoverview Defines all configurations used by FirebaseUI handler. */ + +goog.module('firebaseui.auth.widget.UiHandlerConfig'); +goog.module.declareLegacyNamespace(); + +const AuthConfig = goog.require('firebaseui.auth.Config'); +const log = goog.require('firebaseui.auth.log'); +const util = goog.require('firebaseui.auth.util'); + + +/** The UI handler configuration settings. */ +class UiHandlerConfig { + constructor(config) { + /** @const @private {!AuthConfig} The AuthUI config object. */ + this.config_ = new AuthConfig(); + // The Auth domain of the Auth instance. + this.config_.define('authDomain'); + // The display mode for tenant selection, default to 'optionFirst'. + this.config_.define( + 'displayMode', UiHandlerConfig.DisplayMode.OPTION_FIRST); + // The UI configuration for each tenant. + this.config_.define('tenants'); + // The callbacks configuration. + this.config_.define('callbacks'); + // The terms of service URL configuration for tenant selection UI. + this.config_.define('tosUrl'); + // The privacy policy URL configuration for tenant selection UI. + this.config_.define('privacyPolicyUrl'); + this.setConfig(config); + } + + /** + * Validates and sets the plain configuration settting object. + * @param {!Object} config The configuration setting object. + */ + setConfig(config) { + for (let name in config) { + if (config.hasOwnProperty(name)) { + try { + this.config_.update(name, config[name]); + } catch (e) { + log.error(`Invalid config: "${name}"`); + } + } + } + } + + /** + * Returns the project-level Auth domain. Throws error if not provided + * in the configuration. + * @return {string} The Auth domain. + */ + getAuthDomain() { + const authDomain = this.config_.get('authDomain'); + if (!authDomain) { + throw new Error('Invalid project configuration: authDomain is required!'); + } + return /** @type {string} */ (authDomain); + } + + /** + * Returns the display mode for the tenant selction flow. Default to + * option first mode. + * @return {!UiHandlerConfig.DisplayMode} The display mode. + */ + getDisplayMode() { + const displayMode = this.config_.get('displayMode'); + // Make sure the display mode is valid. + for (let key in UiHandlerConfig.DisplayMode) { + if (UiHandlerConfig.DisplayMode[key] === displayMode) { + // Return valid flow. + return UiHandlerConfig.DisplayMode[key]; + } + } + // Default to option first mode. + return UiHandlerConfig.DisplayMode.OPTION_FIRST; + } + + /** + * Returns the callback configuration object. + * @return {!Object} The callback configuration. + * @private + */ + getCallbacks_() { + return /** @type {!Object} */ (this.config_.get('callbacks') || {}); + } + + /** + * Returns the signInUiShown callback. It is triggered when the sign-in UI + * is shown. The tenant ID is passed. + * @return {?function(?string)} The signInUiShown callback, null if not + * available. + */ + getSignInUiShownCallback() { + return /** @type {?function(?string)} */ ( + this.getCallbacks_()['signInUiShown'] || null); + } + + /** + * Returns the selectTenantUiShown callback. It is triggered when the + * select provider UI is shown. + * @return {?function()} The selectTenantUiShown callback, null if not + * available. + */ + getSelectTenantUiShownCallback() { + return /** @type {?function()} */ ( + this.getCallbacks_()['selectTenantUiShown'] || null); + } + + /** + * Returns the selectTenantUiHidden callback. It is triggered when the + * select provider UI is hidden. + * @return {?function()} The selectTenantUiHidden callback, null if not + * available. + */ + getSelectTenantUiHiddenCallback() { + return /** @type {?function()} */ ( + this.getCallbacks_()['selectTenantUiHidden'] || null); + } + + /** + * Returns the beforeSignInSuccess callback. It is triggered before the user + * signs in successfully. The user signing in is passed. + * @return {?function(!firebase.User)} The beforeSignInSuccess callback, + * null if not available. + */ + getBeforeSignInSuccessCallback() { + return /** @type {?function(!firebase.User)} */ ( + this.getCallbacks_()['beforeSignInSuccess'] || null); + } + + /** + * Returns the terms of service callback. Returns null if either terms of + * service or privacy policy is not configured. + * @return {?function()} The terms of service callback for the tenant + * selection UI. If URL is provided, wraps the URL with a callback + * function. + */ + getTosUrl() { + const tosUrl = this.config_.get('tosUrl') || null; + const privacyPolicyUrl = this.config_.get('privacyPolicyUrl') || null; + if (tosUrl && !privacyPolicyUrl) { + log.warning( + 'Privacy Policy URL is missing, the link will not be displayed.'); + } + if (tosUrl && privacyPolicyUrl) { + if (typeof tosUrl === 'function') { + return /** @type {function()} */ (tosUrl); + } else if (typeof tosUrl === 'string') { + return () => { + util.open( + /** @type {string} */ (tosUrl), + util.isCordovaInAppBrowserInstalled() ? + '_system' : '_blank'); + }; + } + } + return null; + } + + /** + * Returns the privacy policy callback. Returns null if either terms of + * service or privacy policy is not configured. + * @return {?function()} The privacy policy callback for the tenant selection + * UI. If URL is provided, wraps the URL with a callback function. + */ + getPrivacyPolicyUrl() { + const tosUrl = this.config_.get('tosUrl') || null; + const privacyPolicyUrl = this.config_.get('privacyPolicyUrl') || null; + if (privacyPolicyUrl && !tosUrl) { + log.warning( + 'Terms of Service URL is missing, the link will not be displayed.'); + } + if (tosUrl && privacyPolicyUrl) { + if (typeof privacyPolicyUrl === 'function') { + return /** @type {function()} */ (privacyPolicyUrl); + } else if (typeof privacyPolicyUrl === 'string') { + return () => { + util.open( + /** @type {string} */ (privacyPolicyUrl), + util.isCordovaInAppBrowserInstalled() ? + '_system' : '_blank'); + }; + } + } + return null; + } + + /** + * Validates the tenant ID or the top-level project config key. + * @param {string} tenantId The tenantId or top-level project config key. + */ + validateTenantId(tenantId) { + const uiConfigs = this.config_.get('tenants'); + if (!uiConfigs || + (!uiConfigs.hasOwnProperty(tenantId) && + !uiConfigs.hasOwnProperty( + UiHandlerConfig.ConfigKeys.DEFAULT_CONFIG_KEY))) { + throw new Error('Invalid tenant configuration!'); + } + } + + /** + * Validates the tenant ID provided and returns the config object for the + * specific tenant. + * @param {string} tenantId The tenantId or top-level project config key. + * @return {!Object} The config object of the tenant. + * @private + */ + getTenantConfig_(tenantId) { + this.validateTenantId(tenantId); + const uiConfigs = this.config_.get('tenants'); + return uiConfigs[tenantId] || + uiConfigs[UiHandlerConfig.ConfigKeys.DEFAULT_CONFIG_KEY]; + } + + /** + * Returns the providers enabled for the given tenant. If email is provided, + * only returns the providers that match with the given email. Note that if + * email is passed but no hd is available, the associated provider is + * still returned. + * @param {string} tenantId The tenant ID or project-level config key. + * @param {?string=} email The optional email used to match providers. + * @return {!Array} The list of enabled provider IDs. + */ + getProvidersForTenant(tenantId, email = undefined) { + const uiConfigs = this.config_.get('tenants'); + if (!uiConfigs) { + throw new Error('Invalid tenant configuration!'); + } + const providers = []; + const tenantConfig = + uiConfigs[tenantId] || + uiConfigs[UiHandlerConfig.ConfigKeys.DEFAULT_CONFIG_KEY]; + if (!tenantConfig) { + log.error(`Invalid tenant configuration: `+ + `${tenantId} is not configured!`); + return providers; + } + const signInOptions = tenantConfig['signInOptions']; + if (!signInOptions) { + throw new Error( + 'Invalid tenant configuration: signInOptions are invalid!'); + } + signInOptions.forEach((option) => { + if (typeof option === 'string') { + // No hd configured, treat like a match for any email. + providers.push(option); + } else if (typeof option['provider'] === 'string') { + const hd = option['hd']; + // If hd is configured, match the email with the hd. + if (hd && email) { + const regex = hd instanceof RegExp ? + hd : new RegExp('@' + hd.replace('.', '\\.') + '$'); + if (regex.test(email)) { + providers.push(option['provider']); + } + } else { + // No hd configured, treat like a match for any email. + providers.push(option['provider']); + } + } else { + log.error( + `Invalid tenant configuration: signInOption ` + + `${JSON.stringify(option)} is invalid!`); + } + }); + return providers; + } + + /** + * Returns the config object of the specific tenant for the sign-in flow. + * @param {string} tenantId The tenant ID or project-level config key. + * @param {?Array=} providerIds The optional eligible provider IDs. + * @return {!Object} The sign in UI config object for the tenant. + */ + getSignInConfigForTenant(tenantId, providerIds = undefined) { + const signInConfig = this.filterTenantConfig_( + tenantId, + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS); + // If eligible providers are provided, only returns eligible providers in + // the sign in options. + const signInOptions = signInConfig['signInOptions']; + if (signInOptions && providerIds) { + const eligibleOptions = signInOptions.filter((option) => { + if (typeof option === 'string') { + return providerIds.includes(option); + } else { + return providerIds.includes(option['provider']); + } + }); + signInConfig['signInOptions'] = eligibleOptions; + } + return signInConfig; + } + + /** + * Returns the tenant selection button config object for the option first + * tenant selection screen. Returns null if tenant button is not configured. + * @param {string} tenantId The tenant ID or project-level config key. + * @return {?Object} The tenant selection button config object for the tenant. + */ + getSelectionButtonConfigForTenant(tenantId) { + const uiConfigs = this.config_.get('tenants'); + if (!uiConfigs) { + throw new Error('Invalid tenant configuration!'); + } + const tenantConfig = + uiConfigs[tenantId] || + uiConfigs[UiHandlerConfig.ConfigKeys.DEFAULT_CONFIG_KEY]; + if (!tenantConfig) { + log.error(`Invalid tenant configuration: `+ + `${tenantId} is not configured!`); + return null; + } + // The button config key cannot have the quote mark, since the key name will + // be renamed in the soy template. + return { + tenantId: tenantId !== UiHandlerConfig.ConfigKeys.TOP_LEVEL_CONFIG_KEY ? + tenantId : null, + displayName: tenantConfig['displayName'], + iconUrl: tenantConfig['iconUrl'], + buttonColor: tenantConfig['buttonColor'], + }; + } + + /** + * Filters the tenant configuration object by the provided keys. + * @param {string} tenantId The tenant ID or project-level config key. + * @param {!Array} keys The array of property keys used to filter the + * properties. + * @param {!Object=} baseConfig The optional base config object. + * @return {!Object} The copy of the config object for the tenant filtered + * by the keys. + * @private + */ + filterTenantConfig_(tenantId, keys, baseConfig = {}) { + const uiConfig = this.getTenantConfig_(tenantId); + return util.filterProperties( + /** @type {!Object} */ (uiConfig), keys, baseConfig); + } +} + +/** + * The config keys needed for sign-in. + * @const {!Array} + */ +UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS = [ + 'immediateFederatedRedirect', + 'privacyPolicyUrl', + 'signInFlow', + 'signInOptions', + 'tosUrl', +]; + +/** + * The different display modes for tenant selection available. + * @enum {string} + */ +UiHandlerConfig.DisplayMode = { + OPTION_FIRST: 'optionFirst', + IDENTIFIER_FIRST: 'identifierFirst', +}; + +/** + * The UI configuration keys. + * @enum {string} + */ +UiHandlerConfig.ConfigKeys = { + // The UI configuration key for default configuration. + DEFAULT_CONFIG_KEY: '*', + // The UI configuration key for top-level project. + TOP_LEVEL_CONFIG_KEY: '_', +}; + +exports = UiHandlerConfig; diff --git a/javascript/widgets/uihandlerconfig_test.js b/javascript/widgets/uihandlerconfig_test.js new file mode 100644 index 00000000..852141dd --- /dev/null +++ b/javascript/widgets/uihandlerconfig_test.js @@ -0,0 +1,672 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @fileoverview Tests for uihandlerconfig.js */ + +goog.module('firebaseui.auth.widget.UiHandlerConfigTest'); +goog.setTestOnly(); + +const FakeUtil = goog.require('firebaseui.auth.testing.FakeUtil'); +const PropertyReplacer = goog.require('goog.testing.PropertyReplacer'); +const UiHandlerConfig = goog.require('firebaseui.auth.widget.UiHandlerConfig'); +const log = goog.require('firebaseui.auth.log'); +const recordFunction = goog.require('goog.testing.recordFunction'); +const testSuite = goog.require('goog.testing.testSuite'); +const util = goog.require('firebaseui.auth.util'); + +const stub = new PropertyReplacer(); +let testUtil; +let uiHandlerConfig; +// The plain config object. +let configObject; + + +testSuite({ + setUp() { + configObject = { + authDomain: 'project-id1.firebaseapp.com', + displayMode: 'optionsFirst', + tenants: { + tenantId1: { + displayName: 'Contractor A', + buttonColor: '#FFB6C1', + iconUrl: '', + signInOptions: [ + { + hd: 'acme.com', + provider: 'microsoft.com', + providerName: 'Microsoft', + buttonColor: '#2F2F2F', + iconUrl: '', + loginHintKey: 'login_hint', + }, + { + hd: 'sub-acme.com', + provider: 'password', + requireDisplayName: false, + }, + ], + immediateFederatedRedirect: true, + signInFlow: 'redirect', + tosUrl: '/tos', + privacyPolicyUrl: '/privacypolicy', + }, + tenantId2: { + displayName: 'Contractor B', + buttonColor: '#2F2F2F', + iconUrl: '', + signInOptions: [ + { + hd: 'ocp-supplier1.com', + provider: 'saml.my-provider1', + providerName: 'SAML provider', + buttonColor: '#4413AD', + iconUrl: 'https://www.example.com/photos/my_idp/saml.png', + }, + { + provider: 'oidc.my-provider2', + providerName: 'OIDC provider', + buttonColor: '#3566AF', + iconUrl: 'https://www.example.com/photos/my_idp/oidc.png', + }, + ], + credentialHelper: 'none', + }, + _: { + displayName: 'ACME', + buttonColor: '#53B2BF', + iconUrl: '', + signInOptions: [ + 'google.com', + 'password', + ] + }, + } + }; + uiHandlerConfig = new UiHandlerConfig(configObject); + testUtil = new FakeUtil().install(); + stub.replace(log, 'error', recordFunction()); + }, + + tearDown() { + stub.reset(); + configObject = null; + }, + + testGetAuthDomain() { + // Test that the expected Auth domain is returned. + assertEquals(configObject['authDomain'], uiHandlerConfig.getAuthDomain()); + + // Test that error is thrown if Auth domain is not provided in the config. + configObject['authDomain'] = undefined; + uiHandlerConfig.setConfig(configObject); + const error = assertThrows(() => { + uiHandlerConfig.getAuthDomain(); + }); + assertEquals( + 'Invalid project configuration: authDomain is required!', + error.message); + }, + + testGetDisplayMode() { + // Test that the expected optionFirst display mode is returned. + assertEquals('optionFirst', uiHandlerConfig.getDisplayMode()); + + // Test that the expected identifierFirst display mode is returned. + configObject['displayMode'] = 'identifierFirst'; + uiHandlerConfig.setConfig(configObject); + assertEquals('identifierFirst', uiHandlerConfig.getDisplayMode()); + + // Test that default mode is returned if invalid display mode is provided. + configObject['displayMode'] = 'invalid_mode'; + uiHandlerConfig.setConfig(configObject); + assertEquals('optionFirst', uiHandlerConfig.getDisplayMode()); + + // Test that default mode is returned if no display mode is provided. + configObject['displayMode'] = undefined; + uiHandlerConfig.setConfig(configObject); + assertEquals('optionFirst', uiHandlerConfig.getDisplayMode()); + }, + + testGetCallbacks() { + // Test when no callbacks are configured. + assertNull(uiHandlerConfig.getSignInUiShownCallback()); + assertNull(uiHandlerConfig.getSelectTenantUiShownCallback()); + assertNull(uiHandlerConfig.getSelectTenantUiHiddenCallback()); + assertNull(uiHandlerConfig.getBeforeSignInSuccessCallback()); + + // Test when empty callbacks are configured. + configObject['callbacks'] = {}; + uiHandlerConfig.setConfig(configObject); + + assertNull(uiHandlerConfig.getSignInUiShownCallback()); + assertNull(uiHandlerConfig.getSelectTenantUiShownCallback()); + assertNull(uiHandlerConfig.getSelectTenantUiHiddenCallback()); + assertNull(uiHandlerConfig.getBeforeSignInSuccessCallback()); + + // Test that the correct callbacks are returned. + const signInUiShownCallback = (tenantId) => {}; + const selectTenantUiShownCallback = () => {}; + const selectTenantUiHiddenCallback = () => {}; + const beforeSignInSuccessCallback = (user) => {}; + configObject['callbacks'] = { + signInUiShown: signInUiShownCallback, + selectTenantUiShown: selectTenantUiShownCallback, + selectTenantUiHidden: selectTenantUiHiddenCallback, + beforeSignInSuccess: beforeSignInSuccessCallback, + }; + uiHandlerConfig.setConfig(configObject); + assertEquals(signInUiShownCallback, + uiHandlerConfig.getSignInUiShownCallback()); + assertEquals(selectTenantUiShownCallback, + uiHandlerConfig.getSelectTenantUiShownCallback()); + assertEquals(selectTenantUiHiddenCallback, + uiHandlerConfig.getSelectTenantUiHiddenCallback()); + assertEquals(beforeSignInSuccessCallback, + uiHandlerConfig.getBeforeSignInSuccessCallback()); + }, + + testGetTosUrl() { + // Verify null is returned if tosUrl is not provided. + assertNull(uiHandlerConfig.getTosUrl()); + + // Verify null is returned if tosUrl is provided but privacyPolicyUrl is + // not. + configObject['tosUrl'] = 'http://localhost/tos'; + uiHandlerConfig.setConfig(configObject); + assertNull(uiHandlerConfig.getTosUrl()); + + // Verify tos callback is returned if both tosUrl and privacyPolicyUrl are + // provided. + configObject['privacyPolicyUrl'] = 'http://localhost/privacy_policy'; + uiHandlerConfig.setConfig(configObject); + let tosCallback = uiHandlerConfig.getTosUrl(); + tosCallback(); + testUtil.assertOpen('http://localhost/tos', '_blank'); + // Test for Cordova environment. + // Mock that Cordova InAppBrowser plugin is installed. + stub.replace( + util, + 'isCordovaInAppBrowserInstalled', + () => true); + tosCallback = uiHandlerConfig.getTosUrl(); + tosCallback(); + // Target should be _system if Cordova InAppBrowser plugin is installed. + testUtil.assertOpen('http://localhost/tos', '_system'); + + // Test if callback function is passed to tosUrl config. + tosCallback = () => {}; + configObject['tosUrl'] = tosCallback; + uiHandlerConfig.setConfig(configObject); + assertEquals(tosCallback, uiHandlerConfig.getTosUrl()); + + // Test when invalid type is passed to tosUrl config. + configObject['tosUrl'] = 123456; + uiHandlerConfig.setConfig(configObject); + assertNull(uiHandlerConfig.getTosUrl()); + }, + + testGetPrivacyPolicyUrl() { + // Verify null is returned if privacyPolicyUrl is not provided. + assertNull(uiHandlerConfig.getPrivacyPolicyUrl()); + + // Verify null is returned if privacyPolicyUrl is provided but tosUrl is + // not. + configObject['privacyPolicyUrl'] = 'http://localhost/privacy_policy'; + uiHandlerConfig.setConfig(configObject); + assertNull(uiHandlerConfig.getPrivacyPolicyUrl()); + + // Verify privacy policy callback is returned if both tosUrl and + // privacyPolicyUrl are provided. + configObject['tosUrl'] = 'http://localhost/tos'; + uiHandlerConfig.setConfig(configObject); + let privacyPolicyUrlCallback = uiHandlerConfig.getPrivacyPolicyUrl(); + privacyPolicyUrlCallback(); + testUtil.assertOpen('http://localhost/privacy_policy', '_blank'); + // Test for Cordova environment. + // Mock that Cordova InAppBrowser plugin is installed. + stub.replace( + util, + 'isCordovaInAppBrowserInstalled', + () => true); + privacyPolicyUrlCallback = uiHandlerConfig.getPrivacyPolicyUrl(); + privacyPolicyUrlCallback(); + // Target should be _system if Cordova InAppBrowser plugin is installed. + testUtil.assertOpen('http://localhost/privacy_policy', '_system'); + + // Test if callback function is passed to privacyPolicyUrl config. + privacyPolicyUrlCallback = () => {}; + configObject['privacyPolicyUrl'] = privacyPolicyUrlCallback; + uiHandlerConfig.setConfig(configObject); + assertEquals( + privacyPolicyUrlCallback, uiHandlerConfig.getPrivacyPolicyUrl()); + + // Test when invalid type is passed to privacyPolicyUrl config. + configObject['privacyPolicyUrl'] = 123456; + uiHandlerConfig.setConfig(configObject); + assertNull(uiHandlerConfig.getPrivacyPolicyUrl()); + }, + + testValidateTenantId() { + assertNotThrows(() => { + uiHandlerConfig.validateTenantId('tenantId1'); + }); + + const error = assertThrows(() => { + uiHandlerConfig.validateTenantId('invalid_tenant_id'); + }); + assertEquals( + 'Invalid tenant configuration!', + error.message); + + // Provide default config keyed by '*'. No error should be thrown. + configObject['tenants']['*'] = { + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + signInOptions: [ + 'google.com', + 'password', + ], + }; + assertNotThrows(() => { + uiHandlerConfig.validateTenantId('arbitrary_tenant_id'); + }); + }, + + testGetProvidersForTenant() { + // Verify that the expected providers are returned for tenant. + assertArrayEquals(['microsoft.com', 'password'], + uiHandlerConfig.getProvidersForTenant('tenantId1')); + + // Verify that the expected providers are returned for top-level project. + assertArrayEquals(['google.com', 'password'], + uiHandlerConfig.getProvidersForTenant('_')); + + // Verify that invalid signInOption is skipped. + const invalidOption = { + 'provider': 123456, + }; + configObject['tenants']['tenantId1']['signInOptions'][0] = invalidOption; + assertEquals(0, log.error.getCallCount()); + assertArrayEquals(['password'], + uiHandlerConfig.getProvidersForTenant('tenantId1')); + assertEquals(1, log.error.getCallCount()); + assertEquals( + `Invalid tenant configuration: signInOption `+ + `${JSON.stringify(invalidOption)} is invalid!`, + log.error.getLastCall().getArgument(0)); + + // Verify that error is thrown if signInOptions are not configured. + delete configObject['tenants']['tenantId1']['signInOptions']; + let error = assertThrows(() => { + uiHandlerConfig.getProvidersForTenant('tenantId1'); + }); + assertEquals('Invalid tenant configuration: signInOptions are invalid!', + error.message); + + // Verify that empty array is returned if the tenant ID is invalid. + assertEquals(1, log.error.getCallCount()); + assertArrayEquals( + [], + uiHandlerConfig.getProvidersForTenant('invalid_tenant_id')); + assertEquals(2, log.error.getCallCount()); + assertEquals( + 'Invalid tenant configuration: invalid_tenant_id is not configured!', + log.error.getLastCall().getArgument(0)); + + // Verify the default configuration is returned for arbitrary tenant. + configObject['tenants']['*'] = { + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + signInOptions: [ + 'microsoft.com', + 'google.com', + ], + }; + assertArrayEquals( + ['microsoft.com', 'google.com'], + uiHandlerConfig.getProvidersForTenant('arbitrary_tenant_id')); + + // Verify the default configuration is returned for top level project. + delete configObject['tenants']['_']; + assertArrayEquals( + ['microsoft.com', 'google.com'], + uiHandlerConfig.getProvidersForTenant('_')); + }, + + testGetProvidersForTenant_emailMatch() { + // Test getProvidersForTenant when email is provided for matching. + // Verify that expected matching providers are returned when hd is string. + let providers = + uiHandlerConfig.getProvidersForTenant( + 'tenantId1', 'user@acme.com'); + assertArrayEquals(['microsoft.com'], providers); + + // Verify that expected matching providers are returned when hd is regex. + configObject['tenants']['tenantId1']['signInOptions'][0]['hd'] = + /@acme\.com$/; + providers = + uiHandlerConfig.getProvidersForTenant( + 'tenantId1', 'user@acme.com'); + assertArrayEquals(['microsoft.com'], providers); + + // Verify that multiple providers are returned if they all match with the + // email. + configObject['tenants']['tenantId1']['signInOptions'].push({ + hd: 'acme.com', + provider: 'google.com', + }); + providers = + uiHandlerConfig.getProvidersForTenant( + 'tenantId1', 'user@acme.com'); + assertArrayEquals(['microsoft.com', 'google.com'], providers); + + // Verify that empty array is returned if there is no matching providers. + providers = + uiHandlerConfig.getProvidersForTenant( + 'tenantId1', 'user@acme.com123'); + assertArrayEquals([], providers); + + // Verify that if hd is not specified, the associated provider is still + // returned. + // oidc.my-provider2 doesn't have hd configured but is still returned. + providers = + uiHandlerConfig.getProvidersForTenant( + 'tenantId2', 'user@ocp-supplier1.com'); + assertArrayEquals(['saml.my-provider1', 'oidc.my-provider2'], providers); + + // Verify that the provider is returned if signInOption is string. + providers = + uiHandlerConfig.getProvidersForTenant( + '_', 'user@example.com'); + assertArrayEquals(['google.com', 'password'], providers); + + // Verify that invalid signInOption is skipped. + const invalidOption = { + 'provider': 123456, + }; + configObject['tenants']['tenantId1']['signInOptions'][0] = invalidOption; + assertEquals(0, log.error.getCallCount()); + assertArrayEquals( + ['google.com'], + uiHandlerConfig.getProvidersForTenant('tenantId1', 'user@acme.com')); + assertEquals(1, log.error.getCallCount()); + assertEquals( + `Invalid tenant configuration: signInOption `+ + `${JSON.stringify(invalidOption)} is invalid!`, + log.error.getLastCall().getArgument(0)); + + // Verify that empty array is returned if signInOptions are not configured. + delete configObject['tenants']['tenantId1']['signInOptions']; + let error = assertThrows(() => { + uiHandlerConfig.getProvidersForTenant('tenantId1', 'user@example.com'); + }); + assertEquals('Invalid tenant configuration: signInOptions are invalid!', + error.message); + + // Verify that empty array is returned if the tenant ID is invalid. + assertEquals(1, log.error.getCallCount()); + assertArrayEquals( + [], + uiHandlerConfig.getProvidersForTenant( + 'invalid_tenant_id', 'user@acme.com')); + assertEquals(2, log.error.getCallCount()); + assertEquals( + 'Invalid tenant configuration: invalid_tenant_id is not configured!', + log.error.getLastCall().getArgument(0)); + }, + + testGetSignInConfigForTenant() { + // Test that the sign-in related configs for the tenant are returned. + const tenantSignInConfig = + uiHandlerConfig.getSignInConfigForTenant('tenantId1'); + // All sign-in related properties in the original config object should be + // copied over to the returned sign-in config object. + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.forEach((key) => { + if (key in configObject['tenants']['tenantId1']) { + assertObjectEquals( + configObject['tenants']['tenantId1'][key], + tenantSignInConfig[key]); + } + }); + // Verify that no unrelated properties are copied over. + for (let key in tenantSignInConfig) { + assertTrue(UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.includes(key)); + } + }, + + testGetSignInConfigForTenant_eligibleProviders() { + // Test that the sign-in related configs with only eligible providers are + // returned if eligible providers are passed. + const tenantSignInConfig = + uiHandlerConfig.getSignInConfigForTenant( + 'tenantId1', ['microsoft.com']); + // Remove password provider from the expected configs. + configObject['tenants']['tenantId1']['signInOptions'].splice(1, 1); + // All sign-in related properties in the original config object should be + // copied over to the returned sign-in config object. + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.forEach((key) => { + if (key in configObject['tenants']['tenantId1']) { + assertObjectEquals( + configObject['tenants']['tenantId1'][key], + tenantSignInConfig[key]); + } + }); + // Verify that no unrelated properties are copied over. + for (let key in tenantSignInConfig) { + assertTrue(UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.includes(key)); + } + }, + + testGetSignInConfigForTenant_topLevelProject() { + // Test that the sign-in related configs are returned for top-level project. + const tenantSignInConfig = + uiHandlerConfig.getSignInConfigForTenant('_'); + // All sign-in related properties in the original config object should be + // copied over to the returned sign-in config object. + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.forEach((key) => { + if (key in configObject['tenants']['_']) { + assertObjectEquals( + configObject['tenants']['_'][key], + tenantSignInConfig[key]); + } + }); + // Verify that no unrelated properties are copied over. + for (let key in tenantSignInConfig) { + assertTrue(UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.includes(key)); + } + }, + + testGetSignInConfigForTenant_defaultConfig() { + // Test that the default sign-in related configs are returned for arbitrary + // tenant if default configuration is provided. + configObject['tenants']['*'] = { + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + signInOptions: [ + 'microsoft.com', + 'google.com', + ], + }; + const tenantSignInConfig = + uiHandlerConfig.getSignInConfigForTenant('arbitrary_tenant_id'); + // All sign-in related properties in the original config object should be + // copied over to the returned sign-in config object. + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.forEach((key) => { + if (key in configObject['tenants']['*']) { + assertObjectEquals( + configObject['tenants']['*'][key], + tenantSignInConfig[key]); + } + }); + // Verify that no unrelated properties are copied over. + for (let key in tenantSignInConfig) { + assertTrue(UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.includes(key)); + } + }, + + testGetSignInConfigForTenant_defaultConfig_topLevelProject() { + // Test that the default sign-in related configs are returned for top level + // project if default configuration is provided. + delete configObject['tenants']['_']; + configObject['tenants']['*'] = { + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + signInOptions: [ + 'microsoft.com', + 'google.com', + ], + }; + const tenantSignInConfig = + uiHandlerConfig.getSignInConfigForTenant('_'); + // All sign-in related properties in the original config object should be + // copied over to the returned sign-in config object. + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.forEach((key) => { + if (key in configObject['tenants']['*']) { + assertObjectEquals( + configObject['tenants']['*'][key], + tenantSignInConfig[key]); + } + }); + // Verify that no unrelated properties are copied over. + for (let key in tenantSignInConfig) { + assertTrue(UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.includes(key)); + } + }, + + testGetSignInConfigForTenant_unsupportedConfig() { + // Test that additional unsupported configs, eg. 'credentialHelper' are + // removed from the returned object. + const tenantSignInConfig = + uiHandlerConfig.getSignInConfigForTenant('tenantId2'); + // All sign-in related properties in the original config object should be + // copied over to the returned sign-in config object. + UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.forEach((key) => { + if (key in configObject['tenants']['tenantId2']) { + assertObjectEquals( + configObject['tenants']['tenantId2'][key], + tenantSignInConfig[key]); + } + }); + // Verify that no unrelated properties would be copied over. + for (let key in tenantSignInConfig) { + assertTrue(UiHandlerConfig.SUPPORTED_SIGN_IN_CONFIG_KEYS.includes(key)); + } + assertUndefined(tenantSignInConfig['credentialHelper']); + }, + + testGetSignInConfigForTenant_invalidTenantId() { + // Verify that error is thrown if invalid tenant ID is provided. + const error = assertThrows(() => { + uiHandlerConfig.getSignInConfigForTenant('invalid_tenant_id'); + }); + assertEquals( + 'Invalid tenant configuration!', + error.message); + }, + + testGetSelectionButtonConfigForTenant() { + // Test that the option first tenant selection related configs are returned. + const tenantButtonConfig = + uiHandlerConfig.getSelectionButtonConfigForTenant('tenantId1'); + assertObjectEquals( + { + tenantId: 'tenantId1', + displayName: 'Contractor A', + buttonColor: '#FFB6C1', + iconUrl: '', + }, + tenantButtonConfig); + }, + + testGetSelectionButtonConfigForTenant_topLevelProject() { + // Test that the option first tenant selection related configs are returned + // for top-level project. + const tenantButtonConfig = + uiHandlerConfig.getSelectionButtonConfigForTenant('_'); + assertObjectEquals( + { + tenantId: null, + displayName: 'ACME', + buttonColor: '#53B2BF', + iconUrl: '', + }, + tenantButtonConfig); + }, + + testGetSelectionButtonConfigForTenant_defaultConfig() { + // Test that the default option first tenant selection related configs are + // returned for arbitrary tenant if default configuration is provided. + configObject['tenants']['*'] = { + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + signInOptions: [ + 'microsoft.com', + 'google.com', + ], + }; + const tenantButtonConfig = + uiHandlerConfig.getSelectionButtonConfigForTenant( + 'arbitrary_tenant_id'); + assertObjectEquals( + { + tenantId: 'arbitrary_tenant_id', + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + }, + tenantButtonConfig); + }, + + testGetSelectionButtonConfigForTenant_defaultConfig_topLevelProject() { + // Test that the default option first tenant selection related configs are + // returned for top level project if default configuration is provided. + delete configObject['tenants']['_']; + configObject['tenants']['*'] = { + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + signInOptions: [ + 'microsoft.com', + 'google.com', + ], + }; + const tenantButtonConfig = + uiHandlerConfig.getSelectionButtonConfigForTenant('_'); + assertObjectEquals( + { + tenantId: null, + displayName: 'DEALER', + buttonColor: '#37D2AC', + iconUrl: '', + }, + tenantButtonConfig); + }, + + testGetSelectionButtonConfigForTenant_tenantNotConfigured() { + // Verify that null is returned if the tenant button is not configured. + assertEquals(0, log.error.getCallCount()); + assertNull( + uiHandlerConfig.getSelectionButtonConfigForTenant('unknown_tenant_id')); + assertEquals(1, log.error.getCallCount()); + assertEquals( + 'Invalid tenant configuration: unknown_tenant_id is not configured!', + log.error.getLastCall().getArgument(0)); + }, +}); diff --git a/package-lock.json b/package-lock.json index 6efd2eea..a46721e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,163 +4,279 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@firebase/analytics": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.2.8.tgz", + "integrity": "sha512-45U20f8TfMgNWrSk10925UrSLGnTC/p+iTGXRR7nzJLiPsV48suscbJwpD7NmzxPPKAWyxHNSnHE0aIanxkGQA==", + "dev": true, + "requires": { + "@firebase/analytics-types": "0.2.4", + "@firebase/component": "0.1.0", + "@firebase/installations": "0.3.7", + "@firebase/util": "0.2.35", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } + } + }, + "@firebase/analytics-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.2.4.tgz", + "integrity": "sha512-byGvFzzWFLwAI18g3BgUjNG3sBqV6tXt6K3htwveUT71aWbiPlcJE3nAmKFsUD6weiHfsGZS4FsVEqdtopqChg==", + "dev": true + }, "@firebase/app": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.3.17.tgz", - "integrity": "sha512-/8lDeeIxgdCIMffrfBPQ3bcdSkF8bx4KCp8pKMPOG/HYKoeM8I9eP4zlzxL5ABzRjvcdhK9KOYOn0jRrNrGD9g==", + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.4.26.tgz", + "integrity": "sha512-ECQGEcf1maT9Ce9+EWX+zsvjFF48bwSG8z/822k+3npYvj111S+G/1DoJGCLN+VxO+qhPVySDUlMjwDR7ugeNQ==", "dev": true, "requires": { - "@firebase/app-types": "0.3.10", - "@firebase/util": "0.2.14", + "@firebase/app-types": "0.4.9", + "@firebase/component": "0.1.0", + "@firebase/logger": "0.1.32", + "@firebase/util": "0.2.35", "dom-storage": "2.1.0", - "tslib": "1.9.3", + "tslib": "1.10.0", "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/app-types": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.3.10.tgz", - "integrity": "sha512-l+5BJtSQopalBXiY/YuSaB9KF9PnDj37FLV0Sx3qJjh5B3IthCuZbPc1Vpbbbee/QZgudl0G212BBsUMGHP+fQ==", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.4.9.tgz", + "integrity": "sha512-RoUkYVd5X106sFGX+rHVDGrtfZBRugMtT9Cx8YiXtLSqouhi0S+Sx1TVuK6Gkt7lJ27I8qlz/nBvNa0yjg3N7w==", "dev": true }, "@firebase/auth": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.10.2.tgz", - "integrity": "sha512-+S8RZcHhhat2xrW/RGOcSZO8pv0qHveaw09Bq/gXhZyJfN86UeiMc3sv4YMo1Hu7fRRorNteijpmlH522eI0AA==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.13.2.tgz", + "integrity": "sha512-EHmKo4OMgLAWIqqvy45XwDSShDUo9S5TjZFk03h2/aF467WB8AvO3pW/b7kDbnlrK1HaZvn97jwKC71vvklBJw==", "dev": true, "requires": { - "@firebase/auth-types": "0.6.1" + "@firebase/auth-types": "0.9.2" } }, "@firebase/auth-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.6.1.tgz", - "integrity": "sha512-uciPeIQJC1NZDhI5+BWbyqi70YXIjT3jm03sYtIgkPt2sr3n8sq1RpnoTMYfAJkQ0QlgLaBkeM/huMx06eBoXQ==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.9.2.tgz", + "integrity": "sha512-e6raEvmGtV9BzZCtCaYQFKHOxcEBGen43xUEuA1mTRQnb0Hn93ctaEVd/uqjF+hWA6z3KR6wqP//mBCgoTTsUA==", "dev": true }, + "@firebase/component": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.0.tgz", + "integrity": "sha512-l7UTwhmdKVHTWWD+OcBIzlbI5U/FbutSGWNiOxwaTq5nCau1LIC/9S+In9BnEgiTTCFY0CKeuM7H/rHcBZr5pA==", + "dev": true, + "requires": { + "@firebase/util": "0.2.35", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } + } + }, "@firebase/database": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.3.20.tgz", - "integrity": "sha512-fZHRIlRQlND/UrzI1beUTRKfktjMvMEiUOar6ylFZqOj2KNVO4CrF95UGqRl0HBGhZzlBKzaDYAcJze2D6C4+Q==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.15.tgz", + "integrity": "sha512-6GsZGDOoRbAaeBQjjbcKTCscEMV+qDXS1UWPmRYI0UBdhgCY05S1z5aNkjF2B3pFgkr35zDbVAdsQC+JhRCzLQ==", "dev": true, "requires": { - "@firebase/database-types": "0.3.11", - "@firebase/logger": "0.1.13", - "@firebase/util": "0.2.14", - "faye-websocket": "0.11.1", - "tslib": "1.9.3" + "@firebase/component": "0.1.0", + "@firebase/database-types": "0.4.9", + "@firebase/logger": "0.1.32", + "@firebase/util": "0.2.35", + "faye-websocket": "0.11.3", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/database-types": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.3.11.tgz", - "integrity": "sha512-iRAZzs7Zlmmvh7r0XlR1MAO6I6bm1HjW9m1ytfJ6E/8+zItHnbVH4iiVVkC39r1wMGrtPMz8FiIUWoaasPF5dA==", - "dev": true - }, - "@firebase/firestore": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.2.2.tgz", - "integrity": "sha512-5o3SFTpMYaWrWRlm5qBX84fNDwdiPTbb0qo6KDI+OvIzTaMsEfOJ4vUz+Binxfq0dPen0fU6JLO+xix8Sa8TBA==", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.4.9.tgz", + "integrity": "sha512-VIATPku6NuLvDEIt5gkTx6xbtIFfQhATnySL4uoJ5udcVK6hH2KV0po58UPH72vQMtgrQ/clLGr6kkPgWRZw4Q==", "dev": true, "requires": { - "@firebase/firestore-types": "1.2.1", - "@firebase/logger": "0.1.13", - "@firebase/webchannel-wrapper": "0.2.19", - "grpc": "1.20.0", - "tslib": "1.9.3" + "@firebase/app-types": "0.4.9" + } + }, + "@firebase/firestore": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.8.1.tgz", + "integrity": "sha512-pElz88GKKDjdVdg4c3nDCizGtvFfHquvE99DPInKMjpEtZHsuPsAugULQPiTsrQKz7VZ/Lr1eXmoFu9zucVrlQ==", + "dev": true, + "requires": { + "@firebase/component": "0.1.0", + "@firebase/firestore-types": "1.8.1", + "@firebase/logger": "0.1.32", + "@firebase/util": "0.2.35", + "@firebase/webchannel-wrapper": "0.2.33", + "@grpc/proto-loader": "^0.5.0", + "grpc": "1.24.2", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/firestore-types": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.2.1.tgz", - "integrity": "sha512-/Klu3uVLoTjW3ckYqFTV3lr9HzEKM7pMpPHao1Sy+YwIUmTjFMI1LE2WcXMx6HN2jipFjjD/Xjg0hY0+0dnPCg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.8.1.tgz", + "integrity": "sha512-BAap8Oao47/oiJY00nhUFNuUdPVuvvD4vtQby88icLsuCgUy0Wrds54dmXyRFuQWGu8oOEFGpH8v1AomQx+zyg==", "dev": true }, "@firebase/functions": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.6.tgz", - "integrity": "sha512-jpRLY8GyhmFufnN3eilvIuAqD9qsG2/AftGtFaTRL0ObSySmraYcVOpKAxsFZW//9EMNtI9c9/rw+QFq5SkuyA==", + "version": "0.4.27", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.27.tgz", + "integrity": "sha512-LObi8DkxtbdoJSA7EyKfRTugkg0Ci7gp6cbQPZCoyb4wtSamXXl+sbotQGcXqrQLsXGWan38lvERJnLeEbSx5Q==", "dev": true, "requires": { - "@firebase/functions-types": "0.3.5", - "@firebase/messaging-types": "0.2.11", + "@firebase/component": "0.1.0", + "@firebase/functions-types": "0.3.12", + "@firebase/messaging-types": "0.3.6", "isomorphic-fetch": "2.2.1", - "tslib": "1.9.3" + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/functions-types": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.5.tgz", - "integrity": "sha512-3hTMqfSugCfxzT6vZPbzQ58G4941rsFr99fWKXGKFAl2QpdMBCnKmEKdg/p5M47xIPyzIQn6NMF5kCo/eICXhA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.12.tgz", + "integrity": "sha512-4WjXJnh9I7UQw1ZYosoVyHIlXG11HwPjJ++2cAdaeOQugIDA9tL1xyURo1pivx9EY/mGIr8ITkmh3PnkWbtQEw==", "dev": true }, "@firebase/installations": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.1.0.tgz", - "integrity": "sha512-drt9kDT4w/OCXt5nboOIaUGI3lDwHAoSY2V6qJTbtbd3qVSxE0EBLA4c+allpWdmrhGBrASApuA0eAjphxuXIw==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.3.7.tgz", + "integrity": "sha512-aYAw3Kk/AF/sJinqWFnfCQF2/CNWFof/bE0me3GUb0n5Hajj78QwPgdmYis5LHGeE1D/vG6lAEN7CYQ0Wqakjg==", "dev": true, "requires": { - "@firebase/installations-types": "0.1.0", - "@firebase/util": "0.2.14", - "idb": "3.0.2" + "@firebase/component": "0.1.0", + "@firebase/installations-types": "0.2.3", + "@firebase/util": "0.2.35", + "idb": "3.0.2", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/installations-types": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.1.0.tgz", - "integrity": "sha512-cw2UIvPa3+umy6w7dGj0LqQQ9v7WEOro5s+6B+v54Tw25WyLnR6cBIkyyahv3Uu9QPnGZCIsemlQwxIaIOMb9g==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.2.3.tgz", + "integrity": "sha512-G+jeoRFdUih2P4GdnQM7X1WILs2cG+jf2N8QnaC5EdVYJu7f86BVtijCuLvSY3L4w606pZp7sjsIvTkCZbvGAA==", "dev": true }, "@firebase/logger": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.1.13.tgz", - "integrity": "sha512-wIbLwQ2oJCkvHIE7J3FDxpScKY84fSctEEjOi0PB+Yn2dN8AwqtM7YF8rtcY8cxntv8dyR+i7GNg1Nd89cGxkA==", + "version": "0.1.32", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.1.32.tgz", + "integrity": "sha512-txfDHzNS1M39cEDyrOjnpU/zP0vqpbK1ZOS9Rqqa3znjDdnO42AdtuY2UVBU0G5s5LAzawSaYA65AJB5tCVKLg==", "dev": true }, "@firebase/messaging": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.3.19.tgz", - "integrity": "sha512-xY1Hlsj0MqyU/AmJQLyH9Uknhs8+1OsD2xXE8W34qk0g2RtpygUN7JMD21d5w5zZ5dMtLNhVSIxU8oI2rAUjcA==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.5.8.tgz", + "integrity": "sha512-nPODbORCct7hiMnZyZPOHxrE7SBhKhIsi/z9hRdzof9C71KLaYtC+1Hq274D6dEOGBzly8HA4nQqDUlHJFbMLw==", "dev": true, "requires": { - "@firebase/messaging-types": "0.2.11", - "@firebase/util": "0.2.14", - "tslib": "1.9.3" + "@firebase/component": "0.1.0", + "@firebase/installations": "0.3.7", + "@firebase/messaging-types": "0.3.6", + "@firebase/util": "0.2.35", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/messaging-types": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.2.11.tgz", - "integrity": "sha512-uWtzPMj1mAX8EbG68SnxE12Waz+hRuO7vtosUFePGBfxVNNmPx5vJyKZTz+hbM4P77XddshAiaEkyduro4gNgA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.3.6.tgz", + "integrity": "sha512-5D0BTAl2rONszYwsj6g0ZO7rVGBRk/xC3Z4KnOxxPofelBzcqwG6W/AjGwheTJ0lX4QVgaIn55PAKnTtBLSc8Q==", "dev": true }, "@firebase/performance": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.2.1.tgz", - "integrity": "sha512-vo/24+W35foc2ShRgeIlx2Ej45+Sn6uYPpnYzTtJb3DwE3sb0BVGocVgINbXyguUq2PHS+6yLsCm88y12DS2EA==", + "version": "0.2.27", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.2.27.tgz", + "integrity": "sha512-ULIplf3whbvNmHEDIqIVZkiI15YVqzBOXRfKC0rEicAn2JQ0yWn+MxgyXlEwXG3Ul11MqC+F0hPeW8cm5uZL/A==", "dev": true, "requires": { - "@firebase/installations": "0.1.0", - "@firebase/logger": "0.1.13", - "@firebase/performance-types": "0.0.1", - "@firebase/util": "0.2.14", - "tslib": "1.9.3" + "@firebase/component": "0.1.0", + "@firebase/installations": "0.3.7", + "@firebase/logger": "0.1.32", + "@firebase/performance-types": "0.0.7", + "@firebase/util": "0.2.35", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/performance-types": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.1.tgz", - "integrity": "sha512-U45GbVAnPyz7wPLd3FrWdTeaFSvgsnGfGK58VojfEMmFnMAixCM3qBv1XJ0xfhyKbK1xZN4+usWAR8F3CwRAXw==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.7.tgz", + "integrity": "sha512-FElDfwFO6ucSH6acHiHMcLrJdOCUBcs2XnqnoOCJ/XGvORuJRCl7kEiKS6DPsZwvBelp0jZLwHmmTYSm5dpJMQ==", "dev": true }, "@firebase/polyfill": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.13.tgz", - "integrity": "sha512-nmz0KMrGZh4wvy8iPnOCtpSXw0LwXPdj9lqgeOVClXMgJBi5+FS1q0W1Ofn7BULmMc8tYsGGY+T2HvfmznMuPg==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.29.tgz", + "integrity": "sha512-Ogc6BUYoyOb64lFAGBjMydoczSHdazMeINTBjEEfSkaDqOi7l/tgk9X+oWYe5mxfPNrdBLREkfQb6oKqFPqydQ==", "dev": true, "requires": { - "core-js": "3.0.1", - "promise-polyfill": "8.1.0", + "core-js": "3.4.1", + "promise-polyfill": "8.1.3", "whatwg-fetch": "2.0.4" }, "dependencies": { @@ -172,43 +288,179 @@ } } }, + "@firebase/remote-config": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.8.tgz", + "integrity": "sha512-E5h715SxHSosugzbVCh0+qOCXpFoBYRvZHyesjPm+NZ8XU+v0jsdusG6jcoMLEdftt50IYamta6HvdP+oQj2gw==", + "dev": true, + "requires": { + "@firebase/component": "0.1.0", + "@firebase/installations": "0.3.7", + "@firebase/logger": "0.1.32", + "@firebase/remote-config-types": "0.1.4", + "@firebase/util": "0.2.35", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } + } + }, + "@firebase/remote-config-types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.4.tgz", + "integrity": "sha512-GFnfuSomjMOE2ik4TD1DuhfswsWr7UEu9+zSvKgDKslTFQ35L2rPqJEExTfHuL1uVVkYF6k8wEyGw0zwIkaeBQ==", + "dev": true + }, "@firebase/storage": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.2.15.tgz", - "integrity": "sha512-WR80AXm1btlHERavhSwiTwFAyT/M/Jn6/2I3RAlcVOS6NnKVdRIcSVW1zY9jvO3fdeksqBU9NKTXeXFTmsrw6g==", + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.21.tgz", + "integrity": "sha512-WSBMorw/8j6ezRfhCQ0V4qTGA9mSowXUvOZ1CnNs/MCYUqJ5I3w96E7uEg38EgZgSYwf13J1jiYBlta2Q9UfZw==", "dev": true, "requires": { - "@firebase/storage-types": "0.2.11", - "tslib": "1.9.3" + "@firebase/component": "0.1.0", + "@firebase/storage-types": "0.3.7", + "@firebase/util": "0.2.35", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/storage-types": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.2.11.tgz", - "integrity": "sha512-vGTFJmKbfScmCAVUamREIBTopr5Uusxd8xPAgNDxMZwICvdCjHO0UH0pZZj6iBQuwxLe/NEtFycPnu1kKT+TPw==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.7.tgz", + "integrity": "sha512-7HnR4r7bffV7LJwIAmZIKyvEdEBm6eEx8k9SeWNxbQK5nev+KoGrYLpkKTgWsv1BRc9EC+RH4l75zZMGB7KJGw==", "dev": true }, "@firebase/util": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.14.tgz", - "integrity": "sha512-2ke1Lra0R5T+5ucCMWft/IB2rI/IzumHHYm9aqrM9lJ3XURiWmBHAYrvaPVP7///gDhJAo+NNDUCAJH/Y4PmvA==", + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.35.tgz", + "integrity": "sha512-uixPxpdwxP8ATFVmgr3oz82VZovxJqyK6m2oFvZ+0GLY5VlWa37NLfOXWbcBa5QeqX0Ox46Z7/OaE8WfpAlPAA==", "dev": true, "requires": { - "tslib": "1.9.3" + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + } } }, "@firebase/webchannel-wrapper": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.19.tgz", - "integrity": "sha512-U9e2dCB38mD2AvV/zAjghauwa0UX15Wt98iBgm8IOw8spluDxysx8UZFUhj38fu0iFXORVRBqseyK2wCxZIl5w==", + "version": "0.2.33", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.33.tgz", + "integrity": "sha512-xfYZ1Z2CY7YRUJzXRS+nR1HKhxmGItdmGl7SmhhpuX89MXiTP9zjoa65asdSwDwTfCK8vALvya5pl2ecbQAZQg==", "dev": true }, + "@grpc/proto-loader": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", + "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", + "dev": true, + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "dev": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "dev": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dev": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "dev": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "dev": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "dev": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "dev": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "dev": true + }, + "@types/bytebuffer": { + "version": "5.0.40", + "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.40.tgz", + "integrity": "sha512-h48dyzZrPMz25K6Q4+NCwWaxwXany2FhQg/ErOcdZS1ZpsaDnDMZg8JYLMTGz7uvXKrcKGJUZJlZObyfgdaN9g==", + "dev": true, + "requires": { + "@types/long": "*", + "@types/node": "*" + } + }, "@types/estree": { "version": "0.0.38", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.38.tgz", "integrity": "sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA==", "dev": true }, + "@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==", + "dev": true + }, "@types/node": { "version": "12.6.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.2.tgz", @@ -1409,6 +1661,14 @@ "dev": true, "requires": { "long": "~3" + }, + "dependencies": { + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true + } } }, "bytes": { @@ -2040,9 +2300,9 @@ } }, "core-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.1.tgz", - "integrity": "sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz", + "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==", "dev": true }, "core-util-is": { @@ -3202,9 +3462,9 @@ } }, "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "dev": true, "requires": { "websocket-driver": ">=0.5.1" @@ -3314,20 +3574,25 @@ } }, "firebase": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-5.11.1.tgz", - "integrity": "sha512-cop2UHytKas8WJZTovZqhpZIgwRfsvegijyOjgmMJoaOHCnyH4eymPneglgXsK5ExOdxJSTC4QD5qETrdL3dMw==", - "dev": true, - "requires": { - "@firebase/app": "0.3.17", - "@firebase/auth": "0.10.2", - "@firebase/database": "0.3.20", - "@firebase/firestore": "1.2.2", - "@firebase/functions": "0.4.6", - "@firebase/messaging": "0.3.19", - "@firebase/performance": "0.2.1", - "@firebase/polyfill": "0.3.13", - "@firebase/storage": "0.2.15" + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.5.2.tgz", + "integrity": "sha512-G1hJ59CoV/gdNulBAFKiGD8EIDKCeQS16tc3TQ/sBCb87urDuaYKv7/n/CgK8llSt+pWJjPc6U0F223zt0OdKA==", + "dev": true, + "requires": { + "@firebase/analytics": "0.2.8", + "@firebase/app": "0.4.26", + "@firebase/app-types": "0.4.9", + "@firebase/auth": "0.13.2", + "@firebase/database": "0.5.15", + "@firebase/firestore": "1.8.1", + "@firebase/functions": "0.4.27", + "@firebase/installations": "0.3.7", + "@firebase/messaging": "0.5.8", + "@firebase/performance": "0.2.27", + "@firebase/polyfill": "0.3.29", + "@firebase/remote-config": "0.1.8", + "@firebase/storage": "0.3.21", + "@firebase/util": "0.2.35" } }, "firebase-tools": { @@ -4557,15 +4822,16 @@ "dev": true }, "grpc": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.20.0.tgz", - "integrity": "sha512-HgYuJzRomkBlJAfC/78epuWzwMiByxgj4JsO6G6dHXXNfARTsUqpM/FmPSJJNFGvzCev0g6tn33CE7nWEmhDEg==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.2.tgz", + "integrity": "sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw==", "dev": true, "requires": { + "@types/bytebuffer": "^5.0.40", "lodash.camelcase": "^4.3.0", "lodash.clone": "^4.5.0", - "nan": "^2.0.0", - "node-pre-gyp": "^0.12.0", + "nan": "^2.13.2", + "node-pre-gyp": "^0.14.0", "protobufjs": "^5.0.3" }, "dependencies": { @@ -4608,7 +4874,7 @@ } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "dev": true }, @@ -4633,11 +4899,11 @@ "dev": true }, "debug": { - "version": "2.6.9", + "version": "3.2.6", "bundled": true, "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "deep-extend": { @@ -4656,11 +4922,11 @@ "dev": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "dev": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -4684,7 +4950,7 @@ } }, "glob": { - "version": "7.1.2", + "version": "7.1.4", "bundled": true, "dev": true, "requires": { @@ -4702,7 +4968,7 @@ "dev": true }, "iconv-lite": { - "version": "0.4.23", + "version": "0.4.24", "bundled": true, "dev": true, "requires": { @@ -4710,7 +4976,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "dev": true, "requires": { @@ -4727,7 +4993,7 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, "dev": true }, @@ -4763,7 +5029,7 @@ "dev": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "dev": true, "requires": { @@ -4772,11 +5038,11 @@ } }, "minizlib": { - "version": "1.1.1", + "version": "1.3.3", "bundled": true, "dev": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { @@ -4795,22 +5061,22 @@ } }, "ms": { - "version": "2.0.0", + "version": "2.1.2", "bundled": true, "dev": true }, "needle": { - "version": "2.2.4", + "version": "2.4.0", "bundled": true, "dev": true, "requires": { - "debug": "^2.1.2", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "dev": true, "requires": { @@ -4823,7 +5089,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -4836,12 +5102,12 @@ } }, "npm-bundled": { - "version": "1.0.5", + "version": "1.0.6", "bundled": true, "dev": true }, "npm-packlist": { - "version": "1.1.12", + "version": "1.4.6", "bundled": true, "dev": true, "requires": { @@ -4903,10 +5169,22 @@ "dev": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "dev": true, + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } + }, "rc": { "version": "1.2.8", "bundled": true, @@ -4933,11 +5211,11 @@ } }, "rimraf": { - "version": "2.6.2", + "version": "2.7.1", "bundled": true, "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "safe-buffer": { @@ -4956,7 +5234,7 @@ "dev": true }, "semver": { - "version": "5.6.0", + "version": "5.7.1", "bundled": true, "dev": true }, @@ -5002,17 +5280,17 @@ "dev": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -5034,7 +5312,7 @@ "dev": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, "dev": true } @@ -7235,9 +7513,9 @@ "dev": true }, "long": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "dev": true }, "loud-rejection": { @@ -8485,21 +8763,38 @@ "dev": true }, "promise-polyfill": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", "dev": true }, "protobufjs": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", - "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", - "dev": true, - "requires": { - "ascli": "~1", - "bytebuffer": "~5", - "glob": "^7.0.5", - "yargs": "^3.10.0" + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "dev": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.8.tgz", + "integrity": "sha512-FeTtEwXbQa187ABpeEQoO7pq3dHgE85FmAUExx2sKO6U1/MYrLTYv+BIMcgVbQ66WjI4w+Ni+5HJtY+gHgWnPg==", + "dev": true + } } }, "protractor": { @@ -8522,7 +8817,7 @@ "selenium-webdriver": "3.6.0", "source-map-support": "~0.4.0", "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.0.6" + "webdriver-manager": "^12.1.7" } }, "proxy-addr": { diff --git a/package.json b/package.json index a2436757..2e1ae0c1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "license": "Apache-2.0", "devDependencies": { "closure-builder": "^2.3.8", - "firebase": "^5.10.0", + "firebase": "^7.4.0", "firebase-tools": "^7.0.2", "fs-extra": "^3.0.1", "google-closure-compiler": "^20190415.0.0", @@ -62,6 +62,6 @@ "material-design-lite": "^1.2.0" }, "peerDependencies": { - "firebase": ">=6.6.0" + "firebase": ">=7.4.0" } } diff --git a/sauce_browsers.json b/sauce_browsers.json index 2b1612aa..f7f654e6 100644 --- a/sauce_browsers.json +++ b/sauce_browsers.json @@ -12,13 +12,6 @@ "version" : "65.0", "name": "chrome-latest-mac" }, - { - "browserName" : "internet explorer", - "version" : "11.0", - "platform" : "Windows 7", - "timeZone": "Pacific", - "name": "ie-11-windows" - }, { "browserName" : "MicrosoftEdge", "platform" : "Windows 10", @@ -43,8 +36,8 @@ { "deviceName": "Android Emulator", "platformName": "Android", - "platformVersion": "5.1", - "browserName": "Browser", - "name": "android-51-sim" + "platformVersion": "6.0", + "browserName": "Chrome", + "name": "android-6-sim" } ] diff --git a/soy/elements.soy b/soy/elements.soy index 4bfbe1de..07ac85eb 100644 --- a/soy/elements.soy +++ b/soy/elements.soy @@ -123,7 +123,7 @@ * Renders an identity provider button. */ {template .idpButton} - {@param? providerConfig: [providerId:string|null, providerName:string|null, + {@param? providerConfig: [providerId:string, providerName:string|null, buttonColor:string|null, iconUrl: string|null]} /** The IdP provider config. */ +{/template} diff --git a/soy/elements_test.js b/soy/elements_test.js index 1702b947..4bab1384 100644 --- a/soy/elements_test.js +++ b/soy/elements_test.js @@ -40,6 +40,11 @@ var IJ_DATA_ = { 'password': '../image/mail.svg', 'phone': '../image/phone.svg', 'anonymous': '../image/anonymous.svg', + 'microsoft.com': '../image/microsoft.svg', + 'yahoo.com': '../image/yahoo.svg', + 'apple.com': '../image/apple.svg', + 'saml': '../image/saml.svg', + 'oidc': '../image/oidc.svg', }, 'defaultButtonColors': { 'google.com': '#ffffff', @@ -49,6 +54,11 @@ var IJ_DATA_ = { 'password': '#db4437', 'phone': '#02bd7e', 'anonymous': '#f4b400', + 'microsoft.com': '#2F2F2F', + 'yahoo.com': '#720E9E', + 'apple.com': '#000000', + 'saml': '#007bff', + 'oidc': '#007bff', }, 'defaultProviderNames': { 'google.com': 'Google', @@ -186,32 +196,40 @@ function testResendLink() { function testIdpButton() { var idpConfigs = [ { - providerId: 'password' + providerId: 'password', }, { - providerId: 'phone' + providerId: 'phone', }, { - providerId: 'google.com' + providerId: 'google.com', }, { - providerId: 'github.com' + providerId: 'github.com', }, { - providerId: 'facebook.com' + providerId: 'facebook.com', }, { - providerId: 'twitter.com' + providerId: 'twitter.com', }, { - providerId: 'anonymous' + providerId: 'anonymous', }, { providerId: 'microsoft.com', providerName: 'Microsoft', buttonColor: '#FFB6C1', iconUrl: 'icon-url', - loginHintKey: 'login_hint' + }, + { + providerId: 'yahoo.com', + }, + { + providerId: 'saml.provider', + }, + { + providerId: 'oidc.provider', }]; var root = goog.dom.getElement('idp-button'); for (var i = 0; i < idpConfigs.length; i++) { @@ -514,3 +532,34 @@ function testListboxDialogWithIcons() { firebaseui.auth.soy2.element.listBoxDialog, data, IJ_DATA_); attachShowDialogListener('show-list-box-with-icons', dialog); } + + +function testTenantSelectionButton() { + const tenantConfigs = [ + { + tenantId: 'TENANT_ID', + displayName: 'Contractor A', + buttonColor: '#FFB6C1', + iconUrl: 'icon-url', + }, + { + tenantId: null, + displayName: 'ACME', + buttonColor: '#53B2BF', + iconUrl: 'icon-url', + }]; + const root = goog.dom.getElement('tenant-selection-button'); + for (let i = 0; i < tenantConfigs.length; i++) { + const button = goog.soy.renderAsElement( + firebaseui.auth.soy2.element.tenantSelectionButton, + { + tenantConfig: tenantConfigs[i] + }, + IJ_DATA_); + root.appendChild(button); + const separator = goog.dom.createElement('div'); + goog.dom.setProperties(separator, {'style': 'height:15px'}); + root.appendChild(separator); + } +} + diff --git a/soy/elements_test_dom.html b/soy/elements_test_dom.html index 6c2f694f..db333578 100644 --- a/soy/elements_test_dom.html +++ b/soy/elements_test_dom.html @@ -243,4 +243,10 @@

Title

+ + + + tenantSelectionButton +
+ diff --git a/soy/pages.soy b/soy/pages.soy index 9d8587d1..318590ca 100644 --- a/soy/pages.soy +++ b/soy/pages.soy @@ -232,6 +232,18 @@ {/template} +/** + * Renders a page which simply shows a spinner. + */ +{template .spinner} +
+ {call firebaseui.auth.soy2.element.busyIndicator} + {param useSpinner: true /} + {/call} +
+{/template} + + /** * Renders a blank callback page which simply shows an activity loading indicator when calling a * remote API so that the user won't be confused by the blank page. @@ -800,7 +812,7 @@
{if $allowContinue}
- {call firebaseui.auth.soy2.element.submitButton /} + {call firebaseui.auth.soy2.element.continueButton /}
{/if}
@@ -845,7 +857,7 @@
{if $allowContinue}
- {call firebaseui.auth.soy2.element.submitButton /} + {call firebaseui.auth.soy2.element.continueButton /}
{/if}
@@ -881,7 +893,7 @@
{if $allowContinue}
- {call firebaseui.auth.soy2.element.submitButton /} + {call firebaseui.auth.soy2.element.continueButton /}
{/if}
@@ -945,7 +957,171 @@
{if $allowContinue}
- {call firebaseui.auth.soy2.element.submitButton /} + {call firebaseui.auth.soy2.element.continueButton /} +
+ {/if} +
+ +{/template} + + +/** + * Renders a notice page that confirms the user's email has been verified and updated. + */ +{template .verifyAndChangeEmailSuccess} + {@param email: string} /** The email address being verified and updated to. */ + {@param? allowContinue: bool} /** Whether to show the continue button. */ +
+
+

+ {msg desc="Header of a page telling the user their email address has been verified and + updated."} + Your email has been verified and changed + {/msg} +

+
+
+

+ {msg desc="Detailed info of a notice page that the email has been verified and updated."} + You can now sign in with your new email {$email}. + {/msg} +

+
+
+ {if $allowContinue} +
+ {call firebaseui.auth.soy2.element.continueButton /} +
+ {/if} +
+
+{/template} + + +/** + * Renders a notice page for email verification and update failure. + */ +{template .verifyAndChangeEmailFailure} + {@param? allowContinue: bool} /** Whether to show the continue button. */ +
+
+

+ {msg desc="Header of a page notifying that an email verification and + update request has failed"} + Try updating your email again + {/msg} +

+
+
+ {msg desc="Detailed info of a notice page that an email verification and update request has + failed."} +

+ Your request to verify and update your email has expired or the link has already been + used. +

+ {/msg} +
+
+ {if $allowContinue} +
+ {call firebaseui.auth.soy2.element.continueButton /} +
+ {/if} +
+
+{/template} + + +/** + * Renders a notice page for second factor addition revocation success. + */ +{template .revertSecondFactorAdditionSuccess} + {@param factorId: string} /** The factor ID of the second factor being removed. */ + {@param? phoneNumber: string} /** The phone number of the phone second factor being removed. */ + {@param? allowContinue: bool} /** Whether to show the continue button. */ +
+
+
+

+ {msg desc="Header of a page notifying that a second factor addition request has been + revoked"} + Removed second factor + {/msg} +

+
+
+

+ {switch $factorId} + {case 'phone'} + {msg desc="Detailed info of a notice page that a phone second factor addition request + has been revoked."} + The {$factorId} {$phoneNumber} was removed as a second + authentication step. + {/msg} + {default} + {msg desc="Detailed info of a notice page that a second factor addition request + has been revoked."} + The device or app was removed as a second authentication step. + {/msg} + {/switch} +

+

+ {msg desc="Message that explains to the user that they can reset their password if they + suspect someone has accessed their account."} + If you don't recognize this device, someone might be trying to access your account. + Consider + changing your password right away + . + {/msg} +

+
+
+ {if $allowContinue} +
+ {call firebaseui.auth.soy2.element.continueButton /} +
+ {/if} +
+
+
+{/template} + + +/** + * Renders a notice page for second factor addition revocation failure. + */ +{template .revertSecondFactorAdditionFailure} + {@param? allowContinue: bool} /** Whether to show the continue button. */ +
+
+

+ {msg desc="Header of a page notifying that the second factor addition revocation request + has failed"} + Couldn't remove your second factor + {/msg} +

+
+
+ {msg desc="Detailed info of a notice page that the second factor addition revocation request + has failed."} +

+ Something went wrong removing your second factor. +

+

+ Try removing it again. If that doesn't work, contact support for assistance. +

+ {/msg} +
+
+ {if $allowContinue} +
+ {call firebaseui.auth.soy2.element.continueButton /}
{/if}
@@ -953,6 +1129,40 @@ {/template} +/** + * Renders a notice page that shows that an recoverable error was encountered. + */ +{template .recoverableError} + {@param errorMessage: string} /** The detailed error message. */ + {@param? allowRetry: bool} /** Whether to show the retry button. */ +
+
+

+ {msg desc="Header of a notice page"}Error encountered{/msg} +

+
+
+

+ {$errorMessage} +

+
+
+
+ {if $allowRetry} + {call firebaseui.auth.soy2.element.submitButton} + {param label kind="text"} + {msg desc="The retry button to recover from error."} + Retry + {/msg} + {/param} + {/call} + {/if} +
+
+
+{/template} + + /** * Renders a notice page that shows that an unrecoverable error was encountered. */ @@ -1024,7 +1234,7 @@ * Renders the list of supported sign-in providers. */ {template .providerSignIn} - {@param providerConfigs: list<[providerId:string|null, providerName:string|null, + {@param providerConfigs: list<[providerId:string, providerName:string|null, buttonColor:string|null, iconUrl: string|null]>} /** List of supported IdP configs. */ {/template} + + +/** + * Renders a sign out notice page. + */ +{template .signOut} +
+
+

+ {msg desc="Header of a sign out notice page"} + Sign Out + {/msg} +

+
+
+

+ {msg desc="Detailed info of a sign out notice page."} + You are now successfully signed out. + {/msg} +

+
+
+{/template} + + +/** + * Renders the list of tenants to select. + */ +{template .selectTenant} + {@param tenantConfigs: list<[tenantId:string|null, displayName:string, buttonColor:string, + iconUrl: string]>} /** List of tenant selection button configs. */ +
+
+
+
    + {foreach $tenantConfig in $tenantConfigs} +
  • + {call firebaseui.auth.soy2.element.tenantSelectionButton} + {param tenantConfig: $tenantConfig /} + {/call} +
  • + {/foreach} +
+
+
+ +
+{/template} + + +/** + * Renders a provider match by email page. + */ +{template .providerMatchByEmail} +
+
+
+

+ {msg desc="Header of the page requesting a user's email to provide the best + matching sign-in methods associated with this email."} + Sign in + {/msg} +

+
+
+
+ {call firebaseui.auth.soy2.element.email /} +
+
+
+
+ {call firebaseui.auth.soy2.element.submitButton /} +
+
+ +
+
+{/template} diff --git a/soy/pages_test.js b/soy/pages_test.js index b7db3c82..98599bef 100644 --- a/soy/pages_test.js +++ b/soy/pages_test.js @@ -38,6 +38,11 @@ var IJ_DATA_ = { 'password': '../image/mail.svg', 'phone': '../image/phone.svg', 'anonymous': '../image/anonymous.svg', + 'microsoft.com': '../image/microsoft.svg', + 'yahoo.com': '../image/yahoo.svg', + 'apple.com': '../image/apple.svg', + 'saml': '../image/saml.svg', + 'oidc': '../image/oidc.svg', }, 'defaultButtonColors': { 'google.com': '#ffffff', @@ -47,6 +52,11 @@ var IJ_DATA_ = { 'password': '#db4437', 'phone': '#02bd7e', 'anonymous': '#f4b400', + 'microsoft.com': '#2F2F2F', + 'yahoo.com': '#720E9E', + 'apple.com': '#000000', + 'saml': '#007bff', + 'oidc': '#007bff', }, 'defaultProviderNames': { 'google.com': 'Google', @@ -219,6 +229,13 @@ function testBlank_busy() { } +function testSpinner() { + var root = goog.dom.getElement('spinner'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.spinner, {}, IJ_DATA_); +} + + function testEmailLinkSignInSent() { var root = goog.dom.getElement('email-link-sign-in-sent'); goog.soy.renderElement( @@ -459,6 +476,118 @@ function testEmailVerificationFailureNoContinue() { } +function testVerifyAndChangeEmailSuccess() { + var root = goog.dom.getElement('verify-and-change-email-success'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.verifyAndChangeEmailSuccess, + {'email': 'user@example.com', 'allowContinue': true}); +} + + +function testVerifyAndChangeEmailSuccessNoContinue() { + var root = goog.dom.getElement('verify-and-change-email-success-no-continue'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.verifyAndChangeEmailSuccess, + {'email': 'user@example.com', 'allowContinue': false}); +} + + +function testVerifyAndChangeEmailFailure() { + var root = goog.dom.getElement('verify-and-change-email-failure'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.verifyAndChangeEmailFailure, + {'allowContinue': true}); +} + + +function testVerifyAndChangeEmailFailureNoContinue() { + var root = goog.dom.getElement('verify-and-change-email-failure-no-continue'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.verifyAndChangeEmailFailure, + {'allowContinue': false}); +} + + +function testRevertSecondFactorAdditionSuccessPhone() { + var root = goog.dom.getElement('revert-second-factor-addition-success-phone'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.revertSecondFactorAdditionSuccess, + { + 'factorId': 'phone', + 'phoneNumber': '+*******1234', + 'allowContinue': true + }); +} + + +function testRevertSecondFactorAdditionSuccessDefault() { + var root = goog.dom.getElement( + 'revert-second-factor-addition-success-default'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.revertSecondFactorAdditionSuccess, + { + 'factorId': 'unknown', + 'allowContinue': true + }); +} + + +function testRevertSecondFactorAdditionSuccessNoContinue() { + var root = goog.dom.getElement( + 'revert-second-factor-addition-success-no-continue'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.revertSecondFactorAdditionSuccess, + { + 'factorId': 'phone', + 'phoneNumber': '+*******1234', + 'allowContinue': false + }); +} + + +function testRevertSecondFactorAdditionFailure() { + var root = goog.dom.getElement('revert-second-factor-addition-failure'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.revertSecondFactorAdditionFailure, + {'allowContinue': true}); +} + + +function testRevertSecondFactorAdditionFailureNoContinue() { + var root = goog.dom.getElement( + 'revert-second-factor-addition-failure-no-continue'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.revertSecondFactorAdditionFailure, + {'allowContinue': false}); +} + + +function testRecoverableError() { + var root = goog.dom.getElement('recoverable-error'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.recoverableError, + { + 'errorMessage': 'A network error (such as timeout, interrupted ' + + 'connection or unreachable host) has occurred.', + 'allowRetry': true + }, + IJ_DATA_); +} + + +function testRecoverableError_noRetryButton() { + var root = goog.dom.getElement('recoverable-error-no-retry-button'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.recoverableError, + { + 'errorMessage': 'A network error (such as timeout, interrupted ' + + 'connection or unreachable host) has occurred.', + 'allowRetry': false + }, + IJ_DATA_); +} + + function testUnrecoverableError() { var root = goog.dom.getElement('unrecoverable-error'); goog.soy.renderElement(root, firebaseui.auth.soy2.page.unrecoverableError, { @@ -507,7 +636,8 @@ function testProviderSignIn() { providerId: 'microsoft.com', providerName: 'Microsoft', buttonColor: '#FFB6C1', - iconUrl: 'icon-url' + iconUrl: 'icon-url', + loginHintKey: 'login_hint' }] }, IJ_DATA_); @@ -614,3 +744,49 @@ function testPhoneSignInFinish_noTos() { phoneNumber: '+13115552368' }, IJ_DATA_); } + + +function testSignOut() { + var root = goog.dom.getElement('sign-out'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.signOut, {}, IJ_DATA_); +} + + +function testTenantSelect() { + const root = goog.dom.getElement('select-tenant'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.selectTenant, { + tenantConfigs: [ + { + tenantId: 'TENANT_ID', + displayName: 'Contractor A', + buttonColor: '#FFB6C1', + iconUrl: 'icon-url', + }, + { + tenantId: null, + displayName: 'ACME', + buttonColor: '#53B2BF', + iconUrl: 'icon-url', + }], + }, + IJ_DATA_); +} + + +function testTenantSelect_noTenants() { + const root = goog.dom.getElement('select-tenant-no-tenants'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.selectTenant, { + tenantConfigs: [], + }, + IJ_DATA_); +} + + +function testProviderMatchByEmail() { + var root = goog.dom.getElement('provider-match-by-email'); + goog.soy.renderElement( + root, firebaseui.auth.soy2.page.providerMatchByEmail, {}, IJ_DATA_); +} diff --git a/soy/pages_test_dom.html b/soy/pages_test_dom.html index 8362f837..e2ba8f64 100644 --- a/soy/pages_test_dom.html +++ b/soy/pages_test_dom.html @@ -71,6 +71,12 @@
+ + + spinner +
+ + emailLinkSignInSent @@ -214,6 +220,54 @@
+ + + verifyAndChangeEmailSuccess +
+ + +
+ + + + + verifyAndChangeEmailFailure +
+ + +
+ + + + + revertSecondFactorAdditionSuccess +
+ + +
+ + +
+ + + + + revertSecondFactorAdditionFailure +
+ + +
+ + + + + signIn +
+ + +
+ + unrecoverableError @@ -258,4 +312,25 @@
+ + + + signOut +
+ + + + + selectTenant +
+ + +
+ + + + + providerMatchByEmail +
+ diff --git a/soy/strings.soy b/soy/strings.soy index 43f424f0..b7205309 100644 --- a/soy/strings.soy +++ b/soy/strings.soy @@ -293,6 +293,112 @@ {/template} +/** Translates an error code from CIAP to a user-displayable string. */ +{template .errorCIAP kind="text"} + {@param? code: string} /** The error code. */ + {switch $code} + {case 'invalid-argument'} + {msg desc="Error message for the invalid argument error."} + Client specified an invalid argument. + {/msg} + {case 'invalid-configuration'} + {msg desc="Error message for the invalid configuration error."} + Client specified an invalid project configuration. + {/msg} + {case 'failed-precondition'} + {msg desc="Error message for the failed precondition error."} + Request can not be executed in the current system state. + {/msg} + {case 'out-of-range'} + {msg desc="Error message for the out of range error."} + Client specified an invalid range. + {/msg} + {case 'unauthenticated'} + {msg desc="Error message for the unauthenticated request error."} + Request not authenticated due to missing, invalid, or expired OAuth token. + {/msg} + {case 'permission-denied'} + {msg desc="Error message for the permission denied error."} + Client does not have sufficient permission. + {/msg} + {case 'not-found'} + {msg desc="Error message for the resource not found error."} + Specified resource is not found. + {/msg} + {case 'aborted'} + {msg desc="Error message for the concurrency conflict error."} + Concurrency conflict, such as read-modify-write conflict. + {/msg} + {case 'already-exists'} + {msg desc="Error message for the resource already exists error."} + The resource that a client tried to create already exists. + {/msg} + {case 'resource-exhausted'} + {msg desc="Error message for the resource exhausted error."} + Either out of resource quota or reaching rate limiting. + {/msg} + {case 'cancelled'} + {msg desc="Error message for the request cancelled error."} + Request cancelled by the client. + {/msg} + {case 'data-loss'} + {msg desc="Error message the data loss error."} + Unrecoverable data loss or data corruption. + {/msg} + {case 'unknown'} + {msg desc="Error message for the unknown server error."} + Unknown server error. + {/msg} + {case 'internal'} + {msg desc="Error message for the internal server error."} + Internal server error. + {/msg} + {case 'not-implemented'} + {msg desc="Error message for the not implemented server API error."} + API method not implemented by the server. + {/msg} + {case 'unavailable'} + {msg desc="Error message for the unavailable service error."} + Service unavailable. + {/msg} + {case 'deadline-exceeded'} + {msg desc="Error message for the deadline exceeded error."} + Request deadline exceeded. + {/msg} + {case 'auth/user-disabled'} + {msg desc="Error message for the user disabled error."} + The user account has been disabled by an administrator. + {/msg} + {case 'auth/timeout'} + {msg desc="Error message for the timeout error."} + The operation has timed out. + {/msg} + {case 'auth/too-many-requests'} + {msg desc="Error message for too many failed login attempts in a short period of time."} + We have blocked all requests from this device due to unusual activity. Try again later. + {/msg} + {case 'auth/quota-exceeded'} + {msg desc="Error message for the quota exceeded error."} + The quota for this operation has been exceeded. Try again later. + {/msg} + {case 'auth/network-request-failed'} + {msg desc="Error message for the network request failed error."} + A network error has occurred. Try again later. + {/msg} + {case 'restart-process'} + {msg desc="Instructions for the user to restart the authentication process."} + An issue was encountered when authenticating your request. Please visit the URL that + redirected you to this page again to restart the authentication process. + {/msg} + {case 'no-matching-tenant-for-email'} + {msg desc="Instructions for the user to enter a different email when there is no matching + method of sign-in found for the given email."} + No sign-in provider is available for the given email, please try with a different email. + {/msg} + {/switch} +{/template} + + /** Resend countdown. */ {template .resendCountdown kind="text"} {@param timeRemaining:string} /** The time remaining. */ diff --git a/stylesheet/firebase-ui.css b/stylesheet/firebase-ui.css index aa325daa..a2ebaab8 100644 --- a/stylesheet/firebase-ui.css +++ b/stylesheet/firebase-ui.css @@ -244,17 +244,21 @@ input.firebaseui-input-invalid{ margin-right: 5px; } -.firebaseui-page-provider-sign-in { +.firebaseui-page-provider-sign-in, +.firebaseui-page-select-tenant +{ background: inherit; } -.firebaseui-idp-list { +.firebaseui-idp-list, +.firebaseui-tenant-list { list-style: none; margin: 1em 0; padding: 0; } -.firebaseui-idp-button { +.firebaseui-idp-button, +.firebaseui-tenant-button { direction: ltr; font-weight: 500; height: auto; @@ -266,7 +270,8 @@ input.firebaseui-input-invalid{ width: 100%; } -.firebaseui-idp-list > .firebaseui-list-item { +.firebaseui-idp-list > .firebaseui-list-item, +.firebaseui-tenant-list > .firebaseui-list-item { margin-bottom: 15px; text-align: center; } @@ -621,6 +626,11 @@ input.firebaseui-input-invalid{ height: 64px; } +.firebaseui-id-page-spinner { + background: inherit; + height: 64px; +} + .firebaseui-email-sent { background-image: url("https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/success_status.png"); background-position: center; diff --git a/translations/ar-XB.xtb b/translations/ar-XB.xtb index 25883b92..b0677bd0 100644 --- a/translations/ar-XB.xtb +++ b/translations/ar-XB.xtb @@ -12,11 +12,14 @@ ‏‮Philippines‬‏ ‏‮Romania‬‏ ‏‮South‬‏ ‏‮Georgia‬‏ ‏‮and‬‏ ‏‮the‬‏ ‏‮South‬‏ ‏‮Sandwich‬‏ ‏‮Islands‬‏ -‏‮OK‬‏ +‏‮Unrecoverable‬‏ ‏‮data‬‏ ‏‮loss‬‏ ‏‮or‬‏ ‏‮data‬‏ ‏‮corruption‬‏. ‏‮Italy‬‏ ‏‮Lebanon‬‏ ‏‮Guyana‬‏ ‏‮Guatemala‬‏ +‏‮Something‬‏ ‏‮went‬‏ ‏‮wrong‬‏ ‏‮removing‬‏ ‏‮your‬‏ ‏‮second‬‏ ‏‮factor‬‏.‏‮Try‬‏ ‏‮removing‬‏ ‏‮it‬‏ ‏‮again‬‏. ‏‮If‬‏ ‏‮that‬‏ ‏‮doesn‬‏'‏‮t‬‏ ‏‮work‬‏, ‏‮contact‬‏ ‏‮support‬‏ ‏‮for‬‏ ‏‮assistance‬‏. +‏‮Try‬‏ ‏‮updating‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮again‬‏ +‏‮A‬‏ ‏‮network‬‏ ‏‮error‬‏ ‏‮has‬‏ ‏‮occurred‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮later‬‏. ‏‮Enter‬‏ ‏‮a‬‏ ‏‮valid‬‏ ‏‮phone‬‏ ‏‮number‬‏ ‏‮By‬‏ ‏‮tapping‬‏ ‏‮Verify‬‏, ‏‮you‬‏ ‏‮are‬‏ ‏‮indicating‬‏ ‏‮that‬‏ ‏‮you‬‏ ‏‮accept‬‏ ‏‮our‬‏ ‏‮Terms‬‏ ‏‮of‬‏ ‏‮Service‬‏ ‏‮and‬‏ ‏‮Privacy‬‏ ‏‮Policy‬‏. ‏‮An‬‏ ‏‮SMS‬‏ ‏‮may‬‏ ‏‮be‬‏ ‏‮sent‬‏. ‏‮Message‬‏ &amp; ‏‮data‬‏ ‏‮rates‬‏ ‏‮may‬‏ ‏‮apply‬‏. ‏‮Enter‬‏ ‏‮your‬‏ ‏‮password‬‏ @@ -27,6 +30,7 @@ ‏‮Too‬‏ ‏‮many‬‏ ‏‮account‬‏ ‏‮requests‬‏ ‏‮are‬‏ ‏‮coming‬‏ ‏‮from‬‏ ‏‮your‬‏ ‏‮IP‬‏ ‏‮address‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮in‬‏ ‏‮a‬‏ ‏‮few‬‏ ‏‮minutes‬‏. ‏‮Sri‬‏ ‏‮Lanka‬‏ ‏‮You‬‏ ‏‮can‬‏ ‏‮now‬‏ ‏‮sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮your‬‏ ‏‮new‬‏ ‏‮account‬‏ +‏‮Client‬‏ ‏‮specified‬‏ ‏‮an‬‏ ‏‮invalid‬‏ ‏‮argument‬‏. ‏‮Chad‬‏ ‏‮Resend‬‏ ‏‮code‬‏ ‏‮in‬‏ ‏‮Belarus‬‏ @@ -62,6 +66,7 @@ ‏‮Github‬‏ ‏‮Privacy‬‏ ‏‮Policy‬‏ ‏‮Guam‬‏ +‏‮Either‬‏ ‏‮out‬‏ ‏‮of‬‏ ‏‮resource‬‏ ‏‮quota‬‏ ‏‮or‬‏ ‏‮reaching‬‏ ‏‮rate‬‏ ‏‮limiting‬‏. ‏‮Croatia‬‏ ‏‮Lithuania‬‏ ‏‮Malta‬‏ @@ -71,22 +76,28 @@ ‏‮Guest‬‏ ‏‮Ghana‬‏ ‏‮The‬‏ ‏‮browser‬‏ ‏‮you‬‏ ‏‮are‬‏ ‏‮using‬‏ ‏‮does‬‏ ‏‮not‬‏ ‏‮support‬‏ ‏‮Web‬‏ ‏‮Storage‬‏. ‏‮Please‬‏ ‏‮try‬‏ ‏‮again‬‏ ‏‮in‬‏ ‏‮a‬‏ ‏‮different‬‏ ‏‮browser‬‏. +‏‮Request‬‏ ‏‮cancelled‬‏ ‏‮by‬‏ ‏‮the‬‏ ‏‮client‬‏. ‏‮There‬‏ ‏‮was‬‏ ‏‮a‬‏ ‏‮problem‬‏ ‏‮changing‬‏ ‏‮your‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮email‬‏ ‏‮back‬‏.‏‮If‬‏ ‏‮you‬‏ ‏‮try‬‏ ‏‮again‬‏ ‏‮and‬‏ ‏‮still‬‏ ‏‮can‬‏’‏‮t‬‏ ‏‮reset‬‏ ‏‮your‬‏ ‏‮email‬‏, ‏‮try‬‏ ‏‮asking‬‏ ‏‮your‬‏ ‏‮administrator‬‏ ‏‮for‬‏ ‏‮help‬‏. ‏‮Bahrain‬‏ +‏‮Internal‬‏ ‏‮server‬‏ ‏‮error‬‏. ‏‮Iran‬‏ +‏‮GitHub‬‏ ‏‮Verifying‬‏ ‏‮you‬‏'‏‮re‬‏ ‏‮not‬‏ ‏‮a‬‏ ‏‮robot‬‏... ‏‮This‬‏ ‏‮code‬‏ ‏‮is‬‏ ‏‮no‬‏ ‏‮longer‬‏ ‏‮valid‬‏ ‏‮New‬‏ ‏‮Caledonia‬‏ ‏‮Monaco‬‏ ‏‮Seychelles‬‏ +‏‮The‬‏ ‏‮was‬‏ ‏‮removed‬‏ ‏‮as‬‏ ‏‮a‬‏ ‏‮second‬‏ ‏‮authentication‬‏ ‏‮step‬‏. ‏‮The‬‏ ‏‮current‬‏ ‏‮anonymous‬‏ ‏‮user‬‏ ‏‮failed‬‏ ‏‮to‬‏ ‏‮upgrade‬‏. ‏‮The‬‏ ‏‮non‬‏-‏‮anonymous‬‏ ‏‮credential‬‏ ‏‮is‬‏ ‏‮already‬‏ ‏‮associated‬‏ ‏‮with‬‏ ‏‮a‬‏ ‏‮different‬‏ ‏‮user‬‏ ‏‮account‬‏. ‏‮You‬‏’‏‮ve‬‏ ‏‮entered‬‏ ‏‮an‬‏ ‏‮incorrect‬‏ ‏‮password‬‏ ‏‮too‬‏ ‏‮many‬‏ ‏‮times‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮in‬‏ ‏‮a‬‏ ‏‮few‬‏ ‏‮minutes‬‏. ‏‮Enter‬‏ ‏‮a‬‏ ‏‮valid‬‏ ‏‮phone‬‏ ‏‮number‬‏. ‏‮Your‬‏ ‏‮request‬‏ ‏‮to‬‏ ‏‮reset‬‏ ‏‮your‬‏ ‏‮password‬‏ ‏‮has‬‏ ‏‮expired‬‏ ‏‮or‬‏ ‏‮the‬‏ ‏‮link‬‏ ‏‮has‬‏ ‏‮already‬‏ ‏‮been‬‏ ‏‮used‬‏ +‏‮You‬‏ ‏‮can‬‏ ‏‮now‬‏ ‏‮sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮your‬‏ ‏‮new‬‏ ‏‮email‬‏ . ‏‮The‬‏ ‏‮country‬‏ ‏‮code‬‏ ‏‮provided‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮supported‬‏. ‏‮Check‬‏ ‏‮that‬‏ ‏‮you‬‏ ‏‮did‬‏ ‏‮not‬‏ ‏‮misspell‬‏ ‏‮your‬‏ ‏‮email‬‏. ‏‮Twitter‬‏ ‏‮Cambodia‬‏ +‏‮Service‬‏ ‏‮unavailable‬‏. ‏‮Portugal‬‏ ‏‮French‬‏ ‏‮Guiana‬‏ ‏‮British‬‏ ‏‮Virgin‬‏ ‏‮Islands‬‏ @@ -102,6 +113,7 @@ ‏‮Verify‬‏ ‏‮South‬‏ ‏‮Africa‬‏ ‏‮Enter‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮address‬‏ ‏‮to‬‏ ‏‮continue‬‏ +‏‮The‬‏ ‏‮user‬‏ ‏‮account‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮disabled‬‏ ‏‮by‬‏ ‏‮an‬‏ ‏‮administrator‬‏. ‏‮A‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮email‬‏ ‏‮with‬‏ ‏‮additional‬‏ ‏‮instructions‬‏ ‏‮was‬‏ ‏‮sent‬‏ ‏‮to‬‏ . ‏‮Check‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮to‬‏ ‏‮complete‬‏ ‏‮sign‬‏-‏‮in‬‏. ‏‮Enter‬‏ ‏‮your‬‏ ‏‮password‬‏ ‏‮Create‬‏ ‏‮account‬‏ @@ -109,13 +121,14 @@ ‏‮By‬‏ ‏‮tapping‬‏ ‏‮you‬‏ ‏‮are‬‏ ‏‮indicating‬‏ ‏‮that‬‏ ‏‮you‬‏ ‏‮agree‬‏ ‏‮to‬‏ ‏‮the‬‏ . ‏‮Canada‬‏ ‏‮Denmark‬‏ +‏‮Request‬‏ ‏‮deadline‬‏ ‏‮exceeded‬‏. ‏‮Austria‬‏ ‏‮Emails‬‏ ‏‮don‬‏'‏‮t‬‏ ‏‮match‬‏ ‏‮South‬‏ ‏‮Sudan‬‏ ‏‮Yemen‬‏ ‏‮Guinea‬‏-‏‮Bissau‬‏ ‏‮Saudi‬‏ ‏‮Arabia‬‏ -‏‮App‬‏ ‏‮logo‬‏ +‏‮An‬‏ ‏‮extra‬‏ ‏‮security‬‏ ‏‮factor‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮added‬‏ ‏‮in‬‏ ‏‮U‬‏.‏‮S‬‏. ‏‮Virgin‬‏ ‏‮Islands‬‏ ‏‮Sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮phone‬‏ ‏‮Indonesia‬‏ @@ -123,6 +136,7 @@ ‏‮Venezuela‬‏ ‏‮Trouble‬‏ ‏‮signing‬‏ ‏‮in‬‏? ‏‮Verify‬‏ ‏‮it‬‏'‏‮s‬‏ ‏‮you‬‏ +‏‮An‬‏ ‏‮issue‬‏ ‏‮was‬‏ ‏‮encountered‬‏ ‏‮when‬‏ ‏‮authenticating‬‏ ‏‮your‬‏ ‏‮request‬‏. ‏‮Please‬‏ ‏‮visit‬‏ ‏‮the‬‏ ‏‮URL‬‏ ‏‮that‬‏ ‏‮redirected‬‏ ‏‮you‬‏ ‏‮to‬‏ ‏‮this‬‏ ‏‮page‬‏ ‏‮again‬‏ ‏‮to‬‏ ‏‮restart‬‏ ‏‮the‬‏ ‏‮authentication‬‏ ‏‮process‬‏. ‏‮Dominica‬‏ ‏‮New‬‏ ‏‮password‬‏ ‏‮Gabon‬‏ @@ -140,6 +154,7 @@ ‏‮Gambia‬‏ ‏‮Done‬‏ ‏‮Cancel‬‏ +‏‮Unknown‬‏ ‏‮server‬‏ ‏‮error‬‏. ‏‮Enter‬‏ ‏‮your‬‏ ‏‮account‬‏ ‏‮name‬‏ ‏‮Iraq‬‏ ‏‮Vietnam‬‏ @@ -151,6 +166,8 @@ ‏‮Nepal‬‏ ‏‮Tokelau‬‏ ‏‮The‬‏ ‏‮selected‬‏ ‏‮credential‬‏ ‏‮for‬‏ ‏‮the‬‏ ‏‮authentication‬‏ ‏‮provider‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮supported‬‏! +‏‮No‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮provider‬‏ ‏‮is‬‏ ‏‮available‬‏ ‏‮for‬‏ ‏‮the‬‏ ‏‮given‬‏ ‏‮email‬‏, ‏‮please‬‏ ‏‮try‬‏ ‏‮with‬‏ ‏‮a‬‏ ‏‮different‬‏ ‏‮email‬‏. +‏‮Please‬‏ ‏‮enter‬‏ ‏‮a‬‏ ‏‮first‬‏ ‏‮and‬‏ ‏‮last‬‏ ‏‮name‬‏. ‏‮Colombia‬‏ ‏‮By‬‏ ‏‮tapping‬‏ ‏‮SAVE‬‏ ‏‮you‬‏ ‏‮are‬‏ ‏‮indicating‬‏ ‏‮that‬‏ ‏‮you‬‏ ‏‮agree‬‏ ‏‮to‬‏ ‏‮the‬‏ ‏‮Burundi‬‏ @@ -158,7 +175,7 @@ ‏‮Twitter‬‏ ‏‮Your‬‏ ‏‮email‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮verified‬‏ ‏‮Papua‬‏ ‏‮New‬‏ ‏‮Guinea‬‏ -‏‮Save‬‏ +‏‮Specified‬‏ ‏‮resource‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮found‬‏. ‏‮Verifying‬‏... ‏‮Sign‬‏ ‏‮In‬‏ ‏‮East‬‏ ‏‮Timor‬‏ @@ -191,6 +208,7 @@ ‏‮Phone‬‏ ‏‮Resend‬‏ ‏‮Honduras‬‏ +‏‮The‬‏ ‏‮operation‬‏ ‏‮has‬‏ ‏‮timed‬‏ ‏‮out‬‏. ‏‮Niger‬‏ ‏‮That‬‏ ‏‮email‬‏ ‏‮address‬‏ ‏‮doesn‬‏'‏‮t‬‏ ‏‮match‬‏ ‏‮an‬‏ ‏‮existing‬‏ ‏‮account‬‏ ‏‮Jordan‬‏ @@ -202,6 +220,7 @@ ‏‮Israel‬‏ ‏‮Error‬‏ ‏‮encountered‬‏ ‏‮Number‬‏ +‏‮Client‬‏ ‏‮does‬‏ ‏‮not‬‏ ‏‮have‬‏ ‏‮sufficient‬‏ ‏‮permission‬‏. ‏‮Solve‬‏ ‏‮the‬‏ ‏‮reCAPTCHA‬‏ ‏‮Nicaragua‬‏ ‏‮Burkina‬‏ ‏‮Faso‬‏ @@ -210,14 +229,18 @@ ‏‮Turks‬‏ ‏‮and‬‏ ‏‮Caicos‬‏ ‏‮Islands‬‏ ‏‮Resend‬‏ ‏‮code‬‏ ‏‮in‬‏ ‏‮Check‬‏ ‏‮your‬‏ ‏‮email‬‏ +‏‮Couldn‬‏'‏‮t‬‏ ‏‮remove‬‏ ‏‮your‬‏ ‏‮second‬‏ ‏‮factor‬‏ ‏‮Add‬‏ ‏‮password‬‏ ‏‮El‬‏ ‏‮Salvador‬‏ +‏‮To‬‏ ‏‮change‬‏ ‏‮password‬‏ ‏‮to‬‏ ‏‮your‬‏ ‏‮account‬‏, ‏‮you‬‏ ‏‮will‬‏ ‏‮need‬‏ ‏‮to‬‏ ‏‮sign‬‏ ‏‮in‬‏ ‏‮again‬‏. ‏‮Serbia‬‏ ‏‮Belize‬‏ ‏‮for‬‏ ‏‮For‬‏ ‏‮this‬‏ ‏‮flow‬‏ ‏‮to‬‏ ‏‮successfully‬‏ ‏‮connect‬‏ ‏‮your‬‏ ‏‮account‬‏ ‏‮with‬‏ ‏‮this‬‏ ‏‮email‬‏, ‏‮you‬‏ ‏‮have‬‏ ‏‮to‬‏ ‏‮open‬‏ ‏‮the‬‏ ‏‮link‬‏ ‏‮on‬‏ ‏‮the‬‏ ‏‮same‬‏ ‏‮device‬‏ ‏‮or‬‏ ‏‮browser‬‏. ‏‮Sweden‬‏ ‏‮Saint‬‏ ‏‮Martin‬‏ + +‏‮Your‬‏ ‏‮request‬‏ ‏‮to‬‏ ‏‮verify‬‏ ‏‮and‬‏ ‏‮update‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮has‬‏ ‏‮expired‬‏ ‏‮or‬‏ ‏‮the‬‏ ‏‮link‬‏ ‏‮has‬‏ ‏‮already‬‏ ‏‮been‬‏ ‏‮used‬‏. ‏‮St‬‏. ‏‮Lucia‬‏ @‏‮string‬‏/‏‮app‬‏_‏‮name‬‏ ‏‮Check‬‏ ‏‮your‬‏ ‏‮email‬‏ @@ -225,13 +248,15 @@ ‏‮Oman‬‏ ‏‮Caribbean‬‏ ‏‮Netherlands‬‏ ‏‮Updated‬‏ ‏‮email‬‏ ‏‮address‬‏ -‏‮Sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮Twitter‬‏ +‏‮You‬‏ ‏‮are‬‏ ‏‮now‬‏ ‏‮successfully‬‏ ‏‮signed‬‏ ‏‮out‬‏. +‏‮Sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮phone‬‏ ‏‮Phone‬‏ ‏‮number‬‏ ‏‮You‬‏ ‏‮already‬‏ ‏‮have‬‏ ‏‮an‬‏ ‏‮account‬‏ ‏‮Thailand‬‏ ‏‮Wrong‬‏ ‏‮code‬‏. ‏‮Try‬‏ ‏‮again‬‏. ‏‮Liechtenstein‬‏ ‏‮Edit‬‏ ‏‮name‬‏ +‏‮The‬‏ ‏‮device‬‏ ‏‮or‬‏ ‏‮app‬‏ ‏‮was‬‏ ‏‮removed‬‏ ‏‮as‬‏ ‏‮a‬‏ ‏‮second‬‏ ‏‮authentication‬‏ ‏‮step‬‏. ‏‮Try‬‏ ‏‮opening‬‏ ‏‮the‬‏ ‏‮link‬‏ ‏‮using‬‏ ‏‮the‬‏ ‏‮same‬‏ ‏‮device‬‏ ‏‮or‬‏ ‏‮browser‬‏ ‏‮where‬‏ ‏‮you‬‏ ‏‮started‬‏ ‏‮the‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮process‬‏. ‏‮Netherlands‬‏ ‏‮Equatorial‬‏ ‏‮Guinea‬‏ @@ -251,11 +276,12 @@ ‏‮Something‬‏ ‏‮went‬‏ ‏‮wrong‬‏. ‏‮Please‬‏ ‏‮try‬‏ ‏‮again‬‏. ‏‮Password‬‏ ‏‮St‬‏. ‏‮Kitts‬‏ + ‏‮Back‬‏ ‏‮Enter‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮address‬‏ ‏‮to‬‏ ‏‮continue‬‏ ‏‮Verify‬‏ ‏‮your‬‏ ‏‮phone‬‏ ‏‮number‬‏ ‏‮Liberia‬‏ -‏‮An‬‏ ‏‮account‬‏ ‏‮already‬‏ ‏‮exists‬‏ ‏‮with‬‏ ‏‮that‬‏ ‏‮email‬‏ ‏‮address‬‏. +‏‮Sign‬‏ ‏‮Out‬‏ ‏‮Madagascar‬‏ ‏‮Kazakhstan‬‏ ‏‮Signing‬‏ ‏‮in‬‏... @@ -275,18 +301,18 @@ ‏‮Email‬‏ ‏‮This‬‏ ‏‮phone‬‏ ‏‮number‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮used‬‏ ‏‮too‬‏ ‏‮many‬‏ ‏‮times‬‏ ‏‮The‬‏ ‏‮email‬‏ ‏‮provided‬‏ ‏‮does‬‏ ‏‮not‬‏ ‏‮match‬‏ ‏‮the‬‏ ‏‮current‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮session‬‏. -‏‮This‬‏ ‏‮phone‬‏ ‏‮number‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮used‬‏ ‏‮too‬‏ ‏‮many‬‏ ‏‮times‬‏. - -‏‮Sign‬‏ ‏‮in‬‏ ‏‮to‬‏ </p></p> - +‏‮Twitter‬‏ +‏‮Unlink‬‏ ‏‮account‬‏ ‏‮Check‬‏ ‏‮that‬‏ ‏‮your‬‏ ‏‮inbox‬‏ ‏‮space‬‏ ‏‮is‬‏ ‏‮not‬‏ ‏‮running‬‏ ‏‮out‬‏ ‏‮or‬‏ ‏‮other‬‏ ‏‮inbox‬‏ ‏‮settings‬‏ ‏‮related‬‏ ‏‮issues‬‏. +‏‮Client‬‏ ‏‮specified‬‏ ‏‮an‬‏ ‏‮invalid‬‏ ‏‮range‬‏. +‏‮The‬‏ ‏‮quota‬‏ ‏‮for‬‏ ‏‮this‬‏ ‏‮operation‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮exceeded‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮later‬‏. ‏‮Paraguay‬‏ ‏‮Algeria‬‏ ‏‮You‬‏ ‏‮have‬‏ ‏‮entered‬‏ ‏‮an‬‏ ‏‮incorrect‬‏ ‏‮password‬‏ ‏‮too‬‏ ‏‮many‬‏ ‏‮times‬‏. ‏‮Please‬‏ ‏‮try‬‏ ‏‮again‬‏ ‏‮in‬‏ ‏‮a‬‏ ‏‮few‬‏ ‏‮minutes‬‏. ‏‮Norfolk‬‏ ‏‮Island‬‏ ‏‮Phone‬‏ ‏‮number‬‏ ‏‮automatically‬‏ ‏‮verified‬‏ -‏‮In‬‏ ‏‮order‬‏ ‏‮to‬‏ ‏‮change‬‏ ‏‮your‬‏ ‏‮password‬‏, ‏‮you‬‏ ‏‮first‬‏ ‏‮need‬‏ ‏‮to‬‏ ‏‮enter‬‏ ‏‮your‬‏ ‏‮current‬‏ ‏‮password‬‏. -‏‮Back‬‏ +‏‮Network‬‏ ‏‮error‬‏, ‏‮check‬‏ ‏‮your‬‏ ‏‮internet‬‏ ‏‮connection‬‏. +‏‮Verified‬‏! ‏‮India‬‏ ‏‮Benin‬‏ ‏‮Continue‬‏ @@ -335,27 +361,33 @@ ‏‮Email‬‏ ‏‮Next‬‏ ‏‮This‬‏ ‏‮code‬‏ ‏‮is‬‏ ‏‮no‬‏ ‏‮longer‬‏ ‏‮valid‬‏ -‏‮Sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮Facebook‬‏ +‏‮A‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮email‬‏ ‏‮with‬‏ ‏‮additional‬‏ ‏‮instrucitons‬‏ ‏‮was‬‏ ‏‮sent‬‏ ‏‮to‬‏ . ‏‮Check‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮to‬‏ ‏‮complete‬‏ ‏‮sign‬‏-‏‮in‬‏. ‏‮Confirm‬‏ ‏‮email‬‏ ‏‮First‬‏ &amp; ‏‮last‬‏ ‏‮name‬‏ ‏‮Grenada‬‏ ‏‮Western‬‏ ‏‮Sahara‬‏ +‏‮Request‬‏ ‏‮can‬‏ ‏‮not‬‏ ‏‮be‬‏ ‏‮executed‬‏ ‏‮in‬‏ ‏‮the‬‏ ‏‮current‬‏ ‏‮system‬‏ ‏‮state‬‏. ‏‮There‬‏ ‏‮was‬‏ ‏‮a‬‏ ‏‮problem‬‏ ‏‮verifying‬‏ ‏‮your‬‏ ‏‮phone‬‏ ‏‮number‬‏. ‏‮You‬‏’‏‮ve‬‏ ‏‮already‬‏ ‏‮used‬‏ ‏‮to‬‏ ‏‮sign‬‏ ‏‮in‬‏. ‏‮Enter‬‏ ‏‮your‬‏ ‏‮password‬‏ ‏‮for‬‏ ‏‮that‬‏ ‏‮account‬‏. +‏‮Sign‬‏-‏‮in‬‏ ‏‮email‬‏ ‏‮Sent‬‏ ‏‮Ireland‬‏ ‏‮Democratic‬‏ ‏‮Republic‬‏ ‏‮Congo‬‏ ‏‮Recover‬‏ ‏‮password‬‏ ‏‮Republic‬‏ ‏‮of‬‏ ‏‮Congo‬‏ +‏‮The‬‏ ‏‮resource‬‏ ‏‮that‬‏ ‏‮a‬‏ ‏‮client‬‏ ‏‮tried‬‏ ‏‮to‬‏ ‏‮create‬‏ ‏‮already‬‏ ‏‮exists‬‏. ‏‮Continue‬‏ ‏‮as‬‏ ‏‮guest‬‏ ‏‮San‬‏ ‏‮Marino‬‏ ‏‮Cyprus‬‏ ‏‮Solomon‬‏ ‏‮Islands‬‏ ‏‮Continue‬‏ ‏‮with‬‏ ? +‏‮To‬‏ ‏‮continue‬‏ ‏‮sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮on‬‏ ‏‮this‬‏ ‏‮device‬‏, ‏‮you‬‏ ‏‮have‬‏ ‏‮to‬‏ ‏‮recover‬‏ ‏‮the‬‏ ‏‮password‬‏. ‏‮Maldives‬‏ ‏‮S‬‏ã‏‮o‬‏ ‏‮Tom‬‏é ‏‮and‬‏ ‏‮Pr‬‏í‏‮ncipe‬‏ ‏‮Confirm‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮to‬‏ ‏‮complete‬‏ ‏‮sign‬‏ ‏‮in‬‏ ‏‮Slovenia‬‏ ‏‮South‬‏ ‏‮Korea‬‏ +‏‮Concurrency‬‏ ‏‮conflict‬‏, ‏‮such‬‏ ‏‮as‬‏ ‏‮read‬‏-‏‮modify‬‏-‏‮write‬‏ ‏‮conflict‬‏. +‏‮API‬‏ ‏‮method‬‏ ‏‮not‬‏ ‏‮implemented‬‏ ‏‮by‬‏ ‏‮the‬‏ ‏‮server‬‏. ‏‮Lesotho‬‏ ‏‮Sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮phone‬‏ ‏‮Try‬‏ ‏‮verifying‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮again‬‏ @@ -375,9 +407,11 @@ ‏‮to‬‏ ‏‮sign‬‏ ‏‮in‬‏. ‏‮Enter‬‏ ‏‮your‬‏ ‏‮password‬‏ ‏‮for‬‏ ‏‮that‬‏ ‏‮account‬‏. {plural_var,plural, =1{‏‮Password‬‏ ‏‮not‬‏ ‏‮strong‬‏ ‏‮enough‬‏. ‏‮Use‬‏ ‏‮at‬‏ ‏‮least‬‏ ‏‮character‬‏ ‏‮and‬‏ ‏‮a‬‏ ‏‮mix‬‏ ‏‮of‬‏ ‏‮letters‬‏ ‏‮and‬‏ ‏‮numbers‬‏}zero{‏‮Password‬‏ ‏‮not‬‏ ‏‮strong‬‏ ‏‮enough‬‏. ‏‮Use‬‏ ‏‮at‬‏ ‏‮least‬‏ ‏‮characters‬‏ ‏‮and‬‏ ‏‮a‬‏ ‏‮mix‬‏ ‏‮of‬‏ ‏‮letters‬‏ ‏‮and‬‏ ‏‮numbers‬‏}two{‏‮Password‬‏ ‏‮not‬‏ ‏‮strong‬‏ ‏‮enough‬‏. ‏‮Use‬‏ ‏‮at‬‏ ‏‮least‬‏ ‏‮characters‬‏ ‏‮and‬‏ ‏‮a‬‏ ‏‮mix‬‏ ‏‮of‬‏ ‏‮letters‬‏ ‏‮and‬‏ ‏‮numbers‬‏}few{‏‮Password‬‏ ‏‮not‬‏ ‏‮strong‬‏ ‏‮enough‬‏. ‏‮Use‬‏ ‏‮at‬‏ ‏‮least‬‏ ‏‮characters‬‏ ‏‮and‬‏ ‏‮a‬‏ ‏‮mix‬‏ ‏‮of‬‏ ‏‮letters‬‏ ‏‮and‬‏ ‏‮numbers‬‏}many{‏‮Password‬‏ ‏‮not‬‏ ‏‮strong‬‏ ‏‮enough‬‏. ‏‮Use‬‏ ‏‮at‬‏ ‏‮least‬‏ ‏‮characters‬‏ ‏‮and‬‏ ‏‮a‬‏ ‏‮mix‬‏ ‏‮of‬‏ ‏‮letters‬‏ ‏‮and‬‏ ‏‮numbers‬‏}other{‏‮Password‬‏ ‏‮not‬‏ ‏‮strong‬‏ ‏‮enough‬‏. ‏‮Use‬‏ ‏‮at‬‏ ‏‮least‬‏ ‏‮characters‬‏ ‏‮and‬‏ ‏‮a‬‏ ‏‮mix‬‏ ‏‮of‬‏ ‏‮letters‬‏ ‏‮and‬‏ ‏‮numbers‬‏}} ‏‮Zimbabwe‬‏ -‏‮Follow‬‏ ‏‮the‬‏ ‏‮instructions‬‏ ‏‮sent‬‏ ‏‮to‬‏ ‏‮to‬‏ ‏‮recover‬‏ ‏‮your‬‏ ‏‮password‬‏. -‏‮There‬‏ ‏‮was‬‏ ‏‮a‬‏ ‏‮problem‬‏ ‏‮verifying‬‏ ‏‮your‬‏ ‏‮phone‬‏ ‏‮number‬‏ +‏‮Request‬‏ ‏‮not‬‏ ‏‮authenticated‬‏ ‏‮due‬‏ ‏‮to‬‏ ‏‮missing‬‏, ‏‮invalid‬‏, ‏‮or‬‏ ‏‮expired‬‏ ‏‮OAuth‬‏ ‏‮token‬‏. +‏‮Please‬‏ ‏‮provide‬‏ ‏‮a‬‏ 6-‏‮digit‬‏ ‏‮phone‬‏ ‏‮verification‬‏ ‏‮code‬‏. +‏‮Choose‬‏ ‏‮password‬‏ ‏‮New‬‏ ‏‮device‬‏ ‏‮or‬‏ ‏‮browser‬‏ ‏‮detected‬‏ +‏‮Sign‬‏ ‏‮in‬‏ ‏‮to‬‏ ‏‮Czech‬‏ ‏‮Republic‬‏ ‏‮Tuvalu‬‏ ‏‮Sign‬‏ ‏‮in‬‏ ‏‮with‬‏ ‏‮GitHub‬‏ @@ -403,6 +437,10 @@ ‏‮Spain‬‏ ‏‮Costa‬‏ ‏‮Rica‬‏ ‏‮Samoa‬‏ + +‏‮Your‬‏ ‏‮account‬‏ ‏‮in‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮updated‬‏ ‏‮with‬‏ ‏‮for‬‏ ‏‮two‬‏-‏‮factor‬‏ ‏‮authentication‬‏. +‏‮If‬‏ ‏‮you‬‏ ‏‮did‬‏ ‏‮not‬‏ ‏‮request‬‏ ‏‮this‬‏ ‏‮modification‬‏, ‏‮please‬‏ ‏‮use‬‏ ‏‮this‬‏ ‏‮the‬‏ ‏‮following‬‏ ‏‮link‬‏ ‏‮to‬‏ ‏‮undo‬‏ ‏‮the‬‏ ‏‮change‬‏. + ‏‮Mali‬‏ ‏‮Qatar‬‏ ‏‮Uruguay‬‏ @@ -433,6 +471,7 @@ ‏‮Bermuda‬‏ ‏‮Niue‬‏ ‏‮Guadeloupe‬‏ +‏‮Client‬‏ ‏‮specified‬‏ ‏‮an‬‏ ‏‮invalid‬‏ ‏‮project‬‏ ‏‮configuration‬‏. ‏‮Palestinian‬‏ ‏‮Territories‬‏ ‏‮Slovakia‬‏ ‏‮Guernsey‬‏ @@ -445,19 +484,24 @@ ‏‮Malaysia‬‏ ‏‮Cocos‬‏ [‏‮Keeling‬‏] ‏‮Islands‬‏ ‏‮If‬‏ ‏‮you‬‏ ‏‮still‬‏ ‏‮want‬‏ ‏‮to‬‏ ‏‮connect‬‏ ‏‮your‬‏ ‏‮account‬‏, ‏‮open‬‏ ‏‮the‬‏ ‏‮link‬‏ ‏‮on‬‏ ‏‮the‬‏ ‏‮same‬‏ ‏‮device‬‏ ‏‮where‬‏ ‏‮you‬‏ ‏‮started‬‏ ‏‮sign‬‏-‏‮in‬‏. ‏‮Otherwise‬‏, ‏‮tap‬‏ ‏‮Continue‬‏ ‏‮to‬‏ ‏‮sign‬‏-‏‮in‬‏ ‏‮on‬‏ ‏‮this‬‏ ‏‮device‬‏. +‏‮Retry‬‏ ‏‮You‬‏ ‏‮originally‬‏ ‏‮intended‬‏ ‏‮to‬‏ ‏‮connect‬‏ ‏‮to‬‏ ‏‮your‬‏ ‏‮email‬‏ ‏‮account‬‏ ‏‮but‬‏ ‏‮have‬‏ ‏‮opened‬‏ ‏‮the‬‏ ‏‮link‬‏ ‏‮on‬‏ ‏‮a‬‏ ‏‮different‬‏ ‏‮device‬‏ ‏‮where‬‏ ‏‮you‬‏ ‏‮are‬‏ ‏‮not‬‏ ‏‮signed‬‏ ‏‮in‬‏. ‏‮Resend‬‏ ‏‮code‬‏ ‏‮in‬‏ 0: ‏‮Morocco‬‏ +‏‮If‬‏ ‏‮you‬‏ ‏‮don‬‏'‏‮t‬‏ ‏‮recognize‬‏ ‏‮this‬‏ ‏‮device‬‏, ‏‮someone‬‏ ‏‮might‬‏ ‏‮be‬‏ ‏‮trying‬‏ ‏‮to‬‏ ‏‮access‬‏ ‏‮your‬‏ ‏‮account‬‏. ‏‮Consider‬‏ ‏‮changing‬‏ ‏‮your‬‏ ‏‮password‬‏ ‏‮right‬‏ ‏‮away‬‏. ‏‮Bangladesh‬‏ ‏‮Phone‬‏ ‏‮Finland‬‏ ‏‮United‬‏ ‏‮Arab‬‏ ‏‮Emirates‬‏ +‏‮Your‬‏ ‏‮email‬‏ ‏‮has‬‏ ‏‮been‬‏ ‏‮verified‬‏ ‏‮and‬‏ ‏‮changed‬‏ ‏‮Password‬‏ ‏‮Central‬‏ ‏‮African‬‏ ‏‮Republic‬‏ ‏‮Cura‬‏ç‏‮ao‬‏ +‏‮We‬‏ ‏‮have‬‏ ‏‮blocked‬‏ ‏‮all‬‏ ‏‮requests‬‏ ‏‮from‬‏ ‏‮this‬‏ ‏‮device‬‏ ‏‮due‬‏ ‏‮to‬‏ ‏‮unusual‬‏ ‏‮activity‬‏. ‏‮Try‬‏ ‏‮again‬‏ ‏‮later‬‏. ‏‮Tunisia‬‏ ‏‮Password‬‏ ‏‮changed‬‏ ‏‮Greece‬‏ +‏‮Removed‬‏ ‏‮second‬‏ ‏‮factor‬‏ ‏‮Angola‬‏ ‏‮Kuwait‬‏ ‏‮The‬‏ ‏‮email‬‏ ‏‮and‬‏ ‏‮password‬‏ ‏‮you‬‏ ‏‮entered‬‏ ‏‮don‬‏'‏‮t‬‏ ‏‮match‬‏ diff --git a/translations/ar.xtb b/translations/ar.xtb index c704185f..0bf6ae25 100644 --- a/translations/ar.xtb +++ b/translations/ar.xtb @@ -12,11 +12,14 @@ الفيلبين رومانيا جورجيا الجنوبية وجزر ساندويتش الجنوبية -موافق +ثمة بيانات تالفة أو بيانات مفقودة ويتعذّر استرجاعها. إيطاليا لبنان غيانا غواتيمالا +حدث خطأ أثناء إزالة العامل الثاني.يُرجى إعادة المحاولة. إذا لم تنجح المحاولة، يُرجى التواصل مع فريق الدعم للحصول على مساعدة. +يُرجى محاولة تحديث عنوان بريدك الإلكتروني من جديد +حدث خطأ في الشبكة. يُرجى إعادة المحاولة لاحقًا. يُرجى إدخال رقم هاتف صالحًا يشير النقر على "إثبات الملكية" إلى موافقتك على بنود الخدمة وسياسة الخصوصية. وقد يتمّ إرسال رسالة قصيرة كما قد تنطبق رسوم الرسائل والبيانات. يجب إدخال كلمة المرور @@ -27,6 +30,7 @@ ثمة عدد كبير جدًا من الطلبات الواردة من عنوان IP التابع لك. يُرجى المحاولة مجددًا بعد بضع دقائق. سريلانكا يمكنك الآن تسجيل الدخول باستخدام حسابك الجديد +حدّد العميل وسيطة غير صالحة. تشاد إعادة إرسال الرمز بعد بيلاروسيا @@ -62,6 +66,7 @@ Github سياسة الخصوصية غوام +نفذت حصة الموارد أو بلغت حدّ السعر الأقصى المسموح به. كرواتيا ليتوانيا مالطة @@ -71,22 +76,28 @@ ضيف غانا لا يدعم المتصفح الذي تستخدمه التخزين على الويب. يُرجى المحاولة مجددًا في متصفّح مختلف. +ألغى العميل الطلب. حدثت مشكلة أثناء تغيير البريد الإلكتروني لتسجيل الدخول من جديد.في حال المحاولة مجددًا وتعذّر إعادة تعيين البريد الإلكتروني، يُرجى محاولة طلب المساعدة من المشرف. البحرين +حدث خطأ في الخادم الداخلي. إيران +GitHub جارٍ التأكّد من أنك لست برنامج روبوت... لم يعد هذا الرمز صالحًا نيوكاليدونيا موناكو سيشيل +تمّت إزالة كخطوة مصادقة ثانية. تعذّرت ترقية حساب المستخدم المجهول الحالي لأنّ بيانات اعتماد الحساب غير المجهول مقترنة بحساب مستخدم آخر. لقد أدخلت كلمة مرور غير صحيحة لمرات كثيرة جدًا. يُرجى المحاولة مجددًا بعد بضع دقائق. الأمان انتهت صلاحية طلبك لإعادة تعيين كلمة المرور أو سبق أن تم استخدام الرابط +يمكنك الآن تسجيل الدخول باستخدام عنوان بريدك الإلكتروني الجديد . إنّ رمز البلد الذي قدّمته غير مُعتمَد. التأكّد من كتابة عنوان البريد الإلكتروني بالشكل الصحيح Twitter كمبوديا +الخدمة غير متوفّرة. البرتغال غويانا الفرنسية جزر فيرجين البريطانية @@ -102,19 +113,21 @@ إثبات الملكية جنوب أفريقيا يجب إدخال عنوان بريدك الإلكتروني للمتابعة +أوقف المشرف حساب المستخدم. تمّ إرسال رسالة إلكترونية لتسجيل الدخول تتضمّن تعليمات إضافية إلى . يُرجى التحقق من بريدك الإلكتروني لإكمال عملية تسجيل الدخول. إنشاء حساب أفغانستان يُعد النقر على دليلاً على أنك توافق على . كندا الدنمارك +انتهت المهلة النهائية لتقديم الطلب. النمسا لا تتطابق رسالتا البريد الإلكتروني جنوب السودان اليمن غينيا بيساو المملكة العربية السعودية -شعار التطبيق +لقد تمّت إضافة عامل أمان إضافي في جزر فيرجن الأمريكية تسجيل الدخول عبر رقم الهاتف إندونيسيا @@ -122,6 +135,7 @@ فنزويلا هل تواجه مشكلة في تسجيل الدخول؟ تأكيد ملكية الحساب +حدثت مشكلة أثناء المصادقة على طلبك. لبدء عملية المصادقة من جديد، يُرجى العودة إلى عنوان URL الذي أعاد توجيهك إلى هذه الصفحة. دومينيكا كلمة المرور الجديدة الغابون @@ -139,6 +153,7 @@ غامبيا تم إلغاء +حدث خطأ غير معروف في الخادم. يجب إدخال اسم حسابك العراق فيتنام @@ -150,6 +165,8 @@ نيبال توكيلاو لا يمكن استخدام بيانات الاعتماد المُختارة من موفّر المصادقة. +لا يوجد موفر لخدمة تسجيل الدخول للبريد الإلكتروني المحدّد. يُرجى استخدام بريد إلكتروني مختلف. +يُرجى إدخال الاسم الأول واسم العائلة. كولومبيا يشير النقر على "حفظ" إلى موافقتك على بوروندي @@ -157,7 +174,7 @@ Twitter تم إثبات ملكية بريدك الإلكتروني بابوا غينيا الجديدة -حفظ +تعذّر العثور على المورد المحدّد. جارٍ إثبات الملكية... تسجيل الدخول تيمور الشرقية @@ -190,6 +207,7 @@ الهاتف إعادة الإرسال هندوراس +انتهت المهلة المحدّدة للعملية. النيجر لا يتطابق عنوان البريد الإلكتروني هذا مع حساب حالي الأردن @@ -201,6 +219,7 @@ إسرائيل حدث خطأ الرقم +لا يملك العميل الأذونات الكافية. يجب حلّ reCAPTCHA نيكاراغوا بوركينا فاسو @@ -209,14 +228,17 @@ جزر توركس وكايكوس إعادة إرسال الرمز في غضون إثبات ملكية بريدك الإلكتروني +تعذّرت إزالة العامل الثاني إضافة كلمة مرور السلفادور +لتغيير كلمة المرور في حسابك، يجب تسجيل الدخول من جديد. صربيا بليز التابع لـ لكي تنجح في ربط حسابك على بهذا البريد الإلكتروني، يجب فتح الرابط على الجهاز أو المتصفّح نفسه. السويد جزيرة سانت مارتن +انتهت صلاحية طلبك لإثبات ملكية بريدك الإلكتروني وتحديثه أو سبق أن تمّ استخدام الرابط. سانت لوسیا ‎@string/app_name إثبات ملكية بريدك الإلكتروني @@ -224,13 +246,15 @@ عُمان الجزر الكاريبية الهولندية عنوان البريد الإلكتروني المحدّث -تسجيل الدخول عبر Twitter +لقد سجّلت الخروج بنجاح. +تسجيل الدخول عبر رقم الهاتف رقم الهاتف لديك حساب حاليًا تايلاند سياسة الخصوصية ليشتنشتاين تعديل الاسم +تمّت إزالة الجهاز أو التطبيق كخطوة مصادقة ثانية. حاوِل فتح الرابط باستخدام الجهاز أو المتصفّح نفسه الذي استخدمته عند بدء عملية تسجيل الدخول. هولندا غينيا الاستوائية @@ -254,7 +278,7 @@ يجب إدخال عنوان بريدك الإلكتروني للمتابعة إثبات ملكية رقم هاتفك ليبريا -ثمة حساب يستخدم عنوان البريد الإلكتروني هذا. +تسجيل الخروج مدغشقر كازاخستان جارٍ تسجيل الدخول... @@ -273,15 +297,20 @@ أدخل الرمز المكوّن من رقم الذي أرسلناه إلى تم استخدام رقم الهاتف هذا لعدد كبير جدًا من المرات لا يتطابق عنوان البريد الإلكتروني الذي تم توفيره مع العنوان المستخدَم في جلسة تسجيل الدخول الحالية. -إلغاء ربط الحساب +Twitter + +يُرجى تسجيل الدخول إلى </p></p> + التأكّد من توفّر مساحة فارغة في البريد الوارد أو من عدم حدوث أي مشاكل أخرى في إعدادات البريد الوارد +حدّد العميل نطاقًا زمنيًا غير صالح. +لقد تخطّيت حصة هذه العملية. يُرجى إعادة المحاولة لاحقًا. باراغواي الجزائر لقد أدخلت كلمة مرور غير صحيحة لمرات كثيرة جدًا. يُرجى إعادة المحاولة بعد بضع دقائق. جزيرة نورفولك تمّ التحقّق تلقائيًا من رقم الهاتف. -لتغيير كلمة المرور، يجب أولاً إدخال كلمة المرور الحالية. -رجوع +حدث خطأ في الشبكة، يُرجى التحقّق من اتصال الإنترنت. +اكتملت عملية إثبات الملكية! الهند بنين متابعة @@ -330,27 +359,33 @@ بريد إلكتروني التالي لم يعد هذا الرمز صالحًا -تسجيل الدخول عبر Facebook +تمّ إرسال رسالة إلكترونية لتسجيل الدخول تتضمّن تعليمات إضافية إلى . يُرجى التحقق من بريدك الإلكتروني لإكمال عملية تسجيل الدخول. تأكيد البريد الإلكتروني الاسم الأول واسم العائلة غرينادا الصحراء الغربية +لا يمكن تنفيذ الطلب في حالة النظام الحالية. حدث خطأ غير معروف. لقد سبق أن استخدمت لتسجيل الدخول. يُرجى إدخال كلمة المرور لهذا الحساب. +تمّ إرسال رسالة إلكترونية لتسجيل الدخول أيرلندا جمهورية الكونغو الديمقراطية استرداد كلمة المرور جمهورية الكونغو +المورد الذي حاول العميل إنشاءه متوفّر مسبقًا. المتابعة كضيف سان مارينو قبرص جزر سليمان هل تريد المتابعة باستخدام ؟ +لمتابعة تسجيل الدخول باستخدام حساب على هذا الجهاز، يجب استرداد كلمة المرور. جزر المالديف ساو تومي وبرينسيبي تأكيد البريد الإلكتروني لإكمال عملية تسجيل الدخول سلوفينيا كوريا الجنوبية +حدث تعارض في التزامن، مثلاً تعارض في الكتابة والتعديل والقراءة. +لم يطبّق الخادم طريقة واجهة برمجة التطبيقات. ليسوتو تسجيل الدخول عبر رقم الهاتف حاول إثبات ملكية بريدك الإلكتروني مجددًا @@ -370,8 +405,10 @@ لتسجيل الدخول. يُرجى إدخال كلمة المرور لهذا الحساب. {plural_var,plural, =1{كلمة المرور ليست قوية بما فيه الكفاية. يجب استخدام رقم واحد () على الأقل ومزيج من الأحرف والأرقام}zero{كلمة المرور ليست قوية بما فيه الكفاية. يجب استخدام رقم على الأقل ومزيج من الأحرف والأرقام}two{كلمة المرور ليست قوية بما فيه الكفاية. يجب استخدام رقمين () على الأقل ومزيج من الأحرف والأرقام}few{كلمة المرور ليست قوية بما فيه الكفاية. يجب استخدام أرقام على الأقل ومزيج من الأحرف والأرقام}many{كلمة المرور ليست قوية بما فيه الكفاية. يجب استخدام رقمًا على الأقل ومزيج من الأحرف والأرقام}other{كلمة المرور ليست قوية بما فيه الكفاية. يجب استخدام رقم على الأقل ومزيج من الأحرف والأرقام}} زيمبابوي +تعذّرت مصادقة الطلب بسبب فقدان رمز OAuth المميّز أو استخدام رمز غير صالح أو منتهي الصلاحية. حدثت مشكلة أثناء إثبات ملكية رقم هاتفك تمّ رصد جهاز أو متصفّح جديد +تسجيل الدخول إلى جمهورية التشيك توفالو تسجيل الدخول باستخدام GitHub @@ -426,6 +463,7 @@ برمودا نيوي غوادلوب +ضبط العميل إعدادات مشروع غير صالحة. الأراضي الفلسطينية‬ سلوفاكيا غيرنسي @@ -438,19 +476,24 @@ ماليزيا جزر كوكوس [كيلينغ] لربط حسابك على ، يجب فتح الرابط على الجهاز نفسه حيث سجّلت الدخول في البداية. ولإلغاء عملية الربط، يُرجى النقر على "متابعة" لتسجيل الدخول على هذا الجهاز. +إعادة المحاولة لقد بدأت هذه العملية بهدف ربط حسابك على ببريدك الإلكتروني إلّا أنك فتحت الرابط على جهاز آخر لم يتمّ تسجيل الدخول عليه. إعادة إرسال الرمز بعد 0: المغرب +في حال عدم التعرّف على هذا الجهاز، قد يكون أحد المستخدمين يحاول الوصول إلى حسابك. ننصحك بتغيير كلمة المرور على الفور. بنغلاديش رقم هاتف فنلندا الإمارات العربية المتحدة +تمّ إثبات ملكية عنوان بريدك الإلكتروني وتغييره كلمة المرور جمهورية أفريقيا الوسطى كوراساو +لقد حظرنا كلّ الطلبات من هذا الجهاز بسبب نشاط غير معتاد. يُرجى إعادة المحاولة لاحقًا. تونس تم تغيير كلمة المرور اليونان +تمّت إزالة العامل الثاني أنغولا الكويت لا يتطابق البريد الإلكتروني وكلمة المرور اللذين أدخلتهما diff --git a/translations/bg.xtb b/translations/bg.xtb index 38cd96fc..863ee842 100644 --- a/translations/bg.xtb +++ b/translations/bg.xtb @@ -12,11 +12,14 @@ Филипини Румъния Южна Джорджия и Южни Сандвичеви острови -ОК +Загубата или повредата на данните са необратими. Италия Ливан Гаяна Гватемала +Нещо при отстраняването на втората стъпка се обърка.Опитайте отново. Ако това не помогне, свържете се с екипа за поддръжка за съдействие. +Опитайте отново да актуализирате имейла си +Възникна грешка в мрежата. Опитайте отново по-късно. Въведете валиден телефонен номер Докосвайки „Потвърждаване“, приемате нашите Общи условия и Декларация за поверителност. Възможно е да получите SMS съобщение. То може да се таксува по тарифите за данни и SMS. Въведете паролата си @@ -27,6 +30,7 @@ От IP адреса ви се изпращат твърде много заявки за профил. Опитайте отново след няколко минути. Шри Ланка Вече можете да влезете с новия си профил +Посоченият от клиента аргумент е невалиден. Чад Повторно изпращане на кода след Беларус @@ -62,6 +66,7 @@ Github Декларация за поверителност Гуам +Квотата за ресурси е надвишена или е достигнато допустимото ограничение. Хърватия Литва Малта @@ -71,22 +76,28 @@ Гост Гана Използваният от вас браузър не поддържа уеб хранилище. Моля, опитайте отново в друг браузър. +Заявката е анулирана от клиента. При промяната на имейл адреса ви за вход възникна проблем.Ако опитате отново и не успеете, потърсете помощ от администратора си. Бахрейн +Възникна вътрешна грешка в сървъра. Иран +GitHub Потвърждава се, че не сте робот… Този код вече не е валиден Нова Каледония Монако Сейшелски острови +Номерът бе премахнат като втора стъпка за удостоверяване. Надстройването на текущия анонимен потребител не бе успешно. Посочените неанонимни идентификационни данни вече са свързани с профил на друг потребител. Въведохте неправилна парола твърде много пъти. Опитайте отново след няколко минути. Сигурност Заявката за възстановяване на паролата ви е изтекла или връзката вече е използвана +Вече можете да влезете с новия си имейл . Посоченият код на държавата не се поддържа. Проверете дали имейлът е изписан правилно. Twitter Камбоджа +Няма достъп до услугата. Португалия Френска Гвиана Британски Вирджински острови @@ -102,19 +113,21 @@ Потвърждаване Южна Африка Въведете имейл адреса си, за да продължите +Профилът на потребител е деактивиран от администратор. Изпратихме имейл до за вход в профила с допълнителни инструкции. Проверете входящата си поща, за да завършите процеса. Създаване на профил Афганистан Докосвайки , приемате . Канада Дания +Крайният срок на заявката изтече. Австрия Имейл адресите не съвпадат Южен Судан Йемен Гвинея Бисау Саудитска Арабия -Лого на приложението +В приложението бе добавена допълнителна степен за сигурност Американски Вирджински острови Вход с телефон Индонезия @@ -122,6 +135,7 @@ Венецуела Имате проблем при влизането? Потвърждаване на самоличността ви +При удостоверяване на заявката ви възникна проблем. Моля, отворете URL адреса, който ви пренасочи към тази страница, за да стартирате отново процеса на удостоверяване. Доминика Нова парола Габон @@ -139,6 +153,7 @@ Гамбия Готово Отказ +Възникна неизвестна грешка на сървъра. Въведете името на профила си Ирак Виетнам @@ -150,6 +165,8 @@ Непал Токелау Избраните идентификационни данни за доставчика на удостоверителни услуги не се поддържат! +За посочения имейл адрес не е налице метод за вход в профила. Моля, опитайте с друг имейл. +Моля, въведете име и фамилия. Колумбия Докосвайки „ЗАПАЗВАНЕ“, приемате Бурунди @@ -157,7 +174,7 @@ Twitter Имейл адресът ви е потвърден Папуа Нова Гвинея -Запазване +Посоченият ресурс не е намерен. Потвърждава се… Вход Източен Тимор @@ -190,6 +207,7 @@ Телефон Повторно изпращане Хондурас +Времето за изчакване на операцията изтече. Нигер Този имейл адрес не съответства на съществуващ профил Йордания @@ -201,6 +219,7 @@ Израел Възникна грешка Номер +Клиентът не разполага с достатъчно разрешение. Решете reCAPTCHA Никарагуа Буркина Фасо @@ -209,14 +228,17 @@ Търкс и Кайкос, о-ви Повторно изпращане на кода след Проверете електронната си поща +Втората стъпка не можа да бъде премахната Добавяне на парола Салвадор +За да промените паролата за профила си, ще трябва да влезете отново. Сърбия Белиз за За успешното свързване на профила си в(ъв) с този имейл адрес, трябва да отворите връзката на същото устройство или браузър. Швеция Сен Мартен +Заявката за потвърждаване и актуализиране на имейл адреса ви е изтекла или връзката вече е използвана. Сейнт Лусия @string/app_name Проверете електронната си поща @@ -224,13 +246,15 @@ Оман Карибска Нидерландия Актуализиран имейл адрес -Вход с Twitter +Излязохте успешно от профила си. +Вход с телефон Телефонен номер Вече имате профил Тайланд Декларация за поверителност Лихтенщайн Редактиране на името +Устройството или приложението бяха премахнати като втора стъпка за удостоверяване. Опитайте да отворите връзката от същото устройство или браузър, където е започнат процесът на влизане в профила. Нидерландия Екваториална Гвинея @@ -254,7 +278,7 @@ Въведете имейл адреса си, за да продължите Потвърждаване на телефонния ви номер Либерия -Вече съществува профил с този имейл адрес. +Изход Мадагаскар Казахстан Влизате в профила… @@ -273,15 +297,20 @@ Въведете -цифрения код, който изпратихме до Този телефонен номер е използван твърде много пъти Посоченият имейл адрес не съответства на текущата сесия за вход в профила. -Прекратяване на връзката с профила +Twitter + +Влезте в/ъв </p></p> + Проверете дали в пощенската ви кутия има достатъчно пространство, или не е налице друг проблем с настройките й. +Посоченият от клиента диапазон е невалиден. +Надвишихте квотата за тази операция. Опитайте отново по-късно. Парагвай Алжир Въведохте неправилна парола твърде много пъти. Моля, опитайте отново след няколко минути. Норфолк, о-в Телефонният номер е потвърден автоматично -За да промените паролата си, първо трябва да въведете текущата. -Назад +Грешка в мрежата – проверете връзката с интернет. +Потвърдено! Индия Бенин Напред @@ -330,27 +359,33 @@ Имейл Напред Този код вече не е валиден -Вход с Facebook +Изпратихме имейл до за вход в профила с допълнителни инструкции. Проверете входящата си поща, за да завършите процеса. Потвърждаване на имейл адреса Име и фамилия Гренада Западна Сахара +Заявката не може да се изпълни при текущото състояние на системата. Възникна неизвестна грешка. Вече използвахте за вход. Въведете паролата си за този профил. +Изпратен е имейл за вход в профила Ирландия Демократична република Конго Възстановяване на паролата Република Конго +Ресурсът, който клиентът се опита да създаде, вече съществува. Продължаване като гост Сан Марино Кипър Соломонови острови Искате ли да продължите с/ъс ? +За да продължите да влизате в профила си на това устройство с имейл адрес , трябва да възстановите паролата. Малдиви Сао Томе и Принсипи Потвърждаване на имейл адреса за завършване на влизането в профила Словения Южна Корея +Налице е конфликт с конкурентността, напр. при действията четене, промяна и писане. +Методът на API не е внедрен от сървъра. Лесото Вход с телефон Нов опит за потвърждаване на имейл адреса @@ -369,8 +404,10 @@ Вече използвахте за вход. Въведете паролата си за този профил. {plural_var,plural, =1{Паролата не е достатъчно надеждна. Използвайте поне знак и комбинация от букви и цифри}other{Паролата не е достатъчно надеждна. Използвайте поне знака и комбинация от букви и цифри}} Зимбабве +Заявката не е удостоверена поради липсващо, невалидно или изтекло означение за OAuth. При потвърждаването на телефонния ви номер възникна проблем Открито е ново устройство или браузър +Вход в профила в(ъв) Чешка република Тувалу Вход с GitHub @@ -425,6 +462,7 @@ Бермудски острови Ниуе Гваделупа +Конфигурацията на проекта, посочена от клиентската програма, е невалидна. Палестински територии Словакия Гърнси @@ -437,19 +475,24 @@ Малайзия Кокосови острови (о-ви Кийлинг) Ако все още искате да свържете профила си в(ъв) , отворете връзката на същото устройство, на което първоначално използвахте профила. В противен случай докоснете, за да продължите с влизането на това устройство. +Нов опит Първоначално искахте да свържете профила си в(ъв) с имейла си, но отворихте връзката на различно устройство без вход в профила. Повторно изпращане на кода след 0: Мароко +Ако не разпознавате устройството, някой може да опитва да получи достъп до профила ви. Препоръчваме ви да смените паролата си незабавно. Бангладеш Телефон Финландия Обединени арабски емирства +Имейлът ви бе потвърден и сменен Парола Централноафриканска република Кюрасао +Блокирахме всички заявки от това устройство поради необичайна активност. Опитайте отново по-късно. Тунис Паролата е променена Гърция +Втората стъпка е премахната Ангола Кувейт Имейл адресът и паролата, които въведохте, не си съответстват diff --git a/translations/ca.xtb b/translations/ca.xtb index ef204fa2..b6dcd531 100644 --- a/translations/ca.xtb +++ b/translations/ca.xtb @@ -12,11 +12,14 @@ Filipines Romania Illes Geòrgia del Sud i Sandwich del Sud -D'acord +Pèrdua de dades irrecuperables o corrupció de dades. Itàlia Líban Guyana Guatemala +S'ha produït un error en suprimir el segon factor.Torna-ho a provar. Si no funciona, contacta amb el servei d'assistència per obtenir més ajuda. +Torna a provar d'actualitzar la teva adreça electrònica +S'ha produït un error de xarxa. Torna-ho a provar més tard. Introdueix un número de telèfon vàlid En tocar Verifica, acceptes les nostres condicions del servei i la nostra política de privadesa. És possible que s'enviï un SMS. Es poden aplicar tarifes de dades i missatges. Introdueix la teva contrasenya @@ -27,6 +30,7 @@ Aquesta adreça IP està enviant massa sol·licituds per crear comptes. Torna-ho a provar d'aquí a uns quants minuts. Sri Lanka Ara pots iniciar la sessió amb el teu compte nou +El client ha especificat un argument no vàlid. Txad Torna a enviar el codi d'aquí a Bielorússia @@ -62,6 +66,7 @@ Github Política de privadesa Guam +S'ha esgotat la quota del recurs o s'ha arribat al límit de freqüència. Croàcia Lituània Malta @@ -71,22 +76,28 @@ Convidat Ghana El navegador que utilitzes no és compatible amb l'emmagatzematge web. Torna-ho a provar amb un altre navegador. +El client ha cancel·lat la sol·licitud. Hi ha hagut un problema en canviar l'adreça electrònica d'inici de sessió a l'adreça anterior.Si ho tornes a provar i continues sense poder restablir l'adreça electrònica, demana ajuda al teu administrador. Bahrain +Error intern del servidor. Iran +GitHub S'està comprovant que no siguis un robot... Aquest codi ja no és vàlid Nova Caledònia Mònaco Seychelles +S'ha suprimit el número com a segon pas d'autenticació. No s'ha pogut actualitzar l'usuari anònim actual. La credencial no anònima ja està associada a un altre compte d'usuari. Has introduït una contrasenya incorrecta massa vegades. Torna-ho a provar d'aquí a uns quants minuts. Seguretat La teva sol·licitud per restablir la contrasenya ha caducat o l'enllaç ja s'ha utilitzat +Ara pots iniciar la sessió amb la teva nova adreça electrònica . El codi de país que has indicat no s'admet. Comprova que hagis escrit correctament la teva adreça electrònica. Twitter Cambodja +Servei no disponible. Portugal Guaiana Francesa Illes Verges britàniques @@ -102,19 +113,21 @@ Verifica República de Sud-àfrica Introdueix l'adreça electrònica per continuar +Un administrador ha desactivat aquest compte d'usuari. S'ha enviat un correu electrònic d'inici de sessió amb més instruccions a . Comprova si l'has rebut per completar l'inici de sessió. Crea un compte Afganistan En tocar , indiques que acceptes les . Canadà Dinamarca +S'ha superat el termini de la sol·licitud. Àustria Les adreces electròniques no coincideixen Sudan del Sud Iemen Guinea Bissau Aràbia Saudita -Logotip de l'aplicació +S'ha afegit un factor de seguretat addicional a Illes Verges nord-americanes Inicia la sessió amb el telèfon Indonèsia @@ -122,6 +135,7 @@ Veneçuela No pots iniciar la sessió? Verifica la teva identitat +Hi ha hagut un problema en autenticar la sol·licitud. Torna a visitar l'URL que t'ha redirigit a aquesta pàgina per reiniciar el procés d'autenticació. Dominica Contrasenya nova Gabon @@ -139,8 +153,9 @@ Gàmbia Fet Cancel·la +Error desconegut del servidor. Introdueix el nom del teu compte -Irak +Iraq Vietnam Aquesta adreça electrònica no és correcta Mayotte @@ -150,6 +165,8 @@ Nepal Tokelau No s'admet la credencial que s'ha seleccionat per al proveïdor d'autenticació. +No hi ha cap proveïdor d'inici de sessió disponible per a l'adreça electrònica que has proporcionat; prova-ho amb una altra adreça. +Introdueix nom i cognoms. Colòmbia En tocar DESA, indiques que acceptes les Burundi @@ -157,7 +174,7 @@ Twitter S'ha verificat l'adreça electrònica Papua Nova Guinea -Desa +No s'ha trobat el recurs especificat. S'està verificant... Inicia la sessió Timor Oriental @@ -190,6 +207,7 @@ Telèfon Torna a enviar Hondures +S'ha esgotat el temps d'espera de l'operació. Níger Aquesta adreça electrònica no coincideix amb cap compte Jordània @@ -201,6 +219,7 @@ Israel S'ha detectat un error Número +El client no té permisos suficients. Resol el reCAPTCHA Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Illes Turks i Caicos Torna a enviar el codi d'aquí a Comprova el correu electrònic +No s'ha pogut suprimir el segon factor Afegeix una contrasenya República del Salvador +Per canviar la contrasenya del teu compte, has de tornar a iniciar la sessió. Sèrbia Belize per a l'adreça electrònica Perquè el compte que tens a es connecti amb aquesta adreça electrònica, has d'obrir l'enllaç al mateix dispositiu o navegador. Suècia Saint Martin +La teva sol·licitud per verificar i actualitzar la teva adreça electrònica ha caducat o l'enllaç ja s'ha utilitzat. Saint Lucia @string/app_name Comprova el correu electrònic @@ -224,13 +246,15 @@ Oman Carib neerlandès S'ha modificat l'adreça electrònica -Inicia la sessió amb Twitter +Has tancat la sessió correctament. +Inicia la sessió amb el telèfon Número de telèfon Ja tens un compte Tailàndia Política de privadesa Liechtenstein Edita el nom +S'ha suprimit el dispositiu o l'aplicació com a segon pas d'autenticació. Prova d'obrir l'enllaç amb el mateix dispositiu o navegador on has començat el procés d'inici de sessió. Països Baixos Guinea Equatorial @@ -254,7 +278,7 @@ Introdueix l'adreça electrònica per continuar Verifica el número de telèfon Libèria -Ja hi ha un compte amb aquesta adreça electrònica. +Tanca la sessió Madagascar Kazakhstan S'està iniciant la sessió... @@ -273,15 +297,20 @@ Introdueix el codi de dígits que s'ha enviat al número Aquest número de telèfon s'ha fet servir massa vegades L'adreça electrònica proporcionada no coincideix amb l'inici de sessió actual. -Desenllaça el compte +Twitter + +Inicia la sessió a </p></p> + Comprova que tinguis espai a la safata d'entrada i altres problemes relacionats amb la configuració de la safata d'entrada. +El client ha especificat un interval no vàlid. +S'ha superat la quota d'aquesta operació. Torna-ho a provar més tard. Paraguai Algèria Has introduït una contrasenya incorrecta massa vegades. Torna-ho a provar d'aquí a uns quants minuts. -Illa Norfolk +Norfolk El número de telèfon s'ha verificat automàticament -Per canviar la contrasenya, primer has d'introduir la teva contrasenya actual. -Enrere +Error de xarxa; comprova la connexió a Internet. +S'ha verificat. Índia Benín Continua @@ -330,27 +359,33 @@ Adreça electrònica Següent Aquest codi ja no és vàlid -Inicia la sessió amb Facebook +S'ha enviat un correu electrònic d'inici de sessió amb més instruccions a . Comprova si l'has rebut per completar l'inici de sessió. Confirma l'adreça electrònica Nom i cognoms Granada Sàhara Occidental +La sol·licitud no s'ha pogut executar per l'estat actual del sistema. S'ha produït un error desconegut. Ja has utilitzat per iniciar la sessió. Introdueix la contrasenya d'aquest compte. +S'ha enviat el correu electrònic d'inici de sessió Irlanda República Democràtica del Congo Recupera la contrasenya República del Congo +El recurs que ha intentat crear un client ja existeix. Continua com a convidat San Marino Xipre Illes Salomó Vols continuar amb ? +Per iniciar la sessió amb en aquest dispositiu, cal que recuperis la contrasenya. Maldives -Sao Tomé i Príncipe +São Tomé i Príncipe Confirma l'adreça electrònica per completar l'inici de sessió Eslovènia Corea del Sud +Conflicte de concurrència, com ara un conflicte de llegir-modificar-escriure. +El servidor no ha implementat el mètode d'API. Lesotho Inicia la sessió amb el telèfon Torna a provar de verificar l'adreça electrònica @@ -370,8 +405,10 @@ per iniciar la sessió. Introdueix la contrasenya d'aquest compte. {plural_var,plural, =1{La contrasenya no és prou segura. Utilitza com a mínim caràcter i combina lletres i números.}other{La contrasenya no és prou segura. Utilitza com a mínim caràcters i combina lletres i números.}} Zimbàbue +La sol·licitud no s'ha autenticat perquè falta un testimoni d'OAuth, o bé perquè n'hi ha un que no és vàlid o que ha caducat. Hi ha hagut un problema en verificar el número de telèfon S'ha detectat un dispositiu o navegador nou +Inicia la sessió a República Txeca Tuvalu Inicia la sessió amb GitHub @@ -426,6 +463,7 @@ Bermudes Niue Guadalupe +El client ha especificat una configuració de projecte no vàlida. Territoris palestins Eslovàquia Guernsey @@ -438,19 +476,24 @@ Malàisia Illes Cocos Si encara vols connectar el compte que tens a , obre l'enllaç al mateix dispositiu on has començat a iniciar la sessió. Si no, toca Continua per iniciar la sessió en aquest dispositiu. +Torna-ho a provar Inicialment has intentat connectar amb el teu compte de correu electrònic, però has obert l'enllaç amb un dispositiu diferent on no has iniciat la sessió. Torna a enviar el codi d'aquí a 0: Marroc +Si no reconeixes aquest dispositiu, és possible que algú estigui intentant accedir al teu compte. Et recomanem que canviïs la contrasenya immediatament. Bangla Desh Telèfon Finlàndia Emirats Àrabs Units +S'ha verificat i canviat la teva adreça electrònica Contrasenya República Centreafricana Curaçao +Hem bloquejat totes les sol·licituds d'aquest dispositiu a causa d'activitat inusual. Torna-ho a provar més tard. Tunísia S'ha canviat la contrasenya Grècia +S'ha suprimit el segon factor Angola Kuwait L'adreça electrònica i la contrasenya que has introduït no coincideixen diff --git a/translations/cs.xtb b/translations/cs.xtb index 6054d10c..ae1b6da3 100644 --- a/translations/cs.xtb +++ b/translations/cs.xtb @@ -12,11 +12,14 @@ Filipíny Rumunsko Jižní Georgie a Jižní Sandwichovy ostrovy -V pořádku +Došlo ke ztrátě neobnovitelných dat nebo k poškození dat. Itálie Libanon Guayana Guatemala +Něco se pokazilo při odebírání druhého faktoru.Zkuste ho odebrat znovu. Pokud tento postup nefunguje, kontaktujte podporu. +Zkuste e-mail znovu aktualizovat +Došlo k chybě sítě. Zkuste to znovu později. Zadejte platné telefonní číslo Klepnutím na tlačítko Ověřit vyjadřujete svůj souhlas s našimi smluvními podmínkamizásadami ochrany osobních údajů. Může být odeslána SMS a mohou být účtovány poplatky za zprávy a data. Zadejte své heslo @@ -27,6 +30,7 @@ Z vaší adresy IP přichází příliš mnoho požadavků na účet. Zkuste to znovu za několik minut. Srí Lanka Nyní se můžete přihlásit prostřednictvím nového účtu +Klient uvedl neplatný argument. Čad Znovu odeslat kód za Bělorusko @@ -62,6 +66,7 @@ Github Zásady ochrany soukromí Guam +Mimo kvótu zdrojů nebo blízko limitu sazby. Chorvatsko Litva Malta @@ -71,22 +76,28 @@ Host Ghana Váš prohlížeč nepodporuje webové úložiště. Zkuste použít jiný prohlížeč. +Požadavek byl zrušen klientem. Změnu e-mailové adresy na původní adresu se nepodařilo provést.Pokud se obnovení adresy nezdaří ani při dalším pokusu, požádejte o pomoc svého správce. Bahrajn +Interní chyba serveru. Írán +GitHub Ověřujeme, zda nejste robot… Platnost kódu vypršela Nová Kaledonie Monako Seychelly +Odebrána druhá fáze ověření: . Nepodařilo se upgradovat stávajícího anonymního uživatele. Identifikační údaje neanonymního uživatele jsou již přidruženy k účtu jiného uživatele. Provedli jste příliš mnoho neplatných pokusů o zadání hesla. Opakujte akci za chvíli. Zabezpečení Platnost vaší žádosti o obnovení hesla vypršela nebo byl odkaz již použit +Nyní se můžete přihlásit prostřednictvím nového účtu . Zadaný kód země není podporován. Zkontrolujte, zda jste adresu e-mailu napsali správně. Twitter Kambodža +Služba není k dispozici. Portugalsko Francouzská Guyana Britské Panenské ostrovy @@ -102,19 +113,21 @@ Ověřit Jihoafrická republika Nejprve zadejte svou e-mailovou adresu +Uživatelský účet byl deaktivován administrátorem. Na adresu byl odeslán přihlašovací e-mail s dalšími pokyny. Dokončete přihlášení podle instrukcí v e-mailu. Tvorba účtu Afghánistán Klepnutím na možnost vyjadřujete svůj souhlas s dokumentem . Kanada Dánsko +Překročen konečný termín požadavku. Rakousko E-mailové adresy se neshodují Jižní Súdán Jemen Guinea Bissau Saúdská Arábie -Logo aplikace +V aplikaci byla přidána další fáze zabezpečení Americké Panenské ostrovy Přihlásit se pomocí telefonu Indonésie @@ -122,6 +135,7 @@ Venezuela Máte potíže s přihlášením? Ověřte svou identitu +Během ověřování vaší žádosti došlo k chybě. Navštivte znovu adresu URL, která vás na tuto stránku přesměrovala, abyste proces ověřování restartovali. Dominika Nové heslo Gabun @@ -139,6 +153,7 @@ Gambie Hotovo Zrušit +Neznámá chyba serveru. Zadejte název účtu. Irák Vietnam @@ -150,6 +165,8 @@ Nepál Tokelau Vybrané přihlašovací údaje pro daného poskytovatele služeb ověření nejsou podporovány. +Pro daný e-mail není k dispozici žádný poskytovatel přihlášení, zkuste jiný e-mail. +Zadejte jméno a příjmení Kolumbie Klepnutím na tlačítko ULOŽIT potvrzujte svůj souhlas s dokumentem Burundi @@ -157,7 +174,7 @@ Twitter Váš e-mail byl ověřen Papua Nová Guinea -Uložit +Uvedený zdroj nebyl nalezen. Ověřování… Přihlásit se Východní Timor @@ -190,6 +207,7 @@ Telefon Znovu odeslat Honduras +Časový limit pro operaci vypršel. Niger E-mailová adresa neodpovídá žádnému stávajícímu účtu Jordánsko @@ -201,6 +219,7 @@ Izrael Došlo k chybě Číslo +Klient nemá dostatečné oprávnění. Potvrďte ověření reCAPTCHA. Nikaragua Burkina Faso @@ -209,14 +228,17 @@ Ostrovy Turks a Caicos Opakované odeslání kódu možné za Zkontrolujte svůj e-mail +Druhý faktor nelze odebrat Přidat heslo Salvador +Chcete-li změnit heslo k účtu, musíte se znovu přihlásit. Srbsko Belize k účtu Aby došlo k úspěšnému propojení účtu s touto e-mailovou adresou, musíte odkaz otevřít na stejném zařízení nebo ve stejném prohlížeči. Švédsko Svatý Martin +Platnost vaší žádosti o ověření a aktualizaci e-mailu vypršela nebo byl odkaz již použit. Svatá Lucie @string/app_name Kontrola e-mailu @@ -224,13 +246,15 @@ Omán Karibské Nizozemsko E-mailová adresa byla změněna -Přihlaste se přes Twitter +Byli jste úspěšně odhlášeni. +Přihlásit se pomocí telefonu Telefonní číslo Již máte účet Thajsko Zásady ochrany soukromí Lichtenštejnsko Změnit jméno +Zařízení nebo aplikace byla z druhé fáze ověření odebrána. Otevřete odkaz na stejném zařízení nebo ve stejném prohlížeči, kde jste proces přihlášení započali. Nizozemsko Rovníková Guinea @@ -254,7 +278,7 @@ Nejprve zadejte svou e-mailovou adresu. Ověření telefonního čísla Libérie -Účet s touto adresou již existuje. +Odhlášení Madagaskar Kazachstán Přihlašování… @@ -273,15 +297,20 @@ Zadejte místný kód, který jsme vám zaslali Toto telefonní číslo již bylo použito příliš mnohokrát Poskytnutý e-mail neodpovídá aktuální přihlašovací relaci. -Odpojit účet +Twitter + +Přihlaste se do aplikace </p></p> + Zkontrolujte, jestli nemáte plnou schránku příchozích správ nebo nedošlo k nějakému jiného problému se schránkou. +Klient uvedl neplatný rozsah. +Kvóta pro tuto operaci byla překročena. Zkuste to znovu později. Paraguay Alžírsko Provedli jste příliš mnoho neplatných pokusů o zadání hesla. Opakujte akci za chvíli. Norfolk Telefonní číslo bylo automaticky ověřeno -Chcete-li změnit heslo, musíte nejprve zadat aktuální heslo. -Zpět +Chyba sítě. Zkontrolujte připojení k internetu. +Ověřeno! Indie Benin Pokračovat @@ -330,27 +359,33 @@ E-mail Další Platnost kódu vypršela -Přihlásit se přes Facebook +Na adresu byl odeslán přihlašovací e-mail s dalšími pokyny. Dokončete přihlášení podle instrukcí v e-mailu. Potvrzení·e-mailu Jméno a příjmení Grenada Západní Sahara +V současném stavu systému nelze požadavek provést. Došlo k neznámé chybě. K přihlášení jste již použili adresu . Zadejte příslušné heslo k účtu. +Přihlašovací e-mail odeslán Irsko Demokratická republika Kongo Obnovit heslo Konžská republika +Zdroj, který se klient pokoušel vytvořit, již existuje. Pokračovat jako host San Marino Kypr Šalamounovy ostrovy Pokračovat s adresou ? +Abyste na tomto zařízení zůstali přihlášeni pomocí e-mailu , musíte si obnovit heslo. Maledivy Svatý Tomáš a Princův ostrov Dokončete přihlášení potvrzním e-mailové adresy Slovinsko Jižní Korea +Konflikt souběžnosti, jako je konflikt mezi čtením, úpravou a psaním. +Metoda API nebyla implementována serverem. Lesotho Přihlášení pomocí telefonu Zkuste e-mail ověřit znovu @@ -370,8 +405,10 @@ Zadejte příslušné heslo k účtu. {plural_var,plural, =1{Heslo není dostatečně silné. Použijte alespoň  znak a kombinaci písmen a číslic}few{Heslo není dostatečně silné. Použijte alespoň  znaky a kombinaci písmen a číslic}many{Heslo není dostatečně silné. Použijte alespoň  znaku a kombinaci písmen a číslic}other{Heslo není dostatečně silné. Použijte alespoň  znaků a kombinaci písmen a číslic}} Zimbabwe +Požadavek nebyl ověřen, protože token OAuth chybí, je neplatný nebo mu vypršela platnost. Při ověřování telefonního čísla došlo k potížím Bylo rozpoznáno nové zařízení nebo prohlížeč +Přihlásit se k účtu Česko Tuvalu Přihlásit se přes GitHub @@ -426,6 +463,7 @@ Bermudy Niue Guadeloupe +Klient uvedl neplatnou konfiguraci projektu. Palestinská území Slovensko Guernsey @@ -438,19 +476,24 @@ Malajsie Kokosové ostrovy Pokud stále chcete připojit svůj účet , otevřete odkaz na zařízení, na kterém jste s přihlášením začali. Jinak klepněte na Pokračovat a přihlaste se na tomto zařízení. +Znovu Původně jste chtěli připojit ke svému e-mailovému účtu, ale otevřeli jste odkaz na jiném zařízení, na kterém nejste přihlášeni. Znovu zaslat kód za 0: Maroko +Pokud toto zařízení nepoznáváte, někdo se možná snaží získat přístup k vašemu účtu. Zvažte okamžitou změnu hesla. Bangladéš Telefon Finsko Spojené Arabské Emiráty +Vás e-mail byl ověren a změněn Heslo Středoafrická republika Curaçao +Kvůli neobvyklé aktivitě jsme zablokovali všechny požadavky z tohoto zařízení. Zkuste to znovu později. Tunisko Heslo bylo změněno Řecko +Druhý faktor odebrán Angola Kuvajt Zadaný e-mail a heslo se neshodují diff --git a/translations/da.xtb b/translations/da.xtb index 12599a9c..dc21312b 100644 --- a/translations/da.xtb +++ b/translations/da.xtb @@ -12,11 +12,14 @@ Filippinerne Rumænien South Georgia og De Sydlige Sandwichøer -OK +Tab af data, der ikke kan gendannes, eller datakorruption. Italien Libanon Guyana Guatemala +Der gik noget galt under fjernelsen af din anden faktor.Prøv at fjerne den igen. Hvis det ikke virker, kan du kontakte support for at få hjælp. +Prøv at opdatere din mail igen +Der er opstået en netværksfejl. Prøv igen senere. Angiv et gyldigt telefonnummer Ved at trykke på Bekræft accepterer du vores Servicevilkår og Privatlivspolitik. Der bliver evt. sendt en sms. Der opkræves muligvis gebyrer for beskeder og data. Angiv din adgangskode @@ -27,6 +30,7 @@ Der kommer for mange kontoanmodninger fra din IP-adresse. Prøv igen om et par minutter. Sri Lanka Du kan nu logge ind med din nye konto +Klienten angav et ugyldigt argument. Tchad Send koden igen om Hviderusland @@ -62,6 +66,7 @@ Github Privatlivspolitik Guam +Skyldes enten, at ressourcekvoten er overskredet, eller at rategrænsen er nået. Kroatien Litauen Malta @@ -71,22 +76,28 @@ Gæst Ghana Den browser, du bruger, understøtter ikke weblagring. Prøv igen i en anden browser. +Anmodningen blev annulleret af klienten. Der opstod et problem med at ændre din login-mail tilbage.Hvis du prøver igen og stadig ikke kan nulstille din mail, skal du bede din administrator om hjælp. Bahrain +Intern serverfejl. Iran +GitHub Bekræfter, at du ikke er en robot… Denne kode er ikke længere gyldig Ny Kaledonien Monaco Seychellerne + blev fjernet som andet godkendelsestrin. Opgradering af den nuværende anonyme bruger mislykkedes. De ikke-anonyme loginoplysninger er allerede knyttet til en anden brugerkonto. Du har indtastet en forkert adgangskode for mange gange. Prøv igen om et par minutter. Sikkerhed Din anmodning om at nulstille din adgangskode er udløbet, eller linket er allerede brugt +Du kan nu logge ind med din nye mail . Den angivne landekode understøttes ikke. Sørg for, at du ikke har stavet din mailadresse forkert. Twitter Cambodja +Tjenesten er ikke tilgængelig. Portugal Fransk Guyana De Britiske Jomfruøer @@ -102,19 +113,21 @@ Bekræft Sydafrika Angiv din mailadresse for at fortsætte +Brugerkontoen er blevet deaktiveret af en administrator. En loginmail med yderligere vejledning er sendt til . Tjek din mail for at fuldføre loginprocessen. Opret konto Afghanistan Ved at trykke på accepterer du . Canada Danmark +Anmodningsdeadline er overskredet. Østrig Mailadresserne stemmer ikke overens Sydsudan Yemen Guinea-Bissau Saudi-Arabien -App-logo +Der er tilføjet et ekstra sikkerhedstrin i De Amerikanske Jomfruøer Log ind med telefon Indonesien @@ -122,6 +135,7 @@ Venezuela Har du problemer med at logge ind? Bekræft, at det er dig +Der opstod et problem under godkendelsen af din anmodning. Gå endnu en gang til den webadresse, der omdirigerede dig til denne side, for at påbegynde godkendelsesprocessen igen. Dominica Ny adgangskode Gabon @@ -139,6 +153,7 @@ Gambia Udført Annuller +Ukendt serverfejl. Angiv dit kontonavn Irak Vietnam @@ -150,6 +165,8 @@ Nepal Tokelau De valgte loginoplysninger for udbyderen af godkendelse understøttes ikke. +Der er ingen loginudbyder tilgængelig for den angivne mailadresse. Prøv en anden mailadresse. +Angiv et for- og efternavn. Colombia Ved at trykke på GEM angiver du, at du accepterer Burundi @@ -157,7 +174,7 @@ Twitter Din mail er blevet bekræftet Papua Ny Guinea -Gem +Den angivne ressource blev ikke fundet. Bekræfter… Log ind Østtimor @@ -190,6 +207,7 @@ Telefon Send igen Honduras +Handlingen er udløbet. Niger Mailadressen matcher ikke en eksisterende konto Jordan @@ -201,6 +219,7 @@ Israel Der opstod en fejl Tal +Klienten har ikke tilstrækkelig tilladelse. Løs reCAPTCHA Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Turks- og Caicosøerne Send koden igen om Tjek din mail +Kunne ikke fjerne din anden faktor Tilføj adgangskode El Salvador +For at ændre adgangskoden for din konto skal du logge ind igen. Serbien Belize for Hvis det skal lykkes at knytte din -konto til mailadressen på denne måde, skal du åbne linket med den samme enhed eller i samme browser. Sverige Saint-Martin +Din anmodning om at bekræfte og opdatere din mail er udløbet, eller linket er allerede blevet brugt. St. Lucia @string/app_name Tjek din mail @@ -224,13 +246,15 @@ Oman Hollandsk Caribien Opdateret mailadresse -Log ind med Twitter +Du er nu blevet logget ud. +Log ind med telefon Telefonnummer Du har allerede en konto Thailand Privatlivspolitik Liechtenstein Rediger navn +Enheden eller appen blev fjernet som andet godkendelsestrin. Prøv at åbne linket med samme enhed eller samme browser, som du brugte til at starte loginprocessen. Holland Ækvatorialguinea @@ -254,7 +278,7 @@ Angiv din mailadresse for at fortsætte Bekræft dit telefonnummer Liberia -Der findes allerede en konto med den mailadresse. +Log ud Madagaskar Kasakhstan Logger ind… @@ -273,15 +297,20 @@ Angiv den -cifrede kode, vi sendte til Dette telefonnummer er blevet brugt for mange gange Den angivne mailadresse svarer ikke til den aktuelle loginsession. -Fjern tilknytning til konto +Twitter + +Log ind på </p></p> + Sørg for, at din indbakke ikke er løbet tør for plads, og at du ikke har andre problemer med indbakken. +Klienten har angivet et ugyldigt interval. +Kvoten for denne handling er blevet overskredet. Prøv igen senere. Paraguay Algeriet Du har indtastet en forkert adgangskode for mange gange. Prøv igen om et par minutter. Norfolkøen Telefonnummeret blev bekræftet automatisk -For at ændre din adgangskode skal du først angive din nuværende adgangskode. -Tilbage +Netværksfejl. Tjek din internetforbindelse. +Bekræftet! Indien Benin Fortsæt @@ -330,27 +359,33 @@ Mail Næste Denne kode er ikke længere gyldig -Log ind med Facebook +Der er blevet sendt en loginmail med yderligere vejledning til . Tjek din mail for at fuldføre loginprocessen. Bekræft mailadresse For- og efternavn Grenada Vestsahara +Anmodningen kan ikke udføres i den nuværende systemtilstand. Der opstod en ukendt fejl. Du har allerede brugt til at logge ind. Angiv din adgangskode for den pågældende konto. +Loginmailen blev sendt Irland Den Demokratiske Republik Congo Gendan adgangskode Republikken Congo +Den ressource, som en klient prøvede at oprette, eksisterer allerede. Fortsæt som gæst San Marino Cypern Salomonøerne Vil du fortsætte med ? +Gendan adgangskoden for at fortsætte med at logge ind med på denne enhed. Maldiverne Sao Tomé og Principe Bekræft mailadressen for at fuldføre loginprocessen Slovenien Sydkorea +Samtidighedskonflikt såsom læs-rediger-skriv-konflikt. +API-metoden blev ikke implementeret af serveren. Lesotho Log ind med telefon Prøv at bekræfte din mail igen @@ -370,8 +405,10 @@ til at logge ind. Angiv din adgangskode for den pågældende konto. {plural_var,plural, =1{Adgangskoden er ikke stærk nok. Brug mindst tegn og en blanding af bogstaver og tal}one{Adgangskoden er ikke stærk nok. Brug mindst tegn og en blanding af bogstaver og tal}other{Adgangskoden er ikke stærk nok. Brug mindst tegn og en blanding af bogstaver og tal}} Zimbabwe +Anmodningen blev ikke godkendt på grund af manglende, ugyldigt eller udløbet OAuth-token. Dit telefonnummer kunne ikke bekræftes Der er registreret en ny enhed eller browser +Log ind på Tjekkiet Tuvalu Log ind med GitHub @@ -426,6 +463,7 @@ Bermuda Niue Guadeloupe +Klienten angav en ugyldig projektkonfiguration. Palæstinensisk territorium Slovakiet Guernsey @@ -438,19 +476,24 @@ Malaysia Cocosøerne (Keelingøerne) Hvis du stadig vil tilknytte din -konto, skal du åbne linket på den enhed, hvor du startede med at logge ind. Ellers skal du trykke på Fortsæt for at logge ind på denne enhed. +Prøv igen Du ville egentlig knytte til din mailkonto, men du har åbnet linket på en anden enhed, hvor du ikke er logget ind. Send koden igen om 0: Marokko +Hvis du ikke kan genkende denne enhed, er der måske nogen, som forsøger at få adgang til din konto. Overvej at ændre din adgangskode med det samme. Bangladesh Telefon Finland Forenede Arabiske Emirater +Din mail er blevet bekræftet og ændret Adgangskode Den Centralafrikanske Republik Curaçao +Vi har blokeret alle anmodninger fra denne enhed på grund af usædvanlig aktivitet. Prøv igen senere. Tunesien Adgangskoden blev ændret Grækenland +Anden faktor fjernet Angola Kuwait Den mail og adgangskode, du indtastede, stemmer ikke overens diff --git a/translations/de.xtb b/translations/de.xtb index 7b0e5835..b4aabc32 100644 --- a/translations/de.xtb +++ b/translations/de.xtb @@ -12,14 +12,17 @@ Philippinen Rumänien Südgeorgien und die Südlichen Sandwichinseln -OK +Dauerhafter Datenverlust oder Datenkorruption. Italien Libanon Guyana Guatemala -Geben Sie eine gültige Telefonnummer ein +Beim Entfernen des zweiten Authentifizierungsschritts ist ein Problem aufgetreten.Bitte versuchen Sie es noch einmal. Wenn Sie den Authentifizierungsschritt immer noch nicht entfernen können, wenden Sie sich bitte an unseren Support. +Versuchen Sie noch einmal, Ihre E-Mail-Adresse zu aktualisieren +Ein Netzwerkfehler ist aufgetreten. Versuchen Sie es später noch einmal. +Gib eine gültige Telefonnummer ein Indem Sie auf "Verifizieren" tippen, stimmen Sie unseren Nutzungsbedingungen und unserer Datenschutzerklärung zu. Sie erhalten möglicherweise eine SMS und es können Gebühren für die Nachricht und die Datenübertragung anfallen. -Geben Sie Ihr Passwort ein +Gib Ihr Passwort ein Fertig Haiti Wenn Sie nicht um Änderung der Anmelde-E-Mail-Adresse gebeten haben, versucht möglicherweise jemand anderes, auf Ihr Konto zuzugreifen. In diesem Fall sollten Sie Ihr Passwort sofort ändern. @@ -27,6 +30,7 @@ Über Ihre IP-Adresse werden zu viele Kontoanfragen gesendet. Bitte versuchen Sie es in einigen Minuten noch einmal. Sri Lanka Sie können sich jetzt mit Ihrem neuen Konto anmelden +Der Client hat ein ungültiges Argument angegeben. Tschad Code in erneut senden Weißrussland @@ -42,7 +46,7 @@ Macau Hallo , Konto löschen -Geben Sie den 6-stelligen Code ein, den wir an gesendet haben +Gib den 6-stelligen Code ein, den wir an gesendet haben Folgen Sie der an gesendeten Anleitung, um Ihr Passwort zurückzusetzen. Ungarn Suriname @@ -62,6 +66,7 @@ GitHub Datenschutzerklärung Guam +Entweder wurde das Ressourcenkontingent überschritten oder die Ratenbegrenzung erreicht. Kroatien Litauen Malta @@ -71,27 +76,33 @@ Gast Ghana Ihr Browser unterstützt Web Storage nicht. Bitte versuchen Sie es mit einem anderen Browser. +Die Anfrage wurde vom Client abgebrochen. Beim Ändern der E-Mail-Adresse für die Anmeldung ist ein Problem aufgetreten.Wenn Sie die E-Mail-Adresse bei einem erneuten Versuch immer noch nicht zurücksetzen können, wenden Sie sich bitte an Ihren Administrator. Bahrain +Interner Serverfehler. Iran +GitHub Bestätigen Sie bitte, dass Sie kein Roboter sind. Dieser Code ist nicht mehr gültig Neukaledonien Monaco Seychellen + wurde als zweiter Authentifizierungsschritt entfernt. Der aktuelle anonyme Nutzer konnte sein Konto nicht upgraden, weil die entsprechenden Anmeldedaten bereits mit einem anderen nicht-anonymen Nutzerkonto verknüpft sind. Sie haben zu oft ein falsches Passwort eingegeben. Versuchen Sie es in einigen Minuten erneut. Sicherheit Ihre Anfrage zum Zurücksetzen des Passworts ist abgelaufen oder der Link wurde bereits verwendet +Sie können sich jetzt mit Ihrer neuen E-Mail-Adresse anmelden. Der angegebene Ländercode wird nicht unterstützt. Überprüfen Sie die Schreibweise Ihrer E-Mail-Adresse. Twitter Kambodscha +Dienst nicht verfügbar. Portugal Französisch-Guayana Britische Jungferninseln Ablehnen -Geben Sie eine gültige Telefonnummer ein +Gib eine gültige Telefonnummer ein Diese Art von Konto wird von dieser App nicht unterstützt Der Aktionscode ist ungültig. Möglicherweise ist der Code fehlerhaft oder abgelaufen oder er wurde bereits verwendet. Dieser Code ist abgelaufen. @@ -102,6 +113,7 @@ Bestätigen Südafrika E-Mail-Adresse eingeben, um fortzufahren +Das Nutzerkonto wurde von einem Administrator deaktiviert. Wir haben eine Anmelde-E-Mail an gesendet. Öffnen Sie die E-Mail, um die Anmeldung abzuschließen. Passwort eingeben Konto erstellen @@ -109,13 +121,14 @@ Durch Tippen auf stimmen Sie den zu. Kanada Dänemark +Die Frist der Anfrage wurde überschritten. Österreich E-Mail-Adressen stimmen nicht überein Südsudan Jemen Guinea-Bissau Saudi-Arabien -App-Logo +Ein zusätzlicher Sicherheitsfaktor wurde in hinzugefügt Amerikanische Jungferninseln Mit Telefonnummer anmelden Indonesien @@ -123,6 +136,7 @@ Venezuela Probleme bei der Anmeldung? Identität bestätigen +Bei der Authentifizierung Ihrer Anfrage ist ein Problem aufgetreten. Rufen Sie die URL, über die Sie auf diese Seite weitergeleitet wurden, bitte noch einmal auf und starten Sie den Authentifizierungsprozess neu. Dominica Neues Passwort Gabun @@ -140,7 +154,8 @@ Gambia Fertig Abbrechen -Geben Sie Ihren Kontonamen ein +Unbekannter Serverfehler. +Gib Ihren Kontonamen ein Irak Vietnam Diese E-Mail-Adresse ist ungültig @@ -151,6 +166,8 @@ Nepal Tokelau Die für den Authentifizierungsanbieter gewählten Anmeldedaten werden nicht unterstützt. +Für die angegebene E-Mail-Adresse ist kein Anmeldeanbieter verfügbar. Bitte geben Sie eine andere Adresse an. +Gib einen Vor- und Nachnamen ein. Kolumbien Durch Tippen auf SPEICHERN erklären Sie Ihr Einverständnis mit den Burundi @@ -158,7 +175,7 @@ Twitter Ihre E-Mail-Adresse wurde bestätigt Papua-Neuguinea -Speichern +Die angegebene Ressource wurde nicht gefunden. Wird bestätigt... Anmelden Timor-Leste @@ -191,6 +208,7 @@ Telefon Erneut senden Honduras +Das Zeitlimit für den Vorgang wurde überschritten. Niger Diese E-Mail-Adresse passt zu keinem Konto Jordanien @@ -202,6 +220,7 @@ Israel Ein Fehler ist aufgetreten Nummer +Der Client verfügt nicht über die erforderliche Berechtigung. Klicken Sie das reCAPTCHA-Kästchen an Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Turks- und Caicosinseln Code in erneut senden Im Posteingang nachsehen +Zweiter Authentifizierungsschritt konnte nicht entfernt werden Passwort hinzufügen El Salvador +Um das Passwort für Ihr Konto zu ändern, müssen Sie sich erneut anmelden. Serbien Belize für Öffnen Sie den Link mit demselben Gerät oder Browser, um Ihr -Konto mit dieser E-Mail-Adresse zu verbinden. Schweden St. Martin +Ihre Anfrage zur Überprüfung und Änderung der E-Mail-Adresse ist abgelaufen oder der Link wurde bereits verwendet. St. Lucia @string/app_name Im Posteingang nachsehen @@ -225,13 +247,15 @@ Oman Karibische Niederlande Aktualisierte E-Mail-Adresse -Über Twitter anmelden +Sie haben sich erfolgreich abgemeldet. +Mit einer Telefonnummer anmelden Telefonnummer Sie haben bereits ein Konto Thailand Falscher Code. Versuchen Sie es noch einmal. Liechtenstein Name bearbeiten +Das Gerät oder die App wurde als zweiter Authentifizierungsschritt entfernt. Öffnen Sie den Link mit demselben Gerät oder Browser, mit dem Sie die Anmeldung begonnen haben. Niederlande Äquatorialguinea @@ -252,10 +276,10 @@ Passwort St. Kitts Zurück -Geben Sie Ihre E-Mail-Adresse ein, um fortzufahren +Gib Ihre E-Mail-Adresse ein, um fortzufahren Telefonnummer bestätigen Liberia -Ein Konto mit dieser E-Mail-Adresse ist bereits vorhanden. +Abmelden Madagaskar Kasachstan Anmeldung… @@ -263,26 +287,31 @@ Tonga Wallis und Futuna E-Mail-Adresse kann nicht aktualisiert werden -Sie haben bereits zur Anmeldung verwendet. Geben Sie das Passwort für dieses Konto ein. +Sie haben bereits zur Anmeldung verwendet. Gib das Passwort für dieses Konto ein. Anguilla Diese E-Mail-Adresse ist bereits vorhanden und es ist keine Anmeldemethode festgelegt. Bitte setzen Sie das Passwort zurück, um Zugriff zu erhalten. Dominikanische Republik Jersey Passwort zurücksetzen -Geben Sie den 6-stelligen Code ein, den wir an &lrm; geschickt haben +Gib den 6-stelligen Code ein, den wir an &lrm; geschickt haben Ruanda -stelligen Code eingeben, der an folgende Nummer gesendet wurde: Diese Telefonnummer wurde schon zu oft verwendet Die angegebene E-Mail-Adresse stimmt nicht mit der aktuellen Anmeldesitzung überein. -Verknüpfung des Kontos aufheben +Twitter + +Melden Sie sich in an</p></p> + Überprüfen Sie den Speicherplatz und weitere Einstellungen Ihres Posteingangs, die Probleme bereiten könnten. +Der Client hat einen ungültigen Bereich angegeben. +Das Kontingent für diesen Vorgang wurde überschritten. Versuchen Sie es später noch einmal. Paraguay Algerien Sie haben zu oft ein falsches Passwort eingegeben. Bitte versuchen Sie es in einigen Minuten noch einmal. Norfolkinsel Telefonnummer wurde automatisch bestätigt -Um das Passwort zu ändern, müssen Sie zuerst Ihr aktuelles Passwort eingeben. -Zurück +Netzwerkfehler. Überprüfen Sie Ihre Internetverbindung. +Bestätigt Indien Benin Weiter @@ -331,27 +360,33 @@ E-Mail-Adresse Weiter Dieser Code ist nicht mehr gültig -Über Facebook anmelden +Wir haben eine Anmelde-E-Mail mit zusätzlichen Informationen an gesendet. Bitte öffnen Sie die E-Mail, um die Anmeldung abzuschließen. E-Mail-Adresse bestätigen Vor- und Nachname Grenada Westsahara +Die Anfrage kann im aktuellen Systemzustand nicht durchgeführt werden. Ein unbekannter Fehler ist aufgetreten. -Sie haben bereits zur Anmeldung verwendet. Geben Sie Ihr Passwort für dieses Konto ein. +Sie haben bereits zur Anmeldung verwendet. Gib Ihr Passwort für dieses Konto ein. +Anmelde-E-Mail gesendet Irland Demokratische Republik Kongo Passwort zurücksetzen Kongo +Die von einem Client zu erstellende Ressource ist bereits vorhanden. Als Gast fortfahren San Marino Zypern Salomonen Mit fortfahren? +Damit Sie sich mit auf diesem Gerät anmelden können, müssen Sie das Passwort zurücksetzen lassen. Malediven São Tomé und Príncipe Bestätigen Sie Ihre E-Mail-Adresse, um die Anmeldung abzuschließen Slowenien Südkorea +Konflikt aufgrund von gleichzeitig ausgeführten Aktionen, beispielsweise Read-Modify-Write-Konflikt. +Die API-Methode wurde vom Server nicht implementiert. Lesotho Mit einer Telefonnummer anmelden Versuchen Sie noch einmal, Ihre E-Mail-Adresse zu bestätigen @@ -367,12 +402,14 @@ Deutschland Probleme beim Empfangen von E-Mails? Kirgisistan -Sie haben bereits zur Anmeldung verwendet. Geben Sie das Passwort für dieses Konto ein. +Sie haben bereits zur Anmeldung verwendet. Gib das Passwort für dieses Konto ein. {plural_var,plural, =1{Das Passwort ist zu schwach. Verwenden Sie mindestens  Zeichen und eine Mischung aus Buchstaben und Ziffern.}other{Das Passwort ist zu schwach. Verwenden Sie mindestens  Zeichen und eine Mischung aus Buchstaben und Ziffern.}} Simbabwe -Folgen Sie der an gesendeten Anleitung, um Ihr Passwort zurückzusetzen. -Bei der Bestätigung Ihrer Telefonnummer ist ein Problem aufgetreten +Die Anfrage konnte aufgrund eines fehlenden, ungültigen oder abgelaufenen OAuth-Tokens nicht authentifiziert werden. +Gib einen sechsstelligen Bestätigungscode für die Telefonnummer an. +Passwort auswählen Neues Gerät oder neuer Browser erkannt +Jetzt in anmelden Tschechien Tuvalu Über GitHub anmelden @@ -389,7 +426,7 @@ Vielen Dank! Das -Team -Geben Sie zum Senden von Bestätigungscodes eine Telefonnummer für den Empfänger an. +Gib zum Senden von Bestätigungscodes eine Telefonnummer für den Empfänger an. Sudan Bahamas Côte d'Ivoire @@ -398,6 +435,10 @@ Spanien Costa Rica Samoa + +Ihr Konto in wurde mit für die Bestätigung in zwei Schritten eingerichtet. +Wenn Sie diese Änderung nicht angefordert haben, machen Sie sie mit dem folgenden Link wieder rückgängig. + Mali Katar Uruguay @@ -428,6 +469,7 @@ Bermuda Niue Guadeloupe +Der Client hat eine ungültige Projektkonfiguration angegeben. Palästinensische Gebiete Slowakei Guernsey @@ -440,19 +482,24 @@ Malaysia Kokosinseln Wenn Sie Ihr -Konto weiterhin verbinden möchten, öffnen Sie den Link bitte auf dem Gerät, auf dem Sie den Anmeldevorgang gestartet haben. Andernfalls tippen Sie auf "Weiter", um sich auf diesem Gerät anzumelden. +Wiederholen Sie haben versucht, auf einem Gerät, auf dem Sie nicht angemeldet sind, mit Ihrem E-Mail-Konto zu verbinden. Code in 0: erneut senden Marokko +Wenn Sie dieses Gerät nicht kennen, versucht möglicherweise jemand, auf Ihr Konto zuzugreifen. Sie sollten Ihr Passwort sofort ändern. Bangladesch Telefonnummer Irland Vereinigte Arabische Emirate +Ihr E-Mail-Adresse wurde überprüft und geändert Passwort Zentralafrikanische Republik Curaçao +Alle Anfragen von diesem Gerät wurden aufgrund ungewöhnlicher Aktivitäten blockiert. Versuchen Sie es später noch einmal. Tunesien Passwort geändert Griechenland +Zweiter Authentifizierungsschritt wurde entfernt Angola Kuwait Die E-Mail-Adresse und das Passwort passen nicht zusammen diff --git a/translations/el.xtb b/translations/el.xtb index bf785ffc..fb7db753 100644 --- a/translations/el.xtb +++ b/translations/el.xtb @@ -12,11 +12,14 @@ Φιλιππίνες Ρουμανία Νότια Γεωργία και Νότιες Νήσοι Σάντουιτς -ΟΚ +Ανεπανόρθωτη απώλεια ή αλλοίωση δεδομένων. Ιταλία Λίβανος Γουιάνα Γουατεμάλα +Προέκυψε πρόβλημα με την κατάργηση του δεύτερου παράγοντα.Δοκιμάστε να τον καταργήσετε ξανά. Αν δεν λυθεί το πρόβλημα, επικοινωνήστε με το τμήμα υποστήριξης για βοήθεια. +Δοκιμάστε να ενημερώσετε τη διεύθυνση ηλεκτρονικού σας ταχυδρομείου ξανά +Παρουσιάστηκε σφάλμα δικτύου. Δοκιμάστε ξανά αργότερα. Εισαγάγετε έναν έγκυρο αριθμό τηλεφώνου Αν πατήσετε Επαλήθευση, δηλώνετε ότι αποδέχεστε τους Όρους Παροχής Υπηρεσιών και την Πολιτική απορρήτου. Μπορεί να σταλεί ένα SMS. Ενδέχεται να ισχύουν χρεώσεις μηνυμάτων και δεδομένων. Εισαγάγετε τον κωδικό πρόσβασής σας @@ -27,6 +30,7 @@ Η διεύθυνση IP έχει στείλει πάρα πολλά αιτήματα λογαριασμού. Δοκιμάστε ξανά σε λίγα λεπτά. Σρι Λάνκα Πλέον μπορείτε να συνδέεστε με τον νέο λογαριασμό σας +Ο πελάτης καθόρισε ένα μη έγκυρο όρισμα. Τσαντ Επανάληψη αποστολής κωδικού στο Λευκορωσία @@ -62,6 +66,7 @@ Github Πολιτική απορρήτου Γκουάμ +Είτε πόροι εκτός ορίου είτε προσέγγιση του ορίου χρέωσης. Κροατία Λιθουανία Μάλτα @@ -71,22 +76,28 @@ Επισκέπτης Γκάνα Το πρόγραμμα περιήγησης που χρησιμοποιείτε δεν υποστηρίζει αποθήκευση στον ιστό. Δοκιμάστε ξανά σε άλλο πρόγραμμα περιήγησης. +Το αίτημα ακυρώθηκε από τον πελάτη. Παρουσιάστηκε πρόβλημα με την επαναφορά της διεύθυνσης ηλεκτρονικού ταχυδρομείου σύνδεσης.Αν δοκιμάσετε ξανά και εξακολουθείτε να μην μπορείτε να επαναφέρετε τη διεύθυνση, ζητήστε βοήθεια από τον διαχειριστή σας. Μπαχρέιν +Εσωτερικό σφάλμα διακομιστή. Ιράν +GitHub Επαλήθευση ότι δεν είστε ρομπότ… Αυτός ο κωδικός δεν είναι πλέον έγκυρος Νέα Καληδονία Μονακό Σεϋχέλλες +Το καταργήθηκε από δεύτερο βήμα ελέγχου ταυτότητας. Δεν ήταν δυνατή η αναβάθμιση του τρέχοντος ανώνυμου χρήστη. Τα μη ανώνυμα διαπιστευτήρια σχετίζονται ήδη με έναν άλλο λογαριασμό χρήστη. Πληκτρολογήσατε πολλές φορές λανθασμένο κωδικό πρόσβασης. Δοκιμάστε ξανά σε λίγα λεπτά. Ασφάλεια Το αίτημα επαναφοράς του κωδικού πρόσβασής σας έληξε ή ο σύνδεσμος χρησιμοποιείται ήδη +Πλέον μπορείτε να συνδέεστε με τον νέο λογαριασμό σας . Ο κωδικός χώρας που καταχωρίσατε δεν υποστηρίζεται. Βεβαιωθείτε ότι δεν έχετε γράψει λάθος τη διεύθυνση ηλεκτρονικού ταχυδρομείου. Twitter Καμπότζη +Η υπηρεσία δεν είναι διαθέσιμη. Πορτογαλία Γαλλική Γουιάνα Βρετανικές Παρθένοι Νήσοι @@ -102,19 +113,21 @@ Επαλήθευση Νότια Αφρική Εισαγάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου για να συνεχίσετε +Ο λογαριασμός χρήστη απενεργοποιήθηκε από έναν διαχειριστή. Ένα μήνυμα ηλεκτρονικού ταχυδρομείου σύνδεσης με πρόσθετες οδηγίες στάλθηκε στη διεύθυνση . Ελέγξτε τη διεύθυνση ηλεκτρονικού ταχυδρομείου για να ολοκληρώσετε τη σύνδεση. Δημιουργία λογαριασμού Αφγανιστάν Αν πατήσετε , δηλώνετε ότι συμφωνείτε με τους . Καναδάς Δανία +Υπέρβαση προθεσμίας αιτήματος. Αυστρία Οι διευθύνσεις ηλεκτρονικού ταχυδρομείου δεν ταιριάζουν Νότιο Σουδάν Υεμένη Γουινέα-Μπισάου Σαουδική Αραβία -Λογότυπο εφαρμογής +Προστέθηκε ένας επιπλέον παράγοντας ασφάλειας στο Αμερικανικές Παρθένες Νήσοι Σύνδεση μέσω τηλεφώνου Ινδονησία @@ -122,6 +135,7 @@ Βενεζουέλα Πρόβλημα κατά τη σύνδεση; Επαληθεύστε ότι είστε εσείς +Προέκυψε σφάλμα κατά την επαλήθευση του αιτήματός σας. Επισκεφτείτε το URL που σας ανακατεύθυνε σε αυτήν τη σελίδα ξανά για να ξεκινήσετε πάλι τη διαδικασία επαλήθευσης. Ντομίνικα Νέος κωδικός πρόσβασης Γκαμπόν @@ -139,6 +153,7 @@ Γκάμπια Τέλος Ακύρωση +Άγνωστο σφάλμα διακομιστή. Εισαγάγετε το όνομα του λογαριασμού σας Ιράκ Βιετνάμ @@ -150,6 +165,8 @@ Νεπάλ Τοκελάου Τα επιλεγμένα διαπιστευτήρια για τον πάροχο ελέγχου ταυτότητας δεν υποστηρίζονται! +Κανένας πάροχος σύνδεσης δεν είναι διαθέσιμος για τη δεδομένη διεύθυνση ηλεκτρονικού ταχυδρομείου. Δοκιμάστε μια διαφορετική διεύθυνση ηλεκτρονικού ταχυδρομείου. +Εισαγάγετε ένα όνομα και ένα επώνυμο. Κολομβία Αν πατήσετε "ΑΠΟΘΗΚΕΥΣΗ", δηλώνετε ότι συμφωνείτε με τους Μπουρούντι @@ -157,7 +174,7 @@ Twitter Η διεύθυνση ηλεκτρονικού ταχυδρομείου επαληθεύτηκε Παπούα Νέα Γουινέα -Αποθήκευση +Ο καθορισμένος πόρος δεν βρέθηκε. Γίνεται επαλήθευση... Σύνδεση Ανατολικό Τιμόρ @@ -190,6 +207,7 @@ Τηλέφωνο Επανάληψη αποστολής Ονδούρα +Η λειτουργία έληξε. Νίγηρας Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου δεν ταιριάζει με κάποιον υπάρχοντα λογαριασμό Ιορδανία @@ -201,6 +219,7 @@ Ισραήλ Παρουσιάστηκε σφάλμα Αριθμός +Ο πελάτης δεν έχει επαρκή άδεια. Λύστε το reCAPTCHA Νικαράγουα Μπουρκίνα Φάσο @@ -209,14 +228,17 @@ Νησιά Τερκ και Κάικος Επανάληψη αποστολής κωδικού σε Ελέγξτε τα εισερχόμενα του ηλεκτρονικού ταχυδρομείου σας +Δεν ήταν δυνατή η κατάργηση του δεύτερου παράγοντα Προσθήκη κωδικού πρόσβασης Ελ Σαλβαδόρ +Για να αλλάξετε κωδικό πρόσβασης στον λογαριασμό σας, θα πρέπει να συνδεθείτε ξανά. Σερβία Μπελίζ για Σε αυτήν τη ροή για την επιτυχή σύνδεση του λογαριασμού με αυτήν τη διεύθυνση ηλεκτρονικού ταχυδρομείου, θα πρέπει να ανοίξετε τον σύνδεσμο στην ίδια συσκευή ή στο ίδιο πρόγραμμα περιήγησης. Σουηδία Σεν Μαρτέν +Το αίτημά σας για επαλήθευση και ενημέρωση της διεύθυνσης ηλεκτρονικού σας ταχυδρομείου έληξε ή ο σύνδεσμος χρησιμοποιείται ήδη. Αγία Λουκία @string/app_name Ελέγξτε τα εισερχόμενα του ηλεκτρονικού ταχυδρομείου σας @@ -224,13 +246,15 @@ Ομάν Μποναίρ, Άγιος Ευστάθιος και Σάμπα Ενημερωμένη διεύθυνση ηλεκτρονικού ταχυδρομείου -Σύνδεση μέσω Twitter +Έχετε πλέον αποσυνδεθεί με επιτυχία. +Σύνδεση μέσω τηλεφώνου Αριθμός τηλεφώνου Έχετε ήδη λογαριασμό Ταϊλάνδη Πολιτική απορρήτου Λιχτενστάιν Επεξεργασία ονόματος +Η συσκευή ή η εφαρμογή καταργήθηκε από δεύτερο βήμα ελέγχου ταυτότητας. Δοκιμάστε να ανοίξετε τον σύνδεσμο χρησιμοποιώντας την ίδια συσκευή ή το ίδιο πρόγραμμα περιήγησης από όπου ξεκινήσατε τη διαδικασία σύνδεσης. Ολλανδία Ισημερινή Γουινέα @@ -254,7 +278,7 @@ Εισαγάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου για να συνεχίσετε Επαλήθευση του τηλεφώνου σας Λιβερία -Υπάρχει ήδη λογαριασμός με αυτήν τη διεύθυνση ηλεκτρονικού ταχυδρομείου. +Έξοδος Μαδαγασκάρη Καζακστάν Σύνδεση… @@ -273,15 +297,20 @@ Εισαγάγετε τον κωδικό ψηφίων που στείλαμε στο Αυτός ο αριθμός τηλεφώνου χρησιμοποιήθηκε πάρα πολλές φορές Το μήνυμα ηλεκτρονικού ταχυδρομείου που παρείχατε δεν ταιριάζει με την τρέχουσα περίοδο σύνδεσης. -Αποσύνδεση λογαριασμού +Twitter + +Συνδεθείτε στην εφαρμογή </p></p> + Βεβαιωθείτε ότι δεν έχει γεμίσει ο χώρος εισερχομένων ή ότι δεν υπάρχουν άλλα προβλήματα που σχετίζονται με τις ρυθμίσεις εισερχομένων. +Ο πελάτης καθόρισε ένα μη έγκυρο εύρος. +Έχετε υπερβεί το όριο για αυτήν τη λειτουργία. Δοκιμάστε ξανά αργότερα. Παραγουάη Αλγερία Εισαγάγατε λανθασμένο κωδικό πρόσβασης πολλές φορές. Δοκιμάστε ξανά σε μερικά λεπτά. Νήσος Νόρφολκ Ο αριθμός τηλεφώνου επαληθεύτηκε αυτόματα -Για να αλλάξετε τον κωδικό πρόσβασής σας, θα πρέπει πρώτα να εισαγάγετε τον τρέχοντα κωδικό πρόσβασης. -Πίσω +Σφάλμα δικτύου, ελέγξτε τη σύνδεση στο διαδίκτυο. +Επαληθεύτηκε! Ινδία Μπενίν Συνέχεια @@ -330,27 +359,33 @@ Ηλεκτρονικό ταχυδρομείο Επόμενο Αυτός ο κωδικός δεν είναι πλέον έγκυρος -Σύνδεση μέσω Facebook +Ένα μήνυμα ηλεκτρονικού ταχυδρομείου σύνδεσης με πρόσθετες οδηγίες στάλθηκε στη διεύθυνση . Ελέγξτε τη διεύθυνση ηλεκτρονικού ταχυδρομείου για να ολοκληρώσετε τη σύνδεση. Επιβεβαίωση διεύθυνσης ηλεκτρονικού ταχυδρομείου Όνομα και επώνυμο Γρενάδα Δυτική Σαχάρα +Το αίτημα δεν μπορεί να εκτελεστεί στην τρέχουσα κατάσταση συστήματος. Παρουσιάστηκε άγνωστο σφάλμα. Έχετε ήδη χρησιμοποιήσει το για να συνδεθείτε. Εισαγάγετε τον κωδικό πρόσβασης για αυτόν τον λογαριασμό. +Το μήνυμα ηλεκτρονικού ταχυδρομείου σύνδεσης στάλθηκε Ιρλανδία Λαϊκή Δημοκρατία του Κονγκό Ανάκτηση κωδικού πρόσβασης Δημοκρατία του Κονγκό +Ο πόρος που δοκίμασε να δημιουργήσει ένας πελάτης υπάρχει ήδη. Συνέχεια ως επισκέπτης Άγιος Μαρίνος Κύπρος Νήσοι Σολομώντα Συνέχεια με ; +Για να συνεχίσετε τη σύνδεση σε αυτήν τη συσκευή με τη διεύθυνση , πρέπει να ανακτήσετε τον κωδικό πρόσβασης. Μαλδίβες Σάο Τόμε και Πρίνσιπε Επιβεβαιώστε τη διεύθυνση ηλεκτρονικού ταχυδρομείου για να ολοκληρώσετε τη σύνδεση Σλοβενία Νότια Κορέα +Διένεξη ταυτοχρονισμού, όπως διένεξη ανάγνωσης-τροποποίησης-εγγραφής. +Η μέθοδος API δεν εφαρμόστηκε από τον διακομιστή. Λεσότο Σύνδεση μέσω τηλεφώνου Δοκιμάστε να επαληθεύσετε ξανά τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας @@ -370,8 +405,10 @@ για να συνδεθείτε. Εισαγάγετε τον κωδικό πρόσβασής σας για αυτόν τον λογαριασμό. {plural_var,plural, =1{Ο κωδικός πρόσβασης δεν είναι αρκετά ισχυρός. Χρησιμοποιήστε τουλάχιστον χαρακτήρα και έναν συνδυασμό γραμμάτων και αριθμών}other{Ο κωδικός πρόσβασης δεν είναι αρκετά ισχυρός. Χρησιμοποιήστε τουλάχιστον χαρακτήρες και έναν συνδυασμό γραμμάτων και αριθμών}} Ζιμπάμπουε +Μη εξουσιοδοτημένο αίτημα λόγω ελλιπούς, μη έγκυρου ή ληγμένου διακριτικού OAuth. Παρουσιάστηκε πρόβλημα με την επαλήθευση του αριθμού τηλεφώνου Εντοπίστηκε νέα συσκευή ή πρόγραμμα περιήγησης +Σύνδεση με Δημοκρατία της Τσεχίας Τουβάλου Σύνδεση μέσω GitHub @@ -426,6 +463,7 @@ Βερμούδες Νιούε Γουαδελούπη +Ο πελάτης καθόρισε μια μη έγκυρη διαμόρφωση έργου. Παλαιστινιακά εδάφη Σλοβακία Γκέρνσεϊ @@ -438,19 +476,24 @@ Μαλαισία Νησιά Κόκος (Κήλινγκ) Αν εξακολουθείτε να θέλετε να συνδέσετε τον λογαριασμό σας , ανοίξτε τον σύνδεσμο από την ίδια συσκευή από την οποία ξεκινήσατε τη σύνδεση. Διαφορετικά, πατήστε Συνέχεια για να συνδεθείτε από αυτήν τη συσκευή. +Επανάληψη Σκοπεύατε να συνδέσετε το με τον λογαριασμό ηλεκτρονικού σας ταχυδρομείου, αλλά χρησιμοποιήσατε άλλη συσκευή για το άνοιγμα του συνδέσμου στην οποία δεν είστε συνδεδεμένοι. Επανάληψη αποστολής κωδικού σε 0: Μαρόκο +Αν δεν αναγνωρίζετε αυτήν τη συσκευή, ίσως κάποιος προσπαθεί να αποκτήσει πρόσβαση στον λογαριασμό σας. Σκεφτείτε το ενδεχόμενο να αλλάξετε τον κωδικό πρόσβασης αμέσως. Μπανγκλαντές Τηλέφωνο Φινλανδία Ηνωμένα Αραβικά Εμιράτα +Η διεύθυνση ηλεκτρονικού σας ταχυδρομείου επαληθεύτηκε και άλλαξε Κωδικός πρόσβασης Κεντροαφρικανική Δημοκρατία Κουρασάο +Αποκλείσαμε όλα τα αιτήματα από αυτήν τη συσκευή λόγω ασυνήθιστης δραστηριότητας. Δοκιμάστε ξανά αργότερα. Τυνησία Ο κωδικός πρόσβασης άλλαξε Ελλάδα +Καταργημένος δεύτερος παράγοντας Αγκόλα Κουβέιτ Η διεύθυνση ηλεκτρονικού ταχυδρομείου και ο κωδικός πρόσβασης δεν ταιριάζουν diff --git a/translations/en-GB.xtb b/translations/en-GB.xtb index b902e84a..894ce62c 100644 --- a/translations/en-GB.xtb +++ b/translations/en-GB.xtb @@ -12,11 +12,14 @@ Philippines Romania South Georgia and the South Sandwich Islands -OK +Unrecoverable data loss or data corruption. Italy Lebanon Guyana Guatemala +Something went wrong while removing your second factor.Try removing it again. If that doesn't work, contact support for assistance. +Try updating your email again +A network error has occurred. Try again later. Enter a valid phone number By tapping Verify, you are indicating that you accept our Terms of Service and Privacy Policy. An SMS may be sent. Message &amp; data rates may apply. Enter your password @@ -27,6 +30,7 @@ Too many account requests are coming from your IP address. Try again in a few minutes. Sri Lanka You can now sign in with your new account +Client specified an invalid argument. Chad Resend code in Belarus @@ -62,6 +66,7 @@ Github Privacy Policy Guam +Either out of resource quota or reaching rate limiting. Croatia Lithuania Malta @@ -71,22 +76,28 @@ Guest Ghana The browser you are using does not support Web Storage. Please try again in a different browser. +Request cancelled by the client. There was a problem changing your sign-in email back.If you try again and still can't reset your email, try asking your administrator for help. Bahrain +Internal server error. Iran +GitHub Verifying you're not a robot... This code is no longer valid New Caledonia Monaco Seychelles +The was removed as a second authentication step. The current anonymous user failed to upgrade. The non-anonymous credential is already associated with a different user account. You've entered an incorrect password too many times. Try again in a few minutes. Security Your request to reset your password has expired or the link has already been used +You can now sign in with your new email . The country code provided is not supported. Check that you did not misspell your email. Twitter Cambodia +Service unavailable. Portugal French Guiana British Virgin Islands @@ -102,6 +113,7 @@ Verify South Africa Enter your email address to continue +The user account has been disabled by an administrator. A sign-in email with additional instructions was sent to . Check your email to complete sign-in. Enter your password Create account @@ -109,13 +121,14 @@ By tapping you are indicating that you agree to the . Canada Denmark +Request deadline exceeded. Austria Emails don't match South Sudan Yemen Guinea-Bissau Saudi Arabia -App logo +An extra security factor has been added in US Virgin Islands Sign in with phone Indonesia @@ -123,6 +136,7 @@ Venezuela Trouble signing in? Verify that it's you +An issue was encountered when authenticating your request. Please visit the URL that redirected you to this page again to restart the authentication process. Dominica New password Gabon @@ -140,6 +154,7 @@ Gambia Done Cancel +Unknown server error. Enter your account name Iraq Vietnam @@ -151,6 +166,8 @@ Nepal Tokelau The selected credential for the authentication provider is not supported! +No sign-in provider is available for the given email. Please try again with a different email. +Please enter a first name and surname. Colombia By tapping SAVE you are indicating that you agree to the Burundi @@ -158,7 +175,7 @@ Twitter Your email has been verified Papua New Guinea -Save +Specified resource has not been found. Verifying... Sign in East Timor @@ -191,6 +208,7 @@ Phone Resend Honduras +The operation has timed out. Niger That email address doesn't match an existing account Jordan @@ -202,6 +220,7 @@ Israel Error encountered Number +Client does not have sufficient permission. Solve the reCAPTCHA Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Turks and Caicos Islands Resend code in Check your email +Couldn't remove your second factor Add password El Salvador +To change your account's password, you will need to sign in again. Serbia Belize for For this flow to successfully connect your account with this email, you have to open the link on the same device or browser. Sweden Saint Martin +Your request to verify and update your email has expired, or the link has already been used. Saint Lucia @string/app_name Check your email @@ -225,13 +247,15 @@ Oman Caribbean Netherlands Updated email address -Sign in with Twitter +You are now successfully signed out. +Sign in with phone Phone number You already have an account Thailand Wrong code. Try again. Liechtenstein Edit name +The device or app was removed as a second authentication step. Try opening the link using the same device or browser where you started the sign-in process. Netherlands Equatorial Guinea @@ -255,7 +279,7 @@ Enter your email address to continue Verify your phone number Liberia -An account already exists with that email address. +Sign out Madagascar Kazakhstan Signing in... @@ -274,15 +298,20 @@ Enter the -digit code that we sent to This phone number has been used too many times The email provided does not match the current sign-in session. -Unlink account +Twitter + +Sign in to </p></p> + Check that your inbox space is not running out, or for other inbox settings-related issues. +Client specified an invalid range. +The quota for this operation has been exceeded. Try again later. Paraguay Algeria You have entered an incorrect password too many times. Please try again in a few minutes. Norfolk Island Phone number automatically verified -In order to change your password, you first need to enter your current password. -Back +Network error, check your Internet connection. +Verified. India Benin Continue @@ -331,27 +360,33 @@ Email Next This code is no longer valid -Sign in with Facebook +A sign-in email with additional instructions was sent to . Check your email to complete sign-in. Confirm email First name &amp; surname Grenada Western Sahara +Request can not be executed in the current system state. An unknown error occurred. You've already used to sign in. Enter your password for that account. +Sign-in email sent Ireland Democratic Republic of the Congo Recover password Republic of Congo +The resource that a client tried to create already exists. Continue as a guest San Marino Cyprus Solomon Islands Continue with ? +To continue to sign in with on this device, you must recover the password. Maldives São Tomé and Príncipe Confirm your email to complete sign-in Slovenia South Korea +Concurrency conflict, such as read-modify-write conflict. +API method not implemented by the server. Lesotho Sign in with phone Try verifying your email again @@ -371,9 +406,11 @@ to sign in. Enter your password for that account. {plural_var,plural, =1{Password not strong enough. Use at least character and a mix of letters and numbers}other{Password not strong enough. Use at least characters and a mix of letters and numbers}} Zimbabwe -Follow the instructions sent to to recover your password. -There was a problem verifying your phone number +Request not authenticated due to missing, invalid or expired OAuth token. +Please provide a 6-digit phone verification code. +Choose password New device or browser detected +Sign in to Czechia Tuvalu Sign in with GitHub @@ -399,6 +436,10 @@ Spain Costa Rica Samoa + +Your account in has been updated with for two-factor authentication. +If you did not request this modification, please use this the following link to undo the change. + Mali Qatar Uruguay @@ -429,6 +470,7 @@ Bermuda Niue Guadeloupe +Client specified an invalid project configuration. Palestinian Territories Slovakia Guernsey @@ -441,19 +483,24 @@ Malaysia Cocos [Keeling] Islands If you still want to connect your account, open the link on the device that you used to start the sign in process. Otherwise, tap "Continue" to sign in on this device. +Retry You originally intended to connect to your email account, but have opened the link on a different device on which you are not signed in. Resend code in 0: Morocco +If you don't recognise this device, someone might be trying to access your account. Consider changing your password right away. Bangladesh Phone Finland United Arab Emirates +Your email has been verified and changed Password Central African Republic Curaçao +We have blocked all requests from this device due to unusual activity. Try again later. Tunisia Password changed Greece +Removed second factor Angola Kuwait The email and password that you entered don't match diff --git a/translations/en-XA.xtb b/translations/en-XA.xtb index e4b7a418..0214e958 100644 --- a/translations/en-XA.xtb +++ b/translations/en-XA.xtb @@ -12,11 +12,14 @@ [Þĥîļîþþîñéš one two] [Ŕömåñîå one] [Šöûţĥ Ĝéöŕĝîå åñð ţĥé Šöûţĥ Šåñðŵîçĥ Κļåñðš one two three four five six seven eight nine] -[ÖĶ one] +[Ûñŕéçövéŕåбļé ðåţå ļöšš öŕ ðåţå çöŕŕûþţîöñ. one two three four five six seven eight nine] [Îţåļý one] [Ļéбåñöñ one] [Ĝûýåñå one] [Ĝûåţémåļå one two] +[ᐅᐊŠöméţĥîñĝ ŵéñţ ŵŕöñĝ ŕémövîñĝ ýöûŕ šéçöñð ƒåçţöŕ.ᐅᐊᐅᐊŢŕý ŕémövîñĝ îţ åĝåîñ. ΃ ţĥåţ ðöéšñ'ţ ŵöŕķ, çöñţåçţ šûþþöŕţ ƒöŕ åššîšţåñçé.ᐅᐊ one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone] +[Ţŕý ûþðåţîñĝ ýöûŕ émåîļ åĝåîñ one two three four five six seven] +[Å ñéţŵöŕķ éŕŕöŕ ĥåš öççûŕŕéð. Ţŕý åĝåîñ ļåţéŕ. one two three four five six seven eight nine ten] [Éñţéŕ å våļîð þĥöñé ñûmбéŕ one two three four five six] [Бý ţåþþîñĝ Véŕîƒý, ýöû åŕé îñðîçåţîñĝ ţĥåţ ýöû åççéþţ öûŕ ᐅᐊŢéŕmš öƒ Šéŕvîçéᐅᐊ åñð ᐅᐊÞŕîvåçý Þöļîçýᐅᐊ. Åñ ŠMŠ måý бé šéñţ. Méššåĝé &amp; ðåţå ŕåţéš måý åþþļý. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree] [Éñţéŕ ýöûŕ þåššŵöŕð one two three] @@ -27,6 +30,7 @@ [Ţöö måñý åççöûñţ ŕéqûéšţš åŕé çömîñĝ ƒŕöm ýöûŕ ÎÞ åððŕéšš. Ţŕý åĝåîñ îñ å ƒéŵ mîñûţéš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] [Šŕî Ļåñķå one two] [Ýöû çåñ ñöŵ šîĝñ îñ ŵîţĥ ýöûŕ ñéŵ åççöûñţ one two three four five six seven eight nine] +[Çļîéñţ šþéçîƒîéð åñ îñvåļîð åŕĝûméñţ. one two three four five six seven eight] [Çĥåð one] [Ŕéšéñð çöðé îñ ᐅᐊ one two three four] [Бéļåŕûš one] @@ -62,6 +66,7 @@ [Ĝîţĥûб one] [Þŕîvåçý Þöļîçý one two] [Ĝûåm one] +[Éîţĥéŕ öûţ öƒ ŕéšöûŕçé qûöţå öŕ ŕéåçĥîñĝ ŕåţé ļîmîţîñĝ. one two three four five six seven eight nine ten eleven] [Çŕöåţîå one] [Ļîţĥûåñîå one two] [Måļţå one] @@ -71,22 +76,28 @@ [Ĝûéšţ one] [Ĝĥåñå one] [Ţĥé бŕöŵšéŕ ýöû åŕé ûšîñĝ ðöéš ñöţ šûþþöŕţ Ŵéб Šţöŕåĝé. Þļéåšé ţŕý åĝåîñ îñ å ðéŕéñţ бŕöŵšéŕ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen] +[Ŕéqûéšţ çåñçéļļéð бý ţĥé çļîéñţ. one two three four five six seven] [ᐅᐊŢĥéŕé ŵåš å þŕöбļém çĥåñĝîñĝ ýöûŕ šîĝñ-îñ émåîļ бåçķ.ᐅᐊᐅᐊ΃ ýöû ţŕý åĝåîñ åñð šţîļļ çåñ’ţ ŕéšéţ ýöûŕ émåîļ, ţŕý åšķîñĝ ýöûŕ åðmîñîšţŕåţöŕ ƒöŕ ĥéļþ.ᐅᐊ one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree] [Бåĥŕåîñ one] +[Îñţéŕñåļ šéŕvéŕ éŕŕöŕ. one two three] [Îŕåñ one] +[ĜîţĤûб one] [Véŕîƒýîñĝ ýöû'ŕé ñöţ å ŕöбöţ... one two three four five six seven] [Ţĥîš çöðé îš ñö ļöñĝéŕ våļîð one two three four five six] [Ñéŵ Çåļéðöñîå one two] [Möñåçö one] [Šéýçĥéļļéš one two] +[Ţĥé ᐅᐊᐅᐊ ᐅᐊᐅᐊ ŵåš ŕémövéð åš å šéçöñð åûţĥéñţîçåţîöñ šţéþ. one two three four five six seven eight nine ten eleven twelve] [Ţĥé çûŕŕéñţ åñöñýmöûš ûšéŕ ƒåîļéð ţö ûþĝŕåðé. Ţĥé ñöñ-åñöñýmöûš çŕéðéñţîåļ îš åļŕéåðý åššöçîåţéð ŵîţĥ å ðéŕéñţ ûšéŕ åççöûñţ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty] [Ýöû’vé éñţéŕéð åñ îñçöŕŕéçţ þåššŵöŕð ţöö måñý ţîméš. Ţŕý åĝåîñ îñ å ƒéŵ mîñûţéš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen] [Éñţéŕ å våļîð þĥöñé ñûmбéŕ. one two three four five six] [Ýöûŕ ŕéqûéšţ ţö ŕéšéţ ýöûŕ þåššŵöŕð ĥåš éxþîŕéð öŕ ţĥé ļîñķ ĥåš åļŕéåðý бééñ ûšéð one two three four five six seven eight nine ten eleven twelve thirteen fourteen] +[Ýöû çåñ ñöŵ šîĝñ îñ ŵîţĥ ýöûŕ ñéŵ émåîļ ᐅᐊᐅᐊᐅᐊ. one two three four five six seven eight nine ten eleven] [Ţĥé çöûñţŕý çöðé þŕövîðéð îš ñöţ šûþþöŕţéð. one two three four five six seven eight nine] [Çĥéçķ ţĥåţ ýöû ðîð ñöţ mîššþéļļ ýöûŕ émåîļ. one two three four five six seven eight nine] [Ţŵîţţéŕ one] [Çåmбöðîå one] +[Šéŕvîçé ûñåvåîļåбļé. one two three] [Þöŕţûĝåļ one] [Fŕéñçĥ Ĝûîåñå one two] [Бŕîţîšĥ Vîŕĝîñ Κļåñðš one two three] @@ -102,6 +113,7 @@ [Véŕîƒý one] [Šöûţĥ Ńŕîçå one two] [Éñţéŕ ýöûŕ émåîļ åððŕéšš ţö çöñţîñûé one two three four five six seven eight] +[Ţĥé ûšéŕ åççöûñţ ĥåš бééñ ðîšåбļéð бý åñ åðmîñîšţŕåţöŕ. one two three four five six seven eight nine ten eleven] [Å šîĝñ-îñ émåîļ ŵîţĥ åððîţîöñåļ îñšţŕûçţîöñš ŵåš šéñţ ţö ᐅᐊᐅᐊᐅᐊ. Çĥéçķ ýöûŕ émåîļ ţö çömþļéţé šîĝñ-îñ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen] [Éñţéŕ ýöûŕ þåššŵöŕð one two three] [Çŕéåţé åççöûñţ one two] @@ -109,13 +121,14 @@ [Бý ţåþþîñĝ ᐅᐊ ýöû åŕé îñðîçåţîñĝ ţĥåţ ýöû åĝŕéé ţö ţĥé ᐅᐊ. one two three four five six seven eight nine ten eleven twelve] [Çåñåðå one] [Ðéñmåŕķ one] +[Ŕéqûéšţ ðéåðļîñé éxçééðéð. one two three] [Åûšţŕîå one] [Émåîļš ðöñ'ţ måţçĥ one two three] [Šöûţĥ Šûðåñ one two] [Ýéméñ one] [Ĝûîñéå-Бîššåû one two] [Šåûðî Åŕåбîå one two] -[Åþþ ļöĝö one] +[Åñ éxţŕå šéçûŕîţý ƒåçţöŕ ĥåš бééñ åððéð îñ ᐅᐊ one two three four five six seven eight nine ten] [Û.Š. Vîŕĝîñ Κļåñðš one two three] [Šîĝñ îñ ŵîţĥ þĥöñé one two three four] [Îñðöñéšîå one two] @@ -123,6 +136,7 @@ [Véñéžûéļå one two] [Ţŕöûбļé šîĝñîñĝ îñ¿ one two three] [Véŕîƒý îţ'š ýöû one two] +[Åñ îššûé ŵåš éñçöûñţéŕéð ŵĥéñ åûţĥéñţîçåţîñĝ ýöûŕ ŕéqûéšţ. Þļéåšé vîšîţ ţĥé ÛŔĻ ţĥåţ ŕéðîŕéçţéð ýöû ţö ţĥîš þåĝé åĝåîñ ţö ŕéšţåŕţ ţĥé åûţĥéñţîçåţîöñ þŕöçéšš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree] [Ðömîñîçå one] [Ñéŵ þåššŵöŕð one two] [Ĝåбöñ one] @@ -140,6 +154,7 @@ [Ĝåmбîå one] [Ðöñé one] [Çåñçéļ one] +[Ûñķñöŵñ šéŕvéŕ éŕŕöŕ. one two three] [Éñţéŕ ýöûŕ åççöûñţ ñåmé one two three four five] [Îŕåq one] [Vîéţñåm one] @@ -151,6 +166,8 @@ [Ñéþåļ one] [Ţöķéļåû one] [Ţĥé šéļéçţéð çŕéðéñţîåļ ƒöŕ ţĥé åûţĥéñţîçåţîöñ þŕövîðéŕ îš ñöţ šûþþöŕţéð¡ one two three four five six seven eight nine ten eleven twelve thirteen fourteen] +[Ñö šîĝñ-îñ þŕövîðéŕ îš åvåîļåбļé ƒöŕ ţĥé ĝîvéñ émåîļ, þļéåšé ţŕý ŵîţĥ å ðéŕéñţ émåîļ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] +[Þļéåšé éñţéŕ å ƒîŕšţ åñð ļåšţ ñåmé. one two three four five six seven eight] [Çöļömбîå one] [Бý ţåþþîñĝ ŠÅVÉ ýöû åŕé îñðîçåţîñĝ ţĥåţ ýöû åĝŕéé ţö ţĥé one two three four five six seven eight nine ten eleven twelve] [Бûŕûñðî one] @@ -158,7 +175,7 @@ [Ţŵîţţéŕ one] [Ýöûŕ émåîļ ĥåš бééñ véŕîƒîéð one two three four five six] [Þåþûå Ñéŵ Ĝûîñéå one two] -[Šåvé one] +[Šþéçîƒîéð ŕéšöûŕçé îš ñöţ ƒöûñð. one two three four five six seven] [Véŕîƒýîñĝ... one two] [Šîĝñ Îñ one] [Éåšţ Ţîmöŕ one two] @@ -191,6 +208,7 @@ [Þĥöñé one] [Ŕéšéñð one] [Ĥöñðûŕåš one] +[Ţĥé öþéŕåţîöñ ĥåš ţîméð öûţ. one two three four five six] [Ñîĝéŕ one] [Ţĥåţ émåîļ åððŕéšš ðöéšñ'ţ måţçĥ åñ éxîšţîñĝ åççöûñţ one two three four five six seven eight nine ten eleven] [Ĵöŕðåñ one] @@ -202,6 +220,7 @@ [Κŕåéļ one] [Éŕŕöŕ éñçöûñţéŕéð one two three] [Ñûmбéŕ one] +[Çļîéñţ ðöéš ñöţ ĥåvé šûƒƒîçîéñţ þéŕmîššîöñ. one two three four five six seven eight nine] [Šöļvé ţĥé ŕéÇÅÞŢÇĤÅ one two three] [Ñîçåŕåĝûå one two] [Бûŕķîñå Fåšö one two] @@ -210,14 +229,18 @@ [Ţûŕķš åñð Çåîçöš Κļåñðš one two three four five] [Ŕéšéñð çöðé îñ ᐅᐊ one two three four] [Çĥéçķ ýöûŕ émåîļ one two] +[Çöûļðñ'ţ ŕémövé ýöûŕ šéçöñð ƒåçţöŕ one two three four five six seven] [Åðð þåššŵöŕð one two] [Éļ Šåļvåðöŕ one two] +[Ţö çĥåñĝé þåššŵöŕð ţö ýöûŕ åççöûñţ, ýöû ŵîļļ ñééð ţö šîĝñ îñ åĝåîñ. one two three four five six seven eight nine ten eleven twelve thirteen] [Šéŕбîå one] [Бéļîžé one] [ᐅᐊƒöŕ ᐅᐊᐅᐊᐅᐊᐅᐊ one two three four] [Föŕ ţĥîš ƒļöŵ ţö šûççéššƒûļļý çöññéçţ ýöûŕ ᐅᐊ åççöûñţ ŵîţĥ ţĥîš émåîļ, ýöû ĥåvé ţö öþéñ ţĥé ļîñķ öñ ţĥé šåmé ðévîçé öŕ бŕöŵšéŕ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty] [Šŵéðéñ one] [Šåîñţ Måŕţîñ one two] +[ᐅᐊ one] +[ᐅᐊÝöûŕ ŕéqûéšţ ţö véŕîƒý åñð ûþðåţé ýöûŕ émåîļ ĥåš éxþîŕéð öŕ ţĥé ļîñķ ĥåš åļŕéåðý бééñ ûšéð.ᐅᐊ one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen] [Šţ. Ļûçîå one two] [@šţŕîñĝ/åþþ_ñåmé one two] [Çĥéçķ ýöûŕ émåîļ one two] @@ -225,13 +248,15 @@ [Ömåñ one] [Çåŕîббéåñ Ñéţĥéŕļåñðš one two three] [Ûþðåţéð émåîļ åððŕéšš one two three] -[Šîĝñ îñ ŵîţĥ Ţŵîţţéŕ one two three four five] +[Ýöû åŕé ñöŵ šûççéššƒûļļý šîĝñéð öûţ. one two three four five six seven eight] +[Šîĝñ îñ ŵîţĥ þĥöñé one two three four] [Þĥöñé ñûmбéŕ one two] [Ýöû åļŕéåðý ĥåvé åñ åççöûñţ one two three four five six] [Ţĥåîļåñð one] [Ŵŕöñĝ çöðé. Ţŕý åĝåîñ. one two three four five] [Ļîéçĥţéñšţéîñ one two] [Éðîţ ñåmé one two] +[Ţĥé ðévîçé öŕ åþþ ŵåš ŕémövéð åš å šéçöñð åûţĥéñţîçåţîöñ šţéþ. one two three four five six seven eight nine ten eleven twelve] [Ţŕý öþéñîñĝ ţĥé ļîñķ ûšîñĝ ţĥé šåmé ðévîçé öŕ бŕöŵšéŕ ŵĥéŕé ýöû šţåŕţéð ţĥé šîĝñ-îñ þŕöçéšš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen] [Ñéţĥéŕļåñðš one two] [Éqûåţöŕîåļ Ĝûîñéå one two three] @@ -251,11 +276,12 @@ [Šöméţĥîñĝ ŵéñţ ŵŕöñĝ. Þļéåšé ţŕý åĝåîñ. one two three four five six seven eight] [Þåššŵöŕð one] [Šţ. Ķîţţš one two] +[ᐅᐊ one] [Бåçķ one] [Éñţéŕ ýöûŕ émåîļ åððŕéšš ţö çöñţîñûé one two three four five six seven eight] [Véŕîƒý ýöûŕ þĥöñé ñûmбéŕ one two three four five] [Ļîбéŕîå one] -[Åñ åççöûñţ åļŕéåðý éxîšţš ŵîţĥ ţĥåţ émåîļ åððŕéšš. one two three four five six seven eight nine ten eleven] +[Šîĝñ Öûţ one] [Måðåĝåšçåŕ one two] [Ķåžåķĥšţåñ one two] [Šîĝñîñĝ îñ... one two] @@ -275,18 +301,18 @@ [Émåîļ one] [Ţĥîš þĥöñé ñûmбéŕ ĥåš бééñ ûšéð ţöö måñý ţîméš one two three four five six seven eight nine ten] [Ţĥé émåîļ þŕövîðéð ðöéš ñöţ måţçĥ ţĥé çûŕŕéñţ šîĝñ-îñ šéššîöñ. one two three four five six seven eight nine ten eleven twelve] -[Ţĥîš þĥöñé ñûmбéŕ ĥåš бééñ ûšéð ţöö måñý ţîméš. one two three four five six seven eight nine ten] -[ -ᐅᐊŠîĝñ îñ ţö ᐅᐊ </p></p> -ᐅᐊᐅᐊᐅᐊᐅᐊᐅᐊ one two three four five six seven eight] +[Ţŵîţţéŕ one] +[Ûñļîñķ åççöûñţ one two] [Çĥéçķ ţĥåţ ýöûŕ îñбöx šþåçé îš ñöţ ŕûññîñĝ öûţ öŕ öţĥéŕ îñбöx šéţţîñĝš ŕéļåţéð îššûéš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] +[Çļîéñţ šþéçîƒîéð åñ îñvåļîð ŕåñĝé. one two three four five six seven] +[Ţĥé qûöţå ƒöŕ ţĥîš öþéŕåţîöñ ĥåš бééñ éxçééðéð. Ţŕý åĝåîñ ļåţéŕ. one two three four five six seven eight nine ten eleven twelve thirteen] [Þåŕåĝûåý one] [Åļĝéŕîå one] [Ýöû ĥåvé éñţéŕéð åñ îñçöŕŕéçţ þåššŵöŕð ţöö måñý ţîméš. Þļéåšé ţŕý åĝåîñ îñ å ƒéŵ mîñûţéš. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] [Ñöŕƒöļķ Κļåñð one two] [Þĥöñé ñûmбéŕ åûţömåţîçåļļý véŕîƒîéð one two three four five six seven eight] -[Îñ öŕðéŕ ţö çĥåñĝé ýöûŕ þåššŵöŕð, ýöû ƒîŕšţ ñééð ţö éñţéŕ ýöûŕ çûŕŕéñţ þåššŵöŕð. one two three four five six seven eight nine ten eleven twelve thirteen fourteen] -[Бåçķ one] +[Ñéţŵöŕķ éŕŕöŕ, çĥéçķ ýöûŕ îñţéŕñéţ çöññéçţîöñ. one two three four five six seven eight nine ten] +[Véŕîƒîéð¡ one two] [Îñðîå one] [Бéñîñ one] [Çöñţîñûé one] @@ -335,27 +361,33 @@ [Émåîļ one] [Ñéxţ one] [Ţĥîš çöðé îš ñö ļöñĝéŕ våļîð one two three four five six] -[Šîĝñ îñ ŵîţĥ Fåçéбööķ one two three four five] +[Å šîĝñ-îñ émåîļ ŵîţĥ åððîţîöñåļ îñšţŕûçîţöñš ŵåš šéñţ ţö ᐅᐊ. Çĥéçķ ýöûŕ émåîļ ţö çömþļéţé šîĝñ-îñ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen] [Çöñƒîŕm émåîļ one two] [Fîŕšţ &amp; ļåšţ ñåmé one two three four] [Ĝŕéñåðå one] [Ŵéšţéŕñ Šåĥåŕå one two] +[Ŕéqûéšţ çåñ ñöţ бé éxéçûţéð îñ ţĥé çûŕŕéñţ šýšţém šţåţé. one two three four five six seven eight nine ten eleven] [Ţĥéŕé ŵåš å þŕöбļém véŕîƒýîñĝ ýöûŕ þĥöñé ñûmбéŕ. one two three four five six seven eight nine ten] [Ýöû’vé åļŕéåðý ûšéð ᐅᐊᐅᐊᐅᐊ ţö šîĝñ îñ. Éñţéŕ ýöûŕ þåššŵöŕð ƒöŕ ţĥåţ åççöûñţ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen] +[Šîĝñ-îñ émåîļ Šéñţ one two three] [Îŕéļåñð one] [Ðémöçŕåţîç Ŕéþûбļîç Çöñĝö one two three] [Ŕéçövéŕ þåššŵöŕð one two] [Ŕéþûбļîç öƒ Çöñĝö one two three] +[Ţĥé ŕéšöûŕçé ţĥåţ å çļîéñţ ţŕîéð ţö çŕéåţé åļŕéåðý éxîšţš. one two three four five six seven eight nine ten eleven twelve] [Çöñţîñûé åš ĝûéšţ one two three] [Šåñ Måŕîñö one two] [Çýþŕûš one] [Šöļömöñ Κļåñðš one two] [Çöñţîñûé ŵîţĥ ᐅᐊ¿ one two three four] +[Ţö çöñţîñûé šîĝñ îñ ŵîţĥ ᐅᐊᐅᐊᐅᐊ öñ ţĥîš ðévîçé, ýöû ĥåvé ţö ŕéçövéŕ ţĥé þåššŵöŕð. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] [Måļðîvéš one] [Šãö Ţömé åñð Þŕíñçîþé one two three four five] [Çöñƒîŕm ýöûŕ émåîļ ţö çömþļéţé šîĝñ îñ one two three four five six seven eight] [Šļövéñîå one] [Šöûţĥ Ķöŕéå one two] +[Çöñçûŕŕéñçý çöñƒļîçţ, šûçĥ åš ŕéåð-möðîƒý-ŵŕîţé çöñƒļîçţ. one two three four five six seven eight nine ten eleven twelve] +[ÅÞÎ méţĥöð ñöţ îmþļéméñţéð бý ţĥé šéŕvéŕ. one two three four five six seven eight nine] [Ļéšöţĥö one] [Šîĝñ îñ ŵîţĥ þĥöñé one two three four] [Ţŕý véŕîƒýîñĝ ýöûŕ émåîļ åĝåîñ one two three four five six seven] @@ -375,9 +407,11 @@ ţö šîĝñ îñ. Éñţéŕ ýöûŕ þåššŵöŕð ƒöŕ ţĥåţ åççöûñţ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen] {plural_var,plural, =1{[Þåššŵöŕð ñöţ šţŕöñĝ éñöûĝĥ. Ûšé åţ ļéåšţ ᐅᐊ çĥåŕåçţéŕ åñð å mîx öƒ ļéţţéŕš åñð ñûmбéŕš one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen]}other{[Þåššŵöŕð ñöţ šţŕöñĝ éñöûĝĥ. Ûšé åţ ļéåšţ ᐅᐊ çĥåŕåçţéŕš åñð å mîx öƒ ļéţţéŕš åñð ñûmбéŕš one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen]}} [Žîmбåбŵé one] -[Föļļöŵ ţĥé îñšţŕûçţîöñš šéñţ ţö ᐅᐊ ţö ŕéçövéŕ ýöûŕ þåššŵöŕð. one two three four five six seven eight nine ten eleven twelve] -[Ţĥéŕé ŵåš å þŕöбļém véŕîƒýîñĝ ýöûŕ þĥöñé ñûmбéŕ one two three four five six seven eight nine ten] +[Ŕéqûéšţ ñöţ åûţĥéñţîçåţéð ðûé ţö mîššîñĝ, îñvåļîð, öŕ éxþîŕéð ÖÅûţĥ ţöķéñ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen] +[Þļéåšé þŕövîðé å 6-ðîĝîţ þĥöñé véŕîƒîçåţîöñ çöðé. one two three four five six seven eight nine ten] +[Çĥööšé þåššŵöŕð one two] [Ñéŵ ðévîçé öŕ бŕöŵšéŕ ðéţéçţéð one two three four five six seven] +[Šîĝñ îñ ţö ᐅᐊ one two three] [Çžéçĥ Ŕéþûбļîç one two] [Ţûvåļû one] [Šîĝñ îñ ŵîţĥ ĜîţĤûб one two three four] @@ -403,6 +437,10 @@ [Šþåîñ one] [Çöšţå Ŕîçå one two] [Šåmöå one] +[ +ᐅᐊÝöûŕ åççöûñţ îñ ᐅᐊ ĥåš бééñ ûþðåţéð ŵîţĥ ᐅᐊ ƒöŕ ţŵö-ƒåçţöŕ åûţĥéñţîçåţîöñ.ᐅᐊ +ᐅᐊ΃ ýöû ðîð ñöţ ŕéqûéšţ ţĥîš möðîƒîçåţîöñ, þļéåšé ûšé ţĥîš ţĥé ƒöļļöŵîñĝ ļîñķ ţö ûñðö ţĥé çĥåñĝé.ᐅᐊ +ᐅᐊᐅᐊᐅᐊᐅᐊᐅᐊ one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree twentyfour twentyfive twentysix twentyseven] [Måļî one] [Qåţåŕ one] [Ûŕûĝûåý one] @@ -433,6 +471,7 @@ [Бéŕmûðå one] [Ñîûé one] [Ĝûåðéļöûþé one two] +[Çļîéñţ šþéçîƒîéð åñ îñvåļîð þŕöĵéçţ çöñƒîĝûŕåţîöñ. one two three four five six seven eight nine ten eleven] [Þåļéšţîñîåñ Ţéŕŕîţöŕîéš one two three] [Šļövåķîå one] [Ĝûéŕñšéý one] @@ -445,19 +484,24 @@ [Måļåýšîå one] [Çöçöš [Ķééļîñĝ] Κļåñðš one two three] [΃ ýöû šţîļļ ŵåñţ ţö çöññéçţ ýöûŕ ᐅᐊᐅᐊᐅᐊ åççöûñţ, öþéñ ţĥé ļîñķ öñ ţĥé šåmé ðévîçé ŵĥéŕé ýöû šţåŕţéð šîĝñ-îñ. Öţĥéŕŵîšé, ţåþ Çöñţîñûé ţö šîĝñ-îñ öñ ţĥîš ðévîçé. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone twentytwo twentythree] +[Ŕéţŕý one] [Ýöû öŕîĝîñåļļý îñţéñðéð ţö çöññéçţ ᐅᐊᐅᐊᐅᐊ ţö ýöûŕ émåîļ åççöûñţ бûţ ĥåvé öþéñéð ţĥé ļîñķ öñ å ðéŕéñţ ðévîçé ŵĥéŕé ýöû åŕé ñöţ šîĝñéð îñ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone] [Ŕéšéñð çöðé îñ 0:ᐅᐊ one two three four five] [Möŕöççö one] +[΃ ýöû ðöñ'ţ ŕéçöĝñîžé ţĥîš ðévîçé, šöméöñé mîĝĥţ бé ţŕýîñĝ ţö åççéšš ýöûŕ åççöûñţ. Çöñšîðéŕ ᐅᐊçĥåñĝîñĝ ýöûŕ þåššŵöŕð ŕîĝĥţ åŵåýᐅᐊ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twentyone] [Бåñĝļåðéšĥ one two] [Þĥöñé one] [Fîñļåñð one] [Ûñîţéð Åŕåб Émîŕåţéš one two three] +[Ýöûŕ émåîļ ĥåš бééñ véŕîƒîéð åñð çĥåñĝéð one two three four five six seven eight] [Þåššŵöŕð one] [Çéñţŕåļ Ńŕîçåñ Ŕéþûбļîç one two three] [Çûŕåçåö one] +[Ŵé ĥåvé бļöçķéð åļļ ŕéqûéšţš ƒŕöm ţĥîš ðévîçé ðûé ţö ûñûšûåļ åçţîvîţý. Ţŕý åĝåîñ ļåţéŕ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] [Ţûñîšîå one] [Þåššŵöŕð çĥåñĝéð one two] [Ĝŕééçé one] +[Ŕémövéð šéçöñð ƒåçţöŕ one two three] [Åñĝöļå one] [Ķûŵåîţ one] [Ţĥé émåîļ åñð þåššŵöŕð ýöû éñţéŕéð ðöñ'ţ måţçĥ one two three four five six seven eight nine ten] diff --git a/translations/es-419.xtb b/translations/es-419.xtb index 09589b59..3e1282bc 100644 --- a/translations/es-419.xtb +++ b/translations/es-419.xtb @@ -12,11 +12,14 @@ Filipinas Rumania Islas Georgias del Sur y Sandwich del Sur -Aceptar +Daño o pérdida de datos no recuperable. Italia Líbano Guyana Guatemala +Hubo un problema con tu segundo factor.Intenta quitarlo de nuevo. Si no funciona, comunícate con el equipo de asistencia para obtener ayuda. +Intenta actualizar tu correo electrónico de nuevo +Se produjo un error en la red. Vuelve a intentarlo más tarde. Ingresa un número de teléfono válido Si presionas Verificar, indicas que aceptas nuestras Condiciones del Servicio y la Política de Privacidad. Es posible que recibas un SMS. Podrían aplicarse tarifas de mensajes y datos. Ingresa la contraseña @@ -27,6 +30,7 @@ Recibimos demasiadas solicitudes de cuenta desde tu dirección IP. Vuelve a intentarlo en unos minutos. Sri Lanka Ahora puedes acceder con tu cuenta nueva +El cliente especificó un argumento no válido. Chad Volver a enviar el código en Bielorrusia @@ -62,6 +66,7 @@ Github Política de Privacidad Guam +Sin cuota de recursos o a punto de alcanzar el límite de frecuencia. Croacia Lituania Malta @@ -71,22 +76,28 @@ Invitado Ghana El navegador que estás usando no es compatible con el almacenamiento web. Vuelve a intentarlo en otro navegador. +El cliente canceló la solicitud. Ocurrió un problema durante el cambio de tu correo electrónico de acceso.Si vuelves a intentarlo y aún no puedes restablecer tu correo electrónico, pídele ayuda al administrador. Baréin +Error interno del servidor. Irán +GitHub Estamos verificando que no eres un robot… Este código ya no es válido Nueva Caledonia Mónaco Seychelles +Se quitó como segundo paso de autenticación. No se pudo actualizar el usuario anónimo actual. La credencial no anónima ya está asociada con otra cuenta de usuario. Ingresaste una contraseña incorrecta demasiadas veces. Vuelve a intentarlo en unos minutos. Seguridad La solicitud para restablecer tu contraseña caducó o ya se usó el vínculo +Ahora puedes acceder con tu correo electrónico nuevo No se admite el código de país que ingresaste. Verifica que escribiste bien tu correo electrónico. Twitter Camboya +Servicio no disponible Portugal Guayana Francesa Islas Vírgenes Británicas @@ -102,6 +113,7 @@ Verificar Sudáfrica Ingresa tu dirección de correo electrónico para continuar +Un administrador inhabilitó la cuenta de usuario. Se envió un correo electrónico de acceso con instrucciones adicionales a . Revisa tu correo electrónico para completar el acceso. Ingresa la contraseña Crear cuenta @@ -109,13 +121,14 @@ Si presionas , indicas que aceptas las . Canadá Dinamarca +Se excedió el plazo de la solicitud. Austria Los correos electrónicos no coinciden Sudán del Sur Yemen Guinea-Bisáu Arabia Saudita -Logotipo de la app +Se agregó un factor de seguridad adicional a Islas Vírgenes de Estados Unidos Acceder con el teléfono Indonesia @@ -123,6 +136,7 @@ Venezuela ¿Tienes problemas para acceder? Verifica tu identidad +Hubo un problema al autenticar tu solicitud. Vuelve a la URL que te redireccionó a esta página para reiniciar el proceso de autenticación. Dominica Nueva contraseña Gabón @@ -140,6 +154,7 @@ Gambia Listo Cancelar +Error desconocido del servidor. Ingresa el nombre de la cuenta Irak Vietnam @@ -151,6 +166,8 @@ Nepal Tokelau No se admite la credencial del proveedor de autenticación seleccionado. +No hay un proveedor de acceso disponible para el correo electrónico proporcionado. Inténtalo con una dirección diferente. +Ingresa un nombre y un apellido. Colombia Si presionas GUARDAR, indicas que aceptas las Burundi @@ -158,7 +175,7 @@ Twitter Se verificó tu correo electrónico Papúa Nueva Guinea -Guardar +No se encontró el recurso especificado. Verificando… Acceder Timor Oriental @@ -191,6 +208,7 @@ Teléfono Reenviar Honduras +Se agotó el tiempo de espera de la operación. Níger La dirección de correo electrónico no coincide con una cuenta existente. Jordania @@ -202,6 +220,7 @@ Israel Se detectó un error Número +El cliente no cuenta con los permisos necesarios. Completa el reCAPTCHA Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Islas Turcas y Caicos Reenviar el código en Revisa tu correo electrónico +No se pudo quitar tu segundo factor Agregar contraseña El Salvador +Para cambiar la contraseña de tu cuenta, debes acceder de nuevo. Serbia Belice de Para que este flujo conecte correctamente tu cuenta con este correo electrónico, debes abrir el vínculo en el mismo dispositivo o navegador. Suecia San Martín +Venció tu solicitud de verificación y actualización de correo electrónico o ya se usó el vínculo. Santa Lucía @string/app_name Revisa tu correo electrónico @@ -225,13 +247,15 @@ Omán Caribe Neerlandés Dirección de correo electrónico actualizada -Acceder con Twitter +Saliste correctamente. +Acceder con el teléfono Número de teléfono Ya tienes una cuenta Tailandia Código incorrecto. Vuelve a intentarlo. Liechtenstein Editar nombre +Se quitó el dispositivo o la app como segundo paso de autenticación. Intenta abrir el vínculo con el mismo dispositivo o navegador en el que comenzaste el proceso de acceso. Países Bajos Guinea Ecuatorial @@ -255,7 +279,7 @@ Ingresa tu dirección de correo electrónico para continuar Verifica tu número de teléfono Liberia -Ya existe una cuenta con esa dirección de correo electrónico. +Salir Madagascar Kazajistán Accediendo… @@ -274,15 +298,20 @@ Ingresa el código de  dígitos que enviamos a Este número de teléfono se usó demasiadas veces El correo electrónico que proporcionaste no coincide con la sesión actual a la que accediste. -Desvincular cuenta +Twitter + +Accede a </p></p> + Verifica que el espacio de tu bandeja de entrada no se esté agotando o revisa cualquier otro problema relacionado con la configuración de la bandeja de entrada. +El cliente especificó un rango no válido. +Se excedió la cuota para esta operación. Vuelve a intentarlo más tarde. Paraguay Argelia Ingresaste una contraseña incorrecta demasiadas veces. Vuelve a intentarlo en unos minutos. Isla Norfolk Se verificó automáticamente el número de teléfono -Para cambiar tu contraseña, primero debes ingresar la contraseña actual. -Atrás +Se produjo un error de red. Comprueba tu conexión a Internet. +¡Verificado! India Benín Continuar @@ -331,27 +360,33 @@ Correo electrónico Siguiente Este código ya no es válido -Acceder con Facebook +Se envió un correo electrónico de acceso con instrucciones adicionales a . Revisa tu bandeja de entrada para completar el proceso. Confirma el correo electrónico Nombre y apellido Granada Sáhara Occidental +La solicitud no se puede ejecutar en el estado actual del sistema. Se produjo un error desconocido. Ya usaste para acceder. Ingresa la contraseña correspondiente. +Se envió el correo electrónico de acceso Irlanda República Democrática del Congo Recuperar contraseña República del Congo +El recurso que el cliente intentó crear ya existe. Continuar como invitado San Marino Chipre Islas Salomón ¿Quieres continuar con ? +Para poder acceder con en este dispositivo, debes recuperar la contraseña. Maldivas Santo Tomé y Príncipe Confirma tu correo electrónico para completar el acceso Eslovenia Corea del Sur +Conflicto de concurrencia, como conflicto de lectura-modificación-escritura. +El servidor no implementó el método de API. Lesoto Acceder con el teléfono Intenta verificar tu correo electrónico de nuevo @@ -370,9 +405,11 @@ Ya usaste para acceder. Ingresa la contraseña correspondiente. {plural_var,plural, =1{La contraseña no es lo suficientemente segura. Usa al menos carácter y una combinación de letras y números}other{La contraseña no es lo suficientemente segura. Usa al menos caracteres y una combinación de letras y números}} Zimbabue -Sigue las instrucciones que se enviaron a para restablecer la contraseña. -Ocurrió un problema durante la verificación de tu número de teléfono +Solicitud no autenticada debido a que token de OAuth no es válido, falta o venció. +Proporciona un código de verificación por teléfono de 6 dígitos. +Elegir contraseña Se detectó un dispositivo o navegador nuevo +Acceder a República Checa Tuvalu Acceder con GitHub @@ -398,6 +435,9 @@ España Costa Rica Samoa + +Se agregó a tu cuenta de para poder usar la autenticación de dos factores. +Si no solicitaste este cambio, usa el siguiente vínculo para deshacerlo. Malí Catar Uruguay @@ -428,6 +468,7 @@ Bermudas Niue Guadalupe +El cliente especificó una configuración de proyecto no válida. Territorios palestinos Eslovaquia Guernsey @@ -440,19 +481,24 @@ Malasia Islas Cocos Si aún quieres conectar tu cuenta de , abre el vínculo en el mismo dispositivo en el que accediste. En caso contrario, presiona Continuar para acceder en este dispositivo. +Reintentar Originalmente, intentaste conectar con tu cuenta de correo electrónico, pero abriste el vínculo en un dispositivo diferente en el que no accediste. Se reenviará el código en 0: Marruecos +Si no reconoces este dispositivo, es probable que otra persona esté tratando de acceder a tu cuenta. Cambia tu contraseña de inmediato. Bangladés Teléfono Finlandia Emiratos Árabes Unidos +Se verificó y cambió tu correo electrónico Contraseña República Centroafricana Curazao +Se bloquearon todas las solicitudes de este dispositivo debido a que presenta una actividad inusual. Vuelve a intentarlo más tarde. Túnez Se cambió la contraseña Grecia +Se quitó el segundo factor Angola Kuwait El correo electrónico y la contraseña que ingresaste no coinciden diff --git a/translations/es.xtb b/translations/es.xtb index 05fbbcd6..d056f424 100644 --- a/translations/es.xtb +++ b/translations/es.xtb @@ -12,11 +12,14 @@ Filipinas Rumanía Islas Georgia del Sur y Sandwich del Sur -Aceptar +Se han perdido o dañado los datos de forma irrecuperable. Italia Líbano Guyana Guatemala +Se ha producido un error al eliminar el segundo factor.Prueba a eliminarlo de nuevo. Si no funciona, ponte en contacto con el equipo de asistencia. +Prueba a actualizar tu correo electrónico de nuevo +Se ha producido un error de red. Inténtalo de nuevo más tarde. Introduce un número de teléfono válido Si tocas Verificar, confirmas que aceptas nuestras condiciones del servicio y nuestra política de privacidad. Podría enviarse un SMS, sujeto a las tarifas de mensajes y uso de datos de tu operador. Introduce tu contraseña. @@ -27,6 +30,7 @@ Estamos recibiendo demasiadas peticiones desde tu dirección IP. Vuelve a intentarlo en unos minutos. Sri Lanka Ya puedes iniciar sesión con la cuenta nueva +El cliente especificó un argumento que no es válido. Chad Reenviar código en Bielorrusia @@ -62,6 +66,7 @@ Github Política de privacidad Guam +Se ha agotado la cuota del recurso o se ha alcanzado el límite de frecuencia. Croacia Lituania Malta @@ -71,22 +76,28 @@ Invitado Ghana El navegador que estás usando no es compatible con el almacenamiento web. Vuelve a intentarlo con otro navegador. +El cliente ha cancelado la solicitud. No se ha podido cambiar el correo electrónico de inicio de sesión al correo anterior.Si lo intentas de nuevo sin éxito, pídele ayuda a tu administrador. Baréin +Se ha producido un error de servidor interno. Irán +GitHub Estamos comprobando que no eres un robot... Este código ya no es válido Nueva Caledonia Mónaco Seychelles +Se ha eliminado como segundo paso de autenticación. El usuario anónimo actual no ha podido realizar la actualización. Las credenciales no anónimas ya están asociadas a una cuenta de usuario diferente. Has introducido una contraseña incorrecta demasiadas veces. Vuelve a intentarlo en unos minutos. Seguridad La petición para cambiar la contraseña ha caducado o ya se ha usado el enlace +Ya puedes iniciar sesión con tu nuevo correo electrónico . No se admite este código de país. Asegúrate de que has escrito bien tu dirección de correo electrónico. Twitter Camboya +El servicio no está disponible. Portugal Guayana Francesa Islas Vírgenes Británicas @@ -102,6 +113,7 @@ Verificar Sudáfrica Para continuar, introduce tu dirección de correo electrónico +Un administrador ha inhabilitado la cuenta de usuario. Se ha enviado un correo electrónico de inicio de sesión con más instrucciones a . Consulta tu bandeja de entrada para completar el inicio de sesión. Introduce tu contraseña Crear cuenta @@ -109,13 +121,14 @@ Al tocar , indicas que aceptas las . Canadá Dinamarca +Se ha superado el plazo límite de la solicitud. Austria Los correos electrónicos no coinciden Sudán del Sur Yemen Guinea-Bisáu Arabia Saudí -Logotipo de la aplicación +Se ha añadido un factor de seguridad adicional en Islas Vírgenes de Estados Unidos Iniciar sesión con el teléfono Indonesia @@ -123,6 +136,7 @@ Venezuela ¿No puedes iniciar sesión? Verifica que eres tú +Se ha producido un error al autenticar tu solicitud. Vuelve a visitar la URL que te redirigió a esta página para reiniciar el proceso de autenticación. Dominica Nueva contraseña Gabón @@ -140,6 +154,7 @@ Gambia Listo Cancelar +Se ha producido un error de servidor desconocido. Introduce el nombre de tu cuenta Irak Vietnam @@ -151,6 +166,8 @@ Nepal Tokelau No se admite la credencial seleccionada para el proveedor de autenticación. +No hay ningún proveedor de inicio de sesión disponible para el correo electrónico que has especificado. Inténtalo de nuevo con un correo diferente. +Introduce tu nombre y apellidos. Colombia Al tocar Guardar, indicas que aceptas las Burundi @@ -158,7 +175,7 @@ Twitter Se ha verificado tu correo electrónico Papúa Nueva Guinea -Guardar +No se ha encontrado el recurso especificado. Verificando... Iniciar sesión Timor Oriental @@ -191,6 +208,7 @@ Teléfono Reenviar Honduras +Se ha agotado el tiempo de espera de la operación. Níger La dirección de correo electrónico no coincide con ninguna cuenta Jordania @@ -202,6 +220,7 @@ Israel Se ha producido un error Número +El cliente no tiene suficientes permisos. Introduce los caracteres del reCAPTCHA. Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Islas Turcas y Caicos Reenviar código en Consulta tu correo electrónico +No se ha podido eliminar el segundo factor Añadir contraseña El Salvador +Para cambiar la contraseña de tu cuenta, debes iniciar sesión de nuevo. Serbia Belice de Para que este flujo conecte correctamente tu cuenta de con este correo electrónico, debes abrir el enlace en el mismo dispositivo o navegador. Suecia San Martín (Francia) +Tu solicitud para verificar y actualizar tu correo electrónico ha caducado o ya se ha usado el enlace. Santa Lucía @string/app_name Consulta tu correo electrónico @@ -225,13 +247,15 @@ Omán Caribe Neerlandés Se ha actualizado la dirección de correo electrónico -Iniciar sesión con Twitter +Has cerrado sesión correctamente. +Iniciar sesión con el teléfono Número de teléfono Ya tienes una cuenta Tailandia El código es incorrecto. Vuelve a intentarlo. Liechtenstein Editar nombre +Se ha eliminado el dispositivo o aplicación como segundo paso de autenticación. Prueba a abrir el enlace con el mismo dispositivo o navegador en los que empezaste el proceso de inicio de sesión. Países Bajos Guinea Ecuatorial @@ -255,7 +279,7 @@ Para continuar, introduce tu dirección de correo electrónico Verificar el número de teléfono Liberia -Ya existe una cuenta con esa dirección de correo electrónico. +Cerrar sesión Madagascar Kazajistán Iniciando sesión... @@ -274,15 +298,20 @@ Introduce el código de dígitos que te hemos enviado a Este número de teléfono se ha usado demasiadas veces El correo electrónico proporcionado no coincide con la sesión iniciada actual. -Desvincular la cuenta +Twitter + +Inicia sesión en </p></p> + Comprueba que no te estés quedando sin espacio en la bandeja de entrada y que no haya ningún problema con su configuración. +El cliente especificó un intervalo que no es válido. +Se ha superado la cuota de esta aplicación. Inténtalo de nuevo más tarde. Paraguay Argelia Has introducido una contraseña incorrecta demasiadas veces. Vuelve a intentarlo en unos minutos. Isla Norfolk Se ha verificado automáticamente el número de teléfono -Para poder cambiar la contraseña, primero debes introducir la contraseña actual. -Atrás +Se ha producido un error de red. Comprueba la conexión a Internet. +Verificado India Benín Continuar @@ -331,27 +360,33 @@ Correo electrónico Siguiente Este código ya no es válido -Iniciar sesión con Facebook +Se ha enviado un correo electrónico de inicio de sesión con más instrucciones a . Consulta tu bandeja de entrada para completar el inicio de sesión. Confirma tu correo electrónico Nombre y apellidos Granada Sáhara Occidental +No se puede realizar la solicitud debido al estado actual del sistema. Se ha producido un error desconocido. Ya has usado para iniciar sesión. Introduce la contraseña de la cuenta. +Correo electrónico de inicio de sesión enviado Irlanda República Democrática del Congo Recuperar contraseña República del Congo +El cliente ha intentado crear un recurso que ya existe. Continuar como invitado San Marino Chipre Islas Salomón ¿Quieres continuar con ? +Para iniciar sesión con en este dispositivo, debes recuperar tu contraseña. Maldivas Santo Tomé y Príncipe Confirma tu correo electrónico para completar el inicio de sesión Eslovenia Corea del Sur +Se ha producido un conflicto de simultaneidad (por ejemplo, relacionado con acciones de lectura, modificación y escritura). +El servidor no ha implementado el método de API. Lesoto Iniciar sesión con el teléfono Prueba a verificar el correo electrónico de nuevo @@ -371,9 +406,11 @@ para iniciar sesión. Introduce la contraseña de esa cuenta. {plural_var,plural, =1{La contraseña no es lo suficientemente segura. Usa carácter como mínimo y combina letras y números.}other{La contraseña no es lo suficientemente segura. Usa caracteres como mínimo y combina letras y números.}} Zimbabue -Sigue las instrucciones que hemos enviado a para recuperar la contraseña. -Se ha producido un problema al verificar el número de teléfono +La solicitud no se ha autenticado porque el token de OAuth ha caducado, no es válido o no se ha encontrado. +Indica un código de verificación telefónico de seis dígitos. +Elige una contraseña Se ha detectado un nuevo dispositivo o navegador +Iniciar sesión en República Checa Tuvalu Iniciar sesión con GitHub @@ -399,6 +436,10 @@ España Costa Rica Samoa + +Se ha añadido a tu cuenta de para la autenticación de dos factores. +Si no has solicitado esta modificación, accede al siguiente enlace para deshacer el cambio. + Mali Catar Uruguay @@ -429,6 +470,7 @@ Bermudas Isla Niue Guadalupe +El cliente ha especificado una configuración de proyecto no válida. Territorios Palestinos Eslovaquia Guernsey @@ -441,19 +483,24 @@ Malasia Islas Cocos Si sigues queriendo conectar tu cuenta de , abre el enlace en el mismo dispositivo en el que empezaste el inicio de sesión. De lo contrario, toca Continuar para iniciar sesión en este dispositivo. +Reintentar En un principio intentaste conectar con tu cuenta de correo electrónico, pero has abierto el enlace en un dispositivo diferente en el que no has iniciado sesión. Volver a enviar el código en 0: Marruecos +Si no reconoces este dispositivo, es posible que otra persona esté intentando acceder a tu cuenta. Te recomendamos que cambies tu contraseña de inmediato. Bangladés Teléfono Finlandia Emiratos Árabes Unidos +Se ha verificado y modificado tu correo electrónico Contraseña República Centroafricana Curasao +Hemos bloqueado todas las solicitudes de este dispositivo porque se ha detectado una actividad inusual. Inténtalo de nuevo más tarde. Túnez Se ha cambiado la contraseña Grecia +Se ha eliminado el segundo factor Angola Kuwait La dirección de correo electrónico y contraseña que has introducido no coinciden. diff --git a/translations/fa.xtb b/translations/fa.xtb index 7eded120..2ee8bd7b 100644 --- a/translations/fa.xtb +++ b/translations/fa.xtb @@ -12,11 +12,14 @@ فیلیپین رومانی جزایر جورجیای جنوبی و ساندویچ جنوبی -تأیید +از بین‌رفتن داده‌ها یا خرابی داده‌ها به‌طور غیرقابل بازیابی. ایتالیا لبنان گویان گواتمالا +هنگام برداشتن عامل دوم، مشکلی پیش آمد.آن را دوباره بردارید. اگر مشکل حل نشد، برای دریافت راهنمایی با پشتیبانی تماس بگیرید. +ایمیلتان را دوباره به‌روزرسانی کنید +خطای شبکه رخ داد. بعداً دوباره امتحان کنید. شماره تلفن معتبری وارد کنید درصورت ضربه‌زدن روی «تأیید»، موافقتتان را با شرایط خدمات و خطمشی حریم خصوصی اعلام می‌کنید. پیامکی ارسال خواهد شد و ممکن است هزینه داده و «پیام» محاسبه شود. گذرواژه‌تان را وارد کنید @@ -27,6 +30,7 @@ تعداد زیادی درخواست حساب از نشانی IP شما درحال ارسال است. پس از چند دقیقه دوباره امتحان کنید. سریلانکا اکنون می‌توانید با حساب جدیدتان وارد سیستم شوید +متغیر مستقلی که کارخواه تعیین کرده است نامعتبر است. چاد کد پس از مجدداً ارسال می‌شود بلاروس @@ -62,6 +66,7 @@ Github خط‌مشی حریم خصوصی گوام +یا از سهمیه منبع خارج شدید یا از حداکثر مجاز فراتر رفتید. کرواسی لیتوانی مالت @@ -71,22 +76,28 @@ مهمان غنا مرورگر مورد استفاده‌تان از فضای ذخیره‌سازی وب پشتیبانی نمی‌کند. لطفاً با یک مرورگر دیگر امتحان کنید. +کارخواه درخواست را لغو کرد. مشکلی در برگرداندن ایمیل ورود به سیستم‌تان پیش آمد.اگر دوباره امتحان کردید و باز هم نتوانستید ایمیل ‌تان را بازنشانی کنید، از سرپرست سیستم بخواهید شما را راهنمایی کند. بحرین +خطای سرور داخلی. ایران +GitHub درحال تأیید اینکه شما ربات نیستید… این کد دیگر معتبر نیست کالدونیای جدید موناکو سیشل + به‌عنوان مرحله دوم احراز هویت برداشته شد. کاربر ناشناس فعلی ارتقا داده نشد. اطلاعات کاربری شناخته‌شده قبلاً با حساب کاربری دیگری مرتبط شده است. دفعات زیادی گذرواژه اشتباه وارد کرده‌اید. پس از چند دقیقه دوباره امتحان کنید. امنیت درخواست بازنشانی گذرواژه‌تان منقضی شده یا قبلاً از پیوند استفاده شده است +اکنون می‌توانید با ایمیل جدیدتان وارد سیستم شوید. کد کشور ارائه‌شده پشتیبانی نمی‌شود. مطمئن شوید املای ایمیلتان را درست وارد کرده باشید. Twitter کامبوج +سرویس دردسترس نیست. پرتغال گویان فرانسه جزایر ویرجین بریتانیا @@ -101,20 +112,22 @@ جلسه ورود به سیستم‌تان منقضی شد. لطفاً دوباره امتحان کنید. تأیید آفریقای جنوبی -برای ادامه، آدرس رایانامه‌تان را وارد کنید +برای ادامه، آدرس ایمیلتان را وارد کنید +سرپرست حساب کاربری را غیرفعال کرده است. ایمیل ورود به سیستم، حاوی دستورالعمل‌های بیشتر، به ارسال شد. برای تکمیل ورود به سیستم، ایمیلتان را بررسی کنید. ایجاد حساب افغانستان با ضربه زدن روی موافقتتان را با اعلام می‌کنید. کانادا دانمارک +از مهلت درخواست فراتر رفتید. اتریش ایمیل‌ها با هم مطابقت ندارند سودان جنوبی یمن گینه بیسائو عربستان سعودی -نشان‌واره برنامه +عامل امنیتی دیگری در اضافه شد جزایر ویرجین ایالات متحده ورود به سیستم با تلفن اندونزی @@ -122,6 +135,7 @@ ونزوئلا مشکل در ورود به سیستم؟ تأیید کنید این خود شما هستید +هنگام راستی‌آزمایی درخواستتان خطایی رخ داد. لطفاً برای بازراه‌اندازی فرایند راستی‌آزمایی، دوباره به نشانی وبی که شما را به این صفحه هدایت کرد بروید. دومینیکا گذرواژه جدید گابن @@ -139,6 +153,7 @@ گامبیا انجام شد لغو +خطای سرور نامشخص. نام حسابتان را وارد کنید عراق ویتنام @@ -150,6 +165,8 @@ نپال توکلائو اطلاعات کاربری انتخاب‌شده برای ارائه‌دهنده احراز هویت، پشتیبانی نمی‌شود! +ایمیل موردنظر، ارائه‌دهنده ورود به سیستم ندارد. لطفاً ایمیل دیگری را امتحان کنید. +لطفاً نام و نام خانوادگی را وارد کنید. کلمبیا با ضربه زدن روی «ذخیره»، موافقتتان را با شرایط زیر اعلام می‌کنید بوروندی @@ -157,7 +174,7 @@ Twitter رایانامه‌تان تأیید شد پاپوآ گینه نو -ذخیره +منبع مشخص‌شده پیدا نشد. درحال تأیید... ورود به سیستم تیمور شرقی @@ -190,6 +207,7 @@ تلفن ارسال مجدد هندوراس +مهلت عملیات تمام شد. نیجر هیچ حسابی منطبق با این نشانی ایمیل وجود ندارد اردن @@ -201,6 +219,7 @@ اسرائیل خطا رخ داد عدد +کارخواه مجوز کافی ندارد. reCAPTCHA را رفع کنید نیکاراگوئه بورکینا فاسو @@ -209,14 +228,17 @@ جزایر تورکس و کایکوس ارسال مجدد کد پس از رایانامه‌تان را بررسی کنید +عامل دوم برداشته نشد افزودن گذرواژه السالوادور +برای تغییر گذرواژه حسابتان باید دوباره وارد سیستم شوید. صربستان بلیز مربوط به برای اینکه این فرایند بتواند با موفقیت حساب را به این ایمیل متصل کند، باید پیوند را در همان دستگاه یا مرورگر باز کنید. سوئد سن مارتن +درخواست راستی‌آزمایی و به‌روزرسانی ایمیلتان منقضی شده یا قبلاً از پیوند استفاده شده است. سنت لوسیا string/app_name@ رایانامه‌تان را بررسی کنید @@ -224,13 +246,15 @@ عمان جزایر کارائیب هلند نشانی ایمیل به‌روز است -ورود به سیستم با Twitter +از سیستم خارج شدید. +ورود به سیستم با تلفن شماره تلفن از قبل یک حساب دارید تایلند خط‌مشی رازداری لیختن‌اشتاین ویرایش نام +دستگاه یا برنامه به‌عنوان مرحله دوم احراز هویت برداشته شد. با همان دستگاه یا مرورگری که فرایند ورود به سیستم را آغاز کردید، پیوند را باز کنید. هلند گینه استوایی @@ -251,10 +275,10 @@ گذرواژه سنت کيتس برگشت -برای ادامه، آدرس رایانامه‌تان را وارد کنید +برای ادامه، آدرس ایمیلتان را وارد کنید تأیید شماره تلفن لیبریا -حسابی با این نشانی ایمیل از قبل وجود دارد. +خروج از سیستم ماداگاسکار قزاقستان درحال ورود به سیستم… @@ -273,15 +297,20 @@ کد رقمی‌ای را که به این شماره ارسال کردیم وارد کنید از این شماره تلفن به دفعات زیاد استفاده شده است ایمیل ارائه‌شده با جلسه ورود به سیستم فعلی مطابقت ندارد. -لغو پیوند حساب +Twitter + +ورود به </p></p> + مطمئن شوید فضای صندوق ورودی‌تان روبه‌اتمام نباشد یا مشکل دیگری در تنظیمات صندوق ورودی وجود نداشته باشد. +گستره‌ای که کارخواه تعیین کرده است نامعتبر است. +از سهمیه این عملیات فراتر رفتید. بعداً دوباره امتحان کنید. پاراگوئه الجزایر دفعات خیلی زیادی گذرواژه را نادرست وارد کرده‌اید. لطفاً پس از چند دقیقه دوباره امتحان کنید. جزیره نورفک شماره تلفن به‌طور خودکار به‌تأیید رسید -برای تغییر گذرواژه‌‌، ابتدا باید گذرواژه کنونی‌تان را وارد کنید. -قبلی +خطای شبکه. اتصال اینترنت را بررسی کنید. +تأیید شد! هند بنین ادامه @@ -330,27 +359,33 @@ ایمیل بعدی این کد دیگر معتبر نیست -ورود به سیستم با Facebook +ایمیل ورود به سیستم، حاوی دستورالعمل‌های بیشتر، به ارسال شد. برای تکمیل ورود به سیستم، ایمیلتان را بررسی کنید. تأیید ایمیل نام و نام خانوادگی گرنادا صحرای غربی +در حالت کنونی سیستم، امکان اجرای درخواست وجود ندارد. خطای ناشناس رخ داد. قبلاً برای ورود به سیستم از استفاده کرده‌اید. لطفاً گذرواژه این حساب را وارد کنید. +ایمیل ورود به سیستم ارسال شد ایرلند جمهوری دموکراتیک کنگو بازیابی گذرواژه جمهوری کنگو +منبعی که کارخواه قصد ایجادش را داشت از قبل موجود است. ادامه به‌عنوان مهمان سن مارینو قبرس جزایر سلیمان با ادامه می‌دهید؟ +برای ادامه ورود به سیستم با در این دستگاه، باید گذرواژه را بازیابی کنید. مالدیو سائوتومه و پرینسیپ برای تکمیل ورود به سیستم، ایمیلتان را تأیید کنید اسلوونی کره جنوبی +ناسازگاری هم‌روندی، مانند ناسازگاری خواندن-تغییر-نوشتن. +سرور روش «رابط برنامه‌نویسی نرم‌افزار» را پیاده‌سازی نکرده است. لسوتو ورود به سیستم با تلفن دوباره رایانامه‌تان را تأیید کنید @@ -370,8 +405,10 @@ استفاده کرده‌اید. گذرواژه این حساب را وارد کنید. {plural_var,plural, =1{گذرواژه به اندازه کافی قوی نیست. حداقل باید از نویسه و ترکیبی از حروف و اعداد استفاده شود}one{گذرواژه به اندازه کافی قوی نیست. حداقل باید از نویسه و ترکیبی از حروف و اعداد استفاده شود}other{گذرواژه به اندازه کافی قوی نیست. حداقل باید از نویسه و ترکیبی از حروف و اعداد استفاده شود}} زیمبابوه +درخواست به‌دلیل نبود کد OAuth یا منقضی یا نامعتبر بودن آن احراز هویت نشد. مشکلی در تأیید شماره تلفنتان پیش آمد دستگاه یا مرورگر جدید شناسایی شد +ورود به سیستم جمهوری چک تووالو ورود به سیستم با GitHub @@ -426,6 +463,7 @@ برمودا نیووی گوادلوپ +پیکربندی پروژه که مشتری مشخص کرده نامعتبر است. سرزمین‌های فلسطینی اسلواکی گرنزی @@ -438,19 +476,24 @@ مالزی جزایر کوکوس (کیلینگ) اگر همچنان می‌خواهید حساب را متصل کنید، پیوند را در همان دستگاهی باز کنید که ابتدا با آن وارد سیستم شده‌اید. درغیراین‌صورت، روی «ادامه» ضربه بزنید تا در این دستگاه وارد سیستم شوید. +امتحان مجدد دراصل قصد داشتید حساب را به حساب ایمیلتان متصل کنید، اما پیوند را در دستگاه دیگری باز کرده‌اید که در آن وارد سیستم نشده‌اید. کد پس از :0 مجدداً ارسال می‌شود مراکش +اگر این دستگاه را نمی شناسید، ممکن است کسی سعی داشته باشد به حسابتان دسترسی پیدا کند. گذرواژه‌تان را فوراً تغییر دهید. بنگلادش تلفن فنلاند امارات متحده عربی +ایمیلتان به‌تأیید رسید و تغییر کرد گذرواژه جمهوری آفریقایی مرکزی کوراسائو +به‌دلیل فعالیت‌های غیرعادی، همه درخواست‌ها ازطرف این دستگاه را مسدود کرده‌ایم. بعداً دوباره امتحان کنید. تونس گذرواژه عوض شد یونان +عامل دوم برداشته شد آنگولا کویت ایمیل و گذرواژه وارد شده مطابقت ندارند diff --git a/translations/fi.xtb b/translations/fi.xtb index 4b619da2..786307d0 100644 --- a/translations/fi.xtb +++ b/translations/fi.xtb @@ -12,11 +12,14 @@ Filippiinit Romania Etelä-Georgia ja Eteläiset Sandwichsaaret -Ok +Dataa on menetetty tai vahingoittunut peruuttamattomasti. Italia Libanon Guyana Guatemala +Toisen todennustason poistamisessa tapahtui virhe.Yritä poistaa se uudelleen. Jos ongelma toistuu, pyydä apua tuesta. +Yritä muuttaa sähköpostiasi uudelleen +Tapahtui verkkovirhe. Yritä myöhemmin uudelleen. Anna voimassa oleva puhelinnumero. Napauttamalla Vahvista vahvistat hyväksyväsi käyttöehdot ja tietosuojakäytännön. Tekstiviesti voidaan lähettää, ja datan ja viestien käyttö voi olla maksullista. Anna salasana @@ -27,6 +30,7 @@ IP-osoitteestasi on lähetetty liian monta tilipyyntöä. Yritä uudelleen muutaman minuutin kuluttua. Sri Lanka Voit nyt kirjautua sisään uudella tililläsi. +Asiakas määritti virheellisen argumentin. Tšad Lähetä koodi uudelleen seuraavan ajan kuluttua: . Valko-Venäjä @@ -62,6 +66,7 @@ Github Tietosuojakäytäntö Guam +Resurssikiintiö on loppu tai määrää koskeva rajoitus on saavutettu. Kroatia Liettua Malta @@ -71,22 +76,28 @@ Vieras Ghana Käyttämäsi selain ei tue verkkotallennusta. Yritä uudelleen käyttämällä eri selainta. +Asiakas peruutti pyynnön. Kirjautumissähköpostiosoitteen takaisin vaihtamisen aikana tapahtui virhe.Jos yrität uudelleen etkä vieläkään voi nollata sähköpostiosoitettasi, kysy ohjeita järjestelmänvalvojaltasi. Bahrain +Sisäinen palvelinvirhe Iran +GitHub Tarkistamme, ettet ole robotti… Tämä koodi ei ole enää voimassa. Uusi-Kaledonia Monaco Seychellit + ei ole enää käytössä toisena todennustasona. Nykyisen anonyymin käyttäjän päivittäminen epäonnistui. Ei-anonyymi kirjautumistieto on jo yhdistetty toiseen käyttäjätiliin. Olet antanut virheellisen salasanan liian monta kertaa. Yritä uudelleen muutaman minuutin kuluttua. Turvallisuus Pyyntösi nollata salasana on vanhentunut tai linkkiä on jo käytetty. +Voit nyt kirjautua sisään uudella sähköpostillasi . Annettua maakoodia ei tueta. Tarkista että kirjoitit sähköpostiosoitteesi oikein. Twitter Kambodza +Palvelu ei ole saatavilla. Portugali Ranskan Guayana Brittiläiset Neitsytsaaret @@ -102,19 +113,21 @@ Vahvistaminen Etelä-Afrikka Jatka antamalla sähköpostiosoite. +Järjestelmänvalvoja on poistanut käyttäjätilin käytöstä. Lisäohjeita sisältävä kirjautumisviesti lähetettiin osoitteeseen . Tarkista sähköpostisi kirjautumisen suorittamiseksi loppuun. Luo tili Afganistan Valitsemalla vahvistat, että hyväksyt . Kanada Tanska +Pyynnön määräaika ylittyi. Itävalta Sähköpostit eivät täsmää. Etelä-Sudan Jemen Guinea-Bissau Saudi-Arabia -Sovelluksen logo +Sovellukseen on lisätty lisävahvistusvaihe Yhdysvaltain Neitsytsaaret Kirjaudu puhelimella Indonesia @@ -122,6 +135,7 @@ Venezuela Eikö kirjautuminen onnistu? Vahvista henkilöllisyytesi +Pyyntöäsi vahvistettaessa ilmeni ongelma. Aloita vahvistusprosessi uudelleen menemällä URL-osoitteeseen, josta sinut ohjattiin tälle sivulle. Dominica Uusi salasana Gabon @@ -139,6 +153,7 @@ Gambia Valmis Peruuta +Tuntematon palvelinvirhe Anna tilin nimi Irak Vietnam @@ -150,6 +165,8 @@ Nepal Tokelau Valittuja todennuksen tarjoajan kirjautumistietoja ei tueta. +Sisäänkirjautumisen tarjoajaa ei ole saatavilla annetulle sähköpostiosoitteelle. Kokeile jotain muuta sähköpostiosoitetta. +Lisää etu- ja sukunimi. Kolumbia Napauttamalla TALLENNA hyväksyt Burundi @@ -157,7 +174,7 @@ Twitter Sähköpostiosoitteesi on vahvistettu Papua-Uusi-Guinea -Tallenna +Määritettyä resurssia ei löydy. Vahvistetaan… Kirjaudu sisään Itä-Timor @@ -190,6 +207,7 @@ Puhelin Lähetä uudelleen Honduras +Toiminto aikakatkaistiin. Niger Sähköpostiosoite ei vastaa aiemmin luotua tiliä. Jordania @@ -201,6 +219,7 @@ Israel Tapahtui virhe Numero +Asiakkaalla ei ole riittävää lupaa. Ratkaise reCAPTCHA Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Turks- ja Caicossaaret Lähetä koodi uudelleen: Tarkista sähköpostisi +Toisen todennustason poistaminen epäonnistui Lisää salasana El Salvador +Jotta voit vaihtaa tilisi salasanan, sinun pitää kirjautua sisään uudelleen. Serbia Belize osoitteelle Jotta palvelun tilisi voidaan yhdistää tähän sähköpostiosoitteeseen tällä prosessilla, sinun on avattava linkki samalla laitteella tai selaimella. Ruotsi Saint-Martin +Sähköpostisi vahvistus- ja muutospyyntö on vanhentunut tai sen linkki on jo käytetty. Saint Lucia @string/app_name Tarkista sähköpostisi @@ -224,13 +246,15 @@ Oman Alankomaiden Karibia Päivitetty sähköpostiosoite -Kirjaudu Twitter-tilillä +Sinut on nyt kirjattu ulos. +Kirjaudu puhelimella Puhelinnumero Sinulla on jo tili. Thaimaa Tietosuojakäytäntö Liechtenstein Muokkaa nimeä +Laite tai sovellus ei ole enää käytössä toisena todennustasona. Kokeile avata linkki käyttäen samaa laitetta tai selainta, jolla aloitit kirjautumisen. Alankomaat Päiväntasaajan Guinea @@ -254,7 +278,7 @@ Jatka antamalla sähköpostiosoite. Puhelinnumeron vahvistaminen Liberia -Kyseisellä sähköpostiosoitteella on jo rekisteröity tili. +Kirjaudu ulos Madagaskar Kazakstan Kirjaudutaan… @@ -274,15 +298,20 @@ Anna merkin pituinen koodi, jonka lähetimme numeroon Tätä puhelinnumeroa on käytetty liian monta kertaa. Antamasi sähköpostiosoite ei vastaa nykyistä kirjautumisistuntoa. -Poista tilin linkitys +Twitter + +Kirjaudu sisään sovellukseen </p></p> + Tarkista ettei postilaatikkosi tila ole loppumassa tai ettei postilaatikon asetuksissa ole muita ongelmia. +Asiakas määritti virheellisen alueen. +Tämän toiminnon kiintiö on ylitetty. Yritä myöhemmin uudelleen. Paraguay Algeria Olet antanut virheellisen salasanan liian monta kertaa. Yritä uudelleen muutaman minuutin kuluttua. Norfolkinsaari Puhelinnumero vahvistettu automaattisesti -Jotta voit vaihtaa salasanasi, sinun on annettava ensin nykyinen salasanasi. -Takaisin +Verkkovirhe, tarkista internetyhteys. +Vahvistettu! Intia Benin Jatka @@ -331,27 +360,33 @@ Sähköposti Seuraava Tämä koodi ei ole enää voimassa. -Kirjaudu Facebook-tilillä +Lisäohjeita sisältävä kirjautumisviesti lähetettiin osoitteeseen . Tarkista sähköpostisi kirjautumisen suorittamiseksi loppuun. Vahvista sähköposti Etu- ja sukunimi Grenada Länsi-Sahara +Pyyntöä ei voi toteuttaa nykyisessä järjestelmän tilassa. Tapahtui tuntematon virhe. Olet jo käyttänyt osoitetta kirjautumiseen. Anna kyseisen tilin salasana. +Kirjautumisviesti lähetetty Irlanti Kongon demokraattinen tasavalta Palauta salasana Kongon tasavalta +Resurssi, jota asiakas yritti luoda, on jo olemassa. Jatka vieraana San Marino Kypros Salomonsaaret Jatkatko käyttämällä osoitetta ? +Sinun on palautettava sanasana, jotta voit jatkaa kirjautumista tällä laitteella tilillä . Malediivit São Tomé ja Príncipe Vahvista sähköpostisi kirjautumisen suorittamiseksi loppuun Slovenia Etelä-Korea +Samanaikaisuusristiriita, kuten luku-muokkaus-kirjoitus-ristiriita +Palvelin ei ottanut sovellusliittymämenetelmää käyttöön. Lesotho Kirjaudu puhelimella Yritä vahvistaa sähköpostisi uudelleen @@ -371,8 +406,10 @@ kirjautumiseen. Anna kyseisen tilin salasana. {plural_var,plural, =1{Salasana ei ole tarpeeksi vahva. Siinä on oltava vähintään merkkiä – sekä kirjaimia että numeroita.}other{Salasana ei ole tarpeeksi vahva. Siinä on oltava vähintään merkkiä – sekä kirjaimia että numeroita.}} Zimbabwe +Pyyntöä ei todennettu puuttuvan, virheellisen tai vanhentuneen OAuth-tunnuksen vuoksi. Puhelinnumerosi vahvistamisessa tapahtui virhe. Uusi laite tai selain havaittu +Kirjaudu sisään: Tšekin tasavalta Tuvalu Kirjaudu GitHub-tilillä @@ -427,6 +464,7 @@ Bermuda Niuesaari Guadalupe +Asiakassovellus teki virheelliset projektin määritykset. Palestiinalaisalue Slovakia Guernsey @@ -439,19 +477,24 @@ Malesia Kookossaaret Jos haluat edelleen yhdistää palvelun tilisi, avaa linkki samalla laitteella, jolla aloitit kirjautumisen. Jos taas haluat kirjautua tällä laitteella, napauta Jatka. +Yritä uudelleen Tarkoituksesi oli yhdistää sähköpostiosoitteeseesi, mutta avasit linkin eri laitteella, johon et ollut kirjautunut. Lähetä koodi uudelleen seuraavan ajan kuluttua: 0:. Marokko +Jos et tunnista tätä laitetta, joku voi yrittää päästä käsiksi tiliisi. Suosittelemme, että muutat salasanasi heti. Bangladesh Puhelin Suomi Arabiemiirikunnat +Sähköpostiosoitteesi on vahvistettu ja muutettu Salasana Keski-Afrikan tasavalta Curaçao +Olemme estäneet kaikki pyynnöt tältä laitteelta epätavallisen toiminnan vuoksi. Yritä myöhemmin uudelleen. Tunisia Salasana vaihdettu Kreikka +Toinen todennustaso poistettiin Angola Kuwait Antamasi sähköpostiosoite ja salasana eivät täsmää. diff --git a/translations/fil.xtb b/translations/fil.xtb index 65e50123..305f47f0 100644 --- a/translations/fil.xtb +++ b/translations/fil.xtb @@ -12,11 +12,14 @@ Philippines Romania South Georgia and The South Sandwich Islands -OK +Hindi mare-recover na pagkawala ng data o pagkasira ng data. Italy Lebanon Guyana Guatemala +Nagkaproblema sa pag-aalis ng iyong pangalawang factor.Subukang alisin muli. Kung hindi umubra iyon, makipag-ugnayan sa suporta para sa tulong. +Subukang i-update muli ang iyong email +Nagkaroon ng error sa network. Subukang muli sa ibang pagkakataon. Maglagay ng wastong numero ng telepono Sa pag-tap sa I-verify, ipinababatid mo na tinatanggap mo ang aming Mga Tuntunin ng Serbisyo at Patakaran sa Privacy. Maaaring magpadala ng SMS. Maaaring ipatupad ang mga rate ng pagmemensahe at data. Ilagay ang iyong password @@ -27,6 +30,7 @@ Masyadong maraming kahilingan ng account mula sa iyong IP address. Subukang muli pagkalipas ng ilang minuto. Sri Lanka Makakapag-sign in ka na ngayon gamit ang iyong bagong account +May tinukoy na invalid na argument ang client. Chad Sa pag-tap sa "", ipinababatid mo na tinatanggap mo ang aming at . Maaaring magpadala ng SMS. Maaaring ipatupad ang mga rate ng pagmemensahe at data. Belarus @@ -62,6 +66,7 @@ Github Patakaran sa Privacy Guam +Maaaring naubusan ng resource quota o malapit na sa limit ng rate. Croatia Lithuania Malta @@ -71,21 +76,27 @@ Bisita Ghana Hindi sinusuportahan ng browser na ginagamit mo ang Web Storage. Pakisubukang muli sa ibang browser. +Kinansela ng client ang kahilingan. Nagkaproblema sa pagbabalik sa iyong email sa pag-sign in.Kung susubukan mong muli at hindi pa rin ma-reset ang iyong email, subukang humingi ng tulong sa iyong administrator. Bahrain +Internal na error sa server. Iran +GitHub Vine-verify na hindi ka robot... Wala nang bisa ang code na ito New Caledonia Monaco Seychelles +Inalis ang bilang pangalawang hakbang sa pag-authenticate. Hindi nakapag-upgrade ang kasalukuyang anonymous na user. Nauugnay na ang di-anonymous na kredensyal sa ibang account ng user. Nagpasok ka ng di-wastong password nang masyadong maraming beses. Pakisubukang muli pagkalipas ng ilang minuto. Ang iyong kahilingan na i-reset ang iyong password ay nag-expire na o nagamit na ang link +Makakapag-sign in ka na ngayon gamit ang bago mong email . Hindi suportado ang ibinigay na country code Tiyakin na hindi ka nagkamali sa pagbaybay ng iyong email. Twitter Cambodia +Hindi available ang serbisyo. Portugal French Guiana British Virgin Islands @@ -101,19 +112,21 @@ I-verify South Africa Ilagay ang iyong email address upang magpatuloy +Na-disable ng isang administrator ang user account. Nagpadala ng email sa pag-sign in na may mga karagdagang tagubilin sa . Tingnan ang iyong email para makumpleto ang pag-sign in. Gumawa ng account Afghanistan Sa pag-tap sa , ipinababatid mo na sang-ayon ka sa . Canada Denmark +Lumagpas na sa deadline ng kahilingan. Austria Hindi tumutugma ang mga email South Sudan Yemen Guinea-Bissau Saudi Arabia -Logo ng app +May karagdagang seguridad na idinagdag sa U.S. Virgin Islands Mag-sign in gamit ang telepono Indonesia @@ -121,6 +134,7 @@ Venezuela Nagkaproblema sa pag-sign in? I-verify na ikaw ito +Nagkaproblema habang ino-authenticate ang kahilingan mo. Pakibisita ang URL na muling nag-redirect sa iyo sa page na ito para ma-restart ang proseso ng pag-authenticate. Dominica Bagong password Gabon @@ -138,6 +152,7 @@ Gambia Tapos Na Kanselahin +Hindi kilalang server error. Ilagay ang pangalan ng iyong account Iraq Vietnam @@ -149,13 +164,15 @@ Nepal Tokelau Hindi suportado ang napiling kredensyal para sa tagapag-authenticate! +Walang available na provider sa pag-sign-in para sa nasabing email, pakisubukan gamit ang ibang email. +Maglagay ng pangalan at apelyido. Colombia Burundi Mauritania Twitter Na-verify na ang iyong email Papua New Guinea -I-save +Hindi natagpuan ang tinukoy na resource. Bine-verify... Mag-sign In East Timor @@ -188,6 +205,7 @@ Telepono Ipadalang Muli Honduras +Nag-time out ang operasyon. Niger Hindi tumutugma ang email address na iyon sa isang kasalukuyang account Jordan @@ -199,6 +217,7 @@ Israel Nagkaroon ng error Numero +Walang sapat na pahintulot ang client. Sagutin ang reCAPTCHA Nicaragua Burkina Faso @@ -207,27 +226,32 @@ Turks and Caicos Islands Ipadala muli ang code sa  Suriin ang iyong email +Hindi maalis ang iyong pangalawang factor Magdagdag ng password El Salvador +Upang baguhin ang password sa iyong account, kailangan mong mag-sign in muli. Serbia Belize para sa  Para matagumpay na maikonekta ng flow na ito ang iyong account gamit ang email na ito, kailangan mong buksan ang link na ito sa parehong device o browser. Sweden Saint Martin +Ang kahilingan mong i-verify at i-update ang iyong email ay nag-expire na o nagamit na ang link. St. Lucia Suriin ang iyong email Ang iyong kahilingan na i-verify ang iyong email ay nag-expire na o nagamit na ang link Oman Caribbean Netherlands Na-update na email address -Mag-sign in sa Twitter +Matagumpay kang naka-sign out. +Mag-sign in gamit ang telepono Numero ng telepono Mayroon ka nang account Thailand Patakaran sa Privacy Liechtenstein I-edit ang pangalan +Inalis ang device o app bilang pangalawang hakbang sa pag-authenticate. Subukang buksan ang link gamit ang parehong device o browser kung saan mo sinimulan ang proseso ng pag-sign in. Netherlands Equatorial Guinea @@ -251,7 +275,7 @@ Ilagay ang iyong email address upang magpatuloy I-verify ang numero ng iyong telepono Liberia -Mayroon nang account gamit ang email address na iyon. +Mag-sign Out Madagascar Kazakhstan Nagsa-sign in... @@ -270,15 +294,20 @@ Ilagay ang -digit na code na ipinadala namin sa Masyadong maraming beses nang nagamit ang numero ng teleponong ito Hindi tumutugma ang ibinigay na email sa kasalukuyang session ng pag-sign-in. -I-unlink ang account +Twitter + +Mag-sign in sa </p></p> + Tingnan kung hindi pa nauubusan ng espasyo ang iyong inbox o may iba pang isyu na may kinalaman sa mga setting ng inbox. +May tinukoy na invalid na hanay ang client. +Nahigitan ang quota para sa operasyong ito. Subukang muli sa ibang pagkakataon. Paraguay Algeria Nagpasok ka ng di-wastong password nang masyadong maraming beses. Pakisubukang muli pagkalipas ng ilang minuto. Norfolk Island Awtomatikong na-verify ang numero ng telepono -Upang baguhin ang iyong password, kailangan mo munang ipasok ang iyong kasalukuyang password. -Bumalik +Error sa network, tingnan ang koneksyon mo sa internet. +Na-verify! India Benin Magpatuloy @@ -327,27 +356,33 @@ Mag-email Susunod Wala nang bisa ang code na ito -Mag-sign in sa Facebook +Nagpadala ng email sa pag-sign in na may mga karagdagang tagubilin sa . Tingnan ang iyong email para makumpleto ang pag-sign in. Kumpirmahin ang email Pangalan at apelyido Grenada Western Sahara +Hindi maaaring isagawa ang kahilingan sa kasalukuyang status ng system. Nagkaroon ng hindi malamang error. Nagamit mo na ang  upang mag-sign in. Ilagay ang iyong password para sa account na iyon. +Naipadala na ang email sa pag-sign in Ireland Democratic Republic Congo I-recover ang password Republic of Congo +Mayroon nang resource na sinubukang gawin ng client. Magpatuloy bilang bisita San Marino Cyprus Solomon Islands Magpatuloy sa ? +Para magpatuloy sa pag-sign in gamit ang sa device na ito, kailangan mong ma-recover ang password. Maldives São Tomé and Príncipe Kumpirmahin ang iyong email para makumpleto ang pag-sign in Slovenia South Korea +Concurrency conflict, tulad ng read-modify-write conflict. +Hindi naimplementa ng server ang API method. Lesotho Mag-sign in gamit ang telepono Subukang i-verify muli ang iyong email @@ -367,8 +402,10 @@         upang mag-sign in. Ilagay ang iyong password para sa account na iyon. {plural_var,plural, =1{Hindi masyadong malakas ang password. Gumamit ng  character man lang at kumbinasyon ng mga titik at mga numero}one{Hindi masyadong malakas ang password. Gumamit ng  character man lang at kumbinasyon ng mga titik at mga numero}other{Hindi masyadong malakas ang password. Gumamit ng  na character man lang at kumbinasyon ng mga titik at mga numero}} Zimbabwe +Hindi na-authenticate ang kahilingan dahil sa nawawala, invalid, o nag-expire na OAuth token. Nagkaproblema sa pag-verify ng numero ng iyong telepono May nakitang bagong device o browser +Mag-sign in sa Czechia Tuvalu Mag-sign in sa GitHub @@ -423,6 +460,7 @@ Bermuda Niue Guadeloupe +May tinukoy ang client na invalid na configuration ng proyekto. Palestinian Territories Slovakia Guernsey @@ -435,19 +473,24 @@ Malaysia Cocos [Keeling] Islands Kung gusto mo pa ring ikonekta ang iyong account, buksan ang link sa mismong device kung saan ka nagsimula ng pag-sign-in. Kung hindi, i-tap ang Magpatuloy para mag-sign in sa device na ito. +Subukang Muli Orihinal na sinadya mong ikonekta ang sa iyong email account pero nabuksan ang link sa ibang device kung saan hindi ka naka-sign in. Ipadala muli ang code sa loob ng 0: Morocco +Kung hindi mo kilala ang device na ito, maaaring may uma-access ng iyong account. Isaalang-alang ang agad na pagpapalit ng iyong password. Bangladesh Telepono Finland United Arab Emirates +Na-verify at pinalitan ang iyong email Password Central African Republic Curaçao +Na-block na namin ang lahat ng kahilingan mula sa device na ito dahil sa kakaibang aktibidad. Subukang muli sa ibang pagkakataon. Tunisia Pinalitan ang password Greece +Inalis ang pangalawang factor Angola Kuwait Hindi nagtutugma ang inilagay mong email at password diff --git a/translations/fr.xtb b/translations/fr.xtb index b66a9647..c806350a 100644 --- a/translations/fr.xtb +++ b/translations/fr.xtb @@ -12,11 +12,14 @@ Philippines Roumanie Géorgie du Sud et Sandwich du Sud (Îles) -OK +Perte de données irrécupérable ou corruption de données. Italie Liban Guyane Guatemala +Une erreur s'est produite lors de la suppression du deuxième facteur.Veuillez réessayer. Si le problème persiste, contactez l'assistance. +Essayez de modifier à nouveau votre adresse e-mail +Une erreur réseau s'est produite Réessayez plus tard. Saisissez un numéro de téléphone valide En appuyant sur "Valider", vous acceptez les Conditions d'utilisation et les Règles de confidentialité. Vous déclencherez peut-être l'envoi d'un SMS. Des frais de messages et de données peuvent être facturés. Saisissez votre mot de passe @@ -27,6 +30,7 @@ De trop nombreuses demandes de compte proviennent de votre adresse IP. Veuillez réessayer dans quelques minutes. Sri Lanka Vous pouvez maintenant vous connecter avec votre nouveau compte +Le client a spécifié un argument incorrect. Tchad Renvoyer le code dans Biélorussie @@ -62,6 +66,7 @@ GitHub Règles de confidentialité Guam +Le quota de ressources est dépassé ou la limite du débit est atteinte. Croatie Lituanie Malte @@ -71,22 +76,28 @@ Invité Ghana Le navigateur que vous utilisez n'est pas compatible avec le stockage Web. Veuillez réessayer dans un navigateur différent. +La demande a été annulée par le client. Un problème est survenu lors du rétablissement de votre adresse e-mail de connexion.Si l'opération échoue à nouveau lors de votre prochaine tentative, contactez votre administrateur. Bahreïn +Erreur interne du serveur. Iran +GitHub Nous vérifions que vous n'êtes pas un robot… Ce code n'est plus valide Nouvelle-Calédonie Monaco Seychelles +Le facteur suivant de la deuxième étape d'authentification a été supprimé : . Échec de la mise à jour de l'utilisateur anonyme actuel. Les identifiants non anonymes sont déjà associés à un autre compte utilisateur. Vous avez saisi un mot de passe incorrect un trop grand nombre de fois. Veuillez réessayer dans quelques minutes. Sécurité Votre demande de réinitialisation du mot de passe a expiré ou ce lien a déjà été utilisé +Vous pouvez maintenant vous connecter avec votre nouvelle adresse e-mail : . Le code pays indiqué n'est pas accepté. Vérifiez que votre adresse e-mail est correcte. Twitter Cambodge +Service indisponible. Portugal Guyane française Îles Vierges britanniques @@ -102,6 +113,7 @@ Valider Afrique du Sud Saisissez votre adresse e-mail pour continuer +Le compte utilisateur a été désactivé par un administrateur. Un e-mail de connexion avec des instructions supplémentaires a été envoyé à . Consultez cet e-mail pour vous connecter. Saisissez votre mot de passe Créer un compte @@ -109,13 +121,14 @@ En appuyant sur , vous acceptez les . Canada Danemark +Délai de requête dépassé. Autriche Les adresses e-mail ne correspondent pas Soudan du Sud Yémen Guinée-Bissau Arabie saoudite -Logo de l'application +Un facteur de sécurité a été ajouté à Îles Vierges américaines Se connecter avec un téléphone Indonésie @@ -123,6 +136,7 @@ Venezuela Vous ne parvenez pas à vous connecter ? Confirmez qu'il s'agit bien de vous +Une erreur s'est produite lors de l'authentification de votre requête. Veuillez retourner sur la page qui vous a redirigé ici pour relancer le processus d'authentification. Dominique Nouveau mot de passe Gabon @@ -140,6 +154,7 @@ Gambie OK Annuler +Erreur du serveur inconnue. Saisissez le nom de votre compte Iraq Viêt Nam @@ -151,6 +166,8 @@ Népal Tokélaou Les identifiants sélectionnés pour le fournisseur d'authentification ne sont pas compatibles. +Veuillez essayer avec une autre adresse e-mail, car il n'y a pas de méthode de connexion disponible pour celle-ci. +Veuillez saisir un prénom et un nom. Colombie En appuyant sur ENREGISTRER, vous acceptez les Burundi @@ -158,7 +175,7 @@ Twitter Votre adresse e-mail a bien été validée Papouasie - Nouvelle-Guinée -Enregistrer +Ressource indiquée introuvable. Validation… Se connecter Timor Oriental @@ -191,6 +208,7 @@ Numéro de téléphone Renvoyer Honduras +L'opération a dépassé le délai. Niger Cette adresse e-mail ne correspond à aucun compte existant Jordanie @@ -202,6 +220,7 @@ Israël Une erreur s'est produite Numéro +Le client ne dispose pas d'une autorisation suffisante. Résoudre le reCAPTCHA Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Turks-et-Caïcos (Îles) Renvoyer le code dans  Consultez votre boîte de réception +Impossible de supprimer votre deuxième facteur Ajouter un mot de passe Salvador +Vous devrez vous reconnecter pour modifier le mot de passe de votre compte. Serbie Belize pour Pour associer votre compte à cette adresse e-mail, vous devez ouvrir le lien sur le même appareil ou dans le même navigateur. Suède Saint-Martin +Votre demande de validation et de modification de l'adresse e-mail a expiré, ou ce lien a déjà été utilisé. Sainte-Lucie @string/app_name Consultez votre boîte de réception @@ -225,13 +247,15 @@ Oman Antilles néerlandaises L'adresse e-mail a bien été mise à jour -Se connecter avec Twitter +Vous avez bien été déconnecté. +Se connecter avec un téléphone Numéro de téléphone Vous avez déjà un compte Thaïlande Code erroné. Veuillez réessayer. Liechtenstein Modifier le nom +L'application ou l'appareil qui servait de deuxième étape d'authentification a été supprimé. Essayez d'ouvrir le lien en utilisant le même appareil ou navigateur que celui sur lequel vous avez commencé le processus de connexion. Pays-Bas Guinée équatoriale @@ -255,7 +279,7 @@ Saisissez votre adresse e-mail pour continuer Validez votre numéro de téléphone Libéria -Il existe déjà un compte associé à cette adresse e-mail. +Se déconnecter Madagascar Kazakhstan Connexion… @@ -274,15 +298,20 @@ Saisissez le code à  chiffres envoyé au Ce numéro de téléphone a été utilisé un trop grand nombre de fois L'adresse e-mail fournie ne correspond pas à celle utilisée pour la session de connexion en cours. -Dissocier le compte +Twitter + +Connectez vous à </p></p> + Vérifiez que votre boîte de réception n'est pas pleine et que les paramètres sont correctement définis. +Le client a spécifié une plage non valide. +Le quota pour cette opération a été dépassé. Réessayez plus tard. Paraguay Algérie Vous avez saisi un mot de passe incorrect un trop grand nombre de fois. Veuillez réessayer dans quelques minutes. Norfolk (Île) Numéro de téléphone validé automatiquement -Pour modifier votre mot de passe, vous devez d'abord saisir votre mot de passe actuel. -Retour +Erreur de réseau, vérifiez votre connexion Internet. +Le code a bien été validé. Inde Bénin Continuer @@ -331,27 +360,33 @@ E-mail Suivant Ce code n'est plus valide -Se connecter avec Facebook +Un e-mail de connexion avec des instructions supplémentaires a été envoyé à . Consultez cet e-mail pour vous connecter. Confirmer l'e-mail Nom et prénom Grenade Sahara occidental +La requête ne peut pas être exécutée dans l'état actuel du système. Une erreur inconnue s'est produite. Vous avez déjà utilisé l'adresse pour vous connecter. Saisissez le mot de passe pour ce compte. +E-mail de connexion envoyé Irlande République démocratique du Congo Récupérer votre mot de passe République du Congo +La ressource qu'un client a essayé de créer existe déjà. Continuer en tant qu'invité Saint-Marin Chypre Salomon (Îles) Souhaitez-vous continuer avec l'adresse  ? +Pour vous connecter avec l'adresse e-mail sur cet appareil, vous devez récupérer votre mot de passe. Maldives Sao Tomé-et-Principe Confirmez votre adresse e-mail pour vous connecter. Slovénie Corée du Sud +Un conflit de simultanéité existe, tel qu'un conflit lecture-modification-écriture. +Méthode d'API non mise en œuvre par le serveur. Lesotho Se connecter avec un téléphone Essayez de valider à nouveau votre adresse e-mail @@ -370,9 +405,11 @@ Vous avez déjà utilisé l'adresse e-mail pour vous connecter. Saisissez le mot de passe du compte. {plural_var,plural, =1{Le mot de passe n'est pas assez sécurisé. Utilisez au moins  caractère et une combinaison de chiffres et de lettres.}one{Le mot de passe n'est pas assez sécurisé. Utilisez au moins  caractère et une combinaison de chiffres et de lettres.}other{Le mot de passe n'est pas assez sécurisé. Utilisez au moins  caractères et une combinaison de chiffres et de lettres.}} Zimbabwe -Suivez les instructions envoyées à l'adresse e-mail pour récupérer votre mot de passe. -Un problème est survenu lors de la validation de votre numéro de téléphone +La requête n'a pas été authentifiée en raison d'un jeton OAuth manquant, non valide ou ayant expiré. +Veuillez indiquer un code de validation à six chiffres. +Choisissez un mot de passe Nouveau navigateur ou appareil détecté +Se connecter à République tchèque Tuvalu Se connecter avec GitHub @@ -398,6 +435,10 @@ Espagne Costa Rica Samoa + +Le facteur a été ajouté à votre compte pour l'authentification à deux facteurs. +Si vous n'avez pas demandé cette mise à jour, veuillez cliquer sur le lien ci-dessous pour annuler la modification. + Mali Qatar Uruguay @@ -428,6 +469,7 @@ Bermudes Niué Guadeloupe +La configuration de projet spécifiée par le client n'est pas valide. Territoires palestiniens Slovaquie Guernesey @@ -440,19 +482,24 @@ Malaisie Cocos (Îles) (Keeling) Si vous souhaitez toujours associer votre compte , ouvrez le lien sur l'appareil avec lequel vous avez commencé à vous connecter. Sinon, appuyez sur "Continuer" pour vous connecter depuis un autre appareil. +Réessayer Vous aviez décidé d'associer votre compte à votre adresse e-mail, mais vous avez ouvert le lien sur un appareil différent de celui avec lequel vous vous êtes connecté. Renvoyer le code dans 0: Maroc +Si vous ne reconnaissez pas cet appareil, cela signifie peut-être que quelqu'un essaie d'accéder à votre compte. Envisagez de modifier votre mot de passe tout de suite. Bangladesh Téléphone Finlande Émirats arabes unis +Votre adresse e-mail a été validée et modifiée Mot de passe République centrafricaine Curaçao +Nous avons bloqué toutes les requêtes provenant de cet appareil, car nous avons détecté une activité inhabituelle. Réessayez plus tard. Tunisie Le mot de passe a bien été modifié Grèce +Deuxième facteur supprimé Angola Koweït L'adresse e-mail et le mot de passe saisis ne correspondent pas diff --git a/translations/hi.xtb b/translations/hi.xtb index 3aa52631..a7652324 100644 --- a/translations/hi.xtb +++ b/translations/hi.xtb @@ -6,27 +6,31 @@ इस फ़ोन नंबर का उपयोग कई बार किया गया है ui_flow लात्विया -सहेजें +सेव करें सिंगापुर इस कार्रवाई के लिए कृपया फिर से लॉगिन करें फ़िलिपींस रोमानिया दक्षिण जॉर्जिया और दक्षिण सैंडविच आइलैंड -ठीक +डेटा को वापस नहीं पाया जा सकता या डेटा खराब हो गया. इटली लेबनान गुयाना ग्वाटेमाला +पुष्टि करने का दूसरा चरण हटाते समय कोई गड़बड़ी हुई.फिर से हटाने की काेशिश करें. अगर फिर भी काम नहीं बनता, ताे सहायता टीम से संपर्क करें. +अपने ईमेल पते काे फिर से अपडेट करने की कोशिश करें +नेटवर्क में कोई गड़बड़ी हुई. कुछ देर बाद कोशिश करें. कोई मान्य फ़ोन नंबर डालें पुष्टि करें पर टैप करके, आप यह बताते हैं कि आप हमारी सेवा की शर्तें और निजता नीति को मंज़ूर करते हैं. एक मैसेज (एसएमएस) भेजा जा सकता है. मैसेज और डेटा दरें लागू हो सकती हैं. अपना पासवर्ड डालें हो गया हैती -अगर आपने अपना साइन-इन ईमेल बदलने के लिए नहीं कहा है, तो हो सकता है कि कोई आपके खाते का एक्सेस पाने की कोशिश कर रहा है. ऐसे में आपको अपना पासवर्ड तुरंत बदलना चाहिए. +अगर आपने अपना साइन-इन ईमेल बदलने के लिए नहीं कहा है, तो हो सकता है कि कोई आपके खाते का ऐक्सेस पाने की कोशिश कर रहा है. ऐसे में आपको अपना पासवर्ड तुरंत बदलना चाहिए. हर्ड आइलैंड और मैकडॉनल्ड आइलैंड -आपके IP पते से बहुत अधिक खाता अनुरोध मिल रहे हैं. कुछ मिनटों में फिर से कोशिश करें. +आपके IP पते से बहुत ज़्यादा खाता अनुरोध मिल रहे हैं. कुछ मिनटों में फिर से कोशिश करें. श्रीलंका अब आप अपने नए खाते से प्रवेश कर सकते हैं +क्लाइंट ने एक अमान्य तर्क बताया. चाड में कोड फिर से भेजें बेलारुस @@ -62,6 +66,7 @@ Github निजता नीति ग्वाम +संसाधन की सीमा खत्म हो गई है या अनुरोध संख्या की सीमा तक पहुंचने वाले हैं. क्रोएशिया लिथुआनिआ माल्टा @@ -71,22 +76,28 @@ मेहमान घाना आप जिस ब्राउज़र का उपयोग कर रहे हैं वह वेब संग्रहण का समर्थन नहीं करता है. कृपया किसी दूसरे ब्राउज़र में फिर से कोशिश करें. +क्लाइंट ने अनुरोध रद्द कर दिया. आपके साइन-इन ईमेल को वापस बदलते समय एक समस्या हुई.अगर दोबारा कोशिश करने पर भी आप अपना ईमेल रीसेट नहीं कर पाते हैं, तो सहायता के लिए अपने व्यवस्थापक से पूछ कर देखें. बहरीन +सर्वर में गड़बड़ी. ईरान +GitHub आप रोबोट नहीं हैं इसकी पुष्टि की जा रही है... यह कोड अब मान्य नहीं है न्यू कैलेडोनिया मॉनेको सेशल्स +पुष्टि करने के दूसरे चरण के तहत काे हटा दिया गया. मौजूदा अनाम उपयोगकर्ता अपग्रेड नहीं कर सका. गैर-अनाम क्रेडेंशियल पहले से ही किसी दूसरे उपयोगकर्ता खाते से जुड़ा हुआ है. आपने कई बार गलत पासवर्ड डाला. कुछ मिनटों में फिर से कोशिश करें. सुरक्षा -आपके पासवर्ड रीसेट अनुरोध की समय-सीमा समाप्त हो गई है या लिंक का उपयोग पहले ही किया जा चुका है +आपके पासवर्ड रीसेट अनुरोध की समय-सीमा खत्म हो गई है या लिंक का उपयोग पहले ही किया जा चुका है +अब आप अपने नए ईमेल पते से साइन इन कर सकते हैं. डाला गया देश कोड इस्तेमाल नहीं किया जा सकता है. देख लें कि आपने ईमेल पते की वर्तनी गलत तो नहीं लिखी है. Twitter कंबोडिया +सेवा उपलब्ध नहीं है. पुर्तगाल फ़्रेंच गुयाना ब्रिटिश वर्जिन आइलैंड @@ -98,23 +109,25 @@ कोसोवो उज़बेकिस्तान एलैंड आइलैंड -आपका साइन-इन सत्र समाप्त हो गया. कृपया फिर से कोशिश करें. +आपका साइन-इन सत्र खत्म हो गया. कृपया फिर से कोशिश करें. पुष्टि करें दक्षिण अफ़्रीका जारी रखने के लिए अपना ईमेल पता डालें +उपयोगकर्ता के खाते को एडमिन ने बंद कर दिया है. साइन-इन पूरा करने के लिए और भी ज़्यादा निर्देशों वाला एक ईमेल पर भेजा गया है. साइन-इन पूरा करने के लिए अपना ईमेल देखें. खाता बनाएं अफ़गानिस्तान पर टैप करके आप बताते हैं कि आप से सहमत हैं. कनाडा डेनमार्क +अनुरोध का समय खत्म हो गया. ऑस्ट्रिया ईमेल का मिलान नहीं हो रहा है दक्षिण सूडान यमन गिनी-बिसाऊ सऊदी अरब -ऐप्लिकेशन लोगो + में एक और सुरक्षा तरीका जोड़ दिया गया है यू.एस. वर्जिन आइलैंड फ़ोन से प्रवेश करें इंडोनेशिया @@ -122,6 +135,7 @@ वेनेज़ुएला प्रवेश करने में समस्या? पुष्टि करें कि यह आप ही हैं +आपके अनुरोध की पुष्टि करने के दौरान कोई समस्या हुई. पुष्टि करने की प्रक्रिया फिर से शुरू करने के लिए, उस यूआरएल पर जाएं जिससे आप दोबारा इस पेज तक पहुंचे हैं. डोमिनिका नया पासवर्ड गैबॉन @@ -139,6 +153,7 @@ गांबिया हो गया रद्द करें +सर्वर में अज्ञात गड़बड़ी. अपना खाता नाम डालें इराक वियतनाम @@ -150,14 +165,16 @@ नेपाल तोकेलाउ प्रमाणीकरण देने वाले के लिए चयनित क्रेडेंशियल समर्थित नहीं है! +दिए गए ईमेल से साइन इन नहीं किया जा सकता. कृपया कोई दूसरा ईमेल डालें. +कृपया नाम और उपनाम डालें. कोलंबिया -सहेजें को टैप करके आप यह बताते हैं कि आप इससे सहमत हैं +सेव करें को टैप करके आप यह बताते हैं कि आप इससे सहमत हैं बुरुंडी मॉरिटैनिया Twitter आपके ईमेल की पुष्टि हो चुकी है पापुआ न्यू गिनी -सहेजें +बताया गया संसाधन नहीं मिला. पुष्टि कर रहा है… प्रवेश करें पूर्वी तिमोर @@ -190,6 +207,7 @@ फ़ोन फिर से भेजें होंडुरास +ऑपरेशन का समय खत्म हो गया. नाइजर वह ईमेल पता किसी मौजूदा खाते से मेल नहीं खाता है जॉर्डन @@ -201,6 +219,7 @@ इज़राइल गड़बड़ी का सामना करना पड़ा नंबर +क्लाइंट के पास ज़रूरी अनुमति नहीं है. reCAPTCHA हल करें निकारागुआ बुर्किना फ़ासो @@ -209,28 +228,33 @@ टर्क्स और कैकॉस आइलैंड में कोड फिर से भेजें अपना ईमेल जाँचें +पुष्टि करने का दूसरा चरण हटाया नहीं जा सका पासवर्ड जोड़ें अल सल्वाडोर +अपने खाते का पासवर्ड बदलने के लिए, आपको फिर से प्रवेश करना होगा. सर्बिया बेलिज़ के लिए अपने खाते को इस ईमेल पते से सफलतापूर्वक कनेक्ट करने के लिए आपको उसी डिवाइस या ब्राउज़र पर लिंक खोलना होगा, जिससे आपने साइन-इन करना शुरू किया था. स्वीडन सेंट मार्टिन +अपने ईमेल पते की पुष्टि और उसमें बदलाव के आपके अनुरोध की समय-सीमा खत्म हो गई है या लिंक का पहले ही इस्तेमाल किया जा चुका है. सेंट लूसिया @string/app_name अपना ईमेल जाँचें -अपने ईमेल की पुष्टि के आपके अनुरोध की समय-सीमा समाप्त हो गई है या लिंक का पहले ही उपयोग किया जा चुका है +अपने ईमेल की पुष्टि के आपके अनुरोध की समय-सीमा खत्म हो गई है या लिंक का पहले ही उपयोग किया जा चुका है ओमान कैरेबियाई नीदरलैंड्स अपडेट किया हुआ ईमेल पता -Twitter से प्रवेश करें +अब आप साइन आउट हो गए हैं. +फ़ोन से प्रवेश करें फ़ोन नंबर आपके पास पहले से ही एक खाता है थाईलैंड निजता नीति लीचटेंस्टीन -नाम संपादित करें +नाम में बदलाव करें +पुष्टि करने के दूसरे चरण के तहत डिवाइस या ऐप्लिकेशन काे हटा दिया गया. उसी डिवाइस या ब्राउज़र का इस्तेमाल करके लिंक खोलने की कोशिश करें जिससे आपने साइन-इन करना शुरू किया था. नीदरलैंड भूमध्यवर्ती गिनी @@ -254,7 +278,7 @@ जारी रखने के लिए अपना ईमेल पता डालें अपने फ़ोन नंबर की पुष्टि करें लाइबेरिया -उस ईमेल पते के साथ एक खाता पहले से मौजूद है. +साइन आउट करें मेडागास्कर कज़ाकिस्तान साइन इन किया जा रहा है... @@ -273,15 +297,20 @@ हमारी ओर से भेजा गया -अंकों वाला कोड डालें इस फ़ोन नंबर का उपयोग कई बार किया गया है दिया गया ईमेल मौजूदा साइन-इन सत्र से मेल नहीं खा रहा है. -खाता अनलिंक करें +Twitter + + में साइन इन करें </p></p> + देख लें कि आपके इनबॉक्स में नए ईमेल आने के लिए जगह है या नहीं या इनबॉक्स सेटिंग से जुड़ी दूसरी समस्याएं तो नहीं हैं. +क्लाइंट ने एक अमान्य सीमा बताई. +इस ऑपरेशन की सीमा पार हो चुकी है. कुछ देर बाद कोशिश करें. पराग्वे अल्जीरिया आपने कई बार गलत पासवर्ड डाला है. कृपया कुछ मिनटों में फिर से कोशिश करें. नॉरफ़ोक आइलैंड फ़ोन नंबर की अपने आप पुष्टि की गई -अपना पासवर्ड बदलने के लिए, आपको पहले अपना मौजूदा पासवर्ड डालना होगा. -वापस जाएं +नेटवर्क में गड़बड़ी है. देखें कि इंटरनेट काम कर रहा है या नहीं. +पुष्टि हो चुकी है! भारत बेनिन जारी रखें @@ -330,27 +359,33 @@ ईमेल अगला यह कोड अब मान्य नहीं है -Facebook से प्रवेश करें +ज़्यादा निर्देशों वाला एक साइन-इन ईमेल पर भेजा गया है. साइन-इन पूरा करने के लिए अपना ईमेल देखें. ईमेल की पुष्टि करें नाम और उपनाम ग्रेनेडा पश्चिमी सहारा +सिस्टम की मौजूदा स्थिति में अनुरोध पूरा नहीं किया जा सकता. ऐसी गड़बड़ी हुई है, जिसके बारे में पता नहीं है. आपने प्रवेश के लिए पहले ही का उपयोग कर लिया है. उस खाते के लिए अपना पासवर्ड डालें. +साइन-इन करने के लिंक वाला ईमेल भेज दिया गया आयरलैंड लोकतांत्रिक गणराज्य कांगो पासवर्ड फिर से पाएं कांगो गणराज्य +क्लाइंट ने जिस संसाधन को बनाने की कोशिश की, वह पहले से मौजूद है. मेहमान के रूप में जारी रखें सैन मैरीनो साइप्रस सोलोमन आइलैंड के साथ जारी रखें? + से इस डिवाइस पर साइन इन जारी रखने के लिए आपको पासवर्ड रिकवर करना होगा. मालदीव साओ टोम व प्रिंसिपे साइन इन पूरा करने से पहले अपने ईमेल की पुष्टि करें स्लोवेनिया दक्षिण कोरिया +कॉनकरंसी विरोधाभास जैसे पढ़ें-बदलें-लिखें विरोधाभास. +सर्वर ने एपीआई तरीका लागू नहीं किया. लेसोथो फ़ोन से प्रवेश करें अपने ईमेल की फिर से पुष्टि करने की कोशिश करें @@ -368,10 +403,12 @@ किर्गिस्तान आपने पहले ही प्रवेश करने के लिए का उपयोग कर लिया है. उस खाते के लिए अपना पासवर्ड डालें. -{plural_var,plural, =1{पासवर्ड पर्याप्त रूप से मजबूत नहीं है. कम से कम वर्ण का उपयोग करें जो अक्षरों और नंबरों दोनों को मिलाकर बना हो}one{पासवर्ड पर्याप्त रूप से मजबूत नहीं है. कम से कम वर्णों का उपयोग करें जो अक्षरों और नंबरों दोनों को मिलाकर बना हो}other{पासवर्ड पर्याप्त रूप से मजबूत नहीं है. कम से कम वर्णों का उपयोग करें जो अक्षरों और नंबरों दोनों को मिलाकर बना हो}} +{plural_var,plural, =1{पासवर्ड काफ़ी रूप से मजबूत नहीं है. कम से कम वर्ण का उपयोग करें जो अक्षरों और नंबरों दोनों को मिलाकर बना हो}one{पासवर्ड काफ़ी रूप से मजबूत नहीं है. कम से कम वर्णों का उपयोग करें जो अक्षरों और नंबरों दोनों को मिलाकर बना हो}other{पासवर्ड काफ़ी रूप से मजबूत नहीं है. कम से कम वर्णों का उपयोग करें जो अक्षरों और नंबरों दोनों को मिलाकर बना हो}} ज़िंबाब्वे +OAuth टोकन गायब होने, अमान्य होने या उसकी समय सीमा खत्म हो जाने की वजह से अनुरोध की पुष्टि नहीं की जा सकी. आपके फ़ोन नंबर की पुष्टि करने में एक समस्या हुई नए डिवाइस या ब्राउज़र का पता चला है + में साइन इन करें चेक गणराज्य तुवालू GitHub के साथ साइन इन करें @@ -426,6 +463,7 @@ बरमूडा नियुए ग्वाडेलोप +क्लाइंट ने प्रोजेक्ट का अमान्य कॉन्फ़िगरेशन डाला है. फ़िलिस्तीनी क्षेत्र स्लोवाकिया ग्वेर्नसे @@ -437,20 +475,25 @@ आइल ऑफ़ मैन मलेशिया कोकोस [कीलिंग] आइलैंड -अगर आप अभी भी अपना खाता कनेक्ट करना चाहते हैं, तो आपको उसी डिवाइस पर लिंक खोलना होगा, जिस पर आपने साइन-इन करना शुरू किया था. या फिर, इस डिवाइस से साइन इन करने के लिए 'जारी रखें' पर टैप करें. +अगर आप अब भी अपना खाता कनेक्ट करना चाहते हैं, तो आपको उसी डिवाइस पर लिंक खोलना होगा, जिस पर आपने साइन-इन करना शुरू किया था. या फिर, इस डिवाइस से साइन इन करने के लिए 'जारी रखें' पर टैप करें. +फिर कोशिश करें आप पहले को अपने ईमेल खाते से कनेक्ट करना चाहते थे, लेकिन आपने ऐसे किसी अलग डिवाइस पर लिंक खोला है, जहां आप साइन इन नहीं हैं. 0: में कोड फिर से भेजें मोरक्को +अगर आप इस डिवाइस को नहीं पहचानते, तो हाे सकता है कि कोई आपके खाते काे ऐक्सेस करने की काेशिश कर रहा हाे. अभी अपना पासवर्ड बदलें. बांग्लादेश फ़ोन फ़ि‍नलैंड संयुक्त अरब अमीरात +आपके ईमेल पते की पुष्टि की जा चुकी है और इसे बदल दिया गया है पासवर्ड मध्य अफ़्रीकी गणराज्य कुराकाओ +असामान्य गतिविधि की वजह से हमने इस डिवाइस से सभी अनुरोध ब्लॉक कर दिए हैं. कुछ देर बाद कोशिश करें. ट्यूनीशिया पासवर्ड बदल गया यूनान +पुष्टि करने का दूसरा चरण हटाया गया अंगोला कुवैत आपने जो ईमेल और पासवर्ड डाला है, उनका मिलान नहीं हो रहा है. diff --git a/translations/hr.xtb b/translations/hr.xtb index 934faadf..0fdd1337 100644 --- a/translations/hr.xtb +++ b/translations/hr.xtb @@ -12,11 +12,14 @@ Filipini Rumunjska Južna Georgija i Južni Sendvički Otoci -U redu +Nepovratni gubitak podataka ili oštećenje podataka. Italija Libanon Gvajana Gvatemala +Došlo je do pogreške pri uklanjanju drugog faktora.Pokušajte ga ponovo ukloniti. Ako time ne riješite problem, zatražite pomoć od podrške. +Pokušajte ponovo ažurirati e-adresu +Došlo je do mrežne pogreške. Pokušajte ponovo kasnije. Unesite važeći telefonski broj Dodirivanjem opcije "Potvrdi" potvrđujete da prihvaćate naše Uvjete pružanja usluge i Pravila o privatnosti. Možda ćemo vam poslati SMS. Mogu se primijeniti naknade za slanje poruke i podatkovni promet. Unesite zaporku @@ -27,6 +30,7 @@ S vaše je IP adrese došlo previše zahtjeva za izradu računa. Pokušajte ponovo za nekoliko minuta. Šri Lanka Sada se možete prijaviti novim računom +Klijent je naveo nevažeći argument. Čad Ponovno slanje koda za Bjelorusija @@ -62,6 +66,7 @@ Github Pravila o privatnosti Guam +Premašena je kvota resursa ili se približava ograničenje stope. Hrvatska Litva Malta @@ -71,22 +76,28 @@ Gost Gana Preglednik koji upotrebljavate ne podržava web-pohranu. Pokušajte ponovo u drugom pregledniku. +Klijent je otkazao zahtjev. Došlo je do problema pri vraćanju vaše e-adrese za prijavu.Ako ni nakon ponovnog pokušaja ne uspijete vratiti e-adresu, zatražite pomoć od administratora. Bahrein +Interna pogreška poslužitelja. Iran +GitHub Potvrđujemo da niste robot... Kôd više nije važeći Nova Kaledonija Monako Sejšeli +Uklonjen je drugi korak autentifikacije . Trenutačni anonimni korisnik ne može izvršiti nadogradnju. Neanonimna vjerodajnica već je povezana s drugim korisničkim računom. Unijeli ste netočnu zaporku previše puta. Pokušajte ponovo za nekoliko minuta. Sigurnost Vaš je zahtjev za ponovno postavljanje zaporke istekao ili je veza iskorištena +Sada se možete prijaviti pomoću nove e-adrese . Navedeni kôd države nije podržan. Provjerite jeste li točno napisali e-adresu. Twitter Kambodža +Usluga nije dostupna. Portugal Francuska Gvajana Britanski Djevičanski otoci @@ -102,19 +113,21 @@ Potvrdi Južnoafrička Republika Unesite svoju e-adresu za nastavak +Administrator je onemogućio korisnički račun. E-poruka za prijavu s dodatnim uputama poslana je na adresu . Provjerite e-poštu da biste dovršili prijavu. Izrada računa Afganistan Dodirivanjem gumba potvrđujete da prihvaćate odredbe koje sadrže . Kanada Danska +Prekoračen je rok za zahtjev. Austrija E-adrese se ne podudaraju Južni Sudan Jemen Gvineja Bisau Saudijska Arabija -Logotip aplikacije +U aplikaciji dodan je dodatni sigurnosni korak Američki Djevičanski otoci Prijava putem telefona Indonezija @@ -122,6 +135,7 @@ Venezuela Problem s prijavom? Potvrdite svoj identitet +Pri provjeri autentičnosti vašeg zahtjeva došlo je do problema. Ponovo otvorite URL koji vas je preusmjerio na ovu stranicu da biste ponovo pokrenuli postupak provjere autentičnosti. Dominika Nova zaporka Gabon @@ -139,6 +153,7 @@ Gambija Gotovo Odustani +Nepoznata pogreška poslužitelja. Unesite naziv računa Irak Vijetnam @@ -150,6 +165,8 @@ Nepal Tokelau Odabrana vjerodajnica za davatelja usluge autentifikacije nije podržana! +Ne postoji mogućnost prijave za navedenu e-adresu. Pokušajte upotrijebiti drugu e-adresu. +Unesite ime i prezime. Kolumbija Ako dodirnete SPREMI, prihvaćate dokument Burundi @@ -157,7 +174,7 @@ Twitter Vaša je e-adresa potvrđena Papua Nova Gvineja -Spremi +Navedeni resurs nije pronađen. Potvrđivanje… Prijava Istočni Timur @@ -190,6 +207,7 @@ Telefon Ponovo pošalji Honduras +Operacija je istekla. Niger Ta e-adresa ne pripada postojećem računu Jordan @@ -201,6 +219,7 @@ Izrael Došlo je do pogreške Broj +Klijent nema dovoljnu razinu dopuštenja. Riješite reCAPTCHA test Nikaragva Burkina Faso @@ -209,14 +228,17 @@ Otoci Turks i Caicos Ponovno slanje koda za Provjerite e-poštu +Nismo mogli ukloniti vaš drugi faktor Dodavanje zaporke Salvador +Da biste promijenili zaporku za račun, trebate se ponovo prijaviti. Srbija Belize za e-adresu Da biste ovim postupkom povezali svoj račun s tom e-adresom, trebate otvoriti vezu na istom uređaju ili pregledniku. Švedska Saint Martin +Vaš je zahtjev za potvrdu i ažuriranje e-adrese istekao ili je veza već upotrijebljena. Sveta Lucija @string/app_name Provjerite e-poštu @@ -224,13 +246,15 @@ Oman Karipski otoci Nizozemske E-adresa je ažurirana -Prijava putem Twittera +Uspješno ste odjavljeni. +Prijava putem telefona Telefonski broj Već imate račun Tajland Pravila o privatnosti Lihtenštajn Uređivanje naziva +Uređaj ili aplikacija uklonjena je kao drugi korak autentifikacije. Pokušajte otvoriti vezu s onog uređaja ili preglednika u kojem ste započeli postupak prijave. Nizozemska Ekvatorska Gvineja @@ -254,7 +278,7 @@ Unesite svoju e-adresu za nastavak Potvrda telefonskog broja Liberija -Račun s tom e-adresom već postoji. +Odjava Madagaskar Kazahstan Prijava... @@ -273,15 +297,20 @@ Unesite -znamenkasti kôd koji smo poslali na broj Taj telefonski broj upotrijebljen je previše puta Navedena e-adresa ne odgovara trenutačnoj sesiji prijave. -Prekidanje veze s računom +Twitter + +Prijavite se u </p></p> + Provjerite ima li dovoljno prostora u vašem pretincu pristigle pošte ili je došlo do drugih problema povezanih s postavkama pretinca pristigle pošte. +Klijent je naveo nevažeći raspon. +Premašena je kvota za tu operaciju. Pokušajte ponovo kasnije. Paragvaj Alžir Unijeli ste netočnu zaporku previše puta. Pokušajte ponovo za nekoliko minuta. Otok Norfolk Telefonski je broj automatski potvrđen -Da biste promijenili zaporku, najprije trebate unijeti trenutačnu zaporku. -Natrag +Pogreška na mreži; provjerite internetsku vezu. +Potvrda je uspjela! Indija Benin Nastavi @@ -330,27 +359,33 @@ E-adresa Dalje Kôd više nije važeći -Prijava putem Facebooka +E-poruka za prijavu s dodatnim uputama poslana je na adresu . Provjerite e-poštu da biste dovršili prijavu. Potvrdite e-adresu Ime i prezime Granada Zapadna Sahara +Zahtjev se ne može izvršiti u trenutačnom stanju sustava. Došlo je do nepoznate pogreške. Za prijavu ste već upotrijebili e-adresu . Unesite zaporku za taj račun. +E-poruka za prijavu poslana Irska Demokratska Republika Kongo Ponovno postavljanje zaporke Republika Kongo +Resurs koji je klijent pokušao izraditi već postoji. Nastavi kao gost San Marino Cipar Salomonski Otoci Želite li se nastaviti prijavljivati putem e-adrese ? +Ako želite nastaviti s prijavom na ovom uređaju pomoću e-adrese , trebate oporaviti zaporku. Maldivi Sveti Toma i Princip Potvrdite svoju e-adresu da biste dovršili prijavu Slovenija Južna Koreja +Sukob istovremenih radnji, npr. sukob između čitanja, izmjene ili zapisivanja. +API metoda nije implementirana na poslužitelju. Lesoto Prijava putem telefona Pokušajte ponovo potvrditi e-adresu @@ -370,8 +405,10 @@ Unesite zaporku za taj račun. {plural_var,plural, =1{Zaporka nije dovoljno jaka. Upotrijebite najmanje znak i koristite kombinaciju slova i znamenki.}one{Zaporka nije dovoljno jaka. Upotrijebite najmanje znak i koristite kombinaciju slova i znamenki.}few{Zaporka nije dovoljno jaka. Upotrijebite najmanje znaka i koristite kombinaciju slova i znamenki.}other{Zaporka nije dovoljno jaka. Upotrijebite najmanje znakova i koristite kombinaciju slova i znamenki.}} Zimbabve +Nije izvršena autentifikacija zahtjeva jer OAuth token nije prisutan, nije važeći ili je istekao. Došlo je do problema s potvrdom vašeg telefonskog broja. Pronađen je novi uređaj ili preglednik +Prijavite se na uslugu Češka Republika Tuvalu Prijava putem GitHuba @@ -426,6 +463,7 @@ Bermudi Niue Guadalupe +Klijent je naveo nevažeću konfiguraciju projekta. Palestinski teritoriji Slovačka Guernsey @@ -438,19 +476,24 @@ Malezija Kokosovi (Keelingovi) otoci Ako i dalje želite povezati svoj račun, otvorite vezu na uređaju na kojem ste započeli s prijavom. U suprotnom, dodirnite Nastavak da biste se prijavili na ovom uređaju. +Pokušaj ponovo Izvorno ste namjeravali povezati račun sa svojim računom e-pošte, no otvorili ste vezu na drugom uređaju na kojem niste prijavljeni. Ponovno slanje koda za 0: Maroko +Ako ne prepoznajete taj uređaj, to možda znači da netko pokušava pristupiti vašem računu. Razmislite o tome da odmah promijenite zaporku. Bangladeš Telefon Finska Ujedinjeni Arapski Emirati +Vaša je e-adresa potvrđena i promijenjena Zaporka Srednjoafrička Republika Curaçao +Blokirali smo sve zahtjeve s tog uređaja zbog neuobičajene aktivnosti. Pokušajte ponovo kasnije. Tunis Zaporka je promijenjena Grčka +Drugi je faktor uklonjen Angola Kuvajt Unijeli ste e-adresu i zaporku koje se ne podudaraju diff --git a/translations/hu.xtb b/translations/hu.xtb index 0a4cf2c6..f8cda229 100644 --- a/translations/hu.xtb +++ b/translations/hu.xtb @@ -12,11 +12,14 @@ Fülöp-szigetek Románia Dél-Georgia és Déli-Sandwich-szigetek -Rendben +Végleges adatvesztés vagy az adatok helyrehozhatatlan sérülése Olaszország Libanon Guyana Guatemala +A második lépcső eltávolítása során hiba történt.Próbálja meg ismét eltávolítani. Ha nem sikerül, kérjen segítséget az ügyfélszolgálattól. +Próbálja újra frissíteni az e-mail-címét +Hálózati hiba történt. Próbálja meg újra később. Érvényes telefonszámot adjon meg. Az Ellenőrzés gombra való koppintással elfogadja Általános Szerződési Feltételeinket és Adatvédelmi irányelveinket. A rendszer SMS-t küldhet Önnek. A szolgáltató ezért üzenet- és adatforgalmi díjat számíthat fel. Adja meg a jelszót. @@ -27,6 +30,7 @@ Az Ön IP-címéről túl sok fiókkérelem érkezik. Néhány perc múlva próbálja újra. Srí Lanka Most már bejelentkezhet új fiókjával. +Az ügyfél érvénytelen argumentumot adott meg. Csád Kód újraküldése ennyi idő elteltével: Belarusz @@ -62,6 +66,7 @@ Github Adatvédelmi irányelvek Guam +Elfogyott az erőforráshoz rendelkezésre álló kvóta, vagy hamarosan eléri a megengedett értéket. Horvátország Litvánia Málta @@ -71,22 +76,28 @@ Vendég Ghána A böngészője nem támogatja internetes tárhely használatát. Kérjük, egy másik böngészővel próbálja újra. +Az ügyfél visszavonta a kérelmet. Probléma lépett fel bejelentkezési e-mail-címének visszamódosítása során.Ha ismételt próbálkozást követően sem sikerül visszaállítania az e-mail-címét, kérjen segítséget a rendszergazdától. Bahrein +Belső szerverhiba. Irán +GitHub Annak ellenőrzése, hogy Ön nem robot... Ez a kód már nem érvényes. Új-Kaledónia Monaco Seychelle-szigetek +A hitelesítés második lépéseként eltávolítottuk a következőt: (). A jelenlegi anonim felhasználó frissítése nem sikerült. A nem anonim hitelesítési adat már egy másik felhasználói fiókhoz van társítva. Túl sokszor adott meg hibás jelszót. Néhány perc múlva próbálja újra. Biztonság A jelszó-visszaállítási kérelem lejárt, vagy pedig már használták a linket. +Most már bejelentkezhet új e-mail-címével (). A megadott országkód nem támogatott. Ellenőrizze, hogy helyesen adta-e meg az e-mail-címét. Twitter Kambodzsa +A szolgáltatás nem áll rendelkezésre. Portugália Francia Guiana Brit Virgin-szigetek @@ -102,19 +113,21 @@ Ellenőrzés Dél-Afrika A folytatáshoz adja meg e-mail-címét. +A felhasználói fiókot egy adminisztrátor letiltotta. A további utasításokat tartalmazó bejelentkezési e-mailt elküldtük a(z) e-mail-címre. A bejelentkezés befejezéséhez keresse meg e-mailek között. Fiók létrehozása Afganisztán Ha a(z) gombra koppint, az azt jelenti, hogy elfogadja a következőt: . Kanada Dánia +Túllépte a kérelem határidejét. Ausztria Az e-mail-címek nem egyeznek. Dél-Szudán Jemen Bissau-Guinea Szaúd-Arábia -Alkalmazás emblémája +További biztonsági elem került hozzáadásra a(z) alkalmazásban Amerikai Virgin-szigetek Bejelentkezés telefonnal Indonézia @@ -122,6 +135,7 @@ Venezuela Problémái vannak a bejelentkezéssel? Igazolja, hogy Ön az +A kérelem hitelesítésekor probléma merült fel. A hitelesítési folyamat újrakezdéséhez kérjük, hogy nyissa meg ismét azt az URL-t, amely Önt erre az oldalra irányította. Dominika Új jelszó Gabon @@ -139,6 +153,7 @@ Gambia Kész Mégse +Ismeretlen szerverhiba. Adja meg fiókja nevét. Irak Vietnam @@ -150,6 +165,8 @@ Nepál Tokelau-szigetek A hitelesítésszolgáltató megadott hitelesítési adatai nem támogatottak. +Az adott e-mail-cím esetén nem áll rendelkezésre bejelentkezési szolgáltató; kérjük, próbálkozzon másik e-mail-címmel. +Írja be utónevét és vezetéknevét. Kolumbia Ha a MENTÉS lehetőségre koppint, az azt jelenti, hogy elfogadja a következőket: Burundi @@ -157,7 +174,7 @@ Twitter E-mail-címét ellenőriztük Pápua Új-Guinea -Mentés +A megadott forrás nem található. Ellenőrzés… Bejelentkezés Kelet-Timor @@ -190,6 +207,7 @@ Telefon Újraküldés Honduras +A művelet túllépte a megengedett időtartamot. Niger Ehhez az e-mail-címhez egyetlen meglévő fiók sem tartozik. Jordánia @@ -201,6 +219,7 @@ Izrael Hiba történt Szám +Az ügyfél nem rendelkezik megfelelő jogosultsággal. A reCAPTCHA megoldása után próbálja újra Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Turks- és Caicos-szigetek Kód újraküldése ennyi idő elteltével: Ellenőrizze az e-mailjeit. +Nem sikerült eltávolítani a második lépést Jelszó hozzáadása Salvador +Fiókja jelszavának módosításához újra be kell jelentkeznie. Szerbia Beliz a következő e-mail-címhez: () -fiójának és az e-mail-címnek az összekapcsolásához a linket ugyanazon az eszközön vagy ugyanabban a böngészőben kell megnyitni. Svédország Szent Márton-sziget +Az e-mail-címe igazolására és frissítésére vonatkozó kérelem lejárt, vagy már használták a linket. Saint Lucia @string/app_name Ellenőrizze az e-mailjeit. @@ -224,13 +246,15 @@ Omán Holland Karib-térség E-mail-cím frissítése -Bejelentkezés Twitter-fiókkal +Sikeresen kijelentkezett. +Bejelentkezés telefonnal Telefonszám Már rendelkezik fiókkal Thaiföld Adatvédelmi irányelvek Liechtenstein Név szerkesztése +A hitelesítés második lépéseként eltávolítottuk az eszközt vagy az alkalmazást. Ugyanazzal az eszközzel vagy böngészővel nyissa meg a linket, amellyel elkezdte a bejelentkezési műveletet. Hollandia Egyenlítői-Guinea @@ -254,7 +278,7 @@ A folytatáshoz adja meg e-mail-címét. Telefonszám igazolása Libéria -Ezzel az e-mail-címmel már létezik fiók. +Kijelentkezés Madagaszkár Kazahsztán Bejelentkezés... @@ -273,15 +297,20 @@ Adja meg a(z) számjegyű kódot, melyet ide küldtünk: Ezt a telefonszámot már túl sokszor használták. A megadott e-mail-cím nem egyezik meg az aktuális bejelentkezési munkamenetben szereplővel. -Fiók leválasztása +Twitter + +Jelentkezzen be a(z) alkalmazásba</p></p> + Ellenőrizze, hogy a beérkező leveleket tartalmazó mappa nem telt-e meg, vagy nincs-e más, a mappával kapcsolatos probléma. +Az ügyfél érvénytelen tartományt adott meg. +Túllépte a műveletre vonatkozó kvótát. Próbálja meg újra később. Paraguay Algéria Túl sokszor adott meg hibás jelszót. Kérjük, néhány perc múlva próbálja újra. Norfolk-sziget A telefonszám automatikusan ellenőrizve -A jelszó módosításához előbb meg kell adnia a jelenlegi jelszavát. -Vissza +Hálózati hiba; ellenőrizze az internetkapcsolatot. +Ellenőrzött! India Benin Folytatás @@ -330,27 +359,33 @@ E-mail Következő Ez a kód már nem érvényes. -Bejelentkezés Facebook-fiókkal +A további utasításokat tartalmazó bejelentkezési e-mailt elküldtük ide: . A bejelentkezés befejezéséhez keresse meg az e-mailjei között. E-mail-cím megerősítése Utónév és vezetéknév Grenada Nyugat-Szahara +A jelenlegi rendszerállapotban a kérelem nem teljesíthető. Ismeretlen hiba történt. Ezt az e-mail-fiókot () már használta bejelentkezésre. Adja meg a fiókhoz tartozó jelszót. +Elküldtük a bejelentkezési e-mailt Írország Kongói Demokratikus Köztársaság Jelszó visszaállítása Kongói Köztársaság +Egy ügyfél által létrehozni kívánt erőforrás már létezik. Folytatás vendégként San Marino Ciprus Salamon-szigetek Folytatja a következővel: ? +Ha folytatni kívánja a(z) e-mail-címmel való bejelentkezést ezen az eszközön, akkor vissza kell állítania a jelszót. Maldív-szigetek São Tomé és Príncipe A bejelentkezés befejezéséhez erősítse meg az e-mail-címét Szlovénia Dél-Korea +Egyidejűségi ütközés, pl. olvasási–módosítási–írási ütközés. +A szerver nem alkalmazott API-módszert. Lesotho Bejelentkezés telefonnal Próbálja újra igazolni az e-mail-címét @@ -370,8 +405,10 @@ bejelentkezésre. Adja meg a fiókhoz tartozó jelszót. {plural_var,plural, =1{A jelszó nem elég erős. Legalább karaktert használjon, amelyben betűk és számok is szerepeljenek.}other{A jelszó nem elég erős. Legalább karaktert használjon, amelyben betűk és számok is szerepeljenek.}} Zimbabwe +Hiányzó, érvénytelen vagy lejárt OAuth-token miatt a kérelem nem hitelesíthető. Hiba történt a telefonszám ellenőrzésekor. A rendszer új eszközt vagy böngészőt észlelt +Jelentkezzen be a(z) alkalmazásba Csehország Tuvalu Bejelentkezés GitHubbal @@ -426,6 +463,7 @@ Bermuda-szigetek Niue Guadeloupe +Az ügyfél érvénytelen projektbeállítást adott meg. Palesztin Területek Szlovákia Guernsey @@ -438,19 +476,24 @@ Malajzia Kókusz (Keeling)-szigetek Ha továbbra is szeretné összekapcsolni -fiókját, akkor azon az eszközön nyissa meg a linket, amelyen megkezdte a bejelentkezést. Ha ezen az eszközön kíván bejelentkezni, akkor koppintson a Folytatás gombra. +Újra -fiókját eredetileg az e-mail-fiókjával kívánta összekapcsolni, a linket azonban nem azon az eszközön nyitotta meg, amelyen bejelentkezett. Kód újraküldése ennyi idő elteltével: 0: Marokkó +Ha nem ismeri fel ezt az eszközt, előfordulhat, hogy valaki megpróbált belépni a fiókjába. Érdemes lenne azonnal megváltoztatnia jelszavát. Banglades Telefon Finnország Egyesült Arab Emírségek +E-mail-címe sikeresen igazolva és frissítve Jelszó Közép-afrikai Köztársaság Curaçao +Szokatlan tevékenység miatt az erről az eszközről küldött összes kérelmet letiltottuk. Próbálja meg újra később. Tunézia Módosult jelszó Görögország +Második lépcső eltávolítva Angola Kuvait A megadott e-mail-cím és jelszó nem egyezik. diff --git a/translations/id.xtb b/translations/id.xtb index c22976e8..3bf959c4 100644 --- a/translations/id.xtb +++ b/translations/id.xtb @@ -12,13 +12,16 @@ Filipina Rumania Georgia Selatan dan Kepulauan Sandwich Selatan -Oke +Kehilangan data atau kerusakan data yang tidak dapat dipulihkan. Italia Lebanon Guyana Guatemala +Terjadi masalah saat menghapus faktor kedua Anda.Coba hapus lagi. Jika belum berhasil, hubungi dukungan untuk mendapatkan bantuan. +Coba perbarui email Anda lagi +Telah terjadi error pada jaringan. Coba lagi nanti. Masukan nomor telepon yang valid -Dengan menge-tap Verifikasi, Anda menyatakan bahwa Anda menyetujui Persyaratan Layanan dan Kebijakan Privasi kami. SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan &amp; data. +Dengan mengetuk Verifikasi, Anda menyatakan bahwa Anda menyetujui Persyaratan Layanan dan Kebijakan Privasi kami. SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan &amp; data. Masukkan sandi Anda Selesai Haiti @@ -27,14 +30,15 @@ Terlalu banyak permintaan akun yang berasal dari alamat IP Anda. Coba beberapa menit lagi. Sri Lanka Sekarang Anda dapat login dengan akun baru +Klien menentukan ada argumen yang tidak valid. Cad Kirirmkan kembali kode dalam Belarus Alamat email tidak cocok dengan akun yang ada. -Dengan menge-tap "", SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan & data. +Dengan mengetuk "", SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan & data. Saint Helena Barbados -Dengan menge-tap Lanjutkan, berarti Anda menyetujui Persyaratan Layanan +Dengan mengetuk Lanjutkan, berarti Anda menyetujui Persyaratan Layanan Armenia Bosnia-Herzegovina Kepulauan Cook @@ -62,6 +66,7 @@ Github Kebijakan Privasi Guam +Kuota resource telah habis atau mencapai pembatasan kapasitas. Kroasia Lituania Malta @@ -71,22 +76,28 @@ Tamu Ghana Browser yang Anda gunakan tidak mendukung Penyimpanan Web. Coba lagi dengan browser berbeda. +Permintaan dibatalkan oleh klien. Ada masalah saat mengubah kembali email login Anda.Jika Anda sudah mencoba lagi namun masih tidak dapat menyetel ulang email, coba minta bantuan administrator. Bahrain +Error server internal. Iran +GitHub Memverifikasi bahwa Anda bukan robot... Kode ini sudah tidak valid Kaledonia Baru Monako Republik Seychelles + dihapus sebagai langkah autentikasi kedua. Pengguna anonim saat ini gagal melakukan upgrade. Kredensial non-anonim tersebut telah dikaitkan dengan akun pengguna lain. Anda sudah terlalu sering memasukkan sandi yang salah. Coba beberapa menit lagi. Keamanan Permintaan untuk menyetel ulang sandi sudah tidak berlaku atau link telah digunakan +Sekarang Anda dapat login dengan email baru . Kode negara yang dimasukkan tidak didukung. Pastikan Anda tidak salah mengeja email Anda. Twitter Kamboja +Layanan tidak tersedia. Portugal Guyana Perancis Kepulauan Virgin Inggris @@ -102,20 +113,22 @@ Verifikasi Afrika Selatan Masukkan alamat email Anda untuk melanjutkan +Akun pengguna telah dinonaktifkan oleh administrator. Email login dengan petunjuk tambahan dikirim ke . Periksa email Anda untuk menyelesaikan proses login. Masukkan sandi Buat akun Afganistan -Dengan menge-tap Anda menyatakan setuju dengan . +Dengan mengetuk Anda menyatakan setuju dengan . Kanada Denmark +Batas waktu permintaan terlampaui. Austria Email tidak cocok Sudan Selatan Yaman Guinea-Bissau Arab Saudi -Logo aplikasi +Faktor keamanan tambahan telah ditambahkan di Kepulauan Virgin Amerika Serikat Login dengan ponsel Indonesia @@ -123,6 +136,7 @@ Venezuela Terjadi masalah saat login? Verifikasi bahwa itu Anda +Terjadi kesalahan saat mengautentikasi permintaan Anda. Buka URL yang mengarahkan Anda ke halaman ini lagi untuk memulai ulang proses autentikasi. Dominika Sandi baru Gabon @@ -140,6 +154,7 @@ Gambia Selesai Batal +Terjadi error yang tidak diketahui pada server. Masukkan nama akun Anda Irak Vietnam @@ -151,14 +166,16 @@ Nepal Tokelau Kredensial yang dipilih untuk penyedia autentikasi tersebut tidak didukung! +Tidak ada penyedia login yang tersedia untuk email tersebut. Harap coba dengan email lain. +Masukkan nama depan dan belakang. Kolombia -Dengan menge-tap SIMPAN, Anda menunjukkan bahwa Anda menyetujui +Dengan mengetuk SIMPAN, Anda menunjukkan bahwa Anda menyetujui Burundi Mauritania Twitter Email Anda telah diverifikasi Papua Nugini -Simpan +Resource yang ditentukan tidak ditemukan. Memverifikasi... Login Timor Leste @@ -191,6 +208,7 @@ Ponsel Kirim ulang Honduras +Waktu operasi sudah habis. Niger Alamat email tidak cocok dengan akun yang ada Yordania @@ -202,6 +220,7 @@ Israel Terjadi error Nomor +Klien tidak memiliki izin yang memadai. Pecahkan reCAPTCHA Nikaragua Burkina Faso @@ -210,14 +229,17 @@ Kepulauan Turks dan Caicos Kirim ulang kode dalam Periksa email Anda +Tidak dapat menghapus faktor kedua Anda Tambahkan sandi El Salvador +Untuk mengubah sandi akun, Anda harus login kembali. Serbia Belize untuk Agar alur ini berhasil menghubungkan akun Anda dengan email ini, Anda harus membuka link di perangkat atau browser yang sama. Swedia Saint Martin +Permintaan untuk memverifikasi dan memperbarui email Anda tidak berlaku lagi atau link telah digunakan. Santa Lusia @string/app_name Periksa email Anda @@ -225,13 +247,15 @@ Oman Belanda Karibia Alamat email diupdate -Login dengan Twitter +Anda telah berhasil logout. +Login dengan nomor telepon Nomor telepon Anda sudah memiliki akun Thailand Kode salah. Coba lagi. Liechtenstein Edit nama +Perangkat atau aplikasi dihapus sebagai langkah autentikasi kedua. Coba buka link menggunakan perangkat atau browser yang sama tempat Anda memulai proses login. Belanda Guinea Ekuatoral @@ -255,7 +279,7 @@ Masukkan alamat email Anda untuk melanjutkan Verifikasi nomor telepon Anda Liberia -Sudah ada akun dengan alamat email tersebut. +Logout Madagaskar Kazakhstan Login... @@ -274,15 +298,20 @@ Masukkan kode digit yang kami kirimkan Nomor telepon ini sudah terlalu sering digunakan Email yang diberikan tidak sama dengan sesi login saat ini. -Batalkan tautan akun +Twitter + +Login ke </p></p> + Pastikan masih ada ruang untuk kotak masuk Anda, atau periksa masalah terkait setelan kotak masuk lainnya. +Klien menentukan ada rentang yang tidak valid. +Kuota untuk operasi ini telah terlampaui. Coba lagi nanti. Paraguay Aljazair Anda telah berkali-kali memasukkan sandi yang salah. Coba beberapa menit lagi. Pulau Norfolk Nomor telepon terverifikasi secara otomatis -Untuk mengubah sandi, Anda harus memasukkan sandi saat ini terlebih dahulu. -Kembali +Error jaringan, periksa koneksi internet Anda. +Terverifikasi! India Benin Lanjutkan @@ -295,7 +324,7 @@ Anda tidak dapat lagi login menggunakan akun Anda Periksa koneksi internet Anda. Mendaftar… -Dengan menge-tap , SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan & data. +Dengan mengetuk , SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan & data. Vanuatu Samoa Amerika Brasil @@ -331,27 +360,33 @@ Email Berikutnya Kode ini sudah tidak valid -Login dengan Facebook +Email login dengan petunjuk tambahan telah dikirim ke . Buka email Anda untuk menyelesaikan proses login. Konfirmasi email Nama depan &amp; belakang Grenada Sahara Barat +Permintaan tidak dapat dijalankan dalam status sistem saat ini. Terjadi error yang tidak diketahui. Anda telah menggunakan untuk login. Masukkan sandi Anda untuk akun tersebut. +Email Login Terkirim Irlandia Republik Demokratik Kongo Pulihkan sandi Republik Kongo +Resource yang klien coba buat sudah ada. Lanjutkan sebagai tamu San Marino Siprus Kepulauan Solomon Lanjutkan dengan ? +Untuk melanjutkan login dengan di perangkat ini, Anda harus memulihkan sandi. Maladewa São Tomé dan Príncipe Konfirmasi email Anda untuk menyelesaikan proses login Slovenia Korea Selatan +Konflik serentak, seperti konflik baca-ubah-tulis. +Metode API tidak diimplementasikan oleh server. Lesotho Login dengan nomor telepon Coba verifikasi email Anda lagi @@ -371,9 +406,11 @@ untuk login. Masukkan sandi untuk akun tersebut. {plural_var,plural, =1{Sandi tidak cukup kuat. Gunakan minimal karakter dan merupakan kombinasi huruf dan angka}other{Sandi tidak cukup kuat. Gunakan minimal karakter dan merupakan kombinasi huruf dan angka}} Zimbabwe -Ikuti petunjuk yang dikirim ke untuk memulihkan sandi Anda. -Ada masalah saat memverifikasi nomor telepon Anda +Permintaan tidak diautentikasi karena token OAuth tidak ada, tidak valid, atau sudah tidak berlaku. +Masukkan kode verifikasi telepon 6 digit. +Pilih sandi Perangkat atau browser baru terdeteksi +Login ke Republik Ceko Tuvalu Login dengan GitHub @@ -382,7 +419,7 @@ Google Kenya Tindakan ini akan menghapus semua data yang terkait dengan akun Anda, dan tidak dapat diurungkan. Yakin ingin menghapus akun? -Dengan menge-tap Verifikasi, SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan &amp; data. +Dengan mengetuk Verifikasi, SMS mungkin akan dikirim. Mungkin dikenakan biaya pesan &amp; data. Bolivia Puerto Riko Batalkan tautan akun? @@ -399,6 +436,10 @@ Spanyol Kosta Rika Samoa + +Akun Anda di sudah diupdate dengan untuk autentikasi dua langkah. +Jika Anda tidak meminta perubahan ini, gunakan link berikut untuk membatalkan perubahan. + Mali Qatar Uruguay @@ -429,6 +470,7 @@ Bermuda Niue Guadeloupe +Klien menentukan konfigurasi project yang tidak valid. Palestina Slovakia Guernsey @@ -440,20 +482,25 @@ Pulau Man Malaysia Kepulauan Cocos [Keeling] -Jika Anda masih ingin menghubungkan akun , buka link di perangkat yang sama yang digunakan untuk login. Atau tap Lanjutkan untuk login di perangkat ini. +Jika Anda masih ingin menghubungkan akun , buka link di perangkat yang sama yang digunakan untuk login. Atau ketuk Lanjutkan untuk login di perangkat ini. +Coba lagi Anda sebelumnya ingin menghubungkan ke akun email Anda, tapi telah membuka link di perangkat berbeda, yang tidak digunakan untuk login. Kirimkan ulang kode dalam 0. Maroko +Jika Anda tidak mengenali perangkat ini, mungkin ada seseorang yang berusaha untuk mengakses akun Anda. Pertimbangkan untuk segera mengubah sandi Anda. Bangladesh Telepon Finlandia Uni Emirat Arab +Email Anda telah diverifikasi dan diubah Sandi Republik Afrika Tengah Curaçao +Kami telah memblokir semua permintaan dari perangkat ini karena ada aktivitas yang tidak biasa. Coba lagi nanti. Tunisia Sandi diganti Yunani +Faktor kedua telah dihapus Angola Kuwait Email dan sandi yang Anda masukkan tidak cocok diff --git a/translations/it.xtb b/translations/it.xtb index 63dde9b0..c6d6e860 100644 --- a/translations/it.xtb +++ b/translations/it.xtb @@ -12,11 +12,14 @@ Filippine Romania Georgia del sud e Sandwich australi -OK +Perdita di dati non recuperabili o danneggiamento dei dati. Italia Libano Guiana Guatemala +Si è verificato un problema durante la rimozione del secondo fattore.Prova nuovamente a rimuoverlo. Se il problema persiste, contatta l'assistenza. +Prova di nuovo ad aggiornare l'email +Si è verificato un errore di rete. Riprova più tardi. Inserisci un numero di telefono valido Se tocchi Verifica, accetti i nostri Termini di servizio e le nostre Norme sulla privacy. È possibile che venga inviato un SMS. Potrebbero essere applicate le tariffe per l'invio dei messaggi e per il traffico dati. Inserisci la password @@ -27,6 +30,7 @@ Troppe richieste di account provenienti dal tuo indirizzo IP. Riprova tra qualche minuto. Sri Lanka Ora puoi accedere con il tuo nuovo account +Il client ha specificato un argomento non valido. Ciad Invia di nuovo il codice tra Bielorussia @@ -62,6 +66,7 @@ Github Norme sulla privacy Guam +Quota di risorse esaurita o vicina alla limitazione della frequenza. Croazia Lituania Malta @@ -71,22 +76,28 @@ Ospite Ghana Il browser in uso non supporta l'archiviazione web. Prova con un altro browser. +La richiesta è stata annullata dal client. Si è verificato un problema reimpostando l'indirizzo email precedente.Se ritenti e non riesci a reimpostarlo, prova a chiedere aiuto al tuo amministratore. Bahrein +Errore interno del server. Iran +GitHub Stiamo verificando che non sei un robot… Questo codice non è più valido Nuova Caledonia Monaco Seychelles +Il fattore è stato rimosso come secondo passaggio di autenticazione. Impossibile eseguire l'upgrade per l'utente anonimo attuale. La credenziale non anonima è già associata a un altro account utente. Hai effettuato troppi tentativi con una password errata. Riprova tra qualche minuto. Sicurezza La richiesta di reimpostazione della password è scaduta o il collegamento è già stato utilizzato +Ora puoi accedere con la nuova email: . Il codice paese fornito non è supportato. Verifica di avere digitato correttamente l'indirizzo email. Twitter Cambogia +Servizio non disponibile. Portogallo Guiana francese Isole Vergini britanniche @@ -102,6 +113,7 @@ Verifica Sud Africa Inserisci il tuo indirizzo email per continuare +L'account utente è stato disattivato da un amministratore. Un'email di accesso con ulteriori istruzioni è stata inviata all'indirizzo . Controlla la tua casella di posta per completare l'accesso. Inserisci la password Crea account @@ -109,13 +121,14 @@ Se tocchi , accetti i . Canada Danimarca +Scadenza richiesta superata. Austria Gli indirizzi email non corrispondono Sudan del Sud Yemen Guinea-Bissau Arabia Saudita -Logo dell'applicazione +Un ulteriore fattore di sicurezza è stato aggiunto a Isole Vergini americane Accedi con il telefono Indonesia @@ -123,6 +136,7 @@ Venezuela Problemi di accesso? Verifica che sei tu +Si è verificato un problema durante l'autenticazione della richiesta. Torna all'URL che ti ha reindirizzato a questa pagina per riavviare il processo di autenticazione. Dominìca Nuova password Gabon @@ -140,6 +154,7 @@ Gambia Fine Annulla +Errore sconosciuto del server. Inserisci il nome del tuo account Iraq Vietnam @@ -151,6 +166,8 @@ Nepal Tokelau La credenziale selezionata per il provider di autenticazione non è supportata. +Non sono disponibili metodi di accesso per l'indirizzo email specificato. Prova con un altro. +Inserisci un nome e un cognome. Colombia Se tocchi SALVA, accetti i Burundi @@ -158,7 +175,7 @@ Twitter Il tuo indirizzo email è stato verificato Papua Nuova Guinea -Salva +La risorsa specificata non è stata trovata. Verifica in corso… Accedi Timor Est @@ -191,6 +208,7 @@ Telefono Invia di nuovo Honduras +L'operazione è scaduta. Niger L'indirizzo email non corrisponde a un account esistente Giordania @@ -202,6 +220,7 @@ Israele Rilevato errore Numero +Il client non dispone di autorizzazioni sufficienti. Risolvi il reCAPTCHA Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Isole Turks e Caicos Invia di nuovo il codice tra Controlla la tua casella di posta +Impossibile rimuovere il secondo fattore Aggiungi password El Salvador +Per modificare la password del tuo account, devi accedere di nuovo. Serbia Belize per Per collegare correttamente l'account a questo indirizzo email, devi aprire il link sullo stesso dispositivo o browser. Svezia Saint Martin +La richiesta di verificare e aggiornare l'email è scaduta o il link è già stato utilizzato. Santa Lucia @string/app_name Controlla la tua casella di posta @@ -225,13 +247,15 @@ Oman Isole BES Indirizzo email aggiornato -Accedi con Twitter +Disconnessione completata. +Accedi con il telefono Numero di telefono Hai già un account Tailandia Codice errato. Riprova. Liechtenstein Modifica nome +Il dispositivo o l'app sono stati rimossi come secondo passaggio di autenticazione. Prova ad aprire il link utilizzando lo stesso dispositivo o browser dal quale hai iniziato la procedura di accesso. Paesi Bassi Guinea Equatoriale @@ -255,7 +279,7 @@ Inserisci il tuo indirizzo email per continuare Verifica il numero di telefono Liberia -Esiste già un account con questo indirizzo email. +Esci Madagascar Kazakistan Accesso in corso... @@ -274,15 +298,20 @@ Inserisci il codice a cifre che abbiamo inviato al numero Questo numero di telefono è stato usato troppe volte L'email fornita non corrisponde alla sessione di accesso corrente. -Scollega account +Twitter + +Accedi a </p></p> + Verifica che vi sia ancora spazio disponibile nella Posta in arrivo o che non vi siano altri problemi legati alle impostazioni della Posta in arrivo. +Il client ha specificato un intervallo non valido. +La quota per questa operazione è stata superata. Riprova più tardi. Paraguay Algeria Hai effettuato troppi tentativi con una password errata. Riprova tra qualche minuto. Isola Norfolk Numero di telefono verificato automaticamente -Per modificare la password, devi prima inserire la password corrente. -Indietro +Errore di rete. Controlla la connessione a Internet. +Verifica eseguita. India Benin Continua @@ -331,27 +360,33 @@ Indirizzo email Avanti Questo codice non è più valido -Accedi con Facebook +Un'email di accesso con ulteriori istruzioni è stata inviata all'indirizzo . Controlla la tua casella di posta per completare l'accesso. Conferma email Nome e cognome Grenada Sahara occidentale +La richiesta non può essere eseguita nello stato attuale del sistema. Si è verificato un errore sconosciuto. Hai già utilizzato per accedere. Inserisci la password di questo account. +Email di accesso inviata Irlanda Repubblica democratica del Congo Recupera la password Repubblica del Congo +La risorsa che un client ha cercato di creare esiste già. Continua come ospite San Marino Cipro Isole Salomone Vuoi continuare con ? +Per accedere con l'email su questo dispositivo, devi recuperare la password. Maldive São Tomé e Príncipe Conferma l'email per completare l'accesso Slovenia Corea del Sud +Conflitto di contemporaneità, ad esempio conflitto di lettura, modifica e scrittura. +Metodo API non implementato dal server. Lesotho Accedi con il telefono Prova a verificare di nuovo il tuo indirizzo email @@ -371,9 +406,11 @@ per accedere. Inserisci la password di questo account. {plural_var,plural, =1{La password non è abbastanza efficace. Utilizza almeno carattere e una combinazione di lettere e numeri.}other{La password non è abbastanza efficace. Utilizza almeno caratteri e una combinazione di lettere e numeri.}} Zimbabwe -Segui le istruzioni inviate all'indirizzo per recuperare la password. -Si è verificato un problema durante la verifica del tuo numero di telefono +La mancata autenticazione della richiesta è dovuta a un token OAuth mancante, non valido o scaduto. +Specifica un codice di verifica telefonica a 6 cifre. +Scegli password Rilevato nuovo dispositivo o browser +Accedi a Repubblica Ceca Tuvalu Accedi con GitHub @@ -399,6 +436,10 @@ Spagna Costa Rica Samoa + +Il tuo account è stato aggiornato con per l'autenticazione a due fattori. +Se non hai richiesto questa modifica, usa il link seguente per annullarla. + Mali Qatar Uruguay @@ -429,6 +470,7 @@ Bermuda Niue Guadalupa +Il client ha specificato una configurazione di progetto non valida. Territori palestinesi Slovacchia Guernsey @@ -441,19 +483,24 @@ Malesia Isole Cocos [Keeling] Se vuoi ancora collegare il tuo account , apri il link sullo stesso dispositivo sul quale hai iniziato a eseguire l'accesso. Altrimenti, tocca Continua per accedere su questo dispositivo. +Riprova Inizialmente intendevi collegare l'account al tuo account email, ma hai aperto il link su un altro dispositivo sul quale non hai eseguito l'accesso. Invia di nuovo il codice tra 0: Marocco +Se non riconosci questo dispositivo, è possibile che qualcuno stia cercando di accedere al tuo account. Ti consigliamo di modificare immediatamente la password. Bangladesh Telefono Finlandia Emirati Arabi Uniti +L'email è stata verificata e modificata Password Repubblica Centrafricana Curaçao +Abbiamo bloccato tutte le richieste da questo dispositivo a causa della sua attività insolita. Riprova più tardi. Tunisia Password modificata Grecia +Secondo fattore rimosso Angola Kuwait L’indirizzo email e la password non corrispondono diff --git a/translations/iw.xtb b/translations/iw.xtb index 5d747d8e..bbe83c47 100644 --- a/translations/iw.xtb +++ b/translations/iw.xtb @@ -12,11 +12,14 @@ הפיליפינים רומניה איי ג'ורג'יה הדרומית ואיי סנדוויץ' הדרומיים -אישור +פגם בנתונים או אובדן נתונים שלא ניתן לשחזר. איטליה לבנון גיאנה גוואטמלה +משהו השתבש במהלך ההסרה של הגורם השני.רוצה לנסות שוב? אם הבעיה לא נפתרת, ניתן לפנות לצוות התמיכה לקבלת עזרה. +כדאי לנסות לעדכן שוב את כתובת האימייל +אירעה שגיאת רשת. ניתן לנסות שוב מאוחר יותר. מספר הטלפון שהזנת לא תקין הקשה על 'אימות' תפורש כהסכמתך לתנאים ולהגבלות ולמדיניות הפרטיות שלנו. ייתכן שתישלח הודעת SMS. ייתכנו חיובים בגין שליחת הודעות ושימוש בנתונים. לא הזנת את הסיסמה @@ -27,6 +30,7 @@ קיבלנו יותר מדי בקשות ליצירת חשבון מכתובת ה-IP שלך. אפשר יהיה לנסות שוב בעוד כמה דקות. סרי לנקה עכשיו אפשר להיכנס באמצעות החשבון החדש +הלקוח ציין ארגומנט לא חוקי. צ'אד הקשה על "", תפורש כהסכמתך ל ול. ייתכן שתישלח הודעת SMS. ייתכנו חיובים בגין שליחת הודעות ושימוש בנתונים. בלרוס @@ -62,6 +66,7 @@ Github מדיניות פרטיות גואם +מכסת המשאבים מוצתה או שהגבלת הקצב של יצירת הבקשות תמוצה בקרוב. קרואטיה ליטא מלטה @@ -71,21 +76,27 @@ אורח גאנה הדפדפן שלך לא תומך באחסון באינטרנט. כדאי לנסות שוב בדפדפן אחר. +הבקשה בוטלה על ידי הלקוח. לא הצלחנו לבטל את העדכון של כתובת הכניסה לחשבון.אם ניסיון נוסף לא יפתור את הבעיה, אנחנו מציעים לבקש עזרה ממנהל המערכת. בחריין +שגיאת שרת פנימית. איראן +GitHub מוודא שאינך רובוט... הקוד הזה כבר לא בתוקף קלדוניה החדשה מונקו איי סיישל +הגורם הוסר כשלב אימות שני. המשתמש האנונימי הנוכחי לא הצליח לשדרג את החשבון לחשבון לא אנונימי. פרטי הכניסה לחשבון שאינו אנונימי כבר משויכים לחשבון משתמש אחר. הזנת סיסמה שגויה יותר מדי פעמים. אפשר יהיה לנסות שוב בעוד כמה דקות. הבקשה לאיפוס הסיסמה כבר לא בתוקף או שכבר נעשה שימוש בקישור +עכשיו אפשר להיכנס לחשבון באמצעות כתובת האימייל החדשה . קוד המדינה שהזנת אינו נתמך. יש לוודא שכתובת האימייל אויתה בצורה תקינה. Twitter קמבודיה +השירות אינו זמין. פורטוגל גיאנה הצרפתית איי הבתולה הבריטיים @@ -101,19 +112,21 @@ אמת דרום אפריקה כדי להמשיך, עליך להזין כתובת אימייל +חשבון המשתמש הושבת על ידי מנהל מערכת. אימייל לכניסה לחשבון עם הוראות נוספות נשלח אל . יש לבדוק את תיבת הדואר הנכנס כדי להשלים את תהליך הכניסה. יצירת חשבון אפגניסטן הקשה על תתפרש כהסכמה ל. קנדה דנמרק +המועד האחרון לשליחת הבקשה חלף. אוסטריה כתובות האימייל לא זהות דרום סודן תימן גינאה-ביסאו ערב הסעודית -הלוגו של האפליקציה +שלב אבטחה נוסף צורף לאפליקציה איי הבתולה של ארה"ב כניסה באמצעות הטלפון אינדונזיה @@ -121,6 +134,7 @@ ונצואלה לא מצליח להיכנס? אנחנו צריכים לאמת את זהותך +משהו השתבש במהלך אימות הבקשה. יש להיכנס שוב לכתובת ה-URL שהפנתה אותך לדף הזה כדי להתחיל מחדש את תהליך האימות. דומיניקה סיסמה חדשה גאבון @@ -138,6 +152,7 @@ גמביה סיום ביטול +שגיאת שרת לא ידועה. לא הזנת את שם החשבון עיראק וייטנאם @@ -149,13 +164,15 @@ נפאל טוקלאו פרטי הכניסה שנבחרו לספק האימות אינם נתמכים. +אין שיטת כניסה זמינה עבור כתובת האימייל הנתונה. יש לנסות כתובת אימייל אחרת. +יש להזין שם פרטי ושם משפחה. קולומביה בורונדי מאוריטניה Twitter אימתנו את כתובת האימייל שלך פפואה גינאה החדשה -שמור +המשאב שצוין לא נמצא. מאמת… כניסה מזרח טימור @@ -188,6 +205,7 @@ טלפון שלח שוב הונדורס +תם משך הזמן שהוקצב לפעולה. ניז'ר אין חשבון המשויך לכתובת האימייל הזו ירדן @@ -199,6 +217,7 @@ ישראל אירעה שגיאה מספר +ללקוח אין הרשאה מספיקה. לא הזנת את ה-reCAPTCHA ניקרגואה בורקינה פסו @@ -207,27 +226,32 @@ איי טורקס וקאיקוס שלח קוד חדש בעוד שלחנו לך אימייל +לא ניתן היה להסיר את הגורם השני הוספת סיסמה אל סלוודור +כדי לשנות את הסיסמה לחשבון, עליך להיכנס אליו. סרביה בליז לחשבון כדי לקשר את חשבון לכתובת האימייל הזו, צריך לפתוח את הקישור באותו מכשיר או דפדפן. שוודיה סנט מרטין +פג התוקף של בקשתך לאמת ולעדכן את כתובת האימייל, או שנעשה כבר שימוש בקישור. סנט לוצ'יה שלחנו לך אימייל הבקשה לאימות של כתובת האימייל כבר לא בתוקף או שכבר נעשה שימוש בקישור עומאן האיים הקריביים ההולנדיים כתובת האימייל עודכנה -כניסה באמצעות Twitter +יצאת מהחשבון בהצלחה. +כניסה באמצעות הטלפון מספר טלפון כבר יש לך חשבון תאילנד מדיניות פרטיות ליכטנשטיין עריכת שם +המכשיר או האפליקציה הוסרו כשלב אימות שני. מומלץ לפתוח את הקישור באמצעות אותו מכשיר או דפדפן שבהם התחלת את תהליך הכניסה. הולנד גינאה המשוונית @@ -251,7 +275,7 @@ כדי להמשיך, עליך להזין כתובת אימייל אימות של מספר הטלפון ליבריה -כבר קיים חשבון המשויך לכתובת האימייל הזו. +יציאה מדגסקר קזחסטן נכנס... @@ -270,15 +294,20 @@ הזן את הקוד בן הספרות ששלחנו למספר למספר הטלפון הזה כבר נשלחו יותר מדי קודים כתובת האימייל שסיפקת לא תואמת לזו שבה השתמשת בסשן הכניסה הנוכחי. -ביטול הקישור של החשבון +Twitter + +כניסה אל </p></p> + יש לוודא שהשטח הפנוי בתיבת הדואר הנכנס אינו עומד להסתיים, או לבדוק בעיות אחרות שקשורות להגדרות של תיבת הדואר הנכנס. +הלקוח ציין טווח לא חוקי. +חרגת מהמכסה שהוגדרה לפעולה זו. ניתן לנסות שוב מאוחר יותר. פרגוואי אלג'יריה הזנת סיסמה שגויה יותר מדי פעמים. אפשר יהיה לנסות שוב בעוד כמה דקות. האי נורפולק מספר הטלפון אומת באופן אוטומטי -כדי לשנות את הסיסמה, עליך להזין את הסיסמה הנוכחית. -הקודם +אירעה שגיאת רשת. יש לבדוק את החיבור לאינטרנט. +אומת. הודו בנין המשך @@ -327,27 +356,33 @@ אימייל הבא‏ הקוד הזה כבר לא בתוקף -כניסה באמצעות Facebook +אימייל לכניסה לחשבון עם הוראות נוספות נשלח אל . יש לבדוק את תיבת הדואר הנכנס כדי להשלים את תהליך הכניסה. אישור כתובת האימייל שם פרטי ומשפחה גרנדה סהרה המערבית +לא ניתן לבצע את הבקשה במצב הנוכחי של המערכת. אירעה שגיאה לא ידועה. כבר השתמשת בכתובת כדי להיכנס. עליך להזין את הסיסמה לחשבון המשויך אליה. +נשלח אימייל לכניסה לחשבון אירלנד הרפובליקה הדמוקרטית של קונגו שחזור הסיסמה קונגו +המשאב שהלקוח ניסה ליצור כבר קיים. המשך כאורח סן מרינו קפריסין איי שלמה להמשיך עם ? +כדי להמשיך את תהליך הכניסה עם כתובת האימייל במכשיר הזה, עליך לשחזר את הסיסמה. האיים המלדיביים סאו טומה ופרינסיפה יש לאשר את כתובת האימייל כדי להשלים את תהליך הכניסה סלובניה דרום קוריאה +התנגשות מסוג בו-זמניות (Concurrency), כגון התנגשות בפעולה read-modify-write (קריאה-שינוי-כתיבה). +שיטת ה-API לא הוטמעה על ידי השרת. לסוטו כניסה באמצעות הטלפון לא הצלחנו לאמת את כתובת האימייל @@ -367,8 +402,10 @@ כדי להיכנס. עליך להזין את הסיסמה לחשבון המשויך אליה. {plural_var,plural, =1{הסיסמה חלשה מדי. סיסמה חזקה היא סיסמה באורך תו אחד () לפחות שמכילה אותיות ומספרים}two{הסיסמה חלשה מדי. סיסמה חזקה היא סיסמה באורך תווים לפחות שמכילה אותיות ומספרים}many{הסיסמה חלשה מדי. סיסמה חזקה היא סיסמה באורך תווים לפחות שמכילה אותיות ומספרים}other{הסיסמה חלשה מדי. סיסמה חזקה היא סיסמה באורך תווים לפחות שמכילה אותיות ומספרים}} זימבבווה +הבקשה לא אומתה עקב אסימון OAuth חסר, לא חוקי או שתוקפו פג. אירעה בעיה באימות של מספר הטלפון זוהו מכשיר או דפדפן חדשים +כניסה אל צ'כיה טובאלו כניסה באמצעות GitHub @@ -423,6 +460,7 @@ ברמודה ניווה גוואדלופ +הלקוח ציין הגדרת תצורה לא חוקית עבור הפרויקט. שטחי הרשות הפלסטינית סלובקיה גרנזי @@ -435,19 +473,24 @@ מלזיה איי קוקוס (קילינג) בכל זאת רוצה לקשר את חשבון לחשבון שלך? אם כן, יש לפתוח את הקישור באותו מכשיר שבו התחלת את תהליך הכניסה. אם לא, יש להקיש על 'המשך' כדי להיכנס לחשבון במכשיר הזה. +ניסיון חוזר כוונתך המקורית הייתה לקשר את חשבון לחשבון שלך, אבל פתחת את הקישור במכשיר אחר שממנו לא נכנסת לחשבון. שולח קוד חדש בעוד :0 מרוקו +אם אינך מזהה את המכשיר הזה, ייתכן שמישהו מנסה לגשת לחשבון שלך. מומלץ להחליף את הסיסמה באופן מיידי. בנגלדש טלפון פינלנד איחוד האמירויות הערביות +כתובת האימייל שלך אומתה ושונתה סיסמה רפובליקת מרכז אפריקה קוראסאו +חסמנו את כל הבקשות מהמכשיר הזה עקב פעילות חריגה. ניתן לנסות שוב מאוחר יותר. תוניסיה הסיסמה שונתה יוון +הגורם השני הוסר אנגולה כוויית אין התאמה בין כתובת האימייל לסיסמה שהזנת diff --git a/translations/ja.xtb b/translations/ja.xtb index 333332c8..90567328 100644 --- a/translations/ja.xtb +++ b/translations/ja.xtb @@ -12,11 +12,14 @@ フィリピン ルーマニア 南ジョージア諸島および南サンドウィッチ諸島 -OK +復元できないデータ損失またはデータ破損です。 イタリア レバノン ガイアナ グァテマラ +2 つ目の認証要素の削除でエラーが発生しました。もう一度削除してみてください。それでも問題が解決しない場合は、サポートにお問い合わせください。 +もう一度メールアドレスを更新してみてください +ネットワーク エラーが発生しました。しばらくしてからもう一度実行してください。 有効な電話番号を入力してください [確認] をタップすると、利用規約プライバシー ポリシーに同意したことになり、SMS が送信されます。データ通信料がかかることがあります。 パスワードを入力してください @@ -27,6 +30,7 @@ この IP アドレスから多くのアカウント リクエストが送信されています。しばらくしてからもう一度お試しください。 スリランカ 新しいアカウントでログインできるようになりました +クライアントが無効な引数を指定しました。 チャド 後にコードを再送信 ベラルーシ @@ -62,6 +66,7 @@ GitHub プライバシー ポリシー グアム +リソース割り当てが不足しているか、レート制限に達しています。 クロアチア リトアニア マルタ @@ -71,22 +76,28 @@ ゲスト ガーナ お使いのブラウザは Web Storage に対応していません。他のブラウザでもう一度お試しください。 +リクエストはクライアントによってキャンセルされました。 ログイン メールアドレスを元に戻す際に問題が発生しました。再度試してもメールアドレスを再設定できない場合は、管理者にお問い合わせください。 バーレーン +内部サーバーエラーです。 イラン +GitHub ロボットによる操作でないことを確認しています... このコードは無効になりました ニューカレドニア モナコ セーシェル + は 2 つ目の認証手順から削除されました。 現在の匿名ユーザーをアップグレードできませんでした。この認証情報はすでに別の非匿名ユーザー アカウントに関連付けられています。 正しくないパスワードを何度も入力しています。しばらくしてからもう一度お試しください。 セキュリティ パスワードの再設定のリクエストの期限が切れたか、リンクがすでに使用されています +新しいメールアドレス()でログインできるようになりました。 指定した国コードはサポートされていません。 メールアドレスのスペルに誤りがないか確認する。 Twitter カンボジア +サービスを利用できません。 ポルトガル 仏領ギアナ 英領バージン諸島 @@ -102,6 +113,7 @@ 確認 南アフリカ 続行するにはメールアドレスを入力してください +ユーザー アカウントが管理者によって無効にされています。 詳細な説明を記載したログインメールを に送信しました。メールを確認してログインを完了してください。 パスワードを入力 アカウントの作成 @@ -109,13 +121,14 @@ [] をタップすると、 に同意したことになります。 カナダ デンマーク +リクエスト期限を超えました。 オーストリア メールアドレスが一致しません 南スーダン イエメン ギニアビサウ サウジアラビア -アプリのロゴ + に別のセキュリティ要素が追加されました。 米領バージン諸島 携帯電話を使用してログイン インドネシア @@ -123,6 +136,7 @@ ベネズエラ ログインできない場合 ご本人確認 +リクエストの認証時に問題が発生しました。このページにリダイレクトした URL に再度アクセスし、認証プロセスをもう一度行ってください。 ドミニカ国 新しいパスワード ガボン @@ -140,6 +154,7 @@ ガンビア 完了 キャンセル +不明なサーバーエラーです。 アカウント名を入力してください イラク ベトナム @@ -151,6 +166,8 @@ ネパール トケラウ 選択中の認証プロバイダの認証情報はサポートされていません。 +指定のメールアドレスで利用可能なログイン プロバイダはありません。別のメールアドレスでお試しください。 +姓と名を入力してください。 コロンビア [保存] をタップすると、次に同意したことになります: ブルンジ @@ -158,7 +175,7 @@ Twitter メールアドレスは確認済みです パプアニューギニア -保存 +指定されたリソースが見つかりません。 確認しています... ログイン 東ティモール @@ -170,7 +187,7 @@ コソボ 以下の一般的な解決方法をお試しください。 でログイン -ログイン プロセスを開始したときと同じ端末またはブラウザを使用してリンクを開いてみてください。 +ログイン プロセスを開始したときと同じデバイスまたはブラウザを使用してリンクを開いてみてください。 エストニア Facebook アルゼンチン @@ -191,6 +208,7 @@ 電話 再送信 ホンジュラス +オペレーションがタイムアウトしました。 ニジェール メールアドレスが既存のアカウントと一致しません ヨルダン @@ -202,6 +220,7 @@ イスラエル エラーが発生しました 数字 +クライアントに十分な権限がありません。 reCAPTCHA による確認を行ってください ニカラグア ブルキナファソ @@ -210,14 +229,17 @@ タークス カイコス諸島 後にコードを再送信 メールをご確認ください +2 つ目の認証要素を削除できませんでした パスワードの追加 エルサルバドル +アカウントのパスワードを変更するには、再度ログインする必要があります。 セルビア ベリーズ メールアドレス: -このフローで アカウントをこのメールアドレスに正常に接続するには、同じ端末またはブラウザでリンクを開く必要があります。 +このフローで アカウントをこのメールアドレスに正常に接続するには、同じデバイスまたはブラウザでリンクを開く必要があります。 スウェーデン セントマーチン島 +メールアドレスの確認と更新のリクエストの期限が切れたか、リンクがすでに使用されています。 セントルシア @string/app_name メールをご確認ください @@ -225,14 +247,16 @@ オマーン オランダ領カリブ メールアドレスを更新しました -Twitter でログイン +ログアウトが完了しました。 +電話番号でログイン 電話番号 アカウントをすでにお持ちです タイ コードが間違っています。もう一度お試しください。 リヒテンシュタイン 名前の編集 -ログイン プロセスを開始したときと同じ端末またはブラウザを使用してリンクを開いてみてください。 +デバイスまたはアプリが 2 つ目の認証手順から削除されました。 +ログイン プロセスを開始したときと同じデバイスまたはブラウザを使用してリンクを開いてみてください。 オランダ 赤道ギニア オーストラリア @@ -255,7 +279,7 @@ 続行するにはメールアドレスを入力してください 電話番号の確認 リベリア -このメールアドレスのアカウントがすでに存在します。 +ログアウト マダガスカル カザフスタン ログインしています... @@ -274,15 +298,20 @@ 送信された 桁のコードを入力してください この電話番号は何度も使用されています 指定されたメールが現在のログイン セッションと一致しません。 -アカウントのリンク解除 +Twitter + +以下のリンクから にログインしてください</p></p> + 受信トレイの容量不足や、設定関連のその他の問題がないか確認する。 +クライアントが無効な範囲を指定しました。 +このオペレーションの割り当てを超過しています。しばらくしてからもう一度実行してください。 パラグアイ アルジェリア 正しくないパスワードを何度も入力しています。しばらくしてからもう一度お試しください。 ノーフォーク島 電話番号は自動的に確認されました -パスワードを変更するには、まず現在のパスワードを入力する必要があります。 -戻る +ネットワーク エラー。インターネット接続を確認してください。 +確認済み インド ベナン 続行 @@ -331,27 +360,33 @@ メールアドレス 次へ このコードは無効になりました -Facebook でログイン +詳細な手順を記載したログインメールを に送信しました。メールを確認してログインを完了してください。 メールの確認 あなたの名前 グレナダ 西サハラ +現在のシステム状態ではリクエストを実行できません。 不明なエラーが発生しました。 すでに を使用してログインしています。このアカウントのパスワードを入力してください。 +ログインメールを送信しました アイルランド コンゴ民主共和国 パスワードの復元 コンゴ共和国 +クライアントが作成しようとしたリソースはすでに存在します。 ゲストとして続行 サンマリノ キプロス ソロモン諸島 で続行しますか? +このデバイスで によるログインを続行するには、パスワードを復元する必要があります。 モルディブ サントメ プリンシペ メールを確認してログインを完了してください スロベニア 韓国 +同時実行の競合(読み取り - 変更 - 書き込みの競合など)。 +API メソッドはサーバーによって実装されていません。 レソト 電話番号でログイン メールアドレスを再度確認してください @@ -370,9 +405,11 @@ すでに を使用してログインしています。このアカウントのパスワードを入力してください。 {plural_var,plural, =1{パスワードが十分に安全ではありません。 文字以上で、文字と数字を組み合わせたパスワードを使用してください}other{パスワードが十分に安全ではありません。 文字以上で、文字と数字を組み合わせたパスワードを使用してください}} ジンバブエ - に送信された手順に沿ってパスワードを復元します。 -電話番号の確認中に問題が発生しました -新しい端末またはブラウザが検出されました +OAuth トークンがない、もしくは無効、期限切れのためにリクエストが認証されませんでした。 +6 桁の電話による確認コードを入力してください。 +パスワードを設定 +新しいデバイスまたはブラウザが検出されました + にログイン チェコ共和国 ツバル GitHub でログイン @@ -398,6 +435,10 @@ スペイン コスタリカ サモア + +2 要素認証の によって のアカウントが更新されました。 +この変更をリクエストした覚えがない場合は、次のリンクを使用して変更を元に戻してください。 + マリ カタール ウルグアイ @@ -428,6 +469,7 @@ バミューダ ニウエ グアドループ +クライアントが無効なプロジェクトの構成を指定しました。 パレスチナ自治政府 スロバキア ガーンジー @@ -439,20 +481,25 @@ マン島 マレーシア ココス(キーリング)諸島 - アカウントを接続する場合は、ログインを開始した端末でリンクを開いてください。元のプロバイダに接続しない場合は、この端末で [続行] をタップしてログインしてください。 -元々は をメール アカウントに接続しようとしましたが、このプロバイダにログインしていない端末でリンクが開かれました。 + アカウントを接続する場合は、ログインを開始したデバイスでリンクを開いてください。元のプロバイダに接続しない場合は、このデバイスで [続行] をタップしてログインしてください。 +再試行 +元々は をメール アカウントに接続しようとしましたが、このプロバイダにログインしていないデバイスでリンクが開かれました。 0: 秒後にコードを再送信します モロッコ +このデバイスに心当たりがない場合は、他のユーザーがアカウントにアクセスしようとしている可能性があります。今すぐパスワードを変更することをおすすめします。 バングラデシュ 電話番号 フィンランド アラブ首長国連邦 +メールアドレスの確認と変更が完了しました パスワード 中央アフリカ共和国 キュラソー +不審なアクティビティが検出されたため、このデバイスからのリクエストはすべてブロックされました。しばらくしてからもう一度実行してください。 チュニジア パスワードを変更しました ギリシャ +2 つ目の認証要素を削除しました アンゴラ クウェート 入力したメールアドレスとパスワードが一致しません diff --git a/translations/ko.xtb b/translations/ko.xtb index c4dd5945..658cd1ec 100644 --- a/translations/ko.xtb +++ b/translations/ko.xtb @@ -12,11 +12,14 @@ 필리핀 루마니아 사우스 조지아 사우스 샌드위치 제도 -확인 +복구할 수 없는 데이터 손실 또는 손상이 발생했습니다. 이탈리아 레바논 가이아나 과테말라 +인증 2단계 요소를 삭제하는 중에 문제가 발생했습니다.다시 삭제해 보세요. 그래도 문제가 해결되지 않으면 지원팀에 문의하세요. +이메일 업데이트 다시 시도 +네트워크 오류가 발생했습니다. 나중에 다시 시도하세요. 올바른 전화번호를 입력하세요. '인증'을 탭하면 서비스 약관개인정보처리방침에 동의하는 것으로 간주됩니다. SMS가 발송될 수 있으며, 메시지 및 데이터 요금이 부과될 수 있습니다. 비밀번호를 입력해 주세요. @@ -27,6 +30,7 @@ IP 주소에서 계정 요청이 너무 많이 발생하고 있습니다. 잠시 후에 다시 시도해 주세요. 스리랑카 이제 새 계정으로 로그인할 수 있습니다. +클라이언트가 잘못된 인수를 지정했습니다. 차드 후에 코드 재전송 벨라루스 @@ -62,6 +66,7 @@ Github 개인정보처리방침 +리소스 할당량이 부족하거나 비율 제한에 도달했습니다. 크로아티아 리투아니아 몰타 @@ -71,22 +76,28 @@ 비회원 가나 사용하는 브라우저가 웹 저장소를 지원하지 않습니다. 다른 브라우저에서 다시 시도해 주세요. +클라이언트에서 요청을 취소했습니다. 로그인 이메일을 다시 변경하는 중에 문제가 발생했습니다.다시 시도해도 이메일을 재설정할 수 없으면 관리자에게 지원을 요청하시기 바랍니다. 바레인 +내부 서버 오류가 발생했습니다. 이란 +GitHub 로봇이 아닌 실제 사용자임을 확인 중... 더 이상 유효하지 않은 코드입니다. 뉴칼레도니아 모나코 세이셸 +인증 2단계인 이(가) 삭제되었습니다. 현재 익명 사용자를 업그레이드하지 못했습니다. 익명이 아닌 사용자 인증 정보가 이미 다른 사용자 계정에 연결되어 있습니다. 비밀번호 입력 오류 횟수를 초과했습니다. 잠시 후에 다시 시도해 주세요. 보안 비밀번호 재설정 요청이 만료되었거나 링크가 이미 사용되었습니다. +이제 새 이메일()로 로그인할 수 있습니다. 지원되지 않는 국가 코드를 입력했습니다. 이메일을 잘못 입력하지 않았는지 확인합니다. Twitter 캄보디아 +서비스를 사용할 수 없습니다. 포르투갈 프랑스령 기아나 영국령 버진아일랜드 @@ -102,6 +113,7 @@ 인증 남아프리카 공화국 계속하려면 이메일 주소를 입력하세요. +관리자가 사용자 계정을 사용 중지했습니다. 추가 안내가 포함된 로그인 이메일이 (으)로 전송되었습니다. 이메일을 확인하여 로그인을 완료하세요. 비밀번호를 입력하세요. 계정 만들기 @@ -109,13 +121,14 @@ 을(를) 탭하면 에 동의하는 것으로 간주됩니다. 캐나다 덴마크 +요청 기한이 지났습니다. 오스트리아 이메일이 일치하지 않습니다. 남수단 예멘 기니비사우 사우디아라비아 -앱 로고 +에 추가 보안 요소 추가됨 미국령 버진아일랜드 휴대전화로 로그인 인도네시아 @@ -123,6 +136,7 @@ 베네수엘라 로그인하는 데 문제가 있나요? 본인 인증 +요청을 인증하는 중에 문제가 발생했습니다. 이 페이지로 리디렉션된 URL을 다시 방문하여 인증 프로세스를 다시 시작하세요. 도미니카 새 비밀번호 가봉 @@ -140,6 +154,7 @@ 감비아 완료 취소 +알 수 없는 서버 오류가 발생했습니다. 계정 이름을 입력해 주세요. 이라크 베트남 @@ -151,6 +166,8 @@ 네팔 토켈라우 선택한 인증 제공업체의 사용자 인증 정보가 지원되지 않습니다. +지정한 이메일에 사용할 수 있는 로그인 제공업체가 없습니다. 다른 이메일로 시도해 주세요. +성과 이름을 입력하세요. 콜롬비아 '저장'을 탭하면 다음에 동의하는 것으로 간주됩니다. 부룬디 @@ -158,7 +175,7 @@ Twitter 이메일 인증 완료 파푸아 뉴기니 -저장 +지정한 리소스를 찾을 수 없습니다. 인증 중... 로그인 동티모르 @@ -191,6 +208,7 @@ 전화 재전송 온두라스 +작업 제한시간을 초과했습니다. 니제르 이메일 주소가 기존 계정과 일치하지 않습니다. 요르단 @@ -202,6 +220,7 @@ 이스라엘 오류 발생 번호 +클라이언트에게 충분한 권한이 없습니다. reCAPTCHA 입력을 완료하세요. 니카라과 부르키나 파소 @@ -210,14 +229,17 @@ 터크스 케이커스 제도 후에 코드 재전송 이메일 확인 +인증 2단계 요소를 삭제할 수 없음 비밀번호 추가 엘살바도르 +계정의 비밀번호를 변경하려면 다시 로그인해야 합니다. 세르비아 벨리즈 대상: 이 이메일에 계정을 연결하는 과정을 완료하려면 동일한 기기나 브라우저에서 링크를 열어야 합니다. 스웨덴 생마르탱 +이메일 인증 및 업데이트 요청이 만료되었거나 이미 사용된 링크입니다. 세인트 루시아 @string/app_name 이메일 확인 @@ -225,13 +247,15 @@ 오만 네덜란드령 카리브 업데이트된 이메일 주소 -Twitter로 로그인 +로그아웃되었습니다. +전화로 로그인 전화번호 계정이 이미 있음 태국 코드가 잘못되었습니다. 다시 시도하세요. 리히텐슈타인 이름 수정 +인증 2단계용 기기 또는 앱이 삭제되었습니다. 로그인 절차를 시작하는 데 사용한 것과 동일한 기기나 브라우저로 링크를 열어 보세요. 네덜란드 적도 기니 @@ -255,7 +279,7 @@ 계속하려면 이메일 주소를 입력하세요. 전화번호 인증 라이베리아 -해당 이메일 주소를 갖는 계정이 이미 있습니다. +로그아웃 마다가스카르 카자흐스탄 로그인 중... @@ -274,15 +298,20 @@ 전송된 자리 코드를 입력하세요. 이 전화번호로 전송 시도를 너무 많이 했습니다. 입력한 이메일이 현재 로그인 세션과 일치하지 않습니다. -계정 연결 해제 +Twitter + +에 로그인 </p></p> + 받은편지함 용량이 다 찼거나 받은편지함 설정과 관련된 문제가 있는 것이 아닌지 확인합니다. +클라이언트가 잘못된 범위로 지정되었습니다. +이 작업의 할당량을 초과했습니다. 나중에 다시 시도하세요. 파라과이 알제리 비밀번호 입력 오류 횟수를 초과했습니다. 잠시 후에 다시 시도해 주세요. 노퍽 섬 전화번호가 자동으로 확인되었습니다. -비밀번호를 변경하려면 먼저 현재 비밀번호를 입력해야 합니다. -뒤로 +네트워크 오류가 발생했습니다. 인터넷 연결을 확인하세요. +인증되었습니다. 인도 베냉 계속 @@ -331,27 +360,33 @@ 이메일 다음 더 이상 유효하지 않은 코드입니다. -Facebook으로 로그인 +추가 안내가 포함된 로그인 이메일이 (으)로 전송되었습니다. 이메일을 확인하여 로그인을 완료하세요. 이메일 확인 이름 그레나다 서사하라 +현재 시스템 상태에서는 요청을 실행할 수 없습니다. 알 수 없는 오류가 발생했습니다. 이미 을(를) 사용하여 로그인했습니다. 해당 계정의 비밀번호를 입력하세요. +로그인 이메일 전송됨 아일랜드 콩고 민주공화국 비밀번호 복구 콩고 인민공화국 +클라이언트가 만들려고 했던 리소스가 이미 존재합니다. 비회원으로 진행 산마리노 사이프러스 솔로몬 제도 (으)로 계속 진행하시겠습니까? +이 기기에서 계속 계정으로 로그인하려면 비밀번호를 복구해야 합니다. 몰디브 상투메 프린시페 이메일을 확인하여 로그인 완료 슬로베니아 대한민국 +읽기-수정-쓰기 충돌 같은 동시 실행 충돌이 발생했습니다. +서버에서 API 메소드를 구현하지 않았습니다. 레소토 전화로 로그인 이메일 인증 재시도 @@ -371,9 +406,11 @@ 로그인했습니다. 해당 계정의 비밀번호를 입력하세요. {plural_var,plural, =1{안전한 비밀번호가 아닙니다. 길이가 자 이상이고 문자와 숫자가 조합되어야 합니다.}other{안전한 비밀번호가 아닙니다. 길이가 자 이상이고 문자와 숫자가 조합되어야 합니다.}} 짐바브웨 -(으)로 발송된 이메일의 안내에 따라 비밀번호를 복구하세요. -전화번호를 인증하는 중에 문제가 발생했습니다. +OAuth 토큰이 누락되었거나, 잘못되었거나, 만료되어 요청을 인증할 수 없습니다. +6자리 전화 인증 코드를 입력하세요. +비밀번호 선택 새 기기 또는 브라우저 감지됨 +에 로그인 체코 공화국 투발루 GitHub로 로그인 @@ -399,6 +436,10 @@ 스페인 코스타리카 사모아 + +2단계 인증을 위해 항목으로 의 계정이 업데이트되었습니다. +직접 수정을 요청하지 않으셨다면 다음 링크로 이동하여 변경사항을 취소하시기 바랍니다. + 말리 카타르 우루과이 @@ -429,6 +470,7 @@ 버뮤다 니우에 과들루프 +클라이언트에서 잘못된 프로젝트 구성을 지정했습니다. 팔레스타인 자치지구 슬로바키아 건지 @@ -441,19 +483,24 @@ 말레이시아 코코스 제도[킬링 제도] 그래도 계정에 연결하려면 로그인했던 기기에서 링크를 여시기 바랍니다. 아니면, 계속을 탭하여 이 기기에서 로그인하세요. +재시도 원래 이메일 계정에 을(를) 연결하려고 했지만 로그인되지 않은 다른 기기에서 링크를 열었습니다. 0: 후에 코드 재전송 모로코 +모르는 기기라면 누군가 계정 액세스를 시도하려는 것일 수 있습니다. 지금 비밀번호를 변경하는 것이 좋습니다. 방글라데시 전화 핀란드 아랍에미리트 +이메일 인증 및 변경됨 비밀번호 중앙아프리카 공화국 퀴라소 +비정상적 활동으로 인해 이 기기의 모든 요청을 차단했습니다. 나중에 다시 시도하세요. 튀니지 비밀번호 변경 완료 그리스 +인증 2단계 요소 삭제됨 앙골라 쿠웨이트 입력한 이메일과 비밀번호가 일치하지 않습니다. diff --git a/translations/lt.xtb b/translations/lt.xtb index 0eb5ac77..d80387a9 100644 --- a/translations/lt.xtb +++ b/translations/lt.xtb @@ -12,11 +12,14 @@ Filipinai Rumunija Pietų Džordžijos ir Pietų Sandvičo Salos -Gerai +Neatkuriamas duomenų praradimas arba jų sugadinimas. Italija Libanas Gajana Gvatemala +Kažkas nepavyko šalinant antrąjį faktorių.Pabandykite jį dar kartą pašalinti. Jei nepavyks, kreipkitės į palaikymo komandą pagalbos. +Pabandykite dar kartą atnaujinti savo el. pašto adresą +Įvyko tinklo klaida. Bandykite dar kartą vėliau. Įveskite tinkamą telefono numerį Paliesdami „Patvirtinti“ nurodote, kad sutinkate su paslaugų teikimo sąlygomis ir privatumo politika. Gali būti išsiųstas SMS pranešimas, taip pat – taikomi pranešimų ir duomenų įkainiai. Įveskite slaptažodį @@ -27,6 +30,7 @@ Iš jūsų IP adreso siunčiama per daug paskyros užklausų. Bandykite dar kartą po kelių minučių. Šri Lanka Nuo šiol galite prisijungti naudodami naująją paskyrą +Kliento programa nurodė netinkamą argumentą. Čadas Paliesdami „“ nurodote, kad sutinkate su ir . Gali būti išsiųstas SMS pranešimas, taip pat – taikomi pranešimų ir duomenų įkainiai. Baltarusija @@ -62,6 +66,7 @@ Github Privatumo politika Guamas +Baigėsi išteklių kvota arba pasiektas įvertinimo apribojimas. Kroatija Lietuva Malta @@ -71,21 +76,27 @@ Svečias Gana Naršyklė, kurią naudojate, nepalaiko žiniatinklio saugyklos. Bandykite dar kartą naudodami kitą naršyklę. +Kliento programa atšaukė užklausą. Keičiant prisijungimo el. pašto adresą į pirminį kilo problema.Jei bandysite dar kartą ir vis tiek nepavyks nustatyti el. pašto adreso iš naujo, bandykite prašyti administratoriaus pagalbos. Bahreinas +Vidinė serverio klaida. Iranas +GitHub Tikrinama, ar nesate robotas... Šis kodas nebegalioja Naujoji Kaledonija Monakas Seišeliai + kaip antrasis tapatumo nustatymo žingsnis buvo pašalintas. Esamo anoniminio naudotojo naujovinti nepavyko. Neanoniminiai prisijungimo duomenys jau susieti su kito naudotojo paskyra. Įvedėte netinkamą slaptažodį per daug kartų. Po kelių minučių bandykite dar kartą. Jūsų užklausa pakeisti slaptažodį nebegalioja arba nuoroda jau buvo panaudota +Nuo šiol galite prisijungti naudodami naująjį el. pašto adresą . Pateiktas šalies kodas nepalaikomas. patikrinkite, ar nurodėte tikslų el. pašto adresą; Twitter Kambodža +Paslauga nepasiekiama. Portugalija Prancūzijos Gviana Didžiosios Britanijos Mergelių Salos @@ -101,19 +112,21 @@ Patvirtinti Pietų Afrikos Respublika Norėdami tęsti įveskite el. pašto adresą +Naudotojo paskyrą išjungė administratorius. Prisijungimo el. laiškas su papildomomis instrukcijomis išsiųstas adresu . Patikrinkite el. paštą, kad užbaigtumėte prisijungimą. Kurti paskyrą Afganistanas Paliesdami „“ nurodote, kad sutinkate su . Kanada Danija +Užklausos galiojimo laikas baigėsi. Austrija El. paštai nesutampa Pietų Sudanas Jemenas Bisau Gvinėja Saudo Arabija -Programos logotipas +Prie „“ pridėtas papildomas saugos veiksnys Jungtinių Valstijų Mergelių Salos Prisijungti nurodant telefono numerį Indonezija @@ -121,6 +134,7 @@ Venesuela Kyla problemų prisijungiant? Patvirtinkite savo tapatybę +Autentifikuojant jūsų užklausą kilo problema. Dar kartą nueikite URL adresu, kuris peradresavo jus į šį puslapį, ir iš naujo pradėkite autentifikavimo procesą. Dominika Naujas slaptažodis Gabonas @@ -138,6 +152,7 @@ Gambija Atlikta Atšaukti +Nežinoma serverio klaida. Įveskite paskyroje naudojamą vardą Irakas Vietnamas @@ -149,13 +164,15 @@ Nepalas Tokelau Pasirinkti tapatybės nustatymo paslaugų teikėjo prisijungimo duomenys nepalaikomi. +Su šiuo el. pašto adresu prisijungimo teikėjas nenurodytas, bandykite prisijungti su kitu el. pašto adresu. +Įveskite vardą ir pavardę. Kolumbija Burundis Mauritanija Twitter Jūsų el. pašto adresas patvirtintas Papua Naujoji Gvinėja -Išsaugoti +Nurodytas išteklius nerastas. Patvirtinama… Prisijungti Rytų Timoras @@ -188,6 +205,7 @@ Telefono numeris Siųsti iš naujo Hondūras +Baigėsi operacijai skirtas laikas. Nigeris Šis el. pašto adresas neatitinka esamos paskyros Jordanija @@ -199,6 +217,7 @@ Izraelis Įvyko klaida Numeris +Kliento programai nesuteiktas reikiamas leidimas. Atlikite reikiamus „reCAPTCHA“ veiksmus Nikaragva Burkina Fasas @@ -207,27 +226,32 @@ Terkso ir Kaikoso Salos Siųsti kodą iš naujo po Patikrinkite el. paštą +Nepavyko pašalinti antrojo faktoriaus Pridėti slaptažodį Salvadoras +Norėdami pakeisti paskyros slaptažodį, turite prisijungti dar kartą. Serbija Belizas skirtas Norint vykdant šį procesą sėkmingai prijungti paskyrą naudojant šį el. laišką, turite atidaryti nuorodą tame pačiame įrenginyje ar naršyklėje. Švedija Šv. Martyno sala +Jūsų užklausa patvirtinti ir atnaujinti el. pašto adresą nebegalioja arba nuoroda jau buvo panaudota. Šv. Liucija Patikrinkite el. paštą Jūsų užklausa patvirtinti el. pašto adresą nebegalioja arba nuoroda jau buvo panaudota Omanas Karibų Nyderlandai Atnaujintas el. pašto adresas -Prisijungti per „Twitter“ +Dabar sėkmingai atsijungėte. +Prisijungti nurodant telefono numerį Telefono numeris Jūs jau turite paskyrą Tailandas Privatumo politika Lichtenšteinas Redaguoti pavadinimą +Įrenginys arba programa kaip antrasis tapatumo nustatymo žingsnis buvo pašalinti. Bandykite atidaryti nuorodą naudodami tą patį įrenginį ar naršyklę, kur pradėjote prisijungimo procesą. Nyderlandai Pusiaujo Gvinėja @@ -251,7 +275,7 @@ Norėdami tęsti įveskite el. pašto adresą Patvirtinkite telefono numerį Liberija -Šiuo el. pašto adresu jau užregistruota paskyra. +Atsijunkite Madagaskaras Kazachstanas Prisijungiama... @@ -270,15 +294,20 @@ Įveskite skaitmenų kodą, kurį išsiuntėme jums Šis telefono numeris panaudotas per daug kartų Pateiktas el. pašto adresas neatitinka esamos prisijungimo sesijos informacijos. -Atsieti paskyrą +Twitter + +Prisijunkite prie „“ </p></p> + patikrinkite, ar gautiesiems skirta saugyklos vieta nepasibaigė arba kitas su gautųjų nustatymais susijusias problemas. +Kliento programa nurodė netinkamą intervalą. +Viršyta šiai operacijai skirta kvota. Bandykite dar kartą vėliau. Paragvajus Alžyras Įvedėte netinkamą slaptažodį per daug kartų. Po kelių minučių bandykite dar kartą. Norfolko Sala Telefono numeris patvirtintas automatiškai -Norėdami pakeisti slaptažodį, pirmiausia turite įvesti dabartinį. -Atgal +Tinklo klaida: patikrinkite interneto ryšį. +Patvirtinta. Indija Beninas Tęsti @@ -327,27 +356,33 @@ El. pašto adresas Kitas Šis kodas nebegalioja -Prisijungti per „Facebook“ +Prisijungimo el. laiškas su papildomomis instrukcijomis išsiųstas adresu . Patikrinkite el. paštą, kad užbaigtumėte prisijungimą. Patvirtinkite el. paštą Vardas ir pavardė Grenada Vakarų Sachara +Užklausos įvykdyti esant dabartinei sistemos būsenai negalima. Įvyko nežinoma klaida. Jūs jau prisijungėte naudodami . Įveskite tos paskyros slaptažodį. +Prisijungimo el. laiškas išsiųstas Airija Kongo Demokratinė Respublika Atkurti slaptažodį Kongo Respublika +Išteklius, kurį kliento programa bandė sukurti, jau yra. Tęsti kaip svečiui San Marinas Kipras Saliamono Salos Tęsti naudojant ? +Jei norite tęsti prisijungimą su šiame įrenginyje, turite atkurti slaptažodį. Maldyvų Salos San Tomė ir Prinsipė Patvirtinkite el. paštą, kad užbaigtumėte prisijungimą Slovėnija Pietų Korėja +Vienalaikiškumo konfliktas, pvz., skaitymo, modifikavimo ir rašymo konfliktas. +API metodas neįdiegtas serveryje. Lesotas Prisijungti nurodant telefono numerį Bandykite patvirtinti el. pašto adresą dar kartą @@ -366,8 +401,10 @@ Jūs jau prisijungėte naudodami . Įveskite tos paskyros slaptažodį. {plural_var,plural, =1{Slaptažodis nepakankamai sudėtingas. Naudokite bent simbolį ir raidžių bei skaičių derinį.}one{Slaptažodis nepakankamai sudėtingas. Naudokite bent simbolį ir raidžių bei skaičių derinį.}few{Slaptažodis nepakankamai sudėtingas. Naudokite bent simbolius ir raidžių bei skaičių derinį.}many{Slaptažodis nepakankamai sudėtingas. Naudokite bent simbolio ir raidžių bei skaičių derinį.}other{Slaptažodis nepakankamai sudėtingas. Naudokite bent simbolių ir raidžių bei skaičių derinį.}} Zimbabvė +Užklausa nepatvirtinta dėl nesamo, netinkamo arba baigusio galioti „OAuth“ prieigos rakto. Patvirtinant telefono numerį kilo problema Aptiktas naujas įrenginys arba naršyklė +Prisijungti prie „ Čekijos Respublika Tuvalu Prisijungti per „GitHub“ @@ -422,6 +459,7 @@ Bermudų salos Niujė Gvadelupa +Klientas nurodė netinkamus projekto konfigūracijos duomenis. Palestinos Teritorijos Slovakija Gernsis @@ -434,19 +472,24 @@ Malaizija Kokosų salos Jei vis tiek norite susieti paskyrą, atidarykite nuorodą tame pačiame įrenginyje, kuriame pradėjote prisijungimo procesą. Jei nenorite, palieskite „Tęsti“ ir prisijunkite šiame įrenginyje. +Bandyti iš naujo Iš pradžių ketinote susieti ir el. pašto paskyras, bet atidarėte nuorodą kitame įrenginyje, kuriame nesate prisijungę. Siųsti kodą iš naujo po 0: Marokas +Jei nepažįstate šio įrenginio, galbūt kažkas bando prisijungti prie jūsų paskyros. Pagalvokite, ar nevertėtų tučtuojau pakeisti slaptažodžio. Bangladešas Telefonas Suomija Jungtiniai Arabų Emyratai +Jūsų el. pašto adresas patvirtintas ir pakeistas Slaptažodis Centrinės Afrikos Respublika Kiurasao +Užblokavome visas iš šio įrenginio siunčiamas užklausas dėl neįprastos veiklos. Bandykite dar kartą vėliau. Tunisas Slaptažodis pakeistas Graikija +Pašalintas antrasis faktorius Angola Kuveitas Įvestas slaptažodis neatitinka el. pašto adreso diff --git a/translations/lv.xtb b/translations/lv.xtb index cca87fa3..53313660 100644 --- a/translations/lv.xtb +++ b/translations/lv.xtb @@ -12,11 +12,14 @@ Filipīnas Rumānija Dienviddžordžija un Dienvidsendviču Salas -Labi +Neatgūstami datu zaudējumi vai datu bojājumi. Itālija Libāna Gajāna Gvatemala +Radās problēma, noņemot otro faktoru.Mēģiniet noņemt to vēlreiz. Ja problēma netiek novērsta, lūdziet palīdzību atbalsta dienestam. +Mēģiniet vēlreiz atjaunināt savu e-pasta adresi +Ir radusies tīkla kļūda. Mēģiniet vēlāk vēlreiz! Ievadiet derīgu tālruņa numuru Pieskaroties pogai Verificēt, jūs norādāt, ka piekrītat mūs pakalpojumu sniegšanas noteikumiem un konfidencialitātes politikai. Var tikt nosūtīta īsziņa. Var tikt piemērota maksa par ziņojumiem un datu pārsūtīšanu. Ievadiet savu paroli. @@ -27,6 +30,7 @@ Pārāk daudz kontu pieprasījumu ir nosūtīti no jūsu IP adreses. Pēc dažām minūtēm mēģiniet vēlreiz. Šrilanka Tagad varat pierakstīties ar jauno kontu +Klients norādīja nederīgu argumentu. Čada Pieskaroties pogai “”, jūs norādāt, ka piekrītat šādiem dokumentiem: un . Var tikt nosūtīta īsziņa. Var tikt piemērota maksa par ziņojumiem un datu pārsūtīšanu. Baltkrievija @@ -62,6 +66,7 @@ Github Konfidencialitātes politika Guama +Beigusies resursu kvota vai sasniegts vērtējuma ierobežojums. Horvātija Lietuva Malta @@ -71,21 +76,27 @@ Viesis Gana Jūsu izmantotā pārlūkprogramma neatbalsta tīmekļa datu krātuvi. Lūdzu, mēģiniet vēlreiz citā pārlūkprogrammā. +Klients atcēla pieprasījumu. Radās problēma, mainot atpakaļ pierakstīšanās e-pasta adresi.Ja mēģināsit vēlreiz un joprojām nevarēsit atiestatīt e-pasta adresi, lūdziet palīdzību administratoram. Bahreina +Iekšēja servera kļūda. Irāna +GitHub Notiek verifikācija, lai pārliecinātos, ka neesat robots... Šis kods vairs nav derīgs Jaunkaledonija Monako Seišelas + kā otrā autentificēšanas darbība tika noņemta. Pašreizējam anonīmajam lietotājam neizdevās veikt jaunināšanu. Akreditācijas dati, kuri nav anonīmi, jau ir saistīti ar citu lietotāja kontu. Esat pārāk daudz reižu ievadījis nepareizu paroli. Lūdzu, mēģiniet vēlreiz pēc dažām minūtēm. Jūsu pieprasījumam par paroles atiestatīšanu ir beidzies termiņš, vai saite jau ir izmantota +Tagad varat pierakstīties ar jauno e-pasta adresi . Norādītais valsts kods netiek atbalstīts. Pārbaudiet, vai e-pasta adrese ir pareizi uzrakstīta. Twitter Kambodža +Pakalpojums nav pieejams. Portugāle Francijas Gviāna Britu Virdžīnu Salas @@ -101,19 +112,21 @@ Verificēt Dienvidāfrika Lai turpinātu, ievadiet e-pasta adresi +Administrators ir atspējojis lietotāja kontu. Pierakstīšanās e-pasta ziņojums ar papildu norādījumiem tika nosūtīts uz e-pasta adresi . Pārbaudiet savu e-pastu, lai pabeigtu pierakstīšanos. Konta izveide Afganistāna Pieskaroties pogai , jūs norādāt, ka piekrītat šādiem noteikumiem: . Kanāda Dānija +Pieprasījuma derīguma termiņš ir pārsniegts. Austrija E-pasta adreses nesakrīt Dienvidsudāna Jemena Gvineja-Bisava Saūda Arābija -Lietotnes logotips +Lietotnē ir pievienots vēl viens drošības faktors. ASV Virdžīnu Salas Pierakstīties ar tālruni Indonēzija @@ -121,6 +134,7 @@ Venecuēla Vai jums ir problēmas ar pierakstīšanos? Verificējiet savu identitāti +Autentificējot jūsu pieprasījumu, radās kļūda. Lai atsāktu autentifikācijas procesu, vēlreiz atveriet URL, kas jūs novirzīja uz šo lapu. Dominika Jauna parole Gabona @@ -138,6 +152,7 @@ Gambija Gatavs Atcelt +Nezināma servera kļūda. Ievadiet savu konta nosaukumu Irāka Vjetnama @@ -149,13 +164,15 @@ Nepāla Tokelau Netiek atbalstīti autentifikācijas nodrošinātājam atlasītie akreditācijas dati. +Šai e-pasta adresei nav pieejams pierakstīšanās nodrošinātājs. Lūdzu, izmēģiniet citu e-pasta adresi. +Lūdzu, ievadiet vārdu un uzvārdu. Kolumbija Burundija Mauritānija Twitter E-pasta adrese ir verificēta Papua-Jaungvineja -Saglabāt +Norādītais resurss nav atrasts. Notiek verifikācija... Pierakstīties Austrumtimora @@ -188,6 +205,7 @@ Tālrunis Nosūtīt vēlreiz Hondurasa +Ir iestājusies darbības noildze. Nigēra E-pasta adrese neatbilst esošam kontam Jordānija @@ -199,6 +217,7 @@ Izraēla Ir radusies kļūda Numurs +Klientam nav atbilstošas atļaujas. Izpildiet reCAPTCHA uzdevumu Nikaragva Burkinafaso @@ -207,27 +226,32 @@ Tērksas un Kaikosas Salas Vēlreiz nosūtīt kodu pēc Pārbaudiet savu e-pastu +Nevarēja noņemt otro faktoru Paroles pievienošana Salvadora +Lai mainītu konta paroli, būs vēlreiz jāpierakstās. Serbija Beliza šādam kontam: Lai šī plūsma varētu sekmīgi saistīt kontu ar šo e-pasta adresi, jums ir jāatver saite tajā pašā ierīcē vai pārlūkprogrammā. Zviedrija Senmartēna +Jūsu pieprasījumam par e-pasta adreses verifikāciju un atjaunināšanu ir beidzies derīguma termiņš, vai saite jau ir izmantota. Sentlūsija Pārbaudiet savu e-pastu Jūsu pieprasījumam par e-pasta adreses verifikāciju ir beidzies termiņš, vai saite jau ir izmantota Omāna Nīderlandes Karību salas Atjaunināta e-pasta adrese -Pierakstīties ar Twitter +Tagad esat sekmīgi izrakstījies. +Pierakstīties ar tālruni Tālruņa numurs Jums jau ir konts Taizeme Konfidencialitātes politika Lihtenšteina Nosaukuma rediģēšana +Šī ierīce vai lietotne tika noņemta kā otrā autentificēšanas darbība. Mēģiniet atvērt saiti, izmantot to pašu ierīci vai pārlūkprogrammu, kur sākāt pierakstīšanās procesu. Nīderlande Ekvatoriālā Gvineja @@ -251,7 +275,7 @@ Lai turpinātu, ievadiet e-pasta adresi Tālruņa numura verifikācija Libērija -Ar šo e-pasta adresi jau pastāv konts. +Izrakstīšanās Madagaskara Kazahstāna Notiek pierakstīšanās... @@ -270,15 +294,20 @@ Ievadiet  ciparu kodu, ko nosūtījām uz šādu tālruņa numuru: Šis tālruņa numurs ir izmantots pārāk daudz reižu Norādītā e-pasta adrese neatbilst pašreizējai pierakstīšanās sesijai. -Konta atsaistīšana +Twitter + +Pierakstīšanās lietotnē </p></p> + Pārbaudiet, vai jūsu iesūtnes krātuve nav beigusies vai nav citu ar iesūtnes iestatījumiem saistītu problēmu. +Klients norādīja nederīgu diapazonu. +Pārsniegta šīs darbības kvota. Mēģiniet vēlāk vēlreiz! Paragvaja Alžīrija Esat pārāk daudz reižu ievadījis nepareizu paroli. Lūdzu, mēģiniet vēlreiz pēc dažām minūtēm. Norfolkas Sala Tālruņa numurs tika automātiski verificēts -Lai mainītu paroli, vispirms ir jāievada pašreizējā parole. -Atpakaļ +Tīkla kļūda. Pārbaudiet interneta savienojumu. +Verificēts. Indija Benina Turpināt @@ -327,27 +356,33 @@ E-pasts Tālāk Šis kods vairs nav derīgs -Pierakstīties ar Facebook +Pierakstīšanās e-pasta ziņojums ar papildu norādījumiem tika nosūtīts uz e-pasta adresi . Pārbaudiet savu e-pastu, lai pabeigtu pierakstīšanos. E-pasta adreses apstiprināšana Vārds un uzvārds Grenāda Rietumsahāra +Pašreizējā sistēmas stāvoklī pieprasījumu nevar izpildīt. Radās nezināma kļūda. Esat jau izmantojis e-pasta adresi , lai pierakstītos. Ievadiet šī konta paroli. +Pierakstīšanās e-pasta ziņojums ir nosūtīts Īrija Kongo Demokrātiskā Republika Paroles atgūšana Kongo Republika +Jau pastāv resurss, ko klients mēģināja izveidot. Turpināt kā viesim Sanmarīno Kipra Zālamana Salas Vai turpināt ar e-pasta adresi ? +Lai turpinātu, pierakstieties šajā ierīcē, izmantojot e-pasta adresi ; jums ir jāatgūst parole. Maldīvija Santome un Prinsipi Apstipriniet e-pasta adresi, lai pabeigtu pierakstīšanos Slovēnija Dienvidkoreja +Laiksakritības konflikts, piemēram, lasīšanas/mainīšanas/rakstīšanas konflikts. +API metode nav servera ieviesta. Lesoto Pierakstīties ar tālruni Mēģiniet vēlreiz verificēt savu e-pasta adresi @@ -367,8 +402,10 @@ lai pierakstītos. Ievadiet šī konta paroli. {plural_var,plural, =1{Parole nav pietiekami droša. Izmantojiet vismaz  rakstzīmi un burtu un ciparu kombināciju}zero{Parole nav pietiekami droša. Izmantojiet vismaz  rakstzīmes un burtu un ciparu kombināciju}one{Parole nav pietiekami droša. Izmantojiet vismaz  rakstzīmi un burtu un ciparu kombināciju}other{Parole nav pietiekami droša. Izmantojiet vismaz  rakstzīmes un burtu un ciparu kombināciju}} Zimbabve +Pieprasījums nav autentificēts, jo trūkst OAuth pilnvaras, tā ir nederīga vai beidzies tās termiņš. Verificējot jūsu tālruņa numuru, radās problēma Konstatēta jauna ierīce vai pārlūkprogramma +Pierakstīties: Čehija Tuvalu Pierakstīties, izmantojot GitHub @@ -423,6 +460,7 @@ Bermudu Salas Niue Gvadelupa +Klients norādīja nederīgu projekta konfigurāciju. Palestīna Slovākija Gērnsija @@ -435,19 +473,24 @@ Malaizija Kokosu [Kīlinga] Salas Ja joprojām vēlaties saistīt savu kontu, atveriet saiti tajā pašā ierīcē, kurā sākāt pierakstīšanos. Pretējā gadījumā pieskarieties vienumam Turpināt, lai pierakstītos šajā ierīcē. +Mēģināt vēlreiz Sākotnēji vēlējāties saistīt ar savu e-pasta kontu, taču atvērāt saiti citā ierīcē, kurā neesat pierakstījies. Vēlreiz nosūtīt kodu pēc 0: Maroka +Ja neatpazīstat šo ierīci, iespējams, kāda persona mēģina piekļūt jūsu kontam. Apsveriet iespēju nekavējoties nomainīt paroli. Bangladeša Tālrunis Somija Apvienotie Arābu Emirāti +Jūsu e-pasta adrese ir verificēta un nomainīta Parole Centrālāfrikas Republika Kirasao +Visi pieprasījumi no šīs ierīces ir bloķēti neparastas darbības dēļ. Mēģiniet vēlāk vēlreiz! Tunisija Parole ir nomainīta Grieķija +Noņemts otrais faktors Angola Kuveita Ievadītā e-pasta adrese un parole neatbilst diff --git a/translations/nl.xtb b/translations/nl.xtb index b48bcdbd..c3112cd3 100644 --- a/translations/nl.xtb +++ b/translations/nl.xtb @@ -12,11 +12,14 @@ Filipijnen Roemenië Zuid-Georgia en de Zuidelijke Sandwicheilanden -OK +Onherstelbaar gegevensverlies of onherstelbare gegevensbeschadiging. Italië Libanon Guyana Guatemala +Er is iets misgegaan bij het verwijderen van uw tweede stap.Probeer het opnieuw. Als het dan nog niet lukt, kunt u voor hulp contact opnemen met support. +Probeer uw e-mailadres nogmaals te updaten +Er is een netwerkfout opgetreden. Probeer het later opnieuw. Voer een geldig telefoonnummer in Door op Verifiëren te tikken, geeft u aan dat u onze servicevoorwaarden en ons privacybeleid accepteert. Mogelijk ontvangt u een sms. Er kunnen sms- en datakosten van toepassing zijn. Voer uw wachtwoord in @@ -27,6 +30,7 @@ Er komen te veel accountaanvragen van uw IP-adres. Probeer het over enkele minuten opnieuw. Sri Lanka U kunt nu inloggen met uw nieuwe account +De client heeft een ongeldig argument opgegeven. Tsjaad Code opnieuw verzenden over Wit-Rusland @@ -62,6 +66,7 @@ GitHub Privacybeleid Guam +Het resourcequotum is bereikt of de drempel voor rate limiting wordt bereikt. Kroatië Litouwen Malta @@ -71,22 +76,28 @@ Gast Ghana De browser die u gebruikt, ondersteunt webopslag niet. Probeer het opnieuw in een andere browser. +Het verzoek is geannuleerd door de client. Er heeft zich een probleem voorgedaan bij het terugveranderen van uw e-mailadres.Probeer het opnieuw. Als u uw e-mailadres nog steeds niet kunt resetten, kunt u uw beheerder om hulp vragen. Bahrein +Interne serverfout. Iran +GitHub Verifiëren of u geen robot bent... Deze code is niet meer geldig Nieuw-Caledonië Monaco Seychellen + is verwijderd als tweede stap voor authenticatie. De huidige anonieme gebruiker kon niet worden geüpgraded. De niet-anonieme gegevens zijn al gekoppeld aan een ander gebruikersaccount. U heeft te vaak een fout wachtwoord ingevoerd. Probeer het over enkele minuten opnieuw. Beveiliging Uw verzoek om uw wachtwoord te resetten, is verlopen of de link is al gebruikt +U kunt nu inloggen met uw nieuwe e-mailadres . De opgegeven landcode wordt niet ondersteund. Controleer of u uw e-mailadres juist heeft gespeld. Twitter Cambodja +De service is niet beschikbaar. Portugal Frans-Guyana Britse Maagdeneilanden @@ -102,6 +113,7 @@ Verifiëren Zuid-Afrika Voer uw e-mailadres in om verder te gaan +Het gebruikersaccount is uitgeschakeld door een beheerder. Er is een inlogmail met aanvullende instructies verzonden naar . Controleer uw inbox om het inlogproces te voltooien. Voer uw wachtwoord in Account maken @@ -109,13 +121,14 @@ Door op te tikken, geeft u aan dat u akkoord gaat met de . Canada Denemarken +Verzoekdeadline is overschreden. Oostenrijk E-mailadressen komen niet overeen Zuid-Soedan Jemen Guinee-Bissau Saudi-Arabië -App-logo +Er is een extra beveiligingsfactor toegevoegd in Amerikaanse Maagdeneilanden Inloggen met telefoon Indonesië @@ -123,6 +136,7 @@ Venezuela Problemen met inloggen? Bevestig dat u het bent +Er is een probleem opgetreden bij het verifiëren van uw verzoek. Ga naar de URL die u naar deze pagina heeft omgeleid en start het verificatieproces opnieuw. Dominica Nieuw wachtwoord Gabon @@ -140,6 +154,7 @@ Gambia Gereed Annuleren +Onbekende serverfout. Voer uw accountnaam in Irak Vietnam @@ -151,6 +166,8 @@ Nepal Tokelau De geselecteerde inloggegevens voor de verificatieprovider worden niet ondersteund. +Er is geen inlogmethode beschikbaar voor het opgegeven e-mailadres. Gebruik een ander e-mailadres. +Voer een voor- en achternaam in. Colombia Door op OPSLAAN te tikken, gaat u akkoord met de Burundi @@ -158,7 +175,7 @@ Twitter Uw e-mailadres is geverifieerd Papoea-Nieuw-Guinea -Opslaan +De opgegeven resource is niet gevonden. Verifiëren... Inloggen Oost-Timor @@ -176,7 +193,7 @@ Argentinië Door verder te gaan, geeft u aan dat u onze servicevoorwaarden en ons privacybeleid accepteert. Vul een nieuw e-mailadres in -Link verwijderen +Ontkoppelen Guinee-Conakry Zwitserland Andorra @@ -191,6 +208,7 @@ Telefoon Opnieuw verzenden Honduras +Er is een time-out opgetreden bij de bewerking. Niger Dit e-mailadres komt niet overeen met een bestaand account Jordanië @@ -202,6 +220,7 @@ Israël Er is een fout opgetreden Nummer +De client heeft onvoldoende rechten. Vul de reCAPTCHA in Nicaragua Burkina Faso @@ -210,14 +229,17 @@ Turks- en Caicoseilanden Code opnieuw verzenden over Controleer uw inbox +Kan de tweede stap niet verwijderen Wachtwoord toevoegen El Salvador +Om het wachtwoord van uw account te veranderen, moet u opnieuw inloggen. Servië Belize voor Als u uw -account aan dit e-mailadres wilt koppelen, moet u de link openen op hetzelfde apparaat of in dezelfde browser. Zweden Saint-Martin +U heeft een verzoek ingediend om uw e-mailadres te verifiëren en te updaten. Dit verzoek is verlopen of de link is al gebruikt. Saint Lucia @string/app_name Controleer uw inbox @@ -225,13 +247,15 @@ Oman Caribisch Nederland Geüpdatet e-mailadres -Inloggen met Twitter +U bent uitgelogd. +Inloggen met telefoon Telefoonnummer U heeft al een account Thailand Onjuiste code. Probeer het opnieuw. Liechtenstein Naam bewerken +Het apparaat of de app is verwijderd als tweede stap voor authenticatie. Probeer de link te openen op het apparaat of in de browser waarmee u het inlogproces bent gestart. Nederland Equatoriaal-Guinea @@ -255,7 +279,7 @@ Voer uw e-mailadres in om verder te gaan Verifieer uw telefoonnummer Liberia -Er bestaat al een account met dit e-mailadres. +Uitloggen Madagaskar Kazachstan Aanmelden... @@ -274,15 +298,20 @@ Voer de -cijferige code in die we hebben verzonden naar Dit telefoonnummer is te vaak gebruikt Het e-mailadres komt niet overeen met de huidige inlogsessie. -Link met account verwijderen +Twitter + +Log in bij </p></p> + Controleer of er voldoende ruimte in uw inbox is en of er geen andere problemen met inboxinstellingen zijn. +De client heeft een ongeldig bereik opgegeven. +Het quotum voor deze bewerking is overschreden. Probeer het later opnieuw. Paraguay Algerije U heeft te vaak een fout wachtwoord ingevoerd. Probeer het over enkele minuten opnieuw. Norfolkeiland Telefoonnummer is automatisch geverifieerd -Om uw wachtwoord te veranderen, moet u eerst uw huidige wachtwoord invoeren. -Terug +Er is een netwerkfout opgetreden. Controleer uw internetverbinding. +Geverifieerd India Benin Doorgaan @@ -331,27 +360,33 @@ E-mailadres Volgende Deze code is niet meer geldig -Inloggen met Facebook +Er is een inlogmail met aanvullende instructies verzonden naar . Controleer uw inbox om het inlogproces te voltooien. E-mailadres bevestigen Voor- en achternaam Grenada Westelijke Sahara +Het verzoek kan niet worden uitgevoerd bij de huidige systeemstatus. Er is een onbekende fout opgetreden. U heeft al ingelogd met . Voer uw wachtwoord in voor dit account. +Inlogmail verzonden Ierland Democratische Republiek Congo Wachtwoord herstellen Republiek Congo +De resource die een client heeft geprobeerd te maken, bestaat al. Doorgaan als gast San Marino Cyprus Salomonseilanden Doorgaan met ? +Als u het inlogproces met wilt voortzetten op dit apparaat, moet u het wachtwoord herstellen. Maldiven Sao Tomé en Principe Uw e-mailadres bevestigen om het inlogproces te voltooien Slovenië Zuid-Korea +Gelijktijdigheidsconflict, zoals conflicterende lees-, wijzigings- en schrijfbewerkingen. +De API-methode is niet door de server geïmplementeerd. Lesotho Inloggen met telefoon Probeer uw e-mailadres opnieuw te verifiëren @@ -370,9 +405,11 @@ U heeft al ingelogd met . Voer uw wachtwoord in voor dit account. {plural_var,plural, =1{Uw wachtwoord is niet sterk genoeg. Gebruik ten minste teken en een combinatie van letters en cijfers.}other{Uw wachtwoord is niet sterk genoeg. Gebruik ten minste tekens en een combinatie van letters en cijfers.}} Zimbabwe -Volg de instructies die we naar hebben verzonden om uw wachtwoord te herstellen. -Er is een probleem met de verificatie van uw telefoonnummer +Het verzoek is niet geverifieerd vanwege een ontbrekende, ongeldige of verlopen OAuth-token. +Geef een zescijferige telefonische verificatiecode op. +Wachtwoord kiezen Nieuw apparaat of nieuwe browser gedetecteerd +Inloggen bij Tsjechië Tuvalu Inloggen met GitHub @@ -384,7 +421,7 @@ Als u op Verifiëren tikt, ontvangt u mogelijk een sms. Er kunnen kosten voor berichten en dataverbruik van toepassing zijn. Bolivia Puerto Rico -Link met account verwijderen? +Account ontkoppelen? Christmaseiland Met vriendelijke groet, @@ -398,6 +435,10 @@ Spanje Costa Rica Samoa + +Uw account in is geüpdatet met voor authenticatie in twee stappen. +Als u deze aanpassing niet heeft aangevraagd, maak deze dan ongedaan via de volgende link. + Mali Qatar Uruguay @@ -428,6 +469,7 @@ Bermuda Niue Guadeloupe +De client heeft een ongeldige projectconfiguratie opgegeven. Palestijnse gebieden Slowakije Guernsey @@ -440,19 +482,24 @@ Maleisië Cocoseilanden Als u uw -account nog steeds wilt koppelen, opent u de link op het apparaat waarop u het inlogproces bent gestart. Tik anders op Doorgaan om in te loggen op dit apparaat. +Opnieuw proberen U wilde aanvankelijk aan uw e-mailaccount koppelen, maar u heeft de link geopend op een ander apparaat, waarop u niet ingelogd bent. Code opnieuw verzenden over 0: Marokko +Als u dit apparaat niet herkent, probeert iemand anders mogelijk toegang tot uw account te krijgen. We raden u aan om uw wachtwoord onmiddellijk te wijzigen. Bangladesh Telefoon Finland Verenigde Arabische Emiraten +Uw e-mailadres is geverifieerd en gewijzigd Wachtwoord Centraal-Afrikaanse Republiek Curaçao +We hebben alle verzoeken van dit apparaat geblokkeerd vanwege ongebruikelijke activiteit. Probeer het later opnieuw. Tunesië Wachtwoord gewijzigd Griekenland +Tweede stap is verwijderd Angola Koeweit Het ingevoerde e-mailadres en wachtwoord komen niet overeen diff --git a/translations/no.xtb b/translations/no.xtb index 198ca9ab..0d9736a3 100644 --- a/translations/no.xtb +++ b/translations/no.xtb @@ -12,11 +12,14 @@ Filippinene Romania Sør-Georgia og de sørlige Sandwich-øyene -OK +Uopprettelig tap eller ødeleggelse av data. Italia Libanon Guyana Guatemala +Noe gikk galt ved fjerning av den andre faktoren.Prøv å fjerne den på nytt. Hvis dette ikke fungerer, kan du kontakte brukerstøtten for å få hjelp. +Prøv å oppdatere e-postadressen igjen +Det har oppstått en nettverksfeil. Prøv på nytt senere. Oppgi et gyldig telefonnummer Ved å trykke på Bekreft viser du at du godtar vilkårene for bruk og personvernreglene våre. Du kan bli tilsendt en SMS. Kostnader for meldinger og datatrafikk kan påløpe. Oppgi passordet ditt @@ -27,6 +30,7 @@ Det kommer for mange kontoforespørsler fra IP-adressen din. Prøv igjen om noen minutter. Sri Lanka Nå kan du logge på med den nye kontoen din +Klienten oppga et ugyldig argument. Tsjad Send koden på nytt om Hviterussland @@ -62,6 +66,7 @@ Github Personvernregler Guam +Ressurskvoten er nesten eller helt oppbrukt. Kroatia Litauen Malta @@ -71,22 +76,28 @@ Gjest Ghana Nettleseren du bruker, støtter ikke nettlagring. Prøv igjen i en annen nettleser. +Forespørselen ble avbrutt av klienten. Det oppsto et problem med å tilbakestille påloggingsadressen din.Be administratoren din om hjelp hvis du prøver igjen og fortsatt ikke får tilbakestilt e-postadressen. Bahrain +Intern tjenerfeil. Iran +GitHub Bekrefter at du ikke er en robot ... Denne koden er ikke lenger gyldig Ny-Caledonia Monaco Seychellene + ble fjernet som andre autentiseringstrinn. Kunne ikke oppgradere den anonyme brukeren. Den ikke-anonyme legitimasjonen er allerede tilknyttet en annen brukerkonto. Du har angitt feil passord for mange ganger. Prøv igjen om noen minutter. Sikkerhet Forespørselen om å tilbakestille passordet ditt har utløpt, eller så er linken allerede brukt +Nå kan du logge på med den nye e-postadressen, . Landskoden som er oppgitt, støttes ikke. Kontrollér at du ikke har skrevet e-postadressen din feil. Twitter Kambodsja +Tjenesten er ikke tilgjengelig. Portugal Fransk Guyana De britiske Jomfruøyer @@ -102,19 +113,21 @@ Bekreft Sør-Afrika Oppgi e-postadressen din for å fortsette +Brukerkontoen er deaktivert av en administrator. En påloggings-e-post med ytterligere veiledning er sendt til . Sjekk e-posten din for å fullføre påloggingen. Opprett en konto Afghanistan Når du trykker på , godtar du . Canada Danmark +Fristen for forespørselen er utløpt. Østerrike E-postadressene samsvarer ikke Sør-Sudan Jemen Guinea-Bissau Saudi-Arabia -App-logo +Det er lagt til en ekstra sikkerhetsfaktor i De amerikanske Jomfruøyer Logg på med telefonnummeret ditt Indonesia @@ -122,6 +135,7 @@ Venezuela Har du problemer med å logge på? Bekreft identiteten din +Det oppsto en feil ved autentisering av forespørselen. Start autentiseringsprosessen på nytt ved å gå til nettadressen du ble viderekoblet til denne siden fra. Dominica Nytt passord Gabon @@ -139,6 +153,7 @@ Gambia Ferdig Avbryt +Ukjent tjenerfeil. Oppgi kontonavnet ditt Irak Vietnam @@ -150,6 +165,8 @@ Nepal Tokelau Den valgte legitimasjonen for leverandøren av autentisering støttes ikke. +Det finnes ingen tilgjengelig påloggingsleverandør for den oppgitte e-postadressen. Prøv med en annen e-postadresse. +Skriv inn et for- og etternavn. Colombia Når du trykker på LAGRE, godtar du Burundi @@ -157,7 +174,7 @@ Twitter E-postadressen din er bekreftet Papua Ny-Guinea -Lagre +Fant ikke den angitte ressursen. Bekrefter … Logg på Øst-Timor @@ -190,6 +207,7 @@ Telefon Send på nytt Honduras +Operasjonen ble tidsavbrutt. Niger Denne e-postadressen samsvarer ikke med en eksisterende konto Jordan @@ -201,6 +219,7 @@ Israel Det har oppstått en feil Telefonnummer +Klienten har ikke den nødvendige tillatelsen. Svar på reCAPTCHA-en Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Turks- og Caicosøyene Send koden på nytt om Sjekk e-posten din +Kunne ikke fjerne den andre faktoren Legg til passord El Salvador +Du må logge på igjen for å endre til passordet for kontoen din. Serbia Belize for Du må åpne linken med den samme enheten eller nettleseren for at -kontoen din skal knyttes til denne e-posten. Sverige Saint-Martin +Forespørselen din om å bekrefte og oppdatere e-postadressen din er utløpt, eller linken er allerede brukt. Saint Lucia @string/app_name Sjekk e-posten din @@ -224,13 +246,15 @@ Oman Karibisk Nederland E-postadressen er oppdatert -Logg på med Twitter +Du er nå logget av. +Logg på med telefonnummeret ditt Telefonnummer Du har allerede en konto Thailand Personvernregler Liechtenstein Endre navnet +Enheten eller appen ble fjernet som andre autentiseringstrinn. Prøv å åpne linken med den samme enheten eller nettleseren som du brukte til å starte påloggingsprosessen. Nederland Ekvatorial-Guinea @@ -254,7 +278,7 @@ Oppgi e-postadressen din for å fortsette Bekreft telefonnummeret ditt Liberia -Det finnes allerede en konto med denne e-postadressen. +Logg av Madagaskar Kasakhstan Logger på … @@ -273,15 +297,20 @@ Angi den -sifrede koden vi sendte til Dette telefonnummeret er brukt for mange ganger E-postadressen du skrev inn, samsvarer ikke med den nåværende påloggingsøkten. -Fjern tilknytningen til kontoen +Twitter + +Logg på </p></p> + Kontrollér at innboksen din ikke er full, og se etter andre problemer knyttet til innboksinnstillingene. +Klienten oppga et ugyldig område. +Kvoten for denne operasjonen er overskredet. Prøv på nytt senere. Paraguay Algerie Du har angitt feil passord for mange ganger. Prøv igjen om noen minutter. Norfolkøya Telefonnummeret ble bekreftet automatisk -For å endre passordet ditt må du først oppgi det eksisterende passordet. -Tilbake +Nettverksfeil. Sjekk internettilkoblingen din. +Bekreftet. India Benin Fortsett @@ -330,27 +359,33 @@ E-post Neste Denne koden er ikke lenger gyldig -Logg på med Facebook +En påloggings-e-post med ytterligere instruksjoner er sendt til . Sjekk e-posten din for å fullføre påloggingen. Bekreft e-postadressen Fornavn og etternavn Grenada Vest-Sahara +Forespørselen kan ikke gjennomføres ved gjeldende systemtilstand. Det oppsto en ukjent feil. Du har allerede brukt for å logge på. Oppgi passordet ditt for denne kontoen. +Påloggings-e-post er sendt Irland Den demokratiske republikken Kongo Gjenopprett passordet Republikken Kongo +Ressursen som en klient forsøkte å opprette, finnes fra før. Fortsett som gjest San Marino Kypros Salomonøyene Vil du fortsette med ? +Du må gjenopprette passordet for å fortsette å logge på med på denne enheten. Maldivene São Tomé og Príncipe Bekreft e-postadressen din for å fullføre påloggingen Slovenia Sør-Korea +Konflikt knyttet til parallellkjøring, for eksempel en les-endre-skriv-konflikt. +API-metoden er ikke implementert av tjeneren. Lesotho Logg på med telefonnummeret ditt Prøv å bekrefte e-postadressen din på nytt @@ -370,8 +405,10 @@ for å logge på. Angi passordet ditt for denne kontoen. {plural_var,plural, =1{Passordet er ikke sterkt nok. Bruk minst tegn og en blanding av bokstaver og tall}other{Passordet er ikke sterkt nok. Bruk minst tegn og en blanding av bokstaver og tall}} Zimbabwe +Forespørselen er ikke autentisert på grunn av manglende, ugyldig eller utløpt OAuth-token. Kunne ikke bekrefte telefonnummeret ditt Ny enhet eller nettleser er registrert +Logg på Tsjekkia Tuvalu Logg på med GitHub @@ -426,6 +463,7 @@ Bermuda Niue Guadeloupe +Klienten spesifiserte en ugyldig prosjektkonfigurasjon. Palestinske territorier Slovakia Guernsey @@ -438,19 +476,24 @@ Malaysia Kokosøyene Hvis du fortsatt ønsker å koble til -kontoen din, åpner du linken på enheten du er pålogget med. Hvis ikke trykker du på Fortsett for å logge på med denne enheten. +Prøv på nytt Du forsøkte opprinnelig å koble til e-postkontoen din, men du åpnet linken på en annen enhet enn den du er pålogget med. Send koden på nytt om 0: Marokko +Hvis du ikke gjenkjenner denne enheten, kan det hende at noen prøver å få tilgang til kontoen din. Vurder å bytte passord umiddelbart. Bangladesh Telefon Finland De forente arabiske emirater +E-postadressen din er bekreftet og endret Passord Den sentralafrikanske republikk Curaçao +Vi har blokkert alle forespørsler fra denne enheten på grunn av uvanlig aktivitet. Prøv på nytt senere. Tunisia Passordet er endret Hellas +Fjernet den andre faktoren Angola Kuwait E-postadressen og passordet du har angitt, samsvarer ikke diff --git a/translations/pl.xtb b/translations/pl.xtb index 21bf8c2c..23fb46cc 100644 --- a/translations/pl.xtb +++ b/translations/pl.xtb @@ -12,11 +12,14 @@ Filipiny Rumunia Georgia Południowa i Sandwich Południowy -OK +Nieodwracalna utrata danych lub ich uszkodzenie. Włochy Liban Gujana Gwatemala +Coś poszło nie tak podczas usuwania drugiego składnika.Spróbuj usunąć go ponownie. Jeżeli to nie przyniesie rezultatu, skontaktuj się z działem pomocy. +Spróbuj jeszcze raz zaktualizować swój adres e-mail. +Wystąpił błąd sieci. Spróbuj jeszcze raz później. Wpisz prawidłowy numer telefonu Klikając Zweryfikuj, potwierdzasz, że akceptujesz nasze Warunki korzystania z usługi oraz Politykę prywatności. Może zostać wysłany SMS, przez co operator może pobrać opłatę za przesyłanie wiadomości i danych. Wpisz hasło @@ -27,6 +30,7 @@ Z tego adresu IP wysłano zbyt wiele próśb o utworzenie konta. Spróbuj ponownie za kilka minut. Sri Lanka Możesz się teraz zalogować na nowe konto +Klient podał nieprawidłowy argument. Czad Wyślij kod ponownie za Białoruś @@ -62,6 +66,7 @@ Github Polityka prywatności Guam +Limit zasobu został wyczerpany lub usługa aktywuje ograniczanie liczby żądań. Chorwacja Litwa Malta @@ -71,22 +76,28 @@ Gość Ghana Twoja przeglądarka nie obsługuje pamięci podręcznej. Spróbuj ponownie z inną przeglądarką. +Żądanie anulowane przez klienta. Nie mogliśmy zmienić Twojego adresu e-mail do logowania na używany wcześniej.Jeżeli przy kolejnej próbie znów nie uda Ci się go zresetować, poproś o pomoc administratora. Bahrajn +Wewnętrzny błąd serwera. Iran +GitHub Potwierdź, że nie jesteś robotem. Ten kod stracił ważność. Nowa Kaledonia Monako Seszele +Usunięto jako drugi etap uwierzytelniania. Nie udało się uaktualnić bieżącego użytkownika anonimowego. Nieanonimowe dane logowania są już powiązane z innym kontem użytkownika. Zbyt wiele razy podano niepoprawne hasło. Spróbuj jeszcze raz za kilka minut. Zabezpieczenia Twoja prośba o zresetowanie hasła straciła ważność albo ktoś kliknął już ten link +Teraz możesz się zalogować, używając nowego adresu email: . Podany kod kraju nie jest obsługiwany. Sprawdź, czy został wpisany poprawny adres e-mail. Twitter Kambodża +Usługa niedostępna. Portugalia Gujana Francuska Brytyjskie Wyspy Dziewicze @@ -102,6 +113,7 @@ Zweryfikuj Republika Południowej Afryki Aby kontynuować, wpisz adres e-mail +Konto użytkownika zostało wyłączone przez administratora. Na adres wysłaliśmy wiadomość umożliwiającą zalogowanie się z dodatkowymi instrukcjami. Sprawdź pocztę, by się zalogować. Wpisz hasło Tworzenie konta @@ -109,13 +121,14 @@ Klikając , potwierdzasz że znane Ci są i akceptujesz je. Kanada Dania +Upłynął termin realizacji żądania. Austria Adresy e-mail są niezgodne Sudan Południowy Jemen Gwinea Bissau Arabia Saudyjska -Logo aplikacji +Do aplikacji został dodany kolejny element zabezpieczający Wyspy Dziewicze Stanów Zjednoczonych Zaloguj się z użyciem numeru telefonu Indonezja @@ -123,6 +136,7 @@ Wenezuela Masz problem z zalogowaniem się? Potwierdź, że to Ty +Podczas uwierzytelniania żądania wystąpił problem. Otwórz jeszcze raz URL, z którego nastąpiło przekierowanie na tę stronę, by zacząć proces uwierzytelniania od początku. Dominika Nowe hasło Gabon @@ -140,6 +154,7 @@ Gambia Gotowe Anuluj +Nieznany błąd serwera. Wpisz nazwę konta Irak Wietnam @@ -151,6 +166,8 @@ Nepal Tokelau Wybrane dane logowania dla dostawcy weryfikacji nie są obsługiwane. +Dla podanego adresu e-mail nie jest dostępny żaden dostawca logowania. Spróbuj użyć innego adresu. +Wpisz imię i nazwisko. Kolumbia Klikając ZAPISZ, wyrażasz zgodę na Burundi @@ -158,7 +175,7 @@ Twitter Twój adres e-mail został zweryfikowany Papua Nowa Gwinea -Zapisz +Nie udało się znaleźć podanego zasobu. Weryfikuję… Zaloguj się Timor Wschodni @@ -191,6 +208,7 @@ Telefon Wyślij ponownie Honduras +Operacja przekroczyła limit czasu. Niger Nie istnieje konto, do którego pasuje ten adres e-mail. Jordania @@ -202,6 +220,7 @@ Izrael Wystąpił błąd Numer +Klient nie ma wystarczających uprawnień. Rozwiąż test reCAPTCHA Nikaragua Burkina Faso @@ -210,14 +229,17 @@ Turks i Caicos Kod można wysłać ponownie za: Sprawdź pocztę +Nie udało się usunąć drugiego składnika Dodaj hasło Salwador +Aby zmienić hasło do swojego konta, musisz zalogować się jeszcze raz. Serbia Belize dla konta Aby połączyć swoje konto w serwisie z tym adresem e-mail, musisz otworzyć link na tym samym urządzeniu lub w tej samej przeglądarce. Szwecja Saint-Martin +Twoja prośba o potwierdzenie i zmianę adresu e-mail wygasła lub link został już użyty. Saint Lucia @string/app_name Sprawdź pocztę @@ -225,19 +247,21 @@ Oman Karaiby Holenderskie Zaktualizowany adres e-mail -Zaloguj się przez Twittera +Wylogowano. +Zaloguj się z użyciem numeru telefonu Numer telefonu Masz już konto Tajlandia Nieprawidłowy kod. Spróbuj jeszcze raz. Lichtenstein Edycja nazwy +Usunięto urządzenie lub aplikację jako drugi etap uwierzytelniania. Otwórz link na tym samym urządzeniu, na którym rozpoczęto proces logowania, lub w tej samej przeglądarce. Holandia Gwinea Równikowa Australia Trynidad i Tobago -Wpisz nazwę +Wpisz, jak się nazywasz Wyspa Wniebowstąpienia Wystąpił błąd sieci Zaloguj się przez: @@ -255,7 +279,7 @@ Aby kontynuować, wpisz adres e-mail Weryfikowanie numeru telefonu Liberia -Istnieje już konto z tym adresem e-mail +Wylogowywanie Madagaskar Kazachstan Loguję… @@ -266,7 +290,7 @@ Adres e-mail był już używany przez Ciebie do logowania. Wpisz hasło tego konta. Anguilla Ten adres e-mail już istnieje, ale nie ma przypisanej metody logowania. Zresetuj hasło, aby odzyskać dostęp do konta. -Republika Dominikany +Dominikana Wyspa Jersey Odzyskiwanie hasła Wpisz sześciocyfrowy kod, który wysłaliśmy pod numer &lrm; @@ -274,15 +298,20 @@ Wpisz -cyfrowy kod, który wysłaliśmy na numer Ten numer telefonu został użyty zbyt wiele razy. Podany adres e-mail nie jest zgodny z obecną sesją logowania. -Odłączanie konta +Twitter + +Zaloguj się w aplikacji .</p></p> + Sprawdź, czy nie kończy się miejsce w skrzynce odbiorczej oraz czy nie ma innych problemów z ustawieniami skrzynki. +Klient podał nieprawidłowy zakres. +Limit dla tej operacji został przekroczony. Spróbuj jeszcze raz później. Paragwaj Algieria Zbyt wiele razy podano niepoprawne hasło. Spróbuj jeszcze raz za kilka minut. Wyspa Norfolk Numer telefonu został automatycznie zweryfikowany -Aby zmienić hasło, musisz najpierw wpisać to, którego używasz obecnie. -Wstecz +Błąd sieci. Sprawdź połączenie z internetem. +Zweryfikowano. Indie Benin Dalej @@ -331,27 +360,33 @@ E-mail Dalej Ten kod stracił ważność. -Zaloguj się przez Facebooka +Na adres wysłaliśmy e-maila umożliwiającego zalogowanie się z dodatkowymi instrukcjami. Sprawdź pocztę, by się zalogować. Potwierdź adres e-mail Imię i nazwisko Grenada Sahara Zachodnia +Żądania nie można wykonać przy aktualnym stanie systemu. Wystąpił nieznany błąd. Adres został już przez Ciebie użyty do zalogowania się. Wpisz hasło tego konta. +E-mail umożliwiający zalogowanie się został wysłany Irlandia Demokratyczna Republika Konga Odzyskiwanie hasła Republika Konga +Zasób, który próbował utworzyć klient, już istnieje. Kontynuuj jako gość San Marino Cypr Wyspy Salomona Czy chcesz nadal korzystać z adresu ? +Aby kontynuować logowanie się na tym urządzeniu za pomocą adresu , musisz odzyskać hasło. Malediwy Wyspy Świętego Tomasza i Książęca Potwierdź swój adres e-mail, aby dokończyć logowanie Słowenia Korea Południowa +Konflikt równoczesności, na przykład konflikt odczytu-modyfikacji-zapisu. +Metoda interfejsu API nie została zaimplementowana przez serwer. Lesoto Zaloguj się z użyciem numeru telefonu Spróbuj jeszcze raz zweryfikować adres e-mail @@ -370,9 +405,11 @@ Adres został już przez Ciebie użyty do logowania. Wpisz hasło tego konta. {plural_var,plural, =1{Hasło jest za słabe. Powinno mieć przynajmniej  znak i być kombinacją liter i cyfr.}few{Hasło jest za słabe. Powinno mieć przynajmniej  znaki i być kombinacją liter i cyfr.}many{Hasło jest za słabe. Powinno mieć przynajmniej  znaków i być kombinacją liter i cyfr.}other{Hasło jest za słabe. Powinno mieć przynajmniej  znaku i być kombinacją liter i cyfr}} Zimbabwe -Aby odzyskać hasło, wykonaj instrukcje wysłane na adres . -Podczas weryfikacji Twojego numeru telefonu wystąpił problem. +Żądanie nie zostało uwierzytelnione, ponieważ brakuje tokena OAuth, jest on nieprawidłowy lub wygasł. +Podaj sześciocyfrowy kod weryfikacyjny, który wysłaliśmy Ci SMS-em. +Wybierz hasło Wykryto nowe urządzenie lub nową przeglądarkę +Zaloguj się do Czechy Tuwalu Zaloguj się przez GitHuba @@ -398,6 +435,10 @@ Hiszpania Kostaryka Samoa + +Na Twoim koncie w aplikacji dodano element , by umożliwić uwierzytelnianie dwuskładnikowe. +Jeśli prośba o tę modyfikację nie pochodziła od Ciebie, użyj linku poniżej, aby cofnąć zmianę. + Mali Katar Urugwaj @@ -428,6 +469,7 @@ Bermudy Niue Gwadelupa +Klient podał nieprawidłową konfigurację projektu. Terytorium Palestyny Słowacja Wyspa Guernsey @@ -440,19 +482,24 @@ Malezja Wyspy Kokosowe Jeśli nadal chcesz połączyć swoje konto w serwisie , otwórz link na tym samym urządzeniu, na którym zaczęty został proces logowania. Aby zalogować się na obecnie używanym urządzeniu, wybierz „Dalej”. +Ponów Podjęto próbę połączenia konta w serwisie z adresem e-mail, ale link został otwarty na innym urządzeniu, na którym użytkownik nie był zalogowany. Kod można ponownie wysłać za 0: Maroko +Jeżeli nie rozpoznajesz tego urządzenia, to możliwe, że ktoś próbował uzyskać dostęp do Twojego konta. Rozważ natychmiastową zmianę hasła. Bangladesz Telefon Finlandia Zjednoczone Emiraty Arabskie +Twój adres e-mail został potwierdzony i zmieniony Hasło Republika Środkowej Afryki Curaçao +W związku z nietypową aktywnością zablokowaliśmy wszystkie żądania z tego urządzenia. Spróbuj jeszcze raz później. Tunezja Hasło zostało zmienione Grecja +Usunięto drugi składnik Angola Kuwejt Podany przez Ciebie adres e-mail i hasło nie zgadzają się diff --git a/translations/pt-BR.xtb b/translations/pt-BR.xtb index 472ffbca..8ae4e7c1 100644 --- a/translations/pt-BR.xtb +++ b/translations/pt-BR.xtb @@ -12,11 +12,14 @@ Filipinas Romênia Ilhas Geórgia do Sul e Sandwich do Sul -Ok +Perda de dados irrecuperável ou dados corrompidos. Itália Líbano Guiana Guatemala +Ocorreu um erro ao remover o segundo fator.Remova o fator novamente. Se isso não funcionar, entre em contato com o suporte para receber ajuda. +Atualize seu e-mail novamente +Ocorreu um erro na rede. Tente novamente mais tarde. Insira um número de telefone válido. Ao tocar em "Verificar", você concorda com nossos Termos de Serviço e a Política de Privacidade. Um SMS poderá ser enviado e tarifas de mensagens e de dados poderão ser cobradas. Digite sua senha @@ -27,6 +30,7 @@ Seu endereço IP está emitindo muitas solicitações de contas. Tente novamente em alguns minutos. Sri Lanka Faça login com a sua nova conta +O cliente especificou um argumento inválido. Chade Reenviar o código em Bielorrússia @@ -62,6 +66,7 @@ GitHub Política de Privacidade Guam +Fora da cota de recursos ou perto de atingir o limite de taxa. Croácia Lituânia Malta @@ -71,22 +76,28 @@ Convidado Gana O navegador usado não é compatível com o armazenamento na Web. Tente novamente em outro navegador. +Solicitação cancelada pelo cliente. Ocorreu um problema ao alterar seu e-mail de login.Se você tentar novamente e não conseguir redefini-lo, peça ajuda ao seu administrador. Barein +Ocorreu um erro interno no servidor. Irã +GitHub Verificando se você não é um robô... Este código não é mais válido. Nova Caledônia Mônaco Seicheles +O foi removido como segunda etapa de autenticação. Não foi possível fazer o upgrade deste usuário anônimo. A credencial não anônima já está associada a uma conta de usuário diferente. Você digitou a senha incorretamente várias vezes. Tente novamente em alguns minutos. Segurança Sua solicitação para redefinir a senha expirou ou o link já foi usado +Agora é possível fazer login com seu novo e-mail . Este código do país não é aceito. Verifique se você digitou seu e-mail corretamente. Twitter Camboja +Serviço indisponível. Portugal Guiana Francesa Ilhas Virgens Britânicas @@ -102,6 +113,7 @@ Verificar África do Sul Digite seu endereço de e-mail para continuar +A conta do usuário foi desativada pelo administrador. Um e-mail de login com mais instruções foi enviado para . Verifique seu e-mail para concluir o login. Digite sua senha Criar conta @@ -109,13 +121,14 @@ Ao tocar em "", você indica que está de acordo com os . Canadá Dinamarca +O prazo de solicitação foi excedido. Áustria Os e-mails não coincidem Sudão do Sul Iêmen Guiné-Bissau Arábia Saudita -Logotipo do app +Um fator de segurança adicional foi adicionado ao app Ilhas Virgens Americanas Fazer login com o telefone Indonésia @@ -123,6 +136,7 @@ Venezuela Problemas com login? Confirmar sua identidade +Houve um problema ao autenticar sua solicitação. Acesse novamente o URL que redirecionou você para esta página e reinicie o processo de autenticação. Dominica Nova senha Gabão @@ -140,6 +154,7 @@ Gâmbia Concluído Cancelar +Erro desconhecido no servidor. Digite o nome da sua conta Iraque Vietnã @@ -151,6 +166,8 @@ Nepal Toquelau Não há suporte para as credenciais selecionadas do provedor de autenticação. +Nenhum provedor de login está disponível para o e-mail especificado. Tente usar um e-mail diferente. +Digite um nome e um sobrenome. Colômbia Ao tocar em SALVAR, você indica que concordou com os Burundi @@ -158,7 +175,7 @@ Twitter Seu e-mail foi verificado Papua-Nova Guiné -Salvar +O recurso especificado não foi encontrado. Verificando... Fazer login Timor Leste @@ -191,6 +208,7 @@ Telefone Reenviar Honduras +A operação expirou. Níger O endereço de e-mail não corresponde a uma conta existente Jordânia @@ -202,6 +220,7 @@ Israel Erro encontrado Número +O cliente não tem permissão suficiente. Insira o reCAPTCHA. Nicarágua Burkina Fasso @@ -210,14 +229,17 @@ Ilhas Turks e Caicos Reenviar o código em Verifique seu e-mail +Não foi possível remover o segundo fator Adicionar senha El Salvador +Para alterar a senha da sua conta, faça login novamente. Sérvia Belize para Para conectar sua conta do com este e-mail, é necessário abrir o link no mesmo dispositivo ou navegador. Suécia São Martinho +Sua solicitação para verificar e atualizar seu e-mail expirou ou o link já foi usado. Santa Lúcia @string/app_name Verifique seu e-mail @@ -225,13 +247,15 @@ Omã Países Baixos Caribenhos Endereço de e-mail atualizado -Fazer login com o Twitter +Você saiu da página. +Fazer login com o telefone Número de telefone Você já tem uma conta Tailândia Código incorreto. Tente novamente. Principado de Liechtenstein Editar nome +O dispositivo ou aplicativo foi removido como segunda etapa de autenticação. Tente abrir o link usando o mesmo dispositivo ou navegador que você usou para iniciar o processo de login. Holanda Guiné Equatorial @@ -255,7 +279,7 @@ Digite seu endereço de e-mail para continuar Confirmar seu número de telefone Libéria -Já existe uma conta com esse endereço de e-mail. +Sair Madagascar Cazaquistão Fazendo login… @@ -274,15 +298,20 @@ Insira o código de dígitos que enviamos Este número de telefone já foi usado muitas vezes. O e-mail fornecido é diferente do usado no login da sessão atual. -Desvincular conta +Twitter + +Faça login em </p></p> + Verifique se você ainda tem espaço na sua caixa de entrada, além de outros problemas relacionados à configuração. +O cliente especificou um intervalo inválido. +A cota desta operação foi excedida. Tente novamente mais tarde. Paraguai Argélia Você digitou a senha incorretamente várias vezes. Tente novamente em alguns minutos. Ilha Norfolk O número de telefone foi verificado automaticamente -Para alterar sua senha, primeiro insira sua senha atual. -Voltar +Erro de rede, confira sua conexão de Internet. +Verificado. Índia Benin Continuar @@ -331,27 +360,33 @@ E-mail Próxima Este código não é mais válido. -Fazer login com o Facebook +Um e-mail de login com mais instruções foi enviado para . Verifique sua caixa de entrada para concluir o login. Confirmar e-mail Nome e sobrenome Granada Saara Ocidental +A solicitação não pode ser executada no estado atual do sistema. Ocorreu um erro desconhecido. Você já usou o e-mail para fazer login. Digite sua senha para essa conta. +E-mail de login enviado Irlanda República Democrática do Congo Recuperar a senha República do Congo +O recurso que um cliente tentou criar já existe. Continuar como convidado San Marino Chipre Ilhas Salomão Continuar com ? +Para continuar a fazer login com o e-mail neste dispositivo, será necessário recuperar a senha. Maldivas São Tomé e Príncipe Confirme seu e-mail para concluir o login Eslovênia Coreia do Sul +Conflito de simultaneidade, como leitura-modificação-gravação. +Método da API não implementado pelo servidor. Lesoto Fazer login com o telefone Verifique seu e-mail novamente @@ -370,9 +405,11 @@ Você já usou o e-mail para fazer login. Insira a senha dessa conta. {plural_var,plural, =1{A senha não é forte o suficiente. Use pelo menos caractere e combine letras e números.}one{A senha não é forte o suficiente. Use pelo menos caractere e combine letras e números.}other{A senha não é forte o suficiente. Use pelo menos caracteres e combine letras e números.}} Zimbábue -Siga as instruções enviadas para para recuperar sua senha. -Ocorreu um problema na verificação do seu número de telefone. +Solicitação não autenticada devido ao token OAuth ausente, inválido ou expirado. +Forneça um código de verificação do smartphone de 6 dígitos. +Escolha a senha Novo dispositivo ou navegador detectado +Fazer login no locatário República Tcheca Tuvalu Fazer login com o GitHub @@ -398,6 +435,10 @@ Espanha Costa Rica Samoa + +Sua conta no app foi atualizada com para a autenticação de dois fatores. +Se você não solicitou essa modificação, use o link a seguir para desfazer essa mudança. + Mali Catar Uruguai @@ -428,6 +469,7 @@ Bermudas Niue Guadalupe +O cliente especificou uma configuração de projeto inválida. Territórios Palestinos Eslováquia Guernsey @@ -440,19 +482,24 @@ Malásia Ilhas Coco (Keeling) Se você ainda quiser conectar sua conta do , abra o link no mesmo dispositivo em que você iniciou o login. Caso contrário, toque em Continuar para fazer login neste dispositivo. +Tentar novamente Inicialmente, você quis conectar o à sua conta de e-mail, mas abriu o link em um dispositivo diferente, em que você não está conectado. Reenviar código em 0: Marrocos +Se você não reconhece este dispositivo, alguém pode estar tentando acessar sua conta. Considere alterar sua senha agora mesmo. Bangladesh Smartphone Finlândia Emirados Árabes Unidos +Seu e-mail foi verificado e alterado Senha República Centro-Africana Curaçao +Bloqueamos todas as solicitações deste dispositivo devido a uma atividade incomum. Tente novamente mais tarde. Tunísia Senha alterada Grécia +Segundo fator removido Angola Kuwait O e-mail e a senha que você digitou não coincidem diff --git a/translations/pt-PT.xtb b/translations/pt-PT.xtb index ef158170..2532fb8e 100644 --- a/translations/pt-PT.xtb +++ b/translations/pt-PT.xtb @@ -12,11 +12,14 @@ Filipinas Roménia Ilhas Geórgia do Sul e Sandwich do Sul -OK +Perda de dados irrecuperável ou corrupção de dados. Itália Líbano Guiana Guatemala +Ocorreu um erro ao remover o seu segundo fator.Experimente removê-lo novamente. Se isso não funcionar, contacte o apoio técnico para obter assistência. +Experimente atualizar o seu email novamente +Ocorreu um erro de rede. Tente novamente mais tarde. Introduza um número de telefone válido Ao tocar em Validar, indica que aceita os Termos de Utilização e a Política de Privacidade. Pode gerar o envio de uma SMS. Podem aplicar-se tarifas de dados e de mensagens. Introduza a palavra-passe @@ -27,6 +30,7 @@ O seu endereço de IP enviou demasiados pedidos de conta. Tente novamente dentro de alguns minutos. Sri Lanca Já pode iniciar sessão com a nova conta +O cliente especificou um argumento inválido. Chade Ao tocar em "", indica que aceita os e a . Pode gerar o envio de uma SMS. Podem aplicar-se tarifas de dados e de mensagens. Bielorrússia @@ -62,6 +66,7 @@ Github Política de Privacidade Guame +Sem quota de recursos ou a atingir a limitação de velocidade. Croácia Lituânia Malta @@ -71,21 +76,27 @@ Convidado Gana O navegador que está a utilizar não suporta armazenamento na Web. Tente novamente com um navegador diferente. +Pedido cancelado pelo cliente. Ocorreu um problema ao reverter o email de início de sessão.Se após uma segunda tentativa não for possível repor o email, solicite ajuda ao gestor. Barém +Erro do servidor interno. Irão +GitHub A verificar se é um robô... Este código já não é válido Nova Caledónia Mónaco Seicheles +O foi removido como segundo passo de autenticação. Ocorreu uma falha na atualização do utilizador anónimo atual. A credencial não anónima já está associada a outra conta de utilizador. Introduziu uma palavra-passe incorreta demasiadas vezes. Tente novamente dentro de alguns minutos. O pedido para repor a palavra-passe expirou ou o link já foi utilizado +Já pode iniciar sessão com o seu novo email . O código de país fornecido não é suportado. Certifique-se de que não introduziu o seu email incorretamente. Twitter Camboja +Serviço indisponível. Portugal Guiana francesa Ilhas Virgens Britânicas @@ -101,19 +112,21 @@ Validar África do Sul Introduza o endereço de email para continuar +A conta de utilizador foi desativada por um administrador. Enviámos um email de início de sessão com instruções adicionais para . Consulte o seu email para concluir o início de sessão. Criar conta Afeganistão Ao tocar em , indica que aceita os . Canadá Dinamarca +Prazo do pedido excedido. Áustria Os emails não coincidem Sudão do Sul Iémen Guiné-Bissau Arábia Saudita -Logótipo da aplicação +Foi adicionado um fator de segurança extra à aplicação Ilhas Virgens Americanas Iniciar sessão com o telemóvel Indonésia @@ -121,6 +134,7 @@ Venezuela Problemas ao iniciar sessão? Valide a sua identidade +Ocorreu um problema ao autenticar o seu pedido. Aceda novamente ao URL que o redirecionou para esta página, para reiniciar o processo de autenticação. Domínica Nova palavra-passe Gabão @@ -138,6 +152,7 @@ Gâmbia Concluído Cancelar +Erro de servidor desconhecido. Introduza o nome da conta Iraque Vietname @@ -149,13 +164,15 @@ Nepal Toquelau As credenciais selecionadas para o fornecedor de autenticação não são suportadas! +Nenhum fornecedor de início de sessão disponível para o email indicado. Experimente com outro email. +Introduza um nome próprio e um apelido. Colômbia Burundi Mauritânia Twitter O email foi validado Papua-Nova Guiné -Guardar +Não foi possível encontrar o recurso especificado. A validar... Iniciar sessão Timor Leste @@ -188,6 +205,7 @@ Telemóvel Reenviar Honduras +A operação excedeu o limite de tempo. Níger O endereço de email introduzido não corresponde a uma conta existente Jordânia @@ -199,6 +217,7 @@ Israel Ocorreu um erro Número +O cliente não tem autorização suficiente. Resolva o reCAPTCHA Nicarágua Burquina Faso @@ -207,27 +226,32 @@ Ilhas Turcas e Caicos Reenviar código em Verifique o seu email +Não foi possível remover o seu segundo fator Adicionar palavra-passe Salvador +Para alterar a palavra-passe da sua conta, tem de iniciar sessão novamente. Sérvia Belize para Para que este fluxo associe com sucesso a sua conta do a este email, tem de abrir o link no mesmo dispositivo ou navegador. Suécia São Martinho +O seu pedido para validar e atualizar o email expirou ou o link já foi utilizado. Santa Lúcia Verifique o seu email O seu pedido de validação do email expirou ou o link já foi utilizado Omã Países Baixos Caribenhos Endereço de email atualizado -Iniciar sessão com o Twitter +Terminou a sessão com sucesso. +Iniciar sessão com o telemóvel Número de telefone Já tem uma conta Tailândia Política de Privacidade Listenstaine Editar nome +O dispositivo ou a aplicação foi removido como segundo passo de autenticação. Experimente abrir o link através do mesmo dispositivo ou navegador no qual iniciou o processo de início de sessão. Países Baixos Guiné Equatorial @@ -251,7 +275,7 @@ Introduza o endereço de email para continuar Validar o número de telefone Libéria -Já existe uma conta com o endereço de email em questão. +Terminar sessão Madagáscar Cazaquistão A iniciar sessão… @@ -270,15 +294,20 @@ Introduza o código de dígitos que enviámos para Este número de telefone foi utilizado demasiadas vezes O email fornecido não corresponde à sessão atual. -Desassociar conta +Twitter + +Inicie sessão em </p></p> + Certifique-se de que a sua caixa de entrada não está a ficar sem espaço disponível nem está com outros problemas relacionados com as definições da caixa de entrada. +O cliente especificou uma alteração inválida. +A quota para esta operação foi excedida. Tente novamente mais tarde. Paraguai Argélia Introduziu uma palavra-passe errada demasiadas vezes. Tente novamente dentro de alguns minutos. Ilha Norfolk Número de telefone verificado automaticamente -Para alterar a palavra-passe, primeiro tem de introduzir a palavra-passe atual. -Anterior +Erro de rede. Verifique a sua ligação à Internet. +Validado! Índia Benim Continuar @@ -327,27 +356,33 @@ Email Seguinte Este código já não é válido -Iniciar sessão com o Facebook +Enviámos um email de início de sessão com instruções adicionais para . Consulte o seu email para concluir o início de sessão. Confirmar email Nome próprio e apelido Granada Sara Ocidental +Não é possível executar o pedido no estado atual do sistema. Ocorreu um erro desconhecido. Já utilizou para iniciar sessão. Introduza a palavra-passe da conta em questão. +Email de início de sessão enviado. Irlanda República Democrática do Congo Recuperar a palavra-passe República do Congo +O recurso que um cliente tentou criar já existe. Continuar como convidado São Marino Chipre Ilhas Salomão Pretende continuar com ? +Para continuar a iniciar sessão com neste dispositivo, tem de recuperar a palavra-passe. Maldivas São Tomé e Príncipe Confirme o seu email para concluir o início de sessão Eslovénia Coreia do Sul +Conflito de simultaneidade, tal como conflito de ler-modificar-gravar. +Método de API não implementado pelo servidor. Lesoto Iniciar sessão com o telemóvel Tente validar o email novamente @@ -366,8 +401,10 @@ Já utilizou para iniciar sessão. Introduza a palavra-passe da conta em questão. {plural_var,plural, =1{A palavra-passe não é suficientemente forte. Utilize, pelo menos, caráter e uma combinação de letras e números}other{A palavra-passe não é suficientemente forte. Utilize, pelo menos, caracteres e uma combinação de letras e números}} Zimbabué +Pedido não autenticado devido a token OAuth em falta, inválido ou expirado. Ocorreu um problema ao validar o número de telefone Novo dispositivo ou navegador detetado +Iniciar sessão em República Checa Tuvalu Iniciar sessão com o GitHub @@ -422,6 +459,7 @@ Bermudas Niuê Guadalupe +O cliente especificou uma configuração de projeto inválida. Territórios Palestinianos Eslováquia Guernsey @@ -434,19 +472,24 @@ Malásia Ilhas Cocos Se ainda pretender associar a sua conta do , abra o link no mesmo dispositivo em que iniciou sessão. Caso contrário, toque em Continuar para iniciar sessão neste dispositivo. +Tentar novamente Originalmente, pretendia associar a sua conta do à sua conta de email, mas abriu o link num dispositivo diferente no qual não tem sessão iniciada. Reenviar código em 0: Marrocos +Se não reconhecer este dispositivo, alguém pode estar a tentar aceder à sua conta. Considere alterar a sua palavra-passe imediatamente. Bangladeche Telefone Finlândia Emirados Árabes Unidos +O seu email foi validado e alterado Palavra-passe República Centro-Africana Curaçau +Bloqueámos todos os pedidos deste dispositivo devido a atividade invulgar. Tente novamente mais tarde. Tunísia Palavra-passe alterada Grécia +O segundo fator foi removido Angola Koweit O email e a palavra-passe introduzidos não coincidem diff --git a/translations/ro.xtb b/translations/ro.xtb index 3c56986c..6adeb3e1 100644 --- a/translations/ro.xtb +++ b/translations/ro.xtb @@ -12,11 +12,14 @@ Filipine România Georgia de Sud și Insulele Sandwich de Sud -OK +Pierdere de date care nu poate fi remediată sau corupere de date. Italia Liban Guyana Guatemala +A apărut o eroare la eliminarea celui de-al doilea factor.Încercați să îl eliminați din nou. Dacă această soluție nu funcționează, contactați serviciul de asistență. +Încercați să vă actualizați e-mailul din nou. +A apărut o eroare de rețea. Încercați din nou mai târziu. Introduceți un număr de telefon valid. Dacă atingeți Confirmați, sunteți de acord cu Termenii și condițiile și cu Politica de confidențialitate. Poate fi trimis un SMS. Se pot aplica tarife pentru mesaje și date. Introduceți parola @@ -27,6 +30,7 @@ Au fost create prea multe solicitări de conturi de la această adresă IP. Încercați din nou peste câteva minute. Sri Lanka Acum vă puteți conecta cu noul cont +Clientul a specificat un argument nevalid. Ciad Retrimiteți codul în Belarus @@ -62,6 +66,7 @@ Github Politică de confidențialitate Guam +Fie din cota resursei, fie atingând limitarea frecvenței. Croația Lituania Malta @@ -71,22 +76,28 @@ Invitat Ghana Browserul pe care îl folosiți nu acceptă stocarea datelor de pe web. Încercați din nou în alt browser. +Solicitarea a fost anulată de client. A apărut o problemă la restabilirea e-mailului de conectare.Dacă încercați din nou și tot nu vă puteți reseta adresa de e-mail, cereți ajutorul administratorului. Bahrain +Eroare internă de server. Iran +GitHub Se verifică dacă sunteți un robot... Codul nu mai este valid Noua Caledonie Monaco Seychelles + a fost eliminat ca cel de-al doilea pas al autentificării. Nu am putut face upgrade pentru actualul utilizator anonim, deoarece datele de conectare sunt deja asociate altui cont de utilizator. Ați introdus de prea multe ori o parolă incorectă. Încercați din nou peste câteva minute. Securitate Solicitarea de resetare a parolei a expirat sau linkul a fost folosit deja +Acum vă puteți conecta cu noul e-mail . Codul de țară introdus nu este acceptat. Verificați dacă ați scris corect adresa de e-mail. Twitter Cambodgia +Serviciu indisponibil. Portugalia Guyana Franceză Insulele Virgine Britanice @@ -102,19 +113,21 @@ Confirmați Africa de Sud Introduceți adresa de e-mail pentru a continua +Contul de utilizator a fost dezactivat de un administrator. Un e-mail de conectare cu instrucțiuni suplimentare a fost trimis la . Verificați-vă căsuța de e-mail pentru a finaliza conectarea. Creați un cont Afganistan Dacă atingeți , indicați că sunteți de acord cu . Canada Danemarca +Solicitarea pentru termenul limită a fost depășită. Austria Adresele de e-mail nu sunt identice Sudanul de Sud Yemen Guineea-Bissau Arabia Saudită -Sigla aplicației +Un factor pentru securitate sporită a fost adăugat în Insulele Virgine Americane Conectați-vă cu numărul de telefon Indonezia @@ -122,6 +135,7 @@ Venezuela Nu reușiți să vă conectați? Confirmați-vă identitatea +A apărut o problemă la autentificarea solicitării dvs. Accesați din nou adresa URL de la care ați fost redirecționat(ă) către această pagină pentru a relua procesul de autentificare. Dominica Parolă nouă Gabon @@ -139,6 +153,7 @@ Gambia Gata Anulați +Eroare necunoscută a serverului. Introduceți numele contului Irak Vietnam @@ -150,6 +165,8 @@ Nepal Tokelau Datele de conectare selectate nu sunt acceptate de furnizorul de autentificare. +Nu există nicio metodă de conectare disponibilă pentru această adresă de e-mail; încercați cu altă adresă de e-mail. +Introduceți numele și prenumele. Columbia Dacă atingeți SALVAȚI, indicați că sunteți de acord cu Burundi @@ -157,7 +174,7 @@ Twitter Adresa de e-mail a fost confirmată Papua-Noua Guinee -Salvați +Nu se găsește resursa specificată. Se confirmă... Conectați-vă Timorul de Est @@ -190,6 +207,7 @@ Număr de telefon Retrimiteți Honduras +Operația a expirat. Niger Adresa de e-mail nu corespunde unui cont existent Iordania @@ -201,6 +219,7 @@ Israel A apărut o eroare Număr de telefon +Clientul nu are suficientă permisiune. Introduceți reCAPTCHA Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Insulele Turks și Caicos Retrimiteți codul în Verificați căsuța de e-mail +Nu s-a putut elimina al doilea factor Adăugați parola El Salvador +Pentru a schimba parola contului, va trebui să vă conectați din nou. Serbia Belize pentru Pentru ca acest flux să conecteze contul la această adresă de e-mail, trebuie să accesați linkul pe același dispozitiv sau browser. Suedia Saint-Martin +Solicitarea de confirmare și actualizare a adresei de e-mail a expirat sau linkul a fost folosit deja. Saint Lucia @string/app_name Verificați căsuța de e-mail @@ -224,13 +246,15 @@ Oman Antilele Neerlandeze Adresa de e-mail a fost actualizată -Conectați-vă cu Twitter +Ați reușit să vă deconectați. +Conectați-vă cu numărul de telefon Număr de telefon Aveți deja un cont Thailanda Politica de confidențialitate Liechtenstein Modificați numele +Dispozitivul sau aplicația a fost eliminat(ă) ca cel de-al doilea pas al autentificării. Încercați să accesați linkul folosind același dispozitiv sau browser pe care ați început procesul de conectare. Țările de Jos Guineea Ecuatorială @@ -254,7 +278,7 @@ Introduceți adresa de e-mail pentru a continua Confirmați numărul de telefon Liberia -Există deja un cont cu respectiva adresă de e-mail. +Deconectați-vă Madagascar Kazahstan Se conectează… @@ -273,15 +297,20 @@ Introduceți codul din cifre pe care l-am trimis la Acest număr de telefon a fost folosit de prea multe ori Adresa de e-mail nu corespunde sesiunii curente de conectare. -Deconectați contul +Twitter + +Conectați-vă la </p></p> + Verificați dacă aveți spațiu în căsuța de e-mail sau dacă există alte probleme legate de setările căsuței de e-mail. +Clientul a specificat o zonă nevalidă. +Cota pentru această operație a fost depășită. Încercați din nou mai târziu. Paraguay Algeria Ați introdus de prea multe ori o parolă greșită. Încercați din nou peste câteva minute. Insula Norfolk Numărul de telefon este verificat automat -Pentru a vă schimba parola, trebuie să introduceți parola actuală. -Înapoi +Eroare de rețea, verificați conexiunea la internet. +Confirmat! India Benin Continuați @@ -330,27 +359,33 @@ Adresă de e-mail Înainte Codul nu mai este valid -Conectați-vă cu Facebook +Un e-mail de conectare cu instrucțiuni suplimentare a fost trimis la . Verificați-vă căsuța de e-mail pentru a finaliza conectarea. Confirmați adresa de e-mail Prenume și nume de familie Grenada Sahara de Vest +Solicitarea nu poate fi executată în starea curentă a sistemului. A apărut o eroare necunoscută. Ați folosit deja pentru a vă conecta. Introduceți parola acestui cont. +A fost trimis e-mailul de conectare Irlanda Republica Democratică Congo Recuperați parola Republica Congo +Resursa pe care un client a încercat să o creeze există deja. Continuați ca invitat San Marino Cipru Insulele Solomon Continuați cu ? +Pentru a vă conecta cu pe acest dispozitiv, trebuie să recuperați parola. Maldive São Tomé și Príncipe Confirmați adresa de e-mail pentru a finaliza conectarea Slovenia Coreea de Sud +Conflict de simultaneitate, respectiv conflict citire-modificare-scriere. +Metoda API nu a fost implementată de server. Lesotho Conectați-vă cu numărul de telefon Încercați să vă confirmați din nou adresa de e-mail @@ -369,8 +404,10 @@ Ați folosit deja pentru a vă conecta. Introduceți parola acestui cont. {plural_var,plural, =1{Parola nu este suficient de puternică. Folosiți cel puțin caracter și o combinație de litere și cifre.}few{Parola nu este suficient de puternică. Folosiți cel puțin caractere și o combinație de litere și cifre.}other{Parola nu este suficient de puternică. Folosiți cel puțin de caractere și o combinație de litere și cifre.}} Zimbabwe +Solicitarea nu a fost autentificată din cauza indicativului OAuth care fie lipsește, fie este nevalid, fie a expirat. A apărut o problemă la confirmarea numărului de telefon A fost detectat un nou dispozitiv sau browser +Conectați-vă la Republica Cehă Tuvalu Conectați-vă cu GitHub @@ -425,6 +462,7 @@ Bermuda Niue Guadelupa +Clientul a specificat o configurare nevalidă a proiectului. Teritoriile palestiniene Slovacia Guernsey @@ -437,19 +475,24 @@ Malaysia Insulele Cocos [Keeling] Dacă încă doriți să vă asociați contul , deschideți linkul pe același dispozitiv pe care ați început conectarea. În caz contrar, atingeți Continuați pentru a vă conecta pe acest dispozitiv. +Reîncercați Inițial ați intenționat să vă asociați la contul de e-mail, dar ați deschis linkul pe alt dispozitiv pe care nu v-ați conectat. Retrimiteți codul în 00: Maroc +Dacă nu recunoașteți acest dispozitiv, este posibil ca o persoană să încerce să vă acceseze contul. Vă recomandăm să vă schimbați parola chiar acum. Bangladesh Număr de telefon Finlanda Emiratele Arabe Unite +E-mailul dvs. a fost confirmat și modificat Parolă Republica Centrafricană Curaçao +Am blocat toate solicitările de pe acest dispozitiv, din cauza unei activități neobișnuite. Încercați din nou mai târziu. Tunisia Parola a fost schimbată Grecia +S-a eliminat al doilea factor Angola Kuweit Adresa de e-mail și parola pe care le-ați introdus nu se potrivesc diff --git a/translations/ru.xtb b/translations/ru.xtb index a9424657..034699c5 100644 --- a/translations/ru.xtb +++ b/translations/ru.xtb @@ -12,11 +12,14 @@ Филиппины Румыния Южная Джорджия и Южные Сандвичевы острова -ОК +Данные потеряны или повреждены и не подлежат восстановлению. Италия Ливан Гайана Гватемала +Не удалось отменить двухэтапную аутентификацию.Повторите попытку. Если проблема возникнет снова, обратитесь в службу поддержки. +Попробуйте изменить адрес электронной почты ещё раз +Произошла ошибка сети. Повторите попытку позже. Введите действительный номер телефона. Нажимая "Подтвердить", вы принимаете наши Условия использования и Политику конфиденциальности, а также соглашаетесь получить SMS. За его отправку и обмен данными может взиматься плата. Введите пароль. @@ -27,6 +30,7 @@ С вашего IP-адреса поступает слишком много запросов на создание аккаунта. Повторите попытку через несколько минут. Шри-Ланка Теперь вы можете входить в систему через новый аккаунт +Указан недопустимый аргумент. Чад Код можно будет запросить ещё раз через Беларусь @@ -62,6 +66,7 @@ GitHub Политика конфиденциальности Гуам +Исчерпана квота для ресурса, или превышено ограничение частоты запросов. Хорватия Литва Мальта @@ -71,22 +76,28 @@ Гость Гана Ваш браузер не поддерживает веб-хранилище. Повторите попытку в другом браузере. +Запрос отменен клиентом. Не удалось восстановить адрес электронной почты для входа.Повторите попытку. Если ошибка возникнет снова, обратитесь к администратору. Бахрейн +Внутренняя ошибка сервера. Иран +GitHub Нам нужно убедиться, что вы не робот... Этот код уже неактивен. Новая Каледония Монако Сейшелы +Номер телефона больше не будет использоваться для двухэтапной аутентификации. Не удалось обновить текущий анонимный аккаунт, поскольку указанные учетные данные уже связаны с другим аккаунтом пользователя. Вы неправильно ввели пароль слишком много раз. Прежде чем повторить попытку, подождите несколько минут. Безопасность Срок действия запроса на сброс пароля истек, или ссылка уже использована. +Теперь для входа в систему можно использовать адрес . Указанный код страны не поддерживается. Убедитесь, что правильно указали свой адрес электронной почты. Twitter Камбоджа +Сервис недоступен. Португалия Французская Гвиана Британские Виргинские острова @@ -102,6 +113,7 @@ Подтвердить Южная Африка Введите адрес электронной почты. +Аккаунт пользователя отключен администратором. Мы отправили на адрес электронной почты письмо с информацией о том, как войти в аккаунт. Изучите его и выполните инструкции. Введите пароль Создание аккаунта @@ -109,13 +121,14 @@ Нажимая кнопку "", вы принимаете . Канада Дания +Срок выполнения запроса истек. Австрия Адреса электронной почты не совпадают. Южный Судан Йемен Гвинея-Бисау Саудовская Аравия -Логотип приложения +В приложение "" добавлен дополнительный уровень защиты Виргинские острова Соединенных Штатов Войти по номеру телефона Индонезия @@ -123,6 +136,7 @@ Венесуэла Не удается войти в аккаунт? Подтвердите, что это вы +Во время аутентификации запроса произошла ошибка. Чтобы возобновить процесс, снова перейдите по URL, который перенаправляет на эту страницу. Доминика Новый пароль Габон @@ -140,6 +154,7 @@ Гамбия Готово Отмена +Неизвестная ошибка сервера. Введите название аккаунта. Ирак Вьетнам @@ -151,6 +166,8 @@ Непал Токелау Поставщик услуг аутентификации не поддерживает эти учетные данные. +Войти с помощью этого электронного адреса нельзя. Укажите другой. +Введите имя и фамилию. Колумбия Нажимая кнопку "Сохранить", вы принимаете Бурунди @@ -158,7 +175,7 @@ Twitter Адрес электронной почты подтвержден Папуа Новая Гвинея -Сохранить +Указанный ресурс не найден. Проверка... Войти Тимор-Лесте @@ -191,6 +208,7 @@ Телефон Отправить ещё раз Гондурас +Превышено время ожидания операции. Нигер Нет аккаунта с таким адресом электронной почты. Иордания @@ -202,6 +220,7 @@ Израиль Ошибка Номер телефона +У клиента недостаточно прав. Пройдите проверку reCAPTCHA. Никарагуа Буркина-Фасо @@ -210,14 +229,17 @@ Острова Теркс и Кайкос Отправить код ещё раз можно будет через  Проверьте электронную почту +Не удалось отменить двухэтапную аутентификацию Добавить пароль Эль-Сальвадор +Чтобы изменить пароль, нужно будет войти в аккаунт ещё раз. Сербия Белиз для аккаунта Чтобы связать аккаунт сервиса "" с указанным адресом электронной почты, нужно открыть ссылку в текущем браузере или на устройстве, которое вы сейчас используете. Швеция Сен-Мартен +Срок действия запроса на проверку и изменение адреса электронной почты истек, или ссылка уже использована. Сент-Люсия @string/app_name Проверьте электронную почту @@ -225,13 +247,15 @@ Оман Бонэйр, Синт-Эстатиус и Саба Адрес электронной почты изменен -Войти через аккаунт Twitter +Вы вышли из системы. +Войти по номеру телефона Номер телефона У вас уже есть аккаунт Таиланд Неверный код. Повторите попытку. Лихтенштейн Изменить имя +Номер телефона или приложение больше не будет использоваться для двухэтапной аутентификации. Попробуйте открыть ссылку в браузере или на устройстве, которые вы использовали, чтобы начать процесс входа Нидерланды Экваториальная Гвинея @@ -255,7 +279,7 @@ Введите адрес электронной почты. Подтвердите свой номер телефона Либерия -Аккаунт с таким адресом электронной почты уже существует. +Выход Мадагаскар Казахстан Выполняется вход… @@ -274,15 +298,20 @@ Укажите код из  цифр, который мы отправили на номер Этот номер телефона использовался слишком много раз. Указанный адрес электронной почты не совпадает с адресом, который использовался для входа. -Отменить связь с аккаунтом +Twitter + +Войдите в аккаунт в приложении </p></p> + Проверьте настройки папки со входящими сообщениям и убедитесь, что в ней достаточно места. +Указан недопустимый диапазон. +Превышена квота для этой операции. Повторите попытку позже. Парагвай Алжир Вы неправильно ввели пароль слишком много раз. Прежде чем повторить попытку, подождите несколько минут. Остров Норфолк Номер телефона был подтвержден автоматически -Сначала нужно ввести текущий пароль. -Назад +Ошибка сети. Проверьте подключение к Интернету. +Проверка выполнена. Индия Бенин Продолжить @@ -331,27 +360,33 @@ Адрес электронной почты Далее Этот код уже неактивен. -Войти через аккаунт Facebook +На адрес электронной почты отправлено письмо с информацией о том, как войти в аккаунт. Изучите его и выполните инструкции. Подтвердите адрес электронной почты Имя и фамилия Гренада Западная Сахара +Не удалось выполнить запрос из-за текущего состояния системы. Произошла неизвестная ошибка. Вы уже использовали адрес . Введите пароль для этого аккаунта. +Письмо отправлено. Ирландия Конго, Демократическая Республика Восстановление пароля Конго +Клиент инициировал создание уже существующего ресурса. Продолжить как гость Сан-Марино Кипр Соломоновы Острова Использовать адрес для входа? +Чтобы продолжить вход в аккаунт на этом устройстве через адрес , вам нужно восстановить пароль. Мальдивы Сан-Томе и Принсипи Подтверждение адреса электронной почты перед входом в аккаунт Словения Республика Корея +Конфликт параллелизма (например, конфликт в цикле "чтение-изменение-запись"). +Метод API не реализован сервером. Лесото Войти по номеру телефона Повторите попытку @@ -370,9 +405,11 @@ Вы уже использовали адрес . Введите пароль для этого аккаунта. {plural_var,plural, =1{Пароль должен состоять из букв и цифр и содержать не меньше  символа.}one{Пароль должен состоять из букв и цифр и содержать не меньше  символа.}few{Пароль должен состоять из букв и цифр и содержать не меньше  символов.}many{Пароль должен состоять из букв и цифр и содержать не меньше  символов.}other{Пароль должен состоять из букв и цифр и содержать не меньше  символа.}} Зимбабве -На адрес отправлено письмо с инструкциями по сбросу пароля. -Не удалось подтвердить номер телефона. +Запрос не аутентифицирован, поскольку токен OAuth отсутствует, недействителен или устарел. +Код подтверждения должен состоять из 6 цифр. +Выберите пароль Зарегистрирован вход с нового устройства или браузера +Войдите в аккаунт в приложении Чехия Тувалу Войти через аккаунт GitHub @@ -398,6 +435,10 @@ Испания Коста-Рика Самоа + +Для вашего аккаунта в приложении "" была включена двухэтапная аутентификация (). +Если вы не подавали запрос на это изменение, отмените его, перейдя по адресу +. Мали Катар Уругвай @@ -428,6 +469,7 @@ Бермуды Ниуэ Гваделупа +Клиент задал недопустимую конфигурацию проекта. Палестина Словакия Гернси @@ -440,19 +482,24 @@ Малайзия Кокосовые (Килинг) острова Чтобы пройти авторизацию на текущем компьютере, телефоне или планшете, нажмите "Продолжить". Если вы все-таки хотите связать аккаунт сервиса со своим адресом электронной почты, откройте ссылку на устройстве, на котором начали процесс входа. +Повторить Вы пытались связать аккаунт сервиса со своим адресом электронной почты, однако перешли по ссылке на устройстве, на котором у вас не выполнен вход. Отправить код ещё раз можно будет через 0:. Марокко +Если вам не знакомо это устройство, возможно, кто-то пытается получить доступ к вашему аккаунту. Рекомендуем изменить пароль прямо сейчас. Бангладеш Телефон Финляндия Объединенные Арабские Эмираты +Адрес электронной почты подтвержден и изменен Пароль Центрально-Африканская Республика Кюрасао +Все запросы с этого устройства заблокированы из-за подозрительной активности. Повторите попытку позже. Тунис Пароль изменен Греция +Отмена двухэтапной аутентификации Ангола Кувейт Указан неправильный адрес или пароль. diff --git a/translations/sk.xtb b/translations/sk.xtb index 05ca5248..1719078f 100644 --- a/translations/sk.xtb +++ b/translations/sk.xtb @@ -12,11 +12,14 @@ Filipíny Rumunsko Južná Georgia a Južné Sandwichove ostrovy -OK +Neobnoviteľná strata alebo poškodenie údajov. Taliansko Libanon Guyana Guatemala +Pri odstraňovaní druhého prvku sa vyskytla chyba.Skúste ho odstrániť znova. Ak to nepomôže, požiadajte o pomoc podporu. +Skúste e‑mailovú adresu aktualizovať znova +Vyskytla sa chyba siete. Skúste to znova neskôr. Zadajte platné telefónne číslo Klepnutím na tlačidlo Overiť vyjadrujete súhlas so zmluvnými podmienkamipravidlami ochrany súkromia. Môže byť odoslaná SMS a môžu sa účtovať poplatky za správy a dáta. Zadajte svoje heslo @@ -27,6 +30,7 @@ Z vašej adresy IP prichádza príliš veľa žiadostí o účet. Skúste to znova o pár minút. Srí Lanka Teraz sa môžete prihlásiť pomocou nového účtu +Klient zadal neplatný argument. Čad Znova odoslať kód o  Bielorusko @@ -62,6 +66,7 @@ Github Pravidlá ochrany súkromia Guam +Bola vyčerpaná kvóta zdrojov alebo dosiahnutý limit rýchlosti. Chorvátsko Litva Malta @@ -71,22 +76,28 @@ Hosť Ghana Prehliadač, ktorý používate, nepodporuje webové úložisko. Skúste to znova v inom prehliadači. +Klient žiadosť zrušil. Pri zmene prihlasovacieho e-mailu naspäť vznikol problém.Ak sa vám nepodarí obnoviť e-mail ani pri opätovnom pokuse, skúste požiadať o pomoc svojho správcu. Bahrajn +Interná chyba servera. Irán +GitHub Overuje sa, že nie ste robot... Tento kód už nie je platný Nová Kaledónia Monako Seychely +Telefónne číslo bolo ako prvok druhého kroku overenia odstránené. Nepodarilo sa inovovať aktuálneho anonymného používateľa. Neanonymné poverenie je už priradené k inému používateľskému účtu. Zadali ste nesprávne heslo príliš veľakrát. Skúste to znova o pár minút. Zabezpečenie Platnosť vašej žiadosti o obnovu hesla vypršala alebo už bol odkaz použitý +Teraz sa môžete prihlásiť pomocou novej e‑mailovej adresy . Zadaný kód krajiny nie je podporovaný. Skontrolujte, či ste správne napísali svoju e‑mailovú adresu. Twitter Kambodža +Služba nie je k dispozícii. Portugalsko Francúzska Guyana Britské Panenské ostrovy @@ -102,19 +113,21 @@ Overiť Južná Afrika Zadajte svoju e-mailovú adresu, aby ste mohli pokračovať +Správca deaktivoval tento používateľský účet. Prihlasovací e‑mail s dodatočnými pokynmi bol odoslaný na adresu . Na dokončenie prihlásenia skontrolujte svoj e‑mail. Vytvorenie účtu Afganistan Klepnutím na tlačidlo vyjadrujete súhlas so zmluvnými podmienkami . Kanada Dánsko +Bol prekročený časový limit žiadosti. Rakúsko E-maily sa nezhodujú Južný Sudán Jemen Guinea-Bissau Saudská Arábia -Logo aplikácie +Do aplikácie bol pridaný ďalší bezpečnostný prvok Americké Panenské ostrovy Prihlásiť sa telefónom Indonézia @@ -122,6 +135,7 @@ Venezuela Máte problémy s prihlásením? Overte, že ste to vy +Vyskytol sa problém pri overovaní vašej žiadosti. Vráťte sa na webovú adresu, ktorá vás presmerovala na túto stránku, aby sa proces overovania začal odznova. Dominika Nové heslo Gabon @@ -139,6 +153,7 @@ Gambia Hotovo Zrušiť +Neznáma chyba servera. Zadajte názov účtu Irak Vietnam @@ -150,6 +165,8 @@ Nepál Tokelau Vybraté poverenie poskytovateľa služieb overenia totožnosti nie je podporované. +Pre daný e‑mail nie je k dispozícii žiadny poskytovateľ prihlásenia. Skúste iný e‑mail. +Zadajte meno a priezvisko. Kolumbia Klepnutím na ULOŽIŤ vyjadrujete súhlas s dokumentom Burundi @@ -157,7 +174,7 @@ Twitter Váš e-mail bol overený Papua-Nová Guinea -Uložiť +Zadaný zdroj sa nenašiel. Overuje sa... Prihlásiť sa Východný Timor @@ -190,6 +207,7 @@ Telefón Znova odoslať Honduras +Časový limit operácie uplynul. Niger Táto e-mailová adresa sa nezhoduje s existujúcim účtom Jordánsko @@ -201,6 +219,7 @@ Izrael Vyskytla sa chyba Číslo +Klient nemá potrebné povolenie. Dokončite overenie reCAPTCHA Nikaragua Burkina Faso @@ -209,14 +228,17 @@ Turks a Caicos Znova odoslať kód o  Skontrolujte si e-mail +Druhý prvok sa nepodarilo odstrániť Pridanie hesla Salvádor +Ak chcete zmeniť heslo svojho účtu, budete sa musieť znova prihlásiť. Srbsko Belize pre účet Aby tento proces mohol úspešne prepojiť váš účet s týmto e‑mailom, musíte odkaz otvoriť v tom istom zariadení alebo prehliadači. Švédsko Svätý Martin (fr.) +Platnosť vašej žiadosti o overenie a aktualizáciu e‑mailovej adresy vypršala alebo bol odkaz už použitý. Svätá Lucia @string/app_name Skontrolujte si e-mail @@ -224,13 +246,15 @@ Omán Karibské Holandsko Aktualizovaná e-mailová adresa -Prihlásiť sa cez Twitter +Úspešne ste sa odhlásili. +Prihlásiť sa telefónom Telefónne číslo Už máte účet Thajsko Pravidlá ochrany súkromia Lichtenštajnsko Úprava názvu +Zariadenie alebo aplikácia bolo/-a ako prvok druhého kroku overenia odstránené/-á. Skúste otvoriť odkaz pomocou toho istého zariadenia, na ktorom ste začali proces prihlásenia. Holandsko Rovníková Guinea @@ -254,7 +278,7 @@ Zadajte svoju e-mailovú adresu, aby ste mohli pokračovať Overenie telefónneho čísla Libéria -Už existuje účet s touto e-mailovou adresou. +Odhlásenie Madagaskar Kazachstan Prihlasovanie… @@ -273,15 +297,20 @@ Zadajte -ciferný kód, ktorý sme odoslali na číslo Toto telefónne číslo bolo použité príliš veľakrát Zadaná e‑mailová adresa nezodpovedá aktuálnej relácii prihlasovania. -Odpojiť účet +Twitter + +Prihláste sa do aplikácie </p></p> + Skontrolujte, či máte dostatok miesta v doručenej pošte alebo iné problémy súvisiace s nastaveniami doručenej pošty. +Klient zadal neplatný rozsah. +Bola prekročená kvóta tejto operácie. Skúste to znova neskôr. Paraguaj Alžírsko Príliš veľakrát ste zadali nesprávne heslo. Skúste to znova o pár minút. Norfolk Telefónne číslo bolo automaticky overené -Ak chcete zmeniť heslo, musíte najskôr zadať aktuálne heslo. -Späť +Chyba siete. Skontrolujte internetové pripojenie. +Overené. India Benin Pokračovať @@ -330,27 +359,33 @@ E-mail Ďalej Tento kód už nie je platný -Prihlásiť sa cez Facebook +Prihlasovací e‑mail s ďalšími pokynmi bol odoslaný na adresu . Pozrite si poštu a dokončite prihlásenie. Potvrdenie e-mailovej adresy Krstné meno a priezvisko Grenada Západná Sahara +Kým je systém v aktuálnom stave, žiadosť nemožno vykonať. Vyskytla sa neznáma chyba. Adresu ste už použili na prihlásenie. Zadajte heslo pre daný účet. +Prihlasovací e‑mail bol odoslaný Írsko Konžská demokratická republika Obnovenie hesla Konžská republika +Zdroj, ktorý sa klient pokúsil vytvoriť, už existuje. Pokračovať ako hosť San Maríno Cyprus Šalamúnove ostrovy Chcete pokračovať s e-mailom ? +Ak chcete pokračovať v prihlásení do účtu v tomto zariadení, musíte si obnoviť heslo. Maldivy Svätý Tomáš a Princov ostrov Potvrdením svojej e‑mailovej adresy dokončite prihlásenie Slovinsko Južná Kórea +Konflikt súbežnosti operácií, napríklad čítania, úpravy a zápisu. +Na serveri nie je implementovaná táto metóda rozhrania API. Lesotho Prihlásiť sa telefónom Skúste znova overiť svoj e-mail @@ -370,8 +405,10 @@ Zadajte svoje heslo pre daný účet. {plural_var,plural, =1{Heslo nie je dostatočne silné. Použite aspoň znak a kombináciu písmen a čísel}few{Heslo nie je dostatočne silné. Použite aspoň znaky a kombináciu písmen a čísel}many{Heslo nie je dostatočne silné. Použite aspoň znaku a kombináciu písmen a čísel}other{Heslo nie je dostatočne silné. Použite aspoň znakov a kombináciu písmen a čísel}} Zimbabwe +Žiadosť nebola overená, pretože token OAuth chýba, je neplatný alebo jeho platnosť vypršala. Vyskytol sa problém s overením vášho telefónneho čísla Bolo rozpoznané nové zariadenie alebo prehliadač +Prihlásiť sa do aplikácie Česko Tuvalu Prihlásiť sa cez GitHub @@ -426,6 +463,7 @@ Bermudy Niue Guadeloupe +Klient uviedol neplatnú konfiguráciu projektu. Palestínske územia Slovensko Guernsey @@ -438,19 +476,24 @@ Malajzia Kokosové ostrovy Ak stále chcete prepojiť svoj účet v službe , otvorte odkaz v tom istom zariadení, v ktorom ste sa začali prihlasovať. V opačnom prípade klepnite na tlačidlo Pokračovať a prihláste sa v tomto zariadení. +Skúsiť znova Pôvodne ste chceli prepojiť svoj účet v službe so svojím e‑mailovým účtom, ale odkaz ste otvorili v inom zariadení, v ktorom nie ste prihlásený/-á. Kód sa znova odošle o 0: Maroko +Ak toto zariadenie nepoznáte, je možné, že sa niekto snaží získať prístup k vášmu účtu. Mali by ste si ihneď zmeniť heslo. Bangladéš Telefón Fínsko Spojené arabské emiráty +E‑mailová adresa bola overená a zmenená Heslo Stredoafrická republika Curaçao +Pre nezvyčajnú aktivitu sme zablokovali všetky žiadosti z tohto zariadenia. Skúste to znova neskôr. Tunisko Heslo bolo zmenené Grécko +Druhý prvok bol odstránený Angola Kuvajt Zadaný e-mail a heslo sa nezhodujú. diff --git a/translations/sl.xtb b/translations/sl.xtb index aa11130d..e19a270d 100644 --- a/translations/sl.xtb +++ b/translations/sl.xtb @@ -12,11 +12,14 @@ Filipini Romunija Južna Georgia in Južni Sandwichevi otoki -V redu +Neobnovljiva izguba podatkov ali okvara podatkov. Italija Libanon Gvajana Gvatemala +Pri odstranjevanju drugega dejavnika je prišlo do težave.Poskusite ga znova odstraniti. Če to ne deluje, se za pomoč obrnite na podporo. +Znova posodobite e-poštni naslov +Prišlo je do napake v omrežju. Poskusite znova pozneje. Vnesite veljavno telefonsko številko Če se dotaknete možnosti »Preveri«, potrjujete, da se strinjate z našimi pogoji storitve in pravilnikom o zasebnosti. Morda bo poslano sporočilo SMS. Pošiljanje sporočila in prenos podatkov boste morda morali plačati. Vnesite geslo @@ -27,6 +30,7 @@ Z vašega naslova IP prihaja preveč zahtev za ustvarjanje novih računov. Poskusite znova čez nekaj minut. Šrilanka Zdaj se lahko prijavite z novim računom +Odjemalec je določil neveljaven argument. Čad Če se dotaknete možnosti »«, potrjujete, da se strinjate z dokumentoma in . Morda bo poslano sporočilo SMS. Pošiljanje sporočila in prenos podatkov boste morda morali plačati. Belorusija @@ -62,6 +66,7 @@ GitHub Pravilnik o zasebnosti Guam +Presežena omejitev virov ali dosežena omejitev ravni. Hrvaška Litva Malta @@ -71,21 +76,27 @@ Gost Gana Brskalnik, ki ga uporabljate, ne podpira spletne shrambe. Poskusite znova v drugem brskalniku. +Zahtevo je preklical odjemalec. Pri spreminjanju vašega e-poštnega naslova za prijavo nazaj v prejšnjega je prišlo do težave.Če poskusite znova in še vedno ne morete ponastaviti e-poštnega naslova, se za pomoč obrnite na skrbnika. Bahrajn +Notranja napaka strežnika. Iran +GitHub Preverjamo, da niste robot ... Ta koda ni več veljavna Nova Kaledonija Monako Sejšeli +Dejavnik kot drugi korak preverjanja pristnosti je bil odstranjen. Trenutno anonimni uporabnik ni mogel nadgraditi računa. Poverilnica za neanonimni račun je že povezana z drugim uporabniškim računom. Prevečkrat ste vnesli nepravilno geslo. Poskusite znova čez nekaj minut. Vaša zahteva za ponastavitev gesla je potekla ali pa je bila povezava že uporabljena +Zdaj se lahko prijavite z novim e-poštnim naslovom . Vnesena koda države ni podprta. Preverite, ali je e-poštni naslov pravilno zapisan. Twitter Kambodža +Storitev ni na voljo. Portugalska Francoska Gvajana Britanski Deviški otoki @@ -101,19 +112,21 @@ Preveri Južna Afrika Če želite nadaljevati, vnesite svoj e-poštni naslov +Uporabniški račun je onemogočil skrbnik. E-poštno sporočilo za prijavo z dodatnimi navodili je bilo poslano na . Preverite e-pošto in dokončajte prijavo. Ustvarjanje računa Afganistan Če se dotaknete možnosti , potrjujete, da se strinjate z dokumentom . Kanada Danska +Rok zahteve je presežen. Avstrija E-pošti se ne ujemata Južni Sudan Jemen Gvineja Bissau Savdska Arabija -Logotip aplikacije +V aplikacijo je bil dodan še en varnostni korak. Ameriški Deviški otoki Prijava s telefonom Indonezija @@ -121,6 +134,7 @@ Venezuela Ali imate težave pri prijavi? Potrdite, da ste to vi +Pri preverjanju pristnosti zahteve je prišlo do težave. Znova obiščite URL, ki vas je preusmeril na to stran, in znova izvedite postopek preverjanja pristnosti. Dominika Novo geslo Gabon @@ -138,6 +152,7 @@ Gambija Končano Prekliči +Neznana napaka strežnika. Vnesite ime računa Irak Vietnam @@ -149,13 +164,15 @@ Nepal Tokelau Izbrana poverilnica za ponudnika storitev preverjanja pristnosti ni podprta. +Za navedeni e-poštni naslov ni na voljo ponudnika vpisa. Poskusite z drugim e-poštnim naslovom. +Vnesite ime in priimek. Kolumbija Burundi Mavretanija Twitter E-poštni naslov je bil preverjen Papua Nova Gvineja -Shrani +Navedenega vira ni mogoče najti. Preverjanje ... Prijava Vzhodni Timor @@ -188,6 +205,7 @@ Telefon Pošlji znova Honduras +Časovna omejitev postopka je potekla. Niger E-poštni naslov se ne ujema z obstoječim računom Jordanija @@ -199,6 +217,7 @@ Izrael Prišlo je do napake Številka +Odjemalec nima ustreznega dovoljenja. Opravite preskus reCAPTCHA Nikaragva Burkina Faso @@ -207,27 +226,32 @@ Otoki Turks in Caicos Ponovno pošlji kodo čez Preverite e-pošto +Drugega dejavnika ni bilo mogoče odstraniti Dodajanje gesla Salvador +Če želite spremeniti geslo računa, se boste morali prijaviti znova. Srbija Belize za Da bi račun za uspešno povezali s tem e-poštnim računom, morate povezavo odpreti v isti napravi ali brskalniku. Švedska St. Maarten +Vaša zahteva za preverjanje in posodobitev e-poštnega naslova je potekla ali pa je bila povezava že uporabljena. Sveta Lucija Preverite e-pošto Vaša zahteva za preverjanje e-poštnega naslova je potekla ali pa je bila povezava že uporabljena Oman Nizozemski Karibi Posodobljen e-poštni naslov -Prijava z računom za Twitter +Uspešno ste se odjavili. +Prijava s telefonom Telefonska številka Račun že imate Tajska Pravilnik o zasebnosti Lihtenštajn Urejanje imena +Naprava ali aplikacija kot drugi korak preverjanja pristnosti je bila odstranjena. Povezavo poskusite odpreti v isti napravi ali brskalniku, kjer ste začeli postopek prijave. Nizozemska Ekvatorialna Gvineja @@ -251,7 +275,7 @@ Če želite nadaljevati, vnesite svoj e-poštni naslov Preverjanje telefonske številke Liberija -Račun s tem e-poštnim naslovom že obstaja. +Odjava Madagaskar Kazahstan Prijava v teku ... @@ -270,15 +294,20 @@ Vnesite -mestno kodo, ki smo jo poslali na številko Telefonska številka je bila uporabljena prevečkrat E-poštni naslov, ki ste ga navedli, se ne ujema s trenutno sejo prijave. -Prekini povezavo z računom +Twitter + +Prijavite se v aplikacijo </p></p> + Zagotovite, da imate v mapi »Prejeto« dovolj prostora oziroma da ni prišlo do drugih težav, povezanih z nastavitvami mape »Prejeto«. +Odjemalec je določil neveljaven obseg. +Omejitev za ta postopek je bila presežena. Poskusite znova pozneje. Paragvaj Alžirija Prevečkrat ste vnesli nepravilno geslo. Poskusite znova čez nekaj minut. Norfolški otok Telefonska številka je bila samodejno preverjena -Če želite spremeniti geslo, morate najprej vnesti trenutno geslo. -Nazaj +Napaka v omrežju. Preverite internetno povezavo. +Preverjeno. Indija Benin Naprej @@ -327,27 +356,33 @@ E-poštni naslov Naprej Ta koda ni več veljavna -Prijava z računom za Facebook +E-poštno sporočilo za prijavo z dodatnimi navodili je bilo poslano na . Preverite e-pošto in dokončajte prijavo. Potrditev e-poštnega naslova Ime in priimek Granada Zahodna Sahara +Zahteve v trenutnem stanju sistema ni mogoče izvesti. Neznana napaka. Za prijavo ste že uporabili e-poštni naslov . Vnesite geslo za ta račun. +E-poštno sporočilo za prijavo je bilo poslano Irska Demokratična republika Kongo Obnovitev gesla Republika Kongo +Vir, ki ga je poskušal ustvariti odjemalec, že obstaja. Nadaljuj kot gost San Marino Ciper Salomonovi otoki Ali želite nadaljevati z naslovom ? +Če se želite v tej napravi prijaviti z e-poštnim naslovom , morate obnoviti geslo. Maldivi Sao Tome in Principe Potrdite e-poštni naslov, da dokončate prijavo Slovenija Južna Koreja +Spor s sočasnostjo, kot je spor »branje/spreminjanje/pisanje«. +Strežnik ni izvedel metode API. Lesoto Prijava s telefonom E-poštni naslov preverite znova @@ -367,8 +402,10 @@ za prijavo. Vnesite geslo za ta račun. {plural_var,plural, =1{Geslo ni dovolj zapleteno. Uporabite vsaj znak in kombinacijo črk ter številk.}one{Geslo ni dovolj zapleteno. Uporabite vsaj znak in kombinacijo črk ter številk.}two{Geslo ni dovolj zapleteno. Uporabite vsaj znaka in kombinacijo črk ter številk.}few{Geslo ni dovolj zapleteno. Uporabite vsaj znake in kombinacijo črk ter številk.}other{Geslo ni dovolj zapleteno. Uporabite vsaj znakov in kombinacijo črk ter številk.}} Zimbabve +Zahteva ni bila preverjena zaradi manjkajočega, neveljavnega ali poteklega žetona OAuth. Pri preverjanju telefonske številke je prišlo do težave Zaznana je bila nova naprava ali brskalnik +Prijavite se v Češka republika Tuvalu Prijava z računom za GitHub @@ -423,6 +460,7 @@ Bermudski otoki Niue Gvadelupe +Odjemalec je določil neveljavno konfiguracijo projekta. Palestinska območja Slovaška Guernsey @@ -435,19 +473,24 @@ Malezija Kokosovi otoki [otoki Keeling] Če še vedno želite povezati svoj račun za , povezavo odprite v napravi, v kateri ste začeli s prijavo. V nasprotnem primeru se dotaknite možnosti »Nadaljuj«, da se prijavite v tej napravi. +Poskusi znova Najprej ste želeli svoj račun za povezati s svojim e-poštnim računom, vendar ste povezavo odprli v drugi napravi, kjer niste prijavljeni. Ponovno pošlji kodo čez 0: Maroko +Če te naprave ne prepoznate, nekdo morda poskuša dostopati do vašega računa. Priporočamo, da takoj spremenite geslo. Bangladeš Telefon Finska Združeni arabski emirati +Vaš e-poštni naslov je bil preverjen in spremenjen Geslo Srednjeafriška republika Curaçao +Zaradi nenavadne dejavnosti smo blokirali vse zahteve iz te naprave. Poskusite znova pozneje. Tunizija Geslo je spremenjeno Grčija +Drugi dejavnik je odstranjen Angola Kuvajt E-poštni naslov in geslo, ki ste ju vnesli, se ne ujemata diff --git a/translations/sr.xtb b/translations/sr.xtb index 3deae44b..9712da7a 100644 --- a/translations/sr.xtb +++ b/translations/sr.xtb @@ -12,11 +12,14 @@ Филипини Румунија Јужна Џорџија и Јужна Сендвичка Острва -Потврди +Губитак неповратних података или оштећење података. Италија Либан Гвајана Гватемала +Дошло је до грешке при уклањању другог фактора.Пробајте поново да га уклоните. Ако то не успе, обратите се подршци. +Поново ажурирајте имејл +Дошло је до грешке на мрежи. Пробајте поново касније. Унесите важећи број телефона Ако додирнете Верификуј, потврђујете да прихватате наше услове коришћења услуге и политику приватности. Можда ћете послати SMS. Могу да вам буду наплаћени трошкови слања поруке и преноса података. Унесите лозинку @@ -27,6 +30,7 @@ Са ваше IP адресе потиче превише захтева за налог. Пробајте поново за пар минута. Шри Ланка Сада можете да се пријавите помоћу новог налога +Клијент је навео неважећу променљиву. Чад Поново пошаљи кôд за Белорусија @@ -62,6 +66,7 @@ Github Политика приватности Гвам +Недостаје квота ресурса или је достигнуто ограничење броја ресурса. Хрватска Литванија Малта @@ -71,22 +76,28 @@ Гост Гана Прегледач који користите не подржава веб-складиште. Пробајте поново из другог прегледача. +Клијент је отказао захтев. Дошло је до проблема при враћању на имејл који сте раније користили за пријављивање.Ако пробате поново и опет не успете да ресетујете имејл, затражите помоћ од администратора. Бахреин +Интерна грешка сервера. Иран +GitHub Потврђујемо да нисте робот… Тај кôд више не важи Нова Каледонија Монако Сејшели + је уклоњен као други корак потврде идентитета. Надоградња тренутног анонимног корисника није успела. Неанонимни акредитив је већ повезан са другим налогом корисника. Превише пута сте унели нетачну лозинку. Пробајте поново за пар минута. Безбедност Захтев за ресетовање лозинке је истекао или је линк већ употребљен +Можете да се пријавите помоћу новог имејла . Наведени позивни број за земљу није подржан. Проверите да ли сте тачно уписали имејл адресу. Twitter Камбоџа +Услуга није доступна. Португалија Француска Гијана Британска Девичанска Острва @@ -102,19 +113,21 @@ Верификуј Јужноафричка Република Унесите имејл адресу да бисте наставили +Администратор је онемогућио кориснички налог. Послали смо вам имејл за пријављивање са додатним упутствима на . Проверите имејл да бисте довршили пријављивање. Отворите налог Авганистан Додиром на потврђујете да прихватате . Канада Данска +Крајњи рок захтева је премашен. Аустрија Имејлови се не подударају Јужни Судан Јемен Гвинеја Бисао Саудијска Арабија -Логотип апликације +У апликацију је додат додатни фактор безбедности Девичанска Острва (САД) Пријави ме помоћу телефона Индонезија @@ -122,6 +135,7 @@ Венецуела Имате проблема при пријављивању? Потврдите да сте то ви +Дошло је до проблема при потврди идентитета захтева. Поново посетите URL који вас је преусмерио на ову страницу да бисте поново покренули поступак потврде идентитета. Доминика Нова лозинка Габон @@ -139,6 +153,7 @@ Гамбија Готово Откажи +Непозната грешка сервера. Унесите назив налога Ирак Вијетнам @@ -150,6 +165,8 @@ Непал Токелау Изабрани акредитив за даваоца потврде идентитета није подржан! +За пријаву преко наведеног имејла није доступан ниједан провајдер, пробајте са неким другим имејлом. +Унесите име и презиме. Колумбија Ако додирнете САЧУВАЈ, потврђујете да прихватате Бурунди @@ -157,7 +174,7 @@ Twitter Имејл вам је верификован Папуа Нова Гвинеја -Сачувај +Наведени ресурс није пронађен. Потврђује се… Пријави ме Источни Тимор @@ -190,6 +207,7 @@ Телефон Пошаљи поново Хондурас +Операција је истекла. Нигер Имејл адреса не одговара ни једном налогу Јордан @@ -201,6 +219,7 @@ Израел Дошло је до грешке Број +Клијент нема задовољавајуће дозволе. Унесите reCAPTCHA Никарагва Буркина Фасо @@ -209,14 +228,17 @@ Острва Туркс и Каикос Кôд се поново шаље за Проверите имејл +Други фактор није уклоњен Додајте лозинку Салвадор +Да бисте променили лозинку налога, треба поново да се пријавите. Србија Белизе за Да би се овим поступком налог повезао са овом имејл адресом, отворите линк са истог уређаја или из истог прегледача. Шведска Свети Мартин (француски) +Захтев за верификацију и ажурирање имејла је истекао или је линк већ искоришћен. Света Луција @string/app_name Проверите имејл @@ -224,13 +246,15 @@ Оман Карипска Холандија Ажурирана имејл адреса -Пријави ме помоћу Twitter-а +Сада сте одјављени. +Пријави ме помоћу телефона Број телефона Већ имате налог Тајланд Политика приватности Лихтенштајн Измена назива +Уређај или апликација је уклоњена као други корак потврде идентитета. Отворите линк са истог уређаја или из истог прегледача из ког сте покренули поступак пријављивања. Холандија Екваторијална Гвинеја @@ -254,7 +278,7 @@ Унесите имејл адресу да бисте наставили Верификујте број телефона Либерија -Већ постоји налог са том имејл адресом. +Одјави ме Мадагаскар Казахстан Пријављујемо вас… @@ -273,15 +297,20 @@ Унесите -цифрени кôд који смо послали на Овај број телефона је употребљен превише пута Наведена имејл адреса се не подудара са тренутном сесијом пријављивања. -Опозив везе са налогом +Twitter + +Пријавите се у апликацију </p></p> + Проверите да нисте остали без простора у пријемном сандучету, односно да нема других проблема са пријемним сандучетом. +Клијент је навео неважећи опсег. +Квота за ову операцију је премашена. Пробајте поново касније. Парагвај Алжир Превише пута сте унели нетачну лозинку. Пробајте поново за пар минута. Острво Норфолк Број телефона је аутоматски верификован -Да бисте променили лозинку, треба да унесете тренутну лозинку. -Назад +Дошло је до мрежне грешке, проверите интернет везу. +Потврђено је! Индија Бенин Настави @@ -330,27 +359,33 @@ Имејл Даље Тај кôд више не важи -Пријави ме помоћу Facebook-а +Имејл за пријављивање са додатним упутствима је послат на . Проверите имејл да бисте довршили пријављивање. Потврдите имејл адресу Име и презиме Гренада Западна Сахара +Захтев не може да се изврши у тренутном стању система. Дошло је до непознате грешке. Већ сте користили адресу за пријављивање. Унесите лозинку за тај налог. +Послали смо вам имејл за пријављивање Ирска Демократска Република Конго Повратите лозинку Република Конго +Ресурс који је клијент покушао да направи већ постоји. Наставите као гост Сан Марино Кипар Соломонска Острва Желите ли да наставите помоћу имејла ? +Да бисте се пријавили као на овом уређају, треба да повратите лозинку. Малдиви Сао Томе и Принципе Потврдите имејл да бисте довршили пријављивање Словенија Јужна Кореја +Сукоб паралелног извршавања, као што је сукоб читања-мењања-писања. +Сервер није применио API метод. Лесото Пријави ме помоћу телефона Пробајте поново да верификујете имејл @@ -370,8 +405,10 @@ за пријављивање. Унесите лозинку за тај налог. {plural_var,plural, =1{Лозинка није довољно јака. Употребите бар знак, уз комбинацију бројева и слова}one{Лозинка није довољно јака. Употребите бар знак, уз комбинацију бројева и слова}few{Лозинка није довољно јака. Употребите бар знака, уз комбинацију бројева и слова}other{Лозинка није довољно јака. Употребите бар знакова, уз комбинацију бројева и слова}} Зимбабве +Није потврђен идентитет захтева због OAuth токена који недостаје, неважећи је или је истекао. Дошло је до проблема при верификацији броја телефона Открили смо нови уређај или прегледач +Пријавите се у апликацију Чешка Република Тувалу Пријавите се помоћу GitHub-а @@ -426,6 +463,7 @@ Бермуди Ниуе Гвадалупе +Клијент је навео нетачну конфигурацију пројекта. Територија Палестине Словачка Гернзи @@ -438,19 +476,24 @@ Малезија Кокосова (Килингова) Острва Ако и даље желите да повежете налог, отворите линк на истом уређају са ког сте почели са пријављивањем. У супротном, додирните Настави да бисте се пријавили на овом уређају. +Пробај поново Првобитна намера вам је била да повежете налог са својим имејл налогом, али сте отворили линк на другом уређају, на ком нисте пријављени. Кôд ће бити поново послат за 0: Мароко +Ако не препознајете овај уређај, можда неко покушава да приступи налогу. Не би било лоше да одмах промените лозинку. Бангладеш Телефон Финска Уједињени Арапски Емирати +Имејл је верификован и промењен. Лозинка Централноафричка Република Курасао +Блокирали смо све захтеве са овог уређаја због необичне активности. Пробајте поново касније. Тунис Лозинка је промењена Грчка +Други фактор је уклоњен Ангола Кувајт Имејл и лозинка које сте унели се не подударају diff --git a/translations/sv.xtb b/translations/sv.xtb index a0e48332..0eb8e354 100644 --- a/translations/sv.xtb +++ b/translations/sv.xtb @@ -12,11 +12,14 @@ Filippinerna Rumänien Sydgeorgien och Sydsandwichöarna -OK +Oåterkallelig dataförlust eller datafel. Italien Libanon Guyana Guatemala +Ett fel uppstod när din andra faktor skulle tas bort.Testa att ta bort den igen. Kontakta support om det inte fungerar. +Testa att uppdatera din e-post igen +Ett nätverksfel uppstod. Försök igen senare. Ange ett giltigt telefonnummer Genom att trycka på Verifiera godkänner du våra Användarvillkor och vår Sekretesspolicy. Ett sms kan skickas. Meddelande- och dataavgifter kan tillkomma. Ange ditt lösenord @@ -27,6 +30,7 @@ Det kommer för många kontobegäranden från din IP-adress. Försök igen om några minuter. Sri Lanka Nu kan du logga in med ditt nya konto +Klienten angav ett ogiltigt argument. Tchad Skicka kod igen om Vitryssland @@ -62,6 +66,7 @@ Github Sekretesspolicy Guam +Antingen har resurskvoten överskridits eller så är gränsen nästan nådd. Kroatien Litauen Malta @@ -71,22 +76,28 @@ Gäst Ghana Webbläsaren du använder stöder inte webblagring. Försök igen med en annan webbläsare. +Begäran avbröts av klienten. Det gick inte att ändra tillbaka din e-postadress för inloggning.Om du försöker igen och fortfarande inte kan återställa e-postadressen kan du be administratören om hjälp. Bahrain +Internt serverfel. Iran +GitHub Verifierar att du inte är en robot … Koden är inte längre giltig Nya Kaledonien Monaco Seychellerna + togs bort som andra autentiseringssteg. Den aktuella anonyma användaren kunde inte uppgradera. De icke-anonyma användaruppgifterna är redan kopplade till ett annat konto. Du har angett fel lösenord för många gånger. Försök igen om några minuter. Säkerhet Din begäran om att återställa ditt lösenord har upphört, eller så har länken redan använts +Nu kan du logga in med din nya e-post . Landskoden du har angett stöds inte. Kontrollera att du inte stavade e-postadressen fel. Twitter Kambodja +Tjänsten är inte tillgänglig. Portugal Franska Guyana Brittiska Jungfruöarna @@ -102,19 +113,21 @@ Verifiera Sydafrika Ange din e-postadress om du vill fortsätta +Användarkontot har inaktiverats av en administratör. Ett inloggningsmeddelande med instruktioner skickades till . Kontrollera din e-post för att slutföra inloggningen. Skapa konto Afghanistan Genom att trycka på godkänner du tjänstens . Kanada Danmark +Sista dagen för begäran har passerat. Österrike E-postadresserna stämmer inte överens Sydsudan Jemen Guinea-Bissau Saudiarabien -Appens logotyp +Ytterligare ett säkerhetssteg har lagts till i Amerikanska Jungfruöarna Logga in med telefon Indonesien @@ -122,6 +135,7 @@ Venezuela Har du problem med att logga in? Verifiera dig +Ett fel uppstod när din begäran autentiserades. Öppna webbadressen där du omdirigerades till den här sidan på nytt för att starta autentiseringsprocessen igen. Dominica Nytt lösenord Gabon @@ -139,6 +153,7 @@ Gambia Klart Avbryt +Okänt serverfel. Ange ditt kontonamn Irak Vietnam @@ -150,6 +165,8 @@ Nepal Tokelauöarna Valda användaruppgifter för autentiseringsleverantören stöds inte! +Ingen inloggningsleverantör är tillgänglig för angiven e-post. Prova en annan e-post. +Ange för- och efternamn. Colombia När du trycker på Spara godkänner du Burundi @@ -157,7 +174,7 @@ Twitter Din e-postadress har verifierats Papua Nya Guinea -Spara +Den angivna resursen hittades inte. Verifierar ... Logga in Östtimor @@ -190,6 +207,7 @@ Mobil Skicka igen Honduras +Åtgärden överskred tidsgränsen. Niger E-postadressen matchar inget befintligt konto Jordanien @@ -201,6 +219,7 @@ Israel Ett fel har inträffat Nummer +Klienten har inte tillräcklig behörighet. Lös reCAPTCHA Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Turks- och Caicosöarna Skicka kod igen om Kolla dina e-postmeddelanden +Det gick inte att ta bort din andra faktor Lägg till lösenord El Salvador +Du måste logga in igen för att kunna ändra lösenord för kontot. Serbien Belize för Du måste öppna länken på samma enhet eller i samma webbläsare för att -kontot ska anslutas med den här e-postadressen. Sverige Saint-Martin +Din begäran om att verifiera och uppdatera din e-post har upphört att gälla eller så har länken redan använts. St. Lucia @string/app_name Kolla dina e-postmeddelanden @@ -224,13 +246,15 @@ Oman Karibiska Nederländerna Uppdaterad e-postadress -Logga in med Twitter +Du har loggat ut. +Logga in med telefon Telefonnummer Du har redan ett konto Thailand Sekretesspolicy Liechtenstein Redigera namn +Enheten eller appen togs bort som andra autentiseringssteg. Testa att öppna länken med samma enhet eller webbläsare som du använde när du påbörjade inloggningen. Nederländerna Ekvatorialguinea @@ -254,7 +278,7 @@ Ange din e-postadress om du vill fortsätta Verifiera ditt telefonnummer Liberia -Det finns redan ett konto med den e-postadressen. +Logga ut Madagaskar Kazakstan Loggar in … @@ -273,15 +297,20 @@ Ange den -siffriga koden vi skickade till Det här telefonnumret har använts för många gånger Den angivna e-postadressen stämmer inte överens med den aktuella inloggningssessionen. -Ta bort länk till konto +Twitter + +Logga in i </p></p> + Kontrollera att du inte har ont om utrymme i inkorgen samt andra inkorgsrelaterade problem. +Klienten angav ett ogiltigt intervall. +Kvoten för den här åtgärden har överskridits. Försök igen senare. Paraguay Algeriet Du har angett fel lösenord för många gånger. Försök igen om några minuter. Norfolkön Telefonnumret verifierades automatiskt -Du måste först ange ditt aktuella lösenord för att kunna ändra lösenordet. -Föregående +Nätverksfel, kontrollera din internetanslutning. +Verifierat! Indien Benin Fortsätt @@ -330,27 +359,33 @@ E-post Nästa Koden är inte längre giltig -Logga in med Facebook +Ett inloggningsmeddelande med instruktioner skickades till . Kontrollera din e-post för att slutföra inloggningen. Bekräfta e-postadress För- och efternamn Grenada Västsahara +Begäran kan inte göras med aktuell systemstatus. Ett okänt fel uppstod. Du har redan loggat in med . Ange lösenordet för det kontot. +Inloggningsmeddelande skickat Irland Demokratiska republiken Kongo Återställa lösenord Republiken Kongo +Resursen en klient försökte skapa finns redan. Fortsätt som gäst San Marino Cypern Salomonöarna Vill du fortsätta med ? +Om du vill fortsätta logga in med på den här enheten måste du återställa lösenordet. Maldiverna São Tomé och Príncipe Bekräfta e-postadressen för att slutföra inloggningen Slovenien Sydkorea +Samtidighetskontrollskonflikt, till exempel läs-ändra-skriv-konflikt. +API-metod inte implementerad av servern. Lesotho Logga in med telefon Försök att verifiera e-postadressen igen @@ -369,8 +404,10 @@ Du har redan loggat in med . Ange lösenordet för det kontot. {plural_var,plural, =1{Lösenordet är inte tillräckligt starkt. Använd minst tecken och en blandning av bokstäver och siffror.}other{Lösenordet är inte tillräckligt starkt. Använd minst tecken och en blandning av bokstäver och siffror.}} Zimbabwe +Begäran ej autentiserad på grund av att OAuth-token saknas, är ogiltigt eller har upphört att gälla. Det gick inte att verifiera telefonnumret Ny enhet eller webbläsare identifierad +Logga in i Tjeckien Tuvalu Logga in med GitHub @@ -425,6 +462,7 @@ Bermuda Niue Guadeloupe +Klienten angav en ogiltig projektkonfiguration. Palestinska territoriet Slovakien Guernsey @@ -437,19 +475,24 @@ Malaysia Kokosöarna Om du fortfarande vill ansluta ditt -konto ska du öppna länken på enheten där du påbörjade inloggningen. Annars kan du trycka på Fortsätt för att logga in på den här enheten. +Försök igen Du skulle ansluta till ditt e-postkonto men har öppnat länken på en annan enhet där du inte är inloggad. Skicka kod igen om 0: Marocko +Om du inte känner igen den här enheten kan det vara någon som försöker komma åt ditt konto. Överväg att ändra lösenordet direkt. Bangladesh Telefon Finland Förenade arabemiraten +Din e-post har verifierats och ändrats Lösenord Centralafrikanska republiken Curaçao +Vi har blockerat alla begäranden från den här enheten på grund av ovanlig aktivitet. Försök igen senare. Tunisien Lösenordet har ändrats Grekland +Ta bort andra faktor Angola Kuwait E-postadressen och lösenordet matchar inte diff --git a/translations/th.xtb b/translations/th.xtb index f9116c9f..bba6abd3 100644 --- a/translations/th.xtb +++ b/translations/th.xtb @@ -12,11 +12,14 @@ ฟิลิปปินส์ โรมาเนีย เกาะเซาท์จอร์เจียและหมู่เกาะเซาท์แซนด์วิช -ตกลง +ข้อมูลสูญหายโดยกู้คืนไม่ได้หรือข้อมูลเสียหาย อิตาลี เลบานอน กายอานา กัวเตมาลา +เกิดข้อผิดพลาดขณะนำปัจจัยที่ 2 ออกลองนำปัจจัยนั้นออกอีกครั้ง หากไม่ได้ผล โปรดติดต่อฝ่ายสนับสนุนเพื่อขอความช่วยเหลือ +ลองอัปเดตอีเมลอีกครั้ง +เกิดข้อผิดพลาดของเครือข่าย ลองอีกครั้งในภายหลัง ป้อนหมายเลขโทรศัพท์ที่ถูกต้อง การแตะ "ยืนยัน" แสดงว่าคุณยอมรับข้อกำหนดในการให้บริการและนโยบายความเป็นส่วนตัว ระบบจะส่ง SMS ให้คุณ อาจมีค่าบริการรับส่งข้อความและค่าบริการอินเทอร์เน็ต ป้อนรหัสผ่าน @@ -27,6 +30,7 @@ มีคำขอสร้างบัญชีจากที่อยู่ IP ของคุณมากเกินไป โปรดรอสักครู่แล้วลองอีกครั้ง ศรีลังกา ตอนนี้คุณสามารถลงชื่อเข้าใช้ด้วยบัญชีใหม่ได้แล้ว +ไคลเอ็นต์ระบุอาร์กิวเมนต์ไม่ถูกต้อง ชาด ส่งรหัสอีกครั้งใน เบลารุส @@ -62,6 +66,7 @@ GitHub นโยบายความเป็นส่วนตัว กวม +โควต้าทรัพยากรหมดหรือถึงขีดการจำกัดอัตราคำขอ โครเอเชีย ลิทัวเนีย มอลตา @@ -71,22 +76,28 @@ ผู้เข้าร่วม กานา เบราว์เซอร์ที่คุณใช้อยู่ไม่รองรับพื้นที่เก็บข้อมูลเว็บ โปรดใช้เบราว์เซอร์อื่นแล้วลองอีกครั้ง +ไคลเอ็นต์ยกเลิกคำขอ เกิดปัญหาในการเปลี่ยนอีเมลสำหรับลงชื่อเข้าใช้ของคุณกลับไปเป็นอีเมลเดิมหากคุณลองอีกครั้งแล้วยังไม่สามารถรีเซ็ตอีเมลได้ โปรดขอความช่วยเหลือจากผู้ดูแลระบบ บาห์เรน +ข้อผิดพลาดภายในเซิร์ฟเวอร์ อิหร่าน +GitHub กำลังยืนยันว่าคุณไม่ใช่หุ่นยนต์... ไม่สามารถใช้รหัสนี้ได้อีกต่อไป นิวแคลิโดเนีย โมนาโค เซเชลส์ + ถูกนำออกตามขั้นตอนการตรวจสอบสิทธิ์ขั้นที่ 2 การอัปเกรดผู้ใช้ที่ไม่ระบุชื่อปัจจุบันล้มเหลว ข้อมูลรับรองที่ระบุชื่อได้เชื่อมโยงกับบัญชีผู้ใช้รายอื่นอยู่แล้ว คุณป้อนรหัสผ่านไม่ถูกต้องหลายครั้งเกินไป โปรดรอสักครู่แล้วลองอีกครั้ง ความปลอดภัย คำขอรีเซ็ตรหัสผ่านของคุณหมดอายุหรือมีการใช้งานลิงก์นี้แล้ว +ตอนนี้คุณลงชื่อเข้าใช้ด้วยรหัสผ่านใหม่ ได้แล้ว ไม่รองรับรหัสประเทศที่ระบุ ตรวจสอบว่าสะกดชื่ออีเมลถูกต้องแล้ว Twitter กัมพูชา +ไม่พร้อมให้บริการ โปรตุเกส เฟรนช์เกียนา หมู่เกาะบริติชเวอร์จิน @@ -102,6 +113,7 @@ ยืนยัน แอฟริกาใต้ ป้อนที่อยู่อีเมลของคุณเพื่อดำเนินการต่อ +ผู้ดูแลระบบปิดใช้บัญชีผู้ใช้ อีเมลลงชื่อเข้าใช้พร้อมวิธีการเพิ่มเติมได้ส่งไปถึง แล้ว โปรดตรวจสอบอีเมลเพื่อลงชื่อเข้าใช้ ป้อนรหัสผ่าน สร้างบัญชี @@ -109,13 +121,14 @@ การแตะแสดงว่าคุณยอมรับ แคนาดา เดนมาร์ก +เกินกำหนดเวลาในการส่งคำขอแล้ว ออสเตรีย อีเมลไม่ตรงกัน ซูดานใต้ เยเมน กินี-บิสเซา ซาอุดีอาระเบีย -โลโก้แอป +ระบบได้เพิ่มปัจจัยด้านความปลอดภัยเพิ่มเติมใน แล้ว หมู่เกาะเวอร์จินของสหรัฐอเมริกา ลงชื่อเข้าใช้ด้วยโทรศัพท์ อินโดนีเซีย @@ -123,6 +136,7 @@ เวเนซุเอลา หากพบปัญหาในการลงชื่อเช้าใช้ โปรดยืนยันว่าเป็นคุณ +ระบบพบปัญหาขณะตรวจสอบคำขอของคุณ โปรดไปที่ URL ที่เปลี่ยนเส้นทางคุณมาที่หน้านี้เพื่อเริ่มกระบวนการตรวจสอบสิทธิ์ใหม่อีกครั้ง โดมินิกัน รหัสผ่านใหม่ กาบอง @@ -140,6 +154,7 @@ แกมเบีย เสร็จสิ้น ยกเลิก +ข้อผิดพลาดของเซิร์ฟเวอร์ที่ไม่รู้จัก ป้อนชื่อบัญชีของคุณ อิรัก เวียดนาม @@ -151,6 +166,8 @@ เนปาล โตเกเลา ไม่รองรับข้อมูลรับรองสำหรับผู้ให้บริการตรวจสอบสิทธิ์ที่เลือก +ไม่พบผู้ให้บริการการลงชื่อเข้าใช้สำหรับอีเมลนี้ โปรดลองป้อนอีเมลอื่น +โปรดป้อนชื่อและนามสกุล โคลอมเบีย การแตะบันทึกแสดงว่าคุณยอมรับ บุรุนดี @@ -158,7 +175,7 @@ Twitter อีเมลของคุณได้รับการยืนยันแล้ว ปาปัวนิวกินี -บันทึก +ไม่พบทรัพยากรที่ระบุ กำลังตรวจสอบ... ลงชื่อเข้าใช้ ติมอร์ตะวันออก @@ -191,6 +208,7 @@ โทรศัพท์ ส่งอีกครั้ง ฮอนดูรัส +การดำเนินการหมดเวลาแล้ว ไนเจอร์ ที่อยู่อีเมลไม่ตรงกับบัญชีที่มีอยู่ จอร์แดน @@ -202,6 +220,7 @@ อิสราเอล พบข้อผิดพลาด หมายเลข +ไคลเอ็นต์มีสิทธิ์ไม่เพียงพอ แก้ไข reCAPTCHA นิการากัว บูร์กินาฟาโซ @@ -210,14 +229,17 @@ หมู่เกาะเติกส์และหมู่เกาะเคคอส ส่งรหัสอีกครั้งใน ตรวจสอบอีเมลของคุณ +นำปัจจัยที่ 2 ออกไม่ได้ เพิ่มรหัสผ่าน เอล ซัลวาดอร์ +ในการเปลี่ยนรหัสผ่านบัญชี คุณจะต้องลงชื่อเข้าใช้อีกครั้ง เซอร์เบีย เบลีซ สำหรับ คุณต้องเปิดลิงก์บนอุปกรณ์หรือเบราว์เซอร์เดียวกันเพื่อเชื่อมบัญชี เข้ากับอีเมลนี้ได้อย่างสำเร็จ สวีเดน เซนต์มาร์ติน +คำขอยืนยันและอัปเดตอีเมลของคุณหมดอายุหรือมีการใช้งานลิงก์นี้แล้ว เซนต์ลูเซีย @string/ชื่อแอป ตรวจสอบอีเมลของคุณ @@ -225,13 +247,15 @@ โอมาน เนเธอร์แลนด์ แคริบเบียน ที่อยู่อีเมลที่อัปเดตแล้ว -ลงชื่อเข้าใช้ด้วย Twitter +คุณออกจากระบบเรียบร้อยแล้ว +ลงชื่อเข้าใช้ด้วยโทรศัพท์ หมายเลขโทรศัพท์ คุณมีบัญชีอยู่แล้ว ไทย รหัสไม่ถูกต้อง โปรดลองอีกครั้ง ลิกเตนสไตน์ แก้ไขชื่อ +อุปกรณ์หรือแอปถูกนำออกตามขั้นตอนการตรวจสอบสิทธิ์ขั้นที่ 2 ลองเปิดลิงก์โดยใช้อุปกรณ์หรือเบราว์เซอร์เดียวกันกับที่คุณใช้ในขั้นตอนการลงชื่อเข้าใช้ เนเธอร์แลนด์ อิเควทอเรียลกินี @@ -255,7 +279,7 @@ ป้อนที่อยู่อีเมลของคุณเพื่อดำเนินการต่อ ยืนยันหมายเลขโทรศัพท์ ไลบีเรีย -มีบัญชีที่ใช้ที่อยู่อีเมลนี้อยู่แล้ว +ออกจากระบบ มาดากัสการ์ คาซักสถาน กำลังลงชื่อเข้าใช้... @@ -274,15 +298,20 @@ ป้อนรหัส หลักที่เราส่งให้คุณ มีการใช้หมายเลขโทรศัพท์นี้หลายครั้งเกินไป อีเมลที่ระบุไม่ตรงกับเซสชันลงชื่อเข้าใช้ปัจจุบัน -ยกเลิกการลิงก์บัญชี +Twitter + +ลงชื่อเข้าใช้ </p></p> + ตรวจสอบว่ายังมีพื้นที่เหลือในกล่องจดหมายหรือไม่มีปัญหาอื่นๆ ที่เกี่ยวกับการตั้งค่ากล่องจดหมาย +ไคลเอ็นต์ระบุช่วงไม่ถูกต้อง +โควต้าสำหรับการดำเนินการนี้เกินขีดจำกัดแล้ว ลองอีกครั้งในภายหลัง ปารากวัย แอลจีเรีย คุณป้อนรหัสผ่านไม่ถูกต้องหลายครั้งเกินไป โปรดรอสักครู่แล้วลองอีกครั้ง เกาะนอร์ฟอล์ก ยืนยันหมายเลขโทรศัพท์โดยอัตโนมัติแล้ว -คุณต้องป้อนรหัสผ่านปัจจุบันของคุณก่อนจึงจะเปลี่ยนรหัสผ่านได้ -ย้อนกลับ +เกิดข้อผิดพลาดของเครือข่าย โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ต +ยืนยันแล้ว อินเดีย เบนิน ต่อไป @@ -331,27 +360,33 @@ อีเมล ถัดไป ไม่สามารถใช้รหัสนี้ได้อีก -ลงชื่อเข้าใช้ด้วย Facebook +ระบบได้ส่งอีเมลลงชื่อเข้าใช้พร้อมวิธีการเพิ่มเติมไปยัง แล้ว โปรดตรวจสอบอีเมลเพื่อลงชื่อเข้าใช้ ยืนยันอีเมล ชื่อและนามสกุล เกรนาดา สะฮาราตะวันตก +ดำเนินการตามคำขอในสถานะปัจจุบันของระบบไม่ได้ เกิดข้อผิดพลาดที่ไม่รู้จัก คุณใช้ ในการลงชื่อเข้าใช้อยู่แล้ว โปรดป้อนรหัสผ่านสำหรับบัญชีดังกล่าว +ส่งอีเมลลงชื่อเข้าใช้แล้ว ไอร์แลนด์ สาธารณรัฐประชาธิปไตยคองโก กู้คืนรหัสผ่าน สาธารณรัฐคองโก +ทรัพยากรที่ไคลเอ็นต์พยายามสร้างมีอยู่แล้ว ดำเนินการต่อในฐานะผู้เข้าร่วม ซานมาริโน ไซปรัส หมู่เกาะโซโลมอน ดำเนินการต่อด้วย ไหม +หากต้องการลงชื่อเข้าใช้ต่อด้วย บนอุปกรณ์นี้ คุณต้องกู้คืนรหัสผ่าน มัลดีฟส์ เซาตูเมและปรินซิปี ยืนยันอีเมลเพื่อลงชื่อเข้าใช้ให้เสร็จสิ้น สโลวีเนีย เกาหลีใต้ +ความขัดแย้งในการดำเนินการพร้อมกัน เช่น ความขัดแย้งแบบ read-modify-write +เซิร์ฟเวอร์ไม่ได้นำเมธอด API มาใช้ เลโซโท ลงชื่อเข้าใช้ด้วยโทรศัพท์ ลองยืนยันอีเมลของคุณอีกครั้ง @@ -370,9 +405,11 @@ คุณใช้ ในการลงชื่อเข้าใช้อยู่แล้ว โปรดป้อนรหัสผ่านสำหรับบัญชีดังกล่าว {plural_var,plural, =1{รหัสผ่านไม่รัดกุมมากพอ ใช้อักขระอย่างน้อย ตัวและต้องเป็นตัวอักษรปนกับตัวเลข}other{รหัสผ่านไม่รัดกุมมากพอ ใช้อักขระอย่างน้อย ตัวและต้องเป็นตัวอักษรปนกับตัวเลข}} ซิมบับเว -ทำตามวิธีการที่ส่งไปยัง เพื่อกู้คืนรหัสผ่าน -เกิดปัญหาในการยืนยันหมายเลขโทรศัพท์ของคุณ +คำขอไม่ได้รับการตรวจสอบสิทธิ์เนื่องจากไม่มีโทเค็น โทเค็นไม่ถูกต้อง หรือโทเค็น OAuth หมดอายุแล้ว +โปรดระบุรหัสยืนยันทางโทรศัพท์ 6 หลัก +ตั้งรหัสผ่าน พบการใช้อุปกรณ์หรือเบราว์เซอร์ใหม่ +ลงชื่อเข้าใช้ สาธารณรัฐเช็ก ตูวาลู ลงชื่อเข้าใช้ด้วย GitHub @@ -398,6 +435,10 @@ สเปน คอสตาริกา ซามัว + +ระบบได้อัปเดตบัญชีของคุณใน แล้วด้วย สำหรับการตรวจสอบสิทธิ์แบบ 2 ปัจจัย +ในกรณีที่คุณไม่ได้ขอการแก้ไขนี้ โปรดใช้ลิงก์ต่อไปนี้เพื่อเลิกทำการเปลี่ยนแปลง + มาลี กาตาร์ อุรุกวัย @@ -428,6 +469,7 @@ เบอร์มิวดา นีอุเอ กวาเดอลูป +ไคลเอ็นต์ระบุการกำหนดค่าโปรเจ็กต์ไม่ถูกต้อง ดินแดนปาเลสไตน์ สโลวะเกีย เกิร์นซีย์ @@ -440,19 +482,24 @@ มาเลเซีย หมู่เกาะโคโคส [คีลิง] หากคุณยังต้องการเชื่อมต่อบัญชี ให้เปิดลิงก์บนอุปกรณ์ที่เริ่มลงชื่อเข้าใช้ หรือแตะ "ต่อไป" เพื่อลงชื่อเข้าใช้บนอุปกรณ์นี้ +ลองใหม่ แต่เดิมคุณตั้งใจที่จะเชื่อมต่อ ไปยังบัญชีอีเมล แต่คุณได้เปิดลิงก์บนอุปกรณ์อื่นที่ไม่ได้ลงชื่อเข้าใช้ ส่งรหัสอีกครั้งใน 0: โมร็อกโก +หากคุณไม่รู้จักอุปกรณ์นี้ อาจเป็นไปได้ว่ามีผู้อื่นพยายามเข้าถึงบัญชีของคุณ ลองเปลี่ยนรหัสผ่านทันที บังกลาเทศ โทรศัพท์ ฟินแลนด์ สหรัฐอาหรับเอมิเรตส์ +อีเมลของคุณได้รับการยืนยันและเปลี่ยนแปลงแล้ว รหัสผ่าน สาธารณรัฐแอฟริกากลาง คูราเซา +เราได้บล็อกคำขอทั้งหมดจากอุปกรณ์นี้เนื่องจากกิจกรรมที่ผิดปกติ ลองอีกครั้งในภายหลัง ตูนิเซีย เปลี่ยนรหัสผ่านแล้ว กรีซ +นำปัจจัยที่ 2 ออกแล้ว แองโกลา คูเวต อีเมลและรหัสผ่านที่ป้อนไม่ตรงกัน diff --git a/translations/tr.xtb b/translations/tr.xtb index 6d83c9b8..49e8bd48 100644 --- a/translations/tr.xtb +++ b/translations/tr.xtb @@ -12,11 +12,14 @@ Filipinler Romanya Güney Georgia ve Güney Sandwich Adaları -Tamam +Kurtarılamaz veri kaybı veya veri bozulması. İtalya Lübnan Guyana Guatemala +İkinci faktör kaldırılırken hata oluştu.Faktörü kaldırmayı tekrar deneyin. İşlem gerçekleştirilemezse yardım almak için destek ekibiyle iletişime geçin. +E-posta adresinizi güncellemeyi tekrar deneyin +Bir ağ hatası oluştu. Daha sonra tekrar deneyin. Geçerli bir telefon numarası girin Doğrula'ya dokunarak Hizmet Şartlarımızı ve Gizlilik Politikamızı kabul ettiğinizi belirtirsiniz. SMS gönderilebilir. Mesaj ve veri ücretleri uygulanabilir. Şifrenizi girin @@ -27,6 +30,7 @@ IP adresinizden çok fazla sayıda hesap isteği geliyor. Birkaç dakika içinde tekrar deneyin. Sri Lanka Artık yeni hesabınızla oturum açabilirsiniz +İstemci, geçersiz bağımsız değişken belirtti. Çad içinde kodu yeniden gönder Beyaz Rusya @@ -62,6 +66,7 @@ Github Gizlilik Politikası Guam +Kaynak kotası kalmadı veya hız sınırlamasına yaklaşılıyor. Hırvatistan Litvanya Malta @@ -71,22 +76,28 @@ Misafir Gana Kullandığınız tarayıcı Web Depolamasını desteklemiyor. Lütfen farklı bir tarayıcıda tekrar deneyin. +İstek, istemci tarafından iptal edildi. Oturum açma e-posta adresinizi eski haline döndürürken bir sorun oluştu.Tekrar dener ve e-posta adresinizi yine sıfırlayamazsanız yöneticinizden yardım istemeyi deneyin. Bahreyn +Dahili sunucu hatası. İran +GitHub Robot olmadığınız doğrulanıyor... Bu kod artık geçerli değil Yeni Kaledonya Monako Seyşeller +İkinci kimlik doğrulama adımı olarak eklenen numaralı telefon kaldırıldı. Mevcut anonim kullanıcı için yükseltme yapılamadı. Anonim olmayan kimlik bilgileri zaten farklı bir kullanıcı hesabıyla ilişkilendirilmiş. Çok fazla kez yanlış şifre girdiniz. Birkaç dakika içinde tekrar deneyin. Güvenlik Şifrenizi sıfırlama isteğinizin süresi dolmuş veya bağlantı zaten kullanılmış +Artık yeni hesabınız ile oturum açabilirsiniz. Sağlanan ülke kodu desteklenmiyor. E-postanızda yazım hatası bulunmadığından emin olun. Twitter Kamboçya +Hizmet kullanılamıyor. Portekiz Fransız Ginesi İngiliz Virgin Adaları @@ -102,6 +113,7 @@ Doğrula Güney Afrika Devam etmek için e-posta adresinizi girin +Kullanıcı hesabı, bir yönetici tarafından devre dışı bırakıldı. Ek talimatlar içeren bir oturum açma e-postası adresine gönderildi. Oturum açma işlemini tamamlamak için e-postanızı kontrol edin. Şifrenizi girin Hesap oluşturun @@ -109,13 +121,14 @@ öğesine dokunarak şunu kabul ettiğinizi bildirirsiniz: . Kanada Danimarka +İstek bitiş tarihi aşıldı. Avusturya E-postalar eşleşmiyor Güney Sudan Yemen Gine-Bissau Suudi Arabistan -Uygulama logosu + uygulamasına ilave güvenlik faktörü eklendi ABD Virgin Adaları Telefonla oturum aç Endonezya @@ -123,6 +136,7 @@ Venezuela Oturum açılamıyor mu? Kimliğinizi doğrulayın +İsteğinizin kimliği doğrulanırken bir sorun oluştu. Kimlik doğrulama sürecini yeniden başlatmak için lütfen sizi bu sayfaya yönlendiren URL'yi ziyaret edin. Dominik Yeni şifre Gabon @@ -140,6 +154,7 @@ Gambiya Bitti İptal +Bilinmeyen sunucu hatası. Hesap adınızı girin Irak Vietnam @@ -151,6 +166,8 @@ Nepal Tokelau Kimlik doğrulama sağlayıcı için seçilen kimlik bilgisi desteklenmiyor! +Belirtilen e-posta adresi için kullanılabilen oturum açma hizmeti sağlayıcısı yok. Lütfen işlemi farklı bir e-posta adresiyle deneyin. +Lütfen ad ve soyadı girin. Kolombiya KAYDET'e dokunarak, şunu kabul ettiğinizi bildirirsiniz: Burundi @@ -158,7 +175,7 @@ Twitter E-postanız doğrulandı Papua Yeni Gine -Kaydet +Belirtilen kaynak bulunamadı. Doğrulanıyor... Oturum Aç Doğu Timor @@ -191,6 +208,7 @@ Telefon Yeniden Gönder Honduras +İşlem zaman aşımına uğradı. Nijer Bu e-posta adresi mevcut bir hesapla eşleşmiyor Ürdün @@ -202,6 +220,7 @@ İsrail Hata ile karşılaşıldı Sayı +İstemci, gerekli izne sahip değil. reCAPTCHA'yı çözün Nikaragua Burkina Faso @@ -210,14 +229,17 @@ Turks ve Caicos Adaları içinde kodu yeniden gönder E-postanızı kontrol edin +İkinci faktörünüz kaldırılamadı Şifre ekle El Salvador +Hesabınızın şifresini değiştirmek için tekrar oturum açmanız gerekir. Sırbistan Belize için Akışın hesabınızı bu e-posta adresine sorunsuz şekilde bağlayabilmesi için bağlantıyı aynı cihazda veya tarayıcıda açmanız gerekir. İsveç Saint Martin +E-posta adresinizi doğrulama ve güncelleme isteğinizin süresi dolmuş ya da bağlantı zaten kullanılmış. St. Lucia @string/app_name E-postanızı kontrol edin @@ -225,13 +247,15 @@ Umman Hollanda Karayipleri E-posta adresi güncellendi -Twitter ile oturum aç +Oturumu başarıyla kapattınız. +Telefonla oturum aç Telefon numarası Zaten bir hesabınız var Tayland Yanlış kod. Tekrar deneyin. Lihtenştayn Adı düzenleyin +İkinci kimlik doğrulama adımı olarak eklenen cihaz veya uygulama kaldırıldı. Bağlantıyı, oturum açma işlemini başlattığınız cihazda veya tarayıcıda açmayı deneyin. Hollanda Ekvator Ginesi @@ -255,7 +279,7 @@ Devam etmek için e-posta adresinizi girin Telefon numaranızı doğrulayın Liberya -Bu e-posta adresiyle kayıtlı bir hesap zaten var. +Oturumu Kapatın Madagaskar Kazakistan Oturum açılıyor... @@ -274,15 +298,20 @@ Şu telefon numarasına yolladığımız haneli kodu girin: Bu telefon numarası çok fazla kez kullanıldı Sağlanan e-posta adresi, mevcut oturum açma oturumuyla eşleşmiyor. -Hesabın bağlantısını kaldırın +Twitter + + uygulamasında oturum açın </p></p> + Gelen kutunuzdaki boş alanın azalıp azalmadığını ve gelen kutusu ayarlarıyla ilgili başka bir sorun olup olmadığını kontrol edin. +İstemci, geçersiz aralık belirtti. +Bu işlemin kotası aşıldı. Daha sonra tekrar deneyin. Paraguay Cezayir Çok fazla kez yanlış şifre girdiniz. Lütfen birkaç dakika içinde tekrar deneyin. Norfolk Adası Telefon numarası otomatik olarak doğrulandı -Şifrenizi değiştirmek için önce mevcut şifrenizi girmeniz gerekir. -Geri +Ağ hatası, internet bağlantınızı kontrol edin. +Doğrulandı! Hindistan Benin Devam @@ -331,27 +360,33 @@ E-posta İleri Bu kod artık geçerli değil -Facebook ile oturum aç + adresine ek talimatlar içeren bir oturum açma e-postası gönderildi. Oturum açma işlemini tamamlamak için e-postanızı kontrol edin. E-posta adresini onaylayın Ad ve soyadı Grenada Batı Sahra +Mevcut sistem durumunda istek yürütülemiyor. Bilinmeyen bir hata oluştu. Oturum açmak için adresini zaten kullandınız. Bu hesaba ait şifrenizi girin. +Oturum açma e-postası gönderildi İrlanda Demokratik Kongo Cumhuriyeti Şifreyi kurtarın Kongo Cumhuriyeti +Bir istemcinin oluşturmaya çalıştığı kaynak zaten mevcut. Misafir olarak devam et San Marino Güney Kıbrıs Rum Yönetimi Solomon Adaları ile devam edilsin mi? +Bu cihazda ile oturum açmaya devam etmek için şifreyi kurtarmanız gerekir. Maldivler São Tomé ve Príncipe Oturum açma işlemini tamamlamak için e-posta adresinizi onaylayın Slovenya Güney Kore +Eşzamanlılık uyuşmazlığı (ör. okuma-değiştirme-yazma uyuşmazlığı). +API yöntemi, sunucu tarafından uygulanmadı. Lesotho Telefonla oturum aç E-postanızı tekrar doğrulamayı deneyin @@ -370,9 +405,11 @@ adresini oturum açmak için zaten kullandınız. Bu hesap için şifrenizi girin. {plural_var,plural, =1{Şifre yeterince güçlü değil. En az karakter kullanın ve hem harf hem de rakam ekleyin}other{Şifre yeterince güçlü değil. En az karakter kullanın ve hem harf hem de rakam ekleyin}} Zimbabve -Şifrenizi kurtarmak için adresine gönderilen talimatları uygulayın. -Telefon numaranız doğrulanırken bir sorun oluştu +Eksik, geçersiz veya süresi dolmuş OAuth jetonundan dolayı isteğin kimliği doğrulanamadı. +Lütfen 6 haneli bir telefonla doğrulama kodu girin. +Şifre seçin Yeni bir cihaz veya tarayıcı algılandı +Şurada oturum aç: Çek Cumhuriyeti Tuvalu GitHub ile oturum aç @@ -398,6 +435,10 @@ İspanya Kosta Rika Samoa + + uygulamasındaki hesabınız, iki faktörlü kimlik doğrulama için ile güncellendi. +Bu değişikliği siz istemediyseniz işlemi geri almak için aşağıdaki bağlantıyı kullanabilirsiniz. + Mali Katar Uruguay @@ -428,6 +469,7 @@ Bermuda Niue Guadeloupe +İstemci, geçersiz bir proje yapılandırması belirtti. Filistin Toprakları Slovakya Guernsey @@ -440,19 +482,24 @@ Malezya Cocos [Keeling] Adaları Hala hesabınızı bağlamak istiyorsanız bağlantıyı oturum açma işlemini başlattığınız cihazda açın. Hesabınızı bağlamak istemiyorsanız bu cihazda oturum açmak için Devam Et'e dokunun. +Tekrar dene İlk hedefiniz hesabınızı e-posta hesabınıza bağlamaktı ancak bağlantıyı oturumunuzun açık olmadığı farklı bir cihazda açtınız. 0: içinde kodu yeniden gönder Fas +Bu cihazı tanımıyorsanız hesabınıza başka biri erişmeye çalışıyor olabilir. Parolanızı hemen değiştirmeniz önerilir. Bangladeş Telefon Finlandiya Birleşik Arap Emirlikleri +E-posta adresiniz doğrulandı ve değiştirildi Şifre Orta Afrika Cumhuriyeti Curaçao +Olağan dışı etkinlikten dolayı bu cihazdan gelen tüm istekleri engelledik. Daha sonra tekrar deneyin. Tunus Şifre değiştirildi Yunanistan +İkinci faktör kaldırıldı Angola Kuveyt Girdiğiniz e-posta ile şifre eşleşmiyor diff --git a/translations/uk.xtb b/translations/uk.xtb index 45c7aaf8..bb532d37 100644 --- a/translations/uk.xtb +++ b/translations/uk.xtb @@ -12,11 +12,14 @@ Філіппіни Румунія Південна Джорджія та Південні Сандвічеві Острови -ОК +Безповоротна втрата чи пошкодження даних. Італія Ліван Гаяна Гватемала +Під час вилучення другого фактора сталася помилка.Повторіть спробу. Якщо проблема не зникне, зв'яжіться зі службою підтримки. +Оновіть електронну адресу ще раз +Сталася помилка мережі. Спробуйте пізніше. Введіть дійсний номер телефону Торкаючись кнопки "Підтвердити", ви приймаєте наші Умови використання та Політику конфіденційності. Вам може надійти SMS-повідомлення. За SMS і використання трафіку може стягуватися плата. Введіть пароль @@ -27,6 +30,7 @@ З вашої IP-адреси надходить дуже багато запитів на створення облікового запису. Спробуйте за кілька хвилин. Шрі-Ланка Тепер можете ввійти, використовуючи дані нового облікового запису +Клієнт указав недійсний аргумент. Чад Повторно надіслати код через Білорусь @@ -62,6 +66,7 @@ Github Політика конфіденційності Гуам +Вичерпано квоту ресурсу або досягнуто ліміту частоти викликів. Хорватія Литва Мальта @@ -71,22 +76,28 @@ Гість Гана Ваш веб-переглядач не підтримує веб-сховище. Скористайтесь іншим веб-переглядачем. +Запит скасовано клієнтом. Не вдалося змінити вашу електронну адресу для входу.Якщо вам не вдасться скинути адресу, зв’яжіться з адміністратором. Бахрейн +Внутрішня помилка сервера. Іран +GitHub Перевіряємо, чи ви не робот… Цей код уже не дійсний Нова Каледонія Монако Сейшельські Острови +Другий фактор автентифікації ( ) вилучено. Не вдалось оновити поточний анонімний обліковий запис. Указані облікові дані вже пов’язані з іншим обліковим записом користувача. Ви ввели неправильний пароль забагато разів. Спробуйте за кілька хвилин. Безпека Ваш запит на скидання пароля вже не дійсний або посиланням уже скористалися +Тепер ви можете ввійти в обліковий запис за допомогою нової електронної адреси . Наданий код країни не підтримується. Переконайтеся, що ви правильно вказали електронну адресу. Twitter Камбоджа +Сервіс недоступний. Португалія Французька Гвіана Віргінські Острови (Велика Британія) @@ -102,19 +113,21 @@ Підтвердити Південно-Африканська Республіка Щоб продовжити, введіть електронну адресу +Адміністратор вимкнув обліковий запис користувача. Електронну адресу для входу та додаткові вказівки надіслано на адресу . Дотримуйтеся цих вказівок, щоб увійти в обліковий запис. Створити обліковий запис Афганістан Торкаючись кнопки "", ви приймаєте . Канада Данія +Перевищено кінцевий термін запиту. Австрія Електронні адреси не збігаються Південний Судан Ємен Гвінея-Бісау Саудівська Аравія -Логотип додатка +Для входу в додаток використовуватиметься додатковий спосіб ідентифікації Віргінські Острови (США) Увійти, використовуючи телефон Індонезія @@ -122,6 +135,7 @@ Венесуела Не вдається ввійти? Підтвердьте свою особу +Під час авторизації запиту сталася помилка. Ще раз перейдіть за URL-адресою, з якої вас було переспрямовано на цю сторінку, щоб перезапустити авторизацію. Домініка Новий пароль Габон @@ -139,6 +153,7 @@ Гамбія Готово Скасувати +Невідома помилка сервера. Введіть назву свого облікового запису Ірак В’єтнам @@ -150,6 +165,8 @@ Непал Токелау Облікові дані, вибрані для постачальника послуг автентифікації, не підтримуються. +Для цієї електронної адреси немає опцій для входу. Спробуйте ввійти за допомогою іншої електронної адреси. +Введіть ім’я та прізвище. Колумбія Торкаючись опції "ЗБЕРЕГТИ", ви приймаєте Бурунді @@ -157,7 +174,7 @@ Твіттер Вашу електронну адресу підтверджено Папуа – Нова Гвінея -Зберегти +Указаний ресурс не знайдено. Підтвердження… Увійти Східний Тимор @@ -190,6 +207,7 @@ Телефон Надіслати повторно Гондурас +Час очікування операції минув. Нігер Ця електронна адреса не відповідає адресі наявного облікового запису Йорданія @@ -201,6 +219,7 @@ Ізраїль Сталася помилка Номер +Клієнт не має належного дозволу. Введіть символи reCAPTCHA Нікарагуа Буркіна-Фасо @@ -209,14 +228,17 @@ Острови Теркс і Кайкос Повторно надіслати код через Перевірте свою електронну пошту +Не вдалося вилучити другий фактор Додати пароль Сальвадор +Щоб змінити пароль облікового запису, потрібно ввійти ще раз. Сербія Беліз для облікового запису Щоб зв’язати обліковий запис з цієї електронною адресою, потрібно відкрити посилання на тому самому пристрої або в тому самому веб-переглядачі. Швеція Сен-Мартен +Ваш запит на підтвердження й оновлення електронної адреси вже не дійсний або посиланням уже скористалися. Сент-Люсія @string/app_name Перевірте свою електронну пошту @@ -224,13 +246,15 @@ Оман Нідерландські Карибські Острови Змінена електронна адреса -Увійти через Twitter +Ви успішно вийшли. +Увійти, використовуючи телефон Номер телефону У вас уже є обліковий запис Таїланд Політика конфіденційності Ліхтенштейн Змінити ім’я +Другий фактор автентифікації (пристрій або додаток) вилучено. Спробуйте відкрити посилання на пристрої або у веб-переглядачі, де ви починали процедуру входу. Нідерланди Екваторіальна Гвінея @@ -254,7 +278,7 @@ Щоб продовжити, введіть електронну адресу Підтвердьте номер телефону Ліберія -Обліковий запис із такою електронною адресою вже існує. +Вихід Мадагаскар Казахстан Виконується вхід… @@ -273,15 +297,20 @@ Введіть -значний код, який ми надіслали на номер Цей номер телефону використовувався забагато разів Надана електронна адреса не відповідає поточному сеансу входу. -Від’єднати обліковий запис +Твіттер + +Увійдіть в обліковий запис у додатку </p></p> + Перевірте доступний обсяг пам’яті для папки "Вхідні" й інші можливі проблеми з налаштуваннями. +Клієнт указав недійсний діапазон. +Перевищено квоту для цієї операції. Спробуйте пізніше. Парагвай Алжир Ви ввели неправильний пароль забагато разів. Спробуйте за кілька хвилин. Острів Норфолк Номер телефону підтверджено автоматично -Щоб змінити пароль, спершу введіть поточний. -Назад +Помилка мережі. Перевірте інтернет-з’єднання. +Підтверджено Індія Бенін Продовжити @@ -330,27 +359,33 @@ Електронна адреса Далі Цей код уже не дійсний -Увійти через Facebook +Посилання для входу та додаткові вказівки надіслано на адресу . Дотримуйтеся їх, щоб увійти в обліковий запис. Підтвердьте електронну адресу Ім’я та прізвище Гренада Західна Сахара +У поточному стані системи запит не можна виконати. Сталася невідома помилка. Ви вже використовували електронну адресу для входу. Введіть пароль для цього облікового запису. +Електронну адресу для входу надіслано Ірландія Демократична Республіка Конго Відновити пароль Республіка Конго +Ресурс, який клієнт намагався створити, уже є. Продовжити як гість Сан-Марино Кіпр Соломонові Острови Увійти, використовуючи електронну адресу ? +Щоб увійти в обліковий запис на цьому пристрої, потрібно відновити пароль. Мальдіви Сан-Томе і Принсіпі Підтвердьте електронну адресу, щоб завершити вхід Словенія Південна Корея +Конфлікт паралельного доступу, як-от конфлікт "читання-модифікація-запис". +Метод API не застосовано на сервері. Лесото Увійти, використовуючи телефон Ще раз підтвердьте електронну адресу @@ -370,8 +405,10 @@ для входу. Введіть пароль для цього облікового запису. {plural_var,plural, =1{Пароль недостатньо надійний. Введіть принаймні символ та комбінацію літер і цифр}one{Пароль недостатньо надійний. Введіть принаймні символ та комбінацію літер і цифр}few{Пароль недостатньо надійний. Введіть принаймні символи та комбінацію літер і цифр}many{Пароль недостатньо надійний. Введіть принаймні символів та комбінацію літер і цифр}other{Пароль недостатньо надійний. Введіть принаймні символу та комбінацію літер і цифр}} Зімбабве +Запит не автентифіковано через відсутній, недійсний або прострочений маркер OAuth. Не вдалося підтвердити ваш номер телефону Виявлено новий пристрій або веб-переглядач +Увійти в обліковий запис у Чехія Тувалу Увійти через GitHub @@ -426,6 +463,7 @@ Бермудські Острови Ніуе Гваделупа +Клієнт надав недійсну конфігурацію проекту. Палестинські території Словаччина Гернсі @@ -438,19 +476,24 @@ Малайзія Кокосові (Кілінгові) Острови Щоб зв’язати обліковий запис , відкрийте посилання на пристрої, де ви почали входити, або натисніть "Продовжити", щоб увійти на цьому пристрої. +Повторити спробу Ви збиралися зв’язати обліковий запис зі своєю електронною адресою, але відкрили надане посилання на пристрої, де не виконано вхід. Повторно надіслати код через 0: Марокко +Якщо ви не впізнаєте цей пристрій, можливо, хтось намагається отримати доступ до вашого облікового запису. Радимо негайно змінити пароль. Бангладеш Телефон Фінляндія Об’єднані Арабські Емірати +Вашу електронну адресу підтверджено та змінено Пароль Центрально-Африканська Республіка Кюрасао +Ми заблокували всі запити з цього пристрою через незвичну активність. Спробуйте пізніше. Туніс Пароль змінено Греція +Другий фактор вилучено Ангола Кувейт Електронна адреса та пароль не збігаються diff --git a/translations/vi.xtb b/translations/vi.xtb index 6e44f114..8af20e4f 100644 --- a/translations/vi.xtb +++ b/translations/vi.xtb @@ -12,11 +12,14 @@ Philippines Rumani Nam Georgia và Nam Quần đảo Sandwich -OK +Mất hoặc hư hỏng dữ liệu và không phục hồi được. Ý Li Băng Guyana Guatemala +Đã xảy ra lỗi trong quá trình xóa yếu tố thứ hai.Vui lòng thử xóa lại lần nữa. Nếu bạn vẫn không xóa được, hãy liên hệ với bộ phận hỗ trợ. +Hãy thử cập nhật email của bạn lần nữa +Đã xảy ra lỗi mạng. Thử lại sau. Nhập số điện thoại hợp lệ Bằng cách nhấn vào Xác minh, bạn cho biết rằng bạn chấp nhận Điều khoản dịch vụChính sách quyền riêng tư của chúng tôi. Bạn có thể nhận được một tin nhắn SMS. Cước tin nhắn và dữ liệu có thể áp dụng. Nhập mật khẩu của bạn @@ -27,6 +30,7 @@ Có quá nhiều yêu cầu tài khoản từ địa chỉ IP của bạn. Hãy thử lại sau một vài phút. Sri Lanka Bạn hiện có thể đăng nhập bằng tài khoản mới +Ứng dụng khách chỉ định đối số không hợp lệ Chad Gửi lại mã bằng Belarus @@ -62,6 +66,7 @@ Github Chính sách quyền riêng tư Guam +Hết định mức tài nguyên hoặc đã đạt đến tốc độ giới hạn. Croatia Lithuania Malta @@ -71,22 +76,28 @@ Người dùng khách Ghana Trình duyệt bạn đang sử dụng không hỗ trợ Lưu trữ trên web. Vui lòng thử lại bằng một trình duyệt khác. +Ứng dụng khách đã hủy yêu cầu. Đã xảy ra sự cố khi đổi về email đăng nhập của bạn.Nếu bạn thử lại và vẫn không thể đặt lại email, hãy thử yêu cầu quản trị viên trợ giúp. Bahrain +Lỗi máy chủ nội bộ. Iran +GitHub Đang xác minh bạn không phải là rô bốt... Mã này không còn hợp lệ New Caledonia Monaco Seychelles + đã không được sử dụng làm bước xác thực thứ hai. Người dùng ẩn danh hiện tại không thể nâng cấp. Thông tin đăng nhập không ẩn danh này đã được liên kết với một tài khoản người dùng khác. Bạn đã nhập sai mật khẩu quá nhiều lần. Hãy thử lại sau một vài phút. Bảo mật Yêu cầu đặt lại mật khẩu của bạn đã hết hạn hoặc liên kết đã được sử dụng +Giờ đây, bạn có thể đăng nhập bằng email mới là . Không hỗ trợ mã quốc gia người dùng cung cấp. Kiểm tra để đảm bảo bạn không viết sai chính tả tên email. Twitter Campuchia +Không có dịch vụ. Bồ Đào Nha Guiana thuộc Pháp Quần đảo Virgin thuộc Anh @@ -102,19 +113,21 @@ Xác minh Nam Phi Nhập địa chỉ email của bạn để tiếp tục +Quản trị viên đã vô hiệu hóa tài khoản người dùng này. Email đăng nhập có hướng dẫn bổ sung đã được gửi tới . Hãy kiểm tra hộp thư của bạn để hoàn tất đăng nhập. Tạo tài khoản Afghanistan Bằng cách nhấn vào , bạn cho biết rằng bạn đồng ý với . Canada Đan Mạch +Đã quá hạn chót cho yêu cầu. Áo Email không khớp Nam Sudan Yemen Guinea-Bissau Ả Rập Xê Út -Biểu trưng ứng dụng +Đã thêm một yếu tố bảo mật bổ sung vào Quần đảo Virgin thuộc Hoa Kỳ Đăng nhập bằng điện thoại Indonesia @@ -122,6 +135,7 @@ Venezuela Bạn gặp sự cố khi đăng nhập? Xác minh đó là bạn +Đã xảy ra lỗi khi xác thực yêu cầu của bạn. Vui lòng truy cập lại vào URL đã chuyển bạn đến trang này để bắt đầu lại quá trình xác thực. Dominica Mật khẩu mới Gabon @@ -139,6 +153,7 @@ Gambia Xong Hủy +Lỗi máy chủ không xác định. Nhập tên tài khoản của bạn Iraq Việt Nam @@ -150,6 +165,8 @@ Nepal Tokelau Thông tin đăng nhập đã chọn cho nhà cung cấp thông tin xác thực không được hỗ trợ! +Không có phương thức đăng nhập phù hợp với địa chỉ email đã cho. Vui lòng thử lại bằng địa chỉ email khác. +Vui lòng nhập họ và tên. Colombia Bằng cách nhấn vào LƯU, bạn cho biết rằng bạn đồng ý với Burundi @@ -157,7 +174,7 @@ Twitter Email của bạn đã được xác minh Papua New Guinea -Lưu +Không tìm thấy tài nguyên được chỉ định. Đang xác minh... Đăng nhập Đông Timor @@ -190,6 +207,7 @@ Điện thoại Gửi lại Honduras +Đã hết thời gian chờ cho thao tác. Niger Địa chỉ email đó không khớp với tài khoản hiện có Jordan @@ -201,6 +219,7 @@ Israel Đã xảy ra lỗi Số +Ứng dụng khách không có đủ quyền. Giải quyết reCAPTCHA Nicaragua Burkina Faso @@ -209,14 +228,17 @@ Quần đảo Turks và Caicos Gửi lại mã sau Kiểm tra email của bạn +Không thể xóa yếu tố thứ hai Thêm mật khẩu El Salvador +Để thay đổi mật khẩu cho tài khoản của bạn, bạn sẽ phải đăng nhập lại. Serbia Belize cho Để quy trình kết nối tài khoản với email này diễn ra thành công, bạn phải mở liên kết trên cùng một thiết bị hoặc trình duyệt. Thụy Điển Saint Martin +Yêu cầu xác minh và cập nhật email của bạn đã hết hạn hoặc đường dẫn liên kết đã được sử dụng. St. Lucia @string/app_name Kiểm tra email của bạn @@ -224,13 +246,15 @@ Oman Caribe Hà Lan Địa chỉ email đã cập nhật -Đăng nhập bằng Twitter +Bạn đã đăng xuất thành công. +Đăng nhập bằng điện thoại Số điện thoại Bạn đã có tài khoản Thái Lan Chính sách về quyền riêng tư Liechtenstein Chỉnh sửa tên +Thiết bị hoặc ứng dụng này đã không được sử dụng làm bước xác thực thứ hai. Hãy thử mở liên kết bằng cùng một thiết bị hoặc trình duyệt mà bạn dùng để bắt đầu quá trình đăng nhập. Hà Lan Guinea Xích đạo @@ -254,7 +278,7 @@ Nhập địa chỉ email của bạn để tiếp tục Xác minh số điện thoại của bạn Liberia -Tài khoản có địa chỉ email đó đã tồn tại. +Đăng xuất Madagascar Kazakhstan Đang đăng nhập... @@ -273,15 +297,20 @@ Nhập mã chữ số mà chúng tôi gửi cho bạn Số điện thoại này đã được sử dụng quá nhiều lần Email đã cung cấp không khớp với phiên đăng nhập hiện tại. -Hủy liên kết tài khoản +Twitter + +Hãy đăng nhập vào </p></p> + Kiểm tra để đảm bải dung lượng bộ nhớ hộp thư đến của bạn chưa hết hoặc không có vấn đề khác liên quan đến tùy chọn cài đặt hộp thư đến. +Ứng dụng khách chỉ định phạm vi không hợp lệ. +Đã vượt quá định mức cho thao tác này. Thử lại sau. Paraguay Algeria Bạn đã nhập sai mật khẩu quá nhiều lần. Vui lòng thử lại sau một vài phút. Đảo Norfolk Đã tự động xác minh số điện thoại -Để thay đổi mật khẩu, trước tiên bạn phải nhập mật khẩu hiện tại của bạn. -Quay lại +Đã xảy ra lỗi mạng, hãy kiểm tra kết nối Internet của bạn. +Đã xác minh! Ấn Độ Benin Tiếp tục @@ -330,27 +359,33 @@ Email Tiếp Mã này không còn hợp lệ -Đăng nhập bằng Facebook +Email đăng nhập có hướng dẫn bổ sung đã gửi tới . Hãy tìm email này trong hộp thư của bạn để hoàn tất đăng nhập. Xác nhận email Họ và tên Grenada Tây Sahara +Không thể thực thi yêu cầu trong tình trạng hệ thống hiện tại. Đã xảy ra lỗi không xác định. Bạn đã sử dụng để đăng nhập. Hãy nhập mật khẩu của bạn cho tài khoản đó. +Đã gửi email đăng nhập Ireland Cộng hòa dân chủ Congo Khôi phục mật khẩu Cộng hòa Congo +Tài nguyên mà ứng dụng khách tìm cách tạo đã tồn tại. Tiếp tục với vai trò người dùng khách San Marino Đảo Síp Quần đảo Solomon Tiếp tục với ? +Để tiếp tục đăng nhập bằng email trên thiết bị này, bạn phải khôi phục mật khẩu. Maldives São Tomé và Príncipe Xác nhận email của bạn để hoàn tất đăng nhập Slovenia Hàn Quốc +Xung đột tình trạng đồng thời, ví dụ như xung đột read-modify-write. +Máy chủ không triển khai phương thức API. Lesotho Đăng nhập bằng điện thoại Thử xác minh lại email của bạn @@ -370,8 +405,10 @@ để đăng nhập. Hãy nhập mật khẩu của bạn cho tài khoản đó. {plural_var,plural, =1{Mật khẩu không đủ mạnh. Sử dụng ít nhất ký tự, kết hợp chữ cái và số}other{Mật khẩu không đủ mạnh. Sử dụng ít nhất ký tự, kết hợp chữ cái và số}} Zimbabwe +Yêu cầu không được xác thực do không có mã thông báo OAuth hoặc mã thông báo OAuth không hợp lệ hoặc đã hết hạn. Đã xảy ra sự cố khi xác minh số điện thoại của bạn Đã phát hiện thiết bị hoặc trình duyệt mới +Đăng nhập vào Cộng hòa Séc Tuvalu Đăng nhập bằng GitHub @@ -426,6 +463,7 @@ Bermuda Niue Guadeloupe +Ứng dụng xác định thông tin cấu hình không hợp lệ cho dự án. Lãnh thổ Palestin Slovakia Guernsey @@ -438,19 +476,24 @@ Malaysia Quần đảo Cocos [Keeling] Nếu bạn vẫn muốn kết nối với tài khoản , hãy mở liên kết này trên cùng một thiết bị mà bạn đã đăng nhập từ đầu. Nếu không, hãy nhấn Tiếp tục để đăng nhập trên thiết bị này. +Thử lại Ban đầu, bạn có ý định kết nối với tài khoản email của bạn nhưng đã mở liên kết trên một thiết bị khác mà bạn chưa đăng nhập. Gửi lại mã sau 0: Ma-rốc +Nếu bạn không nhận ra thiết bị này, thì có thể có người đang tìm cách truy cập vào tài khoản của bạn. Hãy xem xét việc đổi mật khẩu ngay lập tức. Bangladesh Điện thoại Phần Lan Các Tiểu Vương quốc Ả Rập Thống nhất +Đã xác minh và thay đổi email của bạn Mật khẩu Cộng hòa Trung Phi Curaçao +Chúng tôi đã chặn tất cả yêu cầu từ thiết bị này do phát hiện hoạt động bất thường. Thử lại sau. Tunisia Mật khẩu đã thay đổi Hy Lạp +Đã xóa yếu tố thứ hai Angola Kuwait Email và mật khẩu bạn đã nhập không khớp diff --git a/translations/zh-CN.xtb b/translations/zh-CN.xtb index 14e4a6f0..6fdaf23c 100644 --- a/translations/zh-CN.xtb +++ b/translations/zh-CN.xtb @@ -12,11 +12,14 @@ 菲律宾 罗马尼亚 南乔治亚和南桑德威奇群岛 -确定 +出现了不可恢复的数据丢失或数据损坏问题。 意大利 黎巴嫩 圭亚那 危地马拉 +移除第二重身份验证时出现错误。请尝试重新移除。如果这样解决不了问题,请联系支持团队获取帮助。 +请尝试重新更新您的电子邮件地址 +出现网络错误,请稍后再试。 请输入有效的电话号码 点按“验证”即表示您接受我们的服务条款隐私权政策。系统会向您发送一条短信。这可能会产生短信费用和上网流量费。 请输入您的密码 @@ -27,6 +30,7 @@ 来自您的 IP 地址的帐号请求过多,请过几分钟再试。 斯里兰卡 现在您可以使用新帐号登录了 +客户端指定了无效参数。 乍得 后可重新发送验证码 白俄罗斯 @@ -64,6 +68,7 @@ Github 隐私权政策 关岛 +资源配额不足或达到了速率限制。 克罗地亚 立陶宛 马耳他 @@ -73,22 +78,28 @@ 访客 加纳 您使用的浏览器不支持网络存储。请使用其他浏览器重试。 +请求被客户端取消。 改回登录电子邮件地址时出现了问题。如果您重试后仍无法重置您的电子邮件地址,请尝试向管理员求助。 巴林 +内部服务器错误。 伊朗 +GitHub 正在验证您是否为机器人… 此验证码已失效 新喀里多尼亚 摩纳哥 塞舌尔 +已移除用于第二步身份验证的 当前匿名用户未能升级。此非匿名凭据已与另一用户帐号关联。 您输入错误密码的次数过多,请过几分钟再试。 安全 您的密码重置请求已过期,或者相关链接已被使用 +现在您可以使用新电子邮件地址登录了: 提供的国家/地区代码不受支持。 确保没有写错电子邮件地址。 Twitter 柬埔寨 +服务不可用。 葡萄牙 法属圭亚那 英属维尔京群岛 @@ -104,6 +115,7 @@ 验证 南非 请输入您的电子邮件地址以继续 +用户帐号已被管理员停用。 系统已将登录电子邮件和附加说明发送至 。请查收电子邮件以完成登录。 输入您的密码 创建帐号 @@ -111,20 +123,22 @@ 点按“”即表示您同意 加拿大 丹麦 +已超过请求时限。 奥地利 电子邮件地址不匹配 南苏丹 也门 几内亚比绍 沙特阿拉伯 -应用徽标 +中新增了额外的安全措施 美属维尔京群岛 使用电话号码登录 印度尼西亚 您最初希望使用 登录 委内瑞拉 登录时遇到问题? -请验证是您本人 +请验证您的身份 +在对您的请求进行身份验证时遇到了问题。请再次访问将您重定向至此页面的网址,以重新启动身份验证流程。 多米尼克 新密码 加蓬 @@ -142,6 +156,7 @@ 冈比亚 完成 取消 +出现未知的服务器错误。 请输入您的帐号名称 伊拉克 越南 @@ -153,6 +168,8 @@ 尼泊尔 托克劳 为身份验证提供方选择的凭据不受支持! +找不到指定电子邮件地址对应的登录服务提供方,请尝试使用其他电子邮件地址。 +请输入姓氏和名字。 哥伦比亚 点按“保存”,即表示您同意 布隆迪 @@ -160,7 +177,7 @@ Twitter 您的电子邮件地址已通过验证 巴布亚新几内亚 -保存 +未找到指定的资源。 正在验证… 登录 东帝汶 @@ -193,6 +210,7 @@ 电话 重新发送 洪都拉斯 +操作已超时。 尼日尔 该电子邮件地址没有相匹配的现有帐号 约旦 @@ -204,6 +222,7 @@ 以色列 遇到错误 号码 +客户端权限不足。 请完成 reCAPTCHA 验证 尼加拉瓜 布基纳法索 @@ -212,14 +231,17 @@ 特克斯和凯科斯群岛 后可重新发送验证码 请查收电子邮件 +无法移除您的第二重身份验证 添加密码 萨尔瓦多 +要更改帐号的密码,您需要重新登录。 塞尔维亚 伯利兹 电子邮件地址: 为了完成此流程,顺利将您的帐号与此电子邮件地址关联起来,您必须使用相同的设备或浏览器打开链接。 瑞典 圣马丁岛 +您要求验证并跟新电子邮件地址的请求已过期,或者相关链接已被使用。 圣卢西亚 @string/app_name 请查收电子邮件 @@ -227,13 +249,15 @@ 阿曼 荷兰加勒比 电子邮件地址已更新 -使用 Twitter 帐号登录 +您现在已成功退出帐号。 +使用电话号码登录 电话号码 您已经有帐号了 泰国 验证码有误,请重试。 列支敦士登 修改名称 +已移除用于第二步身份验证的设备或应用。 请尝试使用您最初进入登录流程时所用的设备或浏览器打开链接。 荷兰 赤道几内亚 @@ -257,7 +281,7 @@ 请输入您的电子邮件地址以继续 验证您的电话号码 利比里亚 -已存在一个使用该电子邮件地址的帐号。 +退出帐号 马达加斯加 哈萨克斯坦 正在登录… @@ -276,15 +300,20 @@ 输入我们发送至以下电话号码的 位数验证码: 此电话号码的使用次数过多 提供的电子邮件地址与当前登录会话不符。 -取消帐号关联 +Twitter + +登录</p></p> + 确保收件箱空间充足,且不存在其他与收件箱设置有关的问题。 +客户端指定了无效范围。 +已超出此操作的配额,请稍后再试。 巴拉圭 阿尔及利亚 您输入错误密码的次数过多,请过几分钟再试。 诺福克岛 电话号码已自动验证 -要更改密码,您需要先输入当前的密码。 -返回 +网络错误,请检查您的互联网连接。 +已验证! 印度 贝宁 继续 @@ -333,27 +362,33 @@ 电子邮件地址 下一步 此验证码已失效 -使用 Facebook 帐号登录 +系统已将含附加说明的登录电子邮件发送至 。请查收电子邮件以完成登录。 确认电子邮件地址 姓名 格林纳达 西撒哈拉 +请求无法在当前系统状态下执行。 发生未知错误。 您已经使用 登录了,请输入该帐号的密码。 +已发送登录电子邮件 爱尔兰 刚果民主共和国 找回密码 刚果共和国 +客户端尝试创建的资源已存在。 以访客身份继续 圣马力诺 塞浦路斯 所罗门群岛 是否使用 继续登录? +如要继续,请使用 在此设备上登录,您必须找回此密码。 马尔代夫 圣多美和普林西比 确认电子邮件地址以完成登录 斯洛文尼亚 韩国 +并发冲突,例如读取/修改/写入冲突。 +API 方法未通过服务器实现。 莱索托 使用电话号码登录 尝试重新验证您的电子邮件地址 @@ -372,9 +407,11 @@ 您已经使用 登录了,请输入该帐号的密码。 {plural_var,plural, =1{密码的安全系数不够高。请使用至少 个字符,并混合使用字母和数字}other{密码的安全系数不够高。请使用至少 个字符,并混合使用字母和数字}} 津巴布韦 -请按照发送到 的说明找回密码。 -验证您的电话号码时出现问题 +由于 OAuth 令牌丢失、无效或过期,请求未通过身份验证。 +请提供 6 位数短信验证码。 +选择密码 检测到新设备或新浏览器 +登录 捷克 图瓦卢 使用 GitHub 帐号登录 @@ -401,6 +438,10 @@ 西班牙 哥斯达黎加 萨摩亚 + +您在中的帐号新增了,以实现双重身份验证。 +如果您没有请求过此项修改,请使用以下链接撤销该更改。 + 马里 卡塔尔 乌拉圭 @@ -431,6 +472,7 @@ 百慕大 纽埃 瓜德罗普 +客户端指定的项目配置无效。 巴勒斯坦地区 斯洛伐克 根西岛 @@ -443,19 +485,24 @@ 马来西亚 科科斯(基林)群岛 如果您仍希望关联您的帐号,请在您最初开始登录时所用的设备上打开此链接。否则,请点按“继续”以在当前设备上登录。 +重试 您最初的目的是将与您的电子邮件帐号关联起来,但是您换用其他未登录的设备打开了此链接。 后可重新发送验证码 摩洛哥 +如果您不认识此设备,那么可能是有人在试图访问您的帐号。建议您立即更改密码 孟加拉国 电话号码 芬兰 阿拉伯联合酋长国 +您的电子邮件地址已经验证并更改 密码 中非共和国 库拉索 +由于异常活动,我们已阻止此设备的所有请求。请稍后再试。 突尼斯 密码已更改 希腊 +已移除第二重身份验证 安哥拉 科威特 您输入的电子邮件地址和密码不匹配 diff --git a/translations/zh-TW.xtb b/translations/zh-TW.xtb index 19f7b62e..1f8e95f5 100644 --- a/translations/zh-TW.xtb +++ b/translations/zh-TW.xtb @@ -12,11 +12,14 @@ 菲律賓 羅馬尼亞 南喬治亞及南桑威奇群島 -確定 +發生無法復原的資料遺失或資料毀損情形。 義大利 黎巴嫩 蓋亞那 瓜地馬拉 +移除次要驗證方式時發生錯誤。請試著重新移除一次。如果問題仍無法解決,請聯絡支援小組以取得協助。 +請再次更新您的電子郵件地址 +發生網路錯誤,請稍後再試。 請輸入有效的電話號碼 輕觸 [驗證] 即表示您同意接受我們的《服務條款》與《隱私權政策》。系統將會傳送簡訊給您,不過您可能需要支付簡訊和數據傳輸費用。 輸入密碼 @@ -27,6 +30,7 @@ 您的 IP 位址傳送的建立帳戶要求次數過多,請於幾分鐘後再試一次。 斯里蘭卡 現在您可以登入新的帳戶 +用戶端指定的引數無效。 查德 後重新傳送驗證碼 白俄羅斯 @@ -62,6 +66,7 @@ Github 隱私權政策 關島 +資源配額用盡或達到頻率限制。 克羅埃西亞 立陶宛 馬爾他 @@ -71,22 +76,28 @@ 訪客 迦納 您使用的瀏覽器不支援網路儲存空間,請改用其他瀏覽器並再試一次。 +用戶端已取消要求。 系統無法還原您已變更的登入電子郵件地址。如果重試後仍然無法重設電子郵件地址,請與您的系統管理員聯絡,由對方協助您完成這項操作。 巴林 +內部伺服器發生錯誤。 伊朗 +GitHub 正在驗證您是否為自動程式... 這個代碼已經失效 新喀里多尼亞 摩納哥 塞席爾 +已移除次要驗證方式「 」。 當前的匿名使用者無法升級,因為該名使用者的非匿名憑證已連結至其他使用者帳戶。 您輸入錯誤密碼的次數過多,請於幾分鐘後再試一次。 安全性 您的密碼重設要求已過期,或是先前已使用過重設密碼的連結 +您已可使用新的電子郵件地址 () 登入帳戶。 系統不支援您輸入的國家/地區代碼。 檢查電子郵件地址是否拼錯。 Twitter 柬埔寨 +服務無法使用。 葡萄牙 法屬圭亞那 英屬維京群島 @@ -102,6 +113,7 @@ 驗證 南非 如要繼續,請輸入您的電子郵件地址 +使用者帳戶已遭管理員停用。 系統已將內含操作說明的登入電子郵件傳送至 。請查看您的電子郵件以完成登入程序。 輸入您的密碼 建立帳戶 @@ -109,13 +121,14 @@ 輕觸 [] 即表示您同意《》。 加拿大 丹麥 +已超出要求期限。 奧地利 電子郵件不相符 南蘇丹 葉門 幾內亞比索 沙烏地阿拉伯 -應用程式標誌 +」已新增另一項安全性要素 美屬維京群島 使用電話登入 印尼 @@ -123,6 +136,7 @@ 委內瑞拉 無法登入嗎? 請驗證您的身分 +驗證您的要求時發生問題。請再次點選當初將您重新導向至這個頁面的網址,以便重新啟動驗證程序。 多米尼克 新密碼 加彭 @@ -140,6 +154,7 @@ 甘比亞 完成 取消 +發生不明的伺服器錯誤。 請輸入您的帳戶名稱 伊拉克 越南 @@ -151,6 +166,8 @@ 尼泊爾 托克勞群島 系統不支援您選取的驗證供應商憑證! +找不到適用於這個電子郵件地址的登入方式,請改用其他電子郵件地址。 +請輸入姓名。 哥倫比亞 輕觸 [儲存] 即表示您同意 蒲隆地 @@ -158,7 +175,7 @@ Twitter 您的電子郵件地址已通過驗證 巴布亞紐幾內亞 -儲存 +找不到您指定的資源。 驗證中... 登入 東帝汶 @@ -191,6 +208,7 @@ 電話 重新傳送 宏都拉斯 +作業逾時。 尼日 找不到與這個電子郵件地址相符的帳戶 約旦 @@ -202,6 +220,7 @@ 以色列 發生錯誤 號碼 +用戶端權限不足。 請先解決 reCAPTCHA 問題 尼加拉瓜 布吉納法索 @@ -210,14 +229,17 @@ 特克斯和凱科斯群島 後重新傳送確認碼 請查看您的電子郵件 +無法移除次要驗證方式 新增密碼 薩爾瓦多共和國 +如要變更帳戶密碼,您需要重新登入。 瑟比雅 貝里斯 (登入帳戶:) 如要順利將您的 帳戶連結至這個電子郵件,請透過登入時使用的裝置或瀏覽器來開啟連結。 瑞典 法屬聖馬丁 +您的電子郵件地址驗證與更新要求已過期,或是先前已使用過該連結。 聖露西亞 @string/app_name 請查看您的電子郵件信箱 @@ -225,13 +247,15 @@ 阿曼 荷蘭加勒比海屬地 更新電子郵件地址 -使用 Twitter 帳戶登入 +您已成功登出。 +使用電話號碼登入 電話號碼 您已經有了帳戶 泰國 驗證碼錯誤,請再試一次。 列支敦斯登 編輯名稱 +已移除做為次要驗證方式的裝置或應用程式。 請透過一開始登入時使用的裝置或瀏覽器來開啟連結。 荷蘭 赤道幾內亞 @@ -255,7 +279,7 @@ 如要繼續,請輸入您的電子郵件地址 驗證您的電話號碼 賴比瑞亞 -這個電子郵件地址已連結一個帳戶。 +登出 馬達加斯加 哈薩克 登入中... @@ -264,7 +288,7 @@ 瓦利斯及富圖納群島 無法更新您的電子郵件地址 您已使用 帳戶登入,請輸入該帳戶的密碼。 -安圭拉 +安吉拉 這個電子郵件地址已存在,且不需登入即可使用。如要還原帳戶,請重設密碼。 多明尼加共和國 澤西島 @@ -274,15 +298,20 @@ 請輸入系統傳送到下列電話號碼的 位數驗證碼: 使用這個電話號碼的次數過多 您提供的電子郵件地址與目前的登入工作階段不符。 -取消連結帳戶 +Twitter + +登入 </p></p> + 確認收件匣的儲存空間是否足夠,或檢查與收件匣設定相關的其他問題。 +用戶端指定的範圍無效。 +已超出這項作業的配額上限,請稍後再試。 巴拉圭 阿爾及利亞 您輸入錯誤密碼的次數過多,請於幾分鐘後再試一次。 諾福克島 已自動驗證電話號碼 -如要變更密碼,請先輸入目前的密碼。 -上一步 +網路發生錯誤,請檢查您的網際網路連線。 +已驗證! 印度 貝南 繼續 @@ -331,27 +360,33 @@ 電子郵件 下一步 這個代碼已經失效 -使用 Facebook 帳戶登入 +系統已將登入電子郵件傳送至 ,其中包含額外的操作說明。請查看您的電子郵件以完成登入程序。 確認電子郵件地址 姓氏和名字 格瑞那達 西撒哈拉 +無法在目前的系統狀態下執行要求。 發生不明錯誤。 目前您用於登入的帳戶是 。請輸入這個帳戶的密碼。 +已寄出登入電子郵件 愛爾蘭 剛果民主共和國 重新取得密碼 剛果共和國 +用戶端嘗試建立的資源已存在。 繼續以訪客身分使用 聖馬利諾 賽普勒斯 所羅門群島 要使用 繼續登入嗎? +如要繼續在這部裝置上以 的身分登入,您就必須找回密碼。 馬爾地夫 聖多美普林西比 請確認您的電子郵件地址,以完成登入程序 斯洛維尼亞 南韓 +發生並行衝突,例如讀取-修改-寫入衝突。 +伺服器未執行 API 方法。 賴索托 使用電話號碼登入 請再次驗證您的電子郵件地址 @@ -371,9 +406,11 @@ 登入。請輸入這個帳戶的密碼。 {plural_var,plural, =1{密碼安全強度不足。請使用至少 個字元並混用字母和數字}other{密碼安全強度不足。請使用至少 個字元並混用字母和數字}} 辛巴威 -請依照傳送至 的指示重新取得密碼。 -驗證電話號碼時發生問題 +OAuth 憑證遺漏、無效或過期,因此無法驗證要求。 +請輸入 6 位數的簡訊驗證碼。 +選擇密碼 系統偵測到新的裝置或瀏覽器 +登入 捷克共和國 吐瓦魯 使用 GitHub 登入 @@ -399,6 +436,8 @@ 西班牙 哥斯大黎加 薩摩亞 + +您在「」中的帳戶已經更新,可在雙重驗證程序中使用如果您並未提出這項調整,請使用下列連結來復原變更。 馬利 卡達 烏拉圭 @@ -429,6 +468,7 @@ 百慕達 紐埃島 瓜地洛普 +用戶端指定了無效的專案設定。 巴勒斯坦自治區 斯洛伐克 根西島 @@ -441,19 +481,24 @@ 馬來西亞 科科斯群島 如果您仍想連結您的 帳戶,請在開始登入程序的同一部裝置上開啟連結。如不想連結帳戶,請輕觸 [繼續] 以在這部裝置上進行登入程序。 +重試 您原先想將 連結至您的電子郵件帳戶,卻在另一部未登入帳戶的裝置上開啟了連結。 0: 秒後將重新發送驗證碼 摩洛哥 +如果您不認識這部裝置,可能是有人試圖存取您的帳戶。建議您立即變更密碼 孟加拉 電話 芬蘭 阿拉伯聯合大公國 +您的電子郵件地址已通過驗證並變更完成 密碼 中非共和國 古拉索 +由於出現異常活動,系統已封鎖來自這部裝置的所有要求。請稍後再試。 突尼西亞 已變更密碼 希臘 +已移除次要驗證方式 安哥拉 科威特 您輸入的電子郵件地址和密碼不相符 diff --git a/types/index.d.ts b/types/index.d.ts index 993c9291..9462e4ea 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -30,9 +30,9 @@ interface Callbacks { interface SignInOption { provider: string; + hd?: string|RegExp; } - interface SamlSignInOption extends SignInOption { providerName?: string; buttonColor: string; @@ -106,6 +106,7 @@ declare namespace firebaseui.auth { autoUpgradeAnonymousUsers?: boolean; callbacks?: Callbacks; credentialHelper?: CredentialHelperType; + immediateFederatedRedirect?: boolean; popupMode?: boolean; queryParameterForSignInSuccessUrl?: string; queryParameterForWidgetMode?: string; @@ -120,6 +121,12 @@ declare namespace firebaseui.auth { widgetUrl?: string; } + interface TenantConfig extends firebaseui.auth.Config { + displayName?: string; + buttonColor?: string; + iconUrl?: string; + } + class AuthUI { static getInstance(appId?: string): AuthUI | null; // tslint:disable-next-line:no-any firebase dependency not available. @@ -153,6 +160,65 @@ declare namespace firebaseui.auth { private constructor(); static PROVIDER_ID: string; } + + interface ProjectConfig { + projectId: string; + apiKey: string; + } + + interface SelectedTenantInfo { + email?: string; + tenantId: string|null; + providerIds: string[]; + } + + interface CIAPCallbacks { + signInUiShown?(tenantId: string|null): void; + selectTenantUiShown?(): void; + selectTenantUiHidden?(): void; + // tslint:disable-next-line:no-any firebase dependency not available. + beforeSignInSuccess?(currentUser: any): Promise; + } + + interface CIAPError { + httpErrorCode?: number; + code: string; + message: string; + reason?: Error; + retry?(): Promise; + toJSON(): object; + } + + interface CIAPHandlerConfig { + authDomain: string; + displayMode?: string; + tosUrl?: (() => void)|string; + privacyPolicyUrl?: (() => void)|string; + callbacks?: firebaseui.auth.CIAPCallbacks; + tenants: {[key: string]: firebaseui.auth.TenantConfig}; + } + + class FirebaseUiHandler { + constructor( + element: Element|string, + configs: {[key: string]: firebaseui.auth.CIAPHandlerConfig}); + selectTenant( + projectConfig: firebaseui.auth.ProjectConfig, + tenantIds: string[]): Promise; + // tslint:disable-next-line:no-any firebase dependency not available. + getAuth(apiKey: string, tenantId: string|null): any; + // tslint:disable-next-line:no-any firebase dependency not available. + startSignIn(auth: any, tenantInfo?: firebaseui.auth.SelectedTenantInfo): + Promise; // tslint:disable-line + reset(): Promise; + completeSignOut(): Promise; + showProgressBar(): void; + hideProgressBar(): void; + handleError(error: Error|firebaseui.auth.CIAPError): void; + languageCode: string | null; + // tslint:disable-next-line:no-any firebase dependency not available. + processUser(user: any): Promise; + } } export = firebaseui;