UNPKG

@firebase/auth

Version:

The Firebase Authenticaton component of the Firebase JS SDK.

1,235 lines (1,218 loc) • 58.6 kB
import { av as _getInstance, aw as _assert, ax as _signInWithCredential, ay as _reauthenticate, az as _link$1, M as AuthCredential, aA as signInWithIdp, aB as _fail, aC as debugAssert, aD as _persistenceKeyName, aE as _serverAppCurrentUserOperationNotSupportedError, aF as _castAuth, aG as FederatedAuthProvider, aH as BaseOAuthProvider, aI as _emulatorUrl, aJ as _performApiRequest, aK as _isIOS, aL as _isAndroid, aM as _isIOS7Or8, aN as _createError, aO as _isMobileBrowser, aP as _isIE10 } from './totp-227b37e4.js'; export { A as ActionCodeOperation, aj as ActionCodeURL, M as AuthCredential, J as AuthErrorCodes, aR as AuthImpl, N as EmailAuthCredential, W as EmailAuthProvider, X as FacebookAuthProvider, F as FactorId, aT as FetchProvider, Z as GithubAuthProvider, Y as GoogleAuthProvider, Q as OAuthCredential, _ as OAuthProvider, O as OperationType, U as PhoneAuthCredential, P as PhoneAuthProvider, n as PhoneMultiFactorGenerator, q as ProviderId, R as RecaptchaVerifier, aU as SAMLAuthCredential, $ as SAMLAuthProvider, S as SignInMethod, T as TotpMultiFactorGenerator, o as TotpSecret, a0 as TwitterAuthProvider, aQ as UserImpl, aw as _assert, aF as _castAuth, aB as _fail, aS as _getClientVersion, av as _getInstance, aD as _persistenceKeyName, a8 as applyActionCode, y as beforeAuthStateChanged, a as browserCookiePersistence, b as browserLocalPersistence, m as browserPopupRedirectResolver, c as browserSessionPersistence, a9 as checkActionCode, a7 as confirmPasswordReset, L as connectAuthEmulator, ab as createUserWithEmailAndPassword, H as debugErrorMap, G as deleteUser, ag as fetchSignInMethodsForEmail, ar as getAdditionalUserInfo, p as getAuth, ao as getIdToken, ap as getIdTokenResult, at as getMultiFactorResolver, k as getRedirectResult, V as inMemoryPersistence, i as indexedDBLocalPersistence, K as initializeAuth, v as initializeRecaptchaConfig, ae as isSignInWithEmailLink, a3 as linkWithCredential, l as linkWithPhoneNumber, e as linkWithPopup, h as linkWithRedirect, au as multiFactor, z as onAuthStateChanged, x as onIdTokenChanged, ak as parseActionCodeURL, I as prodErrorMap, a4 as reauthenticateWithCredential, r as reauthenticateWithPhoneNumber, f as reauthenticateWithPopup, j as reauthenticateWithRedirect, as as reload, E as revokeAccessToken, ah as sendEmailVerification, a6 as sendPasswordResetEmail, ad as sendSignInLinkToEmail, t as setPersistence, a1 as signInAnonymously, a2 as signInWithCredential, a5 as signInWithCustomToken, ac as signInWithEmailAndPassword, af as signInWithEmailLink, s as signInWithPhoneNumber, d as signInWithPopup, g as signInWithRedirect, D as signOut, aq as unlink, C as updateCurrentUser, am as updateEmail, an as updatePassword, u as updatePhoneNumber, al as updateProfile, B as useDeviceLanguage, w as validatePassword, ai as verifyBeforeUpdateEmail, aa as verifyPasswordResetCode } from './totp-227b37e4.js'; import { isEmpty, querystring, querystringDecode } from '@firebase/util'; import 'tslib'; import { _isFirebaseServerApp, SDK_VERSION } from '@firebase/app'; import '@firebase/component'; import '@firebase/logger'; /** * @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 _generateEventId(prefix = '', digits = 10) { let random = ''; for (let i = 0; i < digits; i++) { random += Math.floor(Math.random() * 10); } return prefix + random; } /** * @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 AuthPopup { constructor(window) { this.window = window; this.associatedEvent = null; } close() { if (this.window) { try { this.window.close(); } catch (e) { } } } } /** * @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 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; } 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. */ async function _getRedirectResult(auth, resolverExtern, bypassAuthState = false) { if (_isFirebaseServerApp(auth.app)) { return Promise.reject(_serverAppCurrentUserOperationNotSupportedError(auth)); } 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; } const STORAGE_AVAILABLE_KEY = '__sak'; /** * @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. */ 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 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 endpointId // 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 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. */ 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. */ /** * 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 embedded 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 seconds 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. */ // 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. */ // 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; // 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(); } 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. */ 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.resolveInitialized = 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.resolveInitialized(); 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_DOMAIN>/__/auth/callback<OAUTH_RESPONSE> // 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_DOMAIN>/__/auth/callback?firebaseError=<STRINGIFIED_ERROR> 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(