diff options
Diffstat (limited to 'frontend-old/node_modules/firebase/firebase-app.js')
| -rw-r--r-- | frontend-old/node_modules/firebase/firebase-app.js | 2799 |
1 files changed, 2799 insertions, 0 deletions
diff --git a/frontend-old/node_modules/firebase/firebase-app.js b/frontend-old/node_modules/firebase/firebase-app.js new file mode 100644 index 0000000..5693cc3 --- /dev/null +++ b/frontend-old/node_modules/firebase/firebase-app.js @@ -0,0 +1,2799 @@ +/** + * @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. + */ +// This value is retrieved and hardcoded by the NPM postinstall script +const getDefaultsFromPostinstall = () => undefined; + +/** + * @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. + */ +const stringToByteArray$1 = function (str) { + // TODO(user): Use native implementations if/when available + const out = []; + let p = 0; + for (let i = 0; i < str.length; i++) { + let c = str.charCodeAt(i); + if (c < 128) { + out[p++] = c; + } + else if (c < 2048) { + out[p++] = (c >> 6) | 192; + out[p++] = (c & 63) | 128; + } + else if ((c & 0xfc00) === 0xd800 && + i + 1 < str.length && + (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00) { + // Surrogate Pair + c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff); + out[p++] = (c >> 18) | 240; + out[p++] = ((c >> 12) & 63) | 128; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } + else { + out[p++] = (c >> 12) | 224; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } + } + return out; +}; +/** + * Turns an array of numbers into the string given by the concatenation of the + * characters to which the numbers correspond. + * @param bytes Array of numbers representing characters. + * @return Stringification of the array. + */ +const byteArrayToString = function (bytes) { + // TODO(user): Use native implementations if/when available + const out = []; + let pos = 0, c = 0; + while (pos < bytes.length) { + const c1 = bytes[pos++]; + if (c1 < 128) { + out[c++] = String.fromCharCode(c1); + } + else if (c1 > 191 && c1 < 224) { + const c2 = bytes[pos++]; + out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); + } + else if (c1 > 239 && c1 < 365) { + // Surrogate Pair + const c2 = bytes[pos++]; + const c3 = bytes[pos++]; + const c4 = bytes[pos++]; + const u = (((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) - + 0x10000; + out[c++] = String.fromCharCode(0xd800 + (u >> 10)); + out[c++] = String.fromCharCode(0xdc00 + (u & 1023)); + } + else { + const c2 = bytes[pos++]; + const c3 = bytes[pos++]; + out[c++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + } + } + return out.join(''); +}; +// We define it as an object literal instead of a class because a class compiled down to es5 can't +// be treeshaked. https://github.com/rollup/rollup/issues/1691 +// Static lookup maps, lazily populated by init_() +// TODO(dlarocque): Define this as a class, since we no longer target ES5. +const base64 = { + /** + * Maps bytes to characters. + */ + byteToCharMap_: null, + /** + * Maps characters to bytes. + */ + charToByteMap_: null, + /** + * Maps bytes to websafe characters. + * @private + */ + byteToCharMapWebSafe_: null, + /** + * Maps websafe characters to bytes. + * @private + */ + charToByteMapWebSafe_: null, + /** + * Our default alphabet, shared between + * ENCODED_VALS and ENCODED_VALS_WEBSAFE + */ + ENCODED_VALS_BASE: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789', + /** + * Our default alphabet. Value 64 (=) is special; it means "nothing." + */ + get ENCODED_VALS() { + return this.ENCODED_VALS_BASE + '+/='; + }, + /** + * Our websafe alphabet. + */ + get ENCODED_VALS_WEBSAFE() { + return this.ENCODED_VALS_BASE + '-_.'; + }, + /** + * Whether this browser supports the atob and btoa functions. This extension + * started at Mozilla but is now implemented by many browsers. We use the + * ASSUME_* variables to avoid pulling in the full useragent detection library + * but still allowing the standard per-browser compilations. + * + */ + HAS_NATIVE_SUPPORT: typeof atob === 'function', + /** + * Base64-encode an array of bytes. + * + * @param input An array of bytes (numbers with + * value in [0, 255]) to encode. + * @param webSafe Boolean indicating we should use the + * alternative alphabet. + * @return The base64 encoded string. + */ + encodeByteArray(input, webSafe) { + if (!Array.isArray(input)) { + throw Error('encodeByteArray takes an array as a parameter'); + } + this.init_(); + const byteToCharMap = webSafe + ? this.byteToCharMapWebSafe_ + : this.byteToCharMap_; + const output = []; + for (let i = 0; i < input.length; i += 3) { + const byte1 = input[i]; + const haveByte2 = i + 1 < input.length; + const byte2 = haveByte2 ? input[i + 1] : 0; + const haveByte3 = i + 2 < input.length; + const byte3 = haveByte3 ? input[i + 2] : 0; + const outByte1 = byte1 >> 2; + const outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4); + let outByte3 = ((byte2 & 0x0f) << 2) | (byte3 >> 6); + let outByte4 = byte3 & 0x3f; + if (!haveByte3) { + outByte4 = 64; + if (!haveByte2) { + outByte3 = 64; + } + } + output.push(byteToCharMap[outByte1], byteToCharMap[outByte2], byteToCharMap[outByte3], byteToCharMap[outByte4]); + } + return output.join(''); + }, + /** + * Base64-encode a string. + * + * @param input A string to encode. + * @param webSafe If true, we should use the + * alternative alphabet. + * @return The base64 encoded string. + */ + encodeString(input, webSafe) { + // Shortcut for Mozilla browsers that implement + // a native base64 encoder in the form of "btoa/atob" + if (this.HAS_NATIVE_SUPPORT && !webSafe) { + return btoa(input); + } + return this.encodeByteArray(stringToByteArray$1(input), webSafe); + }, + /** + * Base64-decode a string. + * + * @param input to decode. + * @param webSafe True if we should use the + * alternative alphabet. + * @return string representing the decoded value. + */ + decodeString(input, webSafe) { + // Shortcut for Mozilla browsers that implement + // a native base64 encoder in the form of "btoa/atob" + if (this.HAS_NATIVE_SUPPORT && !webSafe) { + return atob(input); + } + return byteArrayToString(this.decodeStringToByteArray(input, webSafe)); + }, + /** + * Base64-decode a string. + * + * In base-64 decoding, groups of four characters are converted into three + * bytes. If the encoder did not apply padding, the input length may not + * be a multiple of 4. + * + * In this case, the last group will have fewer than 4 characters, and + * padding will be inferred. If the group has one or two characters, it decodes + * to one byte. If the group has three characters, it decodes to two bytes. + * + * @param input Input to decode. + * @param webSafe True if we should use the web-safe alphabet. + * @return bytes representing the decoded value. + */ + decodeStringToByteArray(input, webSafe) { + this.init_(); + const charToByteMap = webSafe + ? this.charToByteMapWebSafe_ + : this.charToByteMap_; + const output = []; + for (let i = 0; i < input.length;) { + const byte1 = charToByteMap[input.charAt(i++)]; + const haveByte2 = i < input.length; + const byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0; + ++i; + const haveByte3 = i < input.length; + const byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 64; + ++i; + const haveByte4 = i < input.length; + const byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64; + ++i; + if (byte1 == null || byte2 == null || byte3 == null || byte4 == null) { + throw new DecodeBase64StringError(); + } + const outByte1 = (byte1 << 2) | (byte2 >> 4); + output.push(outByte1); + if (byte3 !== 64) { + const outByte2 = ((byte2 << 4) & 0xf0) | (byte3 >> 2); + output.push(outByte2); + if (byte4 !== 64) { + const outByte3 = ((byte3 << 6) & 0xc0) | byte4; + output.push(outByte3); + } + } + } + return output; + }, + /** + * Lazy static initialization function. Called before + * accessing any of the static map variables. + * @private + */ + init_() { + if (!this.byteToCharMap_) { + this.byteToCharMap_ = {}; + this.charToByteMap_ = {}; + this.byteToCharMapWebSafe_ = {}; + this.charToByteMapWebSafe_ = {}; + // We want quick mappings back and forth, so we precompute two maps. + for (let i = 0; i < this.ENCODED_VALS.length; i++) { + this.byteToCharMap_[i] = this.ENCODED_VALS.charAt(i); + this.charToByteMap_[this.byteToCharMap_[i]] = i; + this.byteToCharMapWebSafe_[i] = this.ENCODED_VALS_WEBSAFE.charAt(i); + this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i; + // Be forgiving when decoding and correctly decode both encodings. + if (i >= this.ENCODED_VALS_BASE.length) { + this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(i)] = i; + this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(i)] = i; + } + } + } + } +}; +/** + * An error encountered while decoding base64 string. + */ +class DecodeBase64StringError extends Error { + constructor() { + super(...arguments); + this.name = 'DecodeBase64StringError'; + } +} +/** + * URL-safe base64 encoding + */ +const base64Encode = function (str) { + const utf8Bytes = stringToByteArray$1(str); + return base64.encodeByteArray(utf8Bytes, true); +}; +/** + * URL-safe base64 encoding (without "." padding in the end). + * e.g. Used in JSON Web Token (JWT) parts. + */ +const base64urlEncodeWithoutPadding = function (str) { + // Use base64url encoding and remove padding in the end (dot characters). + return base64Encode(str).replace(/\./g, ''); +}; +/** + * URL-safe base64 decoding + * + * NOTE: DO NOT use the global atob() function - it does NOT support the + * base64Url variant encoding. + * + * @param str To be decoded + * @return Decoded result, if possible + */ +const base64Decode = function (str) { + try { + return base64.decodeString(str, true); + } + catch (e) { + console.error('base64Decode failed: ', e); + } + return null; +}; +/** + * @license + * Copyright 2022 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. + */ +/** + * Polyfill for `globalThis` object. + * @returns the `globalThis` object for the given environment. + * @public + */ +function getGlobal() { + if (typeof self !== 'undefined') { + return self; + } + if (typeof window !== 'undefined') { + return window; + } + if (typeof global !== 'undefined') { + return global; + } + throw new Error('Unable to locate global object.'); +} +/** + * @license + * Copyright 2022 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 getDefaultsFromGlobal = () => getGlobal().__FIREBASE_DEFAULTS__; +/** + * Attempt to read defaults from a JSON string provided to + * process(.)env(.)__FIREBASE_DEFAULTS__ or a JSON file whose path is in + * process(.)env(.)__FIREBASE_DEFAULTS_PATH__ + * The dots are in parens because certain compilers (Vite?) cannot + * handle seeing that variable in comments. + * See https://github.com/firebase/firebase-js-sdk/issues/6838 + */ +const getDefaultsFromEnvVariable = () => { + if (typeof process === 'undefined' || typeof process.env === 'undefined') { + return; + } + const defaultsJsonString = process.env.__FIREBASE_DEFAULTS__; + if (defaultsJsonString) { + return JSON.parse(defaultsJsonString); + } +}; +const getDefaultsFromCookie = () => { + if (typeof document === 'undefined') { + return; + } + let match; + try { + match = document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/); + } + catch (e) { + // Some environments such as Angular Universal SSR have a + // `document` object but error on accessing `document.cookie`. + return; + } + const decoded = match && base64Decode(match[1]); + return decoded && JSON.parse(decoded); +}; +/** + * Get the __FIREBASE_DEFAULTS__ object. It checks in order: + * (1) if such an object exists as a property of `globalThis` + * (2) if such an object was provided on a shell environment variable + * (3) if such an object exists in a cookie + * @public + */ +const getDefaults = () => { + try { + return (getDefaultsFromPostinstall() || + getDefaultsFromGlobal() || + getDefaultsFromEnvVariable() || + getDefaultsFromCookie()); + } + catch (e) { + /** + * Catch-all for being unable to get __FIREBASE_DEFAULTS__ due + * to any environment case we have not accounted for. Log to + * info instead of swallowing so we can find these unknown cases + * and add paths for them if needed. + */ + console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${e}`); + return; + } +}; +/** + * Returns Firebase app config stored in the __FIREBASE_DEFAULTS__ object. + * @public + */ +const getDefaultAppConfig = () => getDefaults()?.config; +/** + * @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. + */ +class Deferred { + constructor() { + this.reject = () => { }; + this.resolve = () => { }; + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + /** + * Our API internals are not promisified and cannot because our callback APIs have subtle expectations around + * invoking promises inline, which Promises are forbidden to do. This method accepts an optional node-style callback + * and returns a node-style callback which will resolve or reject the Deferred's promise. + */ + wrapCallback(callback) { + return (error, value) => { + if (error) { + this.reject(error); + } + else { + this.resolve(value); + } + if (typeof callback === 'function') { + // Attaching noop handler just in case developer wasn't expecting + // promises + this.promise.catch(() => { }); + // Some of our callbacks don't expect a value and our own tests + // assert that the parameter length is 1 + if (callback.length === 1) { + callback(error); + } + else { + callback(error, value); + } + } + }; + } +} +/** + * Detect Browser Environment. + * Note: This will return true for certain test frameworks that are incompletely + * mimicking a browser, and should not lead to assuming all browser APIs are + * available. + */ +function isBrowser() { + return typeof window !== 'undefined' || isWebWorker(); +} +/** + * Detect Web Worker context. + */ +function isWebWorker() { + return (typeof WorkerGlobalScope !== 'undefined' && + typeof self !== 'undefined' && + self instanceof WorkerGlobalScope); +} +/** + * This method checks if indexedDB is supported by current browser/service worker context + * @return true if indexedDB is supported by current browser/service worker context + */ +function isIndexedDBAvailable() { + try { + return typeof indexedDB === 'object'; + } + catch (e) { + return false; + } +} +/** + * This method validates browser/sw context for indexedDB by opening a dummy indexedDB database and reject + * if errors occur during the database open operation. + * + * @throws exception if current browser/sw context can't run idb.open (ex: Safari iframe, Firefox + * private browsing) + */ +function validateIndexedDBOpenable() { + return new Promise((resolve, reject) => { + try { + let preExist = true; + const DB_CHECK_NAME = 'validate-browser-context-for-indexeddb-analytics-module'; + const request = self.indexedDB.open(DB_CHECK_NAME); + request.onsuccess = () => { + request.result.close(); + // delete database only when it doesn't pre-exist + if (!preExist) { + self.indexedDB.deleteDatabase(DB_CHECK_NAME); + } + resolve(true); + }; + request.onupgradeneeded = () => { + preExist = false; + }; + request.onerror = () => { + reject(request.error?.message || ''); + }; + } + catch (error) { + reject(error); + } + }); +} +/** + * @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. + */ +/** + * @fileoverview Standardized Firebase Error. + * + * Usage: + * + * // TypeScript string literals for type-safe codes + * type Err = + * 'unknown' | + * 'object-not-found' + * ; + * + * // Closure enum for type-safe error codes + * // at-enum {string} + * var Err = { + * UNKNOWN: 'unknown', + * OBJECT_NOT_FOUND: 'object-not-found', + * } + * + * let errors: Map<Err, string> = { + * 'generic-error': "Unknown error", + * 'file-not-found': "Could not find file: {$file}", + * }; + * + * // Type-safe function - must pass a valid error code as param. + * let error = new ErrorFactory<Err>('service', 'Service', errors); + * + * ... + * throw error.create(Err.GENERIC); + * ... + * throw error.create(Err.FILE_NOT_FOUND, {'file': fileName}); + * ... + * // Service: Could not file file: foo.txt (service/file-not-found). + * + * catch (e) { + * assert(e.message === "Could not find file: foo.txt."); + * if ((e as FirebaseError)?.code === 'service/file-not-found') { + * console.log("Could not read file: " + e['file']); + * } + * } + */ +const ERROR_NAME = 'FirebaseError'; +// Based on code from: +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types +class FirebaseError extends Error { + constructor( + /** The error code for this error. */ + code, message, + /** Custom data for this error. */ + customData) { + super(message); + this.code = code; + this.customData = customData; + /** The custom name for all FirebaseErrors. */ + this.name = ERROR_NAME; + // Fix For ES5 + // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work + // TODO(dlarocque): Replace this with `new.target`: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget + // which we can now use since we no longer target ES5. + Object.setPrototypeOf(this, FirebaseError.prototype); + // Maintains proper stack trace for where our error was thrown. + // Only available on V8. + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ErrorFactory.prototype.create); + } + } +} +class ErrorFactory { + constructor(service, serviceName, errors) { + this.service = service; + this.serviceName = serviceName; + this.errors = errors; + } + create(code, ...data) { + const customData = data[0] || {}; + const fullCode = `${this.service}/${code}`; + const template = this.errors[code]; + const message = template ? replaceTemplate(template, customData) : 'Error'; + // Service Name: Error message (service/code). + const fullMessage = `${this.serviceName}: ${message} (${fullCode}).`; + const error = new FirebaseError(fullCode, fullMessage, customData); + return error; + } +} +function replaceTemplate(template, data) { + return template.replace(PATTERN, (_, key) => { + const value = data[key]; + return value != null ? String(value) : `<${key}?>`; + }); +} +const PATTERN = /\{\$([^}]+)}/g; +/** + * Deep equal two objects. Support Arrays and Objects. + */ +function deepEqual(a, b) { + if (a === b) { + return true; + } + const aKeys = Object.keys(a); + const bKeys = Object.keys(b); + for (const k of aKeys) { + if (!bKeys.includes(k)) { + return false; + } + const aProp = a[k]; + const bProp = b[k]; + if (isObject(aProp) && isObject(bProp)) { + if (!deepEqual(aProp, bProp)) { + return false; + } + } + else if (aProp !== bProp) { + return false; + } + } + for (const k of bKeys) { + if (!aKeys.includes(k)) { + return false; + } + } + return true; +} +function isObject(thing) { + return thing !== null && typeof thing === 'object'; +} + +/** + * Component for service name T, e.g. `auth`, `auth-internal` + */ +class Component { + /** + * + * @param name The public service name, e.g. app, auth, firestore, database + * @param instanceFactory Service factory responsible for creating the public interface + * @param type whether the service provided by the component is public or private + */ + constructor(name, instanceFactory, type) { + this.name = name; + this.instanceFactory = instanceFactory; + this.type = type; + this.multipleInstances = false; + /** + * Properties to be added to the service namespace + */ + this.serviceProps = {}; + this.instantiationMode = "LAZY" /* InstantiationMode.LAZY */; + this.onInstanceCreated = null; + } + setInstantiationMode(mode) { + this.instantiationMode = mode; + return this; + } + setMultipleInstances(multipleInstances) { + this.multipleInstances = multipleInstances; + return this; + } + setServiceProps(props) { + this.serviceProps = props; + return this; + } + setInstanceCreatedCallback(callback) { + this.onInstanceCreated = callback; + return this; + } +} +/** + * @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 DEFAULT_ENTRY_NAME$1 = '[DEFAULT]'; +/** + * @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. + */ +/** + * Provider for instance for service name T, e.g. 'auth', 'auth-internal' + * NameServiceMapping[T] is an alias for the type of the instance + */ +class Provider { + constructor(name, container) { + this.name = name; + this.container = container; + this.component = null; + this.instances = new Map(); + this.instancesDeferred = new Map(); + this.instancesOptions = new Map(); + this.onInitCallbacks = new Map(); + } + /** + * @param identifier A provider can provide multiple instances of a service + * if this.component.multipleInstances is true. + */ + get(identifier) { + // if multipleInstances is not supported, use the default name + const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier); + if (!this.instancesDeferred.has(normalizedIdentifier)) { + const deferred = new Deferred(); + this.instancesDeferred.set(normalizedIdentifier, deferred); + if (this.isInitialized(normalizedIdentifier) || + this.shouldAutoInitialize()) { + // initialize the service if it can be auto-initialized + try { + const instance = this.getOrInitializeService({ + instanceIdentifier: normalizedIdentifier + }); + if (instance) { + deferred.resolve(instance); + } + } + catch (e) { + // when the instance factory throws an exception during get(), it should not cause + // a fatal error. We just return the unresolved promise in this case. + } + } + } + return this.instancesDeferred.get(normalizedIdentifier).promise; + } + getImmediate(options) { + // if multipleInstances is not supported, use the default name + const normalizedIdentifier = this.normalizeInstanceIdentifier(options?.identifier); + const optional = options?.optional ?? false; + if (this.isInitialized(normalizedIdentifier) || + this.shouldAutoInitialize()) { + try { + return this.getOrInitializeService({ + instanceIdentifier: normalizedIdentifier + }); + } + catch (e) { + if (optional) { + return null; + } + else { + throw e; + } + } + } + else { + // In case a component is not initialized and should/cannot be auto-initialized at the moment, return null if the optional flag is set, or throw + if (optional) { + return null; + } + else { + throw Error(`Service ${this.name} is not available`); + } + } + } + getComponent() { + return this.component; + } + setComponent(component) { + if (component.name !== this.name) { + throw Error(`Mismatching Component ${component.name} for Provider ${this.name}.`); + } + if (this.component) { + throw Error(`Component for ${this.name} has already been provided`); + } + this.component = component; + // return early without attempting to initialize the component if the component requires explicit initialization (calling `Provider.initialize()`) + if (!this.shouldAutoInitialize()) { + return; + } + // if the service is eager, initialize the default instance + if (isComponentEager(component)) { + try { + this.getOrInitializeService({ instanceIdentifier: DEFAULT_ENTRY_NAME$1 }); + } + catch (e) { + // when the instance factory for an eager Component throws an exception during the eager + // initialization, it should not cause a fatal error. + // TODO: Investigate if we need to make it configurable, because some component may want to cause + // a fatal error in this case? + } + } + // Create service instances for the pending promises and resolve them + // NOTE: if this.multipleInstances is false, only the default instance will be created + // and all promises with resolve with it regardless of the identifier. + for (const [instanceIdentifier, instanceDeferred] of this.instancesDeferred.entries()) { + const normalizedIdentifier = this.normalizeInstanceIdentifier(instanceIdentifier); + try { + // `getOrInitializeService()` should always return a valid instance since a component is guaranteed. use ! to make typescript happy. + const instance = this.getOrInitializeService({ + instanceIdentifier: normalizedIdentifier + }); + instanceDeferred.resolve(instance); + } + catch (e) { + // when the instance factory throws an exception, it should not cause + // a fatal error. We just leave the promise unresolved. + } + } + } + clearInstance(identifier = DEFAULT_ENTRY_NAME$1) { + this.instancesDeferred.delete(identifier); + this.instancesOptions.delete(identifier); + this.instances.delete(identifier); + } + // app.delete() will call this method on every provider to delete the services + // TODO: should we mark the provider as deleted? + async delete() { + const services = Array.from(this.instances.values()); + await Promise.all([ + ...services + .filter(service => 'INTERNAL' in service) // legacy services + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map(service => service.INTERNAL.delete()), + ...services + .filter(service => '_delete' in service) // modularized services + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map(service => service._delete()) + ]); + } + isComponentSet() { + return this.component != null; + } + isInitialized(identifier = DEFAULT_ENTRY_NAME$1) { + return this.instances.has(identifier); + } + getOptions(identifier = DEFAULT_ENTRY_NAME$1) { + return this.instancesOptions.get(identifier) || {}; + } + initialize(opts = {}) { + const { options = {} } = opts; + const normalizedIdentifier = this.normalizeInstanceIdentifier(opts.instanceIdentifier); + if (this.isInitialized(normalizedIdentifier)) { + throw Error(`${this.name}(${normalizedIdentifier}) has already been initialized`); + } + if (!this.isComponentSet()) { + throw Error(`Component ${this.name} has not been registered yet`); + } + const instance = this.getOrInitializeService({ + instanceIdentifier: normalizedIdentifier, + options + }); + // resolve any pending promise waiting for the service instance + for (const [instanceIdentifier, instanceDeferred] of this.instancesDeferred.entries()) { + const normalizedDeferredIdentifier = this.normalizeInstanceIdentifier(instanceIdentifier); + if (normalizedIdentifier === normalizedDeferredIdentifier) { + instanceDeferred.resolve(instance); + } + } + return instance; + } + /** + * + * @param callback - a function that will be invoked after the provider has been initialized by calling provider.initialize(). + * The function is invoked SYNCHRONOUSLY, so it should not execute any longrunning tasks in order to not block the program. + * + * @param identifier An optional instance identifier + * @returns a function to unregister the callback + */ + onInit(callback, identifier) { + const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier); + const existingCallbacks = this.onInitCallbacks.get(normalizedIdentifier) ?? + new Set(); + existingCallbacks.add(callback); + this.onInitCallbacks.set(normalizedIdentifier, existingCallbacks); + const existingInstance = this.instances.get(normalizedIdentifier); + if (existingInstance) { + callback(existingInstance, normalizedIdentifier); + } + return () => { + existingCallbacks.delete(callback); + }; + } + /** + * Invoke onInit callbacks synchronously + * @param instance the service instance` + */ + invokeOnInitCallbacks(instance, identifier) { + const callbacks = this.onInitCallbacks.get(identifier); + if (!callbacks) { + return; + } + for (const callback of callbacks) { + try { + callback(instance, identifier); + } + catch { + // ignore errors in the onInit callback + } + } + } + getOrInitializeService({ instanceIdentifier, options = {} }) { + let instance = this.instances.get(instanceIdentifier); + if (!instance && this.component) { + instance = this.component.instanceFactory(this.container, { + instanceIdentifier: normalizeIdentifierForFactory(instanceIdentifier), + options + }); + this.instances.set(instanceIdentifier, instance); + this.instancesOptions.set(instanceIdentifier, options); + /** + * Invoke onInit listeners. + * Note this.component.onInstanceCreated is different, which is used by the component creator, + * while onInit listeners are registered by consumers of the provider. + */ + this.invokeOnInitCallbacks(instance, instanceIdentifier); + /** + * Order is important + * onInstanceCreated() should be called after this.instances.set(instanceIdentifier, instance); which + * makes `isInitialized()` return true. + */ + if (this.component.onInstanceCreated) { + try { + this.component.onInstanceCreated(this.container, instanceIdentifier, instance); + } + catch { + // ignore errors in the onInstanceCreatedCallback + } + } + } + return instance || null; + } + normalizeInstanceIdentifier(identifier = DEFAULT_ENTRY_NAME$1) { + if (this.component) { + return this.component.multipleInstances ? identifier : DEFAULT_ENTRY_NAME$1; + } + else { + return identifier; // assume multiple instances are supported before the component is provided. + } + } + shouldAutoInitialize() { + return (!!this.component && + this.component.instantiationMode !== "EXPLICIT" /* InstantiationMode.EXPLICIT */); + } +} +// undefined should be passed to the service factory for the default instance +function normalizeIdentifierForFactory(identifier) { + return identifier === DEFAULT_ENTRY_NAME$1 ? undefined : identifier; +} +function isComponentEager(component) { + return component.instantiationMode === "EAGER" /* InstantiationMode.EAGER */; +} +/** + * @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. + */ +/** + * ComponentContainer that provides Providers for service name T, e.g. `auth`, `auth-internal` + */ +class ComponentContainer { + constructor(name) { + this.name = name; + this.providers = new Map(); + } + /** + * + * @param component Component being added + * @param overwrite When a component with the same name has already been registered, + * if overwrite is true: overwrite the existing component with the new component and create a new + * provider with the new component. It can be useful in tests where you want to use different mocks + * for different tests. + * if overwrite is false: throw an exception + */ + addComponent(component) { + const provider = this.getProvider(component.name); + if (provider.isComponentSet()) { + throw new Error(`Component ${component.name} has already been registered with ${this.name}`); + } + provider.setComponent(component); + } + addOrOverwriteComponent(component) { + const provider = this.getProvider(component.name); + if (provider.isComponentSet()) { + // delete the existing provider from the container, so we can register the new component + this.providers.delete(component.name); + } + this.addComponent(component); + } + /** + * getProvider provides a type safe interface where it can only be called with a field name + * present in NameServiceMapping interface. + * + * Firebase SDKs providing services should extend NameServiceMapping interface to register + * themselves. + */ + getProvider(name) { + if (this.providers.has(name)) { + return this.providers.get(name); + } + // create a Provider for a service that hasn't registered with Firebase + const provider = new Provider(name, this); + this.providers.set(name, provider); + return provider; + } + getProviders() { + return Array.from(this.providers.values()); + } +} + +/** + * @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. + */ +/** + * A container for all of the Logger instances + */ +const instances = []; +/** + * The JS SDK supports 5 log levels and also allows a user the ability to + * silence the logs altogether. + * + * The order is a follows: + * DEBUG < VERBOSE < INFO < WARN < ERROR + * + * All of the log types above the current log level will be captured (i.e. if + * you set the log level to `INFO`, errors will still be logged, but `DEBUG` and + * `VERBOSE` logs will not) + */ +var LogLevel; +(function (LogLevel) { + LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG"; + LogLevel[LogLevel["VERBOSE"] = 1] = "VERBOSE"; + LogLevel[LogLevel["INFO"] = 2] = "INFO"; + LogLevel[LogLevel["WARN"] = 3] = "WARN"; + LogLevel[LogLevel["ERROR"] = 4] = "ERROR"; + LogLevel[LogLevel["SILENT"] = 5] = "SILENT"; +})(LogLevel || (LogLevel = {})); +const levelStringToEnum = { + 'debug': LogLevel.DEBUG, + 'verbose': LogLevel.VERBOSE, + 'info': LogLevel.INFO, + 'warn': LogLevel.WARN, + 'error': LogLevel.ERROR, + 'silent': LogLevel.SILENT +}; +/** + * The default log level + */ +const defaultLogLevel = LogLevel.INFO; +/** + * By default, `console.debug` is not displayed in the developer console (in + * chrome). To avoid forcing users to have to opt-in to these logs twice + * (i.e. once for firebase, and once in the console), we are sending `DEBUG` + * logs to the `console.log` function. + */ +const ConsoleMethod = { + [LogLevel.DEBUG]: 'log', + [LogLevel.VERBOSE]: 'log', + [LogLevel.INFO]: 'info', + [LogLevel.WARN]: 'warn', + [LogLevel.ERROR]: 'error' +}; +/** + * The default log handler will forward DEBUG, VERBOSE, INFO, WARN, and ERROR + * messages on to their corresponding console counterparts (if the log method + * is supported by the current log level) + */ +const defaultLogHandler = (instance, logType, ...args) => { + if (logType < instance.logLevel) { + return; + } + const now = new Date().toISOString(); + const method = ConsoleMethod[logType]; + if (method) { + console[method](`[${now}] ${instance.name}:`, ...args); + } + else { + throw new Error(`Attempted to log a message with an invalid logType (value: ${logType})`); + } +}; +class Logger { + /** + * Gives you an instance of a Logger to capture messages according to + * Firebase's logging scheme. + * + * @param name The name that the logs will be associated with + */ + constructor(name) { + this.name = name; + /** + * The log level of the given Logger instance. + */ + this._logLevel = defaultLogLevel; + /** + * The main (internal) log handler for the Logger instance. + * Can be set to a new function in internal package code but not by user. + */ + this._logHandler = defaultLogHandler; + /** + * The optional, additional, user-defined log handler for the Logger instance. + */ + this._userLogHandler = null; + /** + * Capture the current instance for later use + */ + instances.push(this); + } + get logLevel() { + return this._logLevel; + } + set logLevel(val) { + if (!(val in LogLevel)) { + throw new TypeError(`Invalid value "${val}" assigned to \`logLevel\``); + } + this._logLevel = val; + } + // Workaround for setter/getter having to be the same type. + setLogLevel(val) { + this._logLevel = typeof val === 'string' ? levelStringToEnum[val] : val; + } + get logHandler() { + return this._logHandler; + } + set logHandler(val) { + if (typeof val !== 'function') { + throw new TypeError('Value assigned to `logHandler` must be a function'); + } + this._logHandler = val; + } + get userLogHandler() { + return this._userLogHandler; + } + set userLogHandler(val) { + this._userLogHandler = val; + } + /** + * The functions below are all based on the `console` interface + */ + debug(...args) { + this._userLogHandler && this._userLogHandler(this, LogLevel.DEBUG, ...args); + this._logHandler(this, LogLevel.DEBUG, ...args); + } + log(...args) { + this._userLogHandler && + this._userLogHandler(this, LogLevel.VERBOSE, ...args); + this._logHandler(this, LogLevel.VERBOSE, ...args); + } + info(...args) { + this._userLogHandler && this._userLogHandler(this, LogLevel.INFO, ...args); + this._logHandler(this, LogLevel.INFO, ...args); + } + warn(...args) { + this._userLogHandler && this._userLogHandler(this, LogLevel.WARN, ...args); + this._logHandler(this, LogLevel.WARN, ...args); + } + error(...args) { + this._userLogHandler && this._userLogHandler(this, LogLevel.ERROR, ...args); + this._logHandler(this, LogLevel.ERROR, ...args); + } +} +function setLogLevel$1(level) { + instances.forEach(inst => { + inst.setLogLevel(level); + }); +} +function setUserLogHandler(logCallback, options) { + for (const instance of instances) { + let customLogLevel = null; + if (options && options.level) { + customLogLevel = levelStringToEnum[options.level]; + } + if (logCallback === null) { + instance.userLogHandler = null; + } + else { + instance.userLogHandler = (instance, level, ...args) => { + const message = args + .map(arg => { + if (arg == null) { + return null; + } + else if (typeof arg === 'string') { + return arg; + } + else if (typeof arg === 'number' || typeof arg === 'boolean') { + return arg.toString(); + } + else if (arg instanceof Error) { + return arg.message; + } + else { + try { + return JSON.stringify(arg); + } + catch (ignored) { + return null; + } + } + }) + .filter(arg => arg) + .join(' '); + if (level >= (customLogLevel ?? instance.logLevel)) { + logCallback({ + level: LogLevel[level].toLowerCase(), + message, + args, + type: instance.name + }); + } + }; + } + } +} + +const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c); +let idbProxyableTypes; +let cursorAdvanceMethods; +// This is a function to prevent it throwing up in node environments. +function getIdbProxyableTypes() { + return (idbProxyableTypes || + (idbProxyableTypes = [ + IDBDatabase, + IDBObjectStore, + IDBIndex, + IDBCursor, + IDBTransaction, + ])); +} +// This is a function to prevent it throwing up in node environments. +function getCursorAdvanceMethods() { + return (cursorAdvanceMethods || + (cursorAdvanceMethods = [ + IDBCursor.prototype.advance, + IDBCursor.prototype.continue, + IDBCursor.prototype.continuePrimaryKey, + ])); +} +const cursorRequestMap = new WeakMap(); +const transactionDoneMap = new WeakMap(); +const transactionStoreNamesMap = new WeakMap(); +const transformCache = new WeakMap(); +const reverseTransformCache = new WeakMap(); +function promisifyRequest(request) { + const promise = new Promise((resolve, reject) => { + const unlisten = () => { + request.removeEventListener('success', success); + request.removeEventListener('error', error); + }; + const success = () => { + resolve(wrap(request.result)); + unlisten(); + }; + const error = () => { + reject(request.error); + unlisten(); + }; + request.addEventListener('success', success); + request.addEventListener('error', error); + }); + promise + .then((value) => { + // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval + // (see wrapFunction). + if (value instanceof IDBCursor) { + cursorRequestMap.set(value, request); + } + // Catching to avoid "Uncaught Promise exceptions" + }) + .catch(() => { }); + // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This + // is because we create many promises from a single IDBRequest. + reverseTransformCache.set(promise, request); + return promise; +} +function cacheDonePromiseForTransaction(tx) { + // Early bail if we've already created a done promise for this transaction. + if (transactionDoneMap.has(tx)) + return; + const done = new Promise((resolve, reject) => { + const unlisten = () => { + tx.removeEventListener('complete', complete); + tx.removeEventListener('error', error); + tx.removeEventListener('abort', error); + }; + const complete = () => { + resolve(); + unlisten(); + }; + const error = () => { + reject(tx.error || new DOMException('AbortError', 'AbortError')); + unlisten(); + }; + tx.addEventListener('complete', complete); + tx.addEventListener('error', error); + tx.addEventListener('abort', error); + }); + // Cache it for later retrieval. + transactionDoneMap.set(tx, done); +} +let idbProxyTraps = { + get(target, prop, receiver) { + if (target instanceof IDBTransaction) { + // Special handling for transaction.done. + if (prop === 'done') + return transactionDoneMap.get(target); + // Polyfill for objectStoreNames because of Edge. + if (prop === 'objectStoreNames') { + return target.objectStoreNames || transactionStoreNamesMap.get(target); + } + // Make tx.store return the only store in the transaction, or undefined if there are many. + if (prop === 'store') { + return receiver.objectStoreNames[1] + ? undefined + : receiver.objectStore(receiver.objectStoreNames[0]); + } + } + // Else transform whatever we get back. + return wrap(target[prop]); + }, + set(target, prop, value) { + target[prop] = value; + return true; + }, + has(target, prop) { + if (target instanceof IDBTransaction && + (prop === 'done' || prop === 'store')) { + return true; + } + return prop in target; + }, +}; +function replaceTraps(callback) { + idbProxyTraps = callback(idbProxyTraps); +} +function wrapFunction(func) { + // Due to expected object equality (which is enforced by the caching in `wrap`), we + // only create one new func per func. + // Edge doesn't support objectStoreNames (booo), so we polyfill it here. + if (func === IDBDatabase.prototype.transaction && + !('objectStoreNames' in IDBTransaction.prototype)) { + return function (storeNames, ...args) { + const tx = func.call(unwrap(this), storeNames, ...args); + transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]); + return wrap(tx); + }; + } + // Cursor methods are special, as the behaviour is a little more different to standard IDB. In + // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the + // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense + // with real promises, so each advance methods returns a new promise for the cursor object, or + // undefined if the end of the cursor has been reached. + if (getCursorAdvanceMethods().includes(func)) { + return function (...args) { + // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use + // the original object. + func.apply(unwrap(this), args); + return wrap(cursorRequestMap.get(this)); + }; + } + return function (...args) { + // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use + // the original object. + return wrap(func.apply(unwrap(this), args)); + }; +} +function transformCachableValue(value) { + if (typeof value === 'function') + return wrapFunction(value); + // This doesn't return, it just creates a 'done' promise for the transaction, + // which is later returned for transaction.done (see idbObjectHandler). + if (value instanceof IDBTransaction) + cacheDonePromiseForTransaction(value); + if (instanceOfAny(value, getIdbProxyableTypes())) + return new Proxy(value, idbProxyTraps); + // Return the same value back if we're not going to transform it. + return value; +} +function wrap(value) { + // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because + // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached. + if (value instanceof IDBRequest) + return promisifyRequest(value); + // If we've already transformed this value before, reuse the transformed value. + // This is faster, but it also provides object equality. + if (transformCache.has(value)) + return transformCache.get(value); + const newValue = transformCachableValue(value); + // Not all types are transformed. + // These may be primitive types, so they can't be WeakMap keys. + if (newValue !== value) { + transformCache.set(value, newValue); + reverseTransformCache.set(newValue, value); + } + return newValue; +} +const unwrap = (value) => reverseTransformCache.get(value); + +/** + * Open a database. + * + * @param name Name of the database. + * @param version Schema version. + * @param callbacks Additional callbacks. + */ +function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) { + const request = indexedDB.open(name, version); + const openPromise = wrap(request); + if (upgrade) { + request.addEventListener('upgradeneeded', (event) => { + upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event); + }); + } + if (blocked) { + request.addEventListener('blocked', (event) => blocked( + // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405 + event.oldVersion, event.newVersion, event)); + } + openPromise + .then((db) => { + if (terminated) + db.addEventListener('close', () => terminated()); + if (blocking) { + db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event)); + } + }) + .catch(() => { }); + return openPromise; +} +const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count']; +const writeMethods = ['put', 'add', 'delete', 'clear']; +const cachedMethods = new Map(); +function getMethod(target, prop) { + if (!(target instanceof IDBDatabase && + !(prop in target) && + typeof prop === 'string')) { + return; + } + if (cachedMethods.get(prop)) + return cachedMethods.get(prop); + const targetFuncName = prop.replace(/FromIndex$/, ''); + const useIndex = prop !== targetFuncName; + const isWrite = writeMethods.includes(targetFuncName); + if ( + // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge. + !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || + !(isWrite || readMethods.includes(targetFuncName))) { + return; + } + const method = async function (storeName, ...args) { + // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :( + const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly'); + let target = tx.store; + if (useIndex) + target = target.index(args.shift()); + // Must reject if op rejects. + // If it's a write operation, must reject if tx.done rejects. + // Must reject with op rejection first. + // Must resolve with op value. + // Must handle both promises (no unhandled rejections) + return (await Promise.all([ + target[targetFuncName](...args), + isWrite && tx.done, + ]))[0]; + }; + cachedMethods.set(prop, method); + return method; +} +replaceTraps((oldTraps) => ({ + ...oldTraps, + get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver), + has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop), +})); + +/** + * @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 PlatformLoggerServiceImpl { + constructor(container) { + this.container = container; + } + // In initial implementation, this will be called by installations on + // auth token refresh, and installations will send this string. + getPlatformInfoString() { + const providers = this.container.getProviders(); + // Loop through providers and get library/version pairs from any that are + // version components. + return providers + .map(provider => { + if (isVersionServiceProvider(provider)) { + const service = provider.getImmediate(); + return `${service.library}/${service.version}`; + } + else { + return null; + } + }) + .filter(logString => logString) + .join(' '); + } +} +/** + * + * @param provider check if this provider provides a VersionService + * + * NOTE: Using Provider<'app-version'> is a hack to indicate that the provider + * provides VersionService. The provider is not necessarily a 'app-version' + * provider. + */ +function isVersionServiceProvider(provider) { + const component = provider.getComponent(); + return component?.type === "VERSION" /* ComponentType.VERSION */; +} +const name$q = "https://www.gstatic.com/firebasejs/12.5.0/firebase-app.js"; +const version$1 = "0.14.5"; +/** + * @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 logger = new Logger('https://www.gstatic.com/firebasejs/12.5.0/firebase-app.js'); +const name$p = "@firebase/app-compat"; +const name$o = "@firebase/analytics-compat"; +const name$n = "@firebase/analytics"; +const name$m = "@firebase/app-check-compat"; +const name$l = "@firebase/app-check"; +const name$k = "@firebase/auth"; +const name$j = "@firebase/auth-compat"; +const name$i = "@firebase/database"; +const name$h = "@firebase/data-connect"; +const name$g = "@firebase/database-compat"; +const name$f = "@firebase/functions"; +const name$e = "@firebase/functions-compat"; +const name$d = "@firebase/installations"; +const name$c = "@firebase/installations-compat"; +const name$b = "@firebase/messaging"; +const name$a = "@firebase/messaging-compat"; +const name$9 = "@firebase/performance"; +const name$8 = "@firebase/performance-compat"; +const name$7 = "@firebase/remote-config"; +const name$6 = "@firebase/remote-config-compat"; +const name$5 = "@firebase/storage"; +const name$4 = "@firebase/storage-compat"; +const name$3 = "@firebase/firestore"; +const name$2 = "@firebase/ai"; +const name$1 = "@firebase/firestore-compat"; +const name$r = "firebase"; +const version$2 = "12.5.0"; +/** + * @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. + */ +/** + * The default app name + * + * @internal + */ +const DEFAULT_ENTRY_NAME = '[DEFAULT]'; +const PLATFORM_LOG_STRING = { + [name$q]: 'fire-core', + [name$p]: 'fire-core-compat', + [name$n]: 'fire-analytics', + [name$o]: 'fire-analytics-compat', + [name$l]: 'fire-app-check', + [name$m]: 'fire-app-check-compat', + [name$k]: 'fire-auth', + [name$j]: 'fire-auth-compat', + [name$i]: 'fire-rtdb', + [name$h]: 'fire-data-connect', + [name$g]: 'fire-rtdb-compat', + [name$f]: 'fire-fn', + [name$e]: 'fire-fn-compat', + [name$d]: 'fire-iid', + [name$c]: 'fire-iid-compat', + [name$b]: 'fire-fcm', + [name$a]: 'fire-fcm-compat', + [name$9]: 'fire-perf', + [name$8]: 'fire-perf-compat', + [name$7]: 'fire-rc', + [name$6]: 'fire-rc-compat', + [name$5]: 'fire-gcs', + [name$4]: 'fire-gcs-compat', + [name$3]: 'fire-fst', + [name$1]: 'fire-fst-compat', + [name$2]: 'fire-vertex', + 'fire-js': 'fire-js', // Platform identifier for JS SDK. + [name$r]: 'fire-js-all' +}; +/** + * @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. + */ +/** + * @internal + */ +const _apps = new Map(); +/** + * @internal + */ +const _serverApps = new Map(); +/** + * Registered components. + * + * @internal + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const _components = new Map(); +/** + * @param component - the component being added to this app's container + * + * @internal + */ +function _addComponent(app, component) { + try { + app.container.addComponent(component); + } + catch (e) { + logger.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e); + } +} +/** + * + * @internal + */ +function _addOrOverwriteComponent(app, component) { + app.container.addOrOverwriteComponent(component); +} +/** + * + * @param component - the component to register + * @returns whether or not the component is registered successfully + * + * @internal + */ +function _registerComponent(component) { + const componentName = component.name; + if (_components.has(componentName)) { + logger.debug(`There were multiple attempts to register component ${componentName}.`); + return false; + } + _components.set(componentName, component); + // add the component to existing app instances + for (const app of _apps.values()) { + _addComponent(app, component); + } + for (const serverApp of _serverApps.values()) { + _addComponent(serverApp, component); + } + return true; +} +/** + * + * @param app - FirebaseApp instance + * @param name - service name + * + * @returns the provider for the service with the matching name + * + * @internal + */ +function _getProvider(app, name) { + const heartbeatController = app.container + .getProvider('heartbeat') + .getImmediate({ optional: true }); + if (heartbeatController) { + void heartbeatController.triggerHeartbeat(); + } + return app.container.getProvider(name); +} +/** + * + * @param app - FirebaseApp instance + * @param name - service name + * @param instanceIdentifier - service instance identifier in case the service supports multiple instances + * + * @internal + */ +function _removeServiceInstance(app, name, instanceIdentifier = DEFAULT_ENTRY_NAME) { + _getProvider(app, name).clearInstance(instanceIdentifier); +} +/** + * + * @param obj - an object of type FirebaseApp, FirebaseOptions or FirebaseAppSettings. + * + * @returns true if the provide object is of type FirebaseApp. + * + * @internal + */ +function _isFirebaseApp(obj) { + return obj.options !== undefined; +} +/** + * + * @param obj - an object of type FirebaseApp, FirebaseOptions or FirebaseAppSettings. + * + * @returns true if the provided object is of type FirebaseServerAppImpl. + * + * @internal + */ +function _isFirebaseServerAppSettings(obj) { + if (_isFirebaseApp(obj)) { + return false; + } + return ('authIdToken' in obj || + 'appCheckToken' in obj || + 'releaseOnDeref' in obj || + 'automaticDataCollectionEnabled' in obj); +} +/** + * + * @param obj - an object of type FirebaseApp. + * + * @returns true if the provided object is of type FirebaseServerAppImpl. + * + * @internal + */ +function _isFirebaseServerApp(obj) { + if (obj === null || obj === undefined) { + return false; + } + return obj.settings !== undefined; +} +/** + * Test only + * + * @internal + */ +function _clearComponents() { + _components.clear(); +} +/** + * @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 ERRORS = { + ["no-app" /* AppError.NO_APP */]: "No Firebase App '{$appName}' has been created - " + + 'call initializeApp() first', + ["bad-app-name" /* AppError.BAD_APP_NAME */]: "Illegal App name: '{$appName}'", + ["duplicate-app" /* AppError.DUPLICATE_APP */]: "Firebase App named '{$appName}' already exists with different options or config", + ["app-deleted" /* AppError.APP_DELETED */]: "Firebase App named '{$appName}' already deleted", + ["server-app-deleted" /* AppError.SERVER_APP_DELETED */]: 'Firebase Server App has been deleted', + ["no-options" /* AppError.NO_OPTIONS */]: 'Need to provide options, when not being deployed to hosting via source.', + ["invalid-app-argument" /* AppError.INVALID_APP_ARGUMENT */]: 'firebase.{$appName}() takes either no argument or a ' + + 'Firebase App instance.', + ["invalid-log-argument" /* AppError.INVALID_LOG_ARGUMENT */]: 'First argument to `onLog` must be null or a function.', + ["idb-open" /* AppError.IDB_OPEN */]: 'Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.', + ["idb-get" /* AppError.IDB_GET */]: 'Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.', + ["idb-set" /* AppError.IDB_WRITE */]: 'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.', + ["idb-delete" /* AppError.IDB_DELETE */]: 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.', + ["finalization-registry-not-supported" /* AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED */]: 'FirebaseServerApp deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry.', + ["invalid-server-app-environment" /* AppError.INVALID_SERVER_APP_ENVIRONMENT */]: 'FirebaseServerApp is not for use in browser environments.' +}; +const ERROR_FACTORY = new ErrorFactory('app', 'Firebase', ERRORS); +/** + * @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 FirebaseAppImpl { + constructor(options, config, container) { + this._isDeleted = false; + this._options = { ...options }; + this._config = { ...config }; + this._name = config.name; + this._automaticDataCollectionEnabled = + config.automaticDataCollectionEnabled; + this._container = container; + this.container.addComponent(new Component('app', () => this, "PUBLIC" /* ComponentType.PUBLIC */)); + } + get automaticDataCollectionEnabled() { + this.checkDestroyed(); + return this._automaticDataCollectionEnabled; + } + set automaticDataCollectionEnabled(val) { + this.checkDestroyed(); + this._automaticDataCollectionEnabled = val; + } + get name() { + this.checkDestroyed(); + return this._name; + } + get options() { + this.checkDestroyed(); + return this._options; + } + get config() { + this.checkDestroyed(); + return this._config; + } + get container() { + return this._container; + } + get isDeleted() { + return this._isDeleted; + } + set isDeleted(val) { + this._isDeleted = val; + } + /** + * This function will throw an Error if the App has already been deleted - + * use before performing API actions on the App. + */ + checkDestroyed() { + if (this.isDeleted) { + throw ERROR_FACTORY.create("app-deleted" /* AppError.APP_DELETED */, { appName: this._name }); + } + } +} +/** + * @license + * Copyright 2023 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. + */ +// Parse the token and check to see if the `exp` claim is in the future. +// Reports an error to the console if the token or claim could not be parsed, or if `exp` is in +// the past. +function validateTokenTTL(base64Token, tokenName) { + const secondPart = base64Decode(base64Token.split('.')[1]); + if (secondPart === null) { + console.error(`FirebaseServerApp ${tokenName} is invalid: second part could not be parsed.`); + return; + } + const expClaim = JSON.parse(secondPart).exp; + if (expClaim === undefined) { + console.error(`FirebaseServerApp ${tokenName} is invalid: expiration claim could not be parsed`); + return; + } + const exp = JSON.parse(secondPart).exp * 1000; + const now = new Date().getTime(); + const diff = exp - now; + if (diff <= 0) { + console.error(`FirebaseServerApp ${tokenName} is invalid: the token has expired.`); + } +} +class FirebaseServerAppImpl extends FirebaseAppImpl { + constructor(options, serverConfig, name, container) { + // Build configuration parameters for the FirebaseAppImpl base class. + const automaticDataCollectionEnabled = serverConfig.automaticDataCollectionEnabled !== undefined + ? serverConfig.automaticDataCollectionEnabled + : true; + // Create the FirebaseAppSettings object for the FirebaseAppImp constructor. + const config = { + name, + automaticDataCollectionEnabled + }; + if (options.apiKey !== undefined) { + // Construct the parent FirebaseAppImp object. + super(options, config, container); + } + else { + const appImpl = options; + super(appImpl.options, config, container); + } + // Now construct the data for the FirebaseServerAppImpl. + this._serverConfig = { + automaticDataCollectionEnabled, + ...serverConfig + }; + // Ensure that the current time is within the `authIdtoken` window of validity. + if (this._serverConfig.authIdToken) { + validateTokenTTL(this._serverConfig.authIdToken, 'authIdToken'); + } + // Ensure that the current time is within the `appCheckToken` window of validity. + if (this._serverConfig.appCheckToken) { + validateTokenTTL(this._serverConfig.appCheckToken, 'appCheckToken'); + } + this._finalizationRegistry = null; + if (typeof FinalizationRegistry !== 'undefined') { + this._finalizationRegistry = new FinalizationRegistry(() => { + this.automaticCleanup(); + }); + } + this._refCount = 0; + this.incRefCount(this._serverConfig.releaseOnDeref); + // Do not retain a hard reference to the dref object, otherwise the FinalizationRegistry + // will never trigger. + this._serverConfig.releaseOnDeref = undefined; + serverConfig.releaseOnDeref = undefined; + registerVersion(name$q, version$1, 'serverapp'); + } + toJSON() { + return undefined; + } + get refCount() { + return this._refCount; + } + // Increment the reference count of this server app. If an object is provided, register it + // with the finalization registry. + incRefCount(obj) { + if (this.isDeleted) { + return; + } + this._refCount++; + if (obj !== undefined && this._finalizationRegistry !== null) { + this._finalizationRegistry.register(obj, this); + } + } + // Decrement the reference count. + decRefCount() { + if (this.isDeleted) { + return 0; + } + return --this._refCount; + } + // Invoked by the FinalizationRegistry callback to note that this app should go through its + // reference counts and delete itself if no reference count remain. The coordinating logic that + // handles this is in deleteApp(...). + automaticCleanup() { + void deleteApp(this); + } + get settings() { + this.checkDestroyed(); + return this._serverConfig; + } + /** + * This function will throw an Error if the App has already been deleted - + * use before performing API actions on the App. + */ + checkDestroyed() { + if (this.isDeleted) { + throw ERROR_FACTORY.create("server-app-deleted" /* AppError.SERVER_APP_DELETED */); + } + } +} +/** + * @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. + */ +/** + * The current SDK version. + * + * @public + */ +const SDK_VERSION = version$2; +function initializeApp(_options, rawConfig = {}) { + let options = _options; + if (typeof rawConfig !== 'object') { + const name = rawConfig; + rawConfig = { name }; + } + const config = { + name: DEFAULT_ENTRY_NAME, + automaticDataCollectionEnabled: true, + ...rawConfig + }; + const name = config.name; + if (typeof name !== 'string' || !name) { + throw ERROR_FACTORY.create("bad-app-name" /* AppError.BAD_APP_NAME */, { + appName: String(name) + }); + } + options || (options = getDefaultAppConfig()); + if (!options) { + throw ERROR_FACTORY.create("no-options" /* AppError.NO_OPTIONS */); + } + const existingApp = _apps.get(name); + if (existingApp) { + // return the existing app if options and config deep equal the ones in the existing app. + if (deepEqual(options, existingApp.options) && + deepEqual(config, existingApp.config)) { + return existingApp; + } + else { + throw ERROR_FACTORY.create("duplicate-app" /* AppError.DUPLICATE_APP */, { appName: name }); + } + } + const container = new ComponentContainer(name); + for (const component of _components.values()) { + container.addComponent(component); + } + const newApp = new FirebaseAppImpl(options, config, container); + _apps.set(name, newApp); + return newApp; +} +function initializeServerApp(_options, _serverAppConfig = {}) { + if (isBrowser() && !isWebWorker()) { + // FirebaseServerApp isn't designed to be run in browsers. + throw ERROR_FACTORY.create("invalid-server-app-environment" /* AppError.INVALID_SERVER_APP_ENVIRONMENT */); + } + let firebaseOptions; + let serverAppSettings = _serverAppConfig || {}; + if (_options) { + if (_isFirebaseApp(_options)) { + firebaseOptions = _options.options; + } + else if (_isFirebaseServerAppSettings(_options)) { + serverAppSettings = _options; + } + else { + firebaseOptions = _options; + } + } + if (serverAppSettings.automaticDataCollectionEnabled === undefined) { + serverAppSettings.automaticDataCollectionEnabled = true; + } + firebaseOptions || (firebaseOptions = getDefaultAppConfig()); + if (!firebaseOptions) { + throw ERROR_FACTORY.create("no-options" /* AppError.NO_OPTIONS */); + } + // Build an app name based on a hash of the configuration options. + const nameObj = { + ...serverAppSettings, + ...firebaseOptions + }; + // However, Do not mangle the name based on releaseOnDeref, since it will vary between the + // construction of FirebaseServerApp instances. For example, if the object is the request headers. + if (nameObj.releaseOnDeref !== undefined) { + delete nameObj.releaseOnDeref; + } + const hashCode = (s) => { + return [...s].reduce((hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, 0); + }; + if (serverAppSettings.releaseOnDeref !== undefined) { + if (typeof FinalizationRegistry === 'undefined') { + throw ERROR_FACTORY.create("finalization-registry-not-supported" /* AppError.FINALIZATION_REGISTRY_NOT_SUPPORTED */, {}); + } + } + const nameString = '' + hashCode(JSON.stringify(nameObj)); + const existingApp = _serverApps.get(nameString); + if (existingApp) { + existingApp.incRefCount(serverAppSettings.releaseOnDeref); + return existingApp; + } + const container = new ComponentContainer(nameString); + for (const component of _components.values()) { + container.addComponent(component); + } + const newApp = new FirebaseServerAppImpl(firebaseOptions, serverAppSettings, nameString, container); + _serverApps.set(nameString, newApp); + return newApp; +} +/** + * Retrieves a {@link @firebase/app#FirebaseApp} instance. + * + * When called with no arguments, the default app is returned. When an app name + * is provided, the app corresponding to that name is returned. + * + * An exception is thrown if the app being retrieved has not yet been + * initialized. + * + * @example + * ```javascript + * // Return the default app + * const app = getApp(); + * ``` + * + * @example + * ```javascript + * // Return a named app + * const otherApp = getApp("otherApp"); + * ``` + * + * @param name - Optional name of the app to return. If no name is + * provided, the default is `"[DEFAULT]"`. + * + * @returns The app corresponding to the provided app name. + * If no app name is provided, the default app is returned. + * + * @public + */ +function getApp(name = DEFAULT_ENTRY_NAME) { + const app = _apps.get(name); + if (!app && name === DEFAULT_ENTRY_NAME && getDefaultAppConfig()) { + return initializeApp(); + } + if (!app) { + throw ERROR_FACTORY.create("no-app" /* AppError.NO_APP */, { appName: name }); + } + return app; +} +/** + * A (read-only) array of all initialized apps. + * @public + */ +function getApps() { + return Array.from(_apps.values()); +} +/** + * Renders this app unusable and frees the resources of all associated + * services. + * + * @example + * ```javascript + * deleteApp(app) + * .then(function() { + * console.log("App deleted successfully"); + * }) + * .catch(function(error) { + * console.log("Error deleting app:", error); + * }); + * ``` + * + * @public + */ +async function deleteApp(app) { + let cleanupProviders = false; + const name = app.name; + if (_apps.has(name)) { + cleanupProviders = true; + _apps.delete(name); + } + else if (_serverApps.has(name)) { + const firebaseServerApp = app; + if (firebaseServerApp.decRefCount() <= 0) { + _serverApps.delete(name); + cleanupProviders = true; + } + } + if (cleanupProviders) { + await Promise.all(app.container + .getProviders() + .map(provider => provider.delete())); + app.isDeleted = true; + } +} +/** + * Registers a library's name and version for platform logging purposes. + * @param library - Name of 1p or 3p library (e.g. firestore, angularfire) + * @param version - Current version of that library. + * @param variant - Bundle variant, e.g., node, rn, etc. + * + * @public + */ +function registerVersion(libraryKeyOrName, version, variant) { + // TODO: We can use this check to whitelist strings when/if we set up + // a good whitelist system. + let library = PLATFORM_LOG_STRING[libraryKeyOrName] ?? libraryKeyOrName; + if (variant) { + library += `-${variant}`; + } + const libraryMismatch = library.match(/\s|\//); + const versionMismatch = version.match(/\s|\//); + if (libraryMismatch || versionMismatch) { + const warning = [ + `Unable to register library "${library}" with version "${version}":` + ]; + if (libraryMismatch) { + warning.push(`library name "${library}" contains illegal characters (whitespace or "/")`); + } + if (libraryMismatch && versionMismatch) { + warning.push('and'); + } + if (versionMismatch) { + warning.push(`version name "${version}" contains illegal characters (whitespace or "/")`); + } + logger.warn(warning.join(' ')); + return; + } + _registerComponent(new Component(`${library}-version`, () => ({ library, version }), "VERSION" /* ComponentType.VERSION */)); +} +/** + * Sets log handler for all Firebase SDKs. + * @param logCallback - An optional custom log handler that executes user code whenever + * the Firebase SDK makes a logging call. + * + * @public + */ +function onLog(logCallback, options) { + if (logCallback !== null && typeof logCallback !== 'function') { + throw ERROR_FACTORY.create("invalid-log-argument" /* AppError.INVALID_LOG_ARGUMENT */); + } + setUserLogHandler(logCallback, options); +} +/** + * Sets log level for all Firebase SDKs. + * + * All of the log types above the current log level are captured (i.e. if + * you set the log level to `info`, errors are logged, but `debug` and + * `verbose` logs are not). + * + * @public + */ +function setLogLevel(logLevel) { + setLogLevel$1(logLevel); +} +/** + * @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 DB_NAME = 'firebase-heartbeat-database'; +const DB_VERSION = 1; +const STORE_NAME = 'firebase-heartbeat-store'; +let dbPromise = null; +function getDbPromise() { + if (!dbPromise) { + dbPromise = openDB(DB_NAME, DB_VERSION, { + upgrade: (db, oldVersion) => { + // We don't use 'break' in this switch statement, the fall-through + // behavior is what we want, because if there are multiple versions between + // the old version and the current version, we want ALL the migrations + // that correspond to those versions to run, not only the last one. + // eslint-disable-next-line default-case + switch (oldVersion) { + case 0: + try { + db.createObjectStore(STORE_NAME); + } + catch (e) { + // Safari/iOS browsers throw occasional exceptions on + // db.createObjectStore() that may be a bug. Avoid blocking + // the rest of the app functionality. + console.warn(e); + } + } + } + }).catch(e => { + throw ERROR_FACTORY.create("idb-open" /* AppError.IDB_OPEN */, { + originalErrorMessage: e.message + }); + }); + } + return dbPromise; +} +async function readHeartbeatsFromIndexedDB(app) { + try { + const db = await getDbPromise(); + const tx = db.transaction(STORE_NAME); + const result = await tx.objectStore(STORE_NAME).get(computeKey(app)); + // We already have the value but tx.done can throw, + // so we need to await it here to catch errors + await tx.done; + return result; + } + catch (e) { + if (e instanceof FirebaseError) { + logger.warn(e.message); + } + else { + const idbGetError = ERROR_FACTORY.create("idb-get" /* AppError.IDB_GET */, { + originalErrorMessage: e?.message + }); + logger.warn(idbGetError.message); + } + } +} +async function writeHeartbeatsToIndexedDB(app, heartbeatObject) { + try { + const db = await getDbPromise(); + const tx = db.transaction(STORE_NAME, 'readwrite'); + const objectStore = tx.objectStore(STORE_NAME); + await objectStore.put(heartbeatObject, computeKey(app)); + await tx.done; + } + catch (e) { + if (e instanceof FirebaseError) { + logger.warn(e.message); + } + else { + const idbGetError = ERROR_FACTORY.create("idb-set" /* AppError.IDB_WRITE */, { + originalErrorMessage: e?.message + }); + logger.warn(idbGetError.message); + } + } +} +function computeKey(app) { + return `${app.name}!${app.options.appId}`; +} +/** + * @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 MAX_HEADER_BYTES = 1024; +const MAX_NUM_STORED_HEARTBEATS = 30; +class HeartbeatServiceImpl { + constructor(container) { + this.container = container; + /** + * In-memory cache for heartbeats, used by getHeartbeatsHeader() to generate + * the header string. + * Stores one record per date. This will be consolidated into the standard + * format of one record per user agent string before being sent as a header. + * Populated from indexedDB when the controller is instantiated and should + * be kept in sync with indexedDB. + * Leave public for easier testing. + */ + this._heartbeatsCache = null; + const app = this.container.getProvider('app').getImmediate(); + this._storage = new HeartbeatStorageImpl(app); + this._heartbeatsCachePromise = this._storage.read().then(result => { + this._heartbeatsCache = result; + return result; + }); + } + /** + * Called to report a heartbeat. The function will generate + * a HeartbeatsByUserAgent object, update heartbeatsCache, and persist it + * to IndexedDB. + * Note that we only store one heartbeat per day. So if a heartbeat for today is + * already logged, subsequent calls to this function in the same day will be ignored. + */ + async triggerHeartbeat() { + try { + const platformLogger = this.container + .getProvider('platform-logger') + .getImmediate(); + // This is the "Firebase user agent" string from the platform logger + // service, not the browser user agent. + const agent = platformLogger.getPlatformInfoString(); + const date = getUTCDateString(); + if (this._heartbeatsCache?.heartbeats == null) { + this._heartbeatsCache = await this._heartbeatsCachePromise; + // If we failed to construct a heartbeats cache, then return immediately. + if (this._heartbeatsCache?.heartbeats == null) { + return; + } + } + // Do not store a heartbeat if one is already stored for this day + // or if a header has already been sent today. + if (this._heartbeatsCache.lastSentHeartbeatDate === date || + this._heartbeatsCache.heartbeats.some(singleDateHeartbeat => singleDateHeartbeat.date === date)) { + return; + } + else { + // There is no entry for this date. Create one. + this._heartbeatsCache.heartbeats.push({ date, agent }); + // If the number of stored heartbeats exceeds the maximum number of stored heartbeats, remove the heartbeat with the earliest date. + // Since this is executed each time a heartbeat is pushed, the limit can only be exceeded by one, so only one needs to be removed. + if (this._heartbeatsCache.heartbeats.length > MAX_NUM_STORED_HEARTBEATS) { + const earliestHeartbeatIdx = getEarliestHeartbeatIdx(this._heartbeatsCache.heartbeats); + this._heartbeatsCache.heartbeats.splice(earliestHeartbeatIdx, 1); + } + } + return this._storage.overwrite(this._heartbeatsCache); + } + catch (e) { + logger.warn(e); + } + } + /** + * Returns a base64 encoded string which can be attached to the heartbeat-specific header directly. + * It also clears all heartbeats from memory as well as in IndexedDB. + * + * NOTE: Consuming product SDKs should not send the header if this method + * returns an empty string. + */ + async getHeartbeatsHeader() { + try { + if (this._heartbeatsCache === null) { + await this._heartbeatsCachePromise; + } + // If it's still null or the array is empty, there is no data to send. + if (this._heartbeatsCache?.heartbeats == null || + this._heartbeatsCache.heartbeats.length === 0) { + return ''; + } + const date = getUTCDateString(); + // Extract as many heartbeats from the cache as will fit under the size limit. + const { heartbeatsToSend, unsentEntries } = extractHeartbeatsForHeader(this._heartbeatsCache.heartbeats); + const headerString = base64urlEncodeWithoutPadding(JSON.stringify({ version: 2, heartbeats: heartbeatsToSend })); + // Store last sent date to prevent another being logged/sent for the same day. + this._heartbeatsCache.lastSentHeartbeatDate = date; + if (unsentEntries.length > 0) { + // Store any unsent entries if they exist. + this._heartbeatsCache.heartbeats = unsentEntries; + // This seems more likely than emptying the array (below) to lead to some odd state + // since the cache isn't empty and this will be called again on the next request, + // and is probably safest if we await it. + await this._storage.overwrite(this._heartbeatsCache); + } + else { + this._heartbeatsCache.heartbeats = []; + // Do not wait for this, to reduce latency. + void this._storage.overwrite(this._heartbeatsCache); + } + return headerString; + } + catch (e) { + logger.warn(e); + return ''; + } + } +} +function getUTCDateString() { + const today = new Date(); + // Returns date format 'YYYY-MM-DD' + return today.toISOString().substring(0, 10); +} +function extractHeartbeatsForHeader(heartbeatsCache, maxSize = MAX_HEADER_BYTES) { + // Heartbeats grouped by user agent in the standard format to be sent in + // the header. + const heartbeatsToSend = []; + // Single date format heartbeats that are not sent. + let unsentEntries = heartbeatsCache.slice(); + for (const singleDateHeartbeat of heartbeatsCache) { + // Look for an existing entry with the same user agent. + const heartbeatEntry = heartbeatsToSend.find(hb => hb.agent === singleDateHeartbeat.agent); + if (!heartbeatEntry) { + // If no entry for this user agent exists, create one. + heartbeatsToSend.push({ + agent: singleDateHeartbeat.agent, + dates: [singleDateHeartbeat.date] + }); + if (countBytes(heartbeatsToSend) > maxSize) { + // If the header would exceed max size, remove the added heartbeat + // entry and stop adding to the header. + heartbeatsToSend.pop(); + break; + } + } + else { + heartbeatEntry.dates.push(singleDateHeartbeat.date); + // If the header would exceed max size, remove the added date + // and stop adding to the header. + if (countBytes(heartbeatsToSend) > maxSize) { + heartbeatEntry.dates.pop(); + break; + } + } + // Pop unsent entry from queue. (Skipped if adding the entry exceeded + // quota and the loop breaks early.) + unsentEntries = unsentEntries.slice(1); + } + return { + heartbeatsToSend, + unsentEntries + }; +} +class HeartbeatStorageImpl { + constructor(app) { + this.app = app; + this._canUseIndexedDBPromise = this.runIndexedDBEnvironmentCheck(); + } + async runIndexedDBEnvironmentCheck() { + if (!isIndexedDBAvailable()) { + return false; + } + else { + return validateIndexedDBOpenable() + .then(() => true) + .catch(() => false); + } + } + /** + * Read all heartbeats. + */ + async read() { + const canUseIndexedDB = await this._canUseIndexedDBPromise; + if (!canUseIndexedDB) { + return { heartbeats: [] }; + } + else { + const idbHeartbeatObject = await readHeartbeatsFromIndexedDB(this.app); + if (idbHeartbeatObject?.heartbeats) { + return idbHeartbeatObject; + } + else { + return { heartbeats: [] }; + } + } + } + // overwrite the storage with the provided heartbeats + async overwrite(heartbeatsObject) { + const canUseIndexedDB = await this._canUseIndexedDBPromise; + if (!canUseIndexedDB) { + return; + } + else { + const existingHeartbeatsObject = await this.read(); + return writeHeartbeatsToIndexedDB(this.app, { + lastSentHeartbeatDate: heartbeatsObject.lastSentHeartbeatDate ?? + existingHeartbeatsObject.lastSentHeartbeatDate, + heartbeats: heartbeatsObject.heartbeats + }); + } + } + // add heartbeats + async add(heartbeatsObject) { + const canUseIndexedDB = await this._canUseIndexedDBPromise; + if (!canUseIndexedDB) { + return; + } + else { + const existingHeartbeatsObject = await this.read(); + return writeHeartbeatsToIndexedDB(this.app, { + lastSentHeartbeatDate: heartbeatsObject.lastSentHeartbeatDate ?? + existingHeartbeatsObject.lastSentHeartbeatDate, + heartbeats: [ + ...existingHeartbeatsObject.heartbeats, + ...heartbeatsObject.heartbeats + ] + }); + } + } +} +/** + * Calculate bytes of a HeartbeatsByUserAgent array after being wrapped + * in a platform logging header JSON object, stringified, and converted + * to base 64. + */ +function countBytes(heartbeatsCache) { + // base64 has a restricted set of characters, all of which should be 1 byte. + return base64urlEncodeWithoutPadding( + // heartbeatsCache wrapper properties + JSON.stringify({ version: 2, heartbeats: heartbeatsCache })).length; +} +/** + * Returns the index of the heartbeat with the earliest date. + * If the heartbeats array is empty, -1 is returned. + */ +function getEarliestHeartbeatIdx(heartbeats) { + if (heartbeats.length === 0) { + return -1; + } + let earliestHeartbeatIdx = 0; + let earliestHeartbeatDate = heartbeats[0].date; + for (let i = 1; i < heartbeats.length; i++) { + if (heartbeats[i].date < earliestHeartbeatDate) { + earliestHeartbeatDate = heartbeats[i].date; + earliestHeartbeatIdx = i; + } + } + return earliestHeartbeatIdx; +} +/** + * @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. + */ +function registerCoreComponents(variant) { + _registerComponent(new Component('platform-logger', container => new PlatformLoggerServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */)); + _registerComponent(new Component('heartbeat', container => new HeartbeatServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */)); + // Register `app` package. + registerVersion(name$q, version$1, variant); + // BUILD_TARGET will be replaced by values like esm, cjs, etc during the compilation + registerVersion(name$q, version$1, 'esm2020'); + // Register platform SDK identifier (no version). + registerVersion('fire-js', ''); +} +/** + * Firebase App + * + * @remarks This package coordinates the communication between the different Firebase components + * @packageDocumentation + */ +registerCoreComponents(''); + +var name = "firebase"; +var version = "12.5.0"; + +/** + * @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. + */ +registerVersion(name, version, 'cdn'); + +export { FirebaseError, SDK_VERSION, DEFAULT_ENTRY_NAME as _DEFAULT_ENTRY_NAME, _addComponent, _addOrOverwriteComponent, _apps, _clearComponents, _components, _getProvider, _isFirebaseApp, _isFirebaseServerApp, _isFirebaseServerAppSettings, _registerComponent, _removeServiceInstance, _serverApps, deleteApp, getApp, getApps, initializeApp, initializeServerApp, onLog, registerVersion, setLogLevel }; + +//# sourceMappingURL=firebase-app.js.map |
