UNPKG

@firebase/auth

Version:

The Firebase Authenticaton component of the Firebase JS SDK.

1,340 lines (1,325 loc) • 132 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var index = require('./index-c37383d8.js'); var app = require('@firebase/app'); var util = require('@firebase/util'); require('tslib'); require('@firebase/component'); require('@firebase/logger'); /** * @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(index.STORAGE_AVAILABLE_KEY, '1'); this.storage.removeItem(index.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. */ // The polling period in case events are not supported const _POLLING_INTERVAL_MS$1 = 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 = index._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 (index._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$1); } 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 2025 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 POLLING_INTERVAL_MS = 1000; // Pull a cookie value from document.cookie function getDocumentCookie(name) { var _a, _b; const escapedName = name.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&'); const matcher = RegExp(`${escapedName}=([^;]+)`); return (_b = (_a = document.cookie.match(matcher)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : null; } // Produce a sanitized cookie name from the persistence key function getCookieName(key) { // __HOST- doesn't work in localhost https://issues.chromium.org/issues/40196122 but it has // desirable security properties, so lets use a different cookie name while in dev-mode. // Already checked isSecureContext in _isAvailable, so if it's http we're hitting local. const isDevMode = window.location.protocol === 'http:'; return `${isDevMode ? '__dev_' : '__HOST-'}FIREBASE_${key.split(':')[3]}`; } class CookiePersistence { constructor() { this.type = "COOKIE" /* PersistenceType.COOKIE */; this.listenerUnsubscribes = new Map(); } // used to get the URL to the backend to proxy to _getFinalTarget(originalUrl) { if (typeof window === undefined) { return originalUrl; } const url = new URL(`${window.location.origin}/__cookies__`); url.searchParams.set('finalTarget', originalUrl); return url; } // To be a usable persistence method in a chain browserCookiePersistence ensures that // prerequisites have been met, namely that we're in a secureContext, navigator and document are // available and cookies are enabled. Not all UAs support these method, so fallback accordingly. async _isAvailable() { var _a; if (typeof isSecureContext === 'boolean' && !isSecureContext) { return false; } if (typeof navigator === 'undefined' || typeof document === 'undefined') { return false; } return (_a = navigator.cookieEnabled) !== null && _a !== void 0 ? _a : true; } // Set should be a noop as we expect middleware to handle this async _set(_key, _value) { return; } // Attempt to get the cookie from cookieStore, fallback to document.cookie async _get(key) { if (!this._isAvailable()) { return null; } const name = getCookieName(key); if (window.cookieStore) { const cookie = await window.cookieStore.get(name); return cookie === null || cookie === void 0 ? void 0 : cookie.value; } return getDocumentCookie(name); } // Log out by overriding the idToken with a sentinel value of "" async _remove(key) { if (!this._isAvailable()) { return; } // To make sure we don't hit signout over and over again, only do this operation if we need to // with the logout sentinel value of "" this can cause race conditions. Unnecessary set-cookie // headers will reduce CDN hit rates too. const existingValue = await this._get(key); if (!existingValue) { return; } const name = getCookieName(key); document.cookie = `${name}=;Max-Age=34560000;Partitioned;Secure;SameSite=Strict;Path=/;Priority=High`; await fetch(`/__cookies__`, { method: 'DELETE' }).catch(() => undefined); } // Listen for cookie changes, both cookieStore and fallback to polling document.cookie _addListener(key, listener) { if (!this._isAvailable()) { return; } const name = getCookieName(key); if (window.cookieStore) { const cb = ((event) => { const changedCookie = event.changed.find(change => change.name === name); if (changedCookie) { listener(changedCookie.value); } const deletedCookie = event.deleted.find(change => change.name === name); if (deletedCookie) { listener(null); } }); const unsubscribe = () => window.cookieStore.removeEventListener('change', cb); this.listenerUnsubscribes.set(listener, unsubscribe); return window.cookieStore.addEventListener('change', cb); } let lastValue = getDocumentCookie(name); const interval = setInterval(() => { const currentValue = getDocumentCookie(name); if (currentValue !== lastValue) { listener(currentValue); lastValue = currentValue; } }, POLLING_INTERVAL_MS); const unsubscribe = () => clearInterval(interval); this.listenerUnsubscribes.set(listener, unsubscribe); } _removeListener(_key, listener) { const unsubscribe = this.listenerUnsubscribes.get(listener); if (!unsubscribe) { return; } unsubscribe(); this.listenerUnsubscribes.delete(listener); } } CookiePersistence.type = 'COOKIE'; /** * An implementation of {@link Persistence} of type `COOKIE`, for use on the client side in * applications leveraging hybrid rendering and middleware. * * @remarks This persistence method requires companion middleware to function, such as that provided * by {@link https://firebaseopensource.com/projects/firebaseextended/reactfire/ | ReactFire} for * NextJS. * @beta */ const browserCookiePersistence = CookiePersistence; /** * @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 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. */ /** * Shim for Promise.allSettled, note the slightly different format of `fulfilled` vs `status`. * * @param promises - Array of promises to wait on. */ function _allSettled(promises) { return Promise.all(promises.map(async (promise) => { try { const value = await promise; return { fulfilled: true, value }; } catch (reason) { return { fulfilled: false, reason }; } })); } /** * @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. */ /** * Interface class for receiving messages. * */ class Receiver { constructor(eventTarget) { this.eventTarget = eventTarget; this.handlersMap = {}; this.boundEventHandler = this.handleEvent.bind(this); } /** * Obtain an instance of a Receiver for a given event target, if none exists it will be created. * * @param eventTarget - An event target (such as window or self) through which the underlying * messages will be received. */ static _getInstance(eventTarget) { // The results are stored in an array since objects can't be keys for other // objects. In addition, setting a unique property on an event target as a // hash map key may not be allowed due to CORS restrictions. const existingInstance = this.receivers.find(receiver => receiver.isListeningto(eventTarget)); if (existingInstance) { return existingInstance; } const newInstance = new Receiver(eventTarget); this.receivers.push(newInstance); return newInstance; } isListeningto(eventTarget) { return this.eventTarget === eventTarget; } /** * Fans out a MessageEvent to the appropriate listeners. * * @remarks * Sends an {@link Status.ACK} upon receipt and a {@link Status.DONE} once all handlers have * finished processing. * * @param event - The MessageEvent. * */ async handleEvent(event) { const messageEvent = event; const { eventId, eventType, data } = messageEvent.data; const handlers = this.handlersMap[eventType]; if (!(handlers === null || handlers === void 0 ? void 0 : handlers.size)) { return; } messageEvent.ports[0].postMessage({ status: "ack" /* _Status.ACK */, eventId, eventType }); const promises = Array.from(handlers).map(async (handler) => handler(messageEvent.origin, data)); const response = await _allSettled(promises); messageEvent.ports[0].postMessage({ status: "done" /* _Status.DONE */, eventId, eventType, response }); } /** * Subscribe an event handler for a particular event. * * @param eventType - Event name to subscribe to. * @param eventHandler - The event handler which should receive the events. * */ _subscribe(eventType, eventHandler) { if (Object.keys(this.handlersMap).length === 0) { this.eventTarget.addEventListener('message', this.boundEventHandler); } if (!this.handlersMap[eventType]) { this.handlersMap[eventType] = new Set(); } this.handlersMap[eventType].add(eventHandler); } /** * Unsubscribe an event handler from a particular event. * * @param eventType - Event name to unsubscribe from. * @param eventHandler - Optional event handler, if none provided, unsubscribe all handlers on this event. * */ _unsubscribe(eventType, eventHandler) { if (this.handlersMap[eventType] && eventHandler) { this.handlersMap[eventType].delete(eventHandler); } if (!eventHandler || this.handlersMap[eventType].size === 0) { delete this.handlersMap[eventType]; } if (Object.keys(this.handlersMap).length === 0) { this.eventTarget.removeEventListener('message', this.boundEventHandler); } } } Receiver.receivers = []; /** * @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 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. */ /** * Interface for sending messages and waiting for a completion response. * */ class Sender { constructor(target) { this.target = target; this.handlers = new Set(); } /** * Unsubscribe the handler and remove it from our tracking Set. * * @param handler - The handler to unsubscribe. */ removeMessageHandler(handler) { if (handler.messageChannel) { handler.messageChannel.port1.removeEventListener('message', handler.onMessage); handler.messageChannel.port1.close(); } this.handlers.delete(handler); } /** * Send a message to the Receiver located at {@link target}. * * @remarks * We'll first wait a bit for an ACK , if we get one we will wait significantly longer until the * receiver has had a chance to fully process the event. * * @param eventType - Type of event to send. * @param data - The payload of the event. * @param timeout - Timeout for waiting on an ACK from the receiver. * * @returns An array of settled promises from all the handlers that were listening on the receiver. */ async _send(eventType, data, timeout = 50 /* _TimeoutDuration.ACK */) { const messageChannel = typeof MessageChannel !== 'undefined' ? new MessageChannel() : null; if (!messageChannel) { throw new Error("connection_unavailable" /* _MessageError.CONNECTION_UNAVAILABLE */); } // Node timers and browser timers return fundamentally different types. // We don't actually care what the value is but TS won't accept unknown and // we can't cast properly in both environments. // eslint-disable-next-line @typescript-eslint/no-explicit-any let completionTimer; let handler; return new Promise((resolve, reject) => { const eventId = _generateEventId('', 20); messageChannel.port1.start(); const ackTimer = setTimeout(() => { reject(new Error("unsupported_event" /* _MessageError.UNSUPPORTED_EVENT */)); }, timeout); handler = { messageChannel, onMessage(event) { const messageEvent = event; if (messageEvent.data.eventId !== eventId) { return; } switch (messageEvent.data.status) { case "ack" /* _Status.ACK */: // The receiver should ACK first. clearTimeout(ackTimer); completionTimer = setTimeout(() => { reject(new Error("timeout" /* _MessageError.TIMEOUT */)); }, 3000 /* _TimeoutDuration.COMPLETION */); break; case "done" /* _Status.DONE */: // Once the receiver's handlers are finished we will get the results. clearTimeout(completionTimer); resolve(messageEvent.data.response); break; default: clearTimeout(ackTimer); clearTimeout(completionTimer); reject(new Error("invalid_response" /* _MessageError.INVALID_RESPONSE */)); break; } } }; this.handlers.add(handler); messageChannel.port1.addEventListener('message', handler.onMessage); this.target.postMessage({ eventType, eventId, data }, [messageChannel.port2]); }).finally(() => { if (handler) { this.removeMessageHandler(handler); } }); } } /** * @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. */ const DB_NAME = 'firebaseLocalStorageDb'; const DB_VERSION = 1; const DB_OBJECTSTORE_NAME = 'firebaseLocalStorage'; const DB_DATA_KEYPATH = 'fbase_key'; /** * Promise wrapper for IDBRequest * * Unfortunately we can't cleanly extend Promise<T> since promises are not callable in ES6 * */ class DBPromise { constructor(request) { this.request = request; } toPromise() { return new Promise((resolve, reject) => { this.request.addEventListener('success', () => { resolve(this.request.result); }); this.request.addEventListener('error', () => { reject(this.request.error); }); }); } } function getObjectStore(db, isReadWrite) { return db .transaction([DB_OBJECTSTORE_NAME], isReadWrite ? 'readwrite' : 'readonly') .objectStore(DB_OBJECTSTORE_NAME); } function _deleteDatabase() { const request = indexedDB.deleteDatabase(DB_NAME); return new DBPromise(request).toPromise(); } function _openDatabase() { const request = indexedDB.open(DB_NAME, DB_VERSION); return new Promise((resolve, reject) => { request.addEventListener('error', () => { reject(request.error); }); request.addEventListener('upgradeneeded', () => { const db = request.result; try { db.createObjectStore(DB_OBJECTSTORE_NAME, { keyPath: DB_DATA_KEYPATH }); } catch (e) { reject(e); } }); request.addEventListener('success', async () => { const db = request.result; // Strange bug that occurs in Firefox when multiple tabs are opened at the // same time. The only way to recover seems to be deleting the database // and re-initializing it. // https://github.com/firebase/firebase-js-sdk/issues/634 if (!db.objectStoreNames.contains(DB_OBJECTSTORE_NAME)) { // Need to close the database or else you get a `blocked` event db.close(); await _deleteDatabase(); resolve(await _openDatabase()); } else { resolve(db); } }); }); } async function _putObject(db, key, value) { const request = getObjectStore(db, true).put({ [DB_DATA_KEYPATH]: key, value }); return new DBPromise(request).toPromise(); } async function getObject(db, key) { const request = getObjectStore(db, false).get(key); const data = await new DBPromise(request).toPromise(); return data === undefined ? null : data.value; } function _deleteObject(db, key) { const request = getObjectStore(db, true).delete(key); return new DBPromise(request).toPromise(); } const _POLLING_INTERVAL_MS = 800; const _TRANSACTION_RETRY_COUNT = 3; class IndexedDBLocalPersistence { constructor() { this.type = "LOCAL" /* PersistenceType.LOCAL */; this._shouldAllowMigration = true; this.listeners = {}; this.localCache = {}; // setTimeout return value is platform specific // eslint-disable-next-line @typescript-eslint/no-explicit-any this.pollTimer = null; this.pendingWrites = 0; this.receiver = null; this.sender = null; this.serviceWorkerReceiverAvailable = false; this.activeServiceWorker = null; // Fire & forget the service worker registration as it may never resolve this._workerInitializationPromise = this.initializeServiceWorkerMessaging().then(() => { }, () => { }); } async _openDb() { if (this.db) { return this.db; } this.db = await _openDatabase(); return this.db; } async _withRetries(op) { let numAttempts = 0; while (true) { try { const db = await this._openDb(); return await op(db); } catch (e) { if (numAttempts++ > _TRANSACTION_RETRY_COUNT) { throw e; } if (this.db) { this.db.close(); this.db = undefined; } // TODO: consider adding exponential backoff } } } /** * IndexedDB events do not propagate from the main window to the worker context. We rely on a * postMessage interface to send these events to the worker ourselves. */ async initializeServiceWorkerMessaging() { return index._isWorker() ? this.initializeReceiver() : this.initializeSender(); } /** * As the worker we should listen to events from the main window. */ async initializeReceiver() { this.receiver = Receiver._getInstance(index._getWorkerGlobalScope()); // Refresh from persistence if we receive a KeyChanged message. this.receiver._subscribe("keyChanged" /* _EventType.KEY_CHANGED */, async (_origin, data) => { const keys = await this._poll(); return { keyProcessed: keys.includes(data.key) }; }); // Let the sender know that we are listening so they give us more timeout. this.receiver._subscribe("ping" /* _EventType.PING */, async (_origin, _data) => { return ["keyChanged" /* _EventType.KEY_CHANGED */]; }); } /** * As the main window, we should let the worker know when keys change (set and remove). * * @remarks * {@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/ready | ServiceWorkerContainer.ready} * may not resolve. */ async initializeSender() { var _a, _b; // Check to see if there's an active service worker. this.activeServiceWorker = await index._getActiveServiceWorker(); if (!this.activeServiceWorker) { return; } this.sender = new Sender(this.activeServiceWorker); // Ping the service worker to check what events they can handle. const results = await this.sender._send("ping" /* _EventType.PING */, {}, 800 /* _TimeoutDuration.LONG_ACK */); if (!results) { return; } if (((_a = results[0]) === null || _a === void 0 ? void 0 : _a.fulfilled) && ((_b = results[0]) === null || _b === void 0 ? void 0 : _b.value.includes("keyChanged" /* _EventType.KEY_CHANGED */))) { this.serviceWorkerReceiverAvailable = true; } } /** * Let the worker know about a changed key, the exact key doesn't technically matter since the * worker will just trigger a full sync anyway. * * @remarks * For now, we only support one service worker per page. * * @param key - Storage key which changed. */ async notifyServiceWorker(key) { if (!this.sender || !this.activeServiceWorker || index._getServiceWorkerController() !== this.activeServiceWorker) { return; } try { await this.sender._send("keyChanged" /* _EventType.KEY_CHANGED */, { key }, // Use long timeout if receiver has previously responded to a ping from us. this.serviceWorkerReceiverAvailable ? 800 /* _TimeoutDuration.LONG_ACK */ : 50 /* _TimeoutDuration.ACK */); } catch (_a) { // This is a best effort approach. Ignore errors. } } async _isAvailable() { try { if (!indexedDB) { return false; } const db = await _openDatabase(); await _putObject(db, index.STORAGE_AVAILABLE_KEY, '1'); await _deleteObject(db, index.STORAGE_AVAILABLE_KEY); return true; } catch (_a) { } return false; } async _withPendingWrite(write) { this.pendingWrites++; try { await write(); } finally { this.pendingWrites--; } } async _set(key, value) { return this._withPendingWrite(async () => { await this._withRetries((db) => _putObject(db, key, value)); this.localCache[key] = value; return this.notifyServiceWorker(key); }); } async _get(key) { const obj = (await this._withRetries((db) => getObject(db, key))); this.localCache[key] = obj; return obj; } async _remove(key) { return this._withPendingWrite(async () => { await this._withRetries((db) => _deleteObject(db, key)); delete this.localCache[key]; return this.notifyServiceWorker(key); }); } async _poll() { // TODO: check if we need to fallback if getAll is not supported const result = await this._withRetries((db) => { const getAllRequest = getObjectStore(db, false).getAll(); return new DBPromise(getAllRequest).toPromise(); }); if (!result) { return []; } // If we have pending writes in progress abort, we'll get picked up on the next poll if (this.pendingWrites !== 0) { return []; } const keys = []; const keysInResult = new Set(); if (result.length !== 0) { for (const { fbase_key: key, value } of result) { keysInResult.add(key); if (JSON.stringify(this.localCache[key]) !== JSON.stringify(value)) { this.notifyListeners(key, value); keys.push(key); } } } for (const localKey of Object.keys(this.localCache)) { if (this.localCache[localKey] && !keysInResult.has(localKey)) { // Deleted this.notifyListeners(localKey, null); keys.push(localKey); } } return keys; } notifyListeners(key, newValue) { this.localCache[key] = newValue; const listeners = this.listeners[key]; if (listeners) { for (const listener of Array.from(listeners)) { listener(newValue); } } } startPolling() { this.stopPolling(); this.pollTimer = setInterval(async () => this._poll(), _POLLING_INTERVAL_MS); } stopPolling() { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } } _addListener(key, listener) { if (Object.keys(this.listeners).length === 0) { this.startPolling(); } if (!this.listeners[key]) { this.listeners[key] = new Set(); // Populate the cache to avoid spuriously triggering on first poll. void this._get(key); // This can happen in the background async and we can return immediately. } 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.stopPolling(); } } } IndexedDBLocalPersistence.type = 'LOCAL'; /** * An implementation of {@link Persistence} of type `LOCAL` using `indexedDB` * for the underlying storage. * * @public */ const indexedDBLocalPersistence = IndexedDBLocalPersistence; /** * @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 index._getInstance(resolverOverride); } index._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 index.AuthCredential { constructor(params) { super("custom" /* ProviderId.CUSTOM */, "custom" /* ProviderId.CUSTOM */); this.params = params; } _getIdTokenResponse(auth) { return index.signInWithIdp(auth, this._buildIdpRequest()); } _linkToIdToken(auth, idToken) { return index.signInWithIdp(auth, this._buildIdpRequest(idToken)); } _getReauthenticationResolver(auth) { return index.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 index._signInWithCredential(params.auth, new IdpCredential(params), params.bypassAuthState); } function _reauth(params) { const { auth, user } = params; index._assert(user, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return index._reauthenticate(user, new IdpCredential(params), params.bypassAuthState); } async function _link(params) { const { auth, user } = params; index._assert(user, auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); return index._link(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: index._fail(this.auth, "internal-error" /* AuthErrorCode.INTERNAL_ERROR */); } } resolve(cred) { index.debugAssert(this.pendingPromise, 'Pending promise was never set'); this.pendingPromise.resolve(cred); this.unregisterAndCleanUp(); } reject(error) { index.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 index.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 or with {@link Auth} instances created with a * {@link @firebase/app#FirebaseServerApp}. * * @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) { if (app._isFirebaseServerApp(auth.app)) { return Promise.reject(index._createError(auth, "operation-not-supported-in-this-environment" /* AuthErrorCode.OPERATION_NOT_SUPPORTED */)); } const authInternal = index._castAuth(auth); index._assertInstanceOf(auth, provider, index.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 or on any {@link User} signed in by * {@link Auth} instances created with a {@link @firebase/app#FirebaseServerApp}. * * @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, resol