import { a8 as STORAGE_AVAILABLE_KEY, a9 as _isIframe, aa as _isMobileBrowser, ab as _isIE10, ac as _isSafari, ad as _isIOS, ae as _assert, af as Delay, ag as _window, ah as isV2, ai as _createError, aj as _recaptchaV2ScriptUrl, ak as _loadJS, al as _generateCallbackName, am as _castAuth, an as _isHttpOrHttps, ao as _isWorker, ap as getRecaptchaParams, z as signInWithCredential, aq as _assertLinkedStatus, B as linkWithCredential, C as reauthenticateWithCredential, ar as startEnrollPhoneMfa, as as startSignInPhoneMfa, at as sendPhoneVerificationCode, au as _link$1, P as PhoneAuthCredential, av as _getInstance, aw as _signInWithCredential, ax as _reauthenticate, m as AuthCredential, ay as signInWithIdp, az as _fail, aA as debugAssert, aB as _assertInstanceOf, aC as _generateEventId, aD as FederatedAuthProvider, aE as _persistenceKeyName, aF as _performApiRequest, aG as _getCurrentUrl, aH as _gapiScriptUrl, aI as _emulatorUrl, aJ as _isChromeIOS, aK as _isFirefox, aL as _isIOSStandalone, aM as BaseOAuthProvider, aN as _setWindowLocation, aO as MultiFactorAssertionImpl, aP as finalizeEnrollPhoneMfa, aQ as finalizeSignInPhoneMfa, r as registerAuth, i as initializeAuth, a as indexedDBLocalPersistence, e as beforeAuthStateChanged, o as onIdTokenChanged, c as connectAuthEmulator, aR as _setExternalJSProvider, aS as _isAndroid, aT as _isIOS7Or8 } from './register-bd5b4001.js'; export { Y as ActionCodeURL, m as AuthCredential, A as AuthErrorCodes, aV as AuthImpl, E as EmailAuthCredential, q as EmailAuthProvider, F as FacebookAuthProvider, aX as FetchProvider, t as GithubAuthProvider, G as GoogleAuthProvider, O as OAuthCredential, w as OAuthProvider, P as PhoneAuthCredential, aY as SAMLAuthCredential, S as SAMLAuthProvider, T as TotpMultiFactorGenerator, b as TotpSecret, x as TwitterAuthProvider, aU as UserImpl, ae as _assert, am as _castAuth, az as _fail, aC as _generateEventId, aW as _getClientVersion, av as _getInstance, aE as _persistenceKeyName, J as applyActionCode, e as beforeAuthStateChanged, K as checkActionCode, I as confirmPasswordReset, c as connectAuthEmulator, M as createUserWithEmailAndPassword, l as debugErrorMap, k as deleteUser, V as fetchSignInMethodsForEmail, a4 as getAdditionalUserInfo, a1 as getIdToken, a2 as getIdTokenResult, a6 as getMultiFactorResolver, n as inMemoryPersistence, a as indexedDBLocalPersistence, i as initializeAuth, d as initializeRecaptchaConfig, R as isSignInWithEmailLink, B as linkWithCredential, a7 as multiFactor, f as onAuthStateChanged, o as onIdTokenChanged, Z as parseActionCodeURL, p as prodErrorMap, C as reauthenticateWithCredential, a5 as reload, j as revokeAccessToken, W as sendEmailVerification, H as sendPasswordResetEmail, Q as sendSignInLinkToEmail, s as setPersistence, y as signInAnonymously, z as signInWithCredential, D as signInWithCustomToken, N as signInWithEmailAndPassword, U as signInWithEmailLink, h as signOut, a3 as unlink, g as updateCurrentUser, $ as updateEmail, a0 as updatePassword, _ as updateProfile, u as useDeviceLanguage, v as validatePassword, X as verifyBeforeUpdateEmail, L as verifyPasswordResetCode } from './register-bd5b4001.js'; import { getUA, querystring, getModularInstance, isEmpty, getExperimentalSetting, getDefaultEmulatorHost, querystringDecode } from '@firebase/util'; import 'tslib'; import { SDK_VERSION, _getProvider, getApp } from '@firebase/app'; import '@firebase/component'; import '@firebase/logger'; /** * @license * Copyright 2021 Google LLC * * 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. */ /** * An enum of factors that may be used for multifactor authentication. * * @public */ const FactorId = { /** Phone as second factor */ PHONE: 'phone', TOTP: 'totp' }; /** * Enumeration of supported providers. * * @public */ const ProviderId = { /** Facebook provider ID */ FACEBOOK: 'facebook.com', /** GitHub provider ID */ GITHUB: 'github.com', /** Google provider ID */ GOOGLE: 'google.com', /** Password provider */ PASSWORD: 'password', /** Phone provider */ PHONE: 'phone', /** Twitter provider ID */ TWITTER: 'twitter.com' }; /** * Enumeration of supported sign-in methods. * * @public */ const SignInMethod = { /** Email link sign in method */ EMAIL_LINK: 'emailLink', /** Email/password sign in method */ EMAIL_PASSWORD: 'password', /** Facebook sign in method */ FACEBOOK: 'facebook.com', /** GitHub sign in method */ GITHUB: 'github.com', /** Google sign in method */ GOOGLE: 'google.com', /** Phone sign in method */ PHONE: 'phone', /** Twitter sign in method */ TWITTER: 'twitter.com' }; /** * Enumeration of supported operation types. * * @public */ const OperationType = { /** Operation involving linking an additional provider to an already signed-in user. */ LINK: 'link', /** Operation involving using a provider to reauthenticate an already signed-in user. */ REAUTHENTICATE: 'reauthenticate', /** Operation involving signing in a user. */ SIGN_IN: 'signIn' }; /** * An enumeration of the possible email action types. * * @public */ const ActionCodeOperation = { /** The email link sign-in action. */ EMAIL_SIGNIN: 'EMAIL_SIGNIN', /** The password reset action. */ PASSWORD_RESET: 'PASSWORD_RESET', /** The email revocation action. */ RECOVER_EMAIL: 'RECOVER_EMAIL', /** The revert second factor addition email action. */ REVERT_SECOND_FACTOR_ADDITION: 'REVERT_SECOND_FACTOR_ADDITION', /** The revert second factor addition email action. */ VERIFY_AND_CHANGE_EMAIL: 'VERIFY_AND_CHANGE_EMAIL', /** The email verification action. */ VERIFY_EMAIL: 'VERIFY_EMAIL' }; /** * @license * Copyright 2019 Google LLC * * 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. */ // There are two different browser persistence types: local and session. // Both have the same implementation but use a different underlying storage // object. class BrowserPersistenceClass { constructor(storageRetriever, type) { this.storageRetriever = storageRetriever; this.type = type; } _isAvailable() { try { if (!this.storage) { return Promise.resolve(false); } this.storage.setItem(STORAGE_AVAILABLE_KEY, '1'); this.storage.removeItem(STORAGE_AVAILABLE_KEY); return Promise.resolve(true); } catch (_a) { return Promise.resolve(false); } } _set(key, value) { this.storage.setItem(key, JSON.stringify(value)); return Promise.resolve(); } _get(key) { const json = this.storage.getItem(key); return Promise.resolve(json ? JSON.parse(json) : null); } _remove(key) { this.storage.removeItem(key); return Promise.resolve(); } get storage() { return this.storageRetriever(); } } /** * @license * Copyright 2020 Google LLC * * 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. */ function _iframeCannotSyncWebStorage() { const ua = getUA(); return _isSafari(ua) || _isIOS(ua); } // The polling period in case events are not supported const _POLLING_INTERVAL_MS = 1000; // The IE 10 localStorage cross tab synchronization delay in milliseconds const IE10_LOCAL_STORAGE_SYNC_DELAY = 10; class BrowserLocalPersistence extends BrowserPersistenceClass { constructor() { super(() => window.localStorage, "LOCAL" /* PersistenceType.LOCAL */); this.boundEventHandler = (event, poll) => this.onStorageEvent(event, poll); this.listeners = {}; this.localCache = {}; // setTimeout return value is platform specific // eslint-disable-next-line @typescript-eslint/no-explicit-any this.pollTimer = null; // Safari or iOS browser and embedded in an iframe. this.safariLocalStorageNotSynced = _iframeCannotSyncWebStorage() && _isIframe(); // Whether to use polling instead of depending on window events this.fallbackToPolling = _isMobileBrowser(); this._shouldAllowMigration = true; } forAllChangedKeys(cb) { // Check all keys with listeners on them. for (const key of Object.keys(this.listeners)) { // Get value from localStorage. const newValue = this.storage.getItem(key); const oldValue = this.localCache[key]; // If local map value does not match, trigger listener with storage event. // Differentiate this simulated event from the real storage event. if (newValue !== oldValue) { cb(key, oldValue, newValue); } } } onStorageEvent(event, poll = false) { // Key would be null in some situations, like when localStorage is cleared if (!event.key) { this.forAllChangedKeys((key, _oldValue, newValue) => { this.notifyListeners(key, newValue); }); return; } const key = event.key; // Check the mechanism how this event was detected. // The first event will dictate the mechanism to be used. if (poll) { // Environment detects storage changes via polling. // Remove storage event listener to prevent possible event duplication. this.detachListener(); } else { // Environment detects storage changes via storage event listener. // Remove polling listener to prevent possible event duplication. this.stopPolling(); } // Safari embedded iframe. Storage event will trigger with the delta // changes but no changes will be applied to the iframe localStorage. if (this.safariLocalStorageNotSynced) { // Get current iframe page value. const storedValue = this.storage.getItem(key); // Value not synchronized, synchronize manually. if (event.newValue !== storedValue) { if (event.newValue !== null) { // Value changed from current value. this.storage.setItem(key, event.newValue); } else { // Current value deleted. this.storage.removeItem(key); } } else if (this.localCache[key] === event.newValue && !poll) { // Already detected and processed, do not trigger listeners again. return; } } const triggerListeners = () => { // Keep local map up to date in case storage event is triggered before // poll. const storedValue = this.storage.getItem(key); if (!poll && this.localCache[key] === storedValue) { // Real storage event which has already been detected, do nothing. // This seems to trigger in some IE browsers for some reason. return; } this.notifyListeners(key, storedValue); }; const storedValue = this.storage.getItem(key); if (_isIE10() && storedValue !== event.newValue && event.newValue !== event.oldValue) { // IE 10 has this weird bug where a storage event would trigger with the // correct key, oldValue and newValue but localStorage.getItem(key) does // not yield the updated value until a few milliseconds. This ensures // this recovers from that situation. setTimeout(triggerListeners, IE10_LOCAL_STORAGE_SYNC_DELAY); } else { triggerListeners(); } } notifyListeners(key, value) { this.localCache[key] = value; const listeners = this.listeners[key]; if (listeners) { for (const listener of Array.from(listeners)) { listener(value ? JSON.parse(value) : value); } } } startPolling() { this.stopPolling(); this.pollTimer = setInterval(() => { this.forAllChangedKeys((key, oldValue, newValue) => { this.onStorageEvent(new StorageEvent('storage', { key, oldValue, newValue }), /* poll */ true); }); }, _POLLING_INTERVAL_MS); } stopPolling() { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } } attachListener() { window.addEventListener('storage', this.boundEventHandler); } detachListener() { window.removeEventListener('storage', this.boundEventHandler); } _addListener(key, listener) { if (Object.keys(this.listeners).length === 0) { // Whether browser can detect storage event when it had already been pushed to the background. // This may happen in some mobile browsers. A localStorage change in the foreground window // will not be detected in the background window via the storage event. // This was detected in iOS 7.x mobile browsers if (this.fallbackToPolling) { this.startPolling(); } else { this.attachListener(); } } if (!this.listeners[key]) { this.listeners[key] = new Set(); // Populate the cache to avoid spuriously triggering on first poll. this.localCache[key] = this.storage.getItem(key); } this.listeners[key].add(listener); } _removeListener(key, listener) { if (this.listeners[key]) { this.listeners[key].delete(listener); if (this.listeners[key].size === 0) { delete this.listeners[key]; } } if (Object.keys(this.listeners).length === 0) { this.detachListener(); this.stopPolling(); } } // Update local cache on base operations: async _set(key, value) { await super._set(key, value); this.localCache[key] = JSON.stringify(value); } async _get(key) { const value = await super._get(key); this.localCache[key] = JSON.stringify(value); return value; } async _remove(key) { await super._remove(key); delete this.localCache[key]; } } BrowserLocalPersistence.type = 'LOCAL'; /** * An implementation of {@link Persistence} of type `LOCAL` using `localStorage` * for the underlying storage. * * @public */ const browserLocalPersistence = BrowserLocalPersistence; /** * @license * Copyright 2020 Google LLC * * 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. */ class BrowserSessionPersistence extends BrowserPersistenceClass { constructor() { super(() => window.sessionStorage, "SESSION" /* PersistenceType.SESSION */); } _addListener(_key, _listener) { // Listeners are not supported for session storage since it cannot be shared across windows return; } _removeListener(_key, _listener) { // Listeners are not supported for session storage since it cannot be shared across windows return; } } BrowserSessionPersistence.type = 'SESSION'; /** * An implementation of {@link Persistence} of `SESSION` using `sessionStorage` * for the underlying storage. * * @public */ const browserSessionPersistence = BrowserSessionPersistence; /** * @license * Copyright 2020 Google LLC * * 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. */ const _SOLVE_TIME_MS = 500; const _EXPIRATION_TIME_MS = 60000; const _WIDGET_ID_START = 1000000000000; class MockReCaptcha { constructor(auth) { this.auth = auth; this.counter = _WIDGET_ID_START; this._widgets = new Map(); } render(container, parameters) { const id = this.counter; this._widgets.set(id, new MockWidget(container, this.auth.name, parameters || {})); this.counter++; return id; } reset(optWidgetId) { var _a; const id = optWidgetId || _WIDGET_ID_START; void ((_a = this._widgets.get(id)) === null || _a === void 0 ? void 0 : _a.delete()); this._widgets.delete(id); } getResponse(optWidgetId) { var _a; const id = optWidgetId || _WIDGET_ID_START; return ((_a = this._widgets.get(id)) === null || _a === void 0 ? void 0 : _a.getResponse()) || ''; } async execute(optWidgetId) { var _a; const id = optWidgetId || _WIDGET_ID_START; void ((_a = this._widgets.get(id)) === null || _a === void 0 ? void 0 : _a.execute()); return ''; } } class MockWidget { constructor(containerOrId, appName, params) { this.params = params; this.timerId = null; this.deleted = false; this.responseToken = null; this.clickHandler = () => { this.execute(); }; const container = typeof containerOrId === 'string' ? document.getElementById(containerOrId) : containerOrId; _assert(container, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */, { appName }); this.container = container; this.isVisible = this.params.size !== 'invisible'; if (this.isVisible) { this.execute(); } else { this.container.addEventListener('click', this.clickHandler); } } getResponse() { this.checkIfDeleted(); return this.responseToken; } delete() { this.checkIfDeleted(); this.deleted = true; if (this.timerId) { clearTimeout(this.timerId); this.timerId = null; } this.container.removeEventListener('click', this.clickHandler); } execute() { this.checkIfDeleted(); if (this.timerId) { return; } this.timerId = window.setTimeout(() => { this.responseToken = generateRandomAlphaNumericString(50); const { callback, 'expired-callback': expiredCallback } = this.params; if (callback) { try { callback(this.responseToken); } catch (e) { } } this.timerId = window.setTimeout(() => { this.timerId = null; this.responseToken = null; if (expiredCallback) { try { expiredCallback(); } catch (e) { } } if (this.isVisible) { this.execute(); } }, _EXPIRATION_TIME_MS); }, _SOLVE_TIME_MS); } checkIfDeleted() { if (this.deleted) { throw new Error('reCAPTCHA mock was already deleted!'); } } } function generateRandomAlphaNumericString(len) { const chars = []; const allowedChars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; for (let i = 0; i < len; i++) { chars.push(allowedChars.charAt(Math.floor(Math.random() * allowedChars.length))); } return chars.join(''); } /** * @license * Copyright 2020 Google LLC * * 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. */ // ReCaptcha will load using the same callback, so the callback function needs // to be kept around const _JSLOAD_CALLBACK = _generateCallbackName('rcb'); const NETWORK_TIMEOUT_DELAY = new Delay(30000, 60000); /** * Loader for the GReCaptcha library. There should only ever be one of this. */ class ReCaptchaLoaderImpl { constructor() { var _a; this.hostLanguage = ''; this.counter = 0; /** * Check for `render()` method. `window.grecaptcha` will exist if the Enterprise * version of the ReCAPTCHA script was loaded by someone else (e.g. App Check) but * `window.grecaptcha.render()` will not. Another load will add it. */ this.librarySeparatelyLoaded = !!((_a = _window().grecaptcha) === null || _a === void 0 ? void 0 : _a.render); } load(auth, hl = '') { _assert(isHostLanguageValid(hl), auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); if (this.shouldResolveImmediately(hl) && isV2(_window().grecaptcha)) { return Promise.resolve(_window().grecaptcha); } return new Promise((resolve, reject) => { const networkTimeout = _window().setTimeout(() => { reject(_createError(auth, "network-request-failed" /* AuthErrorCode.NETWORK_REQUEST_FAILED */)); }, NETWORK_TIMEOUT_DELAY.get()); _window()[_JSLOAD_CALLBACK] = () => { _window().clearTimeout(networkTimeout); delete _window()[_JSLOAD_CALLBACK]; const recaptcha = _window().grecaptcha; if (!recaptcha || !isV2(recaptcha)) { reject(_createError(auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */)); return; } // Wrap the greptcha render function so that we know if the developer has // called it separately const render = recaptcha.render; recaptcha.render = (container, params) => { const widgetId = render(container, params); this.counter++; return widgetId; }; this.hostLanguage = hl; resolve(recaptcha); }; const url = `${_recaptchaV2ScriptUrl()}?${querystring({ onload: _JSLOAD_CALLBACK, render: 'explicit', hl })}`; _loadJS(url).catch(() => { clearTimeout(networkTimeout); reject(_createError(auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */)); }); }); } clearedOneInstance() { this.counter--; } shouldResolveImmediately(hl) { var _a; // We can resolve immediately if: // • grecaptcha is already defined AND ( // 1. the requested language codes are the same OR // 2. there exists already a ReCaptcha on the page // 3. the library was already loaded by the app // In cases (2) and (3), we _can't_ reload as it would break the recaptchas // that are already in the page return (!!((_a = _window().grecaptcha) === null || _a === void 0 ? void 0 : _a.render) && (hl === this.hostLanguage || this.counter > 0 || this.librarySeparatelyLoaded)); } } function isHostLanguageValid(hl) { return hl.length <= 6 && /^\s*[a-zA-Z0-9\-]*\s*$/.test(hl); } class MockReCaptchaLoaderImpl { async load(auth) { return new MockReCaptcha(auth); } clearedOneInstance() { } } /** * @license * Copyright 2020 Google LLC * * 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. */ const RECAPTCHA_VERIFIER_TYPE = 'recaptcha'; const DEFAULT_PARAMS = { theme: 'light', type: 'image' }; /** * An {@link https://www.google.com/recaptcha/ | reCAPTCHA}-based application verifier. * * @remarks * `RecaptchaVerifier` does not work in a Node.js environment. * * @public */ class RecaptchaVerifier { /** * @param authExtern - The corresponding Firebase {@link Auth} instance. * * @param containerOrId - The reCAPTCHA container parameter. * * @remarks * This has different meaning depending on whether the reCAPTCHA is hidden or visible. For a * visible reCAPTCHA the container must be empty. If a string is used, it has to correspond to * an element ID. The corresponding element must also must be in the DOM at the time of * initialization. * * @param parameters - The optional reCAPTCHA parameters. * * @remarks * Check the reCAPTCHA docs for a comprehensive list. All parameters are accepted except for * the sitekey. Firebase Auth backend provisions a reCAPTCHA for each project and will * configure this upon rendering. For an invisible reCAPTCHA, a size key must have the value * 'invisible'. */ constructor(authExtern, containerOrId, parameters = Object.assign({}, DEFAULT_PARAMS)) { this.parameters = parameters; /** * The application verifier type. * * @remarks * For a reCAPTCHA verifier, this is 'recaptcha'. */ this.type = RECAPTCHA_VERIFIER_TYPE; this.destroyed = false; this.widgetId = null; this.tokenChangeListeners = new Set(); this.renderPromise = null; this.recaptcha = null; this.auth = _castAuth(authExtern); this.isInvisible = this.parameters.size === 'invisible'; _assert(typeof document !== 'undefined', this.auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); const container = typeof containerOrId === 'string' ? document.getElementById(containerOrId) : containerOrId; _assert(container, this.auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); this.container = container; this.parameters.callback = this.makeTokenCallback(this.parameters.callback); this._recaptchaLoader = this.auth.settings.appVerificationDisabledForTesting ? new MockReCaptchaLoaderImpl() : new ReCaptchaLoaderImpl(); this.validateStartingState(); // TODO: Figure out if sdk version is needed } /** * Waits for the user to solve the reCAPTCHA and resolves with the reCAPTCHA token. * * @returns A Promise for the reCAPTCHA token. */ async verify() { this.assertNotDestroyed(); const id = await this.render(); const recaptcha = this.getAssertedRecaptcha(); const response = recaptcha.getResponse(id); if (response) { return response; } return new Promise(resolve => { const tokenChange = (token) => { if (!token) { return; // Ignore token expirations. } this.tokenChangeListeners.delete(tokenChange); resolve(token); }; this.tokenChangeListeners.add(tokenChange); if (this.isInvisible) { recaptcha.execute(id); } }); } /** * Renders the reCAPTCHA widget on the page. * * @returns A Promise that resolves with the reCAPTCHA widget ID. */ render() { try { this.assertNotDestroyed(); } catch (e) { // This method returns a promise. Since it's not async (we want to return the // _same_ promise if rendering is still occurring), the API surface should // reject with the error rather than just throw return Promise.reject(e); } if (this.renderPromise) { return this.renderPromise; } this.renderPromise = this.makeRenderPromise().catch(e => { this.renderPromise = null; throw e; }); return this.renderPromise; } /** @internal */ _reset() { this.assertNotDestroyed(); if (this.widgetId !== null) { this.getAssertedRecaptcha().reset(this.widgetId); } } /** * Clears the reCAPTCHA widget from the page and destroys the instance. */ clear() { this.assertNotDestroyed(); this.destroyed = true; this._recaptchaLoader.clearedOneInstance(); if (!this.isInvisible) { this.container.childNodes.forEach(node => { this.container.removeChild(node); }); } } validateStartingState() { _assert(!this.parameters.sitekey, this.auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); _assert(this.isInvisible || !this.container.hasChildNodes(), this.auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); _assert(typeof document !== 'undefined', this.auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); } makeTokenCallback(existing) { return token => { this.tokenChangeListeners.forEach(listener => listener(token)); if (typeof existing === 'function') { existing(token); } else if (typeof existing === 'string') { const globalFunc = _window()[existing]; if (typeof globalFunc === 'function') { globalFunc(token); } } }; } assertNotDestroyed() { _assert(!this.destroyed, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); } async makeRenderPromise() { await this.init(); if (!this.widgetId) { let container = this.container; if (!this.isInvisible) { const guaranteedEmpty = document.createElement('div'); container.appendChild(guaranteedEmpty); container = guaranteedEmpty; } this.widgetId = this.getAssertedRecaptcha().render(container, this.parameters); } return this.widgetId; } async init() { _assert(_isHttpOrHttps() && !_isWorker(), this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); await domReady(); this.recaptcha = await this._recaptchaLoader.load(this.auth, this.auth.languageCode || undefined); const siteKey = await getRecaptchaParams(this.auth); _assert(siteKey, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); this.parameters.sitekey = siteKey; } getAssertedRecaptcha() { _assert(this.recaptcha, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return this.recaptcha; } } function domReady() { let resolver = null; return new Promise(resolve => { if (document.readyState === 'complete') { resolve(); return; } // Document not ready, wait for load before resolving. // Save resolver, so we can remove listener in case it was externally // cancelled. resolver = () => resolve(); window.addEventListener('load', resolver); }).catch(e => { if (resolver) { window.removeEventListener('load', resolver); } throw e; }); } /** * @license * Copyright 2020 Google LLC * * 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. */ class ConfirmationResultImpl { constructor(verificationId, onConfirmation) { this.verificationId = verificationId; this.onConfirmation = onConfirmation; } confirm(verificationCode) { const authCredential = PhoneAuthCredential._fromVerification(this.verificationId, verificationCode); return this.onConfirmation(authCredential); } } /** * Asynchronously signs in using a phone number. * * @remarks * This method sends a code via SMS to the given * phone number, and returns a {@link ConfirmationResult}. After the user * provides the code sent to their phone, call {@link ConfirmationResult.confirm} * with the code to sign the user in. * * For abuse prevention, this method also requires a {@link ApplicationVerifier}. * This SDK includes a reCAPTCHA-based implementation, {@link RecaptchaVerifier}. * This function can work on other platforms that do not support the * {@link RecaptchaVerifier} (like React Native), but you need to use a * third-party {@link ApplicationVerifier} implementation. * * This method does not work in a Node.js environment. * * @example * ```javascript * // 'recaptcha-container' is the ID of an element in the DOM. * const applicationVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container'); * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); * // Obtain a verificationCode from the user. * const credential = await confirmationResult.confirm(verificationCode); * ``` * * @param auth - The {@link Auth} instance. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ async function signInWithPhoneNumber(auth, phoneNumber, appVerifier) { const authInternal = _castAuth(auth); const verificationId = await _verifyPhoneNumber(authInternal, phoneNumber, getModularInstance(appVerifier)); return new ConfirmationResultImpl(verificationId, cred => signInWithCredential(authInternal, cred)); } /** * Links the user account with the given phone number. * * @remarks * This method does not work in a Node.js environment. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ async function linkWithPhoneNumber(user, phoneNumber, appVerifier) { const userInternal = getModularInstance(user); await _assertLinkedStatus(false, userInternal, "phone" /* ProviderId.PHONE */); const verificationId = await _verifyPhoneNumber(userInternal.auth, phoneNumber, getModularInstance(appVerifier)); return new ConfirmationResultImpl(verificationId, cred => linkWithCredential(userInternal, cred)); } /** * Re-authenticates a user using a fresh phone credential. * * @remarks * Use before operations such as {@link updatePassword} that require tokens from recent sign-in attempts. * * This method does not work in a Node.js environment. * * @param user - The user. * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). * @param appVerifier - The {@link ApplicationVerifier}. * * @public */ async function reauthenticateWithPhoneNumber(user, phoneNumber, appVerifier) { const userInternal = getModularInstance(user); const verificationId = await _verifyPhoneNumber(userInternal.auth, phoneNumber, getModularInstance(appVerifier)); return new ConfirmationResultImpl(verificationId, cred => reauthenticateWithCredential(userInternal, cred)); } /** * Returns a verification ID to be used in conjunction with the SMS code that is sent. * */ async function _verifyPhoneNumber(auth, options, verifier) { var _a; const recaptchaToken = await verifier.verify(); try { _assert(typeof recaptchaToken === 'string', auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); _assert(verifier.type === RECAPTCHA_VERIFIER_TYPE, auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); let phoneInfoOptions; if (typeof options === 'string') { phoneInfoOptions = { phoneNumber: options }; } else { phoneInfoOptions = options; } if ('session' in phoneInfoOptions) { const session = phoneInfoOptions.session; if ('phoneNumber' in phoneInfoOptions) { _assert(session.type === "enroll" /* MultiFactorSessionType.ENROLL */, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); const response = await startEnrollPhoneMfa(auth, { idToken: session.credential, phoneEnrollmentInfo: { phoneNumber: phoneInfoOptions.phoneNumber, recaptchaToken } }); return response.phoneSessionInfo.sessionInfo; } else { _assert(session.type === "signin" /* MultiFactorSessionType.SIGN_IN */, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); const mfaEnrollmentId = ((_a = phoneInfoOptions.multiFactorHint) === null || _a === void 0 ? void 0 : _a.uid) || phoneInfoOptions.multiFactorUid; _assert(mfaEnrollmentId, auth, "missing-multi-factor-info" /* AuthErrorCode.MISSING_MFA_INFO */); const response = await startSignInPhoneMfa(auth, { mfaPendingCredential: session.credential, mfaEnrollmentId, phoneSignInInfo: { recaptchaToken } }); return response.phoneResponseInfo.sessionInfo; } } else { const { sessionInfo } = await sendPhoneVerificationCode(auth, { phoneNumber: phoneInfoOptions.phoneNumber, recaptchaToken }); return sessionInfo; } } finally { verifier._reset(); } } /** * Updates the user's phone number. * * @remarks * This method does not work in a Node.js environment. * * @example * ``` * // 'recaptcha-container' is the ID of an element in the DOM. * const applicationVerifier = new RecaptchaVerifier('recaptcha-container'); * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber('+16505550101', applicationVerifier); * // Obtain the verificationCode from the user. * const phoneCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * await updatePhoneNumber(user, phoneCredential); * ``` * * @param user - The user. * @param credential - A credential authenticating the new phone number. * * @public */ async function updatePhoneNumber(user, credential) { await _link$1(getModularInstance(user), credential); } /** * @license * Copyright 2020 Google LLC * * 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. */ /** * Provider for generating an {@link PhoneAuthCredential}. * * @remarks * `PhoneAuthProvider` does not work in a Node.js environment. * * @example * ```javascript * // 'recaptcha-container' is the ID of an element in the DOM. * const applicationVerifier = new RecaptchaVerifier('recaptcha-container'); * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber('+16505550101', applicationVerifier); * // Obtain the verificationCode from the user. * const phoneCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * const userCredential = await signInWithCredential(auth, phoneCredential); * ``` * * @public */ class PhoneAuthProvider { /** * @param auth - The Firebase {@link Auth} instance in which sign-ins should occur. * */ constructor(auth) { /** Always set to {@link ProviderId}.PHONE. */ this.providerId = PhoneAuthProvider.PROVIDER_ID; this.auth = _castAuth(auth); } /** * * Starts a phone number authentication flow by sending a verification code to the given phone * number. * * @example * ```javascript * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber(phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const authCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * const userCredential = await signInWithCredential(auth, authCredential); * ``` * * @example * An alternative flow is provided using the `signInWithPhoneNumber` method. * ```javascript * const confirmationResult = signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const userCredential = confirmationResult.confirm(verificationCode); * ``` * * @param phoneInfoOptions - The user's {@link PhoneInfoOptions}. The phone number should be in * E.164 format (e.g. +16505550101). * @param applicationVerifier - For abuse prevention, this method also requires a * {@link ApplicationVerifier}. This SDK includes a reCAPTCHA-based implementation, * {@link RecaptchaVerifier}. * * @returns A Promise for a verification ID that can be passed to * {@link PhoneAuthProvider.credential} to identify this flow.. */ verifyPhoneNumber(phoneOptions, applicationVerifier) { return _verifyPhoneNumber(this.auth, phoneOptions, getModularInstance(applicationVerifier)); } /** * Creates a phone auth credential, given the verification ID from * {@link PhoneAuthProvider.verifyPhoneNumber} and the code that was sent to the user's * mobile device. * * @example * ```javascript * const provider = new PhoneAuthProvider(auth); * const verificationId = provider.verifyPhoneNumber(phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const authCredential = PhoneAuthProvider.credential(verificationId, verificationCode); * const userCredential = signInWithCredential(auth, authCredential); * ``` * * @example * An alternative flow is provided using the `signInWithPhoneNumber` method. * ```javascript * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); * // Obtain verificationCode from the user. * const userCredential = await confirmationResult.confirm(verificationCode); * ``` * * @param verificationId - The verification ID returned from {@link PhoneAuthProvider.verifyPhoneNumber}. * @param verificationCode - The verification code sent to the user's mobile device. * * @returns The auth provider credential. */ static credential(verificationId, verificationCode) { return PhoneAuthCredential._fromVerification(verificationId, verificationCode); } /** * Generates an {@link AuthCredential} from a {@link UserCredential}. * @param userCredential - The user credential. */ static credentialFromResult(userCredential) { const credential = userCredential; return PhoneAuthProvider.credentialFromTaggedObject(credential); } /** * Returns an {@link AuthCredential} when passed an error. * * @remarks * * This method works for errors like * `auth/account-exists-with-different-credentials`. This is useful for * recovering when attempting to set a user's phone number but the number * in question is already tied to another account. For example, the following * code tries to update the current user's phone number, and if that * fails, links the user with the account associated with that number: * * ```js * const provider = new PhoneAuthProvider(auth); * const verificationId = await provider.verifyPhoneNumber(number, verifier); * try { * const code = ''; // Prompt the user for the verification code * await updatePhoneNumber( * auth.currentUser, * PhoneAuthProvider.credential(verificationId, code)); * } catch (e) { * if ((e as FirebaseError)?.code === 'auth/account-exists-with-different-credential') { * const cred = PhoneAuthProvider.credentialFromError(e); * await linkWithCredential(auth.currentUser, cred); * } * } * * // At this point, auth.currentUser.phoneNumber === number. * ``` * * @param error - The error to generate a credential from. */ static credentialFromError(error) { return PhoneAuthProvider.credentialFromTaggedObject((error.customData || {})); } static credentialFromTaggedObject({ _tokenResponse: tokenResponse }) { if (!tokenResponse) { return null; } const { phoneNumber, temporaryProof } = tokenResponse; if (phoneNumber && temporaryProof) { return PhoneAuthCredential._fromTokenResponse(phoneNumber, temporaryProof); } return null; } } /** Always set to {@link ProviderId}.PHONE. */ PhoneAuthProvider.PROVIDER_ID = "phone" /* ProviderId.PHONE */; /** Always set to {@link SignInMethod}.PHONE. */ PhoneAuthProvider.PHONE_SIGN_IN_METHOD = "phone" /* SignInMethod.PHONE */; /** * @license * Copyright 2021 Google LLC * * 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. */ /** * Chooses a popup/redirect resolver to use. This prefers the override (which * is directly passed in), and falls back to the property set on the auth * object. If neither are available, this function errors w/ an argument error. */ function _withDefaultResolver(auth, resolverOverride) { if (resolverOverride) { return _getInstance(resolverOverride); } _assert(auth._popupRedirectResolver, auth, "argument-error" /* AuthErrorCode.ARGUMENT_ERROR */); return auth._popupRedirectResolver; } /** * @license * Copyright 2019 Google LLC * * 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. */ class IdpCredential extends AuthCredential { constructor(params) { super("custom" /* ProviderId.CUSTOM */, "custom" /* ProviderId.CUSTOM */); this.params = params; } _getIdTokenResponse(auth) { return signInWithIdp(auth, this._buildIdpRequest()); } _linkToIdToken(auth, idToken) { return signInWithIdp(auth, this._buildIdpRequest(idToken)); } _getReauthenticationResolver(auth) { return signInWithIdp(auth, this._buildIdpRequest()); } _buildIdpRequest(idToken) { const request = { requestUri: this.params.requestUri, sessionId: this.params.sessionId, postBody: this.params.postBody, tenantId: this.params.tenantId, pendingToken: this.params.pendingToken, returnSecureToken: true, returnIdpCredential: true }; if (idToken) { request.idToken = idToken; } return request; } } function _signIn(params) { return _signInWithCredential(params.auth, new IdpCredential(params), params.bypassAuthState); } function _reauth(params) { const { auth, user } = params; _assert(user, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return _reauthenticate(user, new IdpCredential(params), params.bypassAuthState); } async function _link(params) { const { auth, user } = params; _assert(user, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return _link$1(user, new IdpCredential(params), params.bypassAuthState); } /** * @license * Copyright 2020 Google LLC * * 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. */ /** * Popup event manager. Handles the popup's entire lifecycle; listens to auth * events */ class AbstractPopupRedirectOperation { constructor(auth, filter, resolver, user, bypassAuthState = false) { this.auth = auth; this.resolver = resolver; this.user = user; this.bypassAuthState = bypassAuthState; this.pendingPromise = null; this.eventManager = null; this.filter = Array.isArray(filter) ? filter : [filter]; } execute() { return new Promise(async (resolve, reject) => { this.pendingPromise = { resolve, reject }; try { this.eventManager = await this.resolver._initialize(this.auth); await this.onExecution(); this.eventManager.registerConsumer(this); } catch (e) { this.reject(e); } }); } async onAuthEvent(event) { const { urlResponse, sessionId, postBody, tenantId, error, type } = event; if (error) { this.reject(error); return; } const params = { auth: this.auth, requestUri: urlResponse, sessionId: sessionId, tenantId: tenantId || undefined, postBody: postBody || undefined, user: this.user, bypassAuthState: this.bypassAuthState }; try { this.resolve(await this.getIdpTask(type)(params)); } catch (e) { this.reject(e); } } onError(error) { this.reject(error); } getIdpTask(type) { switch (type) { case "signInViaPopup" /* AuthEventType.SIGN_IN_VIA_POPUP */: case "signInViaRedirect" /* AuthEventType.SIGN_IN_VIA_REDIRECT */: return _signIn; case "linkViaPopup" /* AuthEventType.LINK_VIA_POPUP */: case "linkViaRedirect" /* AuthEventType.LINK_VIA_REDIRECT */: return _link; case "reauthViaPopup" /* AuthEventType.REAUTH_VIA_POPUP */: case "reauthViaRedirect" /* AuthEventType.REAUTH_VIA_REDIRECT */: return _reauth; default: _fail(this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); } } resolve(cred) { debugAssert(this.pendingPromise, 'Pending promise was never set'); this.pendingPromise.resolve(cred); this.unregisterAndCleanUp(); } reject(error) { debugAssert(this.pendingPromise, 'Pending promise was never set'); this.pendingPromise.reject(error); this.unregisterAndCleanUp(); } unregisterAndCleanUp() { if (this.eventManager) { this.eventManager.unregisterConsumer(this); } this.pendingPromise = null; this.cleanUp(); } } /** * @license * Copyright 2020 Google LLC * * 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. */ const _POLL_WINDOW_CLOSE_TIMEOUT = new Delay(2000, 10000); /** * Authenticates a Firebase client using a popup-based OAuth authentication flow. * * @remarks * If succeeds, returns the signed in user along with the provider's credential. If sign in was * unsuccessful, returns an error object containing additional information about the error. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using a popup. * const provider = new FacebookAuthProvider(); * const result = await signInWithPopup(auth, provider); * * // The signed-in user info. * const user = result.user; * // This gives you a Facebook Access Token. * const credential = provider.credentialFromResult(auth, result); * const token = credential.accessToken; * ``` * * @param auth - The {@link Auth} instance. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ async function signInWithPopup(auth, provider, resolver) { const authInternal = _castAuth(auth); _assertInstanceOf(auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(authInternal, resolver); const action = new PopupOperation(authInternal, "signInViaPopup" /* AuthEventType.SIGN_IN_VIA_POPUP */, provider, resolverInternal); return action.executeNotNull(); } /** * Reauthenticates the current user with the specified {@link OAuthProvider} using a pop-up based * OAuth flow. * * @remarks * If the reauthentication is successful, the returned result will contain the user and the * provider's credential. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using a popup. * const provider = new FacebookAuthProvider(); * const result = await signInWithPopup(auth, provider); * // Reauthenticate using a popup. * await reauthenticateWithPopup(result.user, provider); * ``` * * @param user - The user. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ async function reauthenticateWithPopup(user, provider, resolver) { const userInternal = getModularInstance(user); _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); const action = new PopupOperation(userInternal.auth, "reauthViaPopup" /* AuthEventType.REAUTH_VIA_POPUP */, provider, resolverInternal, userInternal); return action.executeNotNull(); } /** * Links the authenticated provider to the user account using a pop-up based OAuth flow. * * @remarks * If the linking is successful, the returned result will contain the user and the provider's credential. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using some other provider. * const result = await signInWithEmailAndPassword(auth, email, password); * // Link using a popup. * const provider = new FacebookAuthProvider(); * await linkWithPopup(result.user, provider); * ``` * * @param user - The user. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ async function linkWithPopup(user, provider, resolver) { const userInternal = getModularInstance(user); _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); const action = new PopupOperation(userInternal.auth, "linkViaPopup" /* AuthEventType.LINK_VIA_POPUP */, provider, resolverInternal, userInternal); return action.executeNotNull(); } /** * Popup event manager. Handles the popup's entire lifecycle; listens to auth * events * */ class PopupOperation extends AbstractPopupRedirectOperation { constructor(auth, filter, provider, resolver, user) { super(auth, filter, resolver, user); this.provider = provider; this.authWindow = null; this.pollId = null; if (PopupOperation.currentPopupAction) { PopupOperation.currentPopupAction.cancel(); } PopupOperation.currentPopupAction = this; } async executeNotNull() { const result = await this.execute(); _assert(result, this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return result; } async onExecution() { debugAssert(this.filter.length === 1, 'Popup operations only handle one event'); const eventId = _generateEventId(); this.authWindow = await this.resolver._openPopup(this.auth, this.provider, this.filter[0], // There's always one, see constructor eventId); this.authWindow.associatedEvent = eventId; // Check for web storage support and origin validation _after_ the popup is // loaded. These operations are slow (~1 second or so) Rather than // waiting on them before opening the window, optimistically open the popup // and check for storage support at the same time. If storage support is // not available, this will cause the whole thing to reject properly. It // will also close the popup, but since the promise has already rejected, // the popup closed by user poll will reject into the void. this.resolver._originValidation(this.auth).catch(e => { this.reject(e); }); this.resolver._isIframeWebStorageSupported(this.auth, isSupported => { if (!isSupported) { this.reject(_createError(this.auth, "web-storage-unsupported" /* AuthErrorCode.WEB_STORAGE_UNSUPPORTED */)); } }); // Handle user closure. Notice this does *not* use await this.pollUserCancellation(); } get eventId() { var _a; return ((_a = this.authWindow) === null || _a === void 0 ? void 0 : _a.associatedEvent) || null; } cancel() { this.reject(_createError(this.auth, "cancelled-popup-request" /* AuthErrorCode.EXPIRED_POPUP_REQUEST */)); } cleanUp() { if (this.authWindow) { this.authWindow.close(); } if (this.pollId) { window.clearTimeout(this.pollId); } this.authWindow = null; this.pollId = null; PopupOperation.currentPopupAction = null; } pollUserCancellation() { const poll = () => { var _a, _b; if ((_b = (_a = this.authWindow) === null || _a === void 0 ? void 0 : _a.window) === null || _b === void 0 ? void 0 : _b.closed) { // Make sure that there is sufficient time for whatever action to // complete. The window could have closed but the sign in network // call could still be in flight. This is specifically true for // Firefox or if the opener is in an iframe, in which case the oauth // helper closes the popup. this.pollId = window.setTimeout(() => { this.pollId = null; this.reject(_createError(this.auth, "popup-closed-by-user" /* AuthErrorCode.POPUP_CLOSED_BY_USER */)); }, 8000 /* _Timeout.AUTH_EVENT */); return; } this.pollId = window.setTimeout(poll, _POLL_WINDOW_CLOSE_TIMEOUT.get()); }; poll(); } } // Only one popup is ever shown at once. The lifecycle of the current popup // can be managed / cancelled by the constructor. PopupOperation.currentPopupAction = null; /** * @license * Copyright 2020 Google LLC * * 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. */ const PENDING_REDIRECT_KEY = 'pendingRedirect'; // We only get one redirect outcome for any one auth, so just store it // in here. const redirectOutcomeMap = new Map(); class RedirectAction extends AbstractPopupRedirectOperation { constructor(auth, resolver, bypassAuthState = false) { super(auth, [ "signInViaRedirect" /* AuthEventType.SIGN_IN_VIA_REDIRECT */, "linkViaRedirect" /* AuthEventType.LINK_VIA_REDIRECT */, "reauthViaRedirect" /* AuthEventType.REAUTH_VIA_REDIRECT */, "unknown" /* AuthEventType.UNKNOWN */ ], resolver, undefined, bypassAuthState); this.eventId = null; } /** * Override the execute function; if we already have a redirect result, then * just return it. */ async execute() { let readyOutcome = redirectOutcomeMap.get(this.auth._key()); if (!readyOutcome) { try { const hasPendingRedirect = await _getAndClearPendingRedirectStatus(this.resolver, this.auth); const result = hasPendingRedirect ? await super.execute() : null; readyOutcome = () => Promise.resolve(result); } catch (e) { readyOutcome = () => Promise.reject(e); } redirectOutcomeMap.set(this.auth._key(), readyOutcome); } // If we're not bypassing auth state, the ready outcome should be set to // null. if (!this.bypassAuthState) { redirectOutcomeMap.set(this.auth._key(), () => Promise.resolve(null)); } return readyOutcome(); } async onAuthEvent(event) { if (event.type === "signInViaRedirect" /* AuthEventType.SIGN_IN_VIA_REDIRECT */) { return super.onAuthEvent(event); } else if (event.type === "unknown" /* AuthEventType.UNKNOWN */) { // This is a sentinel value indicating there's no pending redirect this.resolve(null); return; } if (event.eventId) { const user = await this.auth._redirectUserForId(event.eventId); if (user) { this.user = user; return super.onAuthEvent(event); } else { this.resolve(null); } } } async onExecution() { } cleanUp() { } } async function _getAndClearPendingRedirectStatus(resolver, auth) { const key = pendingRedirectKey(auth); const persistence = resolverPersistence(resolver); if (!(await persistence._isAvailable())) { return false; } const hasPendingRedirect = (await persistence._get(key)) === 'true'; await persistence._remove(key); return hasPendingRedirect; } async function _setPendingRedirectStatus(resolver, auth) { return resolverPersistence(resolver)._set(pendingRedirectKey(auth), 'true'); } function _clearRedirectOutcomes() { redirectOutcomeMap.clear(); } function _overrideRedirectResult(auth, result) { redirectOutcomeMap.set(auth._key(), result); } function resolverPersistence(resolver) { return _getInstance(resolver._redirectPersistence); } function pendingRedirectKey(auth) { return _persistenceKeyName(PENDING_REDIRECT_KEY, auth.config.apiKey, auth.name); } /** * @license * Copyright 2020 Google LLC * * 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. */ /** * Authenticates a Firebase client using a full-page redirect flow. * * @remarks * To handle the results and errors for this operation, refer to {@link getRedirectResult}. * Follow the {@link https://firebase.google.com/docs/auth/web/redirect-best-practices * | best practices} when using {@link signInWithRedirect}. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using a redirect. * const provider = new FacebookAuthProvider(); * // You can add additional scopes to the provider: * provider.addScope('user_birthday'); * // Start a sign in process for an unauthenticated user. * await signInWithRedirect(auth, provider); * // This will trigger a full page redirect away from your app * * // After returning from the redirect when your app initializes you can obtain the result * const result = await getRedirectResult(auth); * if (result) { * // This is the signed-in user * const user = result.user; * // This gives you a Facebook Access Token. * const credential = provider.credentialFromResult(auth, result); * const token = credential.accessToken; * } * // As this API can be used for sign-in, linking and reauthentication, * // check the operationType to determine what triggered this redirect * // operation. * const operationType = result.operationType; * ``` * * @param auth - The {@link Auth} instance. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ function signInWithRedirect(auth, provider, resolver) { return _signInWithRedirect(auth, provider, resolver); } async function _signInWithRedirect(auth, provider, resolver) { const authInternal = _castAuth(auth); _assertInstanceOf(auth, provider, FederatedAuthProvider); // Wait for auth initialization to complete, this will process pending redirects and clear the // PENDING_REDIRECT_KEY in persistence. This should be completed before starting a new // redirect and creating a PENDING_REDIRECT_KEY entry. await authInternal._initializationPromise; const resolverInternal = _withDefaultResolver(authInternal, resolver); await _setPendingRedirectStatus(resolverInternal, authInternal); return resolverInternal._openRedirect(authInternal, provider, "signInViaRedirect" /* AuthEventType.SIGN_IN_VIA_REDIRECT */); } /** * Reauthenticates the current user with the specified {@link OAuthProvider} using a full-page redirect flow. * @remarks * To handle the results and errors for this operation, refer to {@link getRedirectResult}. * Follow the {@link https://firebase.google.com/docs/auth/web/redirect-best-practices * | best practices} when using {@link reauthenticateWithRedirect}. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using a redirect. * const provider = new FacebookAuthProvider(); * const result = await signInWithRedirect(auth, provider); * // This will trigger a full page redirect away from your app * * // After returning from the redirect when your app initializes you can obtain the result * const result = await getRedirectResult(auth); * // Reauthenticate using a redirect. * await reauthenticateWithRedirect(result.user, provider); * // This will again trigger a full page redirect away from your app * * // After returning from the redirect when your app initializes you can obtain the result * const result = await getRedirectResult(auth); * ``` * * @param user - The user. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ function reauthenticateWithRedirect(user, provider, resolver) { return _reauthenticateWithRedirect(user, provider, resolver); } async function _reauthenticateWithRedirect(user, provider, resolver) { const userInternal = getModularInstance(user); _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); // Wait for auth initialization to complete, this will process pending redirects and clear the // PENDING_REDIRECT_KEY in persistence. This should be completed before starting a new // redirect and creating a PENDING_REDIRECT_KEY entry. await userInternal.auth._initializationPromise; // Allow the resolver to error before persisting the redirect user const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); await _setPendingRedirectStatus(resolverInternal, userInternal.auth); const eventId = await prepareUserForRedirect(userInternal); return resolverInternal._openRedirect(userInternal.auth, provider, "reauthViaRedirect" /* AuthEventType.REAUTH_VIA_REDIRECT */, eventId); } /** * Links the {@link OAuthProvider} to the user account using a full-page redirect flow. * @remarks * To handle the results and errors for this operation, refer to {@link getRedirectResult}. * Follow the {@link https://firebase.google.com/docs/auth/web/redirect-best-practices * | best practices} when using {@link linkWithRedirect}. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using some other provider. * const result = await signInWithEmailAndPassword(auth, email, password); * // Link using a redirect. * const provider = new FacebookAuthProvider(); * await linkWithRedirect(result.user, provider); * // This will trigger a full page redirect away from your app * * // After returning from the redirect when your app initializes you can obtain the result * const result = await getRedirectResult(auth); * ``` * * @param user - The user. * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ function linkWithRedirect(user, provider, resolver) { return _linkWithRedirect(user, provider, resolver); } async function _linkWithRedirect(user, provider, resolver) { const userInternal = getModularInstance(user); _assertInstanceOf(userInternal.auth, provider, FederatedAuthProvider); // Wait for auth initialization to complete, this will process pending redirects and clear the // PENDING_REDIRECT_KEY in persistence. This should be completed before starting a new // redirect and creating a PENDING_REDIRECT_KEY entry. await userInternal.auth._initializationPromise; // Allow the resolver to error before persisting the redirect user const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); await _assertLinkedStatus(false, userInternal, provider.providerId); await _setPendingRedirectStatus(resolverInternal, userInternal.auth); const eventId = await prepareUserForRedirect(userInternal); return resolverInternal._openRedirect(userInternal.auth, provider, "linkViaRedirect" /* AuthEventType.LINK_VIA_REDIRECT */, eventId); } /** * Returns a {@link UserCredential} from the redirect-based sign-in flow. * * @remarks * If sign-in succeeded, returns the signed in user. If sign-in was unsuccessful, fails with an * error. If no redirect operation was called, returns `null`. * * This method does not work in a Node.js environment. * * @example * ```javascript * // Sign in using a redirect. * const provider = new FacebookAuthProvider(); * // You can add additional scopes to the provider: * provider.addScope('user_birthday'); * // Start a sign in process for an unauthenticated user. * await signInWithRedirect(auth, provider); * // This will trigger a full page redirect away from your app * * // After returning from the redirect when your app initializes you can obtain the result * const result = await getRedirectResult(auth); * if (result) { * // This is the signed-in user * const user = result.user; * // This gives you a Facebook Access Token. * const credential = provider.credentialFromResult(auth, result); * const token = credential.accessToken; * } * // As this API can be used for sign-in, linking and reauthentication, * // check the operationType to determine what triggered this redirect * // operation. * const operationType = result.operationType; * ``` * * @param auth - The {@link Auth} instance. * @param resolver - An instance of {@link PopupRedirectResolver}, optional * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. * * @public */ async function getRedirectResult(auth, resolver) { await _castAuth(auth)._initializationPromise; return _getRedirectResult(auth, resolver, false); } async function _getRedirectResult(auth, resolverExtern, bypassAuthState = false) { const authInternal = _castAuth(auth); const resolver = _withDefaultResolver(authInternal, resolverExtern); const action = new RedirectAction(authInternal, resolver, bypassAuthState); const result = await action.execute(); if (result && !bypassAuthState) { delete result.user._redirectEventId; await authInternal._persistUserIfCurrent(result.user); await authInternal._setRedirectUser(null, resolverExtern); } return result; } async function prepareUserForRedirect(user) { const eventId = _generateEventId(`${user.uid}:::`); user._redirectEventId = eventId; await user.auth._setRedirectUser(user); await user.auth._persistUserIfCurrent(user); return eventId; } /** * @license * Copyright 2020 Google LLC * * 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. */ // The amount of time to store the UIDs of seen events; this is // set to 10 min by default const EVENT_DUPLICATION_CACHE_DURATION_MS = 10 * 60 * 1000; class AuthEventManager { constructor(auth) { this.auth = auth; this.cachedEventUids = new Set(); this.consumers = new Set(); this.queuedRedirectEvent = null; this.hasHandledPotentialRedirect = false; this.lastProcessedEventTime = Date.now(); } registerConsumer(authEventConsumer) { this.consumers.add(authEventConsumer); if (this.queuedRedirectEvent && this.isEventForConsumer(this.queuedRedirectEvent, authEventConsumer)) { this.sendToConsumer(this.queuedRedirectEvent, authEventConsumer); this.saveEventToCache(this.queuedRedirectEvent); this.queuedRedirectEvent = null; } } unregisterConsumer(authEventConsumer) { this.consumers.delete(authEventConsumer); } onEvent(event) { // Check if the event has already been handled if (this.hasEventBeenHandled(event)) { return false; } let handled = false; this.consumers.forEach(consumer => { if (this.isEventForConsumer(event, consumer)) { handled = true; this.sendToConsumer(event, consumer); this.saveEventToCache(event); } }); if (this.hasHandledPotentialRedirect || !isRedirectEvent(event)) { // If we've already seen a redirect before, or this is a popup event, // bail now return handled; } this.hasHandledPotentialRedirect = true; // If the redirect wasn't handled, hang on to it if (!handled) { this.queuedRedirectEvent = event; handled = true; } return handled; } sendToConsumer(event, consumer) { var _a; if (event.error && !isNullRedirectEvent(event)) { const code = ((_a = event.error.code) === null || _a === void 0 ? void 0 : _a.split('auth/')[1]) || "internal-error" /* AuthErrorCode.INTERNAL_ERROR */; consumer.onError(_createError(this.auth, code)); } else { consumer.onAuthEvent(event); } } isEventForConsumer(event, consumer) { const eventIdMatches = consumer.eventId === null || (!!event.eventId && event.eventId === consumer.eventId); return consumer.filter.includes(event.type) && eventIdMatches; } hasEventBeenHandled(event) { if (Date.now() - this.lastProcessedEventTime >= EVENT_DUPLICATION_CACHE_DURATION_MS) { this.cachedEventUids.clear(); } return this.cachedEventUids.has(eventUid(event)); } saveEventToCache(event) { this.cachedEventUids.add(eventUid(event)); this.lastProcessedEventTime = Date.now(); } } function eventUid(e) { return [e.type, e.eventId, e.sessionId, e.tenantId].filter(v => v).join('-'); } function isNullRedirectEvent({ type, error }) { return (type === "unknown" /* AuthEventType.UNKNOWN */ && (error === null || error === void 0 ? void 0 : error.code) === `auth/${"no-auth-event" /* AuthErrorCode.NO_AUTH_EVENT */}`); } function isRedirectEvent(event) { switch (event.type) { case "signInViaRedirect" /* AuthEventType.SIGN_IN_VIA_REDIRECT */: case "linkViaRedirect" /* AuthEventType.LINK_VIA_REDIRECT */: case "reauthViaRedirect" /* AuthEventType.REAUTH_VIA_REDIRECT */: return true; case "unknown" /* AuthEventType.UNKNOWN */: return isNullRedirectEvent(event); default: return false; } } /** * @license * Copyright 2020 Google LLC * * 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. */ async function _getProjectConfig(auth, request = {}) { return _performApiRequest(auth, "GET" /* HttpMethod.GET */, "/v1/projects" /* Endpoint.GET_PROJECT_CONFIG */, request); } /** * @license * Copyright 2020 Google LLC * * 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. */ const IP_ADDRESS_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; const HTTP_REGEX = /^https?/; async function _validateOrigin$1(auth) { // Skip origin validation if we are in an emulated environment if (auth.config.emulator) { return; } const { authorizedDomains } = await _getProjectConfig(auth); for (const domain of authorizedDomains) { try { if (matchDomain(domain)) { return; } } catch (_a) { // Do nothing if there's a URL error; just continue searching } } // In the old SDK, this error also provides helpful messages. _fail(auth, "unauthorized-domain" /* AuthErrorCode.INVALID_ORIGIN */); } function matchDomain(expected) { const currentUrl = _getCurrentUrl(); const { protocol, hostname } = new URL(currentUrl); if (expected.startsWith('chrome-extension://')) { const ceUrl = new URL(expected); if (ceUrl.hostname === '' && hostname === '') { // For some reason we're not parsing chrome URLs properly return (protocol === 'chrome-extension:' && expected.replace('chrome-extension://', '') === currentUrl.replace('chrome-extension://', '')); } return protocol === 'chrome-extension:' && ceUrl.hostname === hostname; } if (!HTTP_REGEX.test(protocol)) { return false; } if (IP_ADDRESS_REGEX.test(expected)) { // The domain has to be exactly equal to the pattern, as an IP domain will // only contain the IP, no extra character. return hostname === expected; } // Dots in pattern should be escaped. const escapedDomainPattern = expected.replace(/\./g, '\\.'); // Non ip address domains. // domain.com = *.domain.com OR domain.com const re = new RegExp('^(.+\\.' + escapedDomainPattern + '|' + escapedDomainPattern + ')$', 'i'); return re.test(hostname); } /** * @license * Copyright 2020 Google LLC. * * 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. */ const NETWORK_TIMEOUT = new Delay(30000, 60000); /** * Reset unlaoded GApi modules. If gapi.load fails due to a network error, * it will stop working after a retrial. This is a hack to fix this issue. */ function resetUnloadedGapiModules() { // Clear last failed gapi.load state to force next gapi.load to first // load the failed gapi.iframes module. // Get gapix.beacon context. const beacon = _window().___jsl; // Get current hint. if (beacon === null || beacon === void 0 ? void 0 : beacon.H) { // Get gapi hint. for (const hint of Object.keys(beacon.H)) { // Requested modules. beacon.H[hint].r = beacon.H[hint].r || []; // Loaded modules. beacon.H[hint].L = beacon.H[hint].L || []; // Set requested modules to a copy of the loaded modules. beacon.H[hint].r = [...beacon.H[hint].L]; // Clear pending callbacks. if (beacon.CP) { for (let i = 0; i < beacon.CP.length; i++) { // Remove all failed pending callbacks. beacon.CP[i] = null; } } } } } function loadGapi(auth) { return new Promise((resolve, reject) => { var _a, _b, _c; // Function to run when gapi.load is ready. function loadGapiIframe() { // The developer may have tried to previously run gapi.load and failed. // Run this to fix that. resetUnloadedGapiModules(); gapi.load('gapi.iframes', { callback: () => { resolve(gapi.iframes.getContext()); }, ontimeout: () => { // The above reset may be sufficient, but having this reset after // failure ensures that if the developer calls gapi.load after the // connection is re-established and before another attempt to embed // the iframe, it would work and would not be broken because of our // failed attempt. // Timeout when gapi.iframes.Iframe not loaded. resetUnloadedGapiModules(); reject(_createError(auth, "network-request-failed" /* AuthErrorCode.NETWORK_REQUEST_FAILED */)); }, timeout: NETWORK_TIMEOUT.get() }); } if ((_b = (_a = _window().gapi) === null || _a === void 0 ? void 0 : _a.iframes) === null || _b === void 0 ? void 0 : _b.Iframe) { // If gapi.iframes.Iframe available, resolve. resolve(gapi.iframes.getContext()); } else if (!!((_c = _window().gapi) === null || _c === void 0 ? void 0 : _c.load)) { // Gapi loader ready, load gapi.iframes. loadGapiIframe(); } else { // Create a new iframe callback when this is called so as not to overwrite // any previous defined callback. This happens if this method is called // multiple times in parallel and could result in the later callback // overwriting the previous one. This would end up with a iframe // timeout. const cbName = _generateCallbackName('iframefcb'); // GApi loader not available, dynamically load platform.js. _window()[cbName] = () => { // GApi loader should be ready. if (!!gapi.load) { loadGapiIframe(); } else { // Gapi loader failed, throw error. reject(_createError(auth, "network-request-failed" /* AuthErrorCode.NETWORK_REQUEST_FAILED */)); } }; // Load GApi loader. return _loadJS(`${_gapiScriptUrl()}?onload=${cbName}`) .catch(e => reject(e)); } }).catch(error => { // Reset cached promise to allow for retrial. cachedGApiLoader = null; throw error; }); } let cachedGApiLoader = null; function _loadGapi(auth) { cachedGApiLoader = cachedGApiLoader || loadGapi(auth); return cachedGApiLoader; } /** * @license * Copyright 2020 Google LLC. * * 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. */ const PING_TIMEOUT = new Delay(5000, 15000); const IFRAME_PATH = '__/auth/iframe'; const EMULATED_IFRAME_PATH = 'emulator/auth/iframe'; const IFRAME_ATTRIBUTES = { style: { position: 'absolute', top: '-100px', width: '1px', height: '1px' }, 'aria-hidden': 'true', tabindex: '-1' }; // Map from apiHost to endpoint ID for passing into iframe. In current SDK, apiHost can be set to // anything (not from a list of endpoints with IDs as in legacy), so this is the closest we can get. const EID_FROM_APIHOST = new Map([ ["identitytoolkit.googleapis.com" /* DefaultConfig.API_HOST */, 'p'], ['staging-identitytoolkit.sandbox.googleapis.com', 's'], ['test-identitytoolkit.sandbox.googleapis.com', 't'] // test ]); function getIframeUrl(auth) { const config = auth.config; _assert(config.authDomain, auth, "auth-domain-config-required" /* AuthErrorCode.MISSING_AUTH_DOMAIN */); const url = config.emulator ? _emulatorUrl(config, EMULATED_IFRAME_PATH) : `https://${auth.config.authDomain}/${IFRAME_PATH}`; const params = { apiKey: config.apiKey, appName: auth.name, v: SDK_VERSION }; const eid = EID_FROM_APIHOST.get(auth.config.apiHost); if (eid) { params.eid = eid; } const frameworks = auth._getFrameworks(); if (frameworks.length) { params.fw = frameworks.join(','); } return `${url}?${querystring(params).slice(1)}`; } async function _openIframe(auth) { const context = await _loadGapi(auth); const gapi = _window().gapi; _assert(gapi, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return context.open({ where: document.body, url: getIframeUrl(auth), messageHandlersFilter: gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER, attributes: IFRAME_ATTRIBUTES, dontclear: true }, (iframe) => new Promise(async (resolve, reject) => { await iframe.restyle({ // Prevent iframe from closing on mouse out. setHideOnLeave: false }); const networkError = _createError(auth, "network-request-failed" /* AuthErrorCode.NETWORK_REQUEST_FAILED */); // Confirm iframe is correctly loaded. // To fallback on failure, set a timeout. const networkErrorTimer = _window().setTimeout(() => { reject(networkError); }, PING_TIMEOUT.get()); // Clear timer and resolve pending iframe ready promise. function clearTimerAndResolve() { _window().clearTimeout(networkErrorTimer); resolve(iframe); } // This returns an IThenable. However the reject part does not call // when the iframe is not loaded. iframe.ping(clearTimerAndResolve).then(clearTimerAndResolve, () => { reject(networkError); }); })); } /** * @license * Copyright 2020 Google LLC. * * 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. */ const BASE_POPUP_OPTIONS = { location: 'yes', resizable: 'yes', statusbar: 'yes', toolbar: 'no' }; const DEFAULT_WIDTH = 500; const DEFAULT_HEIGHT = 600; const TARGET_BLANK = '_blank'; const FIREFOX_EMPTY_URL = 'http://localhost'; class AuthPopup { constructor(window) { this.window = window; this.associatedEvent = null; } close() { if (this.window) { try { this.window.close(); } catch (e) { } } } } function _open(auth, url, name, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT) { const top = Math.max((window.screen.availHeight - height) / 2, 0).toString(); const left = Math.max((window.screen.availWidth - width) / 2, 0).toString(); let target = ''; const options = Object.assign(Object.assign({}, BASE_POPUP_OPTIONS), { width: width.toString(), height: height.toString(), top, left }); // Chrome iOS 7 and 8 is returning an undefined popup win when target is // specified, even though the popup is not necessarily blocked. const ua = getUA().toLowerCase(); if (name) { target = _isChromeIOS(ua) ? TARGET_BLANK : name; } if (_isFirefox(ua)) { // Firefox complains when invalid URLs are popped out. Hacky way to bypass. url = url || FIREFOX_EMPTY_URL; // Firefox disables by default scrolling on popup windows, which can create // issues when the user has many Google accounts, for instance. options.scrollbars = 'yes'; } const optionsString = Object.entries(options).reduce((accum, [key, value]) => `${accum}${key}=${value},`, ''); if (_isIOSStandalone(ua) && target !== '_self') { openAsNewWindowIOS(url || '', target); return new AuthPopup(null); } // about:blank getting sanitized causing browsers like IE/Edge to display // brief error message before redirecting to handler. const newWin = window.open(url || '', target, optionsString); _assert(newWin, auth, "popup-blocked" /* AuthErrorCode.POPUP_BLOCKED */); // Flaky on IE edge, encapsulate with a try and catch. try { newWin.focus(); } catch (e) { } return new AuthPopup(newWin); } function openAsNewWindowIOS(url, target) { const el = document.createElement('a'); el.href = url; el.target = target; const click = document.createEvent('MouseEvent'); click.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 1, null); el.dispatchEvent(click); } /** * @license * Copyright 2021 Google LLC * * 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. */ /** * URL for Authentication widget which will initiate the OAuth handshake * * @internal */ const WIDGET_PATH = '__/auth/handler'; /** * URL for emulated environment * * @internal */ const EMULATOR_WIDGET_PATH = 'emulator/auth/handler'; /** * Fragment name for the App Check token that gets passed to the widget * * @internal */ const FIREBASE_APP_CHECK_FRAGMENT_ID = encodeURIComponent('fac'); async function _getRedirectUrl(auth, provider, authType, redirectUrl, eventId, additionalParams) { _assert(auth.config.authDomain, auth, "auth-domain-config-required" /* AuthErrorCode.MISSING_AUTH_DOMAIN */); _assert(auth.config.apiKey, auth, "invalid-api-key" /* AuthErrorCode.INVALID_API_KEY */); const params = { apiKey: auth.config.apiKey, appName: auth.name, authType, redirectUrl, v: SDK_VERSION, eventId }; if (provider instanceof FederatedAuthProvider) { provider.setDefaultLanguage(auth.languageCode); params.providerId = provider.providerId || ''; if (!isEmpty(provider.getCustomParameters())) { params.customParameters = JSON.stringify(provider.getCustomParameters()); } // TODO set additionalParams from the provider as well? for (const [key, value] of Object.entries(additionalParams || {})) { params[key] = value; } } if (provider instanceof BaseOAuthProvider) { const scopes = provider.getScopes().filter(scope => scope !== ''); if (scopes.length > 0) { params.scopes = scopes.join(','); } } if (auth.tenantId) { params.tid = auth.tenantId; } // TODO: maybe set eid as endipointId // TODO: maybe set fw as Frameworks.join(",") const paramsDict = params; for (const key of Object.keys(paramsDict)) { if (paramsDict[key] === undefined) { delete paramsDict[key]; } } // Sets the App Check token to pass to the widget const appCheckToken = await auth._getAppCheckToken(); const appCheckTokenFragment = appCheckToken ? `#${FIREBASE_APP_CHECK_FRAGMENT_ID}=${encodeURIComponent(appCheckToken)}` : ''; // Start at index 1 to skip the leading '&' in the query string return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(1)}${appCheckTokenFragment}`; } function getHandlerBase({ config }) { if (!config.emulator) { return `https://${config.authDomain}/${WIDGET_PATH}`; } return _emulatorUrl(config, EMULATOR_WIDGET_PATH); } /** * @license * Copyright 2020 Google LLC * * 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. */ /** * The special web storage event * */ const WEB_STORAGE_SUPPORT_KEY = 'webStorageSupport'; class BrowserPopupRedirectResolver { constructor() { this.eventManagers = {}; this.iframes = {}; this.originValidationPromises = {}; this._redirectPersistence = browserSessionPersistence; this._completeRedirectFn = _getRedirectResult; this._overrideRedirectResult = _overrideRedirectResult; } // Wrapping in async even though we don't await anywhere in order // to make sure errors are raised as promise rejections async _openPopup(auth, provider, authType, eventId) { var _a; debugAssert((_a = this.eventManagers[auth._key()]) === null || _a === void 0 ? void 0 : _a.manager, '_initialize() not called before _openPopup()'); const url = await _getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId); return _open(auth, url, _generateEventId()); } async _openRedirect(auth, provider, authType, eventId) { await this._originValidation(auth); const url = await _getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId); _setWindowLocation(url); return new Promise(() => { }); } _initialize(auth) { const key = auth._key(); if (this.eventManagers[key]) { const { manager, promise } = this.eventManagers[key]; if (manager) { return Promise.resolve(manager); } else { debugAssert(promise, 'If manager is not set, promise should be'); return promise; } } const promise = this.initAndGetManager(auth); this.eventManagers[key] = { promise }; // If the promise is rejected, the key should be removed so that the // operation can be retried later. promise.catch(() => { delete this.eventManagers[key]; }); return promise; } async initAndGetManager(auth) { const iframe = await _openIframe(auth); const manager = new AuthEventManager(auth); iframe.register('authEvent', (iframeEvent) => { _assert(iframeEvent === null || iframeEvent === void 0 ? void 0 : iframeEvent.authEvent, auth, "invalid-auth-event" /* AuthErrorCode.INVALID_AUTH_EVENT */); // TODO: Consider splitting redirect and popup events earlier on const handled = manager.onEvent(iframeEvent.authEvent); return { status: handled ? "ACK" /* GapiOutcome.ACK */ : "ERROR" /* GapiOutcome.ERROR */ }; }, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER); this.eventManagers[auth._key()] = { manager }; this.iframes[auth._key()] = iframe; return manager; } _isIframeWebStorageSupported(auth, cb) { const iframe = this.iframes[auth._key()]; iframe.send(WEB_STORAGE_SUPPORT_KEY, { type: WEB_STORAGE_SUPPORT_KEY }, result => { var _a; const isSupported = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a[WEB_STORAGE_SUPPORT_KEY]; if (isSupported !== undefined) { cb(!!isSupported); } _fail(auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); }, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER); } _originValidation(auth) { const key = auth._key(); if (!this.originValidationPromises[key]) { this.originValidationPromises[key] = _validateOrigin$1(auth); } return this.originValidationPromises[key]; } get _shouldInitProactively() { // Mobile browsers and Safari need to optimistically initialize return _isMobileBrowser() || _isSafari() || _isIOS(); } } /** * An implementation of {@link PopupRedirectResolver} suitable for browser * based applications. * * @remarks * This method does not work in a Node.js environment. * * @public */ const browserPopupRedirectResolver = BrowserPopupRedirectResolver; /** * {@inheritdoc PhoneMultiFactorAssertion} * * @public */ class PhoneMultiFactorAssertionImpl extends MultiFactorAssertionImpl { constructor(credential) { super("phone" /* FactorId.PHONE */); this.credential = credential; } /** @internal */ static _fromCredential(credential) { return new PhoneMultiFactorAssertionImpl(credential); } /** @internal */ _finalizeEnroll(auth, idToken, displayName) { return finalizeEnrollPhoneMfa(auth, { idToken, displayName, phoneVerificationInfo: this.credential._makeVerificationRequest() }); } /** @internal */ _finalizeSignIn(auth, mfaPendingCredential) { return finalizeSignInPhoneMfa(auth, { mfaPendingCredential, phoneVerificationInfo: this.credential._makeVerificationRequest() }); } } /** * Provider for generating a {@link PhoneMultiFactorAssertion}. * * @public */ class PhoneMultiFactorGenerator { constructor() { } /** * Provides a {@link PhoneMultiFactorAssertion} to confirm ownership of the phone second factor. * * @remarks * This method does not work in a Node.js environment. * * @param phoneAuthCredential - A credential provided by {@link PhoneAuthProvider.credential}. * @returns A {@link PhoneMultiFactorAssertion} which can be used with * {@link MultiFactorResolver.resolveSignIn} */ static assertion(credential) { return PhoneMultiFactorAssertionImpl._fromCredential(credential); } } /** * The identifier of the phone second factor: `phone`. */ PhoneMultiFactorGenerator.FACTOR_ID = 'phone'; /** * @license * Copyright 2021 Google LLC * * 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. */ const DEFAULT_ID_TOKEN_MAX_AGE = 5 * 60; const authIdTokenMaxAge = getExperimentalSetting('authIdTokenMaxAge') || DEFAULT_ID_TOKEN_MAX_AGE; let lastPostedIdToken = null; const mintCookieFactory = (url) => async (user) => { const idTokenResult = user && (await user.getIdTokenResult()); const idTokenAge = idTokenResult && (new Date().getTime() - Date.parse(idTokenResult.issuedAtTime)) / 1000; if (idTokenAge && idTokenAge > authIdTokenMaxAge) { return; } // Specifically trip null => undefined when logged out, to delete any existing cookie const idToken = idTokenResult === null || idTokenResult === void 0 ? void 0 : idTokenResult.token; if (lastPostedIdToken === idToken) { return; } lastPostedIdToken = idToken; await fetch(url, { method: idToken ? 'POST' : 'DELETE', headers: idToken ? { 'Authorization': `Bearer ${idToken}` } : {} }); }; /** * Returns the Auth instance associated with the provided {@link @firebase/app#FirebaseApp}. * If no instance exists, initializes an Auth instance with platform-specific default dependencies. * * @param app - The Firebase App. * * @public */ function getAuth(app = getApp()) { const provider = _getProvider(app, 'auth'); if (provider.isInitialized()) { return provider.getImmediate(); } const auth = initializeAuth(app, { popupRedirectResolver: browserPopupRedirectResolver, persistence: [ indexedDBLocalPersistence, browserLocalPersistence, browserSessionPersistence ] }); const authTokenSyncUrl = getExperimentalSetting('authTokenSyncURL'); if (authTokenSyncUrl) { const mintCookie = mintCookieFactory(authTokenSyncUrl); beforeAuthStateChanged(auth, mintCookie, () => mintCookie(auth.currentUser)); onIdTokenChanged(auth, user => mintCookie(user)); } const authEmulatorHost = getDefaultEmulatorHost('auth'); if (authEmulatorHost) { connectAuthEmulator(auth, `http://${authEmulatorHost}`); } return auth; } function getScriptParentElement() { var _a, _b; return (_b = (_a = document.getElementsByTagName('head')) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : document; } _setExternalJSProvider({ loadJS(url) { // TODO: consider adding timeout support & cancellation return new Promise((resolve, reject) => { const el = document.createElement('script'); el.setAttribute('src', url); el.onload = resolve; el.onerror = e => { const error = _createError("internal-error" /* AuthErrorCode.INTERNAL_ERROR */); error.customData = e; reject(error); }; el.type = 'text/javascript'; el.charset = 'UTF-8'; getScriptParentElement().appendChild(el); }); }, gapiScript: 'https://apis.google.com/js/api.js', recaptchaV2Script: 'https://www.google.com/recaptcha/api.js', recaptchaEnterpriseScript: 'https://www.google.com/recaptcha/enterprise.js?render=' }); registerAuth("Browser" /* ClientPlatform.BROWSER */); /** * @license * Copyright 2021 Google LLC * * 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. */ function _cordovaWindow() { return window; } /** * @license * Copyright 2020 Google LLC * * 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. */ /** * How long to wait after the app comes back into focus before concluding that * the user closed the sign in tab. */ const REDIRECT_TIMEOUT_MS = 2000; /** * Generates the URL for the OAuth handler. */ async function _generateHandlerUrl(auth, event, provider) { var _a; // Get the cordova plugins const { BuildInfo } = _cordovaWindow(); debugAssert(event.sessionId, 'AuthEvent did not contain a session ID'); const sessionDigest = await computeSha256(event.sessionId); const additionalParams = {}; if (_isIOS()) { // iOS app identifier additionalParams['ibi'] = BuildInfo.packageName; } else if (_isAndroid()) { // Android app identifier additionalParams['apn'] = BuildInfo.packageName; } else { _fail(auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); } // Add the display name if available if (BuildInfo.displayName) { additionalParams['appDisplayName'] = BuildInfo.displayName; } // Attached the hashed session ID additionalParams['sessionId'] = sessionDigest; return _getRedirectUrl(auth, provider, event.type, undefined, (_a = event.eventId) !== null && _a !== void 0 ? _a : undefined, additionalParams); } /** * Validates that this app is valid for this project configuration */ async function _validateOrigin(auth) { const { BuildInfo } = _cordovaWindow(); const request = {}; if (_isIOS()) { request.iosBundleId = BuildInfo.packageName; } else if (_isAndroid()) { request.androidPackageName = BuildInfo.packageName; } else { _fail(auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); } // Will fail automatically if package name is not authorized await _getProjectConfig(auth, request); } function _performRedirect(handlerUrl) { // Get the cordova plugins const { cordova } = _cordovaWindow(); return new Promise(resolve => { cordova.plugins.browsertab.isAvailable(browserTabIsAvailable => { let iabRef = null; if (browserTabIsAvailable) { cordova.plugins.browsertab.openUrl(handlerUrl); } else { // TODO: Return the inappbrowser ref that's returned from the open call iabRef = cordova.InAppBrowser.open(handlerUrl, _isIOS7Or8() ? '_blank' : '_system', 'location=yes'); } resolve(iabRef); }); }); } /** * This function waits for app activity to be seen before resolving. It does * this by attaching listeners to various dom events. Once the app is determined * to be visible, this promise resolves. AFTER that resolution, the listeners * are detached and any browser tabs left open will be closed. */ async function _waitForAppResume(auth, eventListener, iabRef) { // Get the cordova plugins const { cordova } = _cordovaWindow(); let cleanup = () => { }; try { await new Promise((resolve, reject) => { let onCloseTimer = null; // DEFINE ALL THE CALLBACKS ===== function authEventSeen() { var _a; // Auth event was detected. Resolve this promise and close the extra // window if it's still open. resolve(); const closeBrowserTab = (_a = cordova.plugins.browsertab) === null || _a === void 0 ? void 0 : _a.close; if (typeof closeBrowserTab === 'function') { closeBrowserTab(); } // Close inappbrowser emebedded webview in iOS7 and 8 case if still // open. if (typeof (iabRef === null || iabRef === void 0 ? void 0 : iabRef.close) === 'function') { iabRef.close(); } } function resumed() { if (onCloseTimer) { // This code already ran; do not rerun. return; } onCloseTimer = window.setTimeout(() => { // Wait two seeconds after resume then reject. reject(_createError(auth, "redirect-cancelled-by-user" /* AuthErrorCode.REDIRECT_CANCELLED_BY_USER */)); }, REDIRECT_TIMEOUT_MS); } function visibilityChanged() { if ((document === null || document === void 0 ? void 0 : document.visibilityState) === 'visible') { resumed(); } } // ATTACH ALL THE LISTENERS ===== // Listen for the auth event eventListener.addPassiveListener(authEventSeen); // Listen for resume and visibility events document.addEventListener('resume', resumed, false); if (_isAndroid()) { document.addEventListener('visibilitychange', visibilityChanged, false); } // SETUP THE CLEANUP FUNCTION ===== cleanup = () => { eventListener.removePassiveListener(authEventSeen); document.removeEventListener('resume', resumed, false); document.removeEventListener('visibilitychange', visibilityChanged, false); if (onCloseTimer) { window.clearTimeout(onCloseTimer); } }; }); } finally { cleanup(); } } /** * Checks the configuration of the Cordova environment. This has no side effect * if the configuration is correct; otherwise it throws an error with the * missing plugin. */ function _checkCordovaConfiguration(auth) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const win = _cordovaWindow(); // Check all dependencies installed. // https://github.com/nordnet/cordova-universal-links-plugin // Note that cordova-universal-links-plugin has been abandoned. // A fork with latest fixes is available at: // https://www.npmjs.com/package/cordova-universal-links-plugin-fix _assert(typeof ((_a = win === null || win === void 0 ? void 0 : win.universalLinks) === null || _a === void 0 ? void 0 : _a.subscribe) === 'function', auth, "invalid-cordova-configuration" /* AuthErrorCode.INVALID_CORDOVA_CONFIGURATION */, { missingPlugin: 'cordova-universal-links-plugin-fix' }); // https://www.npmjs.com/package/cordova-plugin-buildinfo _assert(typeof ((_b = win === null || win === void 0 ? void 0 : win.BuildInfo) === null || _b === void 0 ? void 0 : _b.packageName) !== 'undefined', auth, "invalid-cordova-configuration" /* AuthErrorCode.INVALID_CORDOVA_CONFIGURATION */, { missingPlugin: 'cordova-plugin-buildInfo' }); // https://github.com/google/cordova-plugin-browsertab _assert(typeof ((_e = (_d = (_c = win === null || win === void 0 ? void 0 : win.cordova) === null || _c === void 0 ? void 0 : _c.plugins) === null || _d === void 0 ? void 0 : _d.browsertab) === null || _e === void 0 ? void 0 : _e.openUrl) === 'function', auth, "invalid-cordova-configuration" /* AuthErrorCode.INVALID_CORDOVA_CONFIGURATION */, { missingPlugin: 'cordova-plugin-browsertab' }); _assert(typeof ((_h = (_g = (_f = win === null || win === void 0 ? void 0 : win.cordova) === null || _f === void 0 ? void 0 : _f.plugins) === null || _g === void 0 ? void 0 : _g.browsertab) === null || _h === void 0 ? void 0 : _h.isAvailable) === 'function', auth, "invalid-cordova-configuration" /* AuthErrorCode.INVALID_CORDOVA_CONFIGURATION */, { missingPlugin: 'cordova-plugin-browsertab' }); // https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/ _assert(typeof ((_k = (_j = win === null || win === void 0 ? void 0 : win.cordova) === null || _j === void 0 ? void 0 : _j.InAppBrowser) === null || _k === void 0 ? void 0 : _k.open) === 'function', auth, "invalid-cordova-configuration" /* AuthErrorCode.INVALID_CORDOVA_CONFIGURATION */, { missingPlugin: 'cordova-plugin-inappbrowser' }); } /** * Computes the SHA-256 of a session ID. The SubtleCrypto interface is only * available in "secure" contexts, which covers Cordova (which is served on a file * protocol). */ async function computeSha256(sessionId) { const bytes = stringToArrayBuffer(sessionId); // TODO: For IE11 crypto has a different name and this operation comes back // as an object, not a promise. This is the old proposed standard that // is used by IE11: // https://www.w3.org/TR/2013/WD-WebCryptoAPI-20130108/#cryptooperation-interface const buf = await crypto.subtle.digest('SHA-256', bytes); const arr = Array.from(new Uint8Array(buf)); return arr.map(num => num.toString(16).padStart(2, '0')).join(''); } function stringToArrayBuffer(str) { // This function is only meant to deal with an ASCII charset and makes // certain simplifying assumptions. debugAssert(/[0-9a-zA-Z]+/.test(str), 'Can only convert alpha-numeric strings'); if (typeof TextEncoder !== 'undefined') { return new TextEncoder().encode(str); } const buff = new ArrayBuffer(str.length); const view = new Uint8Array(buff); for (let i = 0; i < str.length; i++) { view[i] = str.charCodeAt(i); } return view; } /** * @license * Copyright 2020 Google LLC * * 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. */ const SESSION_ID_LENGTH = 20; /** Custom AuthEventManager that adds passive listeners to events */ class CordovaAuthEventManager extends AuthEventManager { constructor() { super(...arguments); this.passiveListeners = new Set(); this.initPromise = new Promise(resolve => { this.resolveInialized = resolve; }); } addPassiveListener(cb) { this.passiveListeners.add(cb); } removePassiveListener(cb) { this.passiveListeners.delete(cb); } // In a Cordova environment, this manager can live through multiple redirect // operations resetRedirect() { this.queuedRedirectEvent = null; this.hasHandledPotentialRedirect = false; } /** Override the onEvent method */ onEvent(event) { this.resolveInialized(); this.passiveListeners.forEach(cb => cb(event)); return super.onEvent(event); } async initialized() { await this.initPromise; } } /** * Generates a (partial) {@link AuthEvent}. */ function _generateNewEvent(auth, type, eventId = null) { return { type, eventId, urlResponse: null, sessionId: generateSessionId(), postBody: null, tenantId: auth.tenantId, error: _createError(auth, "no-auth-event" /* AuthErrorCode.NO_AUTH_EVENT */) }; } function _savePartialEvent(auth, event) { return storage()._set(persistenceKey(auth), event); } async function _getAndRemoveEvent(auth) { const event = (await storage()._get(persistenceKey(auth))); if (event) { await storage()._remove(persistenceKey(auth)); } return event; } function _eventFromPartialAndUrl(partialEvent, url) { var _a, _b; // Parse the deep link within the dynamic link URL. const callbackUrl = _getDeepLinkFromCallback(url); // Confirm it is actually a callback URL. // Currently the universal link will be of this format: // https:///__/auth/callback // This is a fake URL but is not intended to take the user anywhere // and just redirect to the app. if (callbackUrl.includes('/__/auth/callback')) { // Check if there is an error in the URL. // This mechanism is also used to pass errors back to the app: // https:///__/auth/callback?firebaseError= const params = searchParamsOrEmpty(callbackUrl); // Get the error object corresponding to the stringified error if found. const errorObject = params['firebaseError'] ? parseJsonOrNull(decodeURIComponent(params['firebaseError'])) : null; const code = (_b = (_a = errorObject === null || errorObject === void 0 ? void 0 : errorObject['code']) === null || _a === void 0 ? void 0 : _a.split('auth/')) === null || _b === void 0 ? void 0 : _b[1]; const error = code ? _createError(code) : null; if (error) { return { type: partialEvent.type, eventId: partialEvent.eventId, tenantId: partialEvent.tenantId, error, urlResponse: null, sessionId: null, postBody: null }; } else { return { type: partialEvent.type, eventId: partialEvent.eventId, tenantId: partialEvent.tenantId, sessionId: partialEvent.sessionId, urlResponse: callbackUrl, postBody: null }; } } return null; } function generateSessionId() { const chars = []; const allowedChars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; for (let i = 0; i < SESSION_ID_LENGTH; i++) { const idx = Math.floor(Math.random() * allowedChars.length); chars.push(allowedChars.charAt(idx)); } return chars.join(''); } function storage() { return _getInstance(browserLocalPersistence); } function persistenceKey(auth) { return _persistenceKeyName("authEvent" /* KeyName.AUTH_EVENT */, auth.config.apiKey, auth.name); } function parseJsonOrNull(json) { try { return JSON.parse(json); } catch (e) { return null; } } // Exported for testing function _getDeepLinkFromCallback(url) { const params = searchParamsOrEmpty(url); const link = params['link'] ? decodeURIComponent(params['link']) : undefined; // Double link case (automatic redirect) const doubleDeepLink = searchParamsOrEmpty(link)['link']; // iOS custom scheme links. const iOSDeepLink = params['deep_link_id'] ? decodeURIComponent(params['deep_link_id']) : undefined; const iOSDoubleDeepLink = searchParamsOrEmpty(iOSDeepLink)['link']; return iOSDoubleDeepLink || iOSDeepLink || doubleDeepLink || link || url; } /** * Optimistically tries to get search params from a string, or else returns an * empty search params object. */ function searchParamsOrEmpty(url) { if (!(url === null || url === void 0 ? void 0 : url.includes('?'))) { return {}; } const [_, ...rest] = url.split('?'); return querystringDecode(rest.join('?')); } /** * @license * Copyright 2021 Google LLC * * 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. */ /** * How long to wait for the initial auth event before concluding no * redirect pending */ const INITIAL_EVENT_TIMEOUT_MS = 500; class CordovaPopupRedirectResolver { constructor() { this._redirectPersistence = browserSessionPersistence; this._shouldInitProactively = true; // This is lightweight for Cordova this.eventManagers = new Map(); this.originValidationPromises = {}; this._completeRedirectFn = _getRedirectResult; this._overrideRedirectResult = _overrideRedirectResult; } async _initialize(auth) { const key = auth._key(); let manager = this.eventManagers.get(key); if (!manager) { manager = new CordovaAuthEventManager(auth); this.eventManagers.set(key, manager); this.attachCallbackListeners(auth, manager); } return manager; } _openPopup(auth) { _fail(auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */); } async _openRedirect(auth, provider, authType, eventId) { _checkCordovaConfiguration(auth); const manager = await this._initialize(auth); await manager.initialized(); // Reset the persisted redirect states. This does not matter on Web where // the redirect always blows away application state entirely. On Cordova, // the app maintains control flow through the redirect. manager.resetRedirect(); _clearRedirectOutcomes(); await this._originValidation(auth); const event = _generateNewEvent(auth, authType, eventId); await _savePartialEvent(auth, event); const url = await _generateHandlerUrl(auth, event, provider); const iabRef = await _performRedirect(url); return _waitForAppResume(auth, manager, iabRef); } _isIframeWebStorageSupported(_auth, _cb) { throw new Error('Method not implemented.'); } _originValidation(auth) { const key = auth._key(); if (!this.originValidationPromises[key]) { this.originValidationPromises[key] = _validateOrigin(auth); } return this.originValidationPromises[key]; } attachCallbackListeners(auth, manager) { // Get the global plugins const { universalLinks, handleOpenURL, BuildInfo } = _cordovaWindow(); const noEventTimeout = setTimeout(async () => { // We didn't see that initial event. Clear any pending object and // dispatch no event await _getAndRemoveEvent(auth); manager.onEvent(generateNoEvent()); }, INITIAL_EVENT_TIMEOUT_MS); const universalLinksCb = async (eventData) => { // We have an event so we can clear the no event timeout clearTimeout(noEventTimeout); const partialEvent = await _getAndRemoveEvent(auth); let finalEvent = null; if (partialEvent && (eventData === null || eventData === void 0 ? void 0 : eventData['url'])) { finalEvent = _eventFromPartialAndUrl(partialEvent, eventData['url']); } // If finalEvent is never filled, trigger with no event manager.onEvent(finalEvent || generateNoEvent()); }; // Universal links subscriber doesn't exist for iOS, so we need to check if (typeof universalLinks !== 'undefined' && typeof universalLinks.subscribe === 'function') { universalLinks.subscribe(null, universalLinksCb); } // iOS 7 or 8 custom URL schemes. // This is also the current default behavior for iOS 9+. // For this to work, cordova-plugin-customurlscheme needs to be installed. // https://github.com/EddyVerbruggen/Custom-URL-scheme // Do not overwrite the existing developer's URL handler. const existingHandleOpenURL = handleOpenURL; const packagePrefix = `${BuildInfo.packageName.toLowerCase()}://`; _cordovaWindow().handleOpenURL = async (url) => { if (url.toLowerCase().startsWith(packagePrefix)) { // We want this intentionally to float // eslint-disable-next-line @typescript-eslint/no-floating-promises universalLinksCb({ url }); } // Call the developer's handler if it is present. if (typeof existingHandleOpenURL === 'function') { try { existingHandleOpenURL(url); } catch (e) { // This is a developer error. Don't stop the flow of the SDK. console.error(e); } } }; } } /** * An implementation of {@link PopupRedirectResolver} suitable for Cordova * based applications. * * @public */ const cordovaPopupRedirectResolver = CordovaPopupRedirectResolver; function generateNoEvent() { return { type: "unknown" /* AuthEventType.UNKNOWN */, eventId: null, sessionId: null, urlResponse: null, postBody: null, tenantId: null, error: _createError("no-auth-event" /* AuthErrorCode.NO_AUTH_EVENT */) }; } /** * @license * Copyright 2017 Google LLC * * 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. */ // This function should only be called by frameworks (e.g. FirebaseUI-web) to log their usage. // It is not intended for direct use by developer apps. NO jsdoc here to intentionally leave it out // of autogenerated documentation pages to reduce accidental misuse. function addFrameworkForLogging(auth, framework) { _castAuth(auth)._logFramework(framework); } export { ActionCodeOperation, AuthPopup, FactorId, OperationType, PhoneAuthProvider, PhoneMultiFactorGenerator, ProviderId, RecaptchaVerifier, SignInMethod, _getRedirectResult, _overrideRedirectResult, addFrameworkForLogging, browserLocalPersistence, browserPopupRedirectResolver, browserSessionPersistence, cordovaPopupRedirectResolver, getAuth, getRedirectResult, linkWithPhoneNumber, linkWithPopup, linkWithRedirect, reauthenticateWithPhoneNumber, reauthenticateWithPopup, reauthenticateWithRedirect, signInWithPhoneNumber, signInWithPopup, signInWithRedirect, updatePhoneNumber }; //# sourceMappingURL=internal.js.map