Preview: firebase-firestore-compat.js.map
Size: 2.34 MB
/var/www/sitesecurity.bitkit.dk/httpdocs/node_modules/firebase/firebase-firestore-compat.js.map
{"version":3,"file":"firebase-firestore-compat.js","sources":["../util/dist/postinstall.mjs","../util/src/crypt.ts","../util/src/global.ts","../util/src/defaults.ts","../util/src/url.ts","../util/src/emulator.ts","../util/src/environment.ts","../util/src/errors.ts","../util/src/obj.ts","../util/src/compat.ts","../component/src/component.ts","../logger/src/logger.ts","../firestore/src/remote/rpc_error.ts","../firestore/src/core/event_manager.ts","../../node_modules/closure-net/firebase/bloom_blob_es2018.js","../../node_modules/closure-net/firebase/webchannel_blob_es2018.js","../firestore/src/auth/user.ts","../firestore/src/core/version.ts","../firestore/src/util/log.ts","../firestore/src/platform/browser/format_json.ts","../firestore/src/util/assert.ts","../firestore/src/util/error.ts","../firestore/src/util/promise.ts","../firestore/src/api/credentials.ts","../firestore/src/platform/browser/text_serializer.ts","../firestore/src/util/misc.ts","../firestore/src/platform/browser/random_bytes.ts","../firestore/src/model/path.ts","../firestore/src/model/document_key.ts","../firestore/src/util/input_validation.ts","../firestore/src/util/json_validation.ts","../firestore/src/lite-api/timestamp.ts","../firestore/src/core/snapshot_version.ts","../firestore/src/model/field_index.ts","../firestore/src/local/persistence_transaction.ts","../firestore/src/local/local_store.ts","../firestore/src/local/persistence_promise.ts","../firestore/src/local/simple_db.ts","../firestore/src/local/index_backfiller.ts","../firestore/src/core/listen_sequence.ts","../firestore/src/util/types.ts","../firestore/src/local/encoded_resource_path.ts","../firestore/src/local/indexeddb_schema_legacy.ts","../firestore/src/local/indexeddb_sentinels.ts","../firestore/src/local/indexeddb_transaction.ts","../firestore/src/util/obj.ts","../firestore/src/util/sorted_map.ts","../firestore/src/util/sorted_set.ts","../firestore/src/model/field_mask.ts","../firestore/src/util/base64_decode_error.ts","../firestore/src/util/byte_string.ts","../firestore/src/platform/browser/base64.ts","../firestore/src/model/normalize.ts","../firestore/src/model/server_timestamps.ts","../firestore/src/core/database_info.ts","../firestore/src/model/values.ts","../firestore/src/model/object_value.ts","../firestore/src/model/document.ts","../firestore/src/core/bound.ts","../firestore/src/core/order_by.ts","../firestore/src/core/filter.ts","../firestore/src/core/target.ts","../firestore/src/core/query.ts","../firestore/src/util/obj_map.ts","../firestore/src/model/collections.ts","../firestore/src/remote/number_serializer.ts","../firestore/src/model/transform_operation.ts","../firestore/src/model/mutation.ts","../firestore/src/model/mutation_batch.ts","../firestore/src/model/overlay.ts","../firestore/src/remote/existence_filter.ts","../firestore/src/remote/bloom_filter.ts","../firestore/src/remote/remote_event.ts","../firestore/src/remote/watch_change.ts","../firestore/src/remote/serializer.ts","../firestore/src/local/target_data.ts","../firestore/src/local/local_serializer.ts","../firestore/src/local/indexeddb_bundle_cache.ts","../firestore/src/local/indexeddb_document_overlay_cache.ts","../firestore/src/local/indexeddb_globals_cache.ts","../firestore/src/index/firestore_index_value_writer.ts","../firestore/src/index/ordered_code_writer.ts","../firestore/src/index/index_byte_encoder.ts","../firestore/src/index/index_entry.ts","../firestore/src/model/target_index_matcher.ts","../firestore/src/util/logic_utils.ts","../firestore/src/local/memory_index_manager.ts","../firestore/src/local/indexeddb_index_manager.ts","../firestore/src/local/lru_garbage_collector.ts","../firestore/src/local/indexeddb_mutation_batch_impl.ts","../firestore/src/local/indexeddb_mutation_queue.ts","../firestore/src/core/target_id_generator.ts","../firestore/src/local/indexeddb_target_cache.ts","../firestore/src/local/lru_garbage_collector_impl.ts","../firestore/src/local/indexeddb_lru_delegate_impl.ts","../firestore/src/local/remote_document_change_buffer.ts","../firestore/src/local/indexeddb_remote_document_cache.ts","../firestore/src/local/overlayed_document.ts","../firestore/src/local/local_documents_view.ts","../firestore/src/local/memory_bundle_cache.ts","../firestore/src/local/memory_document_overlay_cache.ts","../firestore/src/local/memory_globals_cache.ts","../firestore/src/local/reference_set.ts","../firestore/src/local/memory_mutation_queue.ts","../firestore/src/local/memory_remote_document_cache.ts","../firestore/src/local/memory_target_cache.ts","../firestore/src/local/memory_persistence.ts","../firestore/src/local/indexeddb_schema_converter.ts","../firestore/src/local/indexeddb_persistence.ts","../firestore/src/local/indexeddb_schema.ts","../firestore/src/local/local_view_changes.ts","../firestore/src/local/query_context.ts","../firestore/src/local/query_engine.ts","../firestore/src/local/local_store_impl.ts","../firestore/src/local/shared_client_state_schema.ts","../firestore/src/local/shared_client_state.ts","../firestore/src/remote/connectivity_monitor_noop.ts","../firestore/src/platform/browser/connectivity_monitor.ts","../firestore/src/util/debug_uid.ts","../firestore/src/remote/rest_connection.ts","../firestore/src/remote/stream_bridge.ts","../firestore/src/platform/browser/webchannel_connection.ts","../firestore/src/platform/browser/dom.ts","../firestore/src/platform/browser/serializer.ts","../firestore/src/remote/backoff.ts","../firestore/src/remote/persistent_stream.ts","../firestore/src/remote/datastore.ts","../firestore/src/remote/online_state_tracker.ts","../firestore/src/remote/remote_store.ts","../firestore/src/util/async_queue.ts","../firestore/src/model/document_set.ts","../firestore/src/core/view_snapshot.ts","../firestore/src/util/bundle_reader.ts","../firestore/src/core/bundle_impl.ts","../firestore/src/core/view.ts","../firestore/src/core/sync_engine_impl.ts","../firestore/src/core/component_provider.ts","../firestore/src/platform/browser/connection.ts","../firestore/src/util/byte_stream.ts","../firestore/src/util/async_observer.ts","../firestore/src/util/bundle_reader_impl.ts","../firestore/src/core/transaction.ts","../firestore/src/core/transaction_runner.ts","../firestore/src/core/firestore_client.ts","../firestore/src/platform/browser/byte_stream_reader.ts","../firestore/src/api/long_polling_options.ts","../firestore/src/lite-api/components.ts","../firestore/src/lite-api/settings.ts","../firestore/src/lite-api/database.ts","../firestore/src/lite-api/reference.ts","../firestore/src/util/async_queue_impl.ts","../firestore/src/api/observer.ts","../firestore/src/api/bundle.ts","../firestore/src/api/database.ts","../firestore/src/register.ts","../firestore-compat/src/index.ts","../firestore-compat/src/config.ts","../firestore/src/lite-api/bytes.ts","../firestore/src/lite-api/field_path.ts","../firestore/src/lite-api/field_value.ts","../firestore/src/lite-api/geo_point.ts","../firestore/src/lite-api/vector_value.ts","../firestore/src/util/array.ts","../firestore/src/lite-api/user_data_reader.ts","../firestore/src/lite-api/snapshot.ts","../firestore/src/lite-api/query.ts","../firestore/src/lite-api/user_data_writer.ts","../firestore/src/lite-api/reference_impl.ts","../firestore/src/api/snapshot.ts","../firestore/src/platform/browser/snapshot_to_json.ts","../firestore/src/api/reference_impl.ts","../firestore/src/core/transaction_options.ts","../firestore/src/lite-api/write_batch.ts","../firestore/src/api/transaction.ts","../firestore/src/lite-api/transaction.ts","../firestore-compat/src/util/input_validation.ts","../firestore-compat/src/api/blob.ts","../firestore-compat/src/api/observer.ts","../firestore-compat/src/api/database.ts","../firestore-compat/src/api/field_value.ts","../firestore/src/lite-api/field_value_impl.ts","../firestore-compat/src/api/field_path.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n// This value is retrieved and hardcoded by the NPM postinstall script\nconst getDefaultsFromPostinstall = () => undefined;\n\nexport { getDefaultsFromPostinstall };\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst stringToByteArray = function (str: string): number[] {\n // TODO(user): Use native implementations if/when available\n const out: number[] = [];\n let p = 0;\n for (let i = 0; i < str.length; i++) {\n let c = str.charCodeAt(i);\n if (c < 128) {\n out[p++] = c;\n } else if (c < 2048) {\n out[p++] = (c >> 6) | 192;\n out[p++] = (c & 63) | 128;\n } else if (\n (c & 0xfc00) === 0xd800 &&\n i + 1 < str.length &&\n (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00\n ) {\n // Surrogate Pair\n c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff);\n out[p++] = (c >> 18) | 240;\n out[p++] = ((c >> 12) & 63) | 128;\n out[p++] = ((c >> 6) & 63) | 128;\n out[p++] = (c & 63) | 128;\n } else {\n out[p++] = (c >> 12) | 224;\n out[p++] = ((c >> 6) & 63) | 128;\n out[p++] = (c & 63) | 128;\n }\n }\n return out;\n};\n\n/**\n * Turns an array of numbers into the string given by the concatenation of the\n * characters to which the numbers correspond.\n * @param bytes Array of numbers representing characters.\n * @return Stringification of the array.\n */\nconst byteArrayToString = function (bytes: number[]): string {\n // TODO(user): Use native implementations if/when available\n const out: string[] = [];\n let pos = 0,\n c = 0;\n while (pos < bytes.length) {\n const c1 = bytes[pos++];\n if (c1 < 128) {\n out[c++] = String.fromCharCode(c1);\n } else if (c1 > 191 && c1 < 224) {\n const c2 = bytes[pos++];\n out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));\n } else if (c1 > 239 && c1 < 365) {\n // Surrogate Pair\n const c2 = bytes[pos++];\n const c3 = bytes[pos++];\n const c4 = bytes[pos++];\n const u =\n (((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) -\n 0x10000;\n out[c++] = String.fromCharCode(0xd800 + (u >> 10));\n out[c++] = String.fromCharCode(0xdc00 + (u & 1023));\n } else {\n const c2 = bytes[pos++];\n const c3 = bytes[pos++];\n out[c++] = String.fromCharCode(\n ((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)\n );\n }\n }\n return out.join('');\n};\n\ninterface Base64 {\n byteToCharMap_: { [key: number]: string } | null;\n charToByteMap_: { [key: string]: number } | null;\n byteToCharMapWebSafe_: { [key: number]: string } | null;\n charToByteMapWebSafe_: { [key: string]: number } | null;\n ENCODED_VALS_BASE: string;\n readonly ENCODED_VALS: string;\n readonly ENCODED_VALS_WEBSAFE: string;\n HAS_NATIVE_SUPPORT: boolean;\n encodeByteArray(input: number[] | Uint8Array, webSafe?: boolean): string;\n encodeString(input: string, webSafe?: boolean): string;\n decodeString(input: string, webSafe: boolean): string;\n decodeStringToByteArray(input: string, webSafe: boolean): number[];\n init_(): void;\n}\n\n// We define it as an object literal instead of a class because a class compiled down to es5 can't\n// be treeshaked. https://github.com/rollup/rollup/issues/1691\n// Static lookup maps, lazily populated by init_()\n// TODO(dlarocque): Define this as a class, since we no longer target ES5.\nexport const base64: Base64 = {\n /**\n * Maps bytes to characters.\n */\n byteToCharMap_: null,\n\n /**\n * Maps characters to bytes.\n */\n charToByteMap_: null,\n\n /**\n * Maps bytes to websafe characters.\n * @private\n */\n byteToCharMapWebSafe_: null,\n\n /**\n * Maps websafe characters to bytes.\n * @private\n */\n charToByteMapWebSafe_: null,\n\n /**\n * Our default alphabet, shared between\n * ENCODED_VALS and ENCODED_VALS_WEBSAFE\n */\n ENCODED_VALS_BASE:\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789',\n\n /**\n * Our default alphabet. Value 64 (=) is special; it means \"nothing.\"\n */\n get ENCODED_VALS() {\n return this.ENCODED_VALS_BASE + '+/=';\n },\n\n /**\n * Our websafe alphabet.\n */\n get ENCODED_VALS_WEBSAFE() {\n return this.ENCODED_VALS_BASE + '-_.';\n },\n\n /**\n * Whether this browser supports the atob and btoa functions. This extension\n * started at Mozilla but is now implemented by many browsers. We use the\n * ASSUME_* variables to avoid pulling in the full useragent detection library\n * but still allowing the standard per-browser compilations.\n *\n */\n HAS_NATIVE_SUPPORT: typeof atob === 'function',\n\n /**\n * Base64-encode an array of bytes.\n *\n * @param input An array of bytes (numbers with\n * value in [0, 255]) to encode.\n * @param webSafe Boolean indicating we should use the\n * alternative alphabet.\n * @return The base64 encoded string.\n */\n encodeByteArray(input: number[] | Uint8Array, webSafe?: boolean): string {\n if (!Array.isArray(input)) {\n throw Error('encodeByteArray takes an array as a parameter');\n }\n\n this.init_();\n\n const byteToCharMap = webSafe\n ? this.byteToCharMapWebSafe_!\n : this.byteToCharMap_!;\n\n const output = [];\n\n for (let i = 0; i < input.length; i += 3) {\n const byte1 = input[i];\n const haveByte2 = i + 1 < input.length;\n const byte2 = haveByte2 ? input[i + 1] : 0;\n const haveByte3 = i + 2 < input.length;\n const byte3 = haveByte3 ? input[i + 2] : 0;\n\n const outByte1 = byte1 >> 2;\n const outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);\n let outByte3 = ((byte2 & 0x0f) << 2) | (byte3 >> 6);\n let outByte4 = byte3 & 0x3f;\n\n if (!haveByte3) {\n outByte4 = 64;\n\n if (!haveByte2) {\n outByte3 = 64;\n }\n }\n\n output.push(\n byteToCharMap[outByte1],\n byteToCharMap[outByte2],\n byteToCharMap[outByte3],\n byteToCharMap[outByte4]\n );\n }\n\n return output.join('');\n },\n\n /**\n * Base64-encode a string.\n *\n * @param input A string to encode.\n * @param webSafe If true, we should use the\n * alternative alphabet.\n * @return The base64 encoded string.\n */\n encodeString(input: string, webSafe?: boolean): string {\n // Shortcut for Mozilla browsers that implement\n // a native base64 encoder in the form of \"btoa/atob\"\n if (this.HAS_NATIVE_SUPPORT && !webSafe) {\n return btoa(input);\n }\n return this.encodeByteArray(stringToByteArray(input), webSafe);\n },\n\n /**\n * Base64-decode a string.\n *\n * @param input to decode.\n * @param webSafe True if we should use the\n * alternative alphabet.\n * @return string representing the decoded value.\n */\n decodeString(input: string, webSafe: boolean): string {\n // Shortcut for Mozilla browsers that implement\n // a native base64 encoder in the form of \"btoa/atob\"\n if (this.HAS_NATIVE_SUPPORT && !webSafe) {\n return atob(input);\n }\n return byteArrayToString(this.decodeStringToByteArray(input, webSafe));\n },\n\n /**\n * Base64-decode a string.\n *\n * In base-64 decoding, groups of four characters are converted into three\n * bytes. If the encoder did not apply padding, the input length may not\n * be a multiple of 4.\n *\n * In this case, the last group will have fewer than 4 characters, and\n * padding will be inferred. If the group has one or two characters, it decodes\n * to one byte. If the group has three characters, it decodes to two bytes.\n *\n * @param input Input to decode.\n * @param webSafe True if we should use the web-safe alphabet.\n * @return bytes representing the decoded value.\n */\n decodeStringToByteArray(input: string, webSafe: boolean): number[] {\n this.init_();\n\n const charToByteMap = webSafe\n ? this.charToByteMapWebSafe_!\n : this.charToByteMap_!;\n\n const output: number[] = [];\n\n for (let i = 0; i < input.length; ) {\n const byte1 = charToByteMap[input.charAt(i++)];\n\n const haveByte2 = i < input.length;\n const byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0;\n ++i;\n\n const haveByte3 = i < input.length;\n const byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 64;\n ++i;\n\n const haveByte4 = i < input.length;\n const byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64;\n ++i;\n\n if (byte1 == null || byte2 == null || byte3 == null || byte4 == null) {\n throw new DecodeBase64StringError();\n }\n\n const outByte1 = (byte1 << 2) | (byte2 >> 4);\n output.push(outByte1);\n\n if (byte3 !== 64) {\n const outByte2 = ((byte2 << 4) & 0xf0) | (byte3 >> 2);\n output.push(outByte2);\n\n if (byte4 !== 64) {\n const outByte3 = ((byte3 << 6) & 0xc0) | byte4;\n output.push(outByte3);\n }\n }\n }\n\n return output;\n },\n\n /**\n * Lazy static initialization function. Called before\n * accessing any of the static map variables.\n * @private\n */\n init_() {\n if (!this.byteToCharMap_) {\n this.byteToCharMap_ = {};\n this.charToByteMap_ = {};\n this.byteToCharMapWebSafe_ = {};\n this.charToByteMapWebSafe_ = {};\n\n // We want quick mappings back and forth, so we precompute two maps.\n for (let i = 0; i < this.ENCODED_VALS.length; i++) {\n this.byteToCharMap_[i] = this.ENCODED_VALS.charAt(i);\n this.charToByteMap_[this.byteToCharMap_[i]] = i;\n this.byteToCharMapWebSafe_[i] = this.ENCODED_VALS_WEBSAFE.charAt(i);\n this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i;\n\n // Be forgiving when decoding and correctly decode both encodings.\n if (i >= this.ENCODED_VALS_BASE.length) {\n this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(i)] = i;\n this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(i)] = i;\n }\n }\n }\n }\n};\n\n/**\n * An error encountered while decoding base64 string.\n */\nexport class DecodeBase64StringError extends Error {\n readonly name = 'DecodeBase64StringError';\n}\n\n/**\n * URL-safe base64 encoding\n */\nexport const base64Encode = function (str: string): string {\n const utf8Bytes = stringToByteArray(str);\n return base64.encodeByteArray(utf8Bytes, true);\n};\n\n/**\n * URL-safe base64 encoding (without \".\" padding in the end).\n * e.g. Used in JSON Web Token (JWT) parts.\n */\nexport const base64urlEncodeWithoutPadding = function (str: string): string {\n // Use base64url encoding and remove padding in the end (dot characters).\n return base64Encode(str).replace(/\\./g, '');\n};\n\n/**\n * URL-safe base64 decoding\n *\n * NOTE: DO NOT use the global atob() function - it does NOT support the\n * base64Url variant encoding.\n *\n * @param str To be decoded\n * @return Decoded result, if possible\n */\nexport const base64Decode = function (str: string): string | null {\n try {\n return base64.decodeString(str, true);\n } catch (e) {\n console.error('base64Decode failed: ', e);\n }\n return null;\n};\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Polyfill for `globalThis` object.\n * @returns the `globalThis` object for the given environment.\n * @public\n */\nexport function getGlobal(): typeof globalThis {\n if (typeof self !== 'undefined') {\n return self;\n }\n if (typeof window !== 'undefined') {\n return window;\n }\n if (typeof global !== 'undefined') {\n return global;\n }\n throw new Error('Unable to locate global object.');\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { base64Decode } from './crypt';\nimport { getGlobal } from './global';\nimport { getDefaultsFromPostinstall } from './postinstall';\n\n/**\n * Keys for experimental properties on the `FirebaseDefaults` object.\n * @public\n */\nexport type ExperimentalKey = 'authTokenSyncURL' | 'authIdTokenMaxAge';\n\n/**\n * An object that can be injected into the environment as __FIREBASE_DEFAULTS__,\n * either as a property of globalThis, a shell environment variable, or a\n * cookie.\n *\n * This object can be used to automatically configure and initialize\n * a Firebase app as well as any emulators.\n *\n * @public\n */\nexport interface FirebaseDefaults {\n config?: Record<string, string>;\n emulatorHosts?: Record<string, string>;\n _authTokenSyncURL?: string;\n _authIdTokenMaxAge?: number;\n /**\n * Override Firebase's runtime environment detection and\n * force the SDK to act as if it were in the specified environment.\n */\n forceEnvironment?: 'browser' | 'node';\n [key: string]: unknown;\n}\n\ndeclare global {\n // Need `var` for this to work.\n // eslint-disable-next-line no-var\n var __FIREBASE_DEFAULTS__: FirebaseDefaults | undefined;\n}\n\nconst getDefaultsFromGlobal = (): FirebaseDefaults | undefined =>\n getGlobal().__FIREBASE_DEFAULTS__;\n\n/**\n * Attempt to read defaults from a JSON string provided to\n * process(.)env(.)__FIREBASE_DEFAULTS__ or a JSON file whose path is in\n * process(.)env(.)__FIREBASE_DEFAULTS_PATH__\n * The dots are in parens because certain compilers (Vite?) cannot\n * handle seeing that variable in comments.\n * See https://github.com/firebase/firebase-js-sdk/issues/6838\n */\nconst getDefaultsFromEnvVariable = (): FirebaseDefaults | undefined => {\n if (typeof process === 'undefined' || typeof process.env === 'undefined') {\n return;\n }\n const defaultsJsonString = process.env.__FIREBASE_DEFAULTS__;\n if (defaultsJsonString) {\n return JSON.parse(defaultsJsonString);\n }\n};\n\nconst getDefaultsFromCookie = (): FirebaseDefaults | undefined => {\n if (typeof document === 'undefined') {\n return;\n }\n let match;\n try {\n match = document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/);\n } catch (e) {\n // Some environments such as Angular Universal SSR have a\n // `document` object but error on accessing `document.cookie`.\n return;\n }\n const decoded = match && base64Decode(match[1]);\n return decoded && JSON.parse(decoded);\n};\n\n/**\n * Get the __FIREBASE_DEFAULTS__ object. It checks in order:\n * (1) if such an object exists as a property of `globalThis`\n * (2) if such an object was provided on a shell environment variable\n * (3) if such an object exists in a cookie\n * @public\n */\nexport const getDefaults = (): FirebaseDefaults | undefined => {\n try {\n return (\n getDefaultsFromPostinstall() ||\n getDefaultsFromGlobal() ||\n getDefaultsFromEnvVariable() ||\n getDefaultsFromCookie()\n );\n } catch (e) {\n /**\n * Catch-all for being unable to get __FIREBASE_DEFAULTS__ due\n * to any environment case we have not accounted for. Log to\n * info instead of swallowing so we can find these unknown cases\n * and add paths for them if needed.\n */\n console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${e}`);\n return;\n }\n};\n\n/**\n * Returns emulator host stored in the __FIREBASE_DEFAULTS__ object\n * for the given product.\n * @returns a URL host formatted like `127.0.0.1:9999` or `[::1]:4000` if available\n * @public\n */\nexport const getDefaultEmulatorHost = (\n productName: string\n): string | undefined => getDefaults()?.emulatorHosts?.[productName];\n\n/**\n * Returns emulator hostname and port stored in the __FIREBASE_DEFAULTS__ object\n * for the given product.\n * @returns a pair of hostname and port like `[\"::1\", 4000]` if available\n * @public\n */\nexport const getDefaultEmulatorHostnameAndPort = (\n productName: string\n): [hostname: string, port: number] | undefined => {\n const host = getDefaultEmulatorHost(productName);\n if (!host) {\n return undefined;\n }\n const separatorIndex = host.lastIndexOf(':'); // Finding the last since IPv6 addr also has colons.\n if (separatorIndex <= 0 || separatorIndex + 1 === host.length) {\n throw new Error(`Invalid host ${host} with no separate hostname and port!`);\n }\n // eslint-disable-next-line no-restricted-globals\n const port = parseInt(host.substring(separatorIndex + 1), 10);\n if (host[0] === '[') {\n // Bracket-quoted `[ipv6addr]:port` => return \"ipv6addr\" (without brackets).\n return [host.substring(1, separatorIndex - 1), port];\n } else {\n return [host.substring(0, separatorIndex), port];\n }\n};\n\n/**\n * Returns Firebase app config stored in the __FIREBASE_DEFAULTS__ object.\n * @public\n */\nexport const getDefaultAppConfig = (): Record<string, string> | undefined =>\n getDefaults()?.config;\n\n/**\n * Returns an experimental setting on the __FIREBASE_DEFAULTS__ object (properties\n * prefixed by \"_\")\n * @public\n */\nexport const getExperimentalSetting = <T extends ExperimentalKey>(\n name: T\n): FirebaseDefaults[`_${T}`] =>\n getDefaults()?.[`_${name}`] as FirebaseDefaults[`_${T}`];\n","/**\n * @license\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checks whether host is a cloud workstation or not.\n * @public\n */\nexport function isCloudWorkstation(url: string): boolean {\n // `isCloudWorkstation` is called without protocol in certain connect*Emulator functions\n // In HTTP request builders, it's called with the protocol.\n // If called with protocol prefix, it's a valid URL, so we extract the hostname\n // If called without, we assume the string is the hostname.\n try {\n const host =\n url.startsWith('http://') || url.startsWith('https://')\n ? new URL(url).hostname\n : url;\n return host.endsWith('.cloudworkstations.dev');\n } catch {\n return false;\n }\n}\n\n/**\n * Makes a fetch request to the given server.\n * Mostly used for forwarding cookies in Firebase Studio.\n * @public\n */\nexport async function pingServer(endpoint: string): Promise<boolean> {\n const result = await fetch(endpoint, {\n credentials: 'include'\n });\n return result.ok;\n}\n","/**\n * @license\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { base64urlEncodeWithoutPadding } from './crypt';\nimport { isCloudWorkstation } from './url';\n\n// Firebase Auth tokens contain snake_case claims following the JWT standard / convention.\n/* eslint-disable camelcase */\n\nexport type FirebaseSignInProvider =\n | 'custom'\n | 'email'\n | 'password'\n | 'phone'\n | 'anonymous'\n | 'google.com'\n | 'facebook.com'\n | 'github.com'\n | 'twitter.com'\n | 'microsoft.com'\n | 'apple.com';\n\ninterface FirebaseIdToken {\n // Always set to https://securetoken.google.com/PROJECT_ID\n iss: string;\n\n // Always set to PROJECT_ID\n aud: string;\n\n // The user's unique ID\n sub: string;\n\n // The token issue time, in seconds since epoch\n iat: number;\n\n // The token expiry time, normally 'iat' + 3600\n exp: number;\n\n // The user's unique ID. Must be equal to 'sub'\n user_id: string;\n\n // The time the user authenticated, normally 'iat'\n auth_time: number;\n\n // The sign in provider, only set when the provider is 'anonymous'\n provider_id?: 'anonymous';\n\n // The user's primary email\n email?: string;\n\n // The user's email verification status\n email_verified?: boolean;\n\n // The user's primary phone number\n phone_number?: string;\n\n // The user's display name\n name?: string;\n\n // The user's profile photo URL\n picture?: string;\n\n // Information on all identities linked to this user\n firebase: {\n // The primary sign-in provider\n sign_in_provider: FirebaseSignInProvider;\n\n // A map of providers to the user's list of unique identifiers from\n // each provider\n identities?: { [provider in FirebaseSignInProvider]?: string[] };\n };\n\n // Custom claims set by the developer\n [claim: string]: unknown;\n\n uid?: never; // Try to catch a common mistake of \"uid\" (should be \"sub\" instead).\n}\n\nexport type EmulatorMockTokenOptions = ({ user_id: string } | { sub: string }) &\n Partial<FirebaseIdToken>;\n\nexport function createMockUserToken(\n token: EmulatorMockTokenOptions,\n projectId?: string\n): string {\n if (token.uid) {\n throw new Error(\n 'The \"uid\" field is no longer supported by mockUserToken. Please use \"sub\" instead for Firebase Auth User ID.'\n );\n }\n // Unsecured JWTs use \"none\" as the algorithm.\n const header = {\n alg: 'none',\n type: 'JWT'\n };\n\n const project = projectId || 'demo-project';\n const iat = token.iat || 0;\n const sub = token.sub || token.user_id;\n if (!sub) {\n throw new Error(\"mockUserToken must contain 'sub' or 'user_id' field!\");\n }\n\n const payload: FirebaseIdToken = {\n // Set all required fields to decent defaults\n iss: `https://securetoken.google.com/${project}`,\n aud: project,\n iat,\n exp: iat + 3600,\n auth_time: iat,\n sub,\n user_id: sub,\n firebase: {\n sign_in_provider: 'custom',\n identities: {}\n },\n\n // Override with user options\n ...token\n };\n\n // Unsecured JWTs use the empty string as a signature.\n const signature = '';\n return [\n base64urlEncodeWithoutPadding(JSON.stringify(header)),\n base64urlEncodeWithoutPadding(JSON.stringify(payload)),\n signature\n ].join('.');\n}\n\ninterface EmulatorStatusMap {\n [name: string]: boolean;\n}\nconst emulatorStatus: EmulatorStatusMap = {};\n\ninterface EmulatorSummary {\n prod: string[];\n emulator: string[];\n}\n\n// Checks whether any products are running on an emulator\nfunction getEmulatorSummary(): EmulatorSummary {\n const summary: EmulatorSummary = {\n prod: [],\n emulator: []\n };\n for (const key of Object.keys(emulatorStatus)) {\n if (emulatorStatus[key]) {\n summary.emulator.push(key);\n } else {\n summary.prod.push(key);\n }\n }\n return summary;\n}\n\nfunction getOrCreateEl(id: string): { created: boolean; element: HTMLElement } {\n let parentDiv = document.getElementById(id);\n let created = false;\n if (!parentDiv) {\n parentDiv = document.createElement('div');\n parentDiv.setAttribute('id', id);\n created = true;\n }\n return { created, element: parentDiv };\n}\n\nlet previouslyDismissed = false;\n/**\n * Updates Emulator Banner. Primarily used for Firebase Studio\n * @param name\n * @param isRunningEmulator\n * @public\n */\nexport function updateEmulatorBanner(\n name: string,\n isRunningEmulator: boolean\n): void {\n if (\n typeof window === 'undefined' ||\n typeof document === 'undefined' ||\n !isCloudWorkstation(window.location.host) ||\n emulatorStatus[name] === isRunningEmulator ||\n emulatorStatus[name] || // If already set to use emulator, can't go back to prod.\n previouslyDismissed\n ) {\n return;\n }\n\n emulatorStatus[name] = isRunningEmulator;\n\n function prefixedId(id: string): string {\n return `__firebase__banner__${id}`;\n }\n const bannerId = '__firebase__banner';\n const summary = getEmulatorSummary();\n const showError = summary.prod.length > 0;\n\n function tearDown(): void {\n const element = document.getElementById(bannerId);\n if (element) {\n element.remove();\n }\n }\n\n function setupBannerStyles(bannerEl: HTMLElement): void {\n bannerEl.style.display = 'flex';\n bannerEl.style.background = '#7faaf0';\n bannerEl.style.position = 'fixed';\n bannerEl.style.bottom = '5px';\n bannerEl.style.left = '5px';\n bannerEl.style.padding = '.5em';\n bannerEl.style.borderRadius = '5px';\n bannerEl.style.alignItems = 'center';\n }\n\n function setupIconStyles(prependIcon: SVGElement, iconId: string): void {\n prependIcon.setAttribute('width', '24');\n prependIcon.setAttribute('id', iconId);\n prependIcon.setAttribute('height', '24');\n prependIcon.setAttribute('viewBox', '0 0 24 24');\n prependIcon.setAttribute('fill', 'none');\n prependIcon.style.marginLeft = '-6px';\n }\n\n function setupCloseBtn(): HTMLSpanElement {\n const closeBtn = document.createElement('span');\n closeBtn.style.cursor = 'pointer';\n closeBtn.style.marginLeft = '16px';\n closeBtn.style.fontSize = '24px';\n closeBtn.innerHTML = ' ×';\n closeBtn.onclick = () => {\n previouslyDismissed = true;\n tearDown();\n };\n return closeBtn;\n }\n\n function setupLinkStyles(\n learnMoreLink: HTMLAnchorElement,\n learnMoreId: string\n ): void {\n learnMoreLink.setAttribute('id', learnMoreId);\n learnMoreLink.innerText = 'Learn more';\n learnMoreLink.href =\n 'https://firebase.google.com/docs/studio/preview-apps#preview-backend';\n learnMoreLink.setAttribute('target', '__blank');\n learnMoreLink.style.paddingLeft = '5px';\n learnMoreLink.style.textDecoration = 'underline';\n }\n\n function setupDom(): void {\n const banner = getOrCreateEl(bannerId);\n const firebaseTextId = prefixedId('text');\n const firebaseText: HTMLSpanElement =\n document.getElementById(firebaseTextId) || document.createElement('span');\n const learnMoreId = prefixedId('learnmore');\n const learnMoreLink: HTMLAnchorElement =\n (document.getElementById(learnMoreId) as HTMLAnchorElement) ||\n document.createElement('a');\n const prependIconId = prefixedId('preprendIcon');\n const prependIcon: SVGElement =\n (document.getElementById(\n prependIconId\n ) as HTMLOrSVGElement as SVGElement) ||\n document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n if (banner.created) {\n // update styles\n const bannerEl = banner.element;\n setupBannerStyles(bannerEl);\n setupLinkStyles(learnMoreLink, learnMoreId);\n const closeBtn = setupCloseBtn();\n setupIconStyles(prependIcon, prependIconId);\n bannerEl.append(prependIcon, firebaseText, learnMoreLink, closeBtn);\n document.body.appendChild(bannerEl);\n }\n\n if (showError) {\n firebaseText.innerText = `Preview backend disconnected.`;\n prependIcon.innerHTML = `<g clip-path=\"url(#clip0_6013_33858)\">\n<path d=\"M4.8 17.6L12 5.6L19.2 17.6H4.8ZM6.91667 16.4H17.0833L12 7.93333L6.91667 16.4ZM12 15.6C12.1667 15.6 12.3056 15.5444 12.4167 15.4333C12.5389 15.3111 12.6 15.1667 12.6 15C12.6 14.8333 12.5389 14.6944 12.4167 14.5833C12.3056 14.4611 12.1667 14.4 12 14.4C11.8333 14.4 11.6889 14.4611 11.5667 14.5833C11.4556 14.6944 11.4 14.8333 11.4 15C11.4 15.1667 11.4556 15.3111 11.5667 15.4333C11.6889 15.5444 11.8333 15.6 12 15.6ZM11.4 13.6H12.6V10.4H11.4V13.6Z\" fill=\"#212121\"/>\n</g>\n<defs>\n<clipPath id=\"clip0_6013_33858\">\n<rect width=\"24\" height=\"24\" fill=\"white\"/>\n</clipPath>\n</defs>`;\n } else {\n prependIcon.innerHTML = `<g clip-path=\"url(#clip0_6083_34804)\">\n<path d=\"M11.4 15.2H12.6V11.2H11.4V15.2ZM12 10C12.1667 10 12.3056 9.94444 12.4167 9.83333C12.5389 9.71111 12.6 9.56667 12.6 9.4C12.6 9.23333 12.5389 9.09444 12.4167 8.98333C12.3056 8.86111 12.1667 8.8 12 8.8C11.8333 8.8 11.6889 8.86111 11.5667 8.98333C11.4556 9.09444 11.4 9.23333 11.4 9.4C11.4 9.56667 11.4556 9.71111 11.5667 9.83333C11.6889 9.94444 11.8333 10 12 10ZM12 18.4C11.1222 18.4 10.2944 18.2333 9.51667 17.9C8.73889 17.5667 8.05556 17.1111 7.46667 16.5333C6.88889 15.9444 6.43333 15.2611 6.1 14.4833C5.76667 13.7056 5.6 12.8778 5.6 12C5.6 11.1111 5.76667 10.2833 6.1 9.51667C6.43333 8.73889 6.88889 8.06111 7.46667 7.48333C8.05556 6.89444 8.73889 6.43333 9.51667 6.1C10.2944 5.76667 11.1222 5.6 12 5.6C12.8889 5.6 13.7167 5.76667 14.4833 6.1C15.2611 6.43333 15.9389 6.89444 16.5167 7.48333C17.1056 8.06111 17.5667 8.73889 17.9 9.51667C18.2333 10.2833 18.4 11.1111 18.4 12C18.4 12.8778 18.2333 13.7056 17.9 14.4833C17.5667 15.2611 17.1056 15.9444 16.5167 16.5333C15.9389 17.1111 15.2611 17.5667 14.4833 17.9C13.7167 18.2333 12.8889 18.4 12 18.4ZM12 17.2C13.4444 17.2 14.6722 16.6944 15.6833 15.6833C16.6944 14.6722 17.2 13.4444 17.2 12C17.2 10.5556 16.6944 9.32778 15.6833 8.31667C14.6722 7.30555 13.4444 6.8 12 6.8C10.5556 6.8 9.32778 7.30555 8.31667 8.31667C7.30556 9.32778 6.8 10.5556 6.8 12C6.8 13.4444 7.30556 14.6722 8.31667 15.6833C9.32778 16.6944 10.5556 17.2 12 17.2Z\" fill=\"#212121\"/>\n</g>\n<defs>\n<clipPath id=\"clip0_6083_34804\">\n<rect width=\"24\" height=\"24\" fill=\"white\"/>\n</clipPath>\n</defs>`;\n firebaseText.innerText = 'Preview backend running in this workspace.';\n }\n firebaseText.setAttribute('id', firebaseTextId);\n }\n if (document.readyState === 'loading') {\n window.addEventListener('DOMContentLoaded', setupDom);\n } else {\n setupDom();\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CONSTANTS } from './constants';\nimport { getDefaults } from './defaults';\n\n/**\n * Type placeholder for `WorkerGlobalScope` from `webworker`\n */\ndeclare class WorkerGlobalScope {}\n\n/**\n * Returns navigator.userAgent string or '' if it's not defined.\n * @return user agent string\n */\nexport function getUA(): string {\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator['userAgent'] === 'string'\n ) {\n return navigator['userAgent'];\n } else {\n return '';\n }\n}\n\n/**\n * Detect Cordova / PhoneGap / Ionic frameworks on a mobile device.\n *\n * Deliberately does not rely on checking `file://` URLs (as this fails PhoneGap\n * in the Ripple emulator) nor Cordova `onDeviceReady`, which would normally\n * wait for a callback.\n */\nexport function isMobileCordova(): boolean {\n return (\n typeof window !== 'undefined' &&\n // @ts-ignore Setting up an broadly applicable index signature for Window\n // just to deal with this case would probably be a bad idea.\n !!(window['cordova'] || window['phonegap'] || window['PhoneGap']) &&\n /ios|iphone|ipod|ipad|android|blackberry|iemobile/i.test(getUA())\n );\n}\n\n/**\n * Detect Node.js.\n *\n * @return true if Node.js environment is detected or specified.\n */\n// Node detection logic from: https://github.com/iliakan/detect-node/\nexport function isNode(): boolean {\n const forceEnvironment = getDefaults()?.forceEnvironment;\n if (forceEnvironment === 'node') {\n return true;\n } else if (forceEnvironment === 'browser') {\n return false;\n }\n\n try {\n return (\n Object.prototype.toString.call(global.process) === '[object process]'\n );\n } catch (e) {\n return false;\n }\n}\n\n/**\n * Detect Browser Environment.\n * Note: This will return true for certain test frameworks that are incompletely\n * mimicking a browser, and should not lead to assuming all browser APIs are\n * available.\n */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' || isWebWorker();\n}\n\n/**\n * Detect Web Worker context.\n */\nexport function isWebWorker(): boolean {\n return (\n typeof WorkerGlobalScope !== 'undefined' &&\n typeof self !== 'undefined' &&\n self instanceof WorkerGlobalScope\n );\n}\n\n/**\n * Detect Cloudflare Worker context.\n */\nexport function isCloudflareWorker(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n navigator.userAgent === 'Cloudflare-Workers'\n );\n}\n\n/**\n * Detect browser extensions (Chrome and Firefox at least).\n */\ninterface BrowserRuntime {\n id?: unknown;\n}\ndeclare const chrome: { runtime?: BrowserRuntime };\ndeclare const browser: { runtime?: BrowserRuntime };\nexport function isBrowserExtension(): boolean {\n const runtime =\n typeof chrome === 'object'\n ? chrome.runtime\n : typeof browser === 'object'\n ? browser.runtime\n : undefined;\n return typeof runtime === 'object' && runtime.id !== undefined;\n}\n\n/**\n * Detect React Native.\n *\n * @return true if ReactNative environment is detected.\n */\nexport function isReactNative(): boolean {\n return (\n typeof navigator === 'object' && navigator['product'] === 'ReactNative'\n );\n}\n\n/** Detects Electron apps. */\nexport function isElectron(): boolean {\n return getUA().indexOf('Electron/') >= 0;\n}\n\n/** Detects Internet Explorer. */\nexport function isIE(): boolean {\n const ua = getUA();\n return ua.indexOf('MSIE ') >= 0 || ua.indexOf('Trident/') >= 0;\n}\n\n/** Detects Universal Windows Platform apps. */\nexport function isUWP(): boolean {\n return getUA().indexOf('MSAppHost/') >= 0;\n}\n\n/**\n * Detect whether the current SDK build is the Node version.\n *\n * @return true if it's the Node SDK build.\n */\nexport function isNodeSdk(): boolean {\n return CONSTANTS.NODE_CLIENT === true || CONSTANTS.NODE_ADMIN === true;\n}\n\n/** Returns true if we are running in Safari. */\nexport function isSafari(): boolean {\n return (\n !isNode() &&\n !!navigator.userAgent &&\n navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome')\n );\n}\n\n/** Returns true if we are running in Safari or WebKit */\nexport function isSafariOrWebkit(): boolean {\n return (\n !isNode() &&\n !!navigator.userAgent &&\n (navigator.userAgent.includes('Safari') ||\n navigator.userAgent.includes('WebKit')) &&\n !navigator.userAgent.includes('Chrome')\n );\n}\n\n/**\n * This method checks if indexedDB is supported by current browser/service worker context\n * @return true if indexedDB is supported by current browser/service worker context\n */\nexport function isIndexedDBAvailable(): boolean {\n try {\n return typeof indexedDB === 'object';\n } catch (e) {\n return false;\n }\n}\n\n/**\n * This method validates browser/sw context for indexedDB by opening a dummy indexedDB database and reject\n * if errors occur during the database open operation.\n *\n * @throws exception if current browser/sw context can't run idb.open (ex: Safari iframe, Firefox\n * private browsing)\n */\nexport function validateIndexedDBOpenable(): Promise<boolean> {\n return new Promise((resolve, reject) => {\n try {\n let preExist: boolean = true;\n const DB_CHECK_NAME =\n 'validate-browser-context-for-indexeddb-analytics-module';\n const request = self.indexedDB.open(DB_CHECK_NAME);\n request.onsuccess = () => {\n request.result.close();\n // delete database only when it doesn't pre-exist\n if (!preExist) {\n self.indexedDB.deleteDatabase(DB_CHECK_NAME);\n }\n resolve(true);\n };\n request.onupgradeneeded = () => {\n preExist = false;\n };\n\n request.onerror = () => {\n reject(request.error?.message || '');\n };\n } catch (error) {\n reject(error);\n }\n });\n}\n\n/**\n *\n * This method checks whether cookie is enabled within current browser\n * @return true if cookie is enabled within current browser\n */\nexport function areCookiesEnabled(): boolean {\n if (typeof navigator === 'undefined' || !navigator.cookieEnabled) {\n return false;\n }\n return true;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * @fileoverview Standardized Firebase Error.\n *\n * Usage:\n *\n * // TypeScript string literals for type-safe codes\n * type Err =\n * 'unknown' |\n * 'object-not-found'\n * ;\n *\n * // Closure enum for type-safe error codes\n * // at-enum {string}\n * var Err = {\n * UNKNOWN: 'unknown',\n * OBJECT_NOT_FOUND: 'object-not-found',\n * }\n *\n * let errors: Map<Err, string> = {\n * 'generic-error': \"Unknown error\",\n * 'file-not-found': \"Could not find file: {$file}\",\n * };\n *\n * // Type-safe function - must pass a valid error code as param.\n * let error = new ErrorFactory<Err>('service', 'Service', errors);\n *\n * ...\n * throw error.create(Err.GENERIC);\n * ...\n * throw error.create(Err.FILE_NOT_FOUND, {'file': fileName});\n * ...\n * // Service: Could not file file: foo.txt (service/file-not-found).\n *\n * catch (e) {\n * assert(e.message === \"Could not find file: foo.txt.\");\n * if ((e as FirebaseError)?.code === 'service/file-not-found') {\n * console.log(\"Could not read file: \" + e['file']);\n * }\n * }\n */\n\nexport type ErrorMap<ErrorCode extends string> = {\n readonly [K in ErrorCode]: string;\n};\n\nconst ERROR_NAME = 'FirebaseError';\n\nexport interface StringLike {\n toString(): string;\n}\n\nexport interface ErrorData {\n [key: string]: unknown;\n}\n\n// Based on code from:\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types\nexport class FirebaseError extends Error {\n /** The custom name for all FirebaseErrors. */\n readonly name: string = ERROR_NAME;\n\n constructor(\n /** The error code for this error. */\n readonly code: string,\n message: string,\n /** Custom data for this error. */\n public customData?: Record<string, unknown>\n ) {\n super(message);\n\n // Fix For ES5\n // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n // TODO(dlarocque): Replace this with `new.target`: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget\n // which we can now use since we no longer target ES5.\n Object.setPrototypeOf(this, FirebaseError.prototype);\n\n // Maintains proper stack trace for where our error was thrown.\n // Only available on V8.\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, ErrorFactory.prototype.create);\n }\n }\n}\n\nexport class ErrorFactory<\n ErrorCode extends string,\n ErrorParams extends { readonly [K in ErrorCode]?: ErrorData } = {}\n> {\n constructor(\n private readonly service: string,\n private readonly serviceName: string,\n private readonly errors: ErrorMap<ErrorCode>\n ) {}\n\n create<K extends ErrorCode>(\n code: K,\n ...data: K extends keyof ErrorParams ? [ErrorParams[K]] : []\n ): FirebaseError {\n const customData = (data[0] as ErrorData) || {};\n const fullCode = `${this.service}/${code}`;\n const template = this.errors[code];\n\n const message = template ? replaceTemplate(template, customData) : 'Error';\n // Service Name: Error message (service/code).\n const fullMessage = `${this.serviceName}: ${message} (${fullCode}).`;\n\n const error = new FirebaseError(fullCode, fullMessage, customData);\n\n return error;\n }\n}\n\nfunction replaceTemplate(template: string, data: ErrorData): string {\n return template.replace(PATTERN, (_, key) => {\n const value = data[key];\n return value != null ? String(value) : `<${key}?>`;\n });\n}\n\nconst PATTERN = /\\{\\$([^}]+)}/g;\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function contains<T extends object>(obj: T, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\nexport function safeGet<T extends object, K extends keyof T>(\n obj: T,\n key: K\n): T[K] | undefined {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n return obj[key];\n } else {\n return undefined;\n }\n}\n\nexport function isEmpty(obj: object): obj is {} {\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n return false;\n }\n }\n return true;\n}\n\nexport function map<K extends string, V, U>(\n obj: { [key in K]: V },\n fn: (value: V, key: K, obj: { [key in K]: V }) => U,\n contextObj?: unknown\n): { [key in K]: U } {\n const res: Partial<{ [key in K]: U }> = {};\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n res[key] = fn.call(contextObj, obj[key], key, obj);\n }\n }\n return res as { [key in K]: U };\n}\n\n/**\n * Deep equal two objects. Support Arrays and Objects.\n */\nexport function deepEqual(a: object, b: object): boolean {\n if (a === b) {\n return true;\n }\n\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n for (const k of aKeys) {\n if (!bKeys.includes(k)) {\n return false;\n }\n\n const aProp = (a as Record<string, unknown>)[k];\n const bProp = (b as Record<string, unknown>)[k];\n if (isObject(aProp) && isObject(bProp)) {\n if (!deepEqual(aProp, bProp)) {\n return false;\n }\n } else if (aProp !== bProp) {\n return false;\n }\n }\n\n for (const k of bKeys) {\n if (!aKeys.includes(k)) {\n return false;\n }\n }\n return true;\n}\n\nfunction isObject(thing: unknown): thing is object {\n return thing !== null && typeof thing === 'object';\n}\n","/**\n * @license\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport interface Compat<T> {\n _delegate: T;\n}\n\nexport function getModularInstance<ExpService>(\n service: Compat<ExpService> | ExpService\n): ExpService {\n if (service && (service as Compat<ExpService>)._delegate) {\n return (service as Compat<ExpService>)._delegate;\n } else {\n return service as ExpService;\n }\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n InstantiationMode,\n InstanceFactory,\n ComponentType,\n Dictionary,\n Name,\n onInstanceCreatedCallback\n} from './types';\n\n/**\n * Component for service name T, e.g. `auth`, `auth-internal`\n */\nexport class Component<T extends Name = Name> {\n multipleInstances = false;\n /**\n * Properties to be added to the service namespace\n */\n serviceProps: Dictionary = {};\n\n instantiationMode = InstantiationMode.LAZY;\n\n onInstanceCreated: onInstanceCreatedCallback<T> | null = null;\n\n /**\n *\n * @param name The public service name, e.g. app, auth, firestore, database\n * @param instanceFactory Service factory responsible for creating the public interface\n * @param type whether the service provided by the component is public or private\n */\n constructor(\n readonly name: T,\n readonly instanceFactory: InstanceFactory<T>,\n readonly type: ComponentType\n ) {}\n\n setInstantiationMode(mode: InstantiationMode): this {\n this.instantiationMode = mode;\n return this;\n }\n\n setMultipleInstances(multipleInstances: boolean): this {\n this.multipleInstances = multipleInstances;\n return this;\n }\n\n setServiceProps(props: Dictionary): this {\n this.serviceProps = props;\n return this;\n }\n\n setInstanceCreatedCallback(callback: onInstanceCreatedCallback<T>): this {\n this.onInstanceCreated = callback;\n return this;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport type LogLevelString =\n | 'debug'\n | 'verbose'\n | 'info'\n | 'warn'\n | 'error'\n | 'silent';\n\nexport interface LogOptions {\n level: LogLevelString;\n}\n\nexport type LogCallback = (callbackParams: LogCallbackParams) => void;\n\nexport interface LogCallbackParams {\n level: LogLevelString;\n message: string;\n args: unknown[];\n type: string;\n}\n\n/**\n * A container for all of the Logger instances\n */\nexport const instances: Logger[] = [];\n\n/**\n * The JS SDK supports 5 log levels and also allows a user the ability to\n * silence the logs altogether.\n *\n * The order is a follows:\n * DEBUG < VERBOSE < INFO < WARN < ERROR\n *\n * All of the log types above the current log level will be captured (i.e. if\n * you set the log level to `INFO`, errors will still be logged, but `DEBUG` and\n * `VERBOSE` logs will not)\n */\nexport enum LogLevel {\n DEBUG,\n VERBOSE,\n INFO,\n WARN,\n ERROR,\n SILENT\n}\n\nconst levelStringToEnum: { [key in LogLevelString]: LogLevel } = {\n 'debug': LogLevel.DEBUG,\n 'verbose': LogLevel.VERBOSE,\n 'info': LogLevel.INFO,\n 'warn': LogLevel.WARN,\n 'error': LogLevel.ERROR,\n 'silent': LogLevel.SILENT\n};\n\n/**\n * The default log level\n */\nconst defaultLogLevel: LogLevel = LogLevel.INFO;\n\n/**\n * We allow users the ability to pass their own log handler. We will pass the\n * type of log, the current log level, and any other arguments passed (i.e. the\n * messages that the user wants to log) to this function.\n */\nexport type LogHandler = (\n loggerInstance: Logger,\n logType: LogLevel,\n ...args: unknown[]\n) => void;\n\n/**\n * By default, `console.debug` is not displayed in the developer console (in\n * chrome). To avoid forcing users to have to opt-in to these logs twice\n * (i.e. once for firebase, and once in the console), we are sending `DEBUG`\n * logs to the `console.log` function.\n */\nconst ConsoleMethod = {\n [LogLevel.DEBUG]: 'log',\n [LogLevel.VERBOSE]: 'log',\n [LogLevel.INFO]: 'info',\n [LogLevel.WARN]: 'warn',\n [LogLevel.ERROR]: 'error'\n};\n\n/**\n * The default log handler will forward DEBUG, VERBOSE, INFO, WARN, and ERROR\n * messages on to their corresponding console counterparts (if the log method\n * is supported by the current log level)\n */\nconst defaultLogHandler: LogHandler = (instance, logType, ...args): void => {\n if (logType < instance.logLevel) {\n return;\n }\n const now = new Date().toISOString();\n const method = ConsoleMethod[logType as keyof typeof ConsoleMethod];\n if (method) {\n console[method as 'log' | 'info' | 'warn' | 'error'](\n `[${now}] ${instance.name}:`,\n ...args\n );\n } else {\n throw new Error(\n `Attempted to log a message with an invalid logType (value: ${logType})`\n );\n }\n};\n\nexport class Logger {\n /**\n * Gives you an instance of a Logger to capture messages according to\n * Firebase's logging scheme.\n *\n * @param name The name that the logs will be associated with\n */\n constructor(public name: string) {\n /**\n * Capture the current instance for later use\n */\n instances.push(this);\n }\n\n /**\n * The log level of the given Logger instance.\n */\n private _logLevel = defaultLogLevel;\n\n get logLevel(): LogLevel {\n return this._logLevel;\n }\n\n set logLevel(val: LogLevel) {\n if (!(val in LogLevel)) {\n throw new TypeError(`Invalid value \"${val}\" assigned to \\`logLevel\\``);\n }\n this._logLevel = val;\n }\n\n // Workaround for setter/getter having to be the same type.\n setLogLevel(val: LogLevel | LogLevelString): void {\n this._logLevel = typeof val === 'string' ? levelStringToEnum[val] : val;\n }\n\n /**\n * The main (internal) log handler for the Logger instance.\n * Can be set to a new function in internal package code but not by user.\n */\n private _logHandler: LogHandler = defaultLogHandler;\n get logHandler(): LogHandler {\n return this._logHandler;\n }\n set logHandler(val: LogHandler) {\n if (typeof val !== 'function') {\n throw new TypeError('Value assigned to `logHandler` must be a function');\n }\n this._logHandler = val;\n }\n\n /**\n * The optional, additional, user-defined log handler for the Logger instance.\n */\n private _userLogHandler: LogHandler | null = null;\n get userLogHandler(): LogHandler | null {\n return this._userLogHandler;\n }\n set userLogHandler(val: LogHandler | null) {\n this._userLogHandler = val;\n }\n\n /**\n * The functions below are all based on the `console` interface\n */\n\n debug(...args: unknown[]): void {\n this._userLogHandler && this._userLogHandler(this, LogLevel.DEBUG, ...args);\n this._logHandler(this, LogLevel.DEBUG, ...args);\n }\n log(...args: unknown[]): void {\n this._userLogHandler &&\n this._userLogHandler(this, LogLevel.VERBOSE, ...args);\n this._logHandler(this, LogLevel.VERBOSE, ...args);\n }\n info(...args: unknown[]): void {\n this._userLogHandler && this._userLogHandler(this, LogLevel.INFO, ...args);\n this._logHandler(this, LogLevel.INFO, ...args);\n }\n warn(...args: unknown[]): void {\n this._userLogHandler && this._userLogHandler(this, LogLevel.WARN, ...args);\n this._logHandler(this, LogLevel.WARN, ...args);\n }\n error(...args: unknown[]): void {\n this._userLogHandler && this._userLogHandler(this, LogLevel.ERROR, ...args);\n this._logHandler(this, LogLevel.ERROR, ...args);\n }\n}\n\nexport function setLogLevel(level: LogLevelString | LogLevel): void {\n instances.forEach(inst => {\n inst.setLogLevel(level);\n });\n}\n\nexport function setUserLogHandler(\n logCallback: LogCallback | null,\n options?: LogOptions\n): void {\n for (const instance of instances) {\n let customLogLevel: LogLevel | null = null;\n if (options && options.level) {\n customLogLevel = levelStringToEnum[options.level];\n }\n if (logCallback === null) {\n instance.userLogHandler = null;\n } else {\n instance.userLogHandler = (\n instance: Logger,\n level: LogLevel,\n ...args: unknown[]\n ) => {\n const message = args\n .map(arg => {\n if (arg == null) {\n return null;\n } else if (typeof arg === 'string') {\n return arg;\n } else if (typeof arg === 'number' || typeof arg === 'boolean') {\n return arg.toString();\n } else if (arg instanceof Error) {\n return arg.message;\n } else {\n try {\n return JSON.stringify(arg);\n } catch (ignored) {\n return null;\n }\n }\n })\n .filter(arg => arg)\n .join(' ');\n if (level >= (customLogLevel ?? instance.logLevel)) {\n logCallback({\n level: LogLevel[level].toLowerCase() as LogLevelString,\n message,\n args,\n type: instance.name\n });\n }\n };\n }\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { fail } from '../util/assert';\nimport { Code } from '../util/error';\nimport { logError } from '../util/log';\n\n/**\n * Error Codes describing the different ways GRPC can fail. These are copied\n * directly from GRPC's sources here:\n *\n * https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h\n *\n * Important! The names of these identifiers matter because the string forms\n * are used for reverse lookups from the webchannel stream. Do NOT change the\n * names of these identifiers or change this into a const enum.\n */\nenum RpcCode {\n OK = 0,\n CANCELLED = 1,\n UNKNOWN = 2,\n INVALID_ARGUMENT = 3,\n DEADLINE_EXCEEDED = 4,\n NOT_FOUND = 5,\n ALREADY_EXISTS = 6,\n PERMISSION_DENIED = 7,\n UNAUTHENTICATED = 16,\n RESOURCE_EXHAUSTED = 8,\n FAILED_PRECONDITION = 9,\n ABORTED = 10,\n OUT_OF_RANGE = 11,\n UNIMPLEMENTED = 12,\n INTERNAL = 13,\n UNAVAILABLE = 14,\n DATA_LOSS = 15\n}\n\n/**\n * Determines whether an error code represents a permanent error when received\n * in response to a non-write operation.\n *\n * See isPermanentWriteError for classifying write errors.\n */\nexport function isPermanentError(code: Code): boolean {\n switch (code) {\n case Code.OK:\n return fail(0xfdaa, 'Treated status OK as error');\n case Code.CANCELLED:\n case Code.UNKNOWN:\n case Code.DEADLINE_EXCEEDED:\n case Code.RESOURCE_EXHAUSTED:\n case Code.INTERNAL:\n case Code.UNAVAILABLE:\n // Unauthenticated means something went wrong with our token and we need\n // to retry with new credentials which will happen automatically.\n case Code.UNAUTHENTICATED:\n return false;\n case Code.INVALID_ARGUMENT:\n case Code.NOT_FOUND:\n case Code.ALREADY_EXISTS:\n case Code.PERMISSION_DENIED:\n case Code.FAILED_PRECONDITION:\n // Aborted might be retried in some scenarios, but that is dependent on\n // the context and should handled individually by the calling code.\n // See https://cloud.google.com/apis/design/errors.\n case Code.ABORTED:\n case Code.OUT_OF_RANGE:\n case Code.UNIMPLEMENTED:\n case Code.DATA_LOSS:\n return true;\n default:\n return fail(0x3c6b, 'Unknown status code', { code });\n }\n}\n\n/**\n * Determines whether an error code represents a permanent error when received\n * in response to a write operation.\n *\n * Write operations must be handled specially because as of b/119437764, ABORTED\n * errors on the write stream should be retried too (even though ABORTED errors\n * are not generally retryable).\n *\n * Note that during the initial handshake on the write stream an ABORTED error\n * signals that we should discard our stream token (i.e. it is permanent). This\n * means a handshake error should be classified with isPermanentError, above.\n */\nexport function isPermanentWriteError(code: Code): boolean {\n return isPermanentError(code) && code !== Code.ABORTED;\n}\n\n/**\n * Maps an error Code from a GRPC status identifier like 'NOT_FOUND'.\n *\n * @returns The Code equivalent to the given status string or undefined if\n * there is no match.\n */\nexport function mapCodeFromRpcStatus(status: string): Code | undefined {\n // lookup by string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const code: RpcCode = RpcCode[status as any] as any;\n if (code === undefined) {\n return undefined;\n }\n\n return mapCodeFromRpcCode(code);\n}\n\n/**\n * Maps an error Code from GRPC status code number, like 0, 1, or 14. These\n * are not the same as HTTP status codes.\n *\n * @returns The Code equivalent to the given GRPC status code. Fails if there\n * is no match.\n */\nexport function mapCodeFromRpcCode(code: number | undefined): Code {\n if (code === undefined) {\n // This shouldn't normally happen, but in certain error cases (like trying\n // to send invalid proto messages) we may get an error with no GRPC code.\n logError('GRPC error has no .code');\n return Code.UNKNOWN;\n }\n\n switch (code) {\n case RpcCode.OK:\n return Code.OK;\n case RpcCode.CANCELLED:\n return Code.CANCELLED;\n case RpcCode.UNKNOWN:\n return Code.UNKNOWN;\n case RpcCode.DEADLINE_EXCEEDED:\n return Code.DEADLINE_EXCEEDED;\n case RpcCode.RESOURCE_EXHAUSTED:\n return Code.RESOURCE_EXHAUSTED;\n case RpcCode.INTERNAL:\n return Code.INTERNAL;\n case RpcCode.UNAVAILABLE:\n return Code.UNAVAILABLE;\n case RpcCode.UNAUTHENTICATED:\n return Code.UNAUTHENTICATED;\n case RpcCode.INVALID_ARGUMENT:\n return Code.INVALID_ARGUMENT;\n case RpcCode.NOT_FOUND:\n return Code.NOT_FOUND;\n case RpcCode.ALREADY_EXISTS:\n return Code.ALREADY_EXISTS;\n case RpcCode.PERMISSION_DENIED:\n return Code.PERMISSION_DENIED;\n case RpcCode.FAILED_PRECONDITION:\n return Code.FAILED_PRECONDITION;\n case RpcCode.ABORTED:\n return Code.ABORTED;\n case RpcCode.OUT_OF_RANGE:\n return Code.OUT_OF_RANGE;\n case RpcCode.UNIMPLEMENTED:\n return Code.UNIMPLEMENTED;\n case RpcCode.DATA_LOSS:\n return Code.DATA_LOSS;\n default:\n return fail(0x999b, 'Unknown status code', { code });\n }\n}\n\n/**\n * Maps an RPC code from a Code. This is the reverse operation from\n * mapCodeFromRpcCode and should really only be used in tests.\n */\nexport function mapRpcCodeFromCode(code: Code | undefined): number {\n if (code === undefined) {\n return RpcCode.OK;\n }\n\n switch (code) {\n case Code.OK:\n return RpcCode.OK;\n case Code.CANCELLED:\n return RpcCode.CANCELLED;\n case Code.UNKNOWN:\n return RpcCode.UNKNOWN;\n case Code.DEADLINE_EXCEEDED:\n return RpcCode.DEADLINE_EXCEEDED;\n case Code.RESOURCE_EXHAUSTED:\n return RpcCode.RESOURCE_EXHAUSTED;\n case Code.INTERNAL:\n return RpcCode.INTERNAL;\n case Code.UNAVAILABLE:\n return RpcCode.UNAVAILABLE;\n case Code.UNAUTHENTICATED:\n return RpcCode.UNAUTHENTICATED;\n case Code.INVALID_ARGUMENT:\n return RpcCode.INVALID_ARGUMENT;\n case Code.NOT_FOUND:\n return RpcCode.NOT_FOUND;\n case Code.ALREADY_EXISTS:\n return RpcCode.ALREADY_EXISTS;\n case Code.PERMISSION_DENIED:\n return RpcCode.PERMISSION_DENIED;\n case Code.FAILED_PRECONDITION:\n return RpcCode.FAILED_PRECONDITION;\n case Code.ABORTED:\n return RpcCode.ABORTED;\n case Code.OUT_OF_RANGE:\n return RpcCode.OUT_OF_RANGE;\n case Code.UNIMPLEMENTED:\n return RpcCode.UNIMPLEMENTED;\n case Code.DATA_LOSS:\n return RpcCode.DATA_LOSS;\n default:\n return fail(0x3019, 'Unknown status code', { code });\n }\n}\n\n/**\n * Converts an HTTP Status Code to the equivalent error code.\n *\n * @param status - An HTTP Status Code, like 200, 404, 503, etc.\n * @returns The equivalent Code. Unknown status codes are mapped to\n * Code.UNKNOWN.\n */\nexport function mapCodeFromHttpStatus(status?: number): Code {\n if (status === undefined) {\n logError('RPC_ERROR', 'HTTP error has no status');\n return Code.UNKNOWN;\n }\n\n // The canonical error codes for Google APIs [1] specify mapping onto HTTP\n // status codes but the mapping is not bijective. In each case of ambiguity\n // this function chooses a primary error.\n //\n // [1]\n // https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto\n switch (status) {\n case 200: // OK\n return Code.OK;\n\n case 400: // Bad Request\n return Code.FAILED_PRECONDITION;\n // Other possibilities based on the forward mapping\n // return Code.INVALID_ARGUMENT;\n // return Code.OUT_OF_RANGE;\n\n case 401: // Unauthorized\n return Code.UNAUTHENTICATED;\n\n case 403: // Forbidden\n return Code.PERMISSION_DENIED;\n\n case 404: // Not Found\n return Code.NOT_FOUND;\n\n case 409: // Conflict\n return Code.ABORTED;\n // Other possibilities:\n // return Code.ALREADY_EXISTS;\n\n case 416: // Range Not Satisfiable\n return Code.OUT_OF_RANGE;\n\n case 429: // Too Many Requests\n return Code.RESOURCE_EXHAUSTED;\n\n case 499: // Client Closed Request\n return Code.CANCELLED;\n\n case 500: // Internal Server Error\n return Code.UNKNOWN;\n // Other possibilities:\n // return Code.INTERNAL;\n // return Code.DATA_LOSS;\n\n case 501: // Unimplemented\n return Code.UNIMPLEMENTED;\n\n case 503: // Service Unavailable\n return Code.UNAVAILABLE;\n\n case 504: // Gateway Timeout\n return Code.DEADLINE_EXCEEDED;\n\n default:\n if (status >= 200 && status < 300) {\n return Code.OK;\n }\n if (status >= 400 && status < 500) {\n return Code.FAILED_PRECONDITION;\n }\n if (status >= 500 && status < 600) {\n return Code.INTERNAL;\n }\n return Code.UNKNOWN;\n }\n}\n\n/**\n * Converts an HTTP response's error status to the equivalent error code.\n *\n * @param status - An HTTP error response status (\"FAILED_PRECONDITION\",\n * \"UNKNOWN\", etc.)\n * @returns The equivalent Code. Non-matching responses are mapped to\n * Code.UNKNOWN.\n */\nexport function mapCodeFromHttpResponseErrorStatus(status: string): Code {\n const serverError = status.toLowerCase().replace(/_/g, '-');\n return Object.values(Code).indexOf(serverError as Code) >= 0\n ? (serverError as Code)\n : Code.UNKNOWN;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert, debugCast } from '../util/assert';\nimport { wrapInUserErrorIfRecoverable } from '../util/async_queue';\nimport { Code, FirestoreError } from '../util/error';\nimport { EventHandler } from '../util/misc';\nimport { ObjectMap } from '../util/obj_map';\n\nimport { canonifyQuery, Query, queryEquals, stringifyQuery } from './query';\nimport { OnlineState } from './types';\nimport { ChangeType, DocumentViewChange, ViewSnapshot } from './view_snapshot';\n\n/**\n * Holds the listeners and the last received ViewSnapshot for a query being\n * tracked by EventManager.\n */\nclass QueryListenersInfo {\n viewSnap: ViewSnapshot | undefined = undefined;\n listeners: QueryListener[] = [];\n\n // Helper methods that checks if the query has listeners that listening to remote store\n hasRemoteListeners(): boolean {\n return this.listeners.some(listener => listener.listensToRemoteStore());\n }\n}\n\n/**\n * Interface for handling events from the EventManager.\n */\nexport interface Observer<T> {\n next: EventHandler<T>;\n error: EventHandler<FirestoreError>;\n}\n\n/**\n * EventManager is responsible for mapping queries to query event emitters.\n * It handles \"fan-out\". -- Identical queries will re-use the same watch on the\n * backend.\n *\n * PORTING NOTE: On Web, EventManager `onListen` and `onUnlisten` need to be\n * assigned to SyncEngine's `listen()` and `unlisten()` API before usage. This\n * allows users to tree-shake the Watch logic.\n */\nexport interface EventManager {\n onListen?: (\n query: Query,\n enableRemoteListen: boolean\n ) => Promise<ViewSnapshot>;\n onUnlisten?: (query: Query, disableRemoteListen: boolean) => Promise<void>;\n onFirstRemoteStoreListen?: (query: Query) => Promise<void>;\n onLastRemoteStoreUnlisten?: (query: Query) => Promise<void>;\n terminate(): void;\n}\n\nexport function newEventManager(): EventManager {\n return new EventManagerImpl();\n}\n\nexport class EventManagerImpl implements EventManager {\n queries: ObjectMap<Query, QueryListenersInfo> = newQueriesObjectMap();\n\n onlineState: OnlineState = OnlineState.Unknown;\n\n snapshotsInSyncListeners: Set<Observer<void>> = new Set();\n\n /** Callback invoked when a Query is first listen to. */\n onListen?: (\n query: Query,\n enableRemoteListen: boolean\n ) => Promise<ViewSnapshot>;\n /** Callback invoked once all listeners to a Query are removed. */\n onUnlisten?: (query: Query, disableRemoteListen: boolean) => Promise<void>;\n\n /**\n * Callback invoked when a Query starts listening to the remote store, while\n * already listening to the cache.\n */\n onFirstRemoteStoreListen?: (query: Query) => Promise<void>;\n /**\n * Callback invoked when a Query stops listening to the remote store, while\n * still listening to the cache.\n */\n onLastRemoteStoreUnlisten?: (query: Query) => Promise<void>;\n\n terminate(): void {\n errorAllTargets(\n this,\n new FirestoreError(Code.ABORTED, 'Firestore shutting down')\n );\n }\n}\n\nfunction newQueriesObjectMap(): ObjectMap<Query, QueryListenersInfo> {\n return new ObjectMap<Query, QueryListenersInfo>(\n q => canonifyQuery(q),\n queryEquals\n );\n}\n\nfunction validateEventManager(eventManagerImpl: EventManagerImpl): void {\n debugAssert(!!eventManagerImpl.onListen, 'onListen not set');\n debugAssert(\n !!eventManagerImpl.onFirstRemoteStoreListen,\n 'onFirstRemoteStoreListen not set'\n );\n debugAssert(!!eventManagerImpl.onUnlisten, 'onUnlisten not set');\n debugAssert(\n !!eventManagerImpl.onLastRemoteStoreUnlisten,\n 'onLastRemoteStoreUnlisten not set'\n );\n}\n\nconst enum ListenerSetupAction {\n InitializeLocalListenAndRequireWatchConnection,\n InitializeLocalListenOnly,\n RequireWatchConnectionOnly,\n NoActionRequired\n}\n\nconst enum ListenerRemovalAction {\n TerminateLocalListenAndRequireWatchDisconnection,\n TerminateLocalListenOnly,\n RequireWatchDisconnectionOnly,\n NoActionRequired\n}\n\nexport async function eventManagerListen(\n eventManager: EventManager,\n listener: QueryListener\n): Promise<void> {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n validateEventManager(eventManagerImpl);\n\n let listenerAction = ListenerSetupAction.NoActionRequired;\n\n const query = listener.query;\n\n let queryInfo = eventManagerImpl.queries.get(query);\n if (!queryInfo) {\n queryInfo = new QueryListenersInfo();\n listenerAction = listener.listensToRemoteStore()\n ? ListenerSetupAction.InitializeLocalListenAndRequireWatchConnection\n : ListenerSetupAction.InitializeLocalListenOnly;\n } else if (\n !queryInfo.hasRemoteListeners() &&\n listener.listensToRemoteStore()\n ) {\n // Query has been listening to local cache, and tries to add a new listener sourced from watch.\n listenerAction = ListenerSetupAction.RequireWatchConnectionOnly;\n }\n\n try {\n switch (listenerAction) {\n case ListenerSetupAction.InitializeLocalListenAndRequireWatchConnection:\n queryInfo.viewSnap = await eventManagerImpl.onListen!(\n query,\n /** enableRemoteListen= */ true\n );\n break;\n case ListenerSetupAction.InitializeLocalListenOnly:\n queryInfo.viewSnap = await eventManagerImpl.onListen!(\n query,\n /** enableRemoteListen= */ false\n );\n break;\n case ListenerSetupAction.RequireWatchConnectionOnly:\n await eventManagerImpl.onFirstRemoteStoreListen!(query);\n break;\n default:\n break;\n }\n } catch (e) {\n const firestoreError = wrapInUserErrorIfRecoverable(\n e as Error,\n `Initialization of query '${stringifyQuery(listener.query)}' failed`\n );\n listener.onError(firestoreError);\n return;\n }\n\n eventManagerImpl.queries.set(query, queryInfo);\n queryInfo.listeners.push(listener);\n\n // Run global snapshot listeners if a consistent snapshot has been emitted.\n const raisedEvent = listener.applyOnlineStateChange(\n eventManagerImpl.onlineState\n );\n debugAssert(\n !raisedEvent,\n \"applyOnlineStateChange() shouldn't raise an event for brand-new listeners.\"\n );\n\n if (queryInfo.viewSnap) {\n const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);\n if (raisedEvent) {\n raiseSnapshotsInSyncEvent(eventManagerImpl);\n }\n }\n}\n\nexport async function eventManagerUnlisten(\n eventManager: EventManager,\n listener: QueryListener\n): Promise<void> {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n validateEventManager(eventManagerImpl);\n\n const query = listener.query;\n let listenerAction = ListenerRemovalAction.NoActionRequired;\n\n const queryInfo = eventManagerImpl.queries.get(query);\n if (queryInfo) {\n const i = queryInfo.listeners.indexOf(listener);\n if (i >= 0) {\n queryInfo.listeners.splice(i, 1);\n\n if (queryInfo.listeners.length === 0) {\n listenerAction = listener.listensToRemoteStore()\n ? ListenerRemovalAction.TerminateLocalListenAndRequireWatchDisconnection\n : ListenerRemovalAction.TerminateLocalListenOnly;\n } else if (\n !queryInfo.hasRemoteListeners() &&\n listener.listensToRemoteStore()\n ) {\n // The removed listener is the last one that sourced from watch.\n listenerAction = ListenerRemovalAction.RequireWatchDisconnectionOnly;\n }\n }\n }\n switch (listenerAction) {\n case ListenerRemovalAction.TerminateLocalListenAndRequireWatchDisconnection:\n eventManagerImpl.queries.delete(query);\n return eventManagerImpl.onUnlisten!(\n query,\n /** disableRemoteListen= */ true\n );\n case ListenerRemovalAction.TerminateLocalListenOnly:\n eventManagerImpl.queries.delete(query);\n return eventManagerImpl.onUnlisten!(\n query,\n /** disableRemoteListen= */ false\n );\n case ListenerRemovalAction.RequireWatchDisconnectionOnly:\n return eventManagerImpl.onLastRemoteStoreUnlisten!(query);\n default:\n return;\n }\n}\n\nexport function eventManagerOnWatchChange(\n eventManager: EventManager,\n viewSnaps: ViewSnapshot[]\n): void {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n\n let raisedEvent = false;\n for (const viewSnap of viewSnaps) {\n const query = viewSnap.query;\n const queryInfo = eventManagerImpl.queries.get(query);\n if (queryInfo) {\n for (const listener of queryInfo.listeners) {\n if (listener.onViewSnapshot(viewSnap)) {\n raisedEvent = true;\n }\n }\n queryInfo.viewSnap = viewSnap;\n }\n }\n if (raisedEvent) {\n raiseSnapshotsInSyncEvent(eventManagerImpl);\n }\n}\n\nexport function eventManagerOnWatchError(\n eventManager: EventManager,\n query: Query,\n error: FirestoreError\n): void {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n\n const queryInfo = eventManagerImpl.queries.get(query);\n if (queryInfo) {\n for (const listener of queryInfo.listeners) {\n listener.onError(error);\n }\n }\n\n // Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()\n // after an error.\n eventManagerImpl.queries.delete(query);\n}\n\nexport function eventManagerOnOnlineStateChange(\n eventManager: EventManager,\n onlineState: OnlineState\n): void {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n\n eventManagerImpl.onlineState = onlineState;\n let raisedEvent = false;\n eventManagerImpl.queries.forEach((_, queryInfo) => {\n for (const listener of queryInfo.listeners) {\n // Run global snapshot listeners if a consistent snapshot has been emitted.\n if (listener.applyOnlineStateChange(onlineState)) {\n raisedEvent = true;\n }\n }\n });\n if (raisedEvent) {\n raiseSnapshotsInSyncEvent(eventManagerImpl);\n }\n}\n\nexport function addSnapshotsInSyncListener(\n eventManager: EventManager,\n observer: Observer<void>\n): void {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n\n eventManagerImpl.snapshotsInSyncListeners.add(observer);\n // Immediately fire an initial event, indicating all existing listeners\n // are in-sync.\n observer.next();\n}\n\nexport function removeSnapshotsInSyncListener(\n eventManager: EventManager,\n observer: Observer<void>\n): void {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n eventManagerImpl.snapshotsInSyncListeners.delete(observer);\n}\n\nfunction errorAllTargets(\n eventManager: EventManager,\n error: FirestoreError\n): void {\n const eventManagerImpl = debugCast(eventManager, EventManagerImpl);\n const queries = eventManagerImpl.queries;\n\n // Prevent further access by clearing ObjectMap.\n eventManagerImpl.queries = newQueriesObjectMap();\n\n queries.forEach((_, queryInfo) => {\n for (const listener of queryInfo.listeners) {\n listener.onError(error);\n }\n });\n}\n\n// Call all global snapshot listeners that have been set.\nfunction raiseSnapshotsInSyncEvent(eventManagerImpl: EventManagerImpl): void {\n eventManagerImpl.snapshotsInSyncListeners.forEach(observer => {\n observer.next();\n });\n}\n\nexport enum ListenerDataSource {\n /** Listen to both cache and server changes */\n Default = 'default',\n\n /** Listen to changes in cache only */\n Cache = 'cache'\n}\n\nexport interface ListenOptions {\n /** Raise events even when only the metadata changes */\n readonly includeMetadataChanges?: boolean;\n\n /**\n * Wait for a sync with the server when online, but still raise events while\n * offline.\n */\n readonly waitForSyncWhenOnline?: boolean;\n\n /** Set the source events raised from. */\n readonly source?: ListenerDataSource;\n}\n\n/**\n * QueryListener takes a series of internal view snapshots and determines\n * when to raise the event.\n *\n * It uses an Observer to dispatch events.\n */\nexport class QueryListener {\n /**\n * Initial snapshots (e.g. from cache) may not be propagated to the wrapped\n * observer. This flag is set to true once we've actually raised an event.\n */\n private raisedInitialEvent = false;\n\n private options: ListenOptions;\n\n private snap: ViewSnapshot | null = null;\n\n private onlineState = OnlineState.Unknown;\n\n constructor(\n readonly query: Query,\n private queryObserver: Observer<ViewSnapshot>,\n options?: ListenOptions\n ) {\n this.options = options || {};\n }\n\n /**\n * Applies the new ViewSnapshot to this listener, raising a user-facing event\n * if applicable (depending on what changed, whether the user has opted into\n * metadata-only changes, etc.). Returns true if a user-facing event was\n * indeed raised.\n */\n onViewSnapshot(snap: ViewSnapshot): boolean {\n debugAssert(\n snap.docChanges.length > 0 || snap.syncStateChanged,\n 'We got a new snapshot with no changes?'\n );\n\n if (!this.options.includeMetadataChanges) {\n // Remove the metadata only changes.\n const docChanges: DocumentViewChange[] = [];\n for (const docChange of snap.docChanges) {\n if (docChange.type !== ChangeType.Metadata) {\n docChanges.push(docChange);\n }\n }\n snap = new ViewSnapshot(\n snap.query,\n snap.docs,\n snap.oldDocs,\n docChanges,\n snap.mutatedKeys,\n snap.fromCache,\n snap.syncStateChanged,\n /* excludesMetadataChanges= */ true,\n snap.hasCachedResults\n );\n }\n let raisedEvent = false;\n if (!this.raisedInitialEvent) {\n if (this.shouldRaiseInitialEvent(snap, this.onlineState)) {\n this.raiseInitialEvent(snap);\n raisedEvent = true;\n }\n } else if (this.shouldRaiseEvent(snap)) {\n this.queryObserver.next(snap);\n raisedEvent = true;\n }\n\n this.snap = snap;\n return raisedEvent;\n }\n\n onError(error: FirestoreError): void {\n this.queryObserver.error(error);\n }\n\n /** Returns whether a snapshot was raised. */\n applyOnlineStateChange(onlineState: OnlineState): boolean {\n this.onlineState = onlineState;\n let raisedEvent = false;\n if (\n this.snap &&\n !this.raisedInitialEvent &&\n this.shouldRaiseInitialEvent(this.snap, onlineState)\n ) {\n this.raiseInitialEvent(this.snap);\n raisedEvent = true;\n }\n return raisedEvent;\n }\n\n private shouldRaiseInitialEvent(\n snap: ViewSnapshot,\n onlineState: OnlineState\n ): boolean {\n debugAssert(\n !this.raisedInitialEvent,\n 'Determining whether to raise first event but already had first event'\n );\n\n // Always raise the first event when we're synced\n if (!snap.fromCache) {\n return true;\n }\n\n // Always raise event if listening to cache\n if (!this.listensToRemoteStore()) {\n return true;\n }\n\n // NOTE: We consider OnlineState.Unknown as online (it should become Offline\n // or Online if we wait long enough).\n const maybeOnline = onlineState !== OnlineState.Offline;\n // Don't raise the event if we're online, aren't synced yet (checked\n // above) and are waiting for a sync.\n if (this.options.waitForSyncWhenOnline && maybeOnline) {\n debugAssert(\n snap.fromCache,\n 'Waiting for sync, but snapshot is not from cache'\n );\n return false;\n }\n\n // Raise data from cache if we have any documents, have cached results before,\n // or we are offline.\n return (\n !snap.docs.isEmpty() ||\n snap.hasCachedResults ||\n onlineState === OnlineState.Offline\n );\n }\n\n private shouldRaiseEvent(snap: ViewSnapshot): boolean {\n // We don't need to handle includeDocumentMetadataChanges here because\n // the Metadata only changes have already been stripped out if needed.\n // At this point the only changes we will see are the ones we should\n // propagate.\n if (snap.docChanges.length > 0) {\n return true;\n }\n\n const hasPendingWritesChanged =\n this.snap && this.snap.hasPendingWrites !== snap.hasPendingWrites;\n if (snap.syncStateChanged || hasPendingWritesChanged) {\n return this.options.includeMetadataChanges === true;\n }\n\n // Generally we should have hit one of the cases above, but it's possible\n // to get here if there were only metadata docChanges and they got\n // stripped out.\n return false;\n }\n\n private raiseInitialEvent(snap: ViewSnapshot): void {\n debugAssert(\n !this.raisedInitialEvent,\n 'Trying to raise initial events for second time'\n );\n snap = ViewSnapshot.fromInitialDocuments(\n snap.query,\n snap.docs,\n snap.mutatedKeys,\n snap.fromCache,\n snap.hasCachedResults\n );\n this.raisedInitialEvent = true;\n this.queryObserver.next(snap);\n }\n\n listensToRemoteStore(): boolean {\n return this.options.source !== ListenerDataSource.Cache;\n }\n}\n","/** @license\nCopyright The Closure Library Authors.\nSPDX-License-Identifier: Apache-2.0\n*/\n(function() {'use strict';var h;/** @license\n\n Copyright The Closure Library Authors.\n SPDX-License-Identifier: Apache-2.0\n*/\nfunction k(f,a){function c(){}c.prototype=a.prototype;f.D=a.prototype;f.prototype=new c;f.prototype.constructor=f;f.C=function(d,e,g){for(var b=Array(arguments.length-2),r=2;r<arguments.length;r++)b[r-2]=arguments[r];return a.prototype[e].apply(d,b)}};function l(){this.blockSize=-1};function m(){this.blockSize=-1;this.blockSize=64;this.g=Array(4);this.B=Array(this.blockSize);this.o=this.h=0;this.s()}k(m,l);m.prototype.s=function(){this.g[0]=1732584193;this.g[1]=4023233417;this.g[2]=2562383102;this.g[3]=271733878;this.o=this.h=0};\nfunction n(f,a,c){c||(c=0);var d=Array(16);if(\"string\"===typeof a)for(var e=0;16>e;++e)d[e]=a.charCodeAt(c++)|a.charCodeAt(c++)<<8|a.charCodeAt(c++)<<16|a.charCodeAt(c++)<<24;else for(e=0;16>e;++e)d[e]=a[c++]|a[c++]<<8|a[c++]<<16|a[c++]<<24;a=f.g[0];c=f.g[1];e=f.g[2];var g=f.g[3];var b=a+(g^c&(e^g))+d[0]+3614090360&4294967295;a=c+(b<<7&4294967295|b>>>25);b=g+(e^a&(c^e))+d[1]+3905402710&4294967295;g=a+(b<<12&4294967295|b>>>20);b=e+(c^g&(a^c))+d[2]+606105819&4294967295;e=g+(b<<17&4294967295|b>>>15);\nb=c+(a^e&(g^a))+d[3]+3250441966&4294967295;c=e+(b<<22&4294967295|b>>>10);b=a+(g^c&(e^g))+d[4]+4118548399&4294967295;a=c+(b<<7&4294967295|b>>>25);b=g+(e^a&(c^e))+d[5]+1200080426&4294967295;g=a+(b<<12&4294967295|b>>>20);b=e+(c^g&(a^c))+d[6]+2821735955&4294967295;e=g+(b<<17&4294967295|b>>>15);b=c+(a^e&(g^a))+d[7]+4249261313&4294967295;c=e+(b<<22&4294967295|b>>>10);b=a+(g^c&(e^g))+d[8]+1770035416&4294967295;a=c+(b<<7&4294967295|b>>>25);b=g+(e^a&(c^e))+d[9]+2336552879&4294967295;g=a+(b<<12&4294967295|\nb>>>20);b=e+(c^g&(a^c))+d[10]+4294925233&4294967295;e=g+(b<<17&4294967295|b>>>15);b=c+(a^e&(g^a))+d[11]+2304563134&4294967295;c=e+(b<<22&4294967295|b>>>10);b=a+(g^c&(e^g))+d[12]+1804603682&4294967295;a=c+(b<<7&4294967295|b>>>25);b=g+(e^a&(c^e))+d[13]+4254626195&4294967295;g=a+(b<<12&4294967295|b>>>20);b=e+(c^g&(a^c))+d[14]+2792965006&4294967295;e=g+(b<<17&4294967295|b>>>15);b=c+(a^e&(g^a))+d[15]+1236535329&4294967295;c=e+(b<<22&4294967295|b>>>10);b=a+(e^g&(c^e))+d[1]+4129170786&4294967295;a=c+(b<<\n5&4294967295|b>>>27);b=g+(c^e&(a^c))+d[6]+3225465664&4294967295;g=a+(b<<9&4294967295|b>>>23);b=e+(a^c&(g^a))+d[11]+643717713&4294967295;e=g+(b<<14&4294967295|b>>>18);b=c+(g^a&(e^g))+d[0]+3921069994&4294967295;c=e+(b<<20&4294967295|b>>>12);b=a+(e^g&(c^e))+d[5]+3593408605&4294967295;a=c+(b<<5&4294967295|b>>>27);b=g+(c^e&(a^c))+d[10]+38016083&4294967295;g=a+(b<<9&4294967295|b>>>23);b=e+(a^c&(g^a))+d[15]+3634488961&4294967295;e=g+(b<<14&4294967295|b>>>18);b=c+(g^a&(e^g))+d[4]+3889429448&4294967295;c=\ne+(b<<20&4294967295|b>>>12);b=a+(e^g&(c^e))+d[9]+568446438&4294967295;a=c+(b<<5&4294967295|b>>>27);b=g+(c^e&(a^c))+d[14]+3275163606&4294967295;g=a+(b<<9&4294967295|b>>>23);b=e+(a^c&(g^a))+d[3]+4107603335&4294967295;e=g+(b<<14&4294967295|b>>>18);b=c+(g^a&(e^g))+d[8]+1163531501&4294967295;c=e+(b<<20&4294967295|b>>>12);b=a+(e^g&(c^e))+d[13]+2850285829&4294967295;a=c+(b<<5&4294967295|b>>>27);b=g+(c^e&(a^c))+d[2]+4243563512&4294967295;g=a+(b<<9&4294967295|b>>>23);b=e+(a^c&(g^a))+d[7]+1735328473&4294967295;\ne=g+(b<<14&4294967295|b>>>18);b=c+(g^a&(e^g))+d[12]+2368359562&4294967295;c=e+(b<<20&4294967295|b>>>12);b=a+(c^e^g)+d[5]+4294588738&4294967295;a=c+(b<<4&4294967295|b>>>28);b=g+(a^c^e)+d[8]+2272392833&4294967295;g=a+(b<<11&4294967295|b>>>21);b=e+(g^a^c)+d[11]+1839030562&4294967295;e=g+(b<<16&4294967295|b>>>16);b=c+(e^g^a)+d[14]+4259657740&4294967295;c=e+(b<<23&4294967295|b>>>9);b=a+(c^e^g)+d[1]+2763975236&4294967295;a=c+(b<<4&4294967295|b>>>28);b=g+(a^c^e)+d[4]+1272893353&4294967295;g=a+(b<<11&4294967295|\nb>>>21);b=e+(g^a^c)+d[7]+4139469664&4294967295;e=g+(b<<16&4294967295|b>>>16);b=c+(e^g^a)+d[10]+3200236656&4294967295;c=e+(b<<23&4294967295|b>>>9);b=a+(c^e^g)+d[13]+681279174&4294967295;a=c+(b<<4&4294967295|b>>>28);b=g+(a^c^e)+d[0]+3936430074&4294967295;g=a+(b<<11&4294967295|b>>>21);b=e+(g^a^c)+d[3]+3572445317&4294967295;e=g+(b<<16&4294967295|b>>>16);b=c+(e^g^a)+d[6]+76029189&4294967295;c=e+(b<<23&4294967295|b>>>9);b=a+(c^e^g)+d[9]+3654602809&4294967295;a=c+(b<<4&4294967295|b>>>28);b=g+(a^c^e)+d[12]+\n3873151461&4294967295;g=a+(b<<11&4294967295|b>>>21);b=e+(g^a^c)+d[15]+530742520&4294967295;e=g+(b<<16&4294967295|b>>>16);b=c+(e^g^a)+d[2]+3299628645&4294967295;c=e+(b<<23&4294967295|b>>>9);b=a+(e^(c|~g))+d[0]+4096336452&4294967295;a=c+(b<<6&4294967295|b>>>26);b=g+(c^(a|~e))+d[7]+1126891415&4294967295;g=a+(b<<10&4294967295|b>>>22);b=e+(a^(g|~c))+d[14]+2878612391&4294967295;e=g+(b<<15&4294967295|b>>>17);b=c+(g^(e|~a))+d[5]+4237533241&4294967295;c=e+(b<<21&4294967295|b>>>11);b=a+(e^(c|~g))+d[12]+1700485571&\n4294967295;a=c+(b<<6&4294967295|b>>>26);b=g+(c^(a|~e))+d[3]+2399980690&4294967295;g=a+(b<<10&4294967295|b>>>22);b=e+(a^(g|~c))+d[10]+4293915773&4294967295;e=g+(b<<15&4294967295|b>>>17);b=c+(g^(e|~a))+d[1]+2240044497&4294967295;c=e+(b<<21&4294967295|b>>>11);b=a+(e^(c|~g))+d[8]+1873313359&4294967295;a=c+(b<<6&4294967295|b>>>26);b=g+(c^(a|~e))+d[15]+4264355552&4294967295;g=a+(b<<10&4294967295|b>>>22);b=e+(a^(g|~c))+d[6]+2734768916&4294967295;e=g+(b<<15&4294967295|b>>>17);b=c+(g^(e|~a))+d[13]+1309151649&\n4294967295;c=e+(b<<21&4294967295|b>>>11);b=a+(e^(c|~g))+d[4]+4149444226&4294967295;a=c+(b<<6&4294967295|b>>>26);b=g+(c^(a|~e))+d[11]+3174756917&4294967295;g=a+(b<<10&4294967295|b>>>22);b=e+(a^(g|~c))+d[2]+718787259&4294967295;e=g+(b<<15&4294967295|b>>>17);b=c+(g^(e|~a))+d[9]+3951481745&4294967295;f.g[0]=f.g[0]+a&4294967295;f.g[1]=f.g[1]+(e+(b<<21&4294967295|b>>>11))&4294967295;f.g[2]=f.g[2]+e&4294967295;f.g[3]=f.g[3]+g&4294967295}\nm.prototype.u=function(f,a){void 0===a&&(a=f.length);for(var c=a-this.blockSize,d=this.B,e=this.h,g=0;g<a;){if(0==e)for(;g<=c;)n(this,f,g),g+=this.blockSize;if(\"string\"===typeof f)for(;g<a;){if(d[e++]=f.charCodeAt(g++),e==this.blockSize){n(this,d);e=0;break}}else for(;g<a;)if(d[e++]=f[g++],e==this.blockSize){n(this,d);e=0;break}}this.h=e;this.o+=a};\nm.prototype.v=function(){var f=Array((56>this.h?this.blockSize:2*this.blockSize)-this.h);f[0]=128;for(var a=1;a<f.length-8;++a)f[a]=0;var c=8*this.o;for(a=f.length-8;a<f.length;++a)f[a]=c&255,c/=256;this.u(f);f=Array(16);for(a=c=0;4>a;++a)for(var d=0;32>d;d+=8)f[c++]=this.g[a]>>>d&255;return f};function p(f,a){var c=q;return Object.prototype.hasOwnProperty.call(c,f)?c[f]:c[f]=a(f)};function t(f,a){this.h=a;for(var c=[],d=!0,e=f.length-1;0<=e;e--){var g=f[e]|0;d&&g==a||(c[e]=g,d=!1)}this.g=c}var q={};function u(f){return-128<=f&&128>f?p(f,function(a){return new t([a|0],0>a?-1:0)}):new t([f|0],0>f?-1:0)}function v(f){if(isNaN(f)||!isFinite(f))return w;if(0>f)return x(v(-f));for(var a=[],c=1,d=0;f>=c;d++)a[d]=f/c|0,c*=4294967296;return new t(a,0)}\nfunction y(f,a){if(0==f.length)throw Error(\"number format error: empty string\");a=a||10;if(2>a||36<a)throw Error(\"radix out of range: \"+a);if(\"-\"==f.charAt(0))return x(y(f.substring(1),a));if(0<=f.indexOf(\"-\"))throw Error('number format error: interior \"-\" character');for(var c=v(Math.pow(a,8)),d=w,e=0;e<f.length;e+=8){var g=Math.min(8,f.length-e),b=parseInt(f.substring(e,e+g),a);8>g?(g=v(Math.pow(a,g)),d=d.j(g).add(v(b))):(d=d.j(c),d=d.add(v(b)))}return d}var w=u(0),z=u(1),A=u(16777216);h=t.prototype;\nh.m=function(){if(B(this))return-x(this).m();for(var f=0,a=1,c=0;c<this.g.length;c++){var d=this.i(c);f+=(0<=d?d:4294967296+d)*a;a*=4294967296}return f};h.toString=function(f){f=f||10;if(2>f||36<f)throw Error(\"radix out of range: \"+f);if(C(this))return\"0\";if(B(this))return\"-\"+x(this).toString(f);for(var a=v(Math.pow(f,6)),c=this,d=\"\";;){var e=D(c,a).g;c=F(c,e.j(a));var g=((0<c.g.length?c.g[0]:c.h)>>>0).toString(f);c=e;if(C(c))return g+d;for(;6>g.length;)g=\"0\"+g;d=g+d}};\nh.i=function(f){return 0>f?0:f<this.g.length?this.g[f]:this.h};function C(f){if(0!=f.h)return!1;for(var a=0;a<f.g.length;a++)if(0!=f.g[a])return!1;return!0}function B(f){return-1==f.h}h.l=function(f){f=F(this,f);return B(f)?-1:C(f)?0:1};function x(f){for(var a=f.g.length,c=[],d=0;d<a;d++)c[d]=~f.g[d];return(new t(c,~f.h)).add(z)}h.abs=function(){return B(this)?x(this):this};\nh.add=function(f){for(var a=Math.max(this.g.length,f.g.length),c=[],d=0,e=0;e<=a;e++){var g=d+(this.i(e)&65535)+(f.i(e)&65535),b=(g>>>16)+(this.i(e)>>>16)+(f.i(e)>>>16);d=b>>>16;g&=65535;b&=65535;c[e]=b<<16|g}return new t(c,c[c.length-1]&-2147483648?-1:0)};function F(f,a){return f.add(x(a))}\nh.j=function(f){if(C(this)||C(f))return w;if(B(this))return B(f)?x(this).j(x(f)):x(x(this).j(f));if(B(f))return x(this.j(x(f)));if(0>this.l(A)&&0>f.l(A))return v(this.m()*f.m());for(var a=this.g.length+f.g.length,c=[],d=0;d<2*a;d++)c[d]=0;for(d=0;d<this.g.length;d++)for(var e=0;e<f.g.length;e++){var g=this.i(d)>>>16,b=this.i(d)&65535,r=f.i(e)>>>16,E=f.i(e)&65535;c[2*d+2*e]+=b*E;G(c,2*d+2*e);c[2*d+2*e+1]+=g*E;G(c,2*d+2*e+1);c[2*d+2*e+1]+=b*r;G(c,2*d+2*e+1);c[2*d+2*e+2]+=g*r;G(c,2*d+2*e+2)}for(d=0;d<\na;d++)c[d]=c[2*d+1]<<16|c[2*d];for(d=a;d<2*a;d++)c[d]=0;return new t(c,0)};function G(f,a){for(;(f[a]&65535)!=f[a];)f[a+1]+=f[a]>>>16,f[a]&=65535,a++}function H(f,a){this.g=f;this.h=a}\nfunction D(f,a){if(C(a))throw Error(\"division by zero\");if(C(f))return new H(w,w);if(B(f))return a=D(x(f),a),new H(x(a.g),x(a.h));if(B(a))return a=D(f,x(a)),new H(x(a.g),a.h);if(30<f.g.length){if(B(f)||B(a))throw Error(\"slowDivide_ only works with positive integers.\");for(var c=z,d=a;0>=d.l(f);)c=I(c),d=I(d);var e=J(c,1),g=J(d,1);d=J(d,2);for(c=J(c,2);!C(d);){var b=g.add(d);0>=b.l(f)&&(e=e.add(c),g=b);d=J(d,1);c=J(c,1)}a=F(f,e.j(a));return new H(e,a)}for(e=w;0<=f.l(a);){c=Math.max(1,Math.floor(f.m()/\na.m()));d=Math.ceil(Math.log(c)/Math.LN2);d=48>=d?1:Math.pow(2,d-48);g=v(c);for(b=g.j(a);B(b)||0<b.l(f);)c-=d,g=v(c),b=g.j(a);C(g)&&(g=z);e=e.add(g);f=F(f,b)}return new H(e,f)}h.A=function(f){return D(this,f).h};h.and=function(f){for(var a=Math.max(this.g.length,f.g.length),c=[],d=0;d<a;d++)c[d]=this.i(d)&f.i(d);return new t(c,this.h&f.h)};h.or=function(f){for(var a=Math.max(this.g.length,f.g.length),c=[],d=0;d<a;d++)c[d]=this.i(d)|f.i(d);return new t(c,this.h|f.h)};\nh.xor=function(f){for(var a=Math.max(this.g.length,f.g.length),c=[],d=0;d<a;d++)c[d]=this.i(d)^f.i(d);return new t(c,this.h^f.h)};function I(f){for(var a=f.g.length+1,c=[],d=0;d<a;d++)c[d]=f.i(d)<<1|f.i(d-1)>>>31;return new t(c,f.h)}function J(f,a){var c=a>>5;a%=32;for(var d=f.g.length-c,e=[],g=0;g<d;g++)e[g]=0<a?f.i(g+c)>>>a|f.i(g+c+1)<<32-a:f.i(g+c);return new t(e,f.h)};m.prototype.digest=m.prototype.v;m.prototype.reset=m.prototype.s;m.prototype.update=m.prototype.u;module.exports.Md5=m;t.prototype.add=t.prototype.add;t.prototype.multiply=t.prototype.j;t.prototype.modulo=t.prototype.A;t.prototype.compare=t.prototype.l;t.prototype.toNumber=t.prototype.m;t.prototype.toString=t.prototype.toString;t.prototype.getBits=t.prototype.i;t.fromNumber=v;t.fromString=y;module.exports.Integer=t;}).apply( typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {});\n","/** @license\nCopyright The Closure Library Authors.\nSPDX-License-Identifier: Apache-2.0\n*/\n(function() {'use strict';var h,aa=\"function\"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};function ba(a){a=[\"object\"==typeof globalThis&&globalThis,a,\"object\"==typeof window&&window,\"object\"==typeof self&&self,\"object\"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error(\"Cannot find global object\");}var ca=ba(this);\nfunction da(a,b){if(b)a:{var c=ca;a=a.split(\".\");for(var d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))break a;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&aa(c,a,{configurable:!0,writable:!0,value:b})}}function ea(a,b){a instanceof String&&(a+=\"\");var c=0,d=!1,e={next:function(){if(!d&&c<a.length){var f=c++;return{value:b(f,a[f]),done:!1}}d=!0;return{done:!0,value:void 0}}};e[Symbol.iterator]=function(){return e};return e}\nda(\"Array.prototype.values\",function(a){return a?a:function(){return ea(this,function(b,c){return c})}});/** @license\n\n Copyright The Closure Library Authors.\n SPDX-License-Identifier: Apache-2.0\n*/\nvar fa=fa||{},k=this||self;function ha(a){var b=typeof a;b=\"object\"!=b?b:a?Array.isArray(a)?\"array\":b:\"null\";return\"array\"==b||\"object\"==b&&\"number\"==typeof a.length}function n(a){var b=typeof a;return\"object\"==b&&null!=a||\"function\"==b}function ia(a,b,c){return a.call.apply(a.bind,arguments)}\nfunction ja(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var e=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(e,d);return a.apply(b,e)}}return function(){return a.apply(b,arguments)}}function p(a,b,c){p=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf(\"native code\")?ia:ja;return p.apply(null,arguments)}\nfunction ka(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var d=c.slice();d.push.apply(d,arguments);return a.apply(this,d)}}function r(a,b){function c(){}c.prototype=b.prototype;a.aa=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.Qb=function(d,e,f){for(var g=Array(arguments.length-2),m=2;m<arguments.length;m++)g[m-2]=arguments[m];return b.prototype[e].apply(d,g)}};function la(a){const b=a.length;if(0<b){const c=Array(b);for(let d=0;d<b;d++)c[d]=a[d];return c}return[]}function ma(a,b){for(let c=1;c<arguments.length;c++){const d=arguments[c];if(ha(d)){const e=a.length||0,f=d.length||0;a.length=e+f;for(let g=0;g<f;g++)a[e+g]=d[g]}else a.push(d)}};class na{constructor(a,b){this.i=a;this.j=b;this.h=0;this.g=null}get(){let a;0<this.h?(this.h--,a=this.g,this.g=a.next,a.next=null):a=this.i();return a}};function t(a){return/^[\\s\\xa0]*$/.test(a)};function u(){var a=k.navigator;return a&&(a=a.userAgent)?a:\"\"};function oa(a){oa[\" \"](a);return a}oa[\" \"]=function(){};var pa=-1!=u().indexOf(\"Gecko\")&&!(-1!=u().toLowerCase().indexOf(\"webkit\")&&-1==u().indexOf(\"Edge\"))&&!(-1!=u().indexOf(\"Trident\")||-1!=u().indexOf(\"MSIE\"))&&-1==u().indexOf(\"Edge\");function qa(a,b,c){for(const d in a)b.call(c,a[d],d,a)}function ra(a,b){for(const c in a)b.call(void 0,a[c],c,a)}function sa(a){const b={};for(const c in a)b[c]=a[c];return b}const ta=\"constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf\".split(\" \");function ua(a,b){let c,d;for(let e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(let f=0;f<ta.length;f++)c=ta[f],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};function va(a){var b=1;a=a.split(\":\");const c=[];for(;0<b&&a.length;)c.push(a.shift()),b--;a.length&&c.push(a.join(\":\"));return c};function wa(a){k.setTimeout(()=>{throw a;},0)};function xa(){var a=za;let b=null;a.g&&(b=a.g,a.g=a.g.next,a.g||(a.h=null),b.next=null);return b}class Aa{constructor(){this.h=this.g=null}add(a,b){const c=Ba.get();c.set(a,b);this.h?this.h.next=c:this.g=c;this.h=c}}var Ba=new na(()=>new Ca,a=>a.reset());class Ca{constructor(){this.next=this.g=this.h=null}set(a,b){this.h=a;this.g=b;this.next=null}reset(){this.next=this.g=this.h=null}};let x,y=!1,za=new Aa,Ea=()=>{const a=k.Promise.resolve(void 0);x=()=>{a.then(Da)}};var Da=()=>{for(var a;a=xa();){try{a.h.call(a.g)}catch(c){wa(c)}var b=Ba;b.j(a);100>b.h&&(b.h++,a.next=b.g,b.g=a)}y=!1};function z(){this.s=this.s;this.C=this.C}z.prototype.s=!1;z.prototype.ma=function(){this.s||(this.s=!0,this.N())};z.prototype.N=function(){if(this.C)for(;this.C.length;)this.C.shift()()};function A(a,b){this.type=a;this.g=this.target=b;this.defaultPrevented=!1}A.prototype.h=function(){this.defaultPrevented=!0};var Fa=function(){if(!k.addEventListener||!Object.defineProperty)return!1;var a=!1,b=Object.defineProperty({},\"passive\",{get:function(){a=!0}});try{const c=()=>{};k.addEventListener(\"test\",c,b);k.removeEventListener(\"test\",c,b)}catch(c){}return a}();function C(a,b){A.call(this,a?a.type:\"\");this.relatedTarget=this.g=this.target=null;this.button=this.screenY=this.screenX=this.clientY=this.clientX=0;this.key=\"\";this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.state=null;this.pointerId=0;this.pointerType=\"\";this.i=null;if(a){var c=this.type=a.type,d=a.changedTouches&&a.changedTouches.length?a.changedTouches[0]:null;this.target=a.target||a.srcElement;this.g=b;if(b=a.relatedTarget){if(pa){a:{try{oa(b.nodeName);var e=!0;break a}catch(f){}e=\n!1}e||(b=null)}}else\"mouseover\"==c?b=a.fromElement:\"mouseout\"==c&&(b=a.toElement);this.relatedTarget=b;d?(this.clientX=void 0!==d.clientX?d.clientX:d.pageX,this.clientY=void 0!==d.clientY?d.clientY:d.pageY,this.screenX=d.screenX||0,this.screenY=d.screenY||0):(this.clientX=void 0!==a.clientX?a.clientX:a.pageX,this.clientY=void 0!==a.clientY?a.clientY:a.pageY,this.screenX=a.screenX||0,this.screenY=a.screenY||0);this.button=a.button;this.key=a.key||\"\";this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=\na.shiftKey;this.metaKey=a.metaKey;this.pointerId=a.pointerId||0;this.pointerType=\"string\"===typeof a.pointerType?a.pointerType:Ga[a.pointerType]||\"\";this.state=a.state;this.i=a;a.defaultPrevented&&C.aa.h.call(this)}}r(C,A);var Ga={2:\"touch\",3:\"pen\",4:\"mouse\"};C.prototype.h=function(){C.aa.h.call(this);var a=this.i;a.preventDefault?a.preventDefault():a.returnValue=!1};var D=\"closure_listenable_\"+(1E6*Math.random()|0);var Ha=0;function Ia(a,b,c,d,e){this.listener=a;this.proxy=null;this.src=b;this.type=c;this.capture=!!d;this.ha=e;this.key=++Ha;this.da=this.fa=!1}function Ja(a){a.da=!0;a.listener=null;a.proxy=null;a.src=null;a.ha=null};function Ka(a){this.src=a;this.g={};this.h=0}Ka.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.g[f];a||(a=this.g[f]=[],this.h++);var g=La(a,b,d,e);-1<g?(b=a[g],c||(b.fa=!1)):(b=new Ia(b,this.src,f,!!d,e),b.fa=c,a.push(b));return b};function Ma(a,b){var c=b.type;if(c in a.g){var d=a.g[c],e=Array.prototype.indexOf.call(d,b,void 0),f;(f=0<=e)&&Array.prototype.splice.call(d,e,1);f&&(Ja(b),0==a.g[c].length&&(delete a.g[c],a.h--))}}\nfunction La(a,b,c,d){for(var e=0;e<a.length;++e){var f=a[e];if(!f.da&&f.listener==b&&f.capture==!!c&&f.ha==d)return e}return-1};var Na=\"closure_lm_\"+(1E6*Math.random()|0),Oa={},Pa=0;function Qa(a,b,c,d,e){if(d&&d.once)return Ra(a,b,c,d,e);if(Array.isArray(b)){for(var f=0;f<b.length;f++)Qa(a,b[f],c,d,e);return null}c=Sa(c);return a&&a[D]?a.K(b,c,n(d)?!!d.capture:!!d,e):Ta(a,b,c,!1,d,e)}\nfunction Ta(a,b,c,d,e,f){if(!b)throw Error(\"Invalid event type\");var g=n(e)?!!e.capture:!!e,m=Ua(a);m||(a[Na]=m=new Ka(a));c=m.add(b,c,d,g,f);if(c.proxy)return c;d=Va();c.proxy=d;d.src=a;d.listener=c;if(a.addEventListener)Fa||(e=g),void 0===e&&(e=!1),a.addEventListener(b.toString(),d,e);else if(a.attachEvent)a.attachEvent(Wa(b.toString()),d);else if(a.addListener&&a.removeListener)a.addListener(d);else throw Error(\"addEventListener and attachEvent are unavailable.\");Pa++;return c}\nfunction Va(){function a(c){return b.call(a.src,a.listener,c)}const b=Xa;return a}function Ra(a,b,c,d,e){if(Array.isArray(b)){for(var f=0;f<b.length;f++)Ra(a,b[f],c,d,e);return null}c=Sa(c);return a&&a[D]?a.L(b,c,n(d)?!!d.capture:!!d,e):Ta(a,b,c,!0,d,e)}\nfunction Ya(a,b,c,d,e){if(Array.isArray(b))for(var f=0;f<b.length;f++)Ya(a,b[f],c,d,e);else(d=n(d)?!!d.capture:!!d,c=Sa(c),a&&a[D])?(a=a.i,b=String(b).toString(),b in a.g&&(f=a.g[b],c=La(f,c,d,e),-1<c&&(Ja(f[c]),Array.prototype.splice.call(f,c,1),0==f.length&&(delete a.g[b],a.h--)))):a&&(a=Ua(a))&&(b=a.g[b.toString()],a=-1,b&&(a=La(b,c,d,e)),(c=-1<a?b[a]:null)&&Za(c))}\nfunction Za(a){if(\"number\"!==typeof a&&a&&!a.da){var b=a.src;if(b&&b[D])Ma(b.i,a);else{var c=a.type,d=a.proxy;b.removeEventListener?b.removeEventListener(c,d,a.capture):b.detachEvent?b.detachEvent(Wa(c),d):b.addListener&&b.removeListener&&b.removeListener(d);Pa--;(c=Ua(b))?(Ma(c,a),0==c.h&&(c.src=null,b[Na]=null)):Ja(a)}}}function Wa(a){return a in Oa?Oa[a]:Oa[a]=\"on\"+a}function Xa(a,b){if(a.da)a=!0;else{b=new C(b,this);var c=a.listener,d=a.ha||a.src;a.fa&&Za(a);a=c.call(d,b)}return a}\nfunction Ua(a){a=a[Na];return a instanceof Ka?a:null}var $a=\"__closure_events_fn_\"+(1E9*Math.random()>>>0);function Sa(a){if(\"function\"===typeof a)return a;a[$a]||(a[$a]=function(b){return a.handleEvent(b)});return a[$a]};function E(){z.call(this);this.i=new Ka(this);this.M=this;this.F=null}r(E,z);E.prototype[D]=!0;E.prototype.removeEventListener=function(a,b,c,d){Ya(this,a,b,c,d)};\nfunction F(a,b){var c,d=a.F;if(d)for(c=[];d;d=d.F)c.push(d);a=a.M;d=b.type||b;if(\"string\"===typeof b)b=new A(b,a);else if(b instanceof A)b.target=b.target||a;else{var e=b;b=new A(d,a);ua(b,e)}e=!0;if(c)for(var f=c.length-1;0<=f;f--){var g=b.g=c[f];e=ab(g,d,!0,b)&&e}g=b.g=a;e=ab(g,d,!0,b)&&e;e=ab(g,d,!1,b)&&e;if(c)for(f=0;f<c.length;f++)g=b.g=c[f],e=ab(g,d,!1,b)&&e}\nE.prototype.N=function(){E.aa.N.call(this);if(this.i){var a=this.i,b=0,c;for(c in a.g){for(var d=a.g[c],e=0;e<d.length;e++)++b,Ja(d[e]);delete a.g[c];a.h--}}this.F=null};E.prototype.K=function(a,b,c,d){return this.i.add(String(a),b,!1,c,d)};E.prototype.L=function(a,b,c,d){return this.i.add(String(a),b,!0,c,d)};\nfunction ab(a,b,c,d){b=a.i.g[String(b)];if(!b)return!0;b=b.concat();for(var e=!0,f=0;f<b.length;++f){var g=b[f];if(g&&!g.da&&g.capture==c){var m=g.listener,q=g.ha||g.src;g.fa&&Ma(a.i,g);e=!1!==m.call(q,d)&&e}}return e&&!d.defaultPrevented};function bb(a,b,c){if(\"function\"===typeof a)c&&(a=p(a,c));else if(a&&\"function\"==typeof a.handleEvent)a=p(a.handleEvent,a);else throw Error(\"Invalid listener argument\");return 2147483647<Number(b)?-1:k.setTimeout(a,b||0)};function cb(a){a.g=bb(()=>{a.g=null;a.i&&(a.i=!1,cb(a))},a.l);const b=a.h;a.h=null;a.m.apply(null,b)}class eb extends z{constructor(a,b){super();this.m=a;this.l=b;this.h=null;this.i=!1;this.g=null}j(a){this.h=arguments;this.g?this.i=!0:cb(this)}N(){super.N();this.g&&(k.clearTimeout(this.g),this.g=null,this.i=!1,this.h=null)}};function G(a){z.call(this);this.h=a;this.g={}}r(G,z);var fb=[];function gb(a){qa(a.g,function(b,c){this.g.hasOwnProperty(c)&&Za(b)},a);a.g={}}G.prototype.N=function(){G.aa.N.call(this);gb(this)};G.prototype.handleEvent=function(){throw Error(\"EventHandler.handleEvent not implemented\");};var hb=k.JSON.stringify;var ib=k.JSON.parse;var jb=class{stringify(a){return k.JSON.stringify(a,void 0)}parse(a){return k.JSON.parse(a,void 0)}};function kb(){}kb.prototype.h=null;function lb(a){return a.h||(a.h=a.i())};function mb(){}var H={OPEN:\"a\",kb:\"b\",Ja:\"c\",wb:\"d\"};function nb(){A.call(this,\"d\")}r(nb,A);function ob(){A.call(this,\"c\")}r(ob,A);var I={},pb=null;function qb(){return pb=pb||new E}I.La=\"serverreachability\";function rb(a){A.call(this,I.La,a)}r(rb,A);function J(a){const b=qb();F(b,new rb(b,a))}I.STAT_EVENT=\"statevent\";function sb(a,b){A.call(this,I.STAT_EVENT,a);this.stat=b}r(sb,A);function K(a){const b=qb();F(b,new sb(b,a))}I.Ma=\"timingevent\";function tb(a,b){A.call(this,I.Ma,a);this.size=b}r(tb,A);\nfunction ub(a,b){if(\"function\"!==typeof a)throw Error(\"Fn must not be null and must be a function\");return k.setTimeout(function(){a()},b)};function vb(){this.g=!0}vb.prototype.xa=function(){this.g=!1};function wb(a,b,c,d,e,f){a.info(function(){if(a.g)if(f){var g=\"\";for(var m=f.split(\"&\"),q=0;q<m.length;q++){var l=m[q].split(\"=\");if(1<l.length){var v=l[0];l=l[1];var w=v.split(\"_\");g=2<=w.length&&\"type\"==w[1]?g+(v+\"=\"+l+\"&\"):g+(v+\"=redacted&\")}}}else g=null;else g=f;return\"XMLHTTP REQ (\"+d+\") [attempt \"+e+\"]: \"+b+\"\\n\"+c+\"\\n\"+g})}\nfunction xb(a,b,c,d,e,f,g){a.info(function(){return\"XMLHTTP RESP (\"+d+\") [ attempt \"+e+\"]: \"+b+\"\\n\"+c+\"\\n\"+f+\" \"+g})}function L(a,b,c,d){a.info(function(){return\"XMLHTTP TEXT (\"+b+\"): \"+yb(a,c)+(d?\" \"+d:\"\")})}function zb(a,b){a.info(function(){return\"TIMEOUT: \"+b})}vb.prototype.info=function(){};\nfunction yb(a,b){if(!a.g)return b;if(!b)return null;try{var c=JSON.parse(b);if(c)for(a=0;a<c.length;a++)if(Array.isArray(c[a])){var d=c[a];if(!(2>d.length)){var e=d[1];if(Array.isArray(e)&&!(1>e.length)){var f=e[0];if(\"noop\"!=f&&\"stop\"!=f&&\"close\"!=f)for(var g=1;g<e.length;g++)e[g]=\"\"}}}return hb(c)}catch(m){return b}};var Ab={NO_ERROR:0,gb:1,tb:2,sb:3,nb:4,rb:5,ub:6,Ia:7,TIMEOUT:8,xb:9};var Bb={lb:\"complete\",Hb:\"success\",Ja:\"error\",Ia:\"abort\",zb:\"ready\",Ab:\"readystatechange\",TIMEOUT:\"timeout\",vb:\"incrementaldata\",yb:\"progress\",ob:\"downloadprogress\",Pb:\"uploadprogress\"};var Cb;function Db(){}r(Db,kb);Db.prototype.g=function(){return new XMLHttpRequest};Db.prototype.i=function(){return{}};Cb=new Db;function M(a,b,c,d){this.j=a;this.i=b;this.l=c;this.R=d||1;this.U=new G(this);this.I=45E3;this.H=null;this.o=!1;this.m=this.A=this.v=this.L=this.F=this.S=this.B=null;this.D=[];this.g=null;this.C=0;this.s=this.u=null;this.X=-1;this.J=!1;this.O=0;this.M=null;this.W=this.K=this.T=this.P=!1;this.h=new Eb}function Eb(){this.i=null;this.g=\"\";this.h=!1}var Fb={},Gb={};function Hb(a,b,c){a.L=1;a.v=Ib(N(b));a.m=c;a.P=!0;Jb(a,null)}\nfunction Jb(a,b){a.F=Date.now();Kb(a);a.A=N(a.v);var c=a.A,d=a.R;Array.isArray(d)||(d=[String(d)]);Lb(c.i,\"t\",d);a.C=0;c=a.j.J;a.h=new Eb;a.g=Mb(a.j,c?b:null,!a.m);0<a.O&&(a.M=new eb(p(a.Y,a,a.g),a.O));b=a.U;c=a.g;d=a.ca;var e=\"readystatechange\";Array.isArray(e)||(e&&(fb[0]=e.toString()),e=fb);for(var f=0;f<e.length;f++){var g=Qa(c,e[f],d||b.handleEvent,!1,b.h||b);if(!g)break;b.g[g.key]=g}b=a.H?sa(a.H):{};a.m?(a.u||(a.u=\"POST\"),b[\"Content-Type\"]=\"application/x-www-form-urlencoded\",a.g.ea(a.A,a.u,\na.m,b)):(a.u=\"GET\",a.g.ea(a.A,a.u,null,b));J(1);wb(a.i,a.u,a.A,a.l,a.R,a.m)}M.prototype.ca=function(a){a=a.target;const b=this.M;b&&3==P(a)?b.j():this.Y(a)};\nM.prototype.Y=function(a){try{if(a==this.g)a:{const w=P(this.g);var b=this.g.Ba();const O=this.g.Z();if(!(3>w)&&(3!=w||this.g&&(this.h.h||this.g.oa()||Nb(this.g)))){this.J||4!=w||7==b||(8==b||0>=O?J(3):J(2));Ob(this);var c=this.g.Z();this.X=c;b:if(Pb(this)){var d=Nb(this.g);a=\"\";var e=d.length,f=4==P(this.g);if(!this.h.i){if(\"undefined\"===typeof TextDecoder){Q(this);Qb(this);var g=\"\";break b}this.h.i=new k.TextDecoder}for(b=0;b<e;b++)this.h.h=!0,a+=this.h.i.decode(d[b],{stream:!(f&&b==e-1)});d.length=\n0;this.h.g+=a;this.C=0;g=this.h.g}else g=this.g.oa();this.o=200==c;xb(this.i,this.u,this.A,this.l,this.R,w,c);if(this.o){if(this.T&&!this.K){b:{if(this.g){var m,q=this.g;if((m=q.g?q.g.getResponseHeader(\"X-HTTP-Initial-Response\"):null)&&!t(m)){var l=m;break b}}l=null}if(c=l)L(this.i,this.l,c,\"Initial handshake response via X-HTTP-Initial-Response\"),this.K=!0,Rb(this,c);else{this.o=!1;this.s=3;K(12);Q(this);Qb(this);break a}}if(this.P){c=!0;let B;for(;!this.J&&this.C<g.length;)if(B=Sb(this,g),B==Gb){4==\nw&&(this.s=4,K(14),c=!1);L(this.i,this.l,null,\"[Incomplete Response]\");break}else if(B==Fb){this.s=4;K(15);L(this.i,this.l,g,\"[Invalid Chunk]\");c=!1;break}else L(this.i,this.l,B,null),Rb(this,B);Pb(this)&&0!=this.C&&(this.h.g=this.h.g.slice(this.C),this.C=0);4!=w||0!=g.length||this.h.h||(this.s=1,K(16),c=!1);this.o=this.o&&c;if(!c)L(this.i,this.l,g,\"[Invalid Chunked Response]\"),Q(this),Qb(this);else if(0<g.length&&!this.W){this.W=!0;var v=this.j;v.g==this&&v.ba&&!v.M&&(v.j.info(\"Great, no buffering proxy detected. Bytes received: \"+\ng.length),Tb(v),v.M=!0,K(11))}}else L(this.i,this.l,g,null),Rb(this,g);4==w&&Q(this);this.o&&!this.J&&(4==w?Ub(this.j,this):(this.o=!1,Kb(this)))}else Vb(this.g),400==c&&0<g.indexOf(\"Unknown SID\")?(this.s=3,K(12)):(this.s=0,K(13)),Q(this),Qb(this)}}}catch(w){}finally{}};function Pb(a){return a.g?\"GET\"==a.u&&2!=a.L&&a.j.Ca:!1}\nfunction Sb(a,b){var c=a.C,d=b.indexOf(\"\\n\",c);if(-1==d)return Gb;c=Number(b.substring(c,d));if(isNaN(c))return Fb;d+=1;if(d+c>b.length)return Gb;b=b.slice(d,d+c);a.C=d+c;return b}M.prototype.cancel=function(){this.J=!0;Q(this)};function Kb(a){a.S=Date.now()+a.I;Wb(a,a.I)}function Wb(a,b){if(null!=a.B)throw Error(\"WatchDog timer not null\");a.B=ub(p(a.ba,a),b)}function Ob(a){a.B&&(k.clearTimeout(a.B),a.B=null)}\nM.prototype.ba=function(){this.B=null;const a=Date.now();0<=a-this.S?(zb(this.i,this.A),2!=this.L&&(J(3),K(17)),Q(this),this.s=2,Qb(this)):Wb(this,this.S-a)};function Qb(a){0==a.j.G||a.J||Ub(a.j,a)}function Q(a){Ob(a);var b=a.M;b&&\"function\"==typeof b.ma&&b.ma();a.M=null;gb(a.U);a.g&&(b=a.g,a.g=null,b.abort(),b.ma())}\nfunction Rb(a,b){try{var c=a.j;if(0!=c.G&&(c.g==a||Xb(c.h,a)))if(!a.K&&Xb(c.h,a)&&3==c.G){try{var d=c.Da.g.parse(b)}catch(l){d=null}if(Array.isArray(d)&&3==d.length){var e=d;if(0==e[0])a:{if(!c.u){if(c.g)if(c.g.F+3E3<a.F)Yb(c),Zb(c);else break a;$b(c);K(18)}}else c.za=e[1],0<c.za-c.T&&37500>e[2]&&c.F&&0==c.v&&!c.C&&(c.C=ub(p(c.Za,c),6E3));if(1>=ac(c.h)&&c.ca){try{c.ca()}catch(l){}c.ca=void 0}}else R(c,11)}else if((a.K||c.g==a)&&Yb(c),!t(b))for(e=c.Da.g.parse(b),b=0;b<e.length;b++){let l=e[b];c.T=\nl[0];l=l[1];if(2==c.G)if(\"c\"==l[0]){c.K=l[1];c.ia=l[2];const v=l[3];null!=v&&(c.la=v,c.j.info(\"VER=\"+c.la));const w=l[4];null!=w&&(c.Aa=w,c.j.info(\"SVER=\"+c.Aa));const O=l[5];null!=O&&\"number\"===typeof O&&0<O&&(d=1.5*O,c.L=d,c.j.info(\"backChannelRequestTimeoutMs_=\"+d));d=c;const B=a.g;if(B){const ya=B.g?B.g.getResponseHeader(\"X-Client-Wire-Protocol\"):null;if(ya){var f=d.h;f.g||-1==ya.indexOf(\"spdy\")&&-1==ya.indexOf(\"quic\")&&-1==ya.indexOf(\"h2\")||(f.j=f.l,f.g=new Set,f.h&&(bc(f,f.h),f.h=null))}if(d.D){const db=\nB.g?B.g.getResponseHeader(\"X-HTTP-Session-Id\"):null;db&&(d.ya=db,S(d.I,d.D,db))}}c.G=3;c.l&&c.l.ua();c.ba&&(c.R=Date.now()-a.F,c.j.info(\"Handshake RTT: \"+c.R+\"ms\"));d=c;var g=a;d.qa=cc(d,d.J?d.ia:null,d.W);if(g.K){dc(d.h,g);var m=g,q=d.L;q&&(m.I=q);m.B&&(Ob(m),Kb(m));d.g=g}else ec(d);0<c.i.length&&fc(c)}else\"stop\"!=l[0]&&\"close\"!=l[0]||R(c,7);else 3==c.G&&(\"stop\"==l[0]||\"close\"==l[0]?\"stop\"==l[0]?R(c,7):gc(c):\"noop\"!=l[0]&&c.l&&c.l.ta(l),c.v=0)}J(4)}catch(l){}};var hc=class{constructor(a,b){this.g=a;this.map=b}};function ic(a){this.l=a||10;k.PerformanceNavigationTiming?(a=k.performance.getEntriesByType(\"navigation\"),a=0<a.length&&(\"hq\"==a[0].nextHopProtocol||\"h2\"==a[0].nextHopProtocol)):a=!!(k.chrome&&k.chrome.loadTimes&&k.chrome.loadTimes()&&k.chrome.loadTimes().wasFetchedViaSpdy);this.j=a?this.l:1;this.g=null;1<this.j&&(this.g=new Set);this.h=null;this.i=[]}function jc(a){return a.h?!0:a.g?a.g.size>=a.j:!1}function ac(a){return a.h?1:a.g?a.g.size:0}function Xb(a,b){return a.h?a.h==b:a.g?a.g.has(b):!1}\nfunction bc(a,b){a.g?a.g.add(b):a.h=b}function dc(a,b){a.h&&a.h==b?a.h=null:a.g&&a.g.has(b)&&a.g.delete(b)}ic.prototype.cancel=function(){this.i=kc(this);if(this.h)this.h.cancel(),this.h=null;else if(this.g&&0!==this.g.size){for(const a of this.g.values())a.cancel();this.g.clear()}};function kc(a){if(null!=a.h)return a.i.concat(a.h.D);if(null!=a.g&&0!==a.g.size){let b=a.i;for(const c of a.g.values())b=b.concat(c.D);return b}return la(a.i)};function lc(a){if(a.V&&\"function\"==typeof a.V)return a.V();if(\"undefined\"!==typeof Map&&a instanceof Map||\"undefined\"!==typeof Set&&a instanceof Set)return Array.from(a.values());if(\"string\"===typeof a)return a.split(\"\");if(ha(a)){for(var b=[],c=a.length,d=0;d<c;d++)b.push(a[d]);return b}b=[];c=0;for(d in a)b[c++]=a[d];return b}\nfunction mc(a){if(a.na&&\"function\"==typeof a.na)return a.na();if(!a.V||\"function\"!=typeof a.V){if(\"undefined\"!==typeof Map&&a instanceof Map)return Array.from(a.keys());if(!(\"undefined\"!==typeof Set&&a instanceof Set)){if(ha(a)||\"string\"===typeof a){var b=[];a=a.length;for(var c=0;c<a;c++)b.push(c);return b}b=[];c=0;for(const d in a)b[c++]=d;return b}}}\nfunction nc(a,b){if(a.forEach&&\"function\"==typeof a.forEach)a.forEach(b,void 0);else if(ha(a)||\"string\"===typeof a)Array.prototype.forEach.call(a,b,void 0);else for(var c=mc(a),d=lc(a),e=d.length,f=0;f<e;f++)b.call(void 0,d[f],c&&c[f],a)};var oc=RegExp(\"^(?:([^:/?#.]+):)?(?://(?:([^\\\\\\\\/?#]*)@)?([^\\\\\\\\/?#]*?)(?::([0-9]+))?(?=[\\\\\\\\/?#]|$))?([^?#]+)?(?:\\\\?([^#]*))?(?:#([\\\\s\\\\S]*))?$\");function pc(a,b){if(a){a=a.split(\"&\");for(var c=0;c<a.length;c++){var d=a[c].indexOf(\"=\"),e=null;if(0<=d){var f=a[c].substring(0,d);e=a[c].substring(d+1)}else f=a[c];b(f,e?decodeURIComponent(e.replace(/\\+/g,\" \")):\"\")}}};function T(a){this.g=this.o=this.j=\"\";this.s=null;this.m=this.l=\"\";this.h=!1;if(a instanceof T){this.h=a.h;qc(this,a.j);this.o=a.o;this.g=a.g;rc(this,a.s);this.l=a.l;var b=a.i;var c=new sc;c.i=b.i;b.g&&(c.g=new Map(b.g),c.h=b.h);tc(this,c);this.m=a.m}else a&&(b=String(a).match(oc))?(this.h=!1,qc(this,b[1]||\"\",!0),this.o=uc(b[2]||\"\"),this.g=uc(b[3]||\"\",!0),rc(this,b[4]),this.l=uc(b[5]||\"\",!0),tc(this,b[6]||\"\",!0),this.m=uc(b[7]||\"\")):(this.h=!1,this.i=new sc(null,this.h))}\nT.prototype.toString=function(){var a=[],b=this.j;b&&a.push(vc(b,wc,!0),\":\");var c=this.g;if(c||\"file\"==b)a.push(\"//\"),(b=this.o)&&a.push(vc(b,wc,!0),\"@\"),a.push(encodeURIComponent(String(c)).replace(/%25([0-9a-fA-F]{2})/g,\"%$1\")),c=this.s,null!=c&&a.push(\":\",String(c));if(c=this.l)this.g&&\"/\"!=c.charAt(0)&&a.push(\"/\"),a.push(vc(c,\"/\"==c.charAt(0)?xc:yc,!0));(c=this.i.toString())&&a.push(\"?\",c);(c=this.m)&&a.push(\"#\",vc(c,zc));return a.join(\"\")};function N(a){return new T(a)}\nfunction qc(a,b,c){a.j=c?uc(b,!0):b;a.j&&(a.j=a.j.replace(/:$/,\"\"))}function rc(a,b){if(b){b=Number(b);if(isNaN(b)||0>b)throw Error(\"Bad port number \"+b);a.s=b}else a.s=null}function tc(a,b,c){b instanceof sc?(a.i=b,Ac(a.i,a.h)):(c||(b=vc(b,Bc)),a.i=new sc(b,a.h))}function S(a,b,c){a.i.set(b,c)}function Ib(a){S(a,\"zx\",Math.floor(2147483648*Math.random()).toString(36)+Math.abs(Math.floor(2147483648*Math.random())^Date.now()).toString(36));return a}\nfunction uc(a,b){return a?b?decodeURI(a.replace(/%25/g,\"%2525\")):decodeURIComponent(a):\"\"}function vc(a,b,c){return\"string\"===typeof a?(a=encodeURI(a).replace(b,Cc),c&&(a=a.replace(/%25([0-9a-fA-F]{2})/g,\"%$1\")),a):null}function Cc(a){a=a.charCodeAt(0);return\"%\"+(a>>4&15).toString(16)+(a&15).toString(16)}var wc=/[#\\/\\?@]/g,yc=/[#\\?:]/g,xc=/[#\\?]/g,Bc=/[#\\?@]/g,zc=/#/g;function sc(a,b){this.h=this.g=null;this.i=a||null;this.j=!!b}\nfunction U(a){a.g||(a.g=new Map,a.h=0,a.i&&pc(a.i,function(b,c){a.add(decodeURIComponent(b.replace(/\\+/g,\" \")),c)}))}h=sc.prototype;h.add=function(a,b){U(this);this.i=null;a=V(this,a);var c=this.g.get(a);c||this.g.set(a,c=[]);c.push(b);this.h+=1;return this};function Dc(a,b){U(a);b=V(a,b);a.g.has(b)&&(a.i=null,a.h-=a.g.get(b).length,a.g.delete(b))}function Ec(a,b){U(a);b=V(a,b);return a.g.has(b)}\nh.forEach=function(a,b){U(this);this.g.forEach(function(c,d){c.forEach(function(e){a.call(b,e,d,this)},this)},this)};h.na=function(){U(this);const a=Array.from(this.g.values()),b=Array.from(this.g.keys()),c=[];for(let d=0;d<b.length;d++){const e=a[d];for(let f=0;f<e.length;f++)c.push(b[d])}return c};h.V=function(a){U(this);let b=[];if(\"string\"===typeof a)Ec(this,a)&&(b=b.concat(this.g.get(V(this,a))));else{a=Array.from(this.g.values());for(let c=0;c<a.length;c++)b=b.concat(a[c])}return b};\nh.set=function(a,b){U(this);this.i=null;a=V(this,a);Ec(this,a)&&(this.h-=this.g.get(a).length);this.g.set(a,[b]);this.h+=1;return this};h.get=function(a,b){if(!a)return b;a=this.V(a);return 0<a.length?String(a[0]):b};function Lb(a,b,c){Dc(a,b);0<c.length&&(a.i=null,a.g.set(V(a,b),la(c)),a.h+=c.length)}\nh.toString=function(){if(this.i)return this.i;if(!this.g)return\"\";const a=[],b=Array.from(this.g.keys());for(var c=0;c<b.length;c++){var d=b[c];const f=encodeURIComponent(String(d)),g=this.V(d);for(d=0;d<g.length;d++){var e=f;\"\"!==g[d]&&(e+=\"=\"+encodeURIComponent(String(g[d])));a.push(e)}}return this.i=a.join(\"&\")};function V(a,b){b=String(b);a.j&&(b=b.toLowerCase());return b}\nfunction Ac(a,b){b&&!a.j&&(U(a),a.i=null,a.g.forEach(function(c,d){var e=d.toLowerCase();d!=e&&(Dc(this,d),Lb(this,e,c))},a));a.j=b};function Fc(a,b){const c=new vb;if(k.Image){const d=new Image;d.onload=ka(W,c,\"TestLoadImage: loaded\",!0,b,d);d.onerror=ka(W,c,\"TestLoadImage: error\",!1,b,d);d.onabort=ka(W,c,\"TestLoadImage: abort\",!1,b,d);d.ontimeout=ka(W,c,\"TestLoadImage: timeout\",!1,b,d);k.setTimeout(function(){if(d.ontimeout)d.ontimeout()},1E4);d.src=a}else b(!1)}\nfunction Gc(a,b){const c=new vb,d=new AbortController,e=setTimeout(()=>{d.abort();W(c,\"TestPingServer: timeout\",!1,b)},1E4);fetch(a,{signal:d.signal}).then(f=>{clearTimeout(e);f.ok?W(c,\"TestPingServer: ok\",!0,b):W(c,\"TestPingServer: server error\",!1,b)}).catch(()=>{clearTimeout(e);W(c,\"TestPingServer: error\",!1,b)})}function W(a,b,c,d,e){try{e&&(e.onload=null,e.onerror=null,e.onabort=null,e.ontimeout=null),d(c)}catch(f){}};function Hc(){this.g=new jb}function Ic(a,b,c){const d=c||\"\";try{nc(a,function(e,f){let g=e;n(e)&&(g=hb(e));b.push(d+f+\"=\"+encodeURIComponent(g))})}catch(e){throw b.push(d+\"type=\"+encodeURIComponent(\"_badmap\")),e;}};function Jc(a){this.l=a.Ub||null;this.j=a.eb||!1}r(Jc,kb);Jc.prototype.g=function(){return new Kc(this.l,this.j)};Jc.prototype.i=function(a){return function(){return a}}({});function Kc(a,b){E.call(this);this.D=a;this.o=b;this.m=void 0;this.status=this.readyState=0;this.responseType=this.responseText=this.response=this.statusText=\"\";this.onreadystatechange=null;this.u=new Headers;this.h=null;this.B=\"GET\";this.A=\"\";this.g=!1;this.v=this.j=this.l=null}r(Kc,E);h=Kc.prototype;\nh.open=function(a,b){if(0!=this.readyState)throw this.abort(),Error(\"Error reopening a connection\");this.B=a;this.A=b;this.readyState=1;Lc(this)};h.send=function(a){if(1!=this.readyState)throw this.abort(),Error(\"need to call open() first. \");this.g=!0;const b={headers:this.u,method:this.B,credentials:this.m,cache:void 0};a&&(b.body=a);(this.D||k).fetch(new Request(this.A,b)).then(this.Sa.bind(this),this.ga.bind(this))};\nh.abort=function(){this.response=this.responseText=\"\";this.u=new Headers;this.status=0;this.j&&this.j.cancel(\"Request was aborted.\").catch(()=>{});1<=this.readyState&&this.g&&4!=this.readyState&&(this.g=!1,Mc(this));this.readyState=0};\nh.Sa=function(a){if(this.g&&(this.l=a,this.h||(this.status=this.l.status,this.statusText=this.l.statusText,this.h=a.headers,this.readyState=2,Lc(this)),this.g&&(this.readyState=3,Lc(this),this.g)))if(\"arraybuffer\"===this.responseType)a.arrayBuffer().then(this.Qa.bind(this),this.ga.bind(this));else if(\"undefined\"!==typeof k.ReadableStream&&\"body\"in a){this.j=a.body.getReader();if(this.o){if(this.responseType)throw Error('responseType must be empty for \"streamBinaryChunks\" mode responses.');this.response=\n[]}else this.response=this.responseText=\"\",this.v=new TextDecoder;Nc(this)}else a.text().then(this.Ra.bind(this),this.ga.bind(this))};function Nc(a){a.j.read().then(a.Pa.bind(a)).catch(a.ga.bind(a))}h.Pa=function(a){if(this.g){if(this.o&&a.value)this.response.push(a.value);else if(!this.o){var b=a.value?a.value:new Uint8Array(0);if(b=this.v.decode(b,{stream:!a.done}))this.response=this.responseText+=b}a.done?Mc(this):Lc(this);3==this.readyState&&Nc(this)}};\nh.Ra=function(a){this.g&&(this.response=this.responseText=a,Mc(this))};h.Qa=function(a){this.g&&(this.response=a,Mc(this))};h.ga=function(){this.g&&Mc(this)};function Mc(a){a.readyState=4;a.l=null;a.j=null;a.v=null;Lc(a)}h.setRequestHeader=function(a,b){this.u.append(a,b)};h.getResponseHeader=function(a){return this.h?this.h.get(a.toLowerCase())||\"\":\"\"};\nh.getAllResponseHeaders=function(){if(!this.h)return\"\";const a=[],b=this.h.entries();for(var c=b.next();!c.done;)c=c.value,a.push(c[0]+\": \"+c[1]),c=b.next();return a.join(\"\\r\\n\")};function Lc(a){a.onreadystatechange&&a.onreadystatechange.call(a)}Object.defineProperty(Kc.prototype,\"withCredentials\",{get:function(){return\"include\"===this.m},set:function(a){this.m=a?\"include\":\"same-origin\"}});function Oc(a){let b=\"\";qa(a,function(c,d){b+=d;b+=\":\";b+=c;b+=\"\\r\\n\"});return b}function Pc(a,b,c){a:{for(d in c){var d=!1;break a}d=!0}d||(c=Oc(c),\"string\"===typeof a?(encodeURIComponent(String(b)),null!=c&&encodeURIComponent(String(c))):S(a,b,c))};function X(a){E.call(this);this.headers=new Map;this.o=a||null;this.h=!1;this.v=this.g=null;this.D=\"\";this.m=0;this.l=\"\";this.j=this.B=this.u=this.A=!1;this.I=null;this.H=\"\";this.J=!1}r(X,E);var Qc=/^https?$/i,Rc=[\"POST\",\"PUT\"];h=X.prototype;h.Ha=function(a){this.J=a};\nh.ea=function(a,b,c,d){if(this.g)throw Error(\"[goog.net.XhrIo] Object is active with another request=\"+this.D+\"; newUri=\"+a);b=b?b.toUpperCase():\"GET\";this.D=a;this.l=\"\";this.m=0;this.A=!1;this.h=!0;this.g=this.o?this.o.g():Cb.g();this.v=this.o?lb(this.o):lb(Cb);this.g.onreadystatechange=p(this.Ea,this);try{this.B=!0,this.g.open(b,String(a),!0),this.B=!1}catch(f){Sc(this,f);return}a=c||\"\";c=new Map(this.headers);if(d)if(Object.getPrototypeOf(d)===Object.prototype)for(var e in d)c.set(e,d[e]);else if(\"function\"===\ntypeof d.keys&&\"function\"===typeof d.get)for(const f of d.keys())c.set(f,d.get(f));else throw Error(\"Unknown input type for opt_headers: \"+String(d));d=Array.from(c.keys()).find(f=>\"content-type\"==f.toLowerCase());e=k.FormData&&a instanceof k.FormData;!(0<=Array.prototype.indexOf.call(Rc,b,void 0))||d||e||c.set(\"Content-Type\",\"application/x-www-form-urlencoded;charset=utf-8\");for(const [f,g]of c)this.g.setRequestHeader(f,g);this.H&&(this.g.responseType=this.H);\"withCredentials\"in this.g&&this.g.withCredentials!==\nthis.J&&(this.g.withCredentials=this.J);try{Tc(this),this.u=!0,this.g.send(a),this.u=!1}catch(f){Sc(this,f)}};function Sc(a,b){a.h=!1;a.g&&(a.j=!0,a.g.abort(),a.j=!1);a.l=b;a.m=5;Uc(a);Vc(a)}function Uc(a){a.A||(a.A=!0,F(a,\"complete\"),F(a,\"error\"))}h.abort=function(a){this.g&&this.h&&(this.h=!1,this.j=!0,this.g.abort(),this.j=!1,this.m=a||7,F(this,\"complete\"),F(this,\"abort\"),Vc(this))};h.N=function(){this.g&&(this.h&&(this.h=!1,this.j=!0,this.g.abort(),this.j=!1),Vc(this,!0));X.aa.N.call(this)};\nh.Ea=function(){this.s||(this.B||this.u||this.j?Wc(this):this.bb())};h.bb=function(){Wc(this)};\nfunction Wc(a){if(a.h&&\"undefined\"!=typeof fa&&(!a.v[1]||4!=P(a)||2!=a.Z()))if(a.u&&4==P(a))bb(a.Ea,0,a);else if(F(a,\"readystatechange\"),4==P(a)){a.h=!1;try{const g=a.Z();a:switch(g){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:var b=!0;break a;default:b=!1}var c;if(!(c=b)){var d;if(d=0===g){var e=String(a.D).match(oc)[1]||null;!e&&k.self&&k.self.location&&(e=k.self.location.protocol.slice(0,-1));d=!Qc.test(e?e.toLowerCase():\"\")}c=d}if(c)F(a,\"complete\"),F(a,\"success\");else{a.m=\n6;try{var f=2<P(a)?a.g.statusText:\"\"}catch(m){f=\"\"}a.l=f+\" [\"+a.Z()+\"]\";Uc(a)}}finally{Vc(a)}}}function Vc(a,b){if(a.g){Tc(a);const c=a.g,d=a.v[0]?()=>{}:null;a.g=null;a.v=null;b||F(a,\"ready\");try{c.onreadystatechange=d}catch(e){}}}function Tc(a){a.I&&(k.clearTimeout(a.I),a.I=null)}h.isActive=function(){return!!this.g};function P(a){return a.g?a.g.readyState:0}h.Z=function(){try{return 2<P(this)?this.g.status:-1}catch(a){return-1}};h.oa=function(){try{return this.g?this.g.responseText:\"\"}catch(a){return\"\"}};\nh.Oa=function(a){if(this.g){var b=this.g.responseText;a&&0==b.indexOf(a)&&(b=b.substring(a.length));return ib(b)}};function Nb(a){try{if(!a.g)return null;if(\"response\"in a.g)return a.g.response;switch(a.H){case \"\":case \"text\":return a.g.responseText;case \"arraybuffer\":if(\"mozResponseArrayBuffer\"in a.g)return a.g.mozResponseArrayBuffer}return null}catch(b){return null}}\nfunction Vb(a){const b={};a=(a.g&&2<=P(a)?a.g.getAllResponseHeaders()||\"\":\"\").split(\"\\r\\n\");for(let d=0;d<a.length;d++){if(t(a[d]))continue;var c=va(a[d]);const e=c[0];c=c[1];if(\"string\"!==typeof c)continue;c=c.trim();const f=b[e]||[];b[e]=f;f.push(c)}ra(b,function(d){return d.join(\", \")})}h.Ba=function(){return this.m};h.Ka=function(){return\"string\"===typeof this.l?this.l:String(this.l)};function Xc(a,b,c){return c&&c.internalChannelParams?c.internalChannelParams[a]||b:b}\nfunction Yc(a){this.Aa=0;this.i=[];this.j=new vb;this.ia=this.qa=this.I=this.W=this.g=this.ya=this.D=this.H=this.m=this.S=this.o=null;this.Ya=this.U=0;this.Va=Xc(\"failFast\",!1,a);this.F=this.C=this.u=this.s=this.l=null;this.X=!0;this.za=this.T=-1;this.Y=this.v=this.B=0;this.Ta=Xc(\"baseRetryDelayMs\",5E3,a);this.cb=Xc(\"retryDelaySeedMs\",1E4,a);this.Wa=Xc(\"forwardChannelMaxRetries\",2,a);this.wa=Xc(\"forwardChannelRequestTimeoutMs\",2E4,a);this.pa=a&&a.xmlHttpFactory||void 0;this.Xa=a&&a.Tb||void 0;this.Ca=\na&&a.useFetchStreams||!1;this.L=void 0;this.J=a&&a.supportsCrossDomainXhr||!1;this.K=\"\";this.h=new ic(a&&a.concurrentRequestLimit);this.Da=new Hc;this.P=a&&a.fastHandshake||!1;this.O=a&&a.encodeInitMessageHeaders||!1;this.P&&this.O&&(this.O=!1);this.Ua=a&&a.Rb||!1;a&&a.xa&&this.j.xa();a&&a.forceLongPolling&&(this.X=!1);this.ba=!this.P&&this.X&&a&&a.detectBufferingProxy||!1;this.ja=void 0;a&&a.longPollingTimeout&&0<a.longPollingTimeout&&(this.ja=a.longPollingTimeout);this.ca=void 0;this.R=0;this.M=\n!1;this.ka=this.A=null}h=Yc.prototype;h.la=8;h.G=1;h.connect=function(a,b,c,d){K(0);this.W=a;this.H=b||{};c&&void 0!==d&&(this.H.OSID=c,this.H.OAID=d);this.F=this.X;this.I=cc(this,null,this.W);fc(this)};\nfunction gc(a){Zc(a);if(3==a.G){var b=a.U++,c=N(a.I);S(c,\"SID\",a.K);S(c,\"RID\",b);S(c,\"TYPE\",\"terminate\");$c(a,c);b=new M(a,a.j,b);b.L=2;b.v=Ib(N(c));c=!1;if(k.navigator&&k.navigator.sendBeacon)try{c=k.navigator.sendBeacon(b.v.toString(),\"\")}catch(d){}!c&&k.Image&&((new Image).src=b.v,c=!0);c||(b.g=Mb(b.j,null),b.g.ea(b.v));b.F=Date.now();Kb(b)}ad(a)}function Zb(a){a.g&&(Tb(a),a.g.cancel(),a.g=null)}\nfunction Zc(a){Zb(a);a.u&&(k.clearTimeout(a.u),a.u=null);Yb(a);a.h.cancel();a.s&&(\"number\"===typeof a.s&&k.clearTimeout(a.s),a.s=null)}function fc(a){if(!jc(a.h)&&!a.s){a.s=!0;var b=a.Ga;x||Ea();y||(x(),y=!0);za.add(b,a);a.B=0}}function bd(a,b){if(ac(a.h)>=a.h.j-(a.s?1:0))return!1;if(a.s)return a.i=b.D.concat(a.i),!0;if(1==a.G||2==a.G||a.B>=(a.Va?0:a.Wa))return!1;a.s=ub(p(a.Ga,a,b),cd(a,a.B));a.B++;return!0}\nh.Ga=function(a){if(this.s)if(this.s=null,1==this.G){if(!a){this.U=Math.floor(1E5*Math.random());a=this.U++;const e=new M(this,this.j,a);let f=this.o;this.S&&(f?(f=sa(f),ua(f,this.S)):f=this.S);null!==this.m||this.O||(e.H=f,f=null);if(this.P)a:{var b=0;for(var c=0;c<this.i.length;c++){b:{var d=this.i[c];if(\"__data__\"in d.map&&(d=d.map.__data__,\"string\"===typeof d)){d=d.length;break b}d=void 0}if(void 0===d)break;b+=d;if(4096<b){b=c;break a}if(4096===b||c===this.i.length-1){b=c+1;break a}}b=1E3}else b=\n1E3;b=dd(this,e,b);c=N(this.I);S(c,\"RID\",a);S(c,\"CVER\",22);this.D&&S(c,\"X-HTTP-Session-Id\",this.D);$c(this,c);f&&(this.O?b=\"headers=\"+encodeURIComponent(String(Oc(f)))+\"&\"+b:this.m&&Pc(c,this.m,f));bc(this.h,e);this.Ua&&S(c,\"TYPE\",\"init\");this.P?(S(c,\"$req\",b),S(c,\"SID\",\"null\"),e.T=!0,Hb(e,c,null)):Hb(e,c,b);this.G=2}}else 3==this.G&&(a?ed(this,a):0==this.i.length||jc(this.h)||ed(this))};\nfunction ed(a,b){var c;b?c=b.l:c=a.U++;const d=N(a.I);S(d,\"SID\",a.K);S(d,\"RID\",c);S(d,\"AID\",a.T);$c(a,d);a.m&&a.o&&Pc(d,a.m,a.o);c=new M(a,a.j,c,a.B+1);null===a.m&&(c.H=a.o);b&&(a.i=b.D.concat(a.i));b=dd(a,c,1E3);c.I=Math.round(.5*a.wa)+Math.round(.5*a.wa*Math.random());bc(a.h,c);Hb(c,d,b)}function $c(a,b){a.H&&qa(a.H,function(c,d){S(b,d,c)});a.l&&nc({},function(c,d){S(b,d,c)})}\nfunction dd(a,b,c){c=Math.min(a.i.length,c);var d=a.l?p(a.l.Na,a.l,a):null;a:{var e=a.i;let f=-1;for(;;){const g=[\"count=\"+c];-1==f?0<c?(f=e[0].g,g.push(\"ofs=\"+f)):f=0:g.push(\"ofs=\"+f);let m=!0;for(let q=0;q<c;q++){let l=e[q].g;const v=e[q].map;l-=f;if(0>l)f=Math.max(0,e[q].g-100),m=!1;else try{Ic(v,g,\"req\"+l+\"_\")}catch(w){d&&d(v)}}if(m){d=g.join(\"&\");break a}}}a=a.i.splice(0,c);b.D=a;return d}function ec(a){if(!a.g&&!a.u){a.Y=1;var b=a.Fa;x||Ea();y||(x(),y=!0);za.add(b,a);a.v=0}}\nfunction $b(a){if(a.g||a.u||3<=a.v)return!1;a.Y++;a.u=ub(p(a.Fa,a),cd(a,a.v));a.v++;return!0}h.Fa=function(){this.u=null;fd(this);if(this.ba&&!(this.M||null==this.g||0>=this.R)){var a=2*this.R;this.j.info(\"BP detection timer enabled: \"+a);this.A=ub(p(this.ab,this),a)}};h.ab=function(){this.A&&(this.A=null,this.j.info(\"BP detection timeout reached.\"),this.j.info(\"Buffering proxy detected and switch to long-polling!\"),this.F=!1,this.M=!0,K(10),Zb(this),fd(this))};\nfunction Tb(a){null!=a.A&&(k.clearTimeout(a.A),a.A=null)}function fd(a){a.g=new M(a,a.j,\"rpc\",a.Y);null===a.m&&(a.g.H=a.o);a.g.O=0;var b=N(a.qa);S(b,\"RID\",\"rpc\");S(b,\"SID\",a.K);S(b,\"AID\",a.T);S(b,\"CI\",a.F?\"0\":\"1\");!a.F&&a.ja&&S(b,\"TO\",a.ja);S(b,\"TYPE\",\"xmlhttp\");$c(a,b);a.m&&a.o&&Pc(b,a.m,a.o);a.L&&(a.g.I=a.L);var c=a.g;a=a.ia;c.L=1;c.v=Ib(N(b));c.m=null;c.P=!0;Jb(c,a)}h.Za=function(){null!=this.C&&(this.C=null,Zb(this),$b(this),K(19))};function Yb(a){null!=a.C&&(k.clearTimeout(a.C),a.C=null)}\nfunction Ub(a,b){var c=null;if(a.g==b){Yb(a);Tb(a);a.g=null;var d=2}else if(Xb(a.h,b))c=b.D,dc(a.h,b),d=1;else return;if(0!=a.G)if(b.o)if(1==d){c=b.m?b.m.length:0;b=Date.now()-b.F;var e=a.B;d=qb();F(d,new tb(d,c,b,e));fc(a)}else ec(a);else if(e=b.s,3==e||0==e&&0<b.X||!(1==d&&bd(a,b)||2==d&&$b(a)))switch(c&&0<c.length&&(b=a.h,b.i=b.i.concat(c)),e){case 1:R(a,5);break;case 4:R(a,10);break;case 3:R(a,6);break;default:R(a,2)}}\nfunction cd(a,b){let c=a.Ta+Math.floor(Math.random()*a.cb);a.isActive()||(c*=2);return c*b}function R(a,b){a.j.info(\"Error code \"+b);if(2==b){var c=p(a.fb,a),d=a.Xa;const e=!d;d=new T(d||\"//www.google.com/images/cleardot.gif\");k.location&&\"http\"==k.location.protocol||qc(d,\"https\");Ib(d);e?Fc(d.toString(),c):Gc(d.toString(),c)}else K(2);a.G=0;a.l&&a.l.sa(b);ad(a);Zc(a)}h.fb=function(a){a?(this.j.info(\"Successfully pinged google.com\"),K(2)):(this.j.info(\"Failed to ping google.com\"),K(1))};\nfunction ad(a){a.G=0;a.ka=[];if(a.l){const b=kc(a.h);if(0!=b.length||0!=a.i.length)ma(a.ka,b),ma(a.ka,a.i),a.h.i.length=0,la(a.i),a.i.length=0;a.l.ra()}}function cc(a,b,c){var d=c instanceof T?N(c):new T(c);if(\"\"!=d.g)b&&(d.g=b+\".\"+d.g),rc(d,d.s);else{var e=k.location;d=e.protocol;b=b?b+\".\"+e.hostname:e.hostname;e=+e.port;var f=new T(null);d&&qc(f,d);b&&(f.g=b);e&&rc(f,e);c&&(f.l=c);d=f}c=a.D;b=a.ya;c&&b&&S(d,c,b);S(d,\"VER\",a.la);$c(a,d);return d}\nfunction Mb(a,b,c){if(b&&!a.J)throw Error(\"Can't create secondary domain capable XhrIo object.\");b=a.Ca&&!a.pa?new X(new Jc({eb:c})):new X(a.pa);b.Ha(a.J);return b}h.isActive=function(){return!!this.l&&this.l.isActive(this)};function gd(){}h=gd.prototype;h.ua=function(){};h.ta=function(){};h.sa=function(){};h.ra=function(){};h.isActive=function(){return!0};h.Na=function(){};function hd(){}hd.prototype.g=function(a,b){return new Y(a,b)};\nfunction Y(a,b){E.call(this);this.g=new Yc(b);this.l=a;this.h=b&&b.messageUrlParams||null;a=b&&b.messageHeaders||null;b&&b.clientProtocolHeaderRequired&&(a?a[\"X-Client-Protocol\"]=\"webchannel\":a={\"X-Client-Protocol\":\"webchannel\"});this.g.o=a;a=b&&b.initMessageHeaders||null;b&&b.messageContentType&&(a?a[\"X-WebChannel-Content-Type\"]=b.messageContentType:a={\"X-WebChannel-Content-Type\":b.messageContentType});b&&b.va&&(a?a[\"X-WebChannel-Client-Profile\"]=b.va:a={\"X-WebChannel-Client-Profile\":b.va});this.g.S=\na;(a=b&&b.Sb)&&!t(a)&&(this.g.m=a);this.v=b&&b.supportsCrossDomainXhr||!1;this.u=b&&b.sendRawJson||!1;(b=b&&b.httpSessionIdParam)&&!t(b)&&(this.g.D=b,a=this.h,null!==a&&b in a&&(a=this.h,b in a&&delete a[b]));this.j=new Z(this)}r(Y,E);Y.prototype.m=function(){this.g.l=this.j;this.v&&(this.g.J=!0);this.g.connect(this.l,this.h||void 0)};Y.prototype.close=function(){gc(this.g)};\nY.prototype.o=function(a){var b=this.g;if(\"string\"===typeof a){var c={};c.__data__=a;a=c}else this.u&&(c={},c.__data__=hb(a),a=c);b.i.push(new hc(b.Ya++,a));3==b.G&&fc(b)};Y.prototype.N=function(){this.g.l=null;delete this.j;gc(this.g);delete this.g;Y.aa.N.call(this)};\nfunction id(a){nb.call(this);a.__headers__&&(this.headers=a.__headers__,this.statusCode=a.__status__,delete a.__headers__,delete a.__status__);var b=a.__sm__;if(b){a:{for(const c in b){a=c;break a}a=void 0}if(this.i=a)a=this.i,b=null!==b&&a in b?b[a]:void 0;this.data=b}else this.data=a}r(id,nb);function jd(){ob.call(this);this.status=1}r(jd,ob);function Z(a){this.g=a}r(Z,gd);Z.prototype.ua=function(){F(this.g,\"a\")};Z.prototype.ta=function(a){F(this.g,new id(a))};\nZ.prototype.sa=function(a){F(this.g,new jd(a))};Z.prototype.ra=function(){F(this.g,\"b\")};hd.prototype.createWebChannel=hd.prototype.g;Y.prototype.send=Y.prototype.o;Y.prototype.open=Y.prototype.m;Y.prototype.close=Y.prototype.close;module.exports.createWebChannelTransport=function(){return new hd};module.exports.getStatEventTarget=function(){return qb()};module.exports.Event=I;module.exports.Stat={mb:0,pb:1,qb:2,Jb:3,Ob:4,Lb:5,Mb:6,Kb:7,Ib:8,Nb:9,PROXY:10,NOPROXY:11,Gb:12,Cb:13,Db:14,Bb:15,Eb:16,Fb:17,ib:18,hb:19,jb:20};Ab.NO_ERROR=0;Ab.TIMEOUT=8;Ab.HTTP_ERROR=6;\nmodule.exports.ErrorCode=Ab;Bb.COMPLETE=\"complete\";module.exports.EventType=Bb;mb.EventType=H;H.OPEN=\"a\";H.CLOSE=\"b\";H.ERROR=\"c\";H.MESSAGE=\"d\";E.prototype.listen=E.prototype.K;module.exports.WebChannel=mb;module.exports.FetchXmlHttpFactory=Jc;X.prototype.listenOnce=X.prototype.L;X.prototype.getLastError=X.prototype.Ka;X.prototype.getLastErrorCode=X.prototype.Ba;X.prototype.getStatus=X.prototype.Z;X.prototype.getResponseJson=X.prototype.Oa;X.prototype.getResponseText=X.prototype.oa;\nX.prototype.send=X.prototype.ea;X.prototype.setWithCredentials=X.prototype.Ha;module.exports.XhrIo=X;}).apply( typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {});\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Simple wrapper around a nullable UID. Mostly exists to make code more\n * readable.\n */\nexport class User {\n /** A user with a null UID. */\n static readonly UNAUTHENTICATED = new User(null);\n\n // TODO(mikelehen): Look into getting a proper uid-equivalent for\n // non-FirebaseAuth providers.\n static readonly GOOGLE_CREDENTIALS = new User('google-credentials-uid');\n static readonly FIRST_PARTY = new User('first-party-uid');\n static readonly MOCK_USER = new User('mock-user');\n\n constructor(readonly uid: string | null) {}\n\n isAuthenticated(): boolean {\n return this.uid != null;\n }\n\n /**\n * Returns a key representing this user, suitable for inclusion in a\n * dictionary.\n */\n toKey(): string {\n if (this.isAuthenticated()) {\n return 'uid:' + this.uid;\n } else {\n return 'anonymous-user';\n }\n }\n\n isEqual(otherUser: User): boolean {\n return otherUser.uid === this.uid;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** The semver (www.semver.org) version of the SDK. */\nimport { version } from '../../../firebase/package.json';\nexport let SDK_VERSION = version;\nexport function setSDKVersion(version: string): void {\n SDK_VERSION = version;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Logger, LogLevel, LogLevelString } from '@firebase/logger';\n\nimport { SDK_VERSION } from '../core/version';\nimport { formatJSON } from '../platform/format_json';\n\nexport { LogLevel, LogLevelString };\n\nconst logClient = new Logger('@firebase/firestore');\n\n// Helper methods are needed because variables can't be exported as read/write\nexport function getLogLevel(): LogLevel {\n return logClient.logLevel;\n}\n\n/**\n * Sets the verbosity of Cloud Firestore logs (debug, error, or silent).\n *\n * @param logLevel - The verbosity you set for activity and error logging. Can\n * be any of the following values:\n *\n * <ul>\n * <li>`debug` for the most verbose logging level, primarily for\n * debugging.</li>\n * <li>`error` to log errors only.</li>\n * <li><code>`silent` to turn off logging.</li>\n * </ul>\n */\nexport function setLogLevel(logLevel: LogLevelString): void {\n logClient.setLogLevel(logLevel);\n}\n\nexport function logDebug(msg: string, ...obj: unknown[]): void {\n if (logClient.logLevel <= LogLevel.DEBUG) {\n const args = obj.map(argToString);\n logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);\n }\n}\n\nexport function logError(msg: string, ...obj: unknown[]): void {\n if (logClient.logLevel <= LogLevel.ERROR) {\n const args = obj.map(argToString);\n logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);\n }\n}\n\n/**\n * @internal\n */\nexport function logWarn(msg: string, ...obj: unknown[]): void {\n if (logClient.logLevel <= LogLevel.WARN) {\n const args = obj.map(argToString);\n logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);\n }\n}\n\n/**\n * Converts an additional log parameter to a string representation.\n */\nfunction argToString(obj: unknown): string | unknown {\n if (typeof obj === 'string') {\n return obj;\n } else {\n try {\n return formatJSON(obj);\n } catch (e) {\n // Converting to JSON failed, just log the object directly\n return obj;\n }\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Formats an object as a JSON string, suitable for logging. */\nexport function formatJSON(value: unknown): string {\n return JSON.stringify(value);\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SDK_VERSION } from '../core/version';\n\nimport { logError } from './log';\n\n/**\n * Unconditionally fails, throwing an Error with the given message.\n * Messages are stripped in production builds.\n *\n * Returns `never` and can be used in expressions:\n * @example\n * let futureVar = fail('not implemented yet');\n *\n * @param code generate a new unique value with `yarn assertion-id:generate`\n * Search for an existing value using `yarn assertion-id:find X`\n */\nexport function fail(\n code: number,\n message: string,\n context?: Record<string, unknown>\n): never;\n\n/**\n * Unconditionally fails, throwing an Error with the given message.\n * Messages are stripped in production builds.\n *\n * Returns `never` and can be used in expressions:\n * @example\n * let futureVar = fail('not implemented yet');\n *\n * @param id generate a new unique value with `yarn assertion-id:generate`\n * Search for an existing value using `yarn assertion-id:find X`\n */\nexport function fail(id: number, context?: Record<string, unknown>): never;\n\nexport function fail(\n id: number,\n messageOrContext?: string | Record<string, unknown>,\n context?: Record<string, unknown>\n): never {\n let message = 'Unexpected state';\n if (typeof messageOrContext === 'string') {\n message = messageOrContext;\n } else {\n context = messageOrContext;\n }\n _fail(id, message, context);\n}\n\nfunction _fail(\n id: number,\n failure: string,\n context?: Record<string, unknown>\n): never {\n // Log the failure in addition to throw an exception, just in case the\n // exception is swallowed.\n let message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ${failure} (ID: ${id.toString(\n 16\n )})`;\n if (context !== undefined) {\n try {\n const stringContext = JSON.stringify(context);\n message += ' CONTEXT: ' + stringContext;\n } catch (e) {\n message += ' CONTEXT: ' + context;\n }\n }\n logError(message);\n\n // NOTE: We don't use FirestoreError here because these are internal failures\n // that cannot be handled by the user. (Also it would create a circular\n // dependency between the error and assert modules which doesn't work.)\n throw new Error(message);\n}\n\n/**\n * Fails if the given assertion condition is false, throwing an Error with the\n * given message if it did.\n *\n * Messages are stripped in production builds.\n *\n * @param id generate a new unique value with `yarn assertion-idgenerate`.\n * Search for an existing value using `yarn assertion-id:find X`\n */\nexport function hardAssert(\n assertion: boolean,\n id: number,\n message: string,\n context?: Record<string, unknown>\n): asserts assertion;\n\n/**\n * Fails if the given assertion condition is false, throwing an Error with the\n * given message if it did.\n *\n * Messages are stripped in production builds.\n *\n * @param id generate a new unique value with `yarn assertion-id:generate`.\n * Search for an existing value using `yarn assertion-id:find X`\n */\nexport function hardAssert(\n assertion: boolean,\n id: number,\n context?: Record<string, unknown>\n): asserts assertion;\n\nexport function hardAssert(\n assertion: boolean,\n id: number,\n messageOrContext?: string | Record<string, unknown>,\n context?: Record<string, unknown>\n): asserts assertion {\n let message = 'Unexpected state';\n if (typeof messageOrContext === 'string') {\n message = messageOrContext;\n } else {\n context = messageOrContext;\n }\n\n if (!assertion) {\n _fail(id, message, context);\n }\n}\n\n/**\n * Fails if the given assertion condition is false, throwing an Error with the\n * given message if it did.\n *\n * The code of callsites invoking this function are stripped out in production\n * builds. Any side-effects of code within the debugAssert() invocation will not\n * happen in this case.\n *\n * @internal\n */\nexport function debugAssert(\n assertion: boolean,\n message: string\n): asserts assertion {\n if (!assertion) {\n fail(0xdeb6, message);\n }\n}\n\n/**\n * Casts `obj` to `T`. In non-production builds, verifies that `obj` is an\n * instance of `T` before casting.\n */\nexport function debugCast<T>(\n obj: object,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n constructor: { new (...args: any[]): T }\n): T | never {\n debugAssert(\n obj instanceof constructor,\n `Expected type '${constructor.name}', but was '${obj.constructor.name}'`\n );\n return obj as T;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FirebaseError } from '@firebase/util';\n\n/**\n * The set of Firestore status codes. The codes are the same at the ones\n * exposed by gRPC here:\n * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md\n *\n * Possible values:\n * - 'cancelled': The operation was cancelled (typically by the caller).\n * - 'unknown': Unknown error or an error from a different error domain.\n * - 'invalid-argument': Client specified an invalid argument. Note that this\n * differs from 'failed-precondition'. 'invalid-argument' indicates\n * arguments that are problematic regardless of the state of the system\n * (e.g. an invalid field name).\n * - 'deadline-exceeded': Deadline expired before operation could complete.\n * For operations that change the state of the system, this error may be\n * returned even if the operation has completed successfully. For example,\n * a successful response from a server could have been delayed long enough\n * for the deadline to expire.\n * - 'not-found': Some requested document was not found.\n * - 'already-exists': Some document that we attempted to create already\n * exists.\n * - 'permission-denied': The caller does not have permission to execute the\n * specified operation.\n * - 'resource-exhausted': Some resource has been exhausted, perhaps a\n * per-user quota, or perhaps the entire file system is out of space.\n * - 'failed-precondition': Operation was rejected because the system is not\n * in a state required for the operation's execution.\n * - 'aborted': The operation was aborted, typically due to a concurrency\n * issue like transaction aborts, etc.\n * - 'out-of-range': Operation was attempted past the valid range.\n * - 'unimplemented': Operation is not implemented or not supported/enabled.\n * - 'internal': Internal errors. Means some invariants expected by\n * underlying system has been broken. If you see one of these errors,\n * something is very broken.\n * - 'unavailable': The service is currently unavailable. This is most likely\n * a transient condition and may be corrected by retrying with a backoff.\n * - 'data-loss': Unrecoverable data loss or corruption.\n * - 'unauthenticated': The request does not have valid authentication\n * credentials for the operation.\n */\nexport type FirestoreErrorCode =\n | 'cancelled'\n | 'unknown'\n | 'invalid-argument'\n | 'deadline-exceeded'\n | 'not-found'\n | 'already-exists'\n | 'permission-denied'\n | 'resource-exhausted'\n | 'failed-precondition'\n | 'aborted'\n | 'out-of-range'\n | 'unimplemented'\n | 'internal'\n | 'unavailable'\n | 'data-loss'\n | 'unauthenticated';\n\n/**\n * Error Codes describing the different ways Firestore can fail. These come\n * directly from GRPC.\n */\nexport type Code = FirestoreErrorCode;\n\nexport const Code = {\n // Causes are copied from:\n // https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h\n /** Not an error; returned on success. */\n OK: 'ok' as FirestoreErrorCode,\n\n /** The operation was cancelled (typically by the caller). */\n CANCELLED: 'cancelled' as FirestoreErrorCode,\n\n /** Unknown error or an error from a different error domain. */\n UNKNOWN: 'unknown' as FirestoreErrorCode,\n\n /**\n * Client specified an invalid argument. Note that this differs from\n * FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are\n * problematic regardless of the state of the system (e.g., a malformed file\n * name).\n */\n INVALID_ARGUMENT: 'invalid-argument' as FirestoreErrorCode,\n\n /**\n * Deadline expired before operation could complete. For operations that\n * change the state of the system, this error may be returned even if the\n * operation has completed successfully. For example, a successful response\n * from a server could have been delayed long enough for the deadline to\n * expire.\n */\n DEADLINE_EXCEEDED: 'deadline-exceeded' as FirestoreErrorCode,\n\n /** Some requested entity (e.g., file or directory) was not found. */\n NOT_FOUND: 'not-found' as FirestoreErrorCode,\n\n /**\n * Some entity that we attempted to create (e.g., file or directory) already\n * exists.\n */\n ALREADY_EXISTS: 'already-exists' as FirestoreErrorCode,\n\n /**\n * The caller does not have permission to execute the specified operation.\n * PERMISSION_DENIED must not be used for rejections caused by exhausting\n * some resource (use RESOURCE_EXHAUSTED instead for those errors).\n * PERMISSION_DENIED must not be used if the caller cannot be identified\n * (use UNAUTHENTICATED instead for those errors).\n */\n PERMISSION_DENIED: 'permission-denied' as FirestoreErrorCode,\n\n /**\n * The request does not have valid authentication credentials for the\n * operation.\n */\n UNAUTHENTICATED: 'unauthenticated' as FirestoreErrorCode,\n\n /**\n * Some resource has been exhausted, perhaps a per-user quota, or perhaps the\n * entire file system is out of space.\n */\n RESOURCE_EXHAUSTED: 'resource-exhausted' as FirestoreErrorCode,\n\n /**\n * Operation was rejected because the system is not in a state required for\n * the operation's execution. For example, directory to be deleted may be\n * non-empty, an rmdir operation is applied to a non-directory, etc.\n *\n * A litmus test that may help a service implementor in deciding\n * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:\n * (a) Use UNAVAILABLE if the client can retry just the failing call.\n * (b) Use ABORTED if the client should retry at a higher-level\n * (e.g., restarting a read-modify-write sequence).\n * (c) Use FAILED_PRECONDITION if the client should not retry until\n * the system state has been explicitly fixed. E.g., if an \"rmdir\"\n * fails because the directory is non-empty, FAILED_PRECONDITION\n * should be returned since the client should not retry unless\n * they have first fixed up the directory by deleting files from it.\n * (d) Use FAILED_PRECONDITION if the client performs conditional\n * REST Get/Update/Delete on a resource and the resource on the\n * server does not match the condition. E.g., conflicting\n * read-modify-write on the same resource.\n */\n FAILED_PRECONDITION: 'failed-precondition' as FirestoreErrorCode,\n\n /**\n * The operation was aborted, typically due to a concurrency issue like\n * sequencer check failures, transaction aborts, etc.\n *\n * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,\n * and UNAVAILABLE.\n */\n ABORTED: 'aborted' as FirestoreErrorCode,\n\n /**\n * Operation was attempted past the valid range. E.g., seeking or reading\n * past end of file.\n *\n * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed\n * if the system state changes. For example, a 32-bit file system will\n * generate INVALID_ARGUMENT if asked to read at an offset that is not in the\n * range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from\n * an offset past the current file size.\n *\n * There is a fair bit of overlap between FAILED_PRECONDITION and\n * OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)\n * when it applies so that callers who are iterating through a space can\n * easily look for an OUT_OF_RANGE error to detect when they are done.\n */\n OUT_OF_RANGE: 'out-of-range' as FirestoreErrorCode,\n\n /** Operation is not implemented or not supported/enabled in this service. */\n UNIMPLEMENTED: 'unimplemented' as FirestoreErrorCode,\n\n /**\n * Internal errors. Means some invariants expected by underlying System has\n * been broken. If you see one of these errors, Something is very broken.\n */\n INTERNAL: 'internal' as FirestoreErrorCode,\n\n /**\n * The service is currently unavailable. This is a most likely a transient\n * condition and may be corrected by retrying with a backoff.\n *\n * See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,\n * and UNAVAILABLE.\n */\n UNAVAILABLE: 'unavailable' as FirestoreErrorCode,\n\n /** Unrecoverable data loss or corruption. */\n DATA_LOSS: 'data-loss' as FirestoreErrorCode\n};\n\n/** An error returned by a Firestore operation. */\nexport class FirestoreError extends FirebaseError {\n /** The stack of the error. */\n readonly stack?: string;\n\n /** @hideconstructor */\n constructor(\n /**\n * The backend error code associated with this error.\n */\n readonly code: FirestoreErrorCode,\n /**\n * A custom error description.\n */\n readonly message: string\n ) {\n super(code, message);\n\n // HACK: We write a toString property directly because Error is not a real\n // class and so inheritance does not work correctly. We could alternatively\n // do the same \"back-door inheritance\" trick that FirebaseError does.\n this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport interface Resolver<R> {\n (value: R | Promise<R>): void;\n}\n\nexport interface Rejecter {\n (reason?: Error): void;\n}\n\nexport class Deferred<R = void> {\n promise: Promise<R>;\n // Assigned synchronously in constructor by Promise constructor callback.\n resolve!: Resolver<R>;\n reject!: Rejecter;\n\n constructor() {\n this.promise = new Promise((resolve: Resolver<R>, reject: Rejecter) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n}\n\n/**\n * Takes an array of values and a function from a value to a Promise. The function is run on each\n * value sequentially, waiting for the previous promise to resolve before starting the next one.\n * The returned promise resolves once the function has been run on all values.\n */\nexport function sequence<T>(\n values: T[],\n fn: (value: T) => Promise<void>\n): Promise<void> {\n let p = Promise.resolve();\n for (const value of values) {\n p = p.then(() => fn(value));\n }\n return p;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FirebaseApp, _isFirebaseServerApp } from '@firebase/app';\nimport {\n AppCheckInternalComponentName,\n AppCheckTokenListener,\n AppCheckTokenResult,\n FirebaseAppCheckInternal\n} from '@firebase/app-check-interop-types';\nimport {\n FirebaseAuthInternal,\n FirebaseAuthInternalName\n} from '@firebase/auth-interop-types';\nimport { Provider } from '@firebase/component';\n\nimport { User } from '../auth/user';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { AsyncQueue } from '../util/async_queue';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\nimport { Deferred } from '../util/promise';\n\n// TODO(mikelehen): This should be split into multiple files and probably\n// moved to an auth/ folder to match other platforms.\n\n/**\n * @internal\n */\nexport type AuthTokenFactory = () => string;\n\n/**\n * @internal\n */\nexport interface FirstPartyCredentialsSettings {\n // These are external types. Prevent minification.\n ['type']: 'firstParty';\n ['sessionIndex']: string;\n ['iamToken']: string | null;\n ['authTokenFactory']: AuthTokenFactory | null;\n}\n\nexport interface ProviderCredentialsSettings {\n // These are external types. Prevent minification.\n ['type']: 'provider';\n ['client']: CredentialsProvider<User>;\n}\n\n/** Settings for private credentials */\nexport type CredentialsSettings =\n | FirstPartyCredentialsSettings\n | ProviderCredentialsSettings;\n\nexport type TokenType = 'OAuth' | 'FirstParty' | 'AppCheck';\nexport interface Token {\n /** Type of token. */\n type: TokenType;\n\n /**\n * The user with which the token is associated (used for persisting user\n * state on disk, etc.).\n * This will be null for Tokens of the type 'AppCheck'.\n */\n user?: User;\n\n /** Header values to set for this token */\n headers: Map<string, string>;\n}\n\nexport class OAuthToken implements Token {\n type = 'OAuth' as TokenType;\n headers = new Map();\n\n constructor(value: string, public user: User) {\n this.headers.set('Authorization', `Bearer ${value}`);\n }\n}\n\n/**\n * A Listener for credential change events. The listener should fetch a new\n * token and may need to invalidate other state if the current user has also\n * changed.\n */\nexport type CredentialChangeListener<T> = (credential: T) => Promise<void>;\n\n/**\n * Provides methods for getting the uid and token for the current user and\n * listening for changes.\n */\nexport interface CredentialsProvider<T> {\n /**\n * Starts the credentials provider and specifies a listener to be notified of\n * credential changes (sign-in / sign-out, token changes). It is immediately\n * called once with the initial user.\n *\n * The change listener is invoked on the provided AsyncQueue.\n */\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<T>\n ): void;\n\n /** Requests a token for the current user. */\n getToken(): Promise<Token | null>;\n\n /**\n * Marks the last retrieved token as invalid, making the next GetToken request\n * force-refresh the token.\n */\n invalidateToken(): void;\n\n shutdown(): void;\n}\n\n/**\n * A CredentialsProvider that always yields an empty token.\n * @internal\n */\nexport class EmptyAuthCredentialsProvider implements CredentialsProvider<User> {\n getToken(): Promise<Token | null> {\n return Promise.resolve<Token | null>(null);\n }\n\n invalidateToken(): void {}\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<User>\n ): void {\n // Fire with initial user.\n asyncQueue.enqueueRetryable(() => changeListener(User.UNAUTHENTICATED));\n }\n\n shutdown(): void {}\n}\n\n/**\n * A CredentialsProvider that always returns a constant token. Used for\n * emulator token mocking.\n */\nexport class EmulatorAuthCredentialsProvider\n implements CredentialsProvider<User>\n{\n constructor(private token: Token) {}\n\n /**\n * Stores the listener registered with setChangeListener()\n * This isn't actually necessary since the UID never changes, but we use this\n * to verify the listen contract is adhered to in tests.\n */\n private changeListener: CredentialChangeListener<User> | null = null;\n\n getToken(): Promise<Token | null> {\n return Promise.resolve(this.token);\n }\n\n invalidateToken(): void {}\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<User>\n ): void {\n debugAssert(\n !this.changeListener,\n 'Can only call setChangeListener() once.'\n );\n this.changeListener = changeListener;\n // Fire with initial user.\n asyncQueue.enqueueRetryable(() => changeListener(this.token.user!));\n }\n\n shutdown(): void {\n this.changeListener = null;\n }\n}\n\n/** Credential provider for the Lite SDK. */\nexport class LiteAuthCredentialsProvider implements CredentialsProvider<User> {\n private auth: FirebaseAuthInternal | null = null;\n\n constructor(authProvider: Provider<FirebaseAuthInternalName>) {\n authProvider.onInit(auth => {\n this.auth = auth;\n });\n }\n\n getToken(): Promise<Token | null> {\n if (!this.auth) {\n return Promise.resolve(null);\n }\n\n return this.auth.getToken().then(tokenData => {\n if (tokenData) {\n hardAssert(\n typeof tokenData.accessToken === 'string',\n 0xa539,\n 'Invalid tokenData returned from getToken()',\n { tokenData }\n );\n return new OAuthToken(\n tokenData.accessToken,\n new User(this.auth!.getUid())\n );\n } else {\n return null;\n }\n });\n }\n\n invalidateToken(): void {}\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<User>\n ): void {}\n\n shutdown(): void {}\n}\n\nexport class FirebaseAuthCredentialsProvider\n implements CredentialsProvider<User>\n{\n /**\n * The auth token listener registered with FirebaseApp, retained here so we\n * can unregister it.\n */\n private tokenListener: (() => void) | undefined;\n\n /** Tracks the current User. */\n private currentUser: User = User.UNAUTHENTICATED;\n\n /**\n * Counter used to detect if the token changed while a getToken request was\n * outstanding.\n */\n private tokenCounter = 0;\n\n private forceRefresh = false;\n\n private auth: FirebaseAuthInternal | null = null;\n\n constructor(private authProvider: Provider<FirebaseAuthInternalName>) {}\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<User>\n ): void {\n hardAssert(\n this.tokenListener === undefined,\n 0xa540,\n 'Token listener already added'\n );\n let lastTokenId = this.tokenCounter;\n\n // A change listener that prevents double-firing for the same token change.\n const guardedChangeListener: (user: User) => Promise<void> = user => {\n if (this.tokenCounter !== lastTokenId) {\n lastTokenId = this.tokenCounter;\n return changeListener(user);\n } else {\n return Promise.resolve();\n }\n };\n\n // A promise that can be waited on to block on the next token change.\n // This promise is re-created after each change.\n let nextToken = new Deferred<void>();\n\n this.tokenListener = () => {\n this.tokenCounter++;\n this.currentUser = this.getUser();\n nextToken.resolve();\n nextToken = new Deferred<void>();\n asyncQueue.enqueueRetryable(() =>\n guardedChangeListener(this.currentUser)\n );\n };\n\n const awaitNextToken: () => void = () => {\n const currentTokenAttempt = nextToken;\n asyncQueue.enqueueRetryable(async () => {\n await currentTokenAttempt.promise;\n await guardedChangeListener(this.currentUser);\n });\n };\n\n const registerAuth = (auth: FirebaseAuthInternal): void => {\n logDebug('FirebaseAuthCredentialsProvider', 'Auth detected');\n this.auth = auth;\n if (this.tokenListener) {\n this.auth.addAuthTokenListener(this.tokenListener);\n awaitNextToken();\n }\n };\n\n this.authProvider.onInit(auth => registerAuth(auth));\n\n // Our users can initialize Auth right after Firestore, so we give it\n // a chance to register itself with the component framework before we\n // determine whether to start up in unauthenticated mode.\n setTimeout(() => {\n if (!this.auth) {\n const auth = this.authProvider.getImmediate({ optional: true });\n if (auth) {\n registerAuth(auth);\n } else {\n // If auth is still not available, proceed with `null` user\n logDebug('FirebaseAuthCredentialsProvider', 'Auth not yet detected');\n nextToken.resolve();\n nextToken = new Deferred<void>();\n }\n }\n }, 0);\n\n awaitNextToken();\n }\n\n getToken(): Promise<Token | null> {\n debugAssert(\n this.tokenListener != null,\n 'FirebaseAuthCredentialsProvider not started.'\n );\n\n // Take note of the current value of the tokenCounter so that this method\n // can fail (with an ABORTED error) if there is a token change while the\n // request is outstanding.\n const initialTokenCounter = this.tokenCounter;\n const forceRefresh = this.forceRefresh;\n this.forceRefresh = false;\n\n if (!this.auth) {\n return Promise.resolve(null);\n }\n\n return this.auth.getToken(forceRefresh).then(tokenData => {\n // Cancel the request since the token changed while the request was\n // outstanding so the response is potentially for a previous user (which\n // user, we can't be sure).\n if (this.tokenCounter !== initialTokenCounter) {\n logDebug(\n 'FirebaseAuthCredentialsProvider',\n 'getToken aborted due to token change.'\n );\n return this.getToken();\n } else {\n if (tokenData) {\n hardAssert(\n typeof tokenData.accessToken === 'string',\n 0x7c5d,\n 'Invalid tokenData returned from getToken()',\n { tokenData }\n );\n return new OAuthToken(tokenData.accessToken, this.currentUser);\n } else {\n return null;\n }\n }\n });\n }\n\n invalidateToken(): void {\n this.forceRefresh = true;\n }\n\n shutdown(): void {\n if (this.auth && this.tokenListener) {\n this.auth.removeAuthTokenListener(this.tokenListener);\n }\n this.tokenListener = undefined;\n }\n\n // Auth.getUid() can return null even with a user logged in. It is because\n // getUid() is synchronous, but the auth code populating Uid is asynchronous.\n // This method should only be called in the AuthTokenListener callback\n // to guarantee to get the actual user.\n private getUser(): User {\n const currentUid = this.auth && this.auth.getUid();\n hardAssert(\n currentUid === null || typeof currentUid === 'string',\n 0x0807,\n 'Received invalid UID',\n { currentUid }\n );\n return new User(currentUid);\n }\n}\n\n/*\n * FirstPartyToken provides a fresh token each time its value\n * is requested, because if the token is too old, requests will be rejected.\n * Technically this may no longer be necessary since the SDK should gracefully\n * recover from unauthenticated errors (see b/33147818 for context), but it's\n * safer to keep the implementation as-is.\n */\nexport class FirstPartyToken implements Token {\n type = 'FirstParty' as TokenType;\n user = User.FIRST_PARTY;\n private _headers = new Map();\n\n constructor(\n private readonly sessionIndex: string,\n private readonly iamToken: string | null,\n private readonly authTokenFactory: AuthTokenFactory | null\n ) {}\n\n /**\n * Gets an authorization token, using a provided factory function, or return\n * null.\n */\n private getAuthToken(): string | null {\n if (this.authTokenFactory) {\n return this.authTokenFactory();\n } else {\n return null;\n }\n }\n\n get headers(): Map<string, string> {\n this._headers.set('X-Goog-AuthUser', this.sessionIndex);\n // Use array notation to prevent minification\n const authHeaderTokenValue = this.getAuthToken();\n if (authHeaderTokenValue) {\n this._headers.set('Authorization', authHeaderTokenValue);\n }\n if (this.iamToken) {\n this._headers.set('X-Goog-Iam-Authorization-Token', this.iamToken);\n }\n\n return this._headers;\n }\n}\n\n/*\n * Provides user credentials required for the Firestore JavaScript SDK\n * to authenticate the user, using technique that is only available\n * to applications hosted by Google.\n */\nexport class FirstPartyAuthCredentialsProvider\n implements CredentialsProvider<User>\n{\n constructor(\n private sessionIndex: string,\n private iamToken: string | null,\n private authTokenFactory: AuthTokenFactory | null\n ) {}\n\n getToken(): Promise<Token | null> {\n return Promise.resolve(\n new FirstPartyToken(\n this.sessionIndex,\n this.iamToken,\n this.authTokenFactory\n )\n );\n }\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<User>\n ): void {\n // Fire with initial uid.\n asyncQueue.enqueueRetryable(() => changeListener(User.FIRST_PARTY));\n }\n\n shutdown(): void {}\n\n invalidateToken(): void {}\n}\n\nexport class AppCheckToken implements Token {\n type = 'AppCheck' as TokenType;\n headers = new Map();\n\n constructor(private value: string) {\n if (value && value.length > 0) {\n this.headers.set('x-firebase-appcheck', this.value);\n }\n }\n}\n\nexport class FirebaseAppCheckTokenProvider\n implements CredentialsProvider<string>\n{\n /**\n * The AppCheck token listener registered with FirebaseApp, retained here so\n * we can unregister it.\n */\n private tokenListener: AppCheckTokenListener | undefined;\n private forceRefresh = false;\n private appCheck: FirebaseAppCheckInternal | null = null;\n private latestAppCheckToken: string | null = null;\n private serverAppAppCheckToken: string | null = null;\n\n constructor(\n app: FirebaseApp,\n private appCheckProvider: Provider<AppCheckInternalComponentName>\n ) {\n if (_isFirebaseServerApp(app) && app.settings.appCheckToken) {\n this.serverAppAppCheckToken = app.settings.appCheckToken;\n }\n }\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<string>\n ): void {\n hardAssert(\n this.tokenListener === undefined,\n 0x0db8,\n 'Token listener already added'\n );\n\n const onTokenChanged: (\n tokenResult: AppCheckTokenResult\n ) => Promise<void> = tokenResult => {\n if (tokenResult.error != null) {\n logDebug(\n 'FirebaseAppCheckTokenProvider',\n `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`\n );\n }\n const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;\n this.latestAppCheckToken = tokenResult.token;\n logDebug(\n 'FirebaseAppCheckTokenProvider',\n `Received ${tokenUpdated ? 'new' : 'existing'} token.`\n );\n return tokenUpdated\n ? changeListener(tokenResult.token)\n : Promise.resolve();\n };\n\n this.tokenListener = (tokenResult: AppCheckTokenResult) => {\n asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));\n };\n\n const registerAppCheck = (appCheck: FirebaseAppCheckInternal): void => {\n logDebug('FirebaseAppCheckTokenProvider', 'AppCheck detected');\n this.appCheck = appCheck;\n if (this.tokenListener) {\n this.appCheck.addTokenListener(this.tokenListener);\n }\n };\n\n this.appCheckProvider.onInit(appCheck => registerAppCheck(appCheck));\n\n // Our users can initialize AppCheck after Firestore, so we give it\n // a chance to register itself with the component framework.\n setTimeout(() => {\n if (!this.appCheck) {\n const appCheck = this.appCheckProvider.getImmediate({ optional: true });\n if (appCheck) {\n registerAppCheck(appCheck);\n } else {\n // If AppCheck is still not available, proceed without it.\n logDebug(\n 'FirebaseAppCheckTokenProvider',\n 'AppCheck not yet detected'\n );\n }\n }\n }, 0);\n }\n\n getToken(): Promise<Token | null> {\n if (this.serverAppAppCheckToken) {\n return Promise.resolve(new AppCheckToken(this.serverAppAppCheckToken));\n }\n debugAssert(\n this.tokenListener != null,\n 'FirebaseAppCheckTokenProvider not started.'\n );\n\n const forceRefresh = this.forceRefresh;\n this.forceRefresh = false;\n\n if (!this.appCheck) {\n return Promise.resolve(null);\n }\n\n return this.appCheck.getToken(forceRefresh).then(tokenResult => {\n if (tokenResult) {\n hardAssert(\n typeof tokenResult.token === 'string',\n 0xae0e,\n 'Invalid tokenResult returned from getToken()',\n { tokenResult }\n );\n this.latestAppCheckToken = tokenResult.token;\n return new AppCheckToken(tokenResult.token);\n } else {\n return null;\n }\n });\n }\n\n invalidateToken(): void {\n this.forceRefresh = true;\n }\n\n shutdown(): void {\n if (this.appCheck && this.tokenListener) {\n this.appCheck.removeTokenListener(this.tokenListener);\n }\n this.tokenListener = undefined;\n }\n}\n\n/**\n * An AppCheck token provider that always yields an empty token.\n * @internal\n */\nexport class EmptyAppCheckTokenProvider implements CredentialsProvider<string> {\n getToken(): Promise<Token | null> {\n return Promise.resolve<Token | null>(new AppCheckToken(''));\n }\n\n invalidateToken(): void {}\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<string>\n ): void {}\n\n shutdown(): void {}\n}\n\n/** AppCheck token provider for the Lite SDK. */\nexport class LiteAppCheckTokenProvider implements CredentialsProvider<string> {\n private appCheck: FirebaseAppCheckInternal | null = null;\n private serverAppAppCheckToken: string | null = null;\n\n constructor(\n app: FirebaseApp,\n private appCheckProvider: Provider<AppCheckInternalComponentName>\n ) {\n if (_isFirebaseServerApp(app) && app.settings.appCheckToken) {\n this.serverAppAppCheckToken = app.settings.appCheckToken;\n }\n appCheckProvider.onInit(appCheck => {\n this.appCheck = appCheck;\n });\n }\n\n getToken(): Promise<Token | null> {\n if (this.serverAppAppCheckToken) {\n return Promise.resolve(new AppCheckToken(this.serverAppAppCheckToken));\n }\n\n if (!this.appCheck) {\n return Promise.resolve(null);\n }\n\n return this.appCheck.getToken().then(tokenResult => {\n if (tokenResult) {\n hardAssert(\n typeof tokenResult.token === 'string',\n 0x0d8e,\n 'Invalid tokenResult returned from getToken()',\n { tokenResult }\n );\n return new AppCheckToken(tokenResult.token);\n } else {\n return null;\n }\n });\n }\n\n invalidateToken(): void {}\n\n start(\n asyncQueue: AsyncQueue,\n changeListener: CredentialChangeListener<string>\n ): void {}\n\n shutdown(): void {}\n}\n\n/**\n * Builds a CredentialsProvider depending on the type of\n * the credentials passed in.\n */\nexport function makeAuthCredentialsProvider(\n credentials?: CredentialsSettings\n): CredentialsProvider<User> {\n if (!credentials) {\n return new EmptyAuthCredentialsProvider();\n }\n switch (credentials['type']) {\n case 'firstParty':\n return new FirstPartyAuthCredentialsProvider(\n credentials['sessionIndex'] || '0',\n credentials['iamToken'] || null,\n credentials['authTokenFactory'] || null\n );\n\n case 'provider':\n return credentials['client'];\n\n default:\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'makeAuthCredentialsProvider failed due to invalid credential type'\n );\n }\n}\n","/**\n * @license\n * Copyright 2023 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * An instance of the Platform's 'TextEncoder' implementation.\n */\nexport function newTextEncoder(): TextEncoder {\n return new TextEncoder();\n}\n\n/**\n * An instance of the Platform's 'TextDecoder' implementation.\n */\nexport function newTextDecoder(): TextDecoder {\n return new TextDecoder('utf-8');\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { randomBytes } from '../platform/random_bytes';\nimport { newTextEncoder } from '../platform/text_serializer';\n\nimport { debugAssert } from './assert';\n\nexport type EventHandler<E> = (value: E) => void;\nexport interface Indexable {\n [k: string]: unknown;\n}\n\n/**\n * A utility class for generating unique alphanumeric IDs of a specified length.\n *\n * @internal\n * Exported internally for testing purposes.\n */\nexport class AutoId {\n static newId(): string {\n // Alphanumeric characters\n const chars =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n // The largest byte value that is a multiple of `char.length`.\n const maxMultiple = Math.floor(256 / chars.length) * chars.length;\n debugAssert(\n 0 < maxMultiple && maxMultiple < 256,\n `Expect maxMultiple to be (0, 256), but got ${maxMultiple}`\n );\n\n let autoId = '';\n const targetLength = 20;\n while (autoId.length < targetLength) {\n const bytes = randomBytes(40);\n for (let i = 0; i < bytes.length; ++i) {\n // Only accept values that are [0, maxMultiple), this ensures they can\n // be evenly mapped to indices of `chars` via a modulo operation.\n if (autoId.length < targetLength && bytes[i] < maxMultiple) {\n autoId += chars.charAt(bytes[i] % chars.length);\n }\n }\n }\n debugAssert(autoId.length === targetLength, 'Invalid auto ID: ' + autoId);\n\n return autoId;\n }\n}\n\nexport function primitiveComparator<T>(left: T, right: T): number {\n if (left < right) {\n return -1;\n }\n if (left > right) {\n return 1;\n }\n return 0;\n}\n\nexport interface Equatable<T> {\n isEqual(other: T): boolean;\n}\n\n/** Compare strings in UTF-8 encoded byte order */\nexport function compareUtf8Strings(left: string, right: string): number {\n let i = 0;\n while (i < left.length && i < right.length) {\n const leftCodePoint = left.codePointAt(i)!;\n const rightCodePoint = right.codePointAt(i)!;\n\n if (leftCodePoint !== rightCodePoint) {\n if (leftCodePoint < 128 && rightCodePoint < 128) {\n // ASCII comparison\n return primitiveComparator(leftCodePoint, rightCodePoint);\n } else {\n // Lazy instantiate TextEncoder\n const encoder = newTextEncoder();\n\n // UTF-8 encode the character at index i for byte comparison.\n const leftBytes = encoder.encode(getUtf8SafeSubstring(left, i));\n const rightBytes = encoder.encode(getUtf8SafeSubstring(right, i));\n\n const comp = compareByteArrays(leftBytes, rightBytes);\n if (comp !== 0) {\n return comp;\n } else {\n // EXTREMELY RARE CASE: Code points differ, but their UTF-8 byte\n // representations are identical. This can happen with malformed input\n // (invalid surrogate pairs). The backend also actively prevents invalid\n // surrogates as INVALID_ARGUMENT errors, so we almost never receive\n // invalid strings from backend.\n // Fallback to code point comparison for graceful handling.\n return primitiveComparator(leftCodePoint, rightCodePoint);\n }\n }\n }\n // Increment by 2 for surrogate pairs, 1 otherwise\n i += leftCodePoint > 0xffff ? 2 : 1;\n }\n\n // Compare lengths if all characters are equal\n return primitiveComparator(left.length, right.length);\n}\n\nfunction getUtf8SafeSubstring(str: string, index: number): string {\n const firstCodePoint = str.codePointAt(index)!;\n if (firstCodePoint > 0xffff) {\n // It's a surrogate pair, return the whole pair\n return str.substring(index, index + 2);\n } else {\n // It's a single code point, return it\n return str.substring(index, index + 1);\n }\n}\n\nfunction compareByteArrays(left: Uint8Array, right: Uint8Array): number {\n for (let i = 0; i < left.length && i < right.length; ++i) {\n if (left[i] !== right[i]) {\n return primitiveComparator(left[i], right[i]);\n }\n }\n return primitiveComparator(left.length, right.length);\n}\n\nexport interface Iterable<V> {\n forEach: (cb: (v: V) => void) => void;\n}\n\n/** Helper to compare arrays using isEqual(). */\nexport function arrayEquals<T>(\n left: T[],\n right: T[],\n comparator: (l: T, r: T) => boolean\n): boolean {\n if (left.length !== right.length) {\n return false;\n }\n return left.every((value, index) => comparator(value, right[index]));\n}\n/**\n * Returns the immediate lexicographically-following string. This is useful to\n * construct an inclusive range for indexeddb iterators.\n */\nexport function immediateSuccessor(s: string): string {\n // Return the input string, with an additional NUL byte appended.\n return s + '\\0';\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../../util/assert';\n\n/**\n * Generates `nBytes` of random bytes.\n *\n * If `nBytes < 0` , an error will be thrown.\n */\nexport function randomBytes(nBytes: number): Uint8Array {\n debugAssert(nBytes >= 0, `Expecting non-negative nBytes, got: ${nBytes}`);\n\n // Polyfills for IE and WebWorker by using `self` and `msCrypto` when `crypto` is not available.\n const crypto =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof self !== 'undefined' && (self.crypto || (self as any)['msCrypto']);\n const bytes = new Uint8Array(nBytes);\n if (crypto && typeof crypto.getRandomValues === 'function') {\n crypto.getRandomValues(bytes);\n } else {\n // Falls back to Math.random\n for (let i = 0; i < nBytes; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return bytes;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Integer } from '@firebase/webchannel-wrapper/bloom-blob';\n\nimport { debugAssert, fail } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { compareUtf8Strings, primitiveComparator } from '../util/misc';\n\nexport const DOCUMENT_KEY_NAME = '__name__';\n\n/**\n * Path represents an ordered sequence of string segments.\n */\nabstract class BasePath<B extends BasePath<B>> {\n private segments: string[];\n private offset: number;\n private len: number;\n\n constructor(segments: string[], offset?: number, length?: number) {\n if (offset === undefined) {\n offset = 0;\n } else if (offset > segments.length) {\n fail(0x027d, 'offset out of range', {\n offset,\n range: segments.length\n });\n }\n\n if (length === undefined) {\n length = segments.length - offset;\n } else if (length > segments.length - offset) {\n fail(0x06d2, 'length out of range', {\n length,\n range: segments.length - offset\n });\n }\n this.segments = segments;\n this.offset = offset;\n this.len = length;\n }\n\n /**\n * Abstract constructor method to construct an instance of B with the given\n * parameters.\n */\n protected abstract construct(\n segments: string[],\n offset?: number,\n length?: number\n ): B;\n\n /**\n * Returns a String representation.\n *\n * Implementing classes are required to provide deterministic implementations as\n * the String representation is used to obtain canonical Query IDs.\n */\n abstract toString(): string;\n\n get length(): number {\n return this.len;\n }\n\n isEqual(other: B): boolean {\n return BasePath.comparator(this, other) === 0;\n }\n\n child(nameOrPath: string | B): B {\n const segments = this.segments.slice(this.offset, this.limit());\n if (nameOrPath instanceof BasePath) {\n nameOrPath.forEach(segment => {\n segments.push(segment);\n });\n } else {\n segments.push(nameOrPath);\n }\n return this.construct(segments);\n }\n\n /** The index of one past the last segment of the path. */\n private limit(): number {\n return this.offset + this.length;\n }\n\n popFirst(size?: number): B {\n size = size === undefined ? 1 : size;\n debugAssert(\n this.length >= size,\n \"Can't call popFirst() with less segments\"\n );\n return this.construct(\n this.segments,\n this.offset + size,\n this.length - size\n );\n }\n\n popLast(): B {\n debugAssert(!this.isEmpty(), \"Can't call popLast() on empty path\");\n return this.construct(this.segments, this.offset, this.length - 1);\n }\n\n firstSegment(): string {\n debugAssert(!this.isEmpty(), \"Can't call firstSegment() on empty path\");\n return this.segments[this.offset];\n }\n\n lastSegment(): string {\n debugAssert(!this.isEmpty(), \"Can't call lastSegment() on empty path\");\n return this.get(this.length - 1);\n }\n\n get(index: number): string {\n debugAssert(index < this.length, 'Index out of range');\n return this.segments[this.offset + index];\n }\n\n isEmpty(): boolean {\n return this.length === 0;\n }\n\n isPrefixOf(other: this): boolean {\n if (other.length < this.length) {\n return false;\n }\n\n for (let i = 0; i < this.length; i++) {\n if (this.get(i) !== other.get(i)) {\n return false;\n }\n }\n\n return true;\n }\n\n isImmediateParentOf(potentialChild: this): boolean {\n if (this.length + 1 !== potentialChild.length) {\n return false;\n }\n\n for (let i = 0; i < this.length; i++) {\n if (this.get(i) !== potentialChild.get(i)) {\n return false;\n }\n }\n\n return true;\n }\n\n forEach(fn: (segment: string) => void): void {\n for (let i = this.offset, end = this.limit(); i < end; i++) {\n fn(this.segments[i]);\n }\n }\n\n toArray(): string[] {\n return this.segments.slice(this.offset, this.limit());\n }\n\n /**\n * Compare 2 paths segment by segment, prioritizing numeric IDs\n * (e.g., \"__id123__\") in numeric ascending order, followed by string\n * segments in lexicographical order.\n */\n static comparator<T extends BasePath<T>>(\n p1: BasePath<T>,\n p2: BasePath<T>\n ): number {\n const len = Math.min(p1.length, p2.length);\n for (let i = 0; i < len; i++) {\n const comparison = BasePath.compareSegments(p1.get(i), p2.get(i));\n if (comparison !== 0) {\n return comparison;\n }\n }\n return primitiveComparator(p1.length, p2.length);\n }\n\n private static compareSegments(lhs: string, rhs: string): number {\n const isLhsNumeric = BasePath.isNumericId(lhs);\n const isRhsNumeric = BasePath.isNumericId(rhs);\n\n if (isLhsNumeric && !isRhsNumeric) {\n // Only lhs is numeric\n return -1;\n } else if (!isLhsNumeric && isRhsNumeric) {\n // Only rhs is numeric\n return 1;\n } else if (isLhsNumeric && isRhsNumeric) {\n // both numeric\n return BasePath.extractNumericId(lhs).compare(\n BasePath.extractNumericId(rhs)\n );\n } else {\n // both non-numeric\n return compareUtf8Strings(lhs, rhs);\n }\n }\n\n // Checks if a segment is a numeric ID (starts with \"__id\" and ends with \"__\").\n private static isNumericId(segment: string): boolean {\n return segment.startsWith('__id') && segment.endsWith('__');\n }\n\n private static extractNumericId(segment: string): Integer {\n return Integer.fromString(segment.substring(4, segment.length - 2));\n }\n}\n\n/**\n * A slash-separated path for navigating resources (documents and collections)\n * within Firestore.\n *\n * @internal\n */\nexport class ResourcePath extends BasePath<ResourcePath> {\n protected construct(\n segments: string[],\n offset?: number,\n length?: number\n ): ResourcePath {\n return new ResourcePath(segments, offset, length);\n }\n\n canonicalString(): string {\n // NOTE: The client is ignorant of any path segments containing escape\n // sequences (e.g. __id123__) and just passes them through raw (they exist\n // for legacy reasons and should not be used frequently).\n\n return this.toArray().join('/');\n }\n\n toString(): string {\n return this.canonicalString();\n }\n\n /**\n * Returns a string representation of this path\n * where each path segment has been encoded with\n * `encodeURIComponent`.\n */\n toUriEncodedString(): string {\n return this.toArray().map(encodeURIComponent).join('/');\n }\n\n /**\n * Creates a resource path from the given slash-delimited string. If multiple\n * arguments are provided, all components are combined. Leading and trailing\n * slashes from all components are ignored.\n */\n static fromString(...pathComponents: string[]): ResourcePath {\n // NOTE: The client is ignorant of any path segments containing escape\n // sequences (e.g. __id123__) and just passes them through raw (they exist\n // for legacy reasons and should not be used frequently).\n\n const segments: string[] = [];\n for (const path of pathComponents) {\n if (path.indexOf('//') >= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid segment (${path}). Paths must not contain // in them.`\n );\n }\n // Strip leading and trailing slashed.\n segments.push(...path.split('/').filter(segment => segment.length > 0));\n }\n\n return new ResourcePath(segments);\n }\n\n static emptyPath(): ResourcePath {\n return new ResourcePath([]);\n }\n}\n\nconst identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;\n\n/**\n * A dot-separated path for navigating sub-objects within a document.\n * @internal\n */\nexport class FieldPath extends BasePath<FieldPath> {\n protected construct(\n segments: string[],\n offset?: number,\n length?: number\n ): FieldPath {\n return new FieldPath(segments, offset, length);\n }\n\n /**\n * Returns true if the string could be used as a segment in a field path\n * without escaping.\n */\n private static isValidIdentifier(segment: string): boolean {\n return identifierRegExp.test(segment);\n }\n\n canonicalString(): string {\n return this.toArray()\n .map(str => {\n str = str.replace(/\\\\/g, '\\\\\\\\').replace(/`/g, '\\\\`');\n if (!FieldPath.isValidIdentifier(str)) {\n str = '`' + str + '`';\n }\n return str;\n })\n .join('.');\n }\n\n toString(): string {\n return this.canonicalString();\n }\n\n /**\n * Returns true if this field references the key of a document.\n */\n isKeyField(): boolean {\n return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;\n }\n\n /**\n * The field designating the key of a document.\n */\n static keyField(): FieldPath {\n return new FieldPath([DOCUMENT_KEY_NAME]);\n }\n\n /**\n * Parses a field string from the given server-formatted string.\n *\n * - Splitting the empty string is not allowed (for now at least).\n * - Empty segments within the string (e.g. if there are two consecutive\n * separators) are not allowed.\n *\n * TODO(b/37244157): we should make this more strict. Right now, it allows\n * non-identifier path components, even if they aren't escaped.\n */\n static fromServerFormat(path: string): FieldPath {\n const segments: string[] = [];\n let current = '';\n let i = 0;\n\n const addCurrentSegment = (): void => {\n if (current.length === 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid field path (${path}). Paths must not be empty, begin ` +\n `with '.', end with '.', or contain '..'`\n );\n }\n segments.push(current);\n current = '';\n };\n\n let inBackticks = false;\n\n while (i < path.length) {\n const c = path[i];\n if (c === '\\\\') {\n if (i + 1 === path.length) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Path has trailing escape character: ' + path\n );\n }\n const next = path[i + 1];\n if (!(next === '\\\\' || next === '.' || next === '`')) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Path has invalid escape sequence: ' + path\n );\n }\n current += next;\n i += 2;\n } else if (c === '`') {\n inBackticks = !inBackticks;\n i++;\n } else if (c === '.' && !inBackticks) {\n addCurrentSegment();\n i++;\n } else {\n current += c;\n i++;\n }\n }\n addCurrentSegment();\n\n if (inBackticks) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Unterminated ` in path: ' + path\n );\n }\n\n return new FieldPath(segments);\n }\n\n static emptyPath(): FieldPath {\n return new FieldPath([]);\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../util/assert';\n\nimport { ResourcePath } from './path';\n\n/**\n * @internal\n */\nexport class DocumentKey {\n constructor(readonly path: ResourcePath) {\n debugAssert(\n DocumentKey.isDocumentKey(path),\n 'Invalid DocumentKey with an odd number of segments: ' +\n path.toArray().join('/')\n );\n }\n\n static fromPath(path: string): DocumentKey {\n return new DocumentKey(ResourcePath.fromString(path));\n }\n\n static fromName(name: string): DocumentKey {\n return new DocumentKey(ResourcePath.fromString(name).popFirst(5));\n }\n\n static empty(): DocumentKey {\n return new DocumentKey(ResourcePath.emptyPath());\n }\n\n get collectionGroup(): string {\n debugAssert(\n !this.path.isEmpty(),\n 'Cannot get collection group for empty key'\n );\n return this.path.popLast().lastSegment();\n }\n\n /** Returns true if the document is in the specified collectionId. */\n hasCollectionId(collectionId: string): boolean {\n return (\n this.path.length >= 2 &&\n this.path.get(this.path.length - 2) === collectionId\n );\n }\n\n /** Returns the collection group (i.e. the name of the parent collection) for this key. */\n getCollectionGroup(): string {\n debugAssert(\n !this.path.isEmpty(),\n 'Cannot get collection group for empty key'\n );\n return this.path.get(this.path.length - 2);\n }\n\n /** Returns the fully qualified path to the parent collection. */\n getCollectionPath(): ResourcePath {\n return this.path.popLast();\n }\n\n isEqual(other: DocumentKey | null): boolean {\n return (\n other !== null && ResourcePath.comparator(this.path, other.path) === 0\n );\n }\n\n toString(): string {\n return this.path.toString();\n }\n\n static comparator(k1: DocumentKey, k2: DocumentKey): number {\n return ResourcePath.comparator(k1.path, k2.path);\n }\n\n static isDocumentKey(path: ResourcePath): boolean {\n return path.length % 2 === 0;\n }\n\n /**\n * Creates and returns a new document key with the given segments.\n *\n * @param segments - The segments of the path to the document\n * @returns A new instance of DocumentKey\n */\n static fromSegments(segments: string[]): DocumentKey {\n return new DocumentKey(new ResourcePath(segments.slice()));\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DocumentKey } from '../model/document_key';\nimport { ResourcePath } from '../model/path';\n\nimport { fail } from './assert';\nimport { Code, FirestoreError } from './error';\n\n/** Types accepted by validateType() and related methods for validation. */\nexport type ValidationType =\n | 'undefined'\n | 'object'\n | 'function'\n | 'boolean'\n | 'number'\n | 'string'\n | 'non-empty string';\n\nexport function validateNonEmptyArgument(\n functionName: string,\n argumentName: string,\n argument?: string\n): asserts argument is string {\n if (!argument) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() cannot be called with an empty ${argumentName}.`\n );\n }\n}\n\n/**\n * Validates that two boolean options are not set at the same time.\n * @internal\n */\nexport function validateIsNotUsedTogether(\n optionName1: string,\n argument1: boolean | undefined,\n optionName2: string,\n argument2: boolean | undefined\n): void {\n if (argument1 === true && argument2 === true) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `${optionName1} and ${optionName2} cannot be used together.`\n );\n }\n}\n\n/**\n * Validates that `path` refers to a document (indicated by the fact it contains\n * an even numbers of segments).\n */\nexport function validateDocumentPath(path: ResourcePath): void {\n if (!DocumentKey.isDocumentKey(path)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid document reference. Document references must have an even number of segments, but ${path} has ${path.length}.`\n );\n }\n}\n\n/**\n * Validates that `path` refers to a collection (indicated by the fact it\n * contains an odd numbers of segments).\n */\nexport function validateCollectionPath(path: ResourcePath): void {\n if (DocumentKey.isDocumentKey(path)) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Invalid collection reference. Collection references must have an odd number of segments, but ${path} has ${path.length}.`\n );\n }\n}\n\n/**\n * Returns true if it's a non-null object without a custom prototype\n * (i.e. excludes Array, Date, etc.).\n */\nexport function isPlainObject(input: unknown): boolean {\n return (\n typeof input === 'object' &&\n input !== null &&\n (Object.getPrototypeOf(input) === Object.prototype ||\n Object.getPrototypeOf(input) === null)\n );\n}\n\n/** Returns a string describing the type / value of the provided input. */\nexport function valueDescription(input: unknown): string {\n if (input === undefined) {\n return 'undefined';\n } else if (input === null) {\n return 'null';\n } else if (typeof input === 'string') {\n if (input.length > 20) {\n input = `${input.substring(0, 20)}...`;\n }\n return JSON.stringify(input);\n } else if (typeof input === 'number' || typeof input === 'boolean') {\n return '' + input;\n } else if (typeof input === 'object') {\n if (input instanceof Array) {\n return 'an array';\n } else {\n const customObjectName = tryGetCustomObjectType(input!);\n if (customObjectName) {\n return `a custom ${customObjectName} object`;\n } else {\n return 'an object';\n }\n }\n } else if (typeof input === 'function') {\n return 'a function';\n } else {\n return fail(0x3029, 'Unknown wrong type', { type: typeof input });\n }\n}\n\n/** try to get the constructor name for an object. */\nexport function tryGetCustomObjectType(input: object): string | null {\n if (input.constructor) {\n return input.constructor.name;\n }\n return null;\n}\n\n/**\n * Casts `obj` to `T`, optionally unwrapping Compat types to expose the\n * underlying instance. Throws if `obj` is not an instance of `T`.\n *\n * This cast is used in the Lite and Full SDK to verify instance types for\n * arguments passed to the public API.\n * @internal\n */\nexport function cast<T>(\n obj: object,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n constructor: { new (...args: any[]): T }\n): T | never {\n if ('_delegate' in obj) {\n // Unwrap Compat types\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n obj = (obj as any)._delegate;\n }\n\n if (!(obj instanceof constructor)) {\n if (constructor.name === obj.constructor.name) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Type does not match the expected instance. Did you pass a ' +\n `reference from a different Firestore SDK?`\n );\n } else {\n const description = valueDescription(obj);\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Expected type '${constructor.name}', but it was: ${description}`\n );\n }\n }\n return obj as T;\n}\n\nexport function validatePositiveNumber(functionName: string, n: number): void {\n if (n <= 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n `Function ${functionName}() requires a positive number, but it was: ${n}.`\n );\n }\n}\n","/**\n * @license\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isPlainObject } from '../util/input_validation';\n\nimport { Code, FirestoreError } from './error';\n\n/**\n * A list of data types Firestore objects may serialize in their toJSON implemenetations.\n * @private\n * @internal\n */\nexport type JsonTypeDesc =\n | 'object'\n | 'string'\n | 'number'\n | 'boolean'\n | 'null'\n | 'undefined';\n\n/**\n * An association of JsonTypeDesc values to their native types.\n * @private\n * @internal\n */\nexport type TSType<T extends JsonTypeDesc> = T extends 'object'\n ? object\n : T extends 'string'\n ? string\n : T extends 'number'\n ? number\n : T extends 'boolean'\n ? boolean\n : T extends 'null'\n ? null\n : T extends 'undefined'\n ? undefined\n : never;\n\n/**\n * The representation of a JSON object property name and its type value.\n * @private\n * @internal\n */\nexport interface Property<T extends JsonTypeDesc> {\n value?: TSType<T>;\n typeString: JsonTypeDesc;\n}\n\n/**\n * A type Firestore data types may use to define the fields used in their JSON serialization.\n * @private\n * @internal\n */\nexport interface JsonSchema {\n [key: string]: Property<JsonTypeDesc>;\n}\n\n/**\n * Associates the JSON property type to the native type and sets them to be Required.\n * @private\n * @internal\n */\nexport type Json<T extends JsonSchema> = {\n [K in keyof T]: Required<T[K]>['value'];\n};\n\n/**\n * Helper function to define a JSON schema {@link Property}.\n * @private\n * @internal\n */\nexport function property<T extends JsonTypeDesc>(\n typeString: T,\n optionalValue?: TSType<T>\n): Property<T> {\n const result: Property<T> = {\n typeString\n };\n if (optionalValue) {\n result.value = optionalValue;\n }\n return result;\n}\n\n/**\n * Validates the JSON object based on the provided schema, and narrows the type to the provided\n * JSON schema.\n * @private\n * @internal\n *\n * @param json A JSON object to validate.\n * @param scheme a {@link JsonSchema} that defines the properties to validate.\n * @returns true if the JSON schema exists within the object. Throws a FirestoreError otherwise.\n */\nexport function validateJSON<S extends JsonSchema>(\n json: object,\n schema: S\n): json is Json<S> {\n if (!isPlainObject(json)) {\n throw new FirestoreError(Code.INVALID_ARGUMENT, 'JSON must be an object');\n }\n let error: string | undefined = undefined;\n for (const key in schema) {\n if (schema[key]) {\n const typeString = schema[key].typeString;\n const value: { value: unknown } | undefined =\n 'value' in schema[key] ? { value: schema[key].value } : undefined;\n if (!(key in json)) {\n error = `JSON missing required field: '${key}'`;\n break;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldValue = (json as any)[key];\n if (typeString && typeof fieldValue !== typeString) {\n error = `JSON field '${key}' must be a ${typeString}.`;\n break;\n } else if (value !== undefined && fieldValue !== value.value) {\n error = `Expected '${key}' field to equal '${value.value}'`;\n break;\n }\n }\n }\n if (error) {\n throw new FirestoreError(Code.INVALID_ARGUMENT, error);\n }\n return true;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Code, FirestoreError } from '../util/error';\n// API extractor fails importing 'property' unless we also explicitly import 'Property'.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports-ts\nimport { Property, property, validateJSON } from '../util/json_validation';\nimport { primitiveComparator } from '../util/misc';\n\n// The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).\nconst MIN_SECONDS = -62135596800;\n\n// Number of nanoseconds in a millisecond.\nconst MS_TO_NANOS = 1e6;\n\n/**\n * A `Timestamp` represents a point in time independent of any time zone or\n * calendar, represented as seconds and fractions of seconds at nanosecond\n * resolution in UTC Epoch time.\n *\n * It is encoded using the Proleptic Gregorian Calendar which extends the\n * Gregorian calendar backwards to year one. It is encoded assuming all minutes\n * are 60 seconds long, i.e. leap seconds are \"smeared\" so that no leap second\n * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to\n * 9999-12-31T23:59:59.999999999Z.\n *\n * For examples and further specifications, refer to the\n * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.\n */\nexport class Timestamp {\n /**\n * Creates a new timestamp with the current date, with millisecond precision.\n *\n * @returns a new timestamp representing the current date.\n */\n static now(): Timestamp {\n return Timestamp.fromMillis(Date.now());\n }\n\n /**\n * Creates a new timestamp from the given date.\n *\n * @param date - The date to initialize the `Timestamp` from.\n * @returns A new `Timestamp` representing the same point in time as the given\n * date.\n */\n static fromDate(date: Date): Timestamp {\n return Timestamp.fromMillis(date.getTime());\n }\n\n /**\n * Creates a new timestamp from the given number of milliseconds.\n *\n * @param milliseconds - Number of milliseconds since Unix epoch\n * 1970-01-01T00:00:00Z.\n * @returns A new `Timestamp` representing the same point in time as the given\n * number of milliseconds.\n */\n static fromMillis(milliseconds: number): Timestamp {\n const seconds = Math.floor(milliseconds / 1000);\n const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);\n return new Timestamp(seconds, nanos);\n }\n\n /**\n * Creates a new timestamp.\n *\n * @param seconds - The number of seconds of UTC time since Unix epoch\n * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n * 9999-12-31T23:59:59Z inclusive.\n * @param nanoseconds - The non-negative fractions of a second at nanosecond\n * resolution. Negative second values with fractions must still have\n * non-negative nanoseconds values that count forward in time. Must be\n * from 0 to 999,999,999 inclusive.\n */\n constructor(\n /**\n * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.\n */\n readonly seconds: number,\n /**\n * The fractions of a second at nanosecond resolution.*\n */\n readonly nanoseconds: number\n ) {\n if (nanoseconds < 0) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp nanoseconds out of range: ' + nanoseconds\n );\n }\n if (nanoseconds >= 1e9) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp nanoseconds out of range: ' + nanoseconds\n );\n }\n if (seconds < MIN_SECONDS) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp seconds out of range: ' + seconds\n );\n }\n // This will break in the year 10,000.\n if (seconds >= 253402300800) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Timestamp seconds out of range: ' + seconds\n );\n }\n }\n\n /**\n * Converts a `Timestamp` to a JavaScript `Date` object. This conversion\n * causes a loss of precision since `Date` objects only support millisecond\n * precision.\n *\n * @returns JavaScript `Date` object representing the same point in time as\n * this `Timestamp`, with millisecond precision.\n */\n toDate(): Date {\n return new Date(this.toMillis());\n }\n\n /**\n * Converts a `Timestamp` to a numeric timestamp (in milliseconds since\n * epoch). This operation causes a loss of precision.\n *\n * @returns The point in time corresponding to this timestamp, represented as\n * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.\n */\n toMillis(): number {\n return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;\n }\n\n _compareTo(other: Timestamp): number {\n if (this.seconds === other.seconds) {\n return primitiveComparator(this.nanoseconds, other.nanoseconds);\n }\n return primitiveComparator(this.seconds, other.seconds);\n }\n\n /**\n * Returns true if this `Timestamp` is equal to the provided one.\n *\n * @param other - The `Timestamp` to compare against.\n * @returns true if this `Timestamp` is equal to the provided one.\n */\n isEqual(other: Timestamp): boolean {\n return (\n other.seconds === this.seconds && other.nanoseconds === this.nanoseconds\n );\n }\n\n /** Returns a textual representation of this `Timestamp`. */\n toString(): string {\n return (\n 'Timestamp(seconds=' +\n this.seconds +\n ', nanoseconds=' +\n this.nanoseconds +\n ')'\n );\n }\n\n static _jsonSchemaVersion: string = 'firestore/timestamp/1.0';\n static _jsonSchema = {\n type: property('string', Timestamp._jsonSchemaVersion),\n seconds: property('number'),\n nanoseconds: property('number')\n };\n\n /**\n * Returns a JSON-serializable representation of this `Timestamp`.\n */\n toJSON(): { seconds: number; nanoseconds: number; type: string } {\n return {\n type: Timestamp._jsonSchemaVersion,\n seconds: this.seconds,\n nanoseconds: this.nanoseconds\n };\n }\n\n /**\n * Builds a `Timestamp` instance from a JSON object created by {@link Timestamp.toJSON}.\n */\n static fromJSON(json: object): Timestamp {\n if (validateJSON(json, Timestamp._jsonSchema)) {\n return new Timestamp(json.seconds, json.nanoseconds);\n }\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n 'Unexpected error creating Timestamp from JSON.'\n );\n }\n\n /**\n * Converts this object to a primitive string, which allows `Timestamp` objects\n * to be compared using the `>`, `<=`, `>=` and `>` operators.\n */\n valueOf(): string {\n // This method returns a string of the form <seconds>.<nanoseconds> where\n // <seconds> is translated to have a non-negative value and both <seconds>\n // and <nanoseconds> are left-padded with zeroes to be a consistent length.\n // Strings with this format then have a lexicographical ordering that matches\n // the expected ordering. The <seconds> translation is done to avoid having\n // a leading negative sign (i.e. a leading '-' character) in its string\n // representation, which would affect its lexicographical ordering.\n const adjustedSeconds = this.seconds - MIN_SECONDS;\n // Note: Up to 12 decimal digits are required to represent all valid\n // 'seconds' values.\n const formattedSeconds = String(adjustedSeconds).padStart(12, '0');\n const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');\n return formattedSeconds + '.' + formattedNanoseconds;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../lite-api/timestamp';\n\n/**\n * A version of a document in Firestore. This corresponds to the version\n * timestamp, such as update_time or read_time.\n */\nexport class SnapshotVersion {\n static fromTimestamp(value: Timestamp): SnapshotVersion {\n return new SnapshotVersion(value);\n }\n\n static min(): SnapshotVersion {\n return new SnapshotVersion(new Timestamp(0, 0));\n }\n\n static max(): SnapshotVersion {\n return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));\n }\n\n private constructor(private timestamp: Timestamp) {}\n\n compareTo(other: SnapshotVersion): number {\n return this.timestamp._compareTo(other.timestamp);\n }\n\n isEqual(other: SnapshotVersion): boolean {\n return this.timestamp.isEqual(other.timestamp);\n }\n\n /** Returns a number representation of the version for use in spec tests. */\n toMicroseconds(): number {\n // Convert to microseconds.\n return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;\n }\n\n toString(): string {\n return 'SnapshotVersion(' + this.timestamp.toString() + ')';\n }\n\n toTimestamp(): Timestamp {\n return this.timestamp;\n }\n}\n","/**\n * @license\n * Copyright 2021 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { Timestamp } from '../lite-api/timestamp';\nimport { primitiveComparator } from '../util/misc';\n\nimport { Document } from './document';\nimport { DocumentKey } from './document_key';\nimport { FieldPath } from './path';\n\n/**\n * The initial mutation batch id for each index. Gets updated during index\n * backfill.\n */\nexport const INITIAL_LARGEST_BATCH_ID = -1;\n\n/**\n * The initial sequence number for each index. Gets updated during index\n * backfill.\n */\nexport const INITIAL_SEQUENCE_NUMBER = 0;\n\n/**\n * An index definition for field indexes in Firestore.\n *\n * Every index is associated with a collection. The definition contains a list\n * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or\n * `CONTAINS` for ArrayContains/ArrayContainsAny queries).\n *\n * Unlike the backend, the SDK does not differentiate between collection or\n * collection group-scoped indices. Every index can be used for both single\n * collection and collection group queries.\n */\nexport class FieldIndex {\n /** An ID for an index that has not yet been added to persistence. */\n static UNKNOWN_ID = -1;\n\n constructor(\n /**\n * The index ID. Returns -1 if the index ID is not available (e.g. the index\n * has not yet been persisted).\n */\n readonly indexId: number,\n /** The collection ID this index applies to. */\n readonly collectionGroup: string,\n /** The field segments for this index. */\n readonly fields: IndexSegment[],\n /** Shows how up-to-date the index is for the current user. */\n readonly indexState: IndexState\n ) {}\n}\n\n/** Returns the ArrayContains/ArrayContainsAny segment for this index. */\nexport function fieldIndexGetArraySegment(\n fieldIndex: FieldIndex\n): IndexSegment | undefined {\n return fieldIndex.fields.find(s => s.kind === IndexKind.CONTAINS);\n}\n\n/** Returns all directional (ascending/descending) segments for this index. */\nexport function fieldIndexGetDirectionalSegments(\n fieldIndex: FieldIndex\n): IndexSegment[] {\n return fieldIndex.fields.filter(s => s.kind !== IndexKind.CONTAINS);\n}\n\n/**\n * Returns the order of the document key component for the given index.\n *\n * PORTING NOTE: This is only used in the Web IndexedDb implementation.\n */\nexport function fieldIndexGetKeyOrder(fieldIndex: FieldIndex): IndexKind {\n const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);\n return directionalSegments.length === 0\n ? IndexKind.ASCENDING\n : directionalSegments[directionalSegments.length - 1].kind;\n}\n\n/**\n * Compares indexes by collection group and segments. Ignores update time and\n * index ID.\n */\nexport function fieldIndexSemanticComparator(\n left: FieldIndex,\n right: FieldIndex\n): number {\n let cmp = primitiveComparator(left.collectionGroup, right.collectionGroup);\n if (cmp !== 0) {\n return cmp;\n }\n\n for (let i = 0; i < Math.min(left.fields.length, right.fields.length); ++i) {\n cmp = indexSegmentComparator(left.fields[i], right.fields[i]);\n if (cmp !== 0) {\n return cmp;\n }\n }\n return primitiveComparator(left.fields.length, right.fields.length);\n}\n\n/** Returns a debug representation of the field index */\nexport function fieldIndexToString(fieldIndex: FieldIndex): string {\n return `id=${fieldIndex.indexId}|cg=${\n fieldIndex.collectionGroup\n }|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;\n}\n\n/** The type of the index, e.g. for which type of query it can be used. */\nexport const enum IndexKind {\n // Note: The order of these values cannot be changed as the enum values are\n // stored in IndexedDb.\n /**\n * Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries.\n */\n ASCENDING,\n /**\n * Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries.\n */\n DESCENDING,\n /** Contains index. Can be used for ArrayContains and ArrayContainsAny. */\n CONTAINS\n}\n\n/** An index component consisting of field path and index type. */\nexport class IndexSegment {\n constructor(\n /** The field path of the component. */\n readonly fieldPath: FieldPath,\n /** The fields sorting order. */\n readonly kind: IndexKind\n ) {}\n}\n\nfunction indexSegmentComparator(\n left: IndexSegment,\n right: IndexSegment\n): number {\n const cmp = FieldPath.comparator(left.fieldPath, right.fieldPath);\n if (cmp !== 0) {\n return cmp;\n }\n return primitiveComparator(left.kind, right.kind);\n}\n\n/**\n * Stores the \"high water mark\" that indicates how updated the Index is for the\n * current user.\n */\nexport class IndexState {\n constructor(\n /**\n * Indicates when the index was last updated (relative to other indexes).\n */\n readonly sequenceNumber: number,\n /** The the latest indexed read time, document and batch id. */\n readonly offset: IndexOffset\n ) {}\n\n /** The state of an index that has not yet been backfilled. */\n static empty(): IndexState {\n return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());\n }\n}\n\n/**\n * Creates an offset that matches all documents with a read time higher than\n * `readTime`.\n */\nexport function newIndexOffsetSuccessorFromReadTime(\n readTime: SnapshotVersion,\n largestBatchId: number\n): IndexOffset {\n // We want to create an offset that matches all documents with a read time\n // greater than the provided read time. To do so, we technically need to\n // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use\n // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use\n // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches\n // all valid document IDs.\n const successorSeconds = readTime.toTimestamp().seconds;\n const successorNanos = readTime.toTimestamp().nanoseconds + 1;\n const successor = SnapshotVersion.fromTimestamp(\n successorNanos === 1e9\n ? new Timestamp(successorSeconds + 1, 0)\n : new Timestamp(successorSeconds, successorNanos)\n );\n return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);\n}\n\n/** Creates a new offset based on the provided document. */\nexport function newIndexOffsetFromDocument(document: Document): IndexOffset {\n return new IndexOffset(\n document.readTime,\n document.key,\n INITIAL_LARGEST_BATCH_ID\n );\n}\n\n/**\n * Stores the latest read time, document and batch ID that were processed for an\n * index.\n */\nexport class IndexOffset {\n constructor(\n /**\n * The latest read time version that has been indexed by Firestore for this\n * field index.\n */\n readonly readTime: SnapshotVersion,\n\n /**\n * The key of the last document that was indexed for this query. Use\n * `DocumentKey.empty()` if no document has been indexed.\n */\n readonly documentKey: DocumentKey,\n\n /*\n * The largest mutation batch id that's been processed by Firestore.\n */\n readonly largestBatchId: number\n ) {}\n\n /** Returns an offset that sorts before all regular offsets. */\n static min(): IndexOffset {\n return new IndexOffset(\n SnapshotVersion.min(),\n DocumentKey.empty(),\n INITIAL_LARGEST_BATCH_ID\n );\n }\n\n /** Returns an offset that sorts after all regular offsets. */\n static max(): IndexOffset {\n return new IndexOffset(\n SnapshotVersion.max(),\n DocumentKey.empty(),\n INITIAL_LARGEST_BATCH_ID\n );\n }\n}\n\nexport function indexOffsetComparator(\n left: IndexOffset,\n right: IndexOffset\n): number {\n let cmp = left.readTime.compareTo(right.readTime);\n if (cmp !== 0) {\n return cmp;\n }\n cmp = DocumentKey.comparator(left.documentKey, right.documentKey);\n if (cmp !== 0) {\n return cmp;\n }\n return primitiveComparator(left.largestBatchId, right.largestBatchId);\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ListenSequenceNumber } from '../core/types';\nexport const PRIMARY_LEASE_LOST_ERROR_MSG =\n 'The current tab is not in the required state to perform this operation. ' +\n 'It might be necessary to refresh the browser tab.';\n\n/** The different modes supported by `Persistence.runTransaction()`. */\nexport type PersistenceTransactionMode =\n | 'readonly'\n | 'readwrite'\n | 'readwrite-primary';\n\n/**\n * A base class representing a persistence transaction, encapsulating both the\n * transaction's sequence numbers as well as a list of onCommitted listeners.\n *\n * When you call Persistence.runTransaction(), it will create a transaction and\n * pass it to your callback. You then pass it to any method that operates\n * on persistence.\n */\nexport abstract class PersistenceTransaction {\n private readonly onCommittedListeners: Array<() => void> = [];\n\n abstract readonly currentSequenceNumber: ListenSequenceNumber;\n\n addOnCommittedListener(listener: () => void): void {\n this.onCommittedListeners.push(listener);\n }\n\n raiseOnCommittedEvent(): void {\n this.onCommittedListeners.forEach(listener => listener());\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug } from '../util/log';\n\nimport { IndexManager } from './index_manager';\nimport { LocalDocumentsView } from './local_documents_view';\nimport { LruGarbageCollector, LruResults } from './lru_garbage_collector';\nimport { PRIMARY_LEASE_LOST_ERROR_MSG } from './persistence_transaction';\n\nexport interface LocalStore {\n collectGarbage(garbageCollector: LruGarbageCollector): Promise<LruResults>;\n\n /** Manages the list of active field and collection indices. */\n indexManager: IndexManager;\n\n /**\n * The \"local\" view of all documents (layering mutationQueue on top of\n * remoteDocumentCache).\n */\n localDocuments: LocalDocumentsView;\n}\n\n/**\n * Verifies the error thrown by a LocalStore operation. If a LocalStore\n * operation fails because the primary lease has been taken by another client,\n * we ignore the error (the persistence layer will immediately call\n * `applyPrimaryLease` to propagate the primary state change). All other errors\n * are re-thrown.\n *\n * @param err - An error returned by a LocalStore operation.\n * @returns A Promise that resolves after we recovered, or the original error.\n */\nexport async function ignoreIfPrimaryLeaseLoss(\n err: FirestoreError\n): Promise<void> {\n if (\n err.code === Code.FAILED_PRECONDITION &&\n err.message === PRIMARY_LEASE_LOST_ERROR_MSG\n ) {\n logDebug('LocalStore', 'Unexpectedly lost primary lease');\n } else {\n throw err;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { fail } from '../util/assert';\n\nexport type FulfilledHandler<T, R> =\n | ((result: T) => R | PersistencePromise<R>)\n | null;\nexport type RejectedHandler<R> =\n | ((reason: Error) => R | PersistencePromise<R>)\n | null;\nexport type Resolver<T> = (value?: T) => void;\nexport type Rejector = (error: Error) => void;\n\n/**\n * PersistencePromise is essentially a re-implementation of Promise except\n * it has a .next() method instead of .then() and .next() and .catch() callbacks\n * are executed synchronously when a PersistencePromise resolves rather than\n * asynchronously (Promise implementations use setImmediate() or similar).\n *\n * This is necessary to interoperate with IndexedDB which will automatically\n * commit transactions if control is returned to the event loop without\n * synchronously initiating another operation on the transaction.\n *\n * NOTE: .then() and .catch() only allow a single consumer, unlike normal\n * Promises.\n */\nexport class PersistencePromise<T> {\n // NOTE: next/catchCallback will always point to our own wrapper functions,\n // not the user's raw next() or catch() callbacks.\n private nextCallback: FulfilledHandler<T, unknown> = null;\n private catchCallback: RejectedHandler<unknown> = null;\n\n // When the operation resolves, we'll set result or error and mark isDone.\n private result: T | undefined = undefined;\n private error: Error | undefined = undefined;\n private isDone = false;\n\n // Set to true when .then() or .catch() are called and prevents additional\n // chaining.\n private callbackAttached = false;\n\n constructor(callback: (resolve: Resolver<T>, reject: Rejector) => void) {\n callback(\n value => {\n this.isDone = true;\n this.result = value;\n if (this.nextCallback) {\n // value should be defined unless T is Void, but we can't express\n // that in the type system.\n this.nextCallback(value!);\n }\n },\n error => {\n this.isDone = true;\n this.error = error;\n if (this.catchCallback) {\n this.catchCallback(error);\n }\n }\n );\n }\n\n catch<R>(\n fn: (error: Error) => R | PersistencePromise<R>\n ): PersistencePromise<R> {\n return this.next(undefined, fn);\n }\n\n next<R>(\n nextFn?: FulfilledHandler<T, R>,\n catchFn?: RejectedHandler<R>\n ): PersistencePromise<R> {\n if (this.callbackAttached) {\n fail(0xe830, 'Called next() or catch() twice for PersistencePromise');\n }\n this.callbackAttached = true;\n if (this.isDone) {\n if (!this.error) {\n return this.wrapSuccess(nextFn, this.result!);\n } else {\n return this.wrapFailure(catchFn, this.error);\n }\n } else {\n return new PersistencePromise<R>((resolve, reject) => {\n this.nextCallback = (value: T) => {\n this.wrapSuccess(nextFn, value).next(resolve, reject);\n };\n this.catchCallback = (error: Error) => {\n this.wrapFailure(catchFn, error).next(resolve, reject);\n };\n });\n }\n }\n\n toPromise(): Promise<T> {\n return new Promise((resolve, reject) => {\n this.next(resolve, reject);\n });\n }\n\n private wrapUserFunction<R>(\n fn: () => R | PersistencePromise<R>\n ): PersistencePromise<R> {\n try {\n const result = fn();\n if (result instanceof PersistencePromise) {\n return result;\n } else {\n return PersistencePromise.resolve(result);\n }\n } catch (e) {\n return PersistencePromise.reject<R>(e as Error);\n }\n }\n\n private wrapSuccess<R>(\n nextFn: FulfilledHandler<T, R> | undefined,\n value: T\n ): PersistencePromise<R> {\n if (nextFn) {\n return this.wrapUserFunction(() => nextFn(value));\n } else {\n // If there's no nextFn, then R must be the same as T\n return PersistencePromise.resolve<R>(value as unknown as R);\n }\n }\n\n private wrapFailure<R>(\n catchFn: RejectedHandler<R> | undefined,\n error: Error\n ): PersistencePromise<R> {\n if (catchFn) {\n return this.wrapUserFunction(() => catchFn(error));\n } else {\n return PersistencePromise.reject<R>(error);\n }\n }\n\n static resolve(): PersistencePromise<void>;\n static resolve<R>(result: R): PersistencePromise<R>;\n static resolve<R>(result?: R): PersistencePromise<R | void> {\n return new PersistencePromise<R | void>((resolve, reject) => {\n resolve(result);\n });\n }\n\n static reject<R>(error: Error): PersistencePromise<R> {\n return new PersistencePromise<R>((resolve, reject) => {\n reject(error);\n });\n }\n\n static waitFor(\n // Accept all Promise types in waitFor().\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n all: { forEach: (cb: (el: PersistencePromise<any>) => void) => void }\n ): PersistencePromise<void> {\n return new PersistencePromise<void>((resolve, reject) => {\n let expectedCount = 0;\n let resolvedCount = 0;\n let done = false;\n\n all.forEach(element => {\n ++expectedCount;\n element.next(\n () => {\n ++resolvedCount;\n if (done && resolvedCount === expectedCount) {\n resolve();\n }\n },\n err => reject(err)\n );\n });\n\n done = true;\n if (resolvedCount === expectedCount) {\n resolve();\n }\n });\n }\n\n /**\n * Given an array of predicate functions that asynchronously evaluate to a\n * boolean, implements a short-circuiting `or` between the results. Predicates\n * will be evaluated until one of them returns `true`, then stop. The final\n * result will be whether any of them returned `true`.\n */\n static or(\n predicates: Array<() => PersistencePromise<boolean>>\n ): PersistencePromise<boolean> {\n let p: PersistencePromise<boolean> =\n PersistencePromise.resolve<boolean>(false);\n for (const predicate of predicates) {\n p = p.next(isTrue => {\n if (isTrue) {\n return PersistencePromise.resolve<boolean>(isTrue);\n } else {\n return predicate();\n }\n });\n }\n return p;\n }\n\n /**\n * Given an iterable, call the given function on each element in the\n * collection and wait for all of the resulting concurrent PersistencePromises\n * to resolve.\n */\n static forEach<R, S>(\n collection: { forEach: (cb: (r: R, s: S) => void) => void },\n f:\n | ((r: R, s: S) => PersistencePromise<void>)\n | ((r: R) => PersistencePromise<void>)\n ): PersistencePromise<void>;\n static forEach<R>(\n collection: { forEach: (cb: (r: R) => void) => void },\n f: (r: R) => PersistencePromise<void>\n ): PersistencePromise<void>;\n static forEach<R, S>(\n collection: { forEach: (cb: (r: R, s?: S) => void) => void },\n f: (r: R, s?: S) => PersistencePromise<void>\n ): PersistencePromise<void> {\n const promises: Array<PersistencePromise<void>> = [];\n collection.forEach((r, s) => {\n promises.push(f.call(this, r, s));\n });\n return this.waitFor(promises);\n }\n\n /**\n * Concurrently map all array elements through asynchronous function.\n */\n static mapArray<T, U>(\n array: T[],\n f: (t: T) => PersistencePromise<U>\n ): PersistencePromise<U[]> {\n return new PersistencePromise<U[]>((resolve, reject) => {\n const expectedCount = array.length;\n const results: U[] = new Array(expectedCount);\n let resolvedCount = 0;\n for (let i = 0; i < expectedCount; i++) {\n const current = i;\n f(array[current]).next(\n result => {\n results[current] = result;\n ++resolvedCount;\n if (resolvedCount === expectedCount) {\n resolve(results);\n }\n },\n err => reject(err)\n );\n }\n });\n }\n\n /**\n * An alternative to recursive PersistencePromise calls, that avoids\n * potential memory problems from unbounded chains of promises.\n *\n * The `action` will be called repeatedly while `condition` is true.\n */\n static doWhile(\n condition: () => boolean,\n action: () => PersistencePromise<void>\n ): PersistencePromise<void> {\n return new PersistencePromise<void>((resolve, reject) => {\n const process = (): void => {\n if (condition() === true) {\n action().next(() => {\n process();\n }, reject);\n } else {\n resolve();\n }\n };\n process();\n });\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getGlobal, getUA, isIndexedDBAvailable } from '@firebase/util';\n\nimport { debugAssert } from '../util/assert';\nimport { Code, FirestoreError } from '../util/error';\nimport { logDebug, logError, logWarn } from '../util/log';\nimport { Deferred } from '../util/promise';\n\nimport { DatabaseDeletedListener } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\n\n// References to `indexedDB` are guarded by SimpleDb.isAvailable() and getGlobal()\n/* eslint-disable no-restricted-globals */\n\nconst LOG_TAG = 'SimpleDb';\n\n/**\n * The maximum number of retry attempts for an IndexedDb transaction that fails\n * with a DOMException.\n */\nconst TRANSACTION_RETRY_COUNT = 3;\n\n// The different modes supported by `SimpleDb.runTransaction()`\ntype SimpleDbTransactionMode = 'readonly' | 'readwrite';\n\nexport interface SimpleDbSchemaConverter {\n createOrUpgrade(\n db: IDBDatabase,\n txn: IDBTransaction,\n fromVersion: number,\n toVersion: number\n ): PersistencePromise<void>;\n}\n\n/**\n * Wraps an IDBTransaction and exposes a store() method to get a handle to a\n * specific object store.\n */\nexport class SimpleDbTransaction {\n private aborted = false;\n\n /**\n * A `Promise` that resolves with the result of the IndexedDb transaction.\n */\n private readonly completionDeferred = new Deferred<void>();\n\n static open(\n db: IDBDatabase,\n action: string,\n mode: IDBTransactionMode,\n objectStoreNames: string[]\n ): SimpleDbTransaction {\n try {\n return new SimpleDbTransaction(\n action,\n db.transaction(objectStoreNames, mode)\n );\n } catch (e) {\n throw new IndexedDbTransactionError(action, e as Error);\n }\n }\n\n constructor(\n private readonly action: string,\n private readonly transaction: IDBTransaction\n ) {\n this.transaction.oncomplete = () => {\n this.completionDeferred.resolve();\n };\n this.transaction.onabort = () => {\n if (transaction.error) {\n this.completionDeferred.reject(\n new IndexedDbTransactionError(action, transaction.error)\n );\n } else {\n this.completionDeferred.resolve();\n }\n };\n this.transaction.onerror = (event: Event) => {\n const error = checkForAndReportiOSError(\n (event.target as IDBRequest).error!\n );\n this.completionDeferred.reject(\n new IndexedDbTransactionError(action, error)\n );\n };\n }\n\n get completionPromise(): Promise<void> {\n return this.completionDeferred.promise;\n }\n\n abort(error?: Error): void {\n if (error) {\n this.completionDeferred.reject(error);\n }\n\n if (!this.aborted) {\n logDebug(\n LOG_TAG,\n 'Aborting transaction:',\n error ? error.message : 'Client-initiated abort'\n );\n this.aborted = true;\n this.transaction.abort();\n }\n }\n\n maybeCommit(): void {\n // If the browser supports V3 IndexedDB, we invoke commit() explicitly to\n // speed up index DB processing if the event loop remains blocks.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const maybeV3IndexedDb = this.transaction as any;\n if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {\n maybeV3IndexedDb.commit();\n }\n }\n\n /**\n * Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All\n * operations performed on the SimpleDbStore happen within the context of this\n * transaction and it cannot be used anymore once the transaction is\n * completed.\n *\n * Note that we can't actually enforce that the KeyType and ValueType are\n * correct, but they allow type safety through the rest of the consuming code.\n */\n store<KeyType extends IDBValidKey, ValueType extends unknown>(\n storeName: string\n ): SimpleDbStore<KeyType, ValueType> {\n const store = this.transaction.objectStore(storeName);\n debugAssert(!!store, 'Object store not part of transaction: ' + storeName);\n return new SimpleDbStore<KeyType, ValueType>(store);\n }\n}\n\n/**\n * Provides a wrapper around IndexedDb with a simplified interface that uses\n * Promise-like return values to chain operations. Real promises cannot be used\n * since .then() continuations are executed asynchronously (e.g. via\n * .setImmediate), which would cause IndexedDB to end the transaction.\n * See PersistencePromise for more details.\n */\nexport class SimpleDb {\n private db?: IDBDatabase;\n private databaseDeletedListener?: DatabaseDeletedListener;\n private lastClosedDbVersion: number | null = null;\n\n /** Deletes the specified database. */\n static delete(name: string): Promise<void> {\n logDebug(LOG_TAG, 'Removing database:', name);\n const globals = getGlobal();\n return wrapRequest<void>(\n globals.indexedDB.deleteDatabase(name)\n ).toPromise();\n }\n\n /** Returns true if IndexedDB is available in the current environment. */\n static isAvailable(): boolean {\n if (!isIndexedDBAvailable()) {\n return false;\n }\n\n if (SimpleDb.isMockPersistence()) {\n return true;\n }\n\n // We extensively use indexed array values and compound keys,\n // which IE and Edge do not support. However, they still have indexedDB\n // defined on the window, so we need to check for them here and make sure\n // to return that persistence is not enabled for those browsers.\n // For tracking support of this feature, see here:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/indexeddbarraysandmultientrysupport/\n\n // Check the UA string to find out the browser.\n const ua = getUA();\n\n // IE 10\n // ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';\n\n // IE 11\n // ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';\n\n // Edge\n // ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,\n // like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';\n\n // iOS Safari: Disable for users running iOS version < 10.\n const iOSVersion = SimpleDb.getIOSVersion(ua);\n const isUnsupportedIOS = 0 < iOSVersion && iOSVersion < 10;\n\n // Android browser: Disable for users running version < 4.5.\n const androidVersion = getAndroidVersion(ua);\n const isUnsupportedAndroid = 0 < androidVersion && androidVersion < 4.5;\n\n if (\n ua.indexOf('MSIE ') > 0 ||\n ua.indexOf('Trident/') > 0 ||\n ua.indexOf('Edge/') > 0 ||\n isUnsupportedIOS ||\n isUnsupportedAndroid\n ) {\n return false;\n } else {\n return true;\n }\n }\n\n /**\n * Returns true if the backing IndexedDB store is the Node IndexedDBShim\n * (see https://github.com/axemclion/IndexedDBShim).\n */\n static isMockPersistence(): boolean {\n return (\n typeof process !== 'undefined' &&\n process.env?.USE_MOCK_PERSISTENCE === 'YES'\n );\n }\n\n /** Helper to get a typed SimpleDbStore from a transaction. */\n static getStore<KeyType extends IDBValidKey, ValueType extends unknown>(\n txn: SimpleDbTransaction,\n store: string\n ): SimpleDbStore<KeyType, ValueType> {\n return txn.store<KeyType, ValueType>(store);\n }\n\n // visible for testing\n /** Parse User Agent to determine iOS version. Returns -1 if not found. */\n static getIOSVersion(ua: string): number {\n const iOSVersionRegex = ua.match(/i(?:phone|pad|pod) os ([\\d_]+)/i);\n const version = iOSVersionRegex\n ? iOSVersionRegex[1].split('_').slice(0, 2).join('.')\n : '-1';\n return Number(version);\n }\n\n /*\n * Creates a new SimpleDb wrapper for IndexedDb database `name`.\n *\n * Note that `version` must not be a downgrade. IndexedDB does not support\n * downgrading the schema version. We currently do not support any way to do\n * versioning outside of IndexedDB's versioning mechanism, as only\n * version-upgrade transactions are allowed to do things like create\n * objectstores.\n */\n constructor(\n private readonly name: string,\n private readonly version: number,\n private readonly schemaConverter: SimpleDbSchemaConverter\n ) {\n debugAssert(\n SimpleDb.isAvailable(),\n 'IndexedDB not supported in current environment.'\n );\n\n const iOSVersion = SimpleDb.getIOSVersion(getUA());\n // NOTE: According to https://bugs.webkit.org/show_bug.cgi?id=197050, the\n // bug we're checking for should exist in iOS >= 12.2 and < 13, but for\n // whatever reason it's much harder to hit after 12.2 so we only proactively\n // log on 12.2.\n if (iOSVersion === 12.2) {\n logError(\n 'Firestore persistence suffers from a bug in iOS 12.2 ' +\n 'Safari that may cause your app to stop working. See ' +\n 'https://stackoverflow.com/q/56496296/110915 for details ' +\n 'and a potential workaround.'\n );\n }\n }\n\n /**\n * Opens the specified database, creating or upgrading it if necessary.\n */\n async ensureDb(action: string): Promise<IDBDatabase> {\n if (!this.db) {\n logDebug(LOG_TAG, 'Opening database:', this.name);\n this.db = await new Promise<IDBDatabase>((resolve, reject) => {\n // TODO(mikelehen): Investigate browser compatibility.\n // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB\n // suggests IE9 and older WebKit browsers handle upgrade\n // differently. They expect setVersion, as described here:\n // https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeRequest/setVersion\n const request = indexedDB.open(this.name, this.version);\n\n request.onsuccess = (event: Event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n resolve(db);\n };\n\n request.onblocked = () => {\n reject(\n new IndexedDbTransactionError(\n action,\n 'Cannot upgrade IndexedDB schema while another tab is open. ' +\n 'Close all tabs that access Firestore and reload this page to proceed.'\n )\n );\n };\n\n request.onerror = (event: Event) => {\n const error: DOMException = (event.target as IDBOpenDBRequest).error!;\n if (error.name === 'VersionError') {\n reject(\n new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'A newer version of the Firestore SDK was previously used and so the persisted ' +\n 'data is not compatible with the version of the SDK you are now using. The SDK ' +\n 'will operate with persistence disabled. If you need persistence, please ' +\n 're-upgrade to a newer version of the SDK or else clear the persisted IndexedDB ' +\n 'data for your app to start fresh.'\n )\n );\n } else if (error.name === 'InvalidStateError') {\n reject(\n new FirestoreError(\n Code.FAILED_PRECONDITION,\n 'Unable to open an IndexedDB connection. This could be due to running in a ' +\n 'private browsing session on a browser whose private browsing sessions do not ' +\n 'support IndexedDB: ' +\n error\n )\n );\n } else {\n reject(new IndexedDbTransactionError(action, error));\n }\n };\n\n request.onupgradeneeded = (event: IDBVersionChangeEvent) => {\n logDebug(\n LOG_TAG,\n 'Database \"' + this.name + '\" requires upgrade from version:',\n event.oldVersion\n );\n const db = (event.target as IDBOpenDBRequest).result;\n if (\n this.lastClosedDbVersion !== null &&\n this.lastClosedDbVersion !== event.oldVersion\n ) {\n // This thrown error will get passed to the `onerror` callback\n // registered above, and will then be propagated correctly.\n throw new Error(\n `refusing to open IndexedDB database due to potential ` +\n `corruption of the IndexedDB database data; this corruption ` +\n `could be caused by clicking the \"clear site data\" button in ` +\n `a web browser; try reloading the web page to re-initialize ` +\n `the IndexedDB database: ` +\n `lastClosedDbVersion=${this.lastClosedDbVersion}, ` +\n `event.oldVersion=${event.oldVersion}, ` +\n `event.newVersion=${event.newVersion}, ` +\n `db.version=${db.version}`\n );\n }\n this.schemaConverter\n .createOrUpgrade(\n db,\n request.transaction!,\n event.oldVersion,\n this.version\n )\n .next(() => {\n logDebug(\n LOG_TAG,\n 'Database upgrade to version ' + this.version + ' complete'\n );\n });\n };\n });\n\n this.db.addEventListener(\n 'close',\n event => {\n const db = event.target as IDBDatabase;\n this.lastClosedDbVersion = db.version;\n },\n { passive: true }\n );\n }\n\n this.db.addEventListener(\n 'versionchange',\n event => {\n // Notify the listener if another tab attempted to delete the IndexedDb\n // database, such as by calling clearIndexedDbPersistence().\n if (event.newVersion === null) {\n logWarn(\n `Received \"versionchange\" event with newVersion===null; ` +\n 'notifying the registered DatabaseDeletedListener, if any'\n );\n this.databaseDeletedListener?.();\n }\n },\n { passive: true }\n );\n\n return this.db;\n }\n\n setDatabaseDeletedListener(\n databaseDeletedListener: DatabaseDeletedListener\n ): void {\n if (this.databaseDeletedListener) {\n throw new Error(\n 'setDatabaseDeletedListener() may only be called once, ' +\n 'and it has already been called'\n );\n }\n this.databaseDeletedListener = databaseDeletedListener;\n }\n\n async runTransaction<T>(\n action: string,\n mode: SimpleDbTransactionMode,\n objectStores: string[],\n transactionFn: (transaction: SimpleDbTransaction) => PersistencePromise<T>\n ): Promise<T> {\n const readonly = mode === 'readonly';\n let attemptNumber = 0;\n\n while (true) {\n ++attemptNumber;\n\n try {\n this.db = await this.ensureDb(action);\n\n const transaction = SimpleDbTransaction.open(\n this.db,\n action,\n readonly ? 'readonly' : 'readwrite',\n objectStores\n );\n const transactionFnResult = transactionFn(transaction)\n .next(result => {\n transaction.maybeCommit();\n return result;\n })\n .catch(error => {\n // Abort the transaction if there was an error.\n transaction.abort(error);\n // We cannot actually recover, and calling `abort()` will cause the transaction's\n // completion promise to be rejected. This in turn means that we won't use\n // `transactionFnResult` below. We return a rejection here so that we don't add the\n // possibility of returning `void` to the type of `transactionFnResult`.\n return PersistencePromise.reject<T>(error);\n })\n .toPromise();\n\n // As noted above, errors are propagated by aborting the transaction. So\n // we swallow any error here to avoid the browser logging it as unhandled.\n transactionFnResult.catch(() => {});\n\n // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to\n // fire), but still return the original transactionFnResult back to the\n // caller.\n await transaction.completionPromise;\n return transactionFnResult;\n } catch (e) {\n const error = e as Error;\n // TODO(schmidt-sebastian): We could probably be smarter about this and\n // not retry exceptions that are likely unrecoverable (such as quota\n // exceeded errors).\n\n // Note: We cannot use an instanceof check for FirestoreException, since the\n // exception is wrapped in a generic error by our async/await handling.\n const retryable =\n error.name !== 'FirebaseError' &&\n attemptNumber < TRANSACTION_RETRY_COUNT;\n logDebug(\n LOG_TAG,\n 'Transaction failed with error:',\n error.message,\n 'Retrying:',\n retryable\n );\n\n this.close();\n\n if (!retryable) {\n return Promise.reject(error);\n }\n }\n }\n }\n\n close(): void {\n if (this.db) {\n this.db.close();\n }\n this.db = undefined;\n }\n}\n\n/** Parse User Agent to determine Android version. Returns -1 if not found. */\nexport function getAndroidVersion(ua: string): number {\n const androidVersionRegex = ua.match(/Android ([\\d.]+)/i);\n const version = androidVersionRegex\n ? androidVersionRegex[1].split('.').slice(0, 2).join('.')\n : '-1';\n return Number(version);\n}\n\n/**\n * A controller for iterating over a key range or index. It allows an iterate\n * callback to delete the currently-referenced object, or jump to a new key\n * within the key range or index.\n */\nexport class IterationController {\n private shouldStop = false;\n private nextKey: IDBValidKey | null = null;\n\n constructor(private dbCursor: IDBCursorWithValue) {}\n\n get isDone(): boolean {\n return this.shouldStop;\n }\n\n get skipToKey(): IDBValidKey | null {\n return this.nextKey;\n }\n\n set cursor(value: IDBCursorWithValue) {\n this.dbCursor = value;\n }\n\n /**\n * This function can be called to stop iteration at any point.\n */\n done(): void {\n this.shouldStop = true;\n }\n\n /**\n * This function can be called to skip to that next key, which could be\n * an index or a primary key.\n */\n skip(key: IDBValidKey): void {\n this.nextKey = key;\n }\n\n /**\n * Delete the current cursor value from the object store.\n *\n * NOTE: You CANNOT do this with a keysOnly query.\n */\n delete(): PersistencePromise<void> {\n return wrapRequest<void>(this.dbCursor.delete());\n }\n}\n\n/**\n * Callback used with iterate() method.\n */\nexport type IterateCallback<KeyType, ValueType> = (\n key: KeyType,\n value: ValueType,\n control: IterationController\n) => void | PersistencePromise<void>;\n\n/** Options available to the iterate() method. */\nexport interface IterateOptions {\n /** Index to iterate over (else primary keys will be iterated) */\n index?: string;\n\n /** IndexedDB Range to iterate over (else entire store will be iterated) */\n range?: IDBKeyRange;\n\n /** If true, values aren't read while iterating. */\n keysOnly?: boolean;\n\n /** If true, iterate over the store in reverse. */\n reverse?: boolean;\n}\n\n/** An error that wraps exceptions that thrown during IndexedDB execution. */\nexport class IndexedDbTransactionError extends FirestoreError {\n name = 'IndexedDbTransactionError';\n\n constructor(actionName: string, cause: Error | string) {\n super(\n Code.UNAVAILABLE,\n `IndexedDB transaction '${actionName}' failed: ${cause}`\n );\n }\n}\n\n/** Verifies whether `e` is an IndexedDbTransactionError. */\nexport function isIndexedDbTransactionError(e: Error): boolean {\n // Use name equality, as instanceof checks on errors don't work with errors\n // that wrap other errors.\n return e.name === 'IndexedDbTransactionError';\n}\n\n/**\n * A wrapper around an IDBObjectStore providing an API that:\n *\n * 1) Has generic KeyType / ValueType parameters to provide strongly-typed\n * methods for acting against the object store.\n * 2) Deals with IndexedDB's onsuccess / onerror event callbacks, making every\n * method return a PersistencePromise instead.\n * 3) Provides a higher-level API to avoid needing to do excessive wrapping of\n * intermediate IndexedDB types (IDBCursorWithValue, etc.)\n */\nexport class SimpleDbStore<\n KeyType extends IDBValidKey,\n ValueType extends unknown\n> {\n constructor(private store: IDBObjectStore) {}\n\n /**\n * Writes a value into the Object Store.\n *\n * @param key - Optional explicit key to use when writing the object, else the\n * key will be auto-assigned (e.g. via the defined keyPath for the store).\n * @param value - The object to write.\n */\n put(value: ValueType): PersistencePromise<void>;\n put(key: KeyType, value: ValueType): PersistencePromise<void>;\n put(\n keyOrValue: KeyType | ValueType,\n value?: ValueType\n ): PersistencePromise<void> {\n let request;\n if (value !== undefined) {\n logDebug(LOG_TAG, 'PUT', this.store.name, keyOrValue, value);\n request = this.store.put(value, keyOrValue as KeyType);\n } else {\n logDebug(LOG_TAG, 'PUT', this.store.name, '<auto-key>', keyOrValue);\n request = this.store.put(keyOrValue as ValueType);\n }\n return wrapRequest<void>(request);\n }\n\n /**\n * Adds a new value into an Object Store and returns the new key. Similar to\n * IndexedDb's `add()`, this method will fail on primary key collisions.\n *\n * @param value - The object to write.\n * @returns The key of the value to add.\n */\n add(value: ValueType): PersistencePromise<KeyType> {\n logDebug(LOG_TAG, 'ADD', this.store.name, value, value);\n const request = this.store.add(value as ValueType);\n return wrapRequest<KeyType>(request);\n }\n\n /**\n * Gets the object with the specified key from the specified store, or null\n * if no object exists with the specified key.\n *\n * @key The key of the object to get.\n * @returns The object with the specified key or null if no object exists.\n */\n get(key: KeyType): PersistencePromise<ValueType | null> {\n const request = this.store.get(key);\n // We're doing an unsafe cast to ValueType.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return wrapRequest<any>(request).next(result => {\n // Normalize nonexistence to null.\n if (result === undefined) {\n result = null;\n }\n logDebug(LOG_TAG, 'GET', this.store.name, key, result);\n return result;\n });\n }\n\n delete(key: KeyType | IDBKeyRange): PersistencePromise<void> {\n logDebug(LOG_TAG, 'DELETE', this.store.name, key);\n const request = this.store.delete(key);\n return wrapRequest<void>(request);\n }\n\n /**\n * If we ever need more of the count variants, we can add overloads. For now,\n * all we need is to count everything in a store.\n *\n * Returns the number of rows in the store.\n */\n count(): PersistencePromise<number> {\n logDebug(LOG_TAG, 'COUNT', this.store.name);\n const request = this.store.count();\n return wrapRequest<number>(request);\n }\n\n /** Loads all elements from the object store. */\n loadAll(): PersistencePromise<ValueType[]>;\n /** Loads all elements for the index range from the object store. */\n loadAll(range: IDBKeyRange): PersistencePromise<ValueType[]>;\n /** Loads all elements ordered by the given index. */\n loadAll(index: string): PersistencePromise<ValueType[]>;\n /**\n * Loads all elements from the object store that fall into the provided in the\n * index range for the given index.\n */\n loadAll(index: string, range: IDBKeyRange): PersistencePromise<ValueType[]>;\n loadAll(\n indexOrRange?: string | IDBKeyRange,\n range?: IDBKeyRange\n ): PersistencePromise<ValueType[]> {\n const iterateOptions = this.options(indexOrRange, range);\n // Use `getAll()` if the browser supports IndexedDB v3, as it is roughly\n // 20% faster.\n const store = iterateOptions.index\n ? this.store.index(iterateOptions.index)\n : this.store;\n if (typeof store.getAll === 'function') {\n const request = store.getAll(iterateOptions.range);\n return new PersistencePromise((resolve, reject) => {\n request.onerror = (event: Event) => {\n reject((event.target as IDBRequest).error!);\n };\n request.onsuccess = (event: Event) => {\n resolve((event.target as IDBRequest).result);\n };\n });\n } else {\n const cursor = this.cursor(iterateOptions);\n const results: ValueType[] = [];\n return this.iterateCursor(cursor, (key, value) => {\n results.push(value);\n }).next(() => {\n return results;\n });\n }\n }\n\n /**\n * Loads the first `count` elements from the provided index range. Loads all\n * elements if no limit is provided.\n */\n loadFirst(\n range: IDBKeyRange,\n count: number | null\n ): PersistencePromise<ValueType[]> {\n const request = this.store.getAll(\n range,\n count === null ? undefined : count\n );\n return new PersistencePromise((resolve, reject) => {\n request.onerror = (event: Event) => {\n reject((event.target as IDBRequest).error!);\n };\n request.onsuccess = (event: Event) => {\n resolve((event.target as IDBRequest).result);\n };\n });\n }\n\n deleteAll(): PersistencePromise<void>;\n deleteAll(range: IDBKeyRange): PersistencePromise<void>;\n deleteAll(index: string, range: IDBKeyRange): PersistencePromise<void>;\n deleteAll(\n indexOrRange?: string | IDBKeyRange,\n range?: IDBKeyRange\n ): PersistencePromise<void> {\n logDebug(LOG_TAG, 'DELETE ALL', this.store.name);\n const options = this.options(indexOrRange, range);\n options.keysOnly = false;\n const cursor = this.cursor(options);\n return this.iterateCursor(cursor, (key, value, control) => {\n // NOTE: Calling delete() on a cursor is documented as more efficient than\n // calling delete() on an object store with a single key\n // (https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/delete),\n // however, this requires us *not* to use a keysOnly cursor\n // (https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/delete). We\n // may want to compare the performance of each method.\n return control.delete();\n });\n }\n\n /**\n * Iterates over keys and values in an object store.\n *\n * @param options - Options specifying how to iterate the objects in the\n * store.\n * @param callback - will be called for each iterated object. Iteration can be\n * canceled at any point by calling the doneFn passed to the callback.\n * The callback can return a PersistencePromise if it performs async\n * operations but note that iteration will continue without waiting for them\n * to complete.\n * @returns A PersistencePromise that resolves once all PersistencePromises\n * returned by callbacks resolve.\n */\n iterate(\n callback: IterateCallback<KeyType, ValueType>\n ): PersistencePromise<void>;\n iterate(\n options: IterateOptions,\n callback: IterateCallback<KeyType, ValueType>\n ): PersistencePromise<void>;\n iterate(\n optionsOrCallback: IterateOptions | IterateCallback<KeyType, ValueType>,\n callback?: IterateCallback<KeyType, ValueType>\n ): PersistencePromise<void> {\n let options;\n if (!callback) {\n options = {};\n callback = optionsOrCallback as IterateCallback<KeyType, ValueType>;\n } else {\n options = optionsOrCallback as IterateOptions;\n }\n const cursor = this.cursor(options);\n return this.iterateCursor(cursor, callback);\n }\n\n /**\n * Iterates over a store, but waits for the given callback to complete for\n * each entry before iterating the next entry. This allows the callback to do\n * asynchronous work to determine if this iteration should continue.\n *\n * The provided callback should return `true` to continue iteration, and\n * `false` otherwise.\n */\n iterateSerial(\n callback: (k: KeyType, v: ValueType) => PersistencePromise<boolean>\n ): PersistencePromise<void> {\n const cursorRequest = this.cursor({});\n return new PersistencePromise((resolve, reject) => {\n cursorRequest.onerror = (event: Event) => {\n const error = checkForAndReportiOSError(\n (event.target as IDBRequest).error!\n );\n reject(error);\n };\n cursorRequest.onsuccess = (event: Event) => {\n const cursor: IDBCursorWithValue = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n callback(cursor.primaryKey as KeyType, cursor.value).next(\n shouldContinue => {\n if (shouldContinue) {\n cursor.continue();\n } else {\n resolve();\n }\n }\n );\n };\n });\n }\n\n private iterateCursor(\n cursorRequest: IDBRequest,\n fn: IterateCallback<KeyType, ValueType>\n ): PersistencePromise<void> {\n const results: Array<PersistencePromise<void>> = [];\n return new PersistencePromise((resolve, reject) => {\n cursorRequest.onerror = (event: Event) => {\n reject((event.target as IDBRequest).error!);\n };\n cursorRequest.onsuccess = (event: Event) => {\n const cursor: IDBCursorWithValue = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n const controller = new IterationController(cursor);\n const userResult = fn(\n cursor.primaryKey as KeyType,\n cursor.value,\n controller\n );\n if (userResult instanceof PersistencePromise) {\n const userPromise: PersistencePromise<void> = userResult.catch(\n err => {\n controller.done();\n return PersistencePromise.reject(err);\n }\n );\n results.push(userPromise);\n }\n if (controller.isDone) {\n resolve();\n } else if (controller.skipToKey === null) {\n cursor.continue();\n } else {\n cursor.continue(controller.skipToKey);\n }\n };\n }).next(() => PersistencePromise.waitFor(results));\n }\n\n private options(\n indexOrRange?: string | IDBKeyRange,\n range?: IDBKeyRange\n ): IterateOptions {\n let indexName: string | undefined = undefined;\n if (indexOrRange !== undefined) {\n if (typeof indexOrRange === 'string') {\n indexName = indexOrRange;\n } else {\n debugAssert(\n range === undefined,\n '3rd argument must not be defined if 2nd is a range.'\n );\n range = indexOrRange;\n }\n }\n return { index: indexName, range };\n }\n\n private cursor(options: IterateOptions): IDBRequest {\n let direction: IDBCursorDirection = 'next';\n if (options.reverse) {\n direction = 'prev';\n }\n if (options.index) {\n const index = this.store.index(options.index);\n if (options.keysOnly) {\n return index.openKeyCursor(options.range, direction);\n } else {\n return index.openCursor(options.range, direction);\n }\n } else {\n return this.store.openCursor(options.range, direction);\n }\n }\n}\n\n/**\n * Wraps an IDBRequest in a PersistencePromise, using the onsuccess / onerror\n * handlers to resolve / reject the PersistencePromise as appropriate.\n */\nfunction wrapRequest<R>(request: IDBRequest): PersistencePromise<R> {\n return new PersistencePromise<R>((resolve, reject) => {\n request.onsuccess = (event: Event) => {\n const result = (event.target as IDBRequest).result;\n resolve(result);\n };\n\n request.onerror = (event: Event) => {\n const error = checkForAndReportiOSError(\n (event.target as IDBRequest).error!\n );\n reject(error);\n };\n });\n}\n\n// Guard so we only report the error once.\nlet reportedIOSError = false;\nfunction checkForAndReportiOSError(error: DOMException): Error {\n const iOSVersion = SimpleDb.getIOSVersion(getUA());\n if (iOSVersion >= 12.2 && iOSVersion < 13) {\n const IOS_ERROR =\n 'An internal error was encountered in the Indexed Database server';\n if (error.message.indexOf(IOS_ERROR) >= 0) {\n // Wrap error in a more descriptive one.\n const newError = new FirestoreError(\n 'internal',\n `IOS_INDEXEDDB_BUG1: IndexedDb has thrown '${IOS_ERROR}'. This is likely ` +\n `due to an unavoidable bug in iOS. See https://stackoverflow.com/q/56496296/110915 ` +\n `for details and a potential workaround.`\n );\n if (!reportedIOSError) {\n reportedIOSError = true;\n // Throw a global exception outside of this promise chain, for the user to\n // potentially catch.\n setTimeout(() => {\n throw newError;\n }, 0);\n }\n return newError;\n }\n }\n return error;\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { FirestoreError } from '../api';\nimport { DocumentMap } from '../model/collections';\nimport {\n IndexOffset,\n indexOffsetComparator,\n newIndexOffsetFromDocument\n} from '../model/field_index';\nimport { debugAssert } from '../util/assert';\nimport { AsyncQueue, DelayedOperation, TimerId } from '../util/async_queue';\nimport { logDebug } from '../util/log';\n\nimport { ignoreIfPrimaryLeaseLoss, LocalStore } from './local_store';\nimport { LocalWriteResult } from './local_store_impl';\nimport { Persistence, Scheduler } from './persistence';\nimport { PersistencePromise } from './persistence_promise';\nimport { PersistenceTransaction } from './persistence_transaction';\nimport { isIndexedDbTransactionError } from './simple_db';\n\nconst LOG_TAG = 'IndexBackfiller';\n\n/** How long we wait to try running index backfill after SDK initialization. */\nconst INITIAL_BACKFILL_DELAY_MS = 15 * 1000;\n\n/** Minimum amount of time between backfill checks, after the first one. */\nconst REGULAR_BACKFILL_DELAY_MS = 60 * 1000;\n\n/** The maximum number of documents to process each time backfill() is called. */\nconst MAX_DOCUMENTS_TO_PROCESS = 50;\n\n/** This class is responsible for the scheduling of Index Backfiller. */\nexport class IndexBackfillerScheduler implements Scheduler {\n private task: DelayedOperation<void> | null;\n\n constructor(\n private readonly asyncQueue: AsyncQueue,\n private readonly backfiller: IndexBackfiller\n ) {\n this.task = null;\n }\n\n start(): void {\n debugAssert(\n this.task === null,\n 'Cannot start an already started IndexBackfillerScheduler'\n );\n this.schedule(INITIAL_BACKFILL_DELAY_MS);\n }\n\n stop(): void {\n if (this.task) {\n this.task.cancel();\n this.task = null;\n }\n }\n\n get started(): boolean {\n return this.task !== null;\n }\n\n private schedule(delay: number): void {\n debugAssert(\n this.task === null,\n 'Cannot schedule IndexBackfiller while a task is pending'\n );\n logDebug(LOG_TAG, `Scheduled in ${delay}ms`);\n this.task = this.asyncQueue.enqueueAfterDelay(\n TimerId.IndexBackfill,\n delay,\n async () => {\n this.task = null;\n try {\n const documentsProcessed = await this.backfiller.backfill();\n logDebug(LOG_TAG, `Documents written: ${documentsProcessed}`);\n } catch (e) {\n if (isIndexedDbTransactionError(e as Error)) {\n logDebug(\n LOG_TAG,\n 'Ignoring IndexedDB error during index backfill: ',\n e\n );\n } else {\n await ignoreIfPrimaryLeaseLoss(e as FirestoreError);\n }\n }\n await this.schedule(REGULAR_BACKFILL_DELAY_MS);\n }\n );\n }\n}\n\n/** Implements the steps for backfilling indexes. */\nexport class IndexBackfiller {\n constructor(\n /**\n * LocalStore provides access to IndexManager and LocalDocumentView.\n * These properties will update when the user changes. Consequently,\n * making a local copy of IndexManager and LocalDocumentView will require\n * updates over time. The simpler solution is to rely on LocalStore to have\n * an up-to-date references to IndexManager and LocalDocumentStore.\n */\n private readonly localStore: LocalStore,\n private readonly persistence: Persistence\n ) {}\n\n async backfill(\n maxDocumentsToProcess: number = MAX_DOCUMENTS_TO_PROCESS\n ): Promise<number> {\n return this.persistence.runTransaction(\n 'Backfill Indexes',\n 'readwrite-primary',\n txn => this.writeIndexEntries(txn, maxDocumentsToProcess)\n );\n }\n\n /** Writes index entries until the cap is reached. Returns the number of documents processed. */\n private writeIndexEntries(\n transaction: PersistenceTransaction,\n maxDocumentsToProcess: number\n ): PersistencePromise<number> {\n const processedCollectionGroups = new Set<string>();\n let documentsRemaining = maxDocumentsToProcess;\n let continueLoop = true;\n return PersistencePromise.doWhile(\n () => continueLoop === true && documentsRemaining > 0,\n () => {\n return this.localStore.indexManager\n .getNextCollectionGroupToUpdate(transaction)\n .next((collectionGroup: string | null) => {\n if (\n collectionGroup === null ||\n processedCollectionGroups.has(collectionGroup)\n ) {\n continueLoop = false;\n } else {\n logDebug(LOG_TAG, `Processing collection: ${collectionGroup}`);\n return this.writeEntriesForCollectionGroup(\n transaction,\n collectionGroup,\n documentsRemaining\n ).next(documentsProcessed => {\n documentsRemaining -= documentsProcessed;\n processedCollectionGroups.add(collectionGroup);\n });\n }\n });\n }\n ).next(() => maxDocumentsToProcess - documentsRemaining);\n }\n\n /**\n * Writes entries for the provided collection group. Returns the number of documents processed.\n */\n private writeEntriesForCollectionGroup(\n transaction: PersistenceTransaction,\n collectionGroup: string,\n documentsRemainingUnderCap: number\n ): PersistencePromise<number> {\n // Use the earliest offset of all field indexes to query the local cache.\n return this.localStore.indexManager\n .getMinOffsetFromCollectionGroup(transaction, collectionGroup)\n .next(existingOffset =>\n this.localStore.localDocuments\n .getNextDocuments(\n transaction,\n collectionGroup,\n existingOffset,\n documentsRemainingUnderCap\n )\n .next(nextBatch => {\n const docs: DocumentMap = nextBatch.changes;\n return this.localStore.indexManager\n .updateIndexEntries(transaction, docs)\n .next(() => this.getNewOffset(existingOffset, nextBatch))\n .next(newOffset => {\n logDebug(LOG_TAG, `Updating offset: ${newOffset}`);\n return this.localStore.indexManager.updateCollectionGroup(\n transaction,\n collectionGroup,\n newOffset\n );\n })\n .next(() => docs.size);\n })\n );\n }\n\n /** Returns the next offset based on the provided documents. */\n private getNewOffset(\n existingOffset: IndexOffset,\n lookupResult: LocalWriteResult\n ): IndexOffset {\n let maxOffset: IndexOffset = existingOffset;\n lookupResult.changes.forEach((key, document) => {\n const newOffset: IndexOffset = newIndexOffsetFromDocument(document);\n if (indexOffsetComparator(newOffset, maxOffset) > 0) {\n maxOffset = newOffset;\n }\n });\n return new IndexOffset(\n maxOffset.readTime,\n maxOffset.documentKey,\n Math.max(lookupResult.batchId, existingOffset.largestBatchId)\n );\n }\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ListenSequenceNumber } from './types';\n\n/**\n * `SequenceNumberSyncer` defines the methods required to keep multiple instances of a\n * `ListenSequence` in sync.\n */\nexport interface SequenceNumberSyncer {\n // Notify the syncer that a new sequence number has been used.\n writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void;\n // Setting this property allows the syncer to notify when a sequence number has been used, and\n // and lets the ListenSequence adjust its internal previous value accordingly.\n sequenceNumberHandler:\n | ((sequenceNumber: ListenSequenceNumber) => void)\n | null;\n}\n\n/**\n * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to\n * exceed. All subsequent calls to next will return increasing values. If provided with a\n * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as\n * well as write out sequence numbers that it produces via `next()`.\n */\nexport class ListenSequence {\n static readonly INVALID: ListenSequenceNumber = -1;\n\n private writeNewSequenceNumber?: (\n newSequenceNumber: ListenSequenceNumber\n ) => void;\n\n constructor(\n private previousValue: ListenSequenceNumber,\n sequenceNumberSyncer?: SequenceNumberSyncer\n ) {\n if (sequenceNumberSyncer) {\n sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber =>\n this.setPreviousValue(sequenceNumber);\n this.writeNewSequenceNumber = sequenceNumber =>\n sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);\n }\n }\n\n private setPreviousValue(\n externalPreviousValue: ListenSequenceNumber\n ): ListenSequenceNumber {\n this.previousValue = Math.max(externalPreviousValue, this.previousValue);\n return this.previousValue;\n }\n\n next(): ListenSequenceNumber {\n const nextValue = ++this.previousValue;\n if (this.writeNewSequenceNumber) {\n this.writeNewSequenceNumber(nextValue);\n }\n return nextValue;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** Sentinel value that sorts before any Mutation Batch ID. */\nexport const BATCHID_UNKNOWN = -1;\n\n// An Object whose keys and values are strings.\nexport interface StringMap {\n [key: string]: string;\n}\n\n/**\n * Returns whether a variable is either undefined or null.\n */\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\n return value === null || value === undefined;\n}\n\n/** Returns whether the value represents -0. */\nexport function isNegativeZero(value: number): boolean {\n // Detect if the value is -0.0. Based on polyfill from\n // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n return value === 0 && 1 / value === 1 / -0;\n}\n\n/**\n * Returns whether a value is an integer and in the safe integer range\n * @param value - The value to test for being an integer and in the safe range\n */\nexport function isSafeInteger(value: unknown): boolean {\n return (\n typeof value === 'number' &&\n Number.isInteger(value) &&\n !isNegativeZero(value) &&\n value <= Number.MAX_SAFE_INTEGER &&\n value >= Number.MIN_SAFE_INTEGER\n );\n}\n\n/** The subset of the browser's Window interface used by the SDK. */\nexport interface WindowLike {\n readonly localStorage: Storage;\n readonly indexedDB: IDBFactory | null;\n addEventListener(type: string, listener: EventListener): void;\n removeEventListener(type: string, listener: EventListener): void;\n}\n\n/** The subset of the browser's Document interface used by the SDK. */\nexport interface DocumentLike {\n readonly visibilityState: DocumentVisibilityState;\n addEventListener(type: string, listener: EventListener): void;\n removeEventListener(type: string, listener: EventListener): void;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResourcePath } from '../model/path';\nimport { fail, hardAssert } from '../util/assert';\n\n/**\n * Helpers for dealing with resource paths stored in IndexedDB.\n *\n * Resource paths in their canonical string form do not sort as the server\n * sorts them. Specifically the server splits paths into segments first and then\n * sorts, putting end-of-segment before any character. In a UTF-8 string\n * encoding the slash ('/') that denotes the end-of-segment naturally comes\n * after other characters so the intent here is to encode the path delimiters in\n * such a way that the resulting strings sort naturally.\n *\n * Resource paths are also used for prefix scans so it's important to\n * distinguish whole segments from any longer segments of which they might be a\n * prefix. For example, it's important to make it possible to scan documents in\n * a collection \"foo\" without encountering documents in a collection \"foobar\".\n *\n * Separate from the concerns about resource path ordering and separation,\n * On Android, SQLite imposes additional restrictions since it does not handle\n * keys with embedded NUL bytes particularly well. Rather than change the\n * implementation we keep the encoding identical to keep the ports similar.\n *\n * Taken together this means resource paths when encoded for storage in\n * IndexedDB have the following characteristics:\n *\n * * Segment separators (\"/\") sort before everything else.\n * * All paths have a trailing separator.\n * * NUL bytes do not exist in the output, since IndexedDB doesn't treat them\n * well.\n *\n * Therefore resource paths are encoded into string form using the following\n * rules:\n *\n * * '\\x01' is used as an escape character.\n * * Path separators are encoded as \"\\x01\\x01\"\n * * NUL bytes are encoded as \"\\x01\\x10\"\n * * '\\x01' is encoded as \"\\x01\\x11\"\n *\n * This encoding leaves some room between path separators and the NUL byte\n * just in case we decide to support integer document ids after all.\n *\n * Note that characters treated specially by the backend ('.', '/', and '~')\n * are not treated specially here. This class assumes that any unescaping of\n * resource path strings into actual ResourcePath objects will handle these\n * characters there.\n */\nexport type EncodedResourcePath = string;\n\nconst escapeChar = '\\u0001';\nconst encodedSeparatorChar = '\\u0001';\nconst encodedNul = '\\u0010';\nconst encodedEscape = '\\u0011';\n\n/**\n * Encodes a resource path into a IndexedDb-compatible string form.\n */\nexport function encodeResourcePath(path: ResourcePath): EncodedResourcePath {\n let result = '';\n for (let i = 0; i < path.length; i++) {\n if (result.length > 0) {\n result = encodeSeparator(result);\n }\n result = encodeSegment(path.get(i), result);\n }\n return encodeSeparator(result);\n}\n\n/** Encodes a single segment of a resource path into the given result */\nfunction encodeSegment(segment: string, resultBuf: string): string {\n let result = resultBuf;\n const length = segment.length;\n for (let i = 0; i < length; i++) {\n const c = segment.charAt(i);\n switch (c) {\n case '\\0':\n result += escapeChar + encodedNul;\n break;\n case escapeChar:\n result += escapeChar + encodedEscape;\n break;\n default:\n result += c;\n }\n }\n return result;\n}\n\n/** Encodes a path separator into the given result */\nfunction encodeSeparator(result: string): string {\n return result + escapeChar + encodedSeparatorChar;\n}\n\n/**\n * Decodes the given IndexedDb-compatible string form of a resource path into\n * a ResourcePath instance. Note that this method is not suitable for use with\n * decoding resource names from the server; those are One Platform format\n * strings.\n */\nexport function decodeResourcePath(path: EncodedResourcePath): ResourcePath {\n // Event the empty path must encode as a path of at least length 2. A path\n // with exactly 2 must be the empty path.\n const length = path.length;\n hardAssert(length >= 2, 0xfb98, 'Invalid path', { path });\n if (length === 2) {\n hardAssert(\n path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar,\n 0xdb51,\n 'Non-empty path had length 2',\n { path }\n );\n return ResourcePath.emptyPath();\n }\n\n // Escape characters cannot exist past the second-to-last position in the\n // source value.\n const lastReasonableEscapeIndex = length - 2;\n\n const segments: string[] = [];\n let segmentBuilder = '';\n\n for (let start = 0; start < length; ) {\n // The last two characters of a valid encoded path must be a separator, so\n // there must be an end to this segment.\n const end = path.indexOf(escapeChar, start);\n if (end < 0 || end > lastReasonableEscapeIndex) {\n fail(0xc553, 'Invalid encoded resource path', { path });\n }\n\n const next = path.charAt(end + 1);\n switch (next) {\n case encodedSeparatorChar:\n const currentPiece = path.substring(start, end);\n let segment;\n if (segmentBuilder.length === 0) {\n // Avoid copying for the common case of a segment that excludes \\0\n // and \\001\n segment = currentPiece;\n } else {\n segmentBuilder += currentPiece;\n segment = segmentBuilder;\n segmentBuilder = '';\n }\n segments.push(segment);\n break;\n case encodedNul:\n segmentBuilder += path.substring(start, end);\n segmentBuilder += '\\0';\n break;\n case encodedEscape:\n // The escape character can be used in the output to encode itself.\n segmentBuilder += path.substring(start, end + 1);\n break;\n default:\n fail(0xeeef, 'Invalid encoded resource path', { path });\n }\n\n start = end + 2;\n }\n\n return new ResourcePath(segments);\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Document as ProtoDocument } from '../protos/firestore_proto_api';\n\nimport { DbNoDocument, DbUnknownDocument } from './indexeddb_schema';\nimport { DbTimestampKey } from './indexeddb_sentinels';\n\n// This file contains older schema definitions for object stores that were\n// migrated to newer schema versions. These object stores should only be used\n// during schema migrations.\n\nexport interface DbRemoteDocument {\n unknownDocument?: DbUnknownDocument;\n noDocument?: DbNoDocument;\n document?: ProtoDocument;\n hasCommittedMutations?: boolean;\n readTime?: DbTimestampKey;\n parentPath?: string[];\n}\n\nexport type DbRemoteDocumentKey = string[];\nexport const DbRemoteDocumentStore = 'remoteDocuments';\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BatchId, TargetId } from '../core/types';\nimport { ResourcePath } from '../model/path';\nimport { fail } from '../util/assert';\n\nimport {\n EncodedResourcePath,\n encodeResourcePath\n} from './encoded_resource_path';\nimport { DbDocumentMutation } from './indexeddb_schema';\nimport { DbRemoteDocumentStore as DbRemoteDocumentStoreLegacy } from './indexeddb_schema_legacy';\n\n// This file contains static constants and helper functions for IndexedDB.\n// It is split from indexeddb_schema to allow for minification.\n\n/** A timestamp type that can be used in IndexedDb keys. */\nexport type DbTimestampKey = [/* seconds */ number, /* nanos */ number];\n\n// The key for the singleton object in the DbPrimaryClient is a single string.\nexport type DbPrimaryClientKey = typeof DbPrimaryClientKey;\n\n/**\n * Name of the IndexedDb object store.\n *\n * Note that the name 'owner' is chosen to ensure backwards compatibility with\n * older clients that only supported single locked access to the persistence\n * layer.\n */\nexport const DbPrimaryClientStore = 'owner';\n\n/**\n * The key string used for the single object that exists in the\n * DbPrimaryClient store.\n */\n\nexport const DbPrimaryClientKey = 'owner';\n\n/** Object keys in the 'mutationQueues' store are userId strings. */\nexport type DbMutationQueueKey = string;\n\n/** Name of the IndexedDb object store. */\nexport const DbMutationQueueStore = 'mutationQueues';\n\n/** Keys are automatically assigned via the userId property. */\nexport const DbMutationQueueKeyPath = 'userId';\n\n/** The 'mutations' store is keyed by batch ID. */\nexport type DbMutationBatchKey = BatchId;\n\n/** Name of the IndexedDb object store. */\nexport const DbMutationBatchStore = 'mutations';\n\n/** Keys are automatically assigned via the userId, batchId properties. */\nexport const DbMutationBatchKeyPath = 'batchId';\n\n/** The index name for lookup of mutations by user. */\n\nexport const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';\n\n/** The user mutations index is keyed by [userId, batchId] pairs. */\nexport const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];\n\n/**\n * The key for a db document mutation, which is made up of a userID, path, and\n * batchId. Note that the path must be serialized into a form that indexedDB can\n * sort.\n */\nexport type DbDocumentMutationKey = [string, EncodedResourcePath, BatchId];\n\n/**\n * Creates a [userId] key for use in the DbDocumentMutations index to iterate\n * over all of a user's document mutations.\n */\nexport function newDbDocumentMutationPrefixForUser(userId: string): [string] {\n return [userId];\n}\n\n/**\n * Creates a [userId, encodedPath] key for use in the DbDocumentMutations\n * index to iterate over all at document mutations for a given path or lower.\n */\nexport function newDbDocumentMutationPrefixForPath(\n userId: string,\n path: ResourcePath\n): [string, EncodedResourcePath] {\n return [userId, encodeResourcePath(path)];\n}\n\n/**\n * Creates a full index key of [userId, encodedPath, batchId] for inserting\n * and deleting into the DbDocumentMutations index.\n */\nexport function newDbDocumentMutationKey(\n userId: string,\n path: ResourcePath,\n batchId: BatchId\n): DbDocumentMutationKey {\n return [userId, encodeResourcePath(path), batchId];\n}\n\n/**\n * Because we store all the useful information for this store in the key,\n * there is no useful information to store as the value. The raw (unencoded)\n * path cannot be stored because IndexedDb doesn't store prototype\n * information.\n */\nexport const DbDocumentMutationPlaceholder: DbDocumentMutation = {};\n\nexport const DbDocumentMutationStore = 'documentMutations';\n\nexport const DbRemoteDocumentStore = 'remoteDocumentsV14';\n\n/**\n * A key in the 'remoteDocumentsV14' object store is an array containing the\n * collection path, the collection group, the read time and the document id.\n */\nexport type DbRemoteDocumentKey = [\n /** path to collection */ string[],\n /** collection group */ string,\n /** read time */ DbTimestampKey,\n /** document ID */ string\n];\n\n/**\n * The primary key of the remote documents store, which allows for efficient\n * access by collection path and read time.\n */\nexport const DbRemoteDocumentKeyPath = [\n 'prefixPath',\n 'collectionGroup',\n 'readTime',\n 'documentId'\n];\n\n/** An index that provides access to documents by key. */\nexport const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';\n\nexport const DbRemoteDocumentDocumentKeyIndexPath = [\n 'prefixPath',\n 'collectionGroup',\n 'documentId'\n];\n\n/**\n * An index that provides access to documents by collection group and read\n * time.\n *\n * This index is used by the index backfiller.\n */\nexport const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';\n\nexport const DbRemoteDocumentCollectionGroupIndexPath = [\n 'collectionGroup',\n 'readTime',\n 'prefixPath',\n 'documentId'\n];\n\nexport const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';\n\nexport const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';\n\nexport type DbRemoteDocumentGlobalKey = typeof DbRemoteDocumentGlobalKey;\n\n/**\n * A key in the 'targets' object store is a targetId of the query.\n */\nexport type DbTargetKey = TargetId;\n\nexport const DbTargetStore = 'targets';\n\n/** Keys are automatically assigned via the targetId property. */\nexport const DbTargetKeyPath = 'targetId';\n\n/** The name of the queryTargets index. */\nexport const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';\n\n/**\n * The index of all canonicalIds to the targets that they match. This is not\n * a unique mapping because canonicalId does not promise a unique name for all\n * possible queries, so we append the targetId to make the mapping unique.\n */\nexport const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];\n\n/**\n * The key for a DbTargetDocument, containing a targetId and an encoded resource\n * path.\n */\nexport type DbTargetDocumentKey = [TargetId, EncodedResourcePath];\n\n/** Name of the IndexedDb object store. */\nexport const DbTargetDocumentStore = 'targetDocuments';\n\n/** Keys are automatically assigned via the targetId, path properties. */\nexport const DbTargetDocumentKeyPath = ['targetId', 'path'];\n\n/** The index name for the reverse index. */\nexport const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';\n\n/** We also need to create the reverse index for these properties. */\nexport const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];\n\n/**\n * The type to represent the single allowed key for the DbTargetGlobal store.\n */\nexport type DbTargetGlobalKey = typeof DbTargetGlobalKey;\n\n/**\n * The key string used for the single object that exists in the\n * DbTargetGlobal store.\n */\nexport const DbTargetGlobalKey = 'targetGlobalKey';\n\nexport const DbTargetGlobalStore = 'targetGlobal';\n\n/**\n * The key for a DbCollectionParent entry, containing the collection ID\n * and the parent path that contains it. Note that the parent path will be an\n * empty path in the case of root-level collections.\n */\nexport type DbCollectionParentKey = [string, EncodedResourcePath];\n\n/** Name of the IndexedDb object store. */\nexport const DbCollectionParentStore = 'collectionParents';\n\n/** Keys are automatically assigned via the collectionId, parent properties. */\nexport const DbCollectionParentKeyPath = ['collectionId', 'parent'];\n\n/** Name of the IndexedDb object store. */\nexport const DbClientMetadataStore = 'clientMetadata';\n\n/** Keys are automatically assigned via the clientId properties. */\nexport const DbClientMetadataKeyPath = 'clientId';\n\n/** Object keys in the 'clientMetadata' store are clientId strings. */\nexport type DbClientMetadataKey = string;\n\nexport type DbBundlesKey = string;\n\n/** Name of the IndexedDb object store. */\nexport const DbBundleStore = 'bundles';\n\nexport const DbBundleKeyPath = 'bundleId';\n\nexport type DbNamedQueriesKey = string;\n\n/** Name of the IndexedDb object store. */\nexport const DbNamedQueryStore = 'namedQueries';\n\nexport const DbNamedQueryKeyPath = 'name';\n\n/** The key for each index consisting of just the index id. */\nexport type DbIndexConfigurationKey = number;\n\n/** Name of the IndexedDb object store. */\nexport const DbIndexConfigurationStore = 'indexConfiguration';\n\nexport const DbIndexConfigurationKeyPath = 'indexId';\n\n/**\n * An index that provides access to the index configurations by collection\n * group.\n *\n * PORTING NOTE: iOS and Android maintain this index in-memory, but this is\n * not possible here as the Web client supports concurrent access to\n * persistence via multi-tab.\n */\nexport const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';\n\nexport const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';\n\n/** The key for each index state consisting of the index id and its user id. */\nexport type DbIndexStateKey = [number, string];\n\n/** Name of the IndexedDb object store. */\nexport const DbIndexStateStore = 'indexState';\n\nexport const DbIndexStateKeyPath = ['indexId', 'uid'];\n\n/**\n * An index that provides access to documents in a collection sorted by last\n * update time. Used by the backfiller.\n *\n * PORTING NOTE: iOS and Android maintain this index in-memory, but this is\n * not possible here as the Web client supports concurrent access to\n * persistence via multi-tab.\n */\nexport const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';\n\nexport const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];\n\n/**\n * Representation of a byte array that is safe for\n * use in an IndexedDb key. The value is either\n * a \"sortable byte string\", which is key safe in\n * Safari/WebKit, or the value is a Uint8Array,\n * which is key safe in other browsers.\n */\nexport type KeySafeBytes = Uint8Array | string;\n\n/**\n * The key for each index entry consists of the index id and its user id,\n * the encoded array and directional value for the indexed fields as well as\n * an ordered and an encoded document path for the indexed document.\n */\nexport type DbIndexEntryKey = [\n number,\n string,\n KeySafeBytes,\n KeySafeBytes,\n KeySafeBytes,\n string[]\n];\n\n/** Name of the IndexedDb object store. */\nexport const DbIndexEntryStore = 'indexEntries';\n\nexport const DbIndexEntryKeyPath = [\n 'indexId',\n 'uid',\n 'arrayValue',\n 'directionalValue',\n 'orderedDocumentKey',\n 'documentKey'\n];\n\nexport const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';\n\nexport const DbIndexEntryDocumentKeyIndexPath = [\n 'indexId',\n 'uid',\n 'orderedDocumentKey'\n];\n\nexport type DbDocumentOverlayKey = [\n /* userId */ string,\n /* collectionPath */ string,\n /* documentId */ string\n];\n\n/** Name of the IndexedDb object store. */\nexport const DbDocumentOverlayStore = 'documentOverlays';\n\nexport const DbDocumentOverlayKeyPath = [\n 'userId',\n 'collectionPath',\n 'documentId'\n];\n\nexport const DbDocumentOverlayCollectionPathOverlayIndex =\n 'collectionPathOverlayIndex';\n\nexport const DbDocumentOverlayCollectionPathOverlayIndexPath = [\n 'userId',\n 'collectionPath',\n 'largestBatchId'\n];\n\nexport const DbDocumentOverlayCollectionGroupOverlayIndex =\n 'collectionGroupOverlayIndex';\n\nexport const DbDocumentOverlayCollectionGroupOverlayIndexPath = [\n 'userId',\n 'collectionGroup',\n 'largestBatchId'\n];\n\n/** Name of the IndexedDb object store. */\nexport const DbGlobalsStore = 'globals';\n\nexport const DbGlobalsKeyPath = 'name';\n\n/** Names of global values */\nexport type DbGlobalsKey = 'sessionToken';\n\n// Visible for testing\nexport const V1_STORES = [\n DbMutationQueueStore,\n DbMutationBatchStore,\n DbDocumentMutationStore,\n DbRemoteDocumentStoreLegacy,\n DbTargetStore,\n DbPrimaryClientStore,\n DbTargetGlobalStore,\n DbTargetDocumentStore\n];\n\n// Visible for testing\nexport const V3_STORES = V1_STORES;\n// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.\nexport const V4_STORES = [...V3_STORES, DbClientMetadataStore];\nexport const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];\nexport const V8_STORES = [...V6_STORES, DbCollectionParentStore];\nexport const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];\nexport const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];\nexport const V13_STORES = [\n DbMutationQueueStore,\n DbMutationBatchStore,\n DbDocumentMutationStore,\n DbRemoteDocumentStore,\n DbTargetStore,\n DbPrimaryClientStore,\n DbTargetGlobalStore,\n DbTargetDocumentStore,\n DbClientMetadataStore,\n DbRemoteDocumentGlobalStore,\n DbCollectionParentStore,\n DbBundleStore,\n DbNamedQueryStore,\n DbDocumentOverlayStore\n];\nexport const V14_STORES = V13_STORES;\nexport const V15_STORES = [\n ...V14_STORES,\n DbIndexConfigurationStore,\n DbIndexStateStore,\n DbIndexEntryStore\n];\nexport const V16_STORES = V15_STORES;\nexport const V17_STORES = [...V15_STORES, DbGlobalsStore];\nexport const V18_STORES = V17_STORES;\n\n/**\n * The list of all default IndexedDB stores used throughout the SDK. This is\n * used when creating transactions so that access across all stores is done\n * atomically.\n */\nexport const ALL_STORES = V12_STORES;\n\n/** Returns the object stores for the provided schema. */\nexport function getObjectStores(schemaVersion: number): string[] {\n if (schemaVersion === 18) {\n return V18_STORES;\n } else if (schemaVersion === 17) {\n return V17_STORES;\n } else if (schemaVersion === 16) {\n return V16_STORES;\n } else if (schemaVersion === 15) {\n return V15_STORES;\n } else if (schemaVersion === 14) {\n return V14_STORES;\n } else if (schemaVersion === 13) {\n return V13_STORES;\n } else if (schemaVersion === 12) {\n return V12_STORES;\n } else if (schemaVersion === 11) {\n return V11_STORES;\n } else {\n fail(0xeb55, 'Only schema versions >11 are supported');\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ListenSequenceNumber } from '../core/types';\nimport { debugCast } from '../util/assert';\n\nimport { PersistenceTransaction } from './persistence_transaction';\nimport { SimpleDb, SimpleDbStore, SimpleDbTransaction } from './simple_db';\n\nexport class IndexedDbTransaction extends PersistenceTransaction {\n constructor(\n readonly simpleDbTransaction: SimpleDbTransaction,\n readonly currentSequenceNumber: ListenSequenceNumber\n ) {\n super();\n }\n}\n\nexport function getStore<Key extends IDBValidKey, Value>(\n txn: PersistenceTransaction,\n store: string\n): SimpleDbStore<Key, Value> {\n const indexedDbTransaction = debugCast(txn, IndexedDbTransaction);\n return SimpleDb.getStore<Key, Value>(\n indexedDbTransaction.simpleDbTransaction,\n store\n );\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from './assert';\n\nexport interface Dict<V> {\n [stringKey: string]: V;\n}\n\nexport function objectSize(obj: object): number {\n let count = 0;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n count++;\n }\n }\n return count;\n}\n\nexport function forEach<V>(\n obj: Dict<V> | undefined,\n fn: (key: string, val: V) => void\n): void {\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n fn(key, obj[key]);\n }\n }\n}\n\nexport function mapToArray<V, R>(\n obj: Dict<V>,\n fn: (element: V, key: string, obj: Dict<V>) => R\n): R[] {\n const result: R[] = [];\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n result.push(fn(obj[key], key, obj));\n }\n }\n return result;\n}\n\nexport function isEmpty<V>(obj: Dict<V>): boolean {\n debugAssert(\n obj != null && typeof obj === 'object',\n 'isEmpty() expects object parameter.'\n );\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n return false;\n }\n }\n return true;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert, fail } from './assert';\n\n/*\n * Implementation of an immutable SortedMap using a Left-leaning\n * Red-Black Tree, adapted from the implementation in Mugs\n * (http://mads379.github.com/mugs/) by Mads Hartmann Jensen\n * (mads379@gmail.com).\n *\n * Original paper on Left-leaning Red-Black Trees:\n * http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf\n *\n * Invariant 1: No red node has a red child\n * Invariant 2: Every leaf path has the same number of black nodes\n * Invariant 3: Only the left child can be red (left leaning)\n */\n\nexport type Comparator<K> = (key1: K, key2: K) => number;\n\nexport interface Entry<K, V> {\n key: K;\n value: V;\n}\n\n// An immutable sorted map implementation, based on a Left-leaning Red-Black\n// tree.\nexport class SortedMap<K, V> {\n // visible for testing\n root: LLRBNode<K, V> | LLRBEmptyNode<K, V>;\n\n constructor(\n public comparator: Comparator<K>,\n root?: LLRBNode<K, V> | LLRBEmptyNode<K, V>\n ) {\n this.root = root ? root : LLRBNode.EMPTY;\n }\n\n // Returns a copy of the map, with the specified key/value added or replaced.\n insert(key: K, value: V): SortedMap<K, V> {\n return new SortedMap<K, V>(\n this.comparator,\n this.root\n .insert(key, value, this.comparator)\n .copy(null, null, LLRBNode.BLACK, null, null)\n );\n }\n\n // Returns a copy of the map, with the specified key removed.\n remove(key: K): SortedMap<K, V> {\n return new SortedMap<K, V>(\n this.comparator,\n this.root\n .remove(key, this.comparator)\n .copy(null, null, LLRBNode.BLACK, null, null)\n );\n }\n\n // Returns the value of the node with the given key, or null.\n get(key: K): V | null {\n let node = this.root;\n while (!node.isEmpty()) {\n const cmp = this.comparator(key, node.key);\n if (cmp === 0) {\n return node.value;\n } else if (cmp < 0) {\n node = node.left;\n } else if (cmp > 0) {\n node = node.right;\n }\n }\n return null;\n }\n\n // Returns the index of the element in this sorted map, or -1 if it doesn't\n // exist.\n indexOf(key: K): number {\n // Number of nodes that were pruned when descending right\n let prunedNodes = 0;\n let node = this.root;\n while (!node.isEmpty()) {\n const cmp = this.comparator(key, node.key);\n if (cmp === 0) {\n return prunedNodes + node.left.size;\n } else if (cmp < 0) {\n node = node.left;\n } else {\n // Count all nodes left of the node plus the node itself\n prunedNodes += node.left.size + 1;\n node = node.right;\n }\n }\n // Node not found\n return -1;\n }\n\n isEmpty(): boolean {\n return this.root.isEmpty();\n }\n\n // Returns the total number of nodes in the map.\n get size(): number {\n return this.root.size;\n }\n\n // Returns the minimum key in the map.\n minKey(): K | null {\n return this.root.minKey();\n }\n\n // Returns the maximum key in the map.\n maxKey(): K | null {\n return this.root.maxKey();\n }\n\n // Traverses the map in key order and calls the specified action function\n // for each key/value pair. If action returns true, traversal is aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n inorderTraversal<T>(action: (k: K, v: V) => T): T {\n return (this.root as LLRBNode<K, V>).inorderTraversal(action);\n }\n\n forEach(fn: (k: K, v: V) => void): void {\n this.inorderTraversal((k, v) => {\n fn(k, v);\n return false;\n });\n }\n\n toString(): string {\n const descriptions: string[] = [];\n this.inorderTraversal((k, v) => {\n descriptions.push(`${k}:${v}`);\n return false;\n });\n return `{${descriptions.join(', ')}}`;\n }\n\n // Traverses the map in reverse key order and calls the specified action\n // function for each key/value pair. If action returns true, traversal is\n // aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n reverseTraversal<T>(action: (k: K, v: V) => T): T {\n return (this.root as LLRBNode<K, V>).reverseTraversal(action);\n }\n\n // Returns an iterator over the SortedMap.\n getIterator(): SortedMapIterator<K, V> {\n return new SortedMapIterator<K, V>(this.root, null, this.comparator, false);\n }\n\n getIteratorFrom(key: K): SortedMapIterator<K, V> {\n return new SortedMapIterator<K, V>(this.root, key, this.comparator, false);\n }\n\n getReverseIterator(): SortedMapIterator<K, V> {\n return new SortedMapIterator<K, V>(this.root, null, this.comparator, true);\n }\n\n getReverseIteratorFrom(key: K): SortedMapIterator<K, V> {\n return new SortedMapIterator<K, V>(this.root, key, this.comparator, true);\n }\n} // end SortedMap\n\n// An iterator over an LLRBNode.\nexport class SortedMapIterator<K, V> {\n private isReverse: boolean;\n private nodeStack: Array<LLRBNode<K, V> | LLRBEmptyNode<K, V>>;\n\n constructor(\n node: LLRBNode<K, V> | LLRBEmptyNode<K, V>,\n startKey: K | null,\n comparator: Comparator<K>,\n isReverse: boolean\n ) {\n this.isReverse = isReverse;\n this.nodeStack = [];\n\n let cmp = 1;\n while (!node.isEmpty()) {\n cmp = startKey ? comparator(node.key, startKey) : 1;\n // flip the comparison if we're going in reverse\n if (startKey && isReverse) {\n cmp *= -1;\n }\n\n if (cmp < 0) {\n // This node is less than our start key. ignore it\n if (this.isReverse) {\n node = node.left;\n } else {\n node = node.right;\n }\n } else if (cmp === 0) {\n // This node is exactly equal to our start key. Push it on the stack,\n // but stop iterating;\n this.nodeStack.push(node);\n break;\n } else {\n // This node is greater than our start key, add it to the stack and move\n // to the next one\n this.nodeStack.push(node);\n if (this.isReverse) {\n node = node.right;\n } else {\n node = node.left;\n }\n }\n }\n }\n\n getNext(): Entry<K, V> {\n debugAssert(\n this.nodeStack.length > 0,\n 'getNext() called on iterator when hasNext() is false.'\n );\n\n let node = this.nodeStack.pop()!;\n const result = { key: node.key, value: node.value };\n\n if (this.isReverse) {\n node = node.left;\n while (!node.isEmpty()) {\n this.nodeStack.push(node);\n node = node.right;\n }\n } else {\n node = node.right;\n while (!node.isEmpty()) {\n this.nodeStack.push(node);\n node = node.left;\n }\n }\n\n return result;\n }\n\n hasNext(): boolean {\n return this.nodeStack.length > 0;\n }\n\n peek(): Entry<K, V> | null {\n if (this.nodeStack.length === 0) {\n return null;\n }\n\n const node = this.nodeStack[this.nodeStack.length - 1];\n return { key: node.key, value: node.value };\n }\n} // end SortedMapIterator\n\n// Represents a node in a Left-leaning Red-Black tree.\nexport class LLRBNode<K, V> {\n readonly color: boolean;\n readonly left: LLRBNode<K, V> | LLRBEmptyNode<K, V>;\n readonly right: LLRBNode<K, V> | LLRBEmptyNode<K, V>;\n readonly size: number;\n\n // Empty node is shared between all LLRB trees.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n static EMPTY: LLRBEmptyNode<any, any> = null as any;\n\n static RED = true;\n static BLACK = false;\n\n constructor(\n public key: K,\n public value: V,\n color?: boolean,\n left?: LLRBNode<K, V> | LLRBEmptyNode<K, V>,\n right?: LLRBNode<K, V> | LLRBEmptyNode<K, V>\n ) {\n this.color = color != null ? color : LLRBNode.RED;\n this.left = left != null ? left : LLRBNode.EMPTY;\n this.right = right != null ? right : LLRBNode.EMPTY;\n this.size = this.left.size + 1 + this.right.size;\n }\n\n // Returns a copy of the current node, optionally replacing pieces of it.\n copy(\n key: K | null,\n value: V | null,\n color: boolean | null,\n left: LLRBNode<K, V> | LLRBEmptyNode<K, V> | null,\n right: LLRBNode<K, V> | LLRBEmptyNode<K, V> | null\n ): LLRBNode<K, V> {\n return new LLRBNode<K, V>(\n key != null ? key : this.key,\n value != null ? value : this.value,\n color != null ? color : this.color,\n left != null ? left : this.left,\n right != null ? right : this.right\n );\n }\n\n isEmpty(): boolean {\n return false;\n }\n\n // Traverses the tree in key order and calls the specified action function\n // for each node. If action returns true, traversal is aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n inorderTraversal<T>(action: (k: K, v: V) => T): T {\n return (\n (this.left as LLRBNode<K, V>).inorderTraversal(action) ||\n action(this.key, this.value) ||\n (this.right as LLRBNode<K, V>).inorderTraversal(action)\n );\n }\n\n // Traverses the tree in reverse key order and calls the specified action\n // function for each node. If action returns true, traversal is aborted.\n // Returns the first truthy value returned by action, or the last falsey\n // value returned by action.\n reverseTraversal<T>(action: (k: K, v: V) => T): T {\n return (\n (this.right as LLRBNode<K, V>).reverseTraversal(action) ||\n action(this.key, this.value) ||\n (this.left as LLRBNode<K, V>).reverseTraversal(action)\n );\n }\n\n // Returns the minimum node in the tree.\n private min(): LLRBNode<K, V> {\n if (this.left.isEmpty()) {\n return this;\n } else {\n return (this.left as LLRBNode<K, V>).min();\n }\n }\n\n // Returns the maximum key in the tree.\n minKey(): K | null {\n return this.min().key;\n }\n\n // Returns the maximum key in the tree.\n maxKey(): K | null {\n if (this.right.isEmpty()) {\n return this.key;\n } else {\n return this.right.maxKey();\n }\n }\n\n // Returns new tree, with the key/value added.\n insert(key: K, value: V, comparator: Comparator<K>): LLRBNode<K, V> {\n let n: LLRBNode<K, V> = this;\n const cmp = comparator(key, n.key);\n if (cmp < 0) {\n n = n.copy(null, null, null, n.left.insert(key, value, comparator), null);\n } else if (cmp === 0) {\n n = n.copy(null, value, null, null, null);\n } else {\n n = n.copy(\n null,\n null,\n null,\n null,\n n.right.insert(key, value, comparator)\n );\n }\n return n.fixUp();\n }\n\n private removeMin(): LLRBNode<K, V> | LLRBEmptyNode<K, V> {\n if (this.left.isEmpty()) {\n return LLRBNode.EMPTY;\n }\n let n: LLRBNode<K, V> = this;\n if (!n.left.isRed() && !n.left.left.isRed()) {\n n = n.moveRedLeft();\n }\n n = n.copy(null, null, null, (n.left as LLRBNode<K, V>).removeMin(), null);\n return n.fixUp();\n }\n\n // Returns new tree, with the specified item removed.\n remove(\n key: K,\n comparator: Comparator<K>\n ): LLRBNode<K, V> | LLRBEmptyNode<K, V> {\n let smallest: LLRBNode<K, V>;\n let n: LLRBNode<K, V> = this;\n if (comparator(key, n.key) < 0) {\n if (!n.left.isEmpty() && !n.left.isRed() && !n.left.left.isRed()) {\n n = n.moveRedLeft();\n }\n n = n.copy(null, null, null, n.left.remove(key, comparator), null);\n } else {\n if (n.left.isRed()) {\n n = n.rotateRight();\n }\n if (!n.right.isEmpty() && !n.right.isRed() && !n.right.left.isRed()) {\n n = n.moveRedRight();\n }\n if (comparator(key, n.key) === 0) {\n if (n.right.isEmpty()) {\n return LLRBNode.EMPTY;\n } else {\n smallest = (n.right as LLRBNode<K, V>).min();\n n = n.copy(\n smallest.key,\n smallest.value,\n null,\n null,\n (n.right as LLRBNode<K, V>).removeMin()\n );\n }\n }\n n = n.copy(null, null, null, null, n.right.remove(key, comparator));\n }\n return n.fixUp();\n }\n\n isRed(): boolean {\n return this.color;\n }\n\n // Returns new tree after performing any needed rotations.\n private fixUp(): LLRBNode<K, V> {\n let n: LLRBNode<K, V> = this;\n if (n.right.isRed() && !n.left.isRed()) {\n n = n.rotateLeft();\n }\n if (n.left.isRed() && n.left.left.isRed()) {\n n = n.rotateRight();\n }\n if (n.left.isRed() && n.right.isRed()) {\n n = n.colorFlip();\n }\n return n;\n }\n\n private moveRedLeft(): LLRBNode<K, V> {\n let n = this.colorFlip();\n if (n.right.left.isRed()) {\n n = n.copy(\n null,\n null,\n null,\n null,\n (n.right as LLRBNode<K, V>).rotateRight()\n );\n n = n.rotateLeft();\n n = n.colorFlip();\n }\n return n;\n }\n\n private moveRedRight(): LLRBNode<K, V> {\n let n = this.colorFlip();\n if (n.left.left.isRed()) {\n n = n.rotateRight();\n n = n.colorFlip();\n }\n return n;\n }\n\n private rotateLeft(): LLRBNode<K, V> {\n const nl = this.copy(null, null, LLRBNode.RED, null, this.right.left);\n return (this.right as LLRBNode<K, V>).copy(\n null,\n null,\n this.color,\n nl,\n null\n );\n }\n\n private rotateRight(): LLRBNode<K, V> {\n const nr = this.copy(null, null, LLRBNode.RED, this.left.right, null);\n return (this.left as LLRBNode<K, V>).copy(null, null, this.color, null, nr);\n }\n\n private colorFlip(): LLRBNode<K, V> {\n const left = this.left.copy(null, null, !this.left.color, null, null);\n const right = this.right.copy(null, null, !this.right.color, null, null);\n return this.copy(null, null, !this.color, left, right);\n }\n\n // For testing.\n checkMaxDepth(): boolean {\n const blackDepth = this.check();\n if (Math.pow(2.0, blackDepth) <= this.size + 1) {\n return true;\n } else {\n return false;\n }\n }\n\n // In a balanced RB tree, the black-depth (number of black nodes) from root to\n // leaves is equal on both sides. This function verifies that or asserts.\n protected check(): number {\n if (this.isRed() && this.left.isRed()) {\n throw fail(0xaad2, 'Red node has red child', {\n key: this.key,\n value: this.value\n });\n }\n if (this.right.isRed()) {\n throw fail(0x3721, 'Right child of (`key`, `value`) is red', {\n key: this.key,\n value: this.value\n });\n }\n const blackDepth = (this.left as LLRBNode<K, V>).check();\n if (blackDepth !== (this.right as LLRBNode<K, V>).check()) {\n throw fail(0x6d2d, 'Black depths differ');\n } else {\n return blackDepth + (this.isRed() ? 0 : 1);\n }\n }\n} // end LLRBNode\n\n// Represents an empty node (a leaf node in the Red-Black Tree).\nexport class LLRBEmptyNode<K, V> {\n get key(): never {\n throw fail(0xe1a6, 'LLRBEmptyNode has no key.');\n }\n get value(): never {\n throw fail(0x3f0d, 'LLRBEmptyNode has no value.');\n }\n get color(): never {\n throw fail(0x4157, 'LLRBEmptyNode has no color.');\n }\n get left(): never {\n throw fail(0x741e, 'LLRBEmptyNode has no left child.');\n }\n get right(): never {\n throw fail(0x901e, 'LLRBEmptyNode has no right child.');\n }\n size = 0;\n\n // Returns a copy of the current node.\n copy(\n key: K | null,\n value: V | null,\n color: boolean | null,\n left: LLRBNode<K, V> | LLRBEmptyNode<K, V> | null,\n right: LLRBNode<K, V> | LLRBEmptyNode<K, V> | null\n ): LLRBEmptyNode<K, V> {\n return this;\n }\n\n // Returns a copy of the tree, with the specified key/value added.\n insert(key: K, value: V, comparator: Comparator<K>): LLRBNode<K, V> {\n return new LLRBNode<K, V>(key, value);\n }\n\n // Returns a copy of the tree, with the specified key removed.\n remove(key: K, comparator: Comparator<K>): LLRBEmptyNode<K, V> {\n return this;\n }\n\n isEmpty(): boolean {\n return true;\n }\n\n inorderTraversal(action: (k: K, v: V) => boolean): boolean {\n return false;\n }\n\n reverseTraversal(action: (k: K, v: V) => boolean): boolean {\n return false;\n }\n\n minKey(): K | null {\n return null;\n }\n\n maxKey(): K | null {\n return null;\n }\n\n isRed(): boolean {\n return false;\n }\n\n // For testing.\n checkMaxDepth(): boolean {\n return true;\n }\n\n protected check(): 0 {\n return 0;\n }\n} // end LLRBEmptyNode\n\nLLRBNode.EMPTY = new LLRBEmptyNode<unknown, unknown>();\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SortedMap, SortedMapIterator } from './sorted_map';\n\n/**\n * SortedSet is an immutable (copy-on-write) collection that holds elements\n * in order specified by the provided comparator.\n *\n * NOTE: if provided comparator returns 0 for two elements, we consider them to\n * be equal!\n */\nexport class SortedSet<T> {\n private data: SortedMap<T, boolean>;\n\n constructor(private comparator: (left: T, right: T) => number) {\n this.data = new SortedMap<T, boolean>(this.comparator);\n }\n\n has(elem: T): boolean {\n return this.data.get(elem) !== null;\n }\n\n first(): T | null {\n return this.data.minKey();\n }\n\n last(): T | null {\n return this.data.maxKey();\n }\n\n get size(): number {\n return this.data.size;\n }\n\n indexOf(elem: T): number {\n return this.data.indexOf(elem);\n }\n\n /** Iterates elements in order defined by \"comparator\" */\n forEach(cb: (elem: T) => void): void {\n this.data.inorderTraversal((k: T, v: boolean) => {\n cb(k);\n return false;\n });\n }\n\n /** Iterates over `elem`s such that: range[0] <= elem < range[1]. */\n forEachInRange(range: [T, T], cb: (elem: T) => void): void {\n const iter = this.data.getIteratorFrom(range[0]);\n while (iter.hasNext()) {\n const elem = iter.getNext();\n if (this.comparator(elem.key, range[1]) >= 0) {\n return;\n }\n cb(elem.key);\n }\n }\n\n /**\n * Iterates over `elem`s such that: start <= elem until false is returned.\n */\n forEachWhile(cb: (elem: T) => boolean, start?: T): void {\n let iter: SortedMapIterator<T, boolean>;\n if (start !== undefined) {\n iter = this.data.getIteratorFrom(start);\n } else {\n iter = this.data.getIterator();\n }\n while (iter.hasNext()) {\n const elem = iter.getNext();\n const result = cb(elem.key);\n if (!result) {\n return;\n }\n }\n }\n\n /** Finds the least element greater than or equal to `elem`. */\n firstAfterOrEqual(elem: T): T | null {\n const iter = this.data.getIteratorFrom(elem);\n return iter.hasNext() ? iter.getNext().key : null;\n }\n\n getIterator(): SortedSetIterator<T> {\n return new SortedSetIterator<T>(this.data.getIterator());\n }\n\n getIteratorFrom(key: T): SortedSetIterator<T> {\n return new SortedSetIterator<T>(this.data.getIteratorFrom(key));\n }\n\n /** Inserts or updates an element */\n add(elem: T): SortedSet<T> {\n return this.copy(this.data.remove(elem).insert(elem, true));\n }\n\n /** Deletes an element */\n delete(elem: T): SortedSet<T> {\n if (!this.has(elem)) {\n return this;\n }\n return this.copy(this.data.remove(elem));\n }\n\n isEmpty(): boolean {\n return this.data.isEmpty();\n }\n\n unionWith(other: SortedSet<T>): SortedSet<T> {\n let result: SortedSet<T> = this;\n\n // Make sure `result` always refers to the larger one of the two sets.\n if (result.size < other.size) {\n result = other;\n other = this;\n }\n\n other.forEach(elem => {\n result = result.add(elem);\n });\n return result;\n }\n\n isEqual(other: SortedSet<T>): boolean {\n if (!(other instanceof SortedSet)) {\n return false;\n }\n if (this.size !== other.size) {\n return false;\n }\n\n const thisIt = this.data.getIterator();\n const otherIt = other.data.getIterator();\n while (thisIt.hasNext()) {\n const thisElem = thisIt.getNext().key;\n const otherElem = otherIt.getNext().key;\n if (this.comparator(thisElem, otherElem) !== 0) {\n return false;\n }\n }\n return true;\n }\n\n toArray(): T[] {\n const res: T[] = [];\n this.forEach(targetId => {\n res.push(targetId);\n });\n return res;\n }\n\n toString(): string {\n const result: T[] = [];\n this.forEach(elem => result.push(elem));\n return 'SortedSet(' + result.toString() + ')';\n }\n\n private copy(data: SortedMap<T, boolean>): SortedSet<T> {\n const result = new SortedSet(this.comparator);\n result.data = data;\n return result;\n }\n}\n\nexport class SortedSetIterator<T> {\n constructor(private iter: SortedMapIterator<T, boolean>) {}\n\n getNext(): T {\n return this.iter.getNext().key;\n }\n\n hasNext(): boolean {\n return this.iter.hasNext();\n }\n}\n\n/**\n * Compares two sorted sets for equality using their natural ordering. The\n * method computes the intersection and invokes `onAdd` for every element that\n * is in `after` but not `before`. `onRemove` is invoked for every element in\n * `before` but missing from `after`.\n *\n * The method creates a copy of both `before` and `after` and runs in O(n log\n * n), where n is the size of the two lists.\n *\n * @param before - The elements that exist in the original set.\n * @param after - The elements to diff against the original set.\n * @param comparator - The comparator for the elements in before and after.\n * @param onAdd - A function to invoke for every element that is part of `\n * after` but not `before`.\n * @param onRemove - A function to invoke for every element that is part of\n * `before` but not `after`.\n */\nexport function diffSortedSets<T>(\n before: SortedSet<T>,\n after: SortedSet<T>,\n comparator: (l: T, r: T) => number,\n onAdd: (entry: T) => void,\n onRemove: (entry: T) => void\n): void {\n const beforeIt = before.getIterator();\n const afterIt = after.getIterator();\n\n let beforeValue = advanceIterator(beforeIt);\n let afterValue = advanceIterator(afterIt);\n\n // Walk through the two sets at the same time, using the ordering defined by\n // `comparator`.\n while (beforeValue || afterValue) {\n let added = false;\n let removed = false;\n\n if (beforeValue && afterValue) {\n const cmp = comparator(beforeValue, afterValue);\n if (cmp < 0) {\n // The element was removed if the next element in our ordered\n // walkthrough is only in `before`.\n removed = true;\n } else if (cmp > 0) {\n // The element was added if the next element in our ordered walkthrough\n // is only in `after`.\n added = true;\n }\n } else if (beforeValue != null) {\n removed = true;\n } else {\n added = true;\n }\n\n if (added) {\n onAdd(afterValue!);\n afterValue = advanceIterator(afterIt);\n } else if (removed) {\n onRemove(beforeValue!);\n beforeValue = advanceIterator(beforeIt);\n } else {\n beforeValue = advanceIterator(beforeIt);\n afterValue = advanceIterator(afterIt);\n }\n }\n}\n\n/**\n * Returns the next element from the iterator or `undefined` if none available.\n */\nfunction advanceIterator<T>(it: SortedSetIterator<T>): T | undefined {\n return it.hasNext() ? it.getNext() : undefined;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { debugAssert } from '../util/assert';\nimport { arrayEquals } from '../util/misc';\nimport { SortedSet } from '../util/sorted_set';\n\nimport { FieldPath } from './path';\n\n/**\n * Provides a set of fields that can be used to partially patch a document.\n * FieldMask is used in conjunction with ObjectValue.\n * Examples:\n * foo - Overwrites foo entirely with the provided value. If foo is not\n * present in the companion ObjectValue, the field is deleted.\n * foo.bar - Overwrites only the field bar of the object foo.\n * If foo is not an object, foo is replaced with an object\n * containing foo\n */\nexport class FieldMask {\n constructor(readonly fields: FieldPath[]) {\n // TODO(dimond): validation of FieldMask\n // Sort the field mask to support `FieldMask.isEqual()` and assert below.\n fields.sort(FieldPath.comparator);\n debugAssert(\n !fields.some((v, i) => i !== 0 && v.isEqual(fields[i - 1])),\n 'FieldMask contains field that is not unique: ' +\n fields.find((v, i) => i !== 0 && v.isEqual(fields[i - 1]))!\n );\n }\n\n static empty(): FieldMask {\n return new FieldMask([]);\n }\n\n /**\n * Returns a new FieldMask object that is the result of adding all the given\n * fields paths to this field mask.\n */\n unionWith(extraFields: FieldPath[]): FieldMask {\n let mergedMaskSet = new SortedSet<FieldPath>(FieldPath.comparator);\n for (const fieldPath of this.fields) {\n mergedMaskSet = mergedMaskSet.add(fieldPath);\n }\n for (const fieldPath of extraFields) {\n mergedMaskSet = mergedMaskSet.add(fieldPath);\n }\n return new FieldMask(mergedMaskSet.toArray());\n }\n\n /**\n * Verifies that `fieldPath` is included by at least one field in this field\n * mask.\n *\n * This is an O(n) operation, where `n` is the size of the field mask.\n */\n covers(fieldPath: FieldPath): boolean {\n for (const fieldMaskPath of this.fields) {\n if (fieldMaskPath.isPrefixOf(fieldPath)) {\n return true;\n }\n }\n return false;\n }\n\n isEqual(other: FieldMask): boolean {\n return arrayEquals(this.fields, other.fields, (l, r) => l.isEqual(r));\n }\n}\n","/**\n * @license\n * Copyright 2023 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * An error encountered while decoding base64 string.\n */\nexport class Base64DecodeError extends Error {\n readonly name = 'Base64DecodeError';\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { decodeBase64, encodeBase64 } from '../platform/base64';\n\nimport { primitiveComparator } from './misc';\n\n/**\n * Immutable class that represents a \"proto\" byte string.\n *\n * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when\n * sent on the wire. This class abstracts away this differentiation by holding\n * the proto byte string in a common class that must be converted into a string\n * before being sent as a proto.\n * @internal\n */\nexport class ByteString {\n static readonly EMPTY_BYTE_STRING = new ByteString('');\n\n private constructor(private readonly binaryString: string) {}\n\n static fromBase64String(base64: string): ByteString {\n const binaryString = decodeBase64(base64);\n return new ByteString(binaryString);\n }\n\n static fromUint8Array(array: Uint8Array): ByteString {\n // TODO(indexing); Remove the copy of the byte string here as this method\n // is frequently called during indexing.\n const binaryString = binaryStringFromUint8Array(array);\n return new ByteString(binaryString);\n }\n\n [Symbol.iterator](): Iterator<number> {\n let i = 0;\n return {\n next: () => {\n if (i < this.binaryString.length) {\n return { value: this.binaryString.charCodeAt(i++), done: false };\n } else {\n return { value: undefined, done: true };\n }\n }\n };\n }\n\n toBase64(): string {\n return encodeBase64(this.binaryString);\n }\n\n toUint8Array(): Uint8Array {\n return uint8ArrayFromBinaryString(this.binaryString);\n }\n\n approximateByteSize(): number {\n return this.binaryString.length * 2;\n }\n\n compareTo(other: ByteString): number {\n return primitiveComparator(this.binaryString, other.binaryString);\n }\n\n isEqual(other: ByteString): boolean {\n return this.binaryString === other.binaryString;\n }\n}\n\n/**\n * Helper function to convert an Uint8array to a binary string.\n */\nexport function binaryStringFromUint8Array(array: Uint8Array): string {\n let binaryString = '';\n for (let i = 0; i < array.length; ++i) {\n binaryString += String.fromCharCode(array[i]);\n }\n return binaryString;\n}\n\n/**\n * Helper function to convert a binary string to an Uint8Array.\n */\nexport function uint8ArrayFromBinaryString(binaryString: string): Uint8Array {\n const buffer = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n buffer[i] = binaryString.charCodeAt(i);\n }\n return buffer;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Base64DecodeError } from '../../util/base64_decode_error';\n\n/** Converts a Base64 encoded string to a binary string. */\nexport function decodeBase64(encoded: string): string {\n try {\n return atob(encoded);\n } catch (e) {\n // Check that `DOMException` is defined before using it to avoid\n // \"ReferenceError: Property 'DOMException' doesn't exist\" in react-native.\n // (https://github.com/firebase/firebase-js-sdk/issues/7115)\n if (typeof DOMException !== 'undefined' && e instanceof DOMException) {\n throw new Base64DecodeError('Invalid base64 string: ' + e);\n } else {\n throw e;\n }\n }\n}\n\n/** Converts a binary string to a Base64 encoded string. */\nexport function encodeBase64(raw: string): string {\n return btoa(raw);\n}\n\n/** True if and only if the Base64 conversion functions are available. */\nexport function isBase64Available(): boolean {\n return typeof atob !== 'undefined';\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../protos/firestore_proto_api';\nimport { hardAssert } from '../util/assert';\nimport { ByteString } from '../util/byte_string';\n\n// A RegExp matching ISO 8601 UTC timestamps with optional fraction.\nconst ISO_TIMESTAMP_REG_EXP = new RegExp(\n /^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(?:\\.(\\d+))?Z$/\n);\n\n/**\n * Converts the possible Proto values for a timestamp value into a \"seconds and\n * nanos\" representation.\n */\nexport function normalizeTimestamp(date: Timestamp): {\n seconds: number;\n nanos: number;\n} {\n hardAssert(!!date, 0x986a, 'Cannot normalize null or undefined timestamp.');\n\n // The json interface (for the browser) will return an iso timestamp string,\n // while the proto js library (for node) will return a\n // google.protobuf.Timestamp instance.\n if (typeof date === 'string') {\n // The date string can have higher precision (nanos) than the Date class\n // (millis), so we do some custom parsing here.\n\n // Parse the nanos right out of the string.\n let nanos = 0;\n const fraction = ISO_TIMESTAMP_REG_EXP.exec(date);\n hardAssert(!!fraction, 0xb5de, 'invalid timestamp', {\n timestamp: date\n });\n if (fraction[1]) {\n // Pad the fraction out to 9 digits (nanos).\n let nanoStr = fraction[1];\n nanoStr = (nanoStr + '000000000').substr(0, 9);\n nanos = Number(nanoStr);\n }\n\n // Parse the date to get the seconds.\n const parsedDate = new Date(date);\n const seconds = Math.floor(parsedDate.getTime() / 1000);\n\n return { seconds, nanos };\n } else {\n // TODO(b/37282237): Use strings for Proto3 timestamps\n // assert(!this.options.useProto3Json,\n // 'The timestamp instance format requires Proto JS.');\n const seconds = normalizeNumber(date.seconds);\n const nanos = normalizeNumber(date.nanos);\n return { seconds, nanos };\n }\n}\n\n/**\n * Converts the possible Proto types for numbers into a JavaScript number.\n * Returns 0 if the value is not numeric.\n */\nexport function normalizeNumber(value: number | string | undefined): number {\n // TODO(bjornick): Handle int64 greater than 53 bits.\n if (typeof value === 'number') {\n return value;\n } else if (typeof value === 'string') {\n return Number(value);\n } else {\n return 0;\n }\n}\n\n/** Converts the possible Proto types for Blobs into a ByteString. */\nexport function normalizeByteString(blob: string | Uint8Array): ByteString {\n if (typeof blob === 'string') {\n return ByteString.fromBase64String(blob);\n } else {\n return ByteString.fromUint8Array(blob);\n }\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../lite-api/timestamp';\nimport {\n Value as ProtoValue,\n MapValue as ProtoMapValue\n} from '../protos/firestore_proto_api';\n\nimport { normalizeTimestamp } from './normalize';\n\n/**\n * Represents a locally-applied ServerTimestamp.\n *\n * Server Timestamps are backed by MapValues that contain an internal field\n * `__type__` with a value of `server_timestamp`. The previous value and local\n * write time are stored in its `__previous_value__` and `__local_write_time__`\n * fields respectively.\n *\n * Notes:\n * - ServerTimestampValue instances are created as the result of applying a\n * transform. They can only exist in the local view of a document. Therefore\n * they do not need to be parsed or serialized.\n * - When evaluated locally (e.g. for snapshot.data()), they by default\n * evaluate to `null`. This behavior can be configured by passing custom\n * FieldValueOptions to value().\n * - With respect to other ServerTimestampValues, they sort by their\n * localWriteTime.\n */\n\nconst SERVER_TIMESTAMP_SENTINEL = 'server_timestamp';\nconst TYPE_KEY = '__type__';\nconst PREVIOUS_VALUE_KEY = '__previous_value__';\nconst LOCAL_WRITE_TIME_KEY = '__local_write_time__';\n\nexport function isServerTimestamp(value: ProtoValue | null): boolean {\n const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue;\n return type === SERVER_TIMESTAMP_SENTINEL;\n}\n\n/**\n * Creates a new ServerTimestamp proto value (using the internal format).\n */\nexport function serverTimestamp(\n localWriteTime: Timestamp,\n previousValue: ProtoValue | null\n): ProtoValue {\n const mapValue: ProtoMapValue = {\n fields: {\n [TYPE_KEY]: {\n stringValue: SERVER_TIMESTAMP_SENTINEL\n },\n [LOCAL_WRITE_TIME_KEY]: {\n timestampValue: {\n seconds: localWriteTime.seconds,\n nanos: localWriteTime.nanoseconds\n }\n }\n }\n };\n\n // We should avoid storing deeply nested server timestamp map values\n // because we never use the intermediate \"previous values\".\n // For example:\n // previous: 42L, add: t1, result: t1 -> 42L\n // previous: t1, add: t2, result: t2 -> 42L (NOT t2 -> t1 -> 42L)\n // previous: t2, add: t3, result: t3 -> 42L (NOT t3 -> t2 -> t1 -> 42L)\n // `getPreviousValue` recursively traverses server timestamps to find the\n // least recent Value.\n if (previousValue && isServerTimestamp(previousValue)) {\n previousValue = getPreviousValue(previousValue);\n }\n if (previousValue) {\n mapValue.fields![PREVIOUS_VALUE_KEY] = previousValue;\n }\n\n return { mapValue };\n}\n\n/**\n * Returns the value of the field before this ServerTimestamp was set.\n *\n * Preserving the previous values allows the user to display the last resoled\n * value until the backend responds with the timestamp.\n */\nexport function getPreviousValue(value: ProtoValue): ProtoValue | null {\n const previousValue = value.mapValue!.fields![PREVIOUS_VALUE_KEY];\n\n if (isServerTimestamp(previousValue)) {\n return getPreviousValue(previousValue);\n }\n return previousValue;\n}\n\n/**\n * Returns the local time at which this timestamp was first set.\n */\nexport function getLocalWriteTime(value: ProtoValue): Timestamp {\n const localWriteTime = normalizeTimestamp(\n value.mapValue!.fields![LOCAL_WRITE_TIME_KEY].timestampValue!\n );\n return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);\n}\n","import { FirebaseApp } from '@firebase/app';\n\nimport { ExperimentalLongPollingOptions } from '../api/long_polling_options';\nimport { Code, FirestoreError } from '../util/error';\n\n/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport class DatabaseInfo {\n /**\n * Constructs a DatabaseInfo using the provided host, databaseId and\n * persistenceKey.\n *\n * @param databaseId - The database to use.\n * @param appId - The Firebase App Id.\n * @param persistenceKey - A unique identifier for this Firestore's local\n * storage (used in conjunction with the databaseId).\n * @param host - The Firestore backend host to connect to.\n * @param ssl - Whether to use SSL when connecting.\n * @param forceLongPolling - Whether to use the forceLongPolling option\n * when using WebChannel as the network transport.\n * @param autoDetectLongPolling - Whether to use the detectBufferingProxy\n * option when using WebChannel as the network transport.\n * @param longPollingOptions Options that configure long-polling.\n * @param useFetchStreams Whether to use the Fetch API instead of\n * XMLHTTPRequest\n */\n constructor(\n readonly databaseId: DatabaseId,\n readonly appId: string,\n readonly persistenceKey: string,\n readonly host: string,\n readonly ssl: boolean,\n readonly forceLongPolling: boolean,\n readonly autoDetectLongPolling: boolean,\n readonly longPollingOptions: ExperimentalLongPollingOptions,\n readonly useFetchStreams: boolean,\n readonly isUsingEmulator: boolean\n ) {}\n}\n\n/** The default database name for a project. */\nexport const DEFAULT_DATABASE_NAME = '(default)';\n\n/**\n * Represents the database ID a Firestore client is associated with.\n * @internal\n */\nexport class DatabaseId {\n readonly database: string;\n constructor(readonly projectId: string, database?: string) {\n this.database = database ? database : DEFAULT_DATABASE_NAME;\n }\n\n static empty(): DatabaseId {\n return new DatabaseId('', '');\n }\n\n get isDefaultDatabase(): boolean {\n return this.database === DEFAULT_DATABASE_NAME;\n }\n\n isEqual(other: {}): boolean {\n return (\n other instanceof DatabaseId &&\n other.projectId === this.projectId &&\n other.database === this.database\n );\n }\n}\n\nexport function databaseIdFromApp(\n app: FirebaseApp,\n database?: string\n): DatabaseId {\n if (!Object.prototype.hasOwnProperty.apply(app.options, ['projectId'])) {\n throw new FirestoreError(\n Code.INVALID_ARGUMENT,\n '\"projectId\" not provided in firebase.initializeApp.'\n );\n }\n\n return new DatabaseId(app.options.projectId!, database);\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DatabaseId } from '../core/database_info';\nimport {\n ArrayValue,\n LatLng,\n MapValue,\n Timestamp,\n Value as ProtoValue,\n Value\n} from '../protos/firestore_proto_api';\nimport { fail } from '../util/assert';\nimport {\n arrayEquals,\n compareUtf8Strings,\n primitiveComparator\n} from '../util/misc';\nimport { forEach, objectSize } from '../util/obj';\nimport { isNegativeZero } from '../util/types';\n\nimport { DocumentKey } from './document_key';\nimport {\n normalizeByteString,\n normalizeNumber,\n normalizeTimestamp\n} from './normalize';\nimport {\n getLocalWriteTime,\n getPreviousValue,\n isServerTimestamp\n} from './server_timestamps';\nimport { TypeOrder } from './type_order';\n\nexport const TYPE_KEY = '__type__';\nconst MAX_VALUE_TYPE = '__max__';\nexport const MAX_VALUE: Value = {\n mapValue: {\n fields: {\n '__type__': { stringValue: MAX_VALUE_TYPE }\n }\n }\n};\n\nexport const VECTOR_VALUE_SENTINEL = '__vector__';\nexport const VECTOR_MAP_VECTORS_KEY = 'value';\n\nexport const MIN_VALUE: Value = {\n nullValue: 'NULL_VALUE'\n};\n\n/** Extracts the backend's type order for the provided value. */\nexport function typeOrder(value: Value): TypeOrder {\n if ('nullValue' in value) {\n return TypeOrder.NullValue;\n } else if ('booleanValue' in value) {\n return TypeOrder.BooleanValue;\n } else if ('integerValue' in value || 'doubleValue' in value) {\n return TypeOrder.NumberValue;\n } else if ('timestampValue' in value) {\n return TypeOrder.TimestampValue;\n } else if ('stringValue' in value) {\n return TypeOrder.StringValue;\n } else if ('bytesValue' in value) {\n return TypeOrder.BlobValue;\n } else if ('referenceValue' in value) {\n return TypeOrder.RefValue;\n } else if ('geoPointValue' in value) {\n return TypeOrder.GeoPointValue;\n } else if ('arrayValue' in value) {\n return TypeOrder.ArrayValue;\n } else if ('mapValue' in value) {\n if (isServerTimestamp(value)) {\n return TypeOrder.ServerTimestampValue;\n } else if (isMaxValue(value)) {\n return TypeOrder.MaxValue;\n } else if (isVectorValue(value)) {\n return TypeOrder.VectorValue;\n }\n return TypeOrder.ObjectValue;\n } else {\n return fail(0x6e87, 'Invalid value type', { value });\n }\n}\n\n/** Tests `left` and `right` for equality based on the backend semantics. */\nexport function valueEquals(left: Value, right: Value): boolean {\n if (left === right) {\n return true;\n }\n\n const leftType = typeOrder(left);\n const rightType = typeOrder(right);\n if (leftType !== rightType) {\n return false;\n }\n\n switch (leftType) {\n case TypeOrder.NullValue:\n return true;\n case TypeOrder.BooleanValue:\n return left.booleanValue === right.booleanValue;\n case TypeOrder.ServerTimestampValue:\n return getLocalWriteTime(left).isEqual(getLocalWriteTime(right));\n case TypeOrder.TimestampValue:\n return timestampEquals(left, right);\n case TypeOrder.StringValue:\n return left.stringValue === right.stringValue;\n case TypeOrder.BlobValue:\n return blobEquals(left, right);\n case TypeOrder.RefValue:\n return left.referenceValue === right.referenceValue;\n case TypeOrder.GeoPointValue:\n return geoPointEquals(left, right);\n case TypeOrder.NumberValue:\n return numberEquals(left, right);\n case TypeOrder.ArrayValue:\n return arrayEquals(\n left.arrayValue!.values || [],\n right.arrayValue!.values || [],\n valueEquals\n );\n case TypeOrder.VectorValue:\n case TypeOrder.ObjectValue:\n return objectEquals(left, right);\n case TypeOrder.MaxValue:\n return true;\n default:\n return fail(0xcbf8, 'Unexpected value type', { left });\n }\n}\n\nfunction timestampEquals(left: Value, right: Value): boolean {\n if (\n typeof left.timestampValue === 'string' &&\n typeof right.timestampValue === 'string' &&\n left.timestampValue.length === right.timestampValue.length\n ) {\n // Use string equality for ISO 8601 timestamps\n return left.timestampValue === right.timestampValue;\n }\n\n const leftTimestamp = normalizeTimestamp(left.timestampValue!);\n const rightTimestamp = normalizeTimestamp(right.timestampValue!);\n return (\n leftTimestamp.seconds === rightTimestamp.seconds &&\n leftTimestamp.nanos === rightTimestamp.nanos\n );\n}\n\nfunction geoPointEquals(left: Value, right: Value): boolean {\n return (\n normalizeNumber(left.geoPointValue!.latitude) ===\n normalizeNumber(right.geoPointValue!.latitude) &&\n normalizeNumber(left.geoPointValue!.longitude) ===\n normalizeNumber(right.geoPointValue!.longitude)\n );\n}\n\nfunction blobEquals(left: Value, right: Value): boolean {\n return normalizeByteString(left.bytesValue!).isEqual(\n normalizeByteString(right.bytesValue!)\n );\n}\n\nexport function numberEquals(left: Value, right: Value): boolean {\n if ('integerValue' in left && 'integerValue' in right) {\n return (\n normalizeNumber(left.integerValue) === normalizeNumber(right.integerValue)\n );\n } else if ('doubleValue' in left && 'doubleValue' in right) {\n const n1 = normalizeNumber(left.doubleValue!);\n const n2 = normalizeNumber(right.doubleValue!);\n\n if (n1 === n2) {\n return isNegativeZero(n1) === isNegativeZero(n2);\n } else {\n return isNaN(n1) && isNaN(n2);\n }\n }\n\n return false;\n}\n\nfunction objectEquals(left: Value, right: Value): boolean {\n const leftMap = left.mapValue!.fields || {};\n const rightMap = right.mapValue!.fields || {};\n\n if (objectSize(leftMap) !== objectSize(rightMap)) {\n return false;\n }\n\n for (const key in leftMap) {\n if (leftMap.hasOwnProperty(key)) {\n if (\n rightMap[key] === undefined ||\n !valueEquals(leftMap[key], rightMap[key])\n ) {\n return false;\n }\n }\n }\n return true;\n}\n\n/** Returns true if the ArrayValue contains the specified element. */\nexport function arrayValueContains(\n haystack: ArrayValue,\n needle: Value\n): boolean {\n return (\n (haystack.values || []).find(v => valueEquals(v, needle)) !== undefined\n );\n}\n\nexport function valueCompare(left: Value, right: Value): number {\n if (left === right) {\n return 0;\n }\n\n const leftType = typeOrder(left);\n const rightType = typeOrder(right);\n\n if (leftType !== rightType) {\n return primitiveComparator(leftType, rightType);\n }\n\n switch (leftType) {\n case TypeOrder.NullValue:\n case TypeOrder.MaxValue:\n return 0;\n case TypeOrder.BooleanValue:\n return primitiveComparator(left.booleanValue!, right.booleanValue!);\n case TypeOrder.NumberValue:\n return compareNumbers(left, right);\n case TypeOrder.TimestampValue:\n return compareTimestamps(left.timestampValue!, right.timestampValue!);\n case TypeOrder.ServerTimestampValue:\n return compareTimestamps(\n getLocalWriteTime(left),\n getLocalWriteTime(right)\n );\n case TypeOrder.StringValue:\n return compareUtf8Strings(left.stringValue!, right.stringValue!);\n case TypeOrder.BlobValue:\n return compareBlobs(left.bytesValue!, right.bytesValue!);\n case TypeOrder.RefValue:\n return compareReferences(left.referenceValue!, right.referenceValue!);\n case TypeOrder.GeoPointValue:\n return compareGeoPoints(left.geoPointValue!, right.geoPointValue!);\n case TypeOrder.ArrayValue:\n return compareArrays(left.arrayValue!, right.arrayValue!);\n case TypeOrder.VectorValue:\n return compareVectors(left.mapValue!, right.mapValue!);\n case TypeOrder.ObjectValue:\n return compareMaps(left.mapValue!, right.mapValue!);\n default:\n throw fail(0x5ae0, 'Invalid value type', { leftType });\n }\n}\n\nfunction compareNumbers(left: Value, right: Value): number {\n const leftNumber = normalizeNumber(left.integerValue || left.doubleValue);\n const rightNumber = normalizeNumber(right.integerValue || right.doubleValue);\n\n if (leftNumber < rightNumber) {\n return -1;\n } else if (leftNumber > rightNumber) {\n return 1;\n } else if (leftNumber === rightNumber) {\n return 0;\n } else {\n // one or both are NaN.\n if (isNaN(leftNumber)) {\n return isNaN(rightNumber) ? 0 : -1;\n } else {\n return 1;\n }\n }\n}\n\nfunction compareTimestamps(left: Timestamp, right: Timestamp): number {\n if (\n typeof left === 'string' &&\n typeof right === 'string' &&\n left.length === right.length\n ) {\n return primitiveComparator(left, right);\n }\n\n const leftTimestamp = normalizeTimestamp(left);\n const rightTimestamp = normalizeTimestamp(right);\n\n const comparison = primitiveComparator(\n leftTimestamp.seconds,\n rightTimestamp.seconds\n );\n if (comparison !== 0) {\n return comparison;\n }\n return primitiveComparator(leftTimestamp.nanos, rightTimestamp.nanos);\n}\n\nfunction compareReferences(leftPath: string, rightPath: string): number {\n const leftSegments = leftPath.split('/');\n const rightSegments = rightPath.split('/');\n for (let i = 0; i < leftSegments.length && i < rightSegments.length; i++) {\n const comparison = primitiveComparator(leftSegments[i], rightSegments[i]);\n if (comparison !== 0) {\n return comparison;\n }\n }\n return primitiveComparator(leftSegments.length, rightSegments.length);\n}\n\nfunction compareGeoPoints(left: LatLng, right: LatLng): number {\n const comparison = primitiveComparator(\n normalizeNumber(left.latitude),\n normalizeNumber(right.latitude)\n );\n if (comparison !== 0) {\n return comparison;\n }\n return primitiveComparator(\n normalizeNumber(left.longitude),\n normalizeNumber(right.longitude)\n );\n}\n\nfunction compareBlobs(\n left: string | Uint8Array,\n right: string | Uint8Array\n): number {\n const leftBytes = normalizeByteString(left);\n const rightBytes = normalizeByteString(right);\n return leftBytes.compareTo(rightBytes);\n}\n\nfunction compareArrays(left: ArrayValue, right: ArrayValue): number {\n const leftArray = left.values || [];\n const rightArray = right.values || [];\n\n for (let i = 0; i < leftArray.length && i < rightArray.length; ++i) {\n const compare = valueCompare(leftArray[i], rightArray[i]);\n if (compare) {\n return compare;\n }\n }\n return primitiveComparator(leftArray.length, rightArray.length);\n}\n\nfunction compareVectors(left: MapValue, right: MapValue): number {\n const leftMap = left.fields || {};\n const rightMap = right.fields || {};\n\n // The vector is a map, but only vector value is compared.\n const leftArrayValue = leftMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue;\n const rightArrayValue = rightMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue;\n\n const lengthCompare = primitiveComparator(\n leftArrayValue?.values?.length || 0,\n rightArrayValue?.values?.length || 0\n );\n if (lengthCompare !== 0) {\n return lengthCompare;\n }\n\n return compareArrays(leftArrayValue!, rightArrayValue!);\n}\n\nfunction compareMaps(left: MapValue, right: MapValue): number {\n if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) {\n return 0;\n } else if (left === MAX_VALUE.mapValue) {\n return 1;\n } else if (right === MAX_VALUE.mapValue) {\n return -1;\n }\n\n const leftMap = left.fields || {};\n const leftKeys = Object.keys(leftMap);\n const rightMap = right.fields || {};\n const rightKeys = Object.keys(rightMap);\n\n // Even though MapValues are likely sorted correctly based on their insertion\n // order (e.g. when received from the backend), local modifications can bring\n // elements out of order. We need to re-sort the elements to ensure that\n // canonical IDs are independent of insertion order.\n leftKeys.sort();\n rightKeys.sort();\n\n for (let i = 0; i < leftKeys.length && i < rightKeys.length; ++i) {\n const keyCompare = compareUtf8Strings(leftKeys[i], rightKeys[i]);\n if (keyCompare !== 0) {\n return keyCompare;\n }\n const compare = valueCompare(leftMap[leftKeys[i]], rightMap[rightKeys[i]]);\n if (compare !== 0) {\n return compare;\n }\n }\n\n return primitiveComparator(leftKeys.length, rightKeys.length);\n}\n\n/**\n * Generates the canonical ID for the provided field value (as used in Target\n * serialization).\n */\nexport function canonicalId(value: Value): string {\n return canonifyValue(value);\n}\n\nfunction canonifyValue(value: Value): string {\n if ('nullValue' in value) {\n return 'null';\n } else if ('booleanValue' in value) {\n return '' + value.booleanValue!;\n } else if ('integerValue' in value) {\n return '' + value.integerValue!;\n } else if ('doubleValue' in value) {\n return '' + value.doubleValue!;\n } else if ('timestampValue' in value) {\n return canonifyTimestamp(value.timestampValue!);\n } else if ('stringValue' in value) {\n return value.stringValue!;\n } else if ('bytesValue' in value) {\n return canonifyByteString(value.bytesValue!);\n } else if ('referenceValue' in value) {\n return canonifyReference(value.referenceValue!);\n } else if ('geoPointValue' in value) {\n return canonifyGeoPoint(value.geoPointValue!);\n } else if ('arrayValue' in value) {\n return canonifyArray(value.arrayValue!);\n } else if ('mapValue' in value) {\n return canonifyMap(value.mapValue!);\n } else {\n return fail(0xee4d, 'Invalid value type', { value });\n }\n}\n\nfunction canonifyByteString(byteString: string | Uint8Array): string {\n return normalizeByteString(byteString).toBase64();\n}\n\nfunction canonifyTimestamp(timestamp: Timestamp): string {\n const normalizedTimestamp = normalizeTimestamp(timestamp);\n return `time(${normalizedTimestamp.seconds},${normalizedTimestamp.nanos})`;\n}\n\nfunction canonifyGeoPoint(geoPoint: LatLng): string {\n return `geo(${geoPoint.latitude},${geoPoint.longitude})`;\n}\n\nfunction canonifyReference(referenceValue: string): string {\n return DocumentKey.fromName(referenceValue).toString();\n}\n\nfunction canonifyMap(mapValue: MapValue): string {\n // Iteration order in JavaScript is not guaranteed. To ensure that we generate\n // matching canonical IDs for identical maps, we need to sort the keys.\n const sortedKeys = Object.keys(mapValue.fields || {}).sort();\n\n let result = '{';\n let first = true;\n for (const key of sortedKeys) {\n if (!first) {\n result += ',';\n } else {\n first = false;\n }\n result += `${key}:${canonifyValue(mapValue.fields![key])}`;\n }\n return result + '}';\n}\n\nfunction canonifyArray(arrayValue: ArrayValue): string {\n let result = '[';\n let first = true;\n for (const value of arrayValue.values || []) {\n if (!first) {\n result += ',';\n } else {\n first = false;\n }\n result += canonifyValue(value);\n }\n return result + ']';\n}\n\n/**\n * Returns an approximate (and wildly inaccurate) in-memory size for the field\n * value.\n *\n * The memory size takes into account only the actual user data as it resides\n * in memory and ignores object overhead.\n */\nexport function estimateByteSize(value: Value): number {\n switch (typeOrder(value)) {\n case TypeOrder.NullValue:\n return 4;\n case TypeOrder.BooleanValue:\n return 4;\n case TypeOrder.NumberValue:\n return 8;\n case TypeOrder.TimestampValue:\n // Timestamps are made up of two distinct numbers (seconds + nanoseconds)\n return 16;\n case TypeOrder.ServerTimestampValue:\n const previousValue = getPreviousValue(value);\n return previousValue ? 16 + estimateByteSize(previousValue) : 16;\n case TypeOrder.StringValue:\n // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures:\n // \"JavaScript's String type is [...] a set of elements of 16-bit unsigned\n // integer values\"\n return value.stringValue!.length * 2;\n case TypeOrder.BlobValue:\n return normalizeByteString(value.bytesValue!).approximateByteSize();\n case TypeOrder.RefValue:\n return value.referenceValue!.length;\n case TypeOrder.GeoPointValue:\n // GeoPoints are made up of two distinct numbers (latitude + longitude)\n return 16;\n case TypeOrder.ArrayValue:\n return estimateArrayByteSize(value.arrayValue!);\n case TypeOrder.VectorValue:\n case TypeOrder.ObjectValue:\n return estimateMapByteSize(value.mapValue!);\n default:\n throw fail(0x34ae, 'Invalid value type', { value });\n }\n}\n\nfunction estimateMapByteSize(mapValue: MapValue): number {\n let size = 0;\n forEach(mapValue.fields, (key, val) => {\n size += key.length + estimateByteSize(val);\n });\n return size;\n}\n\nfunction estimateArrayByteSize(arrayValue: ArrayValue): number {\n return (arrayValue.values || []).reduce(\n (previousSize, value) => previousSize + estimateByteSize(value),\n 0\n );\n}\n\n/** Returns a reference value for the provided database and key. */\nexport function refValue(databaseId: DatabaseId, key: DocumentKey): Value {\n return {\n referenceValue: `projects/${databaseId.projectId}/databases/${\n databaseId.database\n }/documents/${key.path.canonicalString()}`\n };\n}\n\n/** Returns true if `value` is an IntegerValue . */\nexport function isInteger(\n value?: Value | null\n): value is { integerValue: string | number } {\n return !!value && 'integerValue' in value;\n}\n\n/** Returns true if `value` is a DoubleValue. */\nexport function isDouble(\n value?: Value | null\n): value is { doubleValue: string | number } {\n return !!value && 'doubleValue' in value;\n}\n\n/** Returns true if `value` is either an IntegerValue or a DoubleValue. */\nexport function isNumber(value?: Value | null): boolean {\n return isInteger(value) || isDouble(value);\n}\n\n/** Returns true if `value` is an ArrayValue. */\nexport function isArray(\n value?: Value | null\n): value is { arrayValue: ArrayValue } {\n return !!value && 'arrayValue' in value;\n}\n\n/** Returns true if `value` is a ReferenceValue. */\nexport function isReferenceValue(\n value?: Value | null\n): value is { referenceValue: string } {\n return !!value && 'referenceValue' in value;\n}\n\n/** Returns true if `value` is a NullValue. */\nexport function isNullValue(\n value?: Value | null\n): value is { nullValue: 'NULL_VALUE' } {\n return !!value && 'nullValue' in value;\n}\n\n/** Returns true if `value` is NaN. */\nexport function isNanValue(\n value?: Value | null\n): value is { doubleValue: 'NaN' | number } {\n return !!value && 'doubleValue' in value && isNaN(Number(value.doubleValue));\n}\n\n/** Returns true if `value` is a MapValue. */\nexport function isMapValue(\n value?: Value | null\n): value is { mapValue: MapValue } {\n return !!value && 'mapValue' in value;\n}\n\n/** Returns true if `value` is a VetorValue. */\nexport function isVectorValue(value: ProtoValue | null): boolean {\n const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue;\n return type === VECTOR_VALUE_SENTINEL;\n}\n\n/** Creates a deep copy of `source`. */\nexport function deepClone(source: Value): Value {\n if (source.geoPointValue) {\n return { geoPointValue: { ...source.geoPointValue } };\n } else if (\n source.timestampValue &&\n typeof source.timestampValue === 'object'\n ) {\n return { timestampValue: { ...source.timestampValue } };\n } else if (source.mapValue) {\n const target: Value = { mapValue: { fields: {} } };\n forEach(\n source.mapValue.fields,\n (key, val) => (target.mapValue!.fields![key] = deepClone(val))\n );\n return target;\n } else if (source.arrayValue) {\n const target: Value = { arrayValue: { values: [] } };\n for (let i = 0; i < (source.arrayValue.values || []).length; ++i) {\n target.arrayValue!.values![i] = deepClone(source.arrayValue.values![i]);\n }\n return target;\n } else {\n return { ...source };\n }\n}\n\n/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */\nexport function isMaxValue(value: Value): boolean {\n return (\n (((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===\n MAX_VALUE_TYPE\n );\n}\n\nexport const MIN_VECTOR_VALUE = {\n mapValue: {\n fields: {\n [TYPE_KEY]: { stringValue: VECTOR_VALUE_SENTINEL },\n [VECTOR_MAP_VECTORS_KEY]: {\n arrayValue: {}\n }\n }\n }\n};\n\n/** Returns the lowest value for the given value type (inclusive). */\nexport function valuesGetLowerBound(value: Value): Value {\n if ('nullValue' in value) {\n return MIN_VALUE;\n } else if ('booleanValue' in value) {\n return { booleanValue: false };\n } else if ('integerValue' in value || 'doubleValue' in value) {\n return { doubleValue: NaN };\n } else if ('timestampValue' in value) {\n return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };\n } else if ('stringValue' in value) {\n return { stringValue: '' };\n } else if ('bytesValue' in value) {\n return { bytesValue: '' };\n } else if ('referenceValue' in value) {\n return refValue(DatabaseId.empty(), DocumentKey.empty());\n } else if ('geoPointValue' in value) {\n return { geoPointValue: { latitude: -90, longitude: -180 } };\n } else if ('arrayValue' in value) {\n return { arrayValue: {} };\n } else if ('mapValue' in value) {\n if (isVectorValue(value)) {\n return MIN_VECTOR_VALUE;\n }\n return { mapValue: {} };\n } else {\n return fail(0x8c66, 'Invalid value type', { value });\n }\n}\n\n/** Returns the largest value for the given value type (exclusive). */\nexport function valuesGetUpperBound(value: Value): Value {\n if ('nullValue' in value) {\n return { booleanValue: false };\n } else if ('booleanValue' in value) {\n return { doubleValue: NaN };\n } else if ('integerValue' in value || 'doubleValue' in value) {\n return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };\n } else if ('timestampValue' in value) {\n return { stringValue: '' };\n } else if ('stringValue' in value) {\n return { bytesValue: '' };\n } else if ('bytesValue' in value) {\n return refValue(DatabaseId.empty(), DocumentKey.empty());\n } else if ('referenceValue' in value) {\n return { geoPointValue: { latitude: -90, longitude: -180 } };\n } else if ('geoPointValue' in value) {\n return { arrayValue: {} };\n } else if ('arrayValue' in value) {\n return MIN_VECTOR_VALUE;\n } else if ('mapValue' in value) {\n if (isVectorValue(value)) {\n return { mapValue: {} };\n }\n return MAX_VALUE;\n } else {\n return fail(0xf207, 'Invalid value type', { value });\n }\n}\n\nexport function lowerBoundCompare(\n left: { value: Value; inclusive: boolean },\n right: { value: Value; inclusive: boolean }\n): number {\n const cmp = valueCompare(left.value, right.value);\n if (cmp !== 0) {\n return cmp;\n }\n\n if (left.inclusive && !right.inclusive) {\n return -1;\n } else if (!left.inclusive && right.inclusive) {\n return 1;\n }\n\n return 0;\n}\n\nexport function upperBoundCompare(\n left: { value: Value; inclusive: boolean },\n right: { value: Value; inclusive: boolean }\n): number {\n const cmp = valueCompare(left.value, right.value);\n if (cmp !== 0) {\n return cmp;\n }\n\n if (left.inclusive && !right.inclusive) {\n return 1;\n } else if (!left.inclusive && right.inclusive) {\n return -1;\n }\n\n return 0;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MapValue as ProtoMapValue,\n Value as ProtoValue\n} from '../protos/firestore_proto_api';\nimport { debugAssert } from '../util/assert';\nimport { forEach } from '../util/obj';\n\nimport { FieldMask } from './field_mask';\nimport { FieldPath } from './path';\nimport { isServerTimestamp } from './server_timestamps';\nimport { deepClone, isMapValue, valueEquals } from './values';\n\nexport interface JsonObject<T> {\n [name: string]: T;\n}\n/**\n * An ObjectValue represents a MapValue in the Firestore Proto and offers the\n * ability to add and remove fields (via the ObjectValueBuilder).\n */\nexport class ObjectValue {\n constructor(readonly value: { mapValue: ProtoMapValue }) {\n debugAssert(\n !isServerTimestamp(value),\n 'ServerTimestamps should be converted to ServerTimestampValue'\n );\n }\n\n static empty(): ObjectValue {\n return new ObjectValue({ mapValue: {} });\n }\n\n /**\n * Returns the value at the given path or null.\n *\n * @param path - the path to search\n * @returns The value at the path or null if the path is not set.\n */\n field(path: FieldPath): ProtoValue | null {\n if (path.isEmpty()) {\n return this.value;\n } else {\n let currentLevel: ProtoValue = this.value;\n for (let i = 0; i < path.length - 1; ++i) {\n currentLevel = (currentLevel.mapValue!.fields || {})[path.get(i)];\n if (!isMapValue(currentLevel)) {\n return null;\n }\n }\n currentLevel = (currentLevel.mapValue!.fields! || {})[path.lastSegment()];\n return currentLevel || null;\n }\n }\n\n /**\n * Sets the field to the provided value.\n *\n * @param path - The field path to set.\n * @param value - The value to set.\n */\n set(path: FieldPath, value: ProtoValue): void {\n debugAssert(\n !path.isEmpty(),\n 'Cannot set field for empty path on ObjectValue'\n );\n const fieldsMap = this.getFieldsMap(path.popLast());\n fieldsMap[path.lastSegment()] = deepClone(value);\n }\n\n /**\n * Sets the provided fields to the provided values.\n *\n * @param data - A map of fields to values (or null for deletes).\n */\n setAll(data: Map<FieldPath, ProtoValue | null>): void {\n let parent = FieldPath.emptyPath();\n\n let upserts: { [key: string]: ProtoValue } = {};\n let deletes: string[] = [];\n\n data.forEach((value, path) => {\n if (!parent.isImmediateParentOf(path)) {\n // Insert the accumulated changes at this parent location\n const fieldsMap = this.getFieldsMap(parent);\n this.applyChanges(fieldsMap, upserts, deletes);\n upserts = {};\n deletes = [];\n parent = path.popLast();\n }\n\n if (value) {\n upserts[path.lastSegment()] = deepClone(value);\n } else {\n deletes.push(path.lastSegment());\n }\n });\n\n const fieldsMap = this.getFieldsMap(parent);\n this.applyChanges(fieldsMap, upserts, deletes);\n }\n\n /**\n * Removes the field at the specified path. If there is no field at the\n * specified path, nothing is changed.\n *\n * @param path - The field path to remove.\n */\n delete(path: FieldPath): void {\n debugAssert(\n !path.isEmpty(),\n 'Cannot delete field for empty path on ObjectValue'\n );\n const nestedValue = this.field(path.popLast());\n if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {\n delete nestedValue.mapValue.fields[path.lastSegment()];\n }\n }\n\n isEqual(other: ObjectValue): boolean {\n return valueEquals(this.value, other.value);\n }\n\n /**\n * Returns the map that contains the leaf element of `path`. If the parent\n * entry does not yet exist, or if it is not a map, a new map will be created.\n */\n private getFieldsMap(path: FieldPath): Record<string, ProtoValue> {\n let current = this.value;\n\n if (!current.mapValue!.fields) {\n current.mapValue = { fields: {} };\n }\n\n for (let i = 0; i < path.length; ++i) {\n let next = current.mapValue!.fields![path.get(i)];\n if (!isMapValue(next) || !next.mapValue.fields) {\n next = { mapValue: { fields: {} } };\n current.mapValue!.fields![path.get(i)] = next;\n }\n current = next as { mapValue: ProtoMapValue };\n }\n\n return current.mapValue!.fields!;\n }\n\n /**\n * Modifies `fieldsMap` by adding, replacing or deleting the specified\n * entries.\n */\n private applyChanges(\n fieldsMap: Record<string, ProtoValue>,\n inserts: { [key: string]: ProtoValue },\n deletes: string[]\n ): void {\n forEach(inserts, (key, val) => (fieldsMap[key] = val));\n for (const field of deletes) {\n delete fieldsMap[field];\n }\n }\n\n clone(): ObjectValue {\n return new ObjectValue(\n deepClone(this.value) as { mapValue: ProtoMapValue }\n );\n }\n}\n\n/**\n * Returns a FieldMask built from all fields in a MapValue.\n */\nexport function extractFieldMask(value: ProtoMapValue): FieldMask {\n const fields: FieldPath[] = [];\n forEach(value!.fields, (key, value) => {\n const currentPath = new FieldPath([key]);\n if (isMapValue(value)) {\n const nestedMask = extractFieldMask(value.mapValue!);\n const nestedFields = nestedMask.fields;\n if (nestedFields.length === 0) {\n // Preserve the empty map by adding it to the FieldMask.\n fields.push(currentPath);\n } else {\n // For nested and non-empty ObjectValues, add the FieldPath of the\n // leaf nodes.\n for (const nestedPath of nestedFields) {\n fields.push(currentPath.child(nestedPath));\n }\n }\n } else {\n // For nested and non-empty ObjectValues, add the FieldPath of the leaf\n // nodes.\n fields.push(currentPath);\n }\n });\n return new FieldMask(fields);\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { debugAssert, fail } from '../util/assert';\n\nimport { DocumentKey } from './document_key';\nimport { ObjectValue } from './object_value';\nimport { FieldPath } from './path';\nimport { valueCompare } from './values';\n\n/**\n * Whether the document represents an existing document, a document that is\n * known to exist or a document whose state or existence is unknown.\n */\nconst enum DocumentType {\n /**\n * Represents the initial state of a MutableDocument when only the document\n * key is known. Invalid documents transition to other states as mutations are\n * applied. If a document remains invalid after applying mutations, it should\n * be discarded.\n *\n * Invalid documents can have neither local nor committed mutations.\n */\n INVALID,\n /**\n * Represents a document in Firestore with a key, version, data and whether\n * the data has local mutations applied to it.\n *\n * Found documents can be synced or have or committed mutations applied.\n */\n FOUND_DOCUMENT,\n /**\n * Represents that no documents exists for the key at the given version.\n *\n * Documents that are deleted based on a local mutation do not raise snapshots\n * with `hasPendingWrites`. As such, deleted documents never have\n * `HAS_LOCAL_MUTATIONS` set. Once a delete is committed, we store them with\n * `HAS_COMMITTED_MUTATIONS` until we received the delete from the Watch\n * stream.\n */\n NO_DOCUMENT,\n /**\n * Represents an existing document whose data is unknown (e.g. a document that\n * was updated without a known base document).\n *\n * An unknown document always has `HAS_COMMITTED_MUTATIONS` set, since unknown\n * documents can only be generated by applying a patch mutation from a write\n * acknowledgement.\n */\n UNKNOWN_DOCUMENT\n}\n\n/** Describes whether a document has latency-compensated edits applied. */\nconst enum DocumentState {\n /** No mutations applied. Document was sent to us by Watch. */\n SYNCED,\n /**\n * Local mutations applied via the mutation queue. Document is potentially\n * inconsistent.\n */\n HAS_LOCAL_MUTATIONS,\n /**\n * Mutations applied based on a write acknowledgment. Document is potentially\n * inconsistent.\n */\n HAS_COMMITTED_MUTATIONS\n}\n\n/**\n * Represents a document in Firestore with a key, version, data and whether the\n * data has local mutations applied to it.\n */\nexport interface Document {\n /** The key for this document */\n readonly key: DocumentKey;\n\n /**\n * The version of this document if it exists or a version at which this\n * document was guaranteed to not exist.\n */\n readonly version: SnapshotVersion;\n\n /**\n * The timestamp at which this document was read from the remote server. Uses\n * `SnapshotVersion.min()` for documents created by the user.\n */\n readonly readTime: SnapshotVersion;\n\n /**\n * The timestamp at which the document was created. This value increases\n * monotonically when a document is deleted then recreated. It can also be\n * compared to `createTime` of other documents and the `readTime` of a query.\n */\n readonly createTime: SnapshotVersion;\n\n /** The underlying data of this document or an empty value if no data exists. */\n readonly data: ObjectValue;\n\n /** Returns whether local mutations were applied via the mutation queue. */\n readonly hasLocalMutations: boolean;\n\n /** Returns whether mutations were applied based on a write acknowledgment. */\n readonly hasCommittedMutations: boolean;\n\n /**\n * Whether this document had a local mutation applied that has not yet been\n * acknowledged by Watch.\n */\n readonly hasPendingWrites: boolean;\n\n /**\n * Returns whether this document is valid (i.e. it is an entry in the\n * RemoteDocumentCache, was created by a mutation or read from the backend).\n */\n isValidDocument(): boolean;\n\n /**\n * Returns whether the document exists and its data is known at the current\n * version.\n */\n isFoundDocument(): boolean;\n\n /**\n * Returns whether the document is known to not exist at the current version.\n */\n isNoDocument(): boolean;\n\n /**\n * Returns whether the document exists and its data is unknown at the current\n * version.\n */\n isUnknownDocument(): boolean;\n\n isEqual(other: Document | null | undefined): boolean;\n\n /** Creates a mutable copy of this document. */\n mutableCopy(): MutableDocument;\n\n toString(): string;\n}\n\n/**\n * Represents a document in Firestore with a key, version, data and whether it\n * has local mutations applied to it.\n *\n * Documents can transition between states via `convertToFoundDocument()`,\n * `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does\n * not transition to one of these states even after all mutations have been\n * applied, `isValidDocument()` returns false and the document should be removed\n * from all views.\n */\nexport class MutableDocument implements Document {\n private constructor(\n readonly key: DocumentKey,\n private documentType: DocumentType,\n public version: SnapshotVersion,\n public readTime: SnapshotVersion,\n public createTime: SnapshotVersion,\n public data: ObjectValue,\n private documentState: DocumentState\n ) {}\n\n /**\n * Creates a document with no known version or data, but which can serve as\n * base document for mutations.\n */\n static newInvalidDocument(documentKey: DocumentKey): MutableDocument {\n return new MutableDocument(\n documentKey,\n DocumentType.INVALID,\n /* version */ SnapshotVersion.min(),\n /* readTime */ SnapshotVersion.min(),\n /* createTime */ SnapshotVersion.min(),\n ObjectValue.empty(),\n DocumentState.SYNCED\n );\n }\n\n /**\n * Creates a new document that is known to exist with the given data at the\n * given version.\n */\n static newFoundDocument(\n documentKey: DocumentKey,\n version: SnapshotVersion,\n createTime: SnapshotVersion,\n value: ObjectValue\n ): MutableDocument {\n return new MutableDocument(\n documentKey,\n DocumentType.FOUND_DOCUMENT,\n /* version */ version,\n /* readTime */ SnapshotVersion.min(),\n /* createTime */ createTime,\n value,\n DocumentState.SYNCED\n );\n }\n\n /** Creates a new document that is known to not exist at the given version. */\n static newNoDocument(\n documentKey: DocumentKey,\n version: SnapshotVersion\n ): MutableDocument {\n return new MutableDocument(\n documentKey,\n DocumentType.NO_DOCUMENT,\n /* version */ version,\n /* readTime */ SnapshotVersion.min(),\n /* createTime */ SnapshotVersion.min(),\n ObjectValue.empty(),\n DocumentState.SYNCED\n );\n }\n\n /**\n * Creates a new document that is known to exist at the given version but\n * whose data is not known (e.g. a document that was updated without a known\n * base document).\n */\n static newUnknownDocument(\n documentKey: DocumentKey,\n version: SnapshotVersion\n ): MutableDocument {\n return new MutableDocument(\n documentKey,\n DocumentType.UNKNOWN_DOCUMENT,\n /* version */ version,\n /* readTime */ SnapshotVersion.min(),\n /* createTime */ SnapshotVersion.min(),\n ObjectValue.empty(),\n DocumentState.HAS_COMMITTED_MUTATIONS\n );\n }\n\n /**\n * Changes the document type to indicate that it exists and that its version\n * and data are known.\n */\n convertToFoundDocument(\n version: SnapshotVersion,\n value: ObjectValue\n ): MutableDocument {\n // If a document is switching state from being an invalid or deleted\n // document to a valid (FOUND_DOCUMENT) document, either due to receiving an\n // update from Watch or due to applying a local set mutation on top\n // of a deleted document, our best guess about its createTime would be the\n // version at which the document transitioned to a FOUND_DOCUMENT.\n if (\n this.createTime.isEqual(SnapshotVersion.min()) &&\n (this.documentType === DocumentType.NO_DOCUMENT ||\n this.documentType === DocumentType.INVALID)\n ) {\n this.createTime = version;\n }\n this.version = version;\n this.documentType = DocumentType.FOUND_DOCUMENT;\n this.data = value;\n this.documentState = DocumentState.SYNCED;\n return this;\n }\n\n /**\n * Changes the document type to indicate that it doesn't exist at the given\n * version.\n */\n convertToNoDocument(version: SnapshotVersion): MutableDocument {\n this.version = version;\n this.documentType = DocumentType.NO_DOCUMENT;\n this.data = ObjectValue.empty();\n this.documentState = DocumentState.SYNCED;\n return this;\n }\n\n /**\n * Changes the document type to indicate that it exists at a given version but\n * that its data is not known (e.g. a document that was updated without a known\n * base document).\n */\n convertToUnknownDocument(version: SnapshotVersion): MutableDocument {\n this.version = version;\n this.documentType = DocumentType.UNKNOWN_DOCUMENT;\n this.data = ObjectValue.empty();\n this.documentState = DocumentState.HAS_COMMITTED_MUTATIONS;\n return this;\n }\n\n setHasCommittedMutations(): MutableDocument {\n debugAssert(\n this.isValidDocument(),\n 'Invalid documents cannot have committed mutations'\n );\n this.documentState = DocumentState.HAS_COMMITTED_MUTATIONS;\n return this;\n }\n\n setHasLocalMutations(): MutableDocument {\n this.documentState = DocumentState.HAS_LOCAL_MUTATIONS;\n this.version = SnapshotVersion.min();\n return this;\n }\n\n setReadTime(readTime: SnapshotVersion): MutableDocument {\n this.readTime = readTime;\n return this;\n }\n\n get hasLocalMutations(): boolean {\n return this.documentState === DocumentState.HAS_LOCAL_MUTATIONS;\n }\n\n get hasCommittedMutations(): boolean {\n return this.documentState === DocumentState.HAS_COMMITTED_MUTATIONS;\n }\n\n get hasPendingWrites(): boolean {\n return this.hasLocalMutations || this.hasCommittedMutations;\n }\n\n isValidDocument(): boolean {\n return this.documentType !== DocumentType.INVALID;\n }\n\n isFoundDocument(): boolean {\n return this.documentType === DocumentType.FOUND_DOCUMENT;\n }\n\n isNoDocument(): boolean {\n return this.documentType === DocumentType.NO_DOCUMENT;\n }\n\n isUnknownDocument(): boolean {\n return this.documentType === DocumentType.UNKNOWN_DOCUMENT;\n }\n\n isEqual(other: Document | null | undefined): boolean {\n return (\n other instanceof MutableDocument &&\n this.key.isEqual(other.key) &&\n this.version.isEqual(other.version) &&\n this.documentType === other.documentType &&\n this.documentState === other.documentState &&\n this.data.isEqual(other.data)\n );\n }\n\n mutableCopy(): MutableDocument {\n return new MutableDocument(\n this.key,\n this.documentType,\n this.version,\n this.readTime,\n this.createTime,\n this.data.clone(),\n this.documentState\n );\n }\n\n toString(): string {\n return (\n `Document(${this.key}, ${this.version}, ${JSON.stringify(\n this.data.value\n )}, ` +\n `{createTime: ${this.createTime}}), ` +\n `{documentType: ${this.documentType}}), ` +\n `{documentState: ${this.documentState}})`\n );\n }\n}\n\n/**\n * Compares the value for field `field` in the provided documents. Throws if\n * the field does not exist in both documents.\n */\nexport function compareDocumentsByField(\n field: FieldPath,\n d1: Document,\n d2: Document\n): number {\n const v1 = d1.data.field(field);\n const v2 = d2.data.field(field);\n if (v1 !== null && v2 !== null) {\n return valueCompare(v1, v2);\n } else {\n return fail(\n 0xa786,\n \"Trying to compare documents on fields that don't exist\"\n );\n }\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Document } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { isReferenceValue, valueCompare, valueEquals } from '../model/values';\nimport { Value as ProtoValue } from '../protos/firestore_proto_api';\nimport { debugAssert } from '../util/assert';\n\nimport { Direction, OrderBy } from './order_by';\n\n/**\n * Represents a bound of a query.\n *\n * The bound is specified with the given components representing a position and\n * whether it's just before or just after the position (relative to whatever the\n * query order is).\n *\n * The position represents a logical index position for a query. It's a prefix\n * of values for the (potentially implicit) order by clauses of a query.\n *\n * Bound provides a function to determine whether a document comes before or\n * after a bound. This is influenced by whether the position is just before or\n * just after the provided values.\n */\nexport class Bound {\n constructor(readonly position: ProtoValue[], readonly inclusive: boolean) {}\n}\n\nfunction boundCompareToDocument(\n bound: Bound,\n orderBy: OrderBy[],\n doc: Document\n): number {\n debugAssert(\n bound.position.length <= orderBy.length,\n \"Bound has more components than query's orderBy\"\n );\n let comparison = 0;\n for (let i = 0; i < bound.position.length; i++) {\n const orderByComponent = orderBy[i];\n const component = bound.position[i];\n if (orderByComponent.field.isKeyField()) {\n debugAssert(\n isReferenceValue(component),\n 'Bound has a non-key value where the key path is being used.'\n );\n comparison = DocumentKey.comparator(\n DocumentKey.fromName(component.referenceValue),\n doc.key\n );\n } else {\n const docValue = doc.data.field(orderByComponent.field);\n debugAssert(\n docValue !== null,\n 'Field should exist since document matched the orderBy already.'\n );\n comparison = valueCompare(component, docValue);\n }\n if (orderByComponent.dir === Direction.DESCENDING) {\n comparison = comparison * -1;\n }\n if (comparison !== 0) {\n break;\n }\n }\n return comparison;\n}\n\n/**\n * Returns true if a document sorts after a bound using the provided sort\n * order.\n */\nexport function boundSortsAfterDocument(\n bound: Bound,\n orderBy: OrderBy[],\n doc: Document\n): boolean {\n const comparison = boundCompareToDocument(bound, orderBy, doc);\n return bound.inclusive ? comparison >= 0 : comparison > 0;\n}\n\n/**\n * Returns true if a document sorts before a bound using the provided sort\n * order.\n */\nexport function boundSortsBeforeDocument(\n bound: Bound,\n orderBy: OrderBy[],\n doc: Document\n): boolean {\n const comparison = boundCompareToDocument(bound, orderBy, doc);\n return bound.inclusive ? comparison <= 0 : comparison < 0;\n}\n\nexport function boundEquals(left: Bound | null, right: Bound | null): boolean {\n if (left === null) {\n return right === null;\n } else if (right === null) {\n return false;\n }\n\n if (\n left.inclusive !== right.inclusive ||\n left.position.length !== right.position.length\n ) {\n return false;\n }\n for (let i = 0; i < left.position.length; i++) {\n const leftPosition = left.position[i];\n const rightPosition = right.position[i];\n if (!valueEquals(leftPosition, rightPosition)) {\n return false;\n }\n }\n return true;\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FieldPath } from '../model/path';\n\n/**\n * The direction of sorting in an order by.\n */\nexport const enum Direction {\n ASCENDING = 'asc',\n DESCENDING = 'desc'\n}\n\n/**\n * An ordering on a field, in some Direction. Direction defaults to ASCENDING.\n */\nexport class OrderBy {\n constructor(\n readonly field: FieldPath,\n readonly dir: Direction = Direction.ASCENDING\n ) {}\n}\n\nexport function canonifyOrderBy(orderBy: OrderBy): string {\n // TODO(b/29183165): Make this collision robust.\n return orderBy.field.canonicalString() + orderBy.dir;\n}\n\nexport function stringifyOrderBy(orderBy: OrderBy): string {\n return `${orderBy.field.canonicalString()} (${orderBy.dir})`;\n}\n\nexport function orderByEquals(left: OrderBy, right: OrderBy): boolean {\n return left.dir === right.dir && left.field.isEqual(right.field);\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Document } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { FieldPath } from '../model/path';\nimport {\n arrayValueContains,\n canonicalId,\n isArray,\n isReferenceValue,\n typeOrder,\n valueCompare,\n valueEquals\n} from '../model/values';\nimport { Value as ProtoValue } from '../protos/firestore_proto_api';\nimport { debugAssert, fail } from '../util/assert';\n\n// The operator of a FieldFilter\nexport const enum Operator {\n LESS_THAN = '<',\n LESS_THAN_OR_EQUAL = '<=',\n EQUAL = '==',\n NOT_EQUAL = '!=',\n GREATER_THAN = '>',\n GREATER_THAN_OR_EQUAL = '>=',\n ARRAY_CONTAINS = 'array-contains',\n IN = 'in',\n NOT_IN = 'not-in',\n ARRAY_CONTAINS_ANY = 'array-contains-any'\n}\n\n// The operator of a CompositeFilter\nexport const enum CompositeOperator {\n OR = 'or',\n AND = 'and'\n}\n\nexport abstract class Filter {\n abstract matches(doc: Document): boolean;\n\n abstract getFlattenedFilters(): readonly FieldFilter[];\n\n abstract getFilters(): Filter[];\n}\n\nexport class FieldFilter extends Filter {\n protected constructor(\n public readonly field: FieldPath,\n public readonly op: Operator,\n public readonly value: ProtoValue\n ) {\n super();\n }\n\n /**\n * Creates a filter based on the provided arguments.\n */\n static create(\n field: FieldPath,\n op: Operator,\n value: ProtoValue\n ): FieldFilter {\n if (field.isKeyField()) {\n if (op === Operator.IN || op === Operator.NOT_IN) {\n return this.createKeyFieldInFilter(field, op, value);\n } else {\n debugAssert(\n isReferenceValue(value),\n 'Comparing on key, but filter value not a RefValue'\n );\n debugAssert(\n op !== Operator.ARRAY_CONTAINS && op !== Operator.ARRAY_CONTAINS_ANY,\n `'${op.toString()}' queries don't make sense on document keys.`\n );\n return new KeyFieldFilter(field, op, value);\n }\n } else if (op === Operator.ARRAY_CONTAINS) {\n return new ArrayContainsFilter(field, value);\n } else if (op === Operator.IN) {\n debugAssert(\n isArray(value),\n 'IN filter has invalid value: ' + value.toString()\n );\n return new InFilter(field, value);\n } else if (op === Operator.NOT_IN) {\n debugAssert(\n isArray(value),\n 'NOT_IN filter has invalid value: ' + value.toString()\n );\n return new NotInFilter(field, value);\n } else if (op === Operator.ARRAY_CONTAINS_ANY) {\n debugAssert(\n isArray(value),\n 'ARRAY_CONTAINS_ANY filter has invalid value: ' + value.toString()\n );\n return new ArrayContainsAnyFilter(field, value);\n } else {\n return new FieldFilter(field, op, value);\n }\n }\n\n private static createKeyFieldInFilter(\n field: FieldPath,\n op: Operator.IN | Operator.NOT_IN,\n value: ProtoValue\n ): FieldFilter {\n debugAssert(\n isArray(value),\n `Comparing on key with ${op.toString()}` +\n ', but filter value not an ArrayValue'\n );\n debugAssert(\n (value.arrayValue.values || []).every(elem => isReferenceValue(elem)),\n `Comparing on key with ${op.toString()}` +\n ', but an array value was not a RefValue'\n );\n\n return op === Operator.IN\n ? new KeyFieldInFilter(field, value)\n : new KeyFieldNotInFilter(field, value);\n }\n\n matches(doc: Document): boolean {\n const other = doc.data.field(this.field);\n // Types do not have to match in NOT_EQUAL filters.\n if (this.op === Operator.NOT_EQUAL) {\n return (\n other !== null &&\n other.nullValue === undefined &&\n this.matchesComparison(valueCompare(other!, this.value))\n );\n }\n\n // Only compare types with matching backend order (such as double and int).\n return (\n other !== null &&\n typeOrder(this.value) === typeOrder(other) &&\n this.matchesComparison(valueCompare(other, this.value))\n );\n }\n\n protected matchesComparison(comparison: number): boolean {\n switch (this.op) {\n case Operator.LESS_THAN:\n return comparison < 0;\n case Operator.LESS_THAN_OR_EQUAL:\n return comparison <= 0;\n case Operator.EQUAL:\n return comparison === 0;\n case Operator.NOT_EQUAL:\n return comparison !== 0;\n case Operator.GREATER_THAN:\n return comparison > 0;\n case Operator.GREATER_THAN_OR_EQUAL:\n return comparison >= 0;\n default:\n return fail(0xb8a2, 'Unknown FieldFilter operator', {\n operator: this.op\n });\n }\n }\n\n isInequality(): boolean {\n return (\n [\n Operator.LESS_THAN,\n Operator.LESS_THAN_OR_EQUAL,\n Operator.GREATER_THAN,\n Operator.GREATER_THAN_OR_EQUAL,\n Operator.NOT_EQUAL,\n Operator.NOT_IN\n ].indexOf(this.op) >= 0\n );\n }\n\n getFlattenedFilters(): readonly FieldFilter[] {\n return [this];\n }\n\n getFilters(): Filter[] {\n return [this];\n }\n}\n\nexport class CompositeFilter extends Filter {\n private memoizedFlattenedFilters: FieldFilter[] | null = null;\n\n protected constructor(\n public readonly filters: readonly Filter[],\n public readonly op: CompositeOperator\n ) {\n super();\n }\n\n /**\n * Creates a filter based on the provided arguments.\n */\n static create(filters: Filter[], op: CompositeOperator): CompositeFilter {\n return new CompositeFilter(filters, op);\n }\n\n matches(doc: Document): boolean {\n if (compositeFilterIsConjunction(this)) {\n // For conjunctions, all filters must match, so return false if any filter doesn't match.\n return this.filters.find(filter => !filter.matches(doc)) === undefined;\n } else {\n // For disjunctions, at least one filter should match.\n return this.filters.find(filter => filter.matches(doc)) !== undefined;\n }\n }\n\n getFlattenedFilters(): readonly FieldFilter[] {\n if (this.memoizedFlattenedFilters !== null) {\n return this.memoizedFlattenedFilters;\n }\n\n this.memoizedFlattenedFilters = this.filters.reduce((result, subfilter) => {\n return result.concat(subfilter.getFlattenedFilters());\n }, [] as FieldFilter[]);\n\n return this.memoizedFlattenedFilters;\n }\n\n // Returns a mutable copy of `this.filters`\n getFilters(): Filter[] {\n return Object.assign([], this.filters);\n }\n}\n\nexport function compositeFilterIsConjunction(\n compositeFilter: CompositeFilter\n): boolean {\n return compositeFilter.op === CompositeOperator.AND;\n}\n\nexport function compositeFilterIsDisjunction(\n compositeFilter: CompositeFilter\n): boolean {\n return compositeFilter.op === CompositeOperator.OR;\n}\n\n/**\n * Returns true if this filter is a conjunction of field filters only. Returns false otherwise.\n */\nexport function compositeFilterIsFlatConjunction(\n compositeFilter: CompositeFilter\n): boolean {\n return (\n compositeFilterIsFlat(compositeFilter) &&\n compositeFilterIsConjunction(compositeFilter)\n );\n}\n\n/**\n * Returns true if this filter does not contain any composite filters. Returns false otherwise.\n */\nexport function compositeFilterIsFlat(\n compositeFilter: CompositeFilter\n): boolean {\n for (const filter of compositeFilter.filters) {\n if (filter instanceof CompositeFilter) {\n return false;\n }\n }\n return true;\n}\n\nexport function canonifyFilter(filter: Filter): string {\n debugAssert(\n filter instanceof FieldFilter || filter instanceof CompositeFilter,\n 'canonifyFilter() only supports FieldFilters and CompositeFilters'\n );\n\n if (filter instanceof FieldFilter) {\n // TODO(b/29183165): Technically, this won't be unique if two values have\n // the same description, such as the int 3 and the string \"3\". So we should\n // add the types in here somehow, too.\n return (\n filter.field.canonicalString() +\n filter.op.toString() +\n canonicalId(filter.value)\n );\n } else if (compositeFilterIsFlatConjunction(filter)) {\n // Older SDK versions use an implicit AND operation between their filters.\n // In the new SDK versions, the developer may use an explicit AND filter.\n // To stay consistent with the old usages, we add a special case to ensure\n // the canonical ID for these two are the same. For example:\n // `col.whereEquals(\"a\", 1).whereEquals(\"b\", 2)` should have the same\n // canonical ID as `col.where(and(equals(\"a\",1), equals(\"b\",2)))`.\n return filter.filters.map(filter => canonifyFilter(filter)).join(',');\n } else {\n // filter instanceof CompositeFilter\n const canonicalIdsString = filter.filters\n .map(filter => canonifyFilter(filter))\n .join(',');\n return `${filter.op}(${canonicalIdsString})`;\n }\n}\n\nexport function filterEquals(f1: Filter, f2: Filter): boolean {\n if (f1 instanceof FieldFilter) {\n return fieldFilterEquals(f1, f2);\n } else if (f1 instanceof CompositeFilter) {\n return compositeFilterEquals(f1, f2);\n } else {\n fail(0x4bef, 'Only FieldFilters and CompositeFilters can be compared');\n }\n}\n\nexport function fieldFilterEquals(f1: FieldFilter, f2: Filter): boolean {\n return (\n f2 instanceof FieldFilter &&\n f1.op === f2.op &&\n f1.field.isEqual(f2.field) &&\n valueEquals(f1.value, f2.value)\n );\n}\n\nexport function compositeFilterEquals(\n f1: CompositeFilter,\n f2: Filter\n): boolean {\n if (\n f2 instanceof CompositeFilter &&\n f1.op === f2.op &&\n f1.filters.length === f2.filters.length\n ) {\n const subFiltersMatch: boolean = f1.filters.reduce(\n (result: boolean, f1Filter: Filter, index: number): boolean =>\n result && filterEquals(f1Filter, f2.filters[index]),\n true\n );\n\n return subFiltersMatch;\n }\n\n return false;\n}\n\n/**\n * Returns a new composite filter that contains all filter from\n * `compositeFilter` plus all the given filters in `otherFilters`.\n */\nexport function compositeFilterWithAddedFilters(\n compositeFilter: CompositeFilter,\n otherFilters: Filter[]\n): CompositeFilter {\n const mergedFilters = compositeFilter.filters.concat(otherFilters);\n return CompositeFilter.create(mergedFilters, compositeFilter.op);\n}\n\n/** Returns a debug description for `filter`. */\nexport function stringifyFilter(filter: Filter): string {\n debugAssert(\n filter instanceof FieldFilter || filter instanceof CompositeFilter,\n 'stringifyFilter() only supports FieldFilters and CompositeFilters'\n );\n if (filter instanceof FieldFilter) {\n return stringifyFieldFilter(filter);\n } else if (filter instanceof CompositeFilter) {\n return stringifyCompositeFilter(filter);\n } else {\n return 'Filter';\n }\n}\n\nexport function stringifyCompositeFilter(filter: CompositeFilter): string {\n return (\n filter.op.toString() +\n ` {` +\n filter.getFilters().map(stringifyFilter).join(' ,') +\n '}'\n );\n}\n\nexport function stringifyFieldFilter(filter: FieldFilter): string {\n return `${filter.field.canonicalString()} ${filter.op} ${canonicalId(\n filter.value\n )}`;\n}\n\n/** Filter that matches on key fields (i.e. '__name__'). */\nexport class KeyFieldFilter extends FieldFilter {\n private readonly key: DocumentKey;\n\n constructor(field: FieldPath, op: Operator, value: ProtoValue) {\n super(field, op, value);\n debugAssert(\n isReferenceValue(value),\n 'KeyFieldFilter expects a ReferenceValue'\n );\n this.key = DocumentKey.fromName(value.referenceValue);\n }\n\n matches(doc: Document): boolean {\n const comparison = DocumentKey.comparator(doc.key, this.key);\n return this.matchesComparison(comparison);\n }\n}\n\n/** Filter that matches on key fields within an array. */\nexport class KeyFieldInFilter extends FieldFilter {\n private readonly keys: DocumentKey[];\n\n constructor(field: FieldPath, value: ProtoValue) {\n super(field, Operator.IN, value);\n this.keys = extractDocumentKeysFromArrayValue(Operator.IN, value);\n }\n\n matches(doc: Document): boolean {\n return this.keys.some(key => key.isEqual(doc.key));\n }\n}\n\n/** Filter that matches on key fields not present within an array. */\nexport class KeyFieldNotInFilter extends FieldFilter {\n private readonly keys: DocumentKey[];\n\n constructor(field: FieldPath, value: ProtoValue) {\n super(field, Operator.NOT_IN, value);\n this.keys = extractDocumentKeysFromArrayValue(Operator.NOT_IN, value);\n }\n\n matches(doc: Document): boolean {\n return !this.keys.some(key => key.isEqual(doc.key));\n }\n}\n\nfunction extractDocumentKeysFromArrayValue(\n op: Operator.IN | Operator.NOT_IN,\n value: ProtoValue\n): DocumentKey[] {\n debugAssert(\n isArray(value),\n 'KeyFieldInFilter/KeyFieldNotInFilter expects an ArrayValue'\n );\n return (value.arrayValue?.values || []).map(v => {\n debugAssert(\n isReferenceValue(v),\n `Comparing on key with ${op.toString()}, but an array value was not ` +\n `a ReferenceValue`\n );\n return DocumentKey.fromName(v.referenceValue);\n });\n}\n\n/** A Filter that implements the array-contains operator. */\nexport class ArrayContainsFilter extends FieldFilter {\n constructor(field: FieldPath, value: ProtoValue) {\n super(field, Operator.ARRAY_CONTAINS, value);\n }\n\n matches(doc: Document): boolean {\n const other = doc.data.field(this.field);\n return isArray(other) && arrayValueContains(other.arrayValue, this.value);\n }\n}\n\n/** A Filter that implements the IN operator. */\nexport class InFilter extends FieldFilter {\n constructor(field: FieldPath, value: ProtoValue) {\n super(field, Operator.IN, value);\n debugAssert(isArray(value), 'InFilter expects an ArrayValue');\n }\n\n matches(doc: Document): boolean {\n const other = doc.data.field(this.field);\n return other !== null && arrayValueContains(this.value.arrayValue!, other);\n }\n}\n\n/** A Filter that implements the not-in operator. */\nexport class NotInFilter extends FieldFilter {\n constructor(field: FieldPath, value: ProtoValue) {\n super(field, Operator.NOT_IN, value);\n debugAssert(isArray(value), 'NotInFilter expects an ArrayValue');\n }\n\n matches(doc: Document): boolean {\n if (\n arrayValueContains(this.value.arrayValue!, { nullValue: 'NULL_VALUE' })\n ) {\n return false;\n }\n const other = doc.data.field(this.field);\n return (\n other !== null &&\n other.nullValue === undefined &&\n !arrayValueContains(this.value.arrayValue!, other)\n );\n }\n}\n\n/** A Filter that implements the array-contains-any operator. */\nexport class ArrayContainsAnyFilter extends FieldFilter {\n constructor(field: FieldPath, value: ProtoValue) {\n super(field, Operator.ARRAY_CONTAINS_ANY, value);\n debugAssert(isArray(value), 'ArrayContainsAnyFilter expects an ArrayValue');\n }\n\n matches(doc: Document): boolean {\n const other = doc.data.field(this.field);\n if (!isArray(other) || !other.arrayValue.values) {\n return false;\n }\n return other.arrayValue.values.some(val =>\n arrayValueContains(this.value.arrayValue!, val)\n );\n }\n}\n","/**\n * @license\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DocumentKey } from '../model/document_key';\nimport {\n FieldIndex,\n fieldIndexGetArraySegment,\n fieldIndexGetDirectionalSegments,\n IndexKind\n} from '../model/field_index';\nimport { FieldPath, ResourcePath } from '../model/path';\nimport {\n canonicalId,\n MAX_VALUE,\n MIN_VALUE,\n lowerBoundCompare,\n upperBoundCompare,\n valuesGetLowerBound,\n valuesGetUpperBound\n} from '../model/values';\nimport { Value as ProtoValue } from '../protos/firestore_proto_api';\nimport { debugCast } from '../util/assert';\nimport { SortedSet } from '../util/sorted_set';\nimport { isNullOrUndefined } from '../util/types';\n\nimport { Bound, boundEquals } from './bound';\nimport {\n Filter,\n FieldFilter,\n canonifyFilter,\n stringifyFilter,\n filterEquals,\n Operator\n} from './filter';\nimport {\n canonifyOrderBy,\n OrderBy,\n orderByEquals,\n stringifyOrderBy\n} from './order_by';\n\n/**\n * A Target represents the WatchTarget representation of a Query, which is used\n * by the LocalStore and the RemoteStore to keep track of and to execute\n * backend queries. While a Query can represent multiple Targets, each Targets\n * maps to a single WatchTarget in RemoteStore and a single TargetData entry\n * in persistence.\n */\nexport interface Target {\n readonly path: ResourcePath;\n readonly collectionGroup: string | null;\n readonly orderBy: OrderBy[];\n readonly filters: Filter[];\n readonly limit: number | null;\n readonly startAt: Bound | null;\n readonly endAt: Bound | null;\n}\n\n// Visible for testing\nexport class TargetImpl implements Target {\n memoizedCanonicalId: string | null = null;\n constructor(\n readonly path: ResourcePath,\n readonly collectionGroup: string | null = null,\n readonly orderBy: OrderBy[] = [],\n readonly filters: Filter[] = [],\n readonly limit: number | null = null,\n readonly startAt: Bound | null = null,\n readonly endAt: Bound | null = null\n ) {}\n}\n\n/**\n * Initializes a Target with a path and optional additional query constraints.\n * Path must currently be empty if this is a collection group query.\n *\n * NOTE: you should always construct `Target` from `Query.toTarget` instead of\n * using this factory method, because `Query` provides an implicit `orderBy`\n * property.\n */\nexport function newTarget(\n path: ResourcePath,\n collectionGroup: string | null = null,\n orderBy: OrderBy[] = [],\n filters: Filter[] = [],\n limit: number | null = null,\n startAt: Bound | null = null,\n endAt: Bound | null = null\n): Target {\n return new TargetImpl(\n path,\n collectionGroup,\n orderBy,\n filters,\n limit,\n startAt,\n endAt\n );\n}\n\nexport function canonifyTarget(target: Target): string {\n const targetImpl = debugCast(target, TargetImpl);\n\n if (targetImpl.memoizedCanonicalId === null) {\n let str = targetImpl.path.canonicalString();\n if (targetImpl.collectionGroup !== null) {\n str += '|cg:' + targetImpl.collectionGroup;\n }\n str += '|f:';\n str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');\n str += '|ob:';\n str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');\n\n if (!isNullOrUndefined(targetImpl.limit)) {\n str += '|l:';\n str += targetImpl.limit!;\n }\n if (targetImpl.startAt) {\n str += '|lb:';\n str += targetImpl.startAt.inclusive ? 'b:' : 'a:';\n str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');\n }\n if (targetImpl.endAt) {\n str += '|ub:';\n str += targetImpl.endAt.inclusive ? 'a:' : 'b:';\n str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');\n }\n targetImpl.memoizedCanonicalId = str;\n }\n return targetImpl.memoizedCanonicalId;\n}\n\nexport function stringifyTarget(target: Target): string {\n let str = target.path.canonicalString();\n if (target.collectionGroup !== null) {\n str += ' collectionGroup=' + target.collectionGroup;\n }\n if (target.filters.length > 0) {\n str += `, filters: [${target.filters\n .map(f => stringifyFilter(f))\n .join(', ')}]`;\n }\n if (!isNullOrUndefined(target.limit)) {\n str += ', limit: ' + target.limit;\n }\n if (target.orderBy.length > 0) {\n str += `, orderBy: [${target.orderBy\n .map(o => stringifyOrderBy(o))\n .join(', ')}]`;\n }\n if (target.startAt) {\n str += ', startAt: ';\n str += target.startAt.inclusive ? 'b:' : 'a:';\n str += target.startAt.position.map(p => canonicalId(p)).join(',');\n }\n if (target.endAt) {\n str += ', endAt: ';\n str += target.endAt.inclusive ? 'a:' : 'b:';\n str += target.endAt.position.map(p => canonicalId(p)).join(',');\n }\n return `Target(${str})`;\n}\n\nexport function targetEquals(left: Target, right: Target): boolean {\n if (left.limit !== right.limit) {\n return false;\n }\n\n if (left.orderBy.length !== right.orderBy.length) {\n return false;\n }\n\n for (let i = 0; i < left.orderBy.length; i++) {\n if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {\n return false;\n }\n }\n\n if (left.filters.length !== right.filters.length) {\n return false;\n }\n\n for (let i = 0; i < left.filters.length; i++) {\n if (!filterEquals(left.filters[i], right.filters[i])) {\n return false;\n }\n }\n\n if (left.collectionGroup !== right.collectionGroup) {\n return false;\n }\n\n if (!left.path.isEqual(right.path)) {\n return false;\n }\n\n if (!boundEquals(left.startAt, right.startAt)) {\n return false;\n }\n\n return boundEquals(left.endAt, right.endAt);\n}\n\nexport function targetIsDocumentTarget(target: Target): boolean {\n return (\n DocumentKey.isDocumentKey(target.path) &&\n target.collectionGroup === null &&\n target.filters.length === 0\n );\n}\n\n/** Returns the field filters that target the given field path. */\nexport function targetGetFieldFiltersForPath(\n target: Target,\n path: FieldPath\n): FieldFilter[] {\n return target.filters.filter(\n f => f instanceof FieldFilter && f.field.isEqual(path)\n ) as FieldFilter[];\n}\n\n/**\n * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY\n * filters. Returns `null` if there are no such filters.\n */\nexport function targetGetArrayValues(\n target: Target,\n fieldIndex: FieldIndex\n): ProtoValue[] | null {\n const segment = fieldIndexGetArraySegment(fieldIndex);\n if (segment === undefined) {\n return null;\n }\n\n for (const fieldFilter of targetGetFieldFiltersForPath(\n target,\n segment.fieldPath\n )) {\n switch (fieldFilter.op) {\n case Operator.ARRAY_CONTAINS_ANY:\n return fieldFilter.value.arrayValue!.values || [];\n case Operator.ARRAY_CONTAINS:\n return [fieldFilter.value];\n default:\n // Remaining filters are not array filters.\n }\n }\n return null;\n}\n\n/**\n * Returns the list of values that are used in != or NOT_IN filters. Returns\n * `null` if there are no such filters.\n */\nexport function targetGetNotInValues(\n target: Target,\n fieldIndex: FieldIndex\n): ProtoValue[] | null {\n const values = new Map</* fieldPath = */ string, ProtoValue>();\n\n for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {\n for (const fieldFilter of targetGetFieldFiltersForPath(\n target,\n segment.fieldPath\n )) {\n switch (fieldFilter.op) {\n case Operator.EQUAL:\n case Operator.IN:\n // Encode equality prefix, which is encoded in the index value before\n // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to\n // `value != 'ab'`).\n values.set(segment.fieldPath.canonicalString(), fieldFilter.value);\n break;\n case Operator.NOT_IN:\n case Operator.NOT_EQUAL:\n // NotIn/NotEqual is always a suffix. There cannot be any remaining\n // segments and hence we can return early here.\n values.set(segment.fieldPath.canonicalString(), fieldFilter.value);\n return Array.from(values.values());\n default:\n // Remaining filters cannot be used as notIn bounds.\n }\n }\n }\n\n return null;\n}\n\n/**\n * Returns a lower bound of field values that can be used as a starting point to\n * scan the index defined by `fieldIndex`. Returns `MIN_VALUE` if no lower bound\n * exists.\n */\nexport function targetGetLowerBound(\n target: Target,\n fieldIndex: FieldIndex\n): Bound {\n const values: ProtoValue[] = [];\n let inclusive = true;\n\n // For each segment, retrieve a lower bound if there is a suitable filter or\n // startAt.\n for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {\n const segmentBound =\n segment.kind === IndexKind.ASCENDING\n ? targetGetAscendingBound(target, segment.fieldPath, target.startAt)\n : targetGetDescendingBound(target, segment.fieldPath, target.startAt);\n\n values.push(segmentBound.value);\n inclusive &&= segmentBound.inclusive;\n }\n return new Bound(values, inclusive);\n}\n\n/**\n * Returns an upper bound of field values that can be used as an ending point\n * when scanning the index defined by `fieldIndex`. Returns `MAX_VALUE` if no\n * upper bound exists.\n */\nexport function targetGetUpperBound(\n target: Target,\n fieldIndex: FieldIndex\n): Bound {\n const values: ProtoValue[] = [];\n let inclusive = true;\n\n // For each segment, retrieve an upper bound if there is a suitable filter or\n // endAt.\n for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {\n const segmentBound =\n segment.kind === IndexKind.ASCENDING\n ? targetGetDescendingBound(target, segment.fieldPath, target.endAt)\n : targetGetAscendingBound(target, segment.fieldPath, target.endAt);\n\n values.push(segmentBound.value);\n inclusive &&= segmentBound.inclusive;\n }\n\n return new Bound(values, inclusive);\n}\n\n/**\n * Returns the value to use as the lower bound for ascending index segment at\n * the provided `fieldPath` (or the upper bound for an descending segment).\n */\nfunction targetGetAscendingBound(\n target: Target,\n fieldPath: FieldPath,\n bound: Bound | null\n): { value: ProtoValue; inclusive: boolean } {\n let value: ProtoValue = MIN_VALUE;\n\n let inclusive = true;\n\n // Process all filters to find a value for the current field segment\n for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {\n let filterValue: ProtoValue = MIN_VALUE;\n let filterInclusive = true;\n\n switch (fieldFilter.op) {\n case Operator.LESS_THAN:\n case Operator.LESS_THAN_OR_EQUAL:\n filterValue = valuesGetLowerBound(fieldFilter.value);\n break;\n case Operator.EQUAL:\n case Operator.IN:\n case Operator.GREATER_THAN_OR_EQUAL:\n filterValue = fieldFilter.value;\n break;\n case Operator.GREATER_THAN:\n filterValue = fieldFilter.value;\n filterInclusive = false;\n break;\n case Operator.NOT_EQUAL:\n case Operator.NOT_IN:\n filterValue = MIN_VALUE;\n break;\n default:\n // Remaining filters cannot be used as lower bounds.\n }\n\n if (\n lowerBoundCompare(\n { value, inclusive },\n { value: filterValue, inclusive: filterInclusive }\n ) < 0\n ) {\n value = filterValue;\n inclusive = filterInclusive;\n }\n }\n\n // If there is an additional bound, compare the values against the existing\n // range to see if we can narrow the scope.\n if (bound !== null) {\n for (let i = 0; i < target.orderBy.length; ++i) {\n const orderBy = target.orderBy[i];\n if (orderBy.field.isEqual(fieldPath)) {\n const cursorValue = bound.position[i];\n if (\n lowerBoundCompare(\n { value, inclusive },\n { value: cursorValue, inclusive: bound.inclusive }\n ) < 0\n ) {\n value = cursorValue;\n inclusive = bound.inclusive;\n }\n break;\n }\n }\n }\n\n return { value, inclusive };\n}\n\n/**\n * Returns the value to use as the upper bound for ascending index segment at\n * the provided `fieldPath` (or the lower bound for a descending segment).\n */\nfunction targetGetDescendingBound(\n target: Target,\n fieldPath: FieldPath,\n bound: Bound | null\n): { value: ProtoValue; inclusive: boolean } {\n let value: ProtoValue = MAX_VALUE;\n let inclusive = true;\n\n // Process all filters to find a value for the current field segment\n for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {\n let filterValue: ProtoValue = MAX_VALUE;\n let filterInclusive = true;\n\n switch (fieldFilter.op) {\n case Operator.GREATER_THAN_OR_EQUAL:\n case Operator.GREATER_THAN:\n filterValue = valuesGetUpperBound(fieldFilter.value);\n filterInclusive = false;\n break;\n case Operator.EQUAL:\n case Operator.IN:\n case Operator.LESS_THAN_OR_EQUAL:\n filterValue = fieldFilter.value;\n break;\n case Operator.LESS_THAN:\n filterValue = fieldFilter.value;\n filterInclusive = false;\n break;\n case Operator.NOT_EQUAL:\n case Operator.NOT_IN:\n filterValue = MAX_VALUE;\n break;\n default:\n // Remaining filters cannot be used as upper bounds.\n }\n\n if (\n upperBoundCompare(\n { value, inclusive },\n { value: filterValue, inclusive: filterInclusive }\n ) > 0\n ) {\n value = filterValue;\n inclusive = filterInclusive;\n }\n }\n\n // If there is an additional bound, compare the values against the existing\n // range to see if we can narrow the scope.\n if (bound !== null) {\n for (let i = 0; i < target.orderBy.length; ++i) {\n const orderBy = target.orderBy[i];\n if (orderBy.field.isEqual(fieldPath)) {\n const cursorValue = bound.position[i];\n if (\n upperBoundCompare(\n { value, inclusive },\n { value: cursorValue, inclusive: bound.inclusive }\n ) > 0\n ) {\n value = cursorValue;\n inclusive = bound.inclusive;\n }\n break;\n }\n }\n }\n\n return { value, inclusive };\n}\n\n/** Returns the number of segments of a perfect index for this target. */\nexport function targetGetSegmentCount(target: Target): number {\n let fields = new SortedSet<FieldPath>(FieldPath.comparator);\n let hasArraySegment = false;\n\n for (const filter of target.filters) {\n for (const subFilter of filter.getFlattenedFilters()) {\n // __name__ is not an explicit segment of any index, so we don't need to\n // count it.\n if (subFilter.field.isKeyField()) {\n continue;\n }\n\n // ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filters must be counted separately.\n // For instance, it is possible to have an index for \"a ARRAY a ASC\". Even\n // though these are on the same field, they should be counted as two\n // separate segments in an index.\n if (\n subFilter.op === Operator.ARRAY_CONTAINS ||\n subFilter.op === Operator.ARRAY_CONTAINS_ANY\n ) {\n hasArraySegment = true;\n } else {\n fields = fields.add(subFilter.field);\n }\n }\n }\n\n for (const orderBy of target.orderBy) {\n // __name__ is not an explicit segment of any index, so we don't need to\n // count it.\n if (!orderBy.field.isKeyField()) {\n fields = fields.add(orderBy.field);\n }\n }\n\n return fields.size + (hasArraySegment ? 1 : 0);\n}\n\nexport function targetHasLimit(target: Target): boolean {\n return target.limit !== null;\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { compareDocumentsByField, Document } from '../model/document';\nimport { DocumentKey } from '../model/document_key';\nimport { FieldPath, ResourcePath } from '../model/path';\nimport { debugAssert, debugCast, fail } from '../util/assert';\nimport { SortedSet } from '../util/sorted_set';\n\nimport {\n Bound,\n boundSortsAfterDocument,\n boundSortsBeforeDocument\n} from './bound';\nimport { FieldFilter, Filter } from './filter';\nimport { Direction, OrderBy } from './order_by';\nimport {\n canonifyTarget,\n newTarget,\n stringifyTarget,\n Target,\n targetEquals\n} from './target';\n\nexport const enum LimitType {\n First = 'F',\n Last = 'L'\n}\n\n/**\n * The Query interface defines all external properties of a query.\n *\n * QueryImpl implements this interface to provide memoization for `queryNormalizedOrderBy`\n * and `queryToTarget`.\n */\nexport interface Query {\n readonly path: ResourcePath;\n readonly collectionGroup: string | null;\n readonly explicitOrderBy: OrderBy[];\n readonly filters: Filter[];\n readonly limit: number | null;\n readonly limitType: LimitType;\n readonly startAt: Bound | null;\n readonly endAt: Bound | null;\n}\n\n/**\n * Query encapsulates all the query attributes we support in the SDK. It can\n * be run against the LocalStore, as well as be converted to a `Target` to\n * query the RemoteStore results.\n *\n * Visible for testing.\n */\nexport class QueryImpl implements Query {\n memoizedNormalizedOrderBy: OrderBy[] | null = null;\n\n // The corresponding `Target` of this `Query` instance, for use with\n // non-aggregate queries.\n memoizedTarget: Target | null = null;\n\n // The corresponding `Target` of this `Query` instance, for use with\n // aggregate queries. Unlike targets for non-aggregate queries,\n // aggregate query targets do not contain normalized order-bys, they only\n // contain explicit order-bys.\n memoizedAggregateTarget: Target | null = null;\n\n /**\n * Initializes a Query with a path and optional additional query constraints.\n * Path must currently be empty if this is a collection group query.\n */\n constructor(\n readonly path: ResourcePath,\n readonly collectionGroup: string | null = null,\n readonly explicitOrderBy: OrderBy[] = [],\n readonly filters: Filter[] = [],\n readonly limit: number | null = null,\n readonly limitType: LimitType = LimitType.First,\n readonly startAt: Bound | null = null,\n readonly endAt: Bound | null = null\n ) {\n if (this.startAt) {\n debugAssert(\n this.startAt.position.length <= queryNormalizedOrderBy(this).length,\n 'Bound is longer than orderBy'\n );\n }\n if (this.endAt) {\n debugAssert(\n this.endAt.position.length <= queryNormalizedOrderBy(this).length,\n 'Bound is longer than orderBy'\n );\n }\n }\n}\n\n/** Creates a new Query instance with the options provided. */\nexport function newQuery(\n path: ResourcePath,\n collectionGroup: string | null,\n explicitOrderBy: OrderBy[],\n filters: Filter[],\n limit: number | null,\n limitType: LimitType,\n startAt: Bound | null,\n endAt: Bound | null\n): Query {\n return new QueryImpl(\n path,\n collectionGroup,\n explicitOrderBy,\n filters,\n limit,\n limitType,\n startAt,\n endAt\n );\n}\n\n/** Creates a new Query for a query that matches all documents at `path` */\nexport function newQueryForPath(path: ResourcePath): Query {\n return new QueryImpl(path);\n}\n\n/**\n * Helper to convert a collection group query into a collection query at a\n * specific path. This is used when executing collection group queries, since\n * we have to split the query into a set of collection queries at multiple\n * paths.\n */\nexport function asCollectionQueryAtPath(\n query: Query,\n path: ResourcePath\n): Query {\n return new QueryImpl(\n path,\n /*collectionGroup=*/ null,\n query.explicitOrderBy.slice(),\n query.filters.slice(),\n query.limit,\n query.limitType,\n query.startAt,\n query.endAt\n );\n}\n\n/**\n * Returns true if this query does not specify any query constraints that\n * could remove results.\n */\nexport function queryMatchesAllDocuments(query: Query): boolean {\n return (\n query.filters.length === 0 &&\n query.limit === null &&\n query.startAt == null &&\n query.endAt == null &&\n (query.explicitOrderBy.length === 0 ||\n (query.explicitOrderBy.length === 1 &&\n query.explicitOrderBy[0].field.isKeyField()))\n );\n}\n\n// Returns the sorted set of inequality filter fields used in this query.\nexport function getInequalityFilterFields(query: Query): SortedSet<FieldPath> {\n let result = new SortedSet<FieldPath>(FieldPath.comparator);\n query.filters.forEach((filter: Filter) => {\n const subFilters = filter.getFlattenedFilters();\n subFilters.forEach((filter: FieldFilter) => {\n if (filter.isInequality()) {\n result = result.add(filter.field);\n }\n });\n });\n return result;\n}\n\n/**\n * Creates a new Query for a collection group query that matches all documents\n * within the provided collection group.\n */\nexport function newQueryForCollectionGroup(collectionId: string): Query {\n return new QueryImpl(ResourcePath.emptyPath(), collectionId);\n}\n\n/**\n * Returns whether the query matches a single document by path (rather than a\n * collection).\n */\nexport function isDocumentQuery(query: Query): boolean {\n return (\n DocumentKey.isDocumentKey(query.path) &&\n query.collectionGroup === null &&\n query.filters.length === 0\n );\n}\n\n/**\n * Returns whether the query matches a collection group rather than a specific\n * collection.\n */\nexport function isCollectionGroupQuery(query: Query): boolean {\n return query.collectionGroup !== null;\n}\n\n/**\n * Returns the normalized order-by constraint that is used to execute the Query,\n * which can be different from the order-by constraints the user provided (e.g.\n * the SDK and backend always orders by `__name__`). The normalized order-by\n * includes implicit order-bys in addition to the explicit user provided\n * order-bys.\n */\nexport function queryNormalizedOrderBy(query: Query): OrderBy[] {\n const queryImpl = debugCast(query, QueryImpl);\n if (queryImpl.memoizedNormalizedOrderBy === null) {\n queryImpl.memoizedNormalizedOrderBy = [];\n const fieldsNormalized = new Set<string>();\n\n // Any explicit order by fields should be added as is.\n for (const orderBy of queryImpl.explicitOrderBy) {\n queryImpl.memoizedNormalizedOrderBy.push(orderBy);\n fieldsNormalized.add(orderBy.field.canonicalString());\n }\n\n // The order of the implicit ordering always matches the last explicit order by.\n const lastDirection =\n queryImpl.explicitOrderBy.length > 0\n ? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1].dir\n : Direction.ASCENDING;\n\n // Any inequality fields not explicitly ordered should be implicitly ordered in a lexicographical\n // order. When there are multiple inequality filters on the same field, the field should be added\n // only once.\n // Note: `SortedSet<FieldPath>` sorts the key field before other fields. However, we want the key\n // field to be sorted last.\n const inequalityFields: SortedSet<FieldPath> =\n getInequalityFilterFields(queryImpl);\n inequalityFields.forEach(field => {\n if (\n !fieldsNormalized.has(field.canonicalString()) &&\n !field.isKeyField()\n ) {\n queryImpl.memoizedNormalizedOrderBy!.push(\n new OrderBy(field, lastDirection)\n );\n }\n });\n\n // Add the document key field to the last if it is not explicitly ordered.\n if (!fieldsNormalized.has(FieldPath.keyField().canonicalString())) {\n queryImpl.memoizedNormalizedOrderBy.push(\n new OrderBy(FieldPath.keyField(), lastDirection)\n );\n }\n }\n return queryImpl.memoizedNormalizedOrderBy;\n}\n\n/**\n * Converts this `Query` instance to its corresponding `Target` representation.\n */\nexport function queryToTarget(query: Query): Target {\n const queryImpl = debugCast(query, QueryImpl);\n if (!queryImpl.memoizedTarget) {\n queryImpl.memoizedTarget = _queryToTarget(\n queryImpl,\n queryNormalizedOrderBy(query)\n );\n }\n\n return queryImpl.memoizedTarget;\n}\n\n/**\n * Converts this `Query` instance to its corresponding `Target` representation,\n * for use within an aggregate query. Unlike targets for non-aggregate queries,\n * aggregate query targets do not contain normalized order-bys, they only\n * contain explicit order-bys.\n */\nexport function queryToAggregateTarget(query: Query): Target {\n const queryImpl = debugCast(query, QueryImpl);\n\n if (!queryImpl.memoizedAggregateTarget) {\n // Do not include implicit order-bys for aggregate queries.\n queryImpl.memoizedAggregateTarget = _queryToTarget(\n queryImpl,\n query.explicitOrderBy\n );\n }\n\n return queryImpl.memoizedAggregateTarget;\n}\n\nfunction _queryToTarget(queryImpl: QueryImpl, orderBys: OrderBy[]): Target {\n if (queryImpl.limitType === LimitType.First) {\n return newTarget(\n queryImpl.path,\n queryImpl.collectionGroup,\n orderBys,\n queryImpl.filters,\n queryImpl.limit,\n queryImpl.startAt,\n queryImpl.endAt\n );\n } else {\n // Flip the orderBy directions since we want the last results\n orderBys = orderBys.map(orderBy => {\n const dir =\n orderBy.dir === Direction.DESCENDING\n ? Direction.ASCENDING\n : Direction.DESCENDING;\n return new OrderBy(orderBy.field, dir);\n });\n\n // We need to swap the cursors to match the now-flipped query ordering.\n const startAt = queryImpl.endAt\n ? new Bound(queryImpl.endAt.position, queryImpl.endAt.inclusive)\n : null;\n const endAt = queryImpl.startAt\n ? new Bound(queryImpl.startAt.position, queryImpl.startAt.inclusive)\n : null;\n\n // Now return as a LimitType.First query.\n return newTarget(\n queryImpl.path,\n queryImpl.collectionGroup,\n orderBys,\n queryImpl.filters,\n queryImpl.limit,\n startAt,\n endAt\n );\n }\n}\n\nexport function queryWithAddedFilter(query: Query, filter: Filter): Query {\n debugAssert(\n !isDocumentQuery(query),\n 'No filtering allowed for document query'\n );\n\n const newFilters = query.filters.concat([filter]);\n return new QueryImpl(\n query.path,\n query.collectionGroup,\n query.explicitOrderBy.slice(),\n newFilters,\n query.limit,\n query.limitType,\n query.startAt,\n query.endAt\n );\n}\n\nexport function queryWithAddedOrderBy(query: Query, orderBy: OrderBy): Query {\n debugAssert(\n !query.startAt && !query.endAt,\n 'Bounds must be set after orderBy'\n );\n // TODO(dimond): validate that orderBy does not list the same key twice.\n const newOrderBy = query.explicitOrderBy.concat([orderBy]);\n return new QueryImpl(\n query.path,\n query.collectionGroup,\n newOrderBy,\n query.filters.slice(),\n query.limit,\n query.limitType,\n query.startAt,\n query.endAt\n );\n}\n\nexport function queryWithLimit(\n query: Query,\n limit: number | null,\n limitType: LimitType\n): Query {\n return new QueryImpl(\n query.path,\n query.collectionGroup,\n query.explicitOrderBy.slice(),\n query.filters.slice(),\n limit,\n limitType,\n query.startAt,\n query.endAt\n );\n}\n\nexport function queryWithStartAt(query: Query, bound: Bound): Query {\n return new QueryImpl(\n query.path,\n query.collectionGroup,\n query.explicitOrderBy.slice(),\n query.filters.slice(),\n query.limit,\n query.limitType,\n bound,\n query.endAt\n );\n}\n\nexport function queryWithEndAt(query: Query, bound: Bound): Query {\n return new QueryImpl(\n query.path,\n query.collectionGroup,\n query.explicitOrderBy.slice(),\n query.filters.slice(),\n query.limit,\n query.limitType,\n query.startAt,\n bound\n );\n}\n\nexport function queryEquals(left: Query, right: Query): boolean {\n return (\n targetEquals(queryToTarget(left), queryToTarget(right)) &&\n left.limitType === right.limitType\n );\n}\n\n// TODO(b/29183165): This is used to get a unique string from a query to, for\n// example, use as a dictionary key, but the implementation is subject to\n// collisions. Make it collision-free.\nexport function canonifyQuery(query: Query): string {\n return `${canonifyTarget(queryToTarget(query))}|lt:${query.limitType}`;\n}\n\nexport function stringifyQuery(query: Query): string {\n return `Query(target=${stringifyTarget(queryToTarget(query))}; limitType=${\n query.limitType\n })`;\n}\n\n/** Returns whether `doc` matches the constraints of `query`. */\nexport function queryMatches(query: Query, doc: Document): boolean {\n return (\n doc.isFoundDocument() &&\n queryMatchesPathAndCollectionGroup(query, doc) &&\n queryMatchesOrderBy(query, doc) &&\n queryMatchesFilters(query, doc) &&\n queryMatchesBounds(query, doc)\n );\n}\n\nfunction queryMatchesPathAndCollectionGroup(\n query: Query,\n doc: Document\n): boolean {\n const docPath = doc.key.path;\n if (query.collectionGroup !== null) {\n // NOTE: this.path is currently always empty since we don't expose Collection\n // Group queries rooted at a document path yet.\n return (\n doc.key.hasCollectionId(query.collectionGroup) &&\n query.path.isPrefixOf(docPath)\n );\n } else if (DocumentKey.isDocumentKey(query.path)) {\n // exact match for document queries\n return query.path.isEqual(docPath);\n } else {\n // shallow ancestor queries by default\n return query.path.isImmediateParentOf(docPath);\n }\n}\n\n/**\n * A document must have a value for every ordering clause in order to show up\n * in the results.\n */\nfunction queryMatchesOrderBy(query: Query, doc: Document): boolean {\n // We must use `queryNormalizedOrderBy()` to get the list of all orderBys (both implicit and explicit).\n // Note that for OR queries, orderBy applies to all disjunction terms and implicit orderBys must\n // be taken into account. For example, the query \"a > 1 || b==1\" has an implicit \"orderBy a\" due\n // to the inequality, and is evaluated as \"a > 1 orderBy a || b==1 orderBy a\".\n // A document with content of {b:1} matches the filters, but does not match the orderBy because\n // it's missing the field 'a'.\n for (const orderBy of queryNormalizedOrderBy(query)) {\n // order-by key always matches\n if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {\n return false;\n }\n }\n return true;\n}\n\nfunction queryMatchesFilters(query: Query, doc: Document): boolean {\n for (const filter of query.filters) {\n if (!filter.matches(doc)) {\n return false;\n }\n }\n return true;\n}\n\n/** Makes sure a document is within the bounds, if provided. */\nfunction queryMatchesBounds(query: Query, doc: Document): boolean {\n if (\n query.startAt &&\n !boundSortsBeforeDocument(query.startAt, queryNormalizedOrderBy(query), doc)\n ) {\n return false;\n }\n if (\n query.endAt &&\n !boundSortsAfterDocument(query.endAt, queryNormalizedOrderBy(query), doc)\n ) {\n return false;\n }\n return true;\n}\n\n/**\n * Returns the collection group that this query targets.\n *\n * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab\n * synchronization for query results.\n */\nexport function queryCollectionGroup(query: Query): string {\n return (\n query.collectionGroup ||\n (query.path.length % 2 === 1\n ? query.path.lastSegment()\n : query.path.get(query.path.length - 2))\n );\n}\n\n/**\n * Returns a new comparator function that can be used to compare two documents\n * based on the Query's ordering constraint.\n */\nexport function newQueryComparator(\n query: Query\n): (d1: Document, d2: Document) => number {\n return (d1: Document, d2: Document): number => {\n let comparedOnKeyField = false;\n for (const orderBy of queryNormalizedOrderBy(query)) {\n const comp = compareDocs(orderBy, d1, d2);\n if (comp !== 0) {\n return comp;\n }\n comparedOnKeyField = comparedOnKeyField || orderBy.field.isKeyField();\n }\n // Assert that we actually compared by key\n debugAssert(\n comparedOnKeyField,\n \"orderBy used that doesn't compare on key field\"\n );\n return 0;\n };\n}\n\nexport function compareDocs(\n orderBy: OrderBy,\n d1: Document,\n d2: Document\n): number {\n const comparison = orderBy.field.isKeyField()\n ? DocumentKey.comparator(d1.key, d2.key)\n : compareDocumentsByField(orderBy.field, d1, d2);\n switch (orderBy.dir) {\n case Direction.ASCENDING:\n return comparison;\n case Direction.DESCENDING:\n return -1 * comparison;\n default:\n return fail(0x4d4e, 'Unknown direction', { direction: orderBy.dir });\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { forEach, isEmpty } from './obj';\n\ntype Entry<K, V> = [K, V];\n\n/**\n * A map implementation that uses objects as keys. Objects must have an\n * associated equals function and must be immutable. Entries in the map are\n * stored together with the key being produced from the mapKeyFn. This map\n * automatically handles collisions of keys.\n */\nexport class ObjectMap<KeyType, ValueType> {\n /**\n * The inner map for a key/value pair. Due to the possibility of collisions we\n * keep a list of entries that we do a linear search through to find an actual\n * match. Note that collisions should be rare, so we still expect near\n * constant time lookups in practice.\n */\n private inner: {\n [canonicalId: string]: Array<Entry<KeyType, ValueType>>;\n } = {};\n\n /** The number of entries stored in the map */\n private innerSize = 0;\n\n constructor(\n private mapKeyFn: (key: KeyType) => string,\n private equalsFn: (l: KeyType, r: KeyType) => boolean\n ) {}\n\n /** Get a value for this key, or undefined if it does not exist. */\n get(key: KeyType): ValueType | undefined {\n const id = this.mapKeyFn(key);\n const matches = this.inner[id];\n if (matches === undefined) {\n return undefined;\n }\n for (const [otherKey, value] of matches) {\n if (this.equalsFn(otherKey, key)) {\n return value;\n }\n }\n return undefined;\n }\n\n has(key: KeyType): boolean {\n return this.get(key) !== undefined;\n }\n\n /** Put this key and value in the map. */\n set(key: KeyType, value: ValueType): void {\n const id = this.mapKeyFn(key);\n const matches = this.inner[id];\n if (matches === undefined) {\n this.inner[id] = [[key, value]];\n this.innerSize++;\n return;\n }\n for (let i = 0; i < matches.length; i++) {\n if (this.equalsFn(matches[i][0], key)) {\n // This is updating an existing entry and does not increase `innerSize`.\n matches[i] = [key, value];\n return;\n }\n }\n matches.push([key, value]);\n this.innerSize++;\n }\n\n /**\n * Remove this key from the map. Returns a boolean if anything was deleted.\n */\n delete(key: KeyType): boolean {\n const id = this.mapKeyFn(key);\n const matches = this.inner[id];\n if (matches === undefined) {\n return false;\n }\n for (let i = 0; i < matches.length; i++) {\n if (this.equalsFn(matches[i][0], key)) {\n if (matches.length === 1) {\n delete this.inner[id];\n } else {\n matches.splice(i, 1);\n }\n this.innerSize--;\n return true;\n }\n }\n return false;\n }\n\n forEach(fn: (key: KeyType, val: ValueType) => void): void {\n forEach(this.inner, (_, entries) => {\n for (const [k, v] of entries) {\n fn(k, v);\n }\n });\n }\n\n isEmpty(): boolean {\n return isEmpty(this.inner);\n }\n\n size(): number {\n return this.innerSize;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { TargetId } from '../core/types';\nimport { OverlayedDocument } from '../local/overlayed_document';\nimport { primitiveComparator } from '../util/misc';\nimport { ObjectMap } from '../util/obj_map';\nimport { SortedMap } from '../util/sorted_map';\nimport { SortedSet } from '../util/sorted_set';\n\nimport { Document, MutableDocument } from './document';\nimport { DocumentKey } from './document_key';\nimport { Mutation } from './mutation';\nimport { Overlay } from './overlay';\n\n/** Miscellaneous collection types / constants. */\n\nexport type MutableDocumentMap = SortedMap<DocumentKey, MutableDocument>;\nconst EMPTY_MUTABLE_DOCUMENT_MAP = new SortedMap<DocumentKey, MutableDocument>(\n DocumentKey.comparator\n);\nexport function mutableDocumentMap(): MutableDocumentMap {\n return EMPTY_MUTABLE_DOCUMENT_MAP;\n}\n\nexport interface DocumentSizeEntries {\n documents: MutableDocumentMap;\n sizeMap: SortedMap<DocumentKey, number>;\n}\n\nexport type DocumentMap = SortedMap<DocumentKey, Document>;\nconst EMPTY_DOCUMENT_MAP = new SortedMap<DocumentKey, Document>(\n DocumentKey.comparator\n);\nexport function documentMap(...docs: Document[]): DocumentMap {\n let map = EMPTY_DOCUMENT_MAP;\n for (const doc of docs) {\n map = map.insert(doc.key, doc);\n }\n return map;\n}\n\nexport type OverlayedDocumentMap = DocumentKeyMap<OverlayedDocument>;\nexport function newOverlayedDocumentMap(): OverlayedDocumentMap {\n return newDocumentKeyMap<OverlayedDocument>();\n}\n\nexport function convertOverlayedDocumentMapToDocumentMap(\n collection: OverlayedDocumentMap\n): DocumentMap {\n let documents = EMPTY_DOCUMENT_MAP;\n collection.forEach(\n (k, v) => (documents = documents.insert(k, v.overlayedDocument))\n );\n return documents;\n}\n\nexport type OverlayMap = DocumentKeyMap<Overlay>;\nexport function newOverlayMap(): OverlayMap {\n return newDocumentKeyMap<Overlay>();\n}\n\nexport type MutationMap = DocumentKeyMap<Mutation>;\nexport function newMutationMap(): MutationMap {\n return newDocumentKeyMap<Mutation>();\n}\n\nexport type DocumentKeyMap<T> = ObjectMap<DocumentKey, T>;\nexport function newDocumentKeyMap<T>(): DocumentKeyMap<T> {\n return new ObjectMap<DocumentKey, T>(\n key => key.toString(),\n (l, r) => l.isEqual(r)\n );\n}\n\nexport type DocumentVersionMap = SortedMap<DocumentKey, SnapshotVersion>;\nconst EMPTY_DOCUMENT_VERSION_MAP = new SortedMap<DocumentKey, SnapshotVersion>(\n DocumentKey.comparator\n);\nexport function documentVersionMap(): DocumentVersionMap {\n return EMPTY_DOCUMENT_VERSION_MAP;\n}\n\nexport type DocumentKeySet = SortedSet<DocumentKey>;\nconst EMPTY_DOCUMENT_KEY_SET = new SortedSet(DocumentKey.comparator);\nexport function documentKeySet(...keys: DocumentKey[]): DocumentKeySet {\n let set = EMPTY_DOCUMENT_KEY_SET;\n for (const key of keys) {\n set = set.add(key);\n }\n return set;\n}\n\nexport type TargetIdSet = SortedSet<TargetId>;\nconst EMPTY_TARGET_ID_SET = new SortedSet<TargetId>(primitiveComparator);\nexport function targetIdSet(): SortedSet<TargetId> {\n return EMPTY_TARGET_ID_SET;\n}\n","/**\n * @license\n * Copyright 2020 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Value as ProtoValue } from '../protos/firestore_proto_api';\nimport { isNegativeZero, isSafeInteger } from '../util/types';\n\n/** Base interface for the Serializer implementation. */\nexport interface Serializer {\n readonly useProto3Json: boolean;\n}\n\n/**\n * Returns an DoubleValue for `value` that is encoded based the serializer's\n * `useProto3Json` setting.\n */\nexport function toDouble(serializer: Serializer, value: number): ProtoValue {\n if (serializer.useProto3Json) {\n if (isNaN(value)) {\n return { doubleValue: 'NaN' };\n } else if (value === Infinity) {\n return { doubleValue: 'Infinity' };\n } else if (value === -Infinity) {\n return { doubleValue: '-Infinity' };\n }\n }\n return { doubleValue: isNegativeZero(value) ? '-0' : value };\n}\n\n/**\n * Returns an IntegerValue for `value`.\n */\nexport function toInteger(value: number): ProtoValue {\n return { integerValue: '' + value };\n}\n\n/**\n * Returns a value for a number that's appropriate to put into a proto.\n * The return value is an IntegerValue if it can safely represent the value,\n * otherwise a DoubleValue is returned.\n */\nexport function toNumber(serializer: Serializer, value: number): ProtoValue {\n return isSafeInteger(value) ? toInteger(value) : toDouble(serializer, value);\n}\n","/**\n * @license\n * Copyright 2018 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Timestamp } from '../lite-api/timestamp';\nimport { Value as ProtoValue } from '../protos/firestore_proto_api';\nimport { Serializer, toDouble, toInteger } from '../remote/number_serializer';\nimport { debugAssert } from '../util/assert';\nimport { arrayEquals } from '../util/misc';\n\nimport { normalizeNumber } from './normalize';\nimport { serverTimestamp } from './server_timestamps';\nimport { isArray, isInteger, isNumber, valueEquals } from './values';\n\n/** Used to represent a field transform on a mutation. */\nexport class TransformOperation {\n // Make sure that the structural type of `TransformOperation` is unique.\n // See https://github.com/microsoft/TypeScript/issues/5451\n private _ = undefined;\n}\n\n/**\n * Computes the local transform result against the provided `previousValue`,\n * optionally using the provided localWriteTime.\n */\nexport function applyTransformOperationToLocalView(\n transform: TransformOperation,\n previousValue: ProtoValue | null,\n localWriteTime: Timestamp\n): ProtoValue {\n if (transform instanceof ServerTimestampTransform) {\n return serverTimestamp(localWriteTime, previousValue);\n } else if (transform instanceof ArrayUnionTransformOperation) {\n return applyArrayUnionTransformOperation(transform, previousValue);\n } else if (transform instanceof ArrayRemoveTransformOperation) {\n return applyArrayRemoveTransformOperation(transform, previousValue);\n } else {\n debugAssert(\n transform instanceof NumericIncrementTransformOperation,\n 'Expected NumericIncrementTransformOperation but was: ' + transform\n );\n return applyNumericIncrementTransformOperationToLocalView(\n transform,\n previousValue\n );\n }\n}\n\n/**\n * Computes a final transform result after the transform has been acknowledged\n * by the server, potentially using the server-provided transformResult.\n */\nexport function applyTransformOperationToRemoteDocument(\n transform: TransformOperation,\n previousValue: ProtoValue | null,\n transformResult: ProtoValue | null\n): ProtoValue {\n // The server just sends null as the transform result for array operations,\n // so we have to calculate a result the same as we do for local\n // applications.\n if (transform instanceof ArrayUnionTransformOperation) {\n return applyArrayUnionTransformOperation(transform, previousValue);\n } else if (transform instanceof ArrayRemoveTransformOperation) {\n return applyArrayRemoveTransformOperation(transform, previousValue);\n }\n\n debugAssert(\n transformResult !== null,\n \"Didn't receive transformResult for non-array transform\"\n );\n return transformResult;\n}\n\n/**\n * If this transform operation is not idempotent, returns the base value to\n * persist for this transform. If a base value is returned, the transform\n * operation is always applied to this base value, even if document has\n * already been updated.\n *\n * Base values provide consistent behavior for non-idempotent transforms and\n * allow us to return the same latency-compensated value even if the backend\n * has already applied the transform operation. The base value is null for\n * idempotent transforms, as they can be re-played even if the backend has\n * already applied them.\n *\n * @returns a base value to store along with the mutation, or null for\n * idempotent transforms.\n */\nexport function computeTransformOperationBaseValue(\n transform: TransformOperation,\n previousValue: ProtoValue | null\n): ProtoValue | null {\n if (transform instanceof NumericIncrementTransformOperation) {\n return isNumber(previousValue) ? previousValue! : { integerValue: 0 };\n }\n return null;\n}\n\nexport function transformOperationEquals(\n left: TransformOperation,\n right: TransformOperation\n): boolean {\n if (\n left instanceof ArrayUnionTransformOperation &&\n right instanceof ArrayUnionTransformOperation\n ) {\n return arrayEquals(left.elements, right.elements, valueEquals);\n } else if (\n left instanceof ArrayRemoveTransformOperation &&\n right instanceof ArrayRemoveTransformOperation\n ) {\n return arrayEquals(left.elements, right.elements, valueEquals);\n } else if (\n left instanceof NumericIncrementTransformOperation &&\n right instanceof NumericIncrementTransformOperation\n ) {\n return valueEquals(left.operand, right.operand);\n }\n\n return (\n left instanceof ServerTimestampTransform &&\n right instanceof ServerTimestampTransform\n );\n}\n\n/** Transforms a value into a server-generated timestamp. */\nexport class ServerTimestampTransform extends TransformOperation {}\n\n/** Transforms an array value via a union operation. */\nexport class ArrayUnionTransformOperation extends TransformOperation {\n constructor(readonly elements: ProtoValue[]) {\n super();\n }\n}\n\nfunction applyArrayUnionTransformOperation(\n transform: ArrayUnionTransformOperation,\n previousValue: ProtoValue | null\n): ProtoValue {\n const values = coercedFieldValuesArray(previousValue);\n for (const toUnion of transform.elements) {\n if (!values.some(element => valueEquals(element, toUnion))) {\n values.push(toUnion);\n }\n }\n return { arrayValue: { values } };\n}\n\n/** Transforms an array value via a remove operation. */\nexport class ArrayRemoveTransformOperation extends TransformOperation {\n constructor(readonly elements: ProtoValue[]) {\n super();\n }\n}\n\nfunction applyArrayRemoveTransformOperation(\n transform: ArrayRemoveTransformOperation,\n previousValue: ProtoValue | null\n): ProtoValue {\n let values = coercedFieldValuesArray(previousValue);\n for (const toRemove of transform.elements) {\n values = values.filter(element => !valueEquals(element, toRemove));\n }\n return { arrayValue: { values } };\n}\n\n/**\n * Implements the backend semantics for locally computed NUMERIC_ADD (increment)\n * transforms. Converts all field values to integers or doubles, but unlike the\n * backend does not cap integer values at 2^63. Instead, JavaScript number\n * arithmetic is used and precision loss can occur for values greater than 2^53.\n */\nexport class NumericIncrementTransformOperation extends TransformOperation {\n constructor(readonly serializer: Serializer, readonly operand: ProtoValue) {\n super();\n debugAssert(\n isNumber(operand),\n 'NumericIncrementTransform transform requires a NumberValue'\n );\n }\n}\n\nexport function applyNumericIncrementTransformOperationToLocalView(\n transform: NumericIncrementTransformOperation,\n previousValue: ProtoValue | null\n): ProtoValue {\n // PORTING NOTE: Since JavaScript's integer arithmetic is limited to 53 bit\n // precision and resolves overflows by reducing precision, we do not\n // manually cap overflows at 2^63.\n const baseValue = computeTransformOperationBaseValue(\n transform,\n previousValue\n )!;\n const sum = asNumber(baseValue) + asNumber(transform.operand);\n if (isInteger(baseValue) && isInteger(transform.operand)) {\n return toInteger(sum);\n } else {\n return toDouble(transform.serializer, sum);\n }\n}\n\nfunction asNumber(value: ProtoValue): number {\n return normalizeNumber(value.integerValue || value.doubleValue);\n}\n\nfunction coercedFieldValuesArray(value: ProtoValue | null): ProtoValue[] {\n return isArray(value) && value.arrayValue.values\n ? value.arrayValue.values.slice()\n : [];\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { Timestamp } from '../lite-api/timestamp';\nimport { Value as ProtoValue } from '../protos/firestore_proto_api';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { arrayEquals } from '../util/misc';\nimport { SortedSet } from '../util/sorted_set';\n\nimport { Document, MutableDocument } from './document';\nimport { DocumentKey } from './document_key';\nimport { FieldMask } from './field_mask';\nimport { ObjectValue } from './object_value';\nimport { FieldPath } from './path';\nimport {\n applyTransformOperationToLocalView,\n applyTransformOperationToRemoteDocument,\n computeTransformOperationBaseValue,\n TransformOperation,\n transformOperationEquals\n} from './transform_operation';\n\n/** A field path and the TransformOperation to perform upon it. */\nexport class FieldTransform {\n constructor(\n readonly field: FieldPath,\n readonly transform: TransformOperation\n ) {}\n}\n\nexport function fieldTransformEquals(\n left: FieldTransform,\n right: FieldTransform\n): boolean {\n return (\n left.field.isEqual(right.field) &&\n transformOperationEquals(left.transform, right.transform)\n );\n}\n\nexport function fieldTransformsAreEqual(\n left?: FieldTransform[],\n right?: FieldTransform[]\n): boolean {\n if (left === undefined && right === undefined) {\n return true;\n }\n\n if (left && right) {\n return arrayEquals(left, right, (l, r) => fieldTransformEquals(l, r));\n }\n\n return false;\n}\n\n/** The result of successfully applying a mutation to the backend. */\nexport class MutationResult {\n constructor(\n /**\n * The version at which the mutation was committed:\n *\n * - For most operations, this is the updateTime in the WriteResult.\n * - For deletes, the commitTime of the WriteResponse (because deletes are\n * not stored and have no updateTime).\n *\n * Note that these versions can be different: No-op writes will not change\n * the updateTime even though the commitTime advances.\n */\n readonly version: SnapshotVersion,\n /**\n * The resulting fields returned from the backend after a mutation\n * containing field transforms has been committed. Contains one FieldValue\n * for each FieldTransform that was in the mutation.\n *\n * Will be empty if the mutation did not contain any field transforms.\n */\n readonly transformResults: Array<ProtoValue | null>\n ) {}\n}\n\nexport const enum MutationType {\n Set,\n Patch,\n Delete,\n Verify\n}\n\n/**\n * Encodes a precondition for a mutation. This follows the model that the\n * backend accepts with the special case of an explicit \"empty\" precondition\n * (meaning no precondition).\n */\nexport class Precondition {\n private constructor(\n readonly updateTime?: SnapshotVersion,\n readonly exists?: boolean\n ) {\n debugAssert(\n updateTime === undefined || exists === undefined,\n 'Precondition can specify \"exists\" or \"updateTime\" but not both'\n );\n }\n\n /** Creates a new empty Precondition. */\n static none(): Precondition {\n return new Precondition();\n }\n\n /** Creates a new Precondition with an exists flag. */\n static exists(exists: boolean): Precondition {\n return new Precondition(undefined, exists);\n }\n\n /** Creates a new Precondition based on a version a document exists at. */\n static updateTime(version: SnapshotVersion): Precondition {\n return new Precondition(version);\n }\n\n /** Returns whether this Precondition is empty. */\n get isNone(): boolean {\n return this.updateTime === undefined && this.exists === undefined;\n }\n\n isEqual(other: Precondition): boolean {\n return (\n this.exists === other.exists &&\n (this.updateTime\n ? !!other.updateTime && this.updateTime.isEqual(other.updateTime)\n : !other.updateTime)\n );\n }\n}\n\n/** Returns true if the preconditions is valid for the given document. */\nexport function preconditionIsValidForDocument(\n precondition: Precondition,\n document: MutableDocument\n): boolean {\n if (precondition.updateTime !== undefined) {\n return (\n document.isFoundDocument() &&\n document.version.isEqual(precondition.updateTime)\n );\n } else if (precondition.exists !== undefined) {\n return precondition.exists === document.isFoundDocument();\n } else {\n debugAssert(precondition.isNone, 'Precondition should be empty');\n return true;\n }\n}\n\n/**\n * A mutation describes a self-contained change to a document. Mutations can\n * create, replace, delete, and update subsets of documents.\n *\n * Mutations not only act on the value of the document but also its version.\n *\n * For local mutations (mutations that haven't been committed yet), we preserve\n * the existing version for Set and Patch mutations. For Delete mutations, we\n * reset the version to 0.\n *\n * Here's the expected transition table.\n *\n * MUTATION APPLIED TO RESULTS IN\n *\n * SetMutation Document(v3) Document(v3)\n * SetMutation NoDocument(v3) Document(v0)\n * SetMutation InvalidDocument(v0) Document(v0)\n * PatchMutation Document(v3) Document(v3)\n * PatchMutation NoDocument(v3) NoDocument(v3)\n * PatchMutation InvalidDocument(v0) UnknownDocument(v3)\n * DeleteMutation Document(v3) NoDocument(v0)\n * DeleteMutation NoDocument(v3) NoDocument(v0)\n * DeleteMutation InvalidDocument(v0) NoDocument(v0)\n *\n * For acknowledged mutations, we use the updateTime of the WriteResponse as\n * the resulting version for Set and Patch mutations. As deletes have no\n * explicit update time, we use the commitTime of the WriteResponse for\n * Delete mutations.\n *\n * If a mutation is acknowledged by the backend but fails the precondition check\n * locally, we transition to an `UnknownDocument` and rely on Watch to send us\n * the updated version.\n *\n * Field transforms are used only with Patch and Set Mutations. We use the\n * `updateTransforms` message to store transforms, rather than the `transforms`s\n * messages.\n *\n * ## Subclassing Notes\n *\n * Every type of mutation needs to implement its own applyToRemoteDocument() and\n * applyToLocalView() to implement the actual behavior of applying the mutation\n * to some source document (see `setMutationApplyToRemoteDocument()` for an\n * example).\n */\nexport abstract class Mutation {\n abstract readonly type: MutationType;\n abstract readonly key: DocumentKey;\n abstract readonly precondition: Precondition;\n abstract readonly fieldTransforms: FieldTransform[];\n /**\n * Returns a `FieldMask` representing the fields that will be changed by\n * applying this mutation. Returns `null` if the mutation will overwrite the\n * entire document.\n */\n abstract getFieldMask(): FieldMask | null;\n}\n\n/**\n * A utility method to calculate a `Mutation` representing the overlay from the\n * final state of the document, and a `FieldMask` representing the fields that\n * are mutated by the local mutations.\n */\nexport function calculateOverlayMutation(\n doc: MutableDocument,\n mask: FieldMask | null\n): Mutation | null {\n if (!doc.hasLocalMutations || (mask && mask!.fields.length === 0)) {\n return null;\n }\n\n // mask is null when sets or deletes are applied to the current document.\n if (mask === null) {\n if (doc.isNoDocument()) {\n return new DeleteMutation(doc.key, Precondition.none());\n } else {\n return new SetMutation(doc.key, doc.data, Precondition.none());\n }\n } else {\n const docValue = doc.data;\n const patchValue = ObjectValue.empty();\n let maskSet = new SortedSet<FieldPath>(FieldPath.comparator);\n for (let path of mask.fields) {\n if (!maskSet.has(path)) {\n let value = docValue.field(path);\n // If we are deleting a nested field, we take the immediate parent as\n // the mask used to construct the resulting mutation.\n // Justification: Nested fields can create parent fields implicitly. If\n // only a leaf entry is deleted in later mutations, the parent field\n // should still remain, but we may have lost this information.\n // Consider mutation (foo.bar 1), then mutation (foo.bar delete()).\n // This leaves the final result (foo, {}). Despite the fact that `doc`\n // has the correct result, `foo` is not in `mask`, and the resulting\n // mutation would miss `foo`.\n if (value === null && path.length > 1) {\n path = path.popLast();\n value = docValue.field(path);\n }\n if (value === null) {\n patchValue.delete(path);\n } else {\n patchValue.set(path, value);\n }\n maskSet = maskSet.add(path);\n }\n }\n return new PatchMutation(\n doc.key,\n patchValue,\n new FieldMask(maskSet.toArray()),\n Precondition.none()\n );\n }\n}\n\n/**\n * Applies this mutation to the given document for the purposes of computing a\n * new remote document. If the input document doesn't match the expected state\n * (e.g. it is invalid or outdated), the document type may transition to\n * unknown.\n *\n * @param mutation - The mutation to apply.\n * @param document - The document to mutate. The input document can be an\n * invalid document if the client has no knowledge of the pre-mutation state\n * of the document.\n * @param mutationResult - The result of applying the mutation from the backend.\n */\nexport function mutationApplyToRemoteDocument(\n mutation: Mutation,\n document: MutableDocument,\n mutationResult: MutationResult\n): void {\n mutationVerifyKeyMatches(mutation, document);\n if (mutation instanceof SetMutation) {\n setMutationApplyToRemoteDocument(mutation, document, mutationResult);\n } else if (mutation instanceof PatchMutation) {\n patchMutationApplyToRemoteDocument(mutation, document, mutationResult);\n } else {\n debugAssert(\n mutation instanceof DeleteMutation,\n 'Unexpected mutation type: ' + mutation\n );\n deleteMutationApplyToRemoteDocument(mutation, document, mutationResult);\n }\n}\n\n/**\n * Applies this mutation to the given document for the purposes of computing\n * the new local view of a document. If the input document doesn't match the\n * expected state, the document is not modified.\n *\n * @param mutation - The mutation to apply.\n * @param document - The document to mutate. The input document can be an\n * invalid document if the client has no knowledge of the pre-mutation state\n * of the document.\n * @param previousMask - The fields that have been updated before applying this mutation.\n * @param localWriteTime - A timestamp indicating the local write time of the\n * batch this mutation is a part of.\n * @returns A `FieldMask` representing the fields that are changed by applying this mutation.\n */\nexport function mutationApplyToLocalView(\n mutation: Mutation,\n document: MutableDocument,\n previousMask: FieldMask | null,\n localWriteTime: Timestamp\n): FieldMask | null {\n mutationVerifyKeyMatches(mutation, document);\n\n if (mutation instanceof SetMutation) {\n return setMutationApplyToLocalView(\n mutation,\n document,\n previousMask,\n localWriteTime\n );\n } else if (mutation instanceof PatchMutation) {\n return patchMutationApplyToLocalView(\n mutation,\n document,\n previousMask,\n localWriteTime\n );\n } else {\n debugAssert(\n mutation instanceof DeleteMutation,\n 'Unexpected mutation type: ' + mutation\n );\n return deleteMutationApplyToLocalView(mutation, document, previousMask);\n }\n}\n\n/**\n * If this mutation is not idempotent, returns the base value to persist with\n * this mutation. If a base value is returned, the mutation is always applied\n * to this base value, even if document has already been updated.\n *\n * The base value is a sparse object that consists of only the document\n * fields for which this mutation contains a non-idempotent transformation\n * (e.g. a numeric increment). The provided value guarantees consistent\n * behavior for non-idempotent transforms and allow us to return the same\n * latency-compensated value even if the backend has already applied the\n * mutation. The base value is null for idempotent mutations, as they can be\n * re-played even if the backend has already applied them.\n *\n * @returns a base value to store along with the mutation, or null for\n * idempotent mutations.\n */\nexport function mutationExtractBaseValue(\n mutation: Mutation,\n document: Document\n): ObjectValue | null {\n let baseObject: ObjectValue | null = null;\n for (const fieldTransform of mutation.fieldTransforms) {\n const existingValue = document.data.field(fieldTransform.field);\n const coercedValue = computeTransformOperationBaseValue(\n fieldTransform.transform,\n existingValue || null\n );\n\n if (coercedValue != null) {\n if (baseObject === null) {\n baseObject = ObjectValue.empty();\n }\n baseObject.set(fieldTransform.field, coercedValue);\n }\n }\n return baseObject ? baseObject : null;\n}\n\nexport function mutationEquals(left: Mutation, right: Mutation): boolean {\n if (left.type !== right.type) {\n return false;\n }\n\n if (!left.key.isEqual(right.key)) {\n return false;\n }\n\n if (!left.precondition.isEqual(right.precondition)) {\n return false;\n }\n\n if (!fieldTransformsAreEqual(left.fieldTransforms, right.fieldTransforms)) {\n return false;\n }\n\n if (left.type === MutationType.Set) {\n return (left as SetMutation).value.isEqual((right as SetMutation).value);\n }\n\n if (left.type === MutationType.Patch) {\n return (\n (left as PatchMutation).data.isEqual((right as PatchMutation).data) &&\n (left as PatchMutation).fieldMask.isEqual(\n (right as PatchMutation).fieldMask\n )\n );\n }\n\n return true;\n}\n\nfunction mutationVerifyKeyMatches(\n mutation: Mutation,\n document: MutableDocument\n): void {\n debugAssert(\n document.key.isEqual(mutation.key),\n 'Can only apply a mutation to a document with the same key'\n );\n}\n\n/**\n * A mutation that creates or replaces the document at the given key with the\n * object value contents.\n */\nexport class SetMutation extends Mutation {\n constructor(\n readonly key: DocumentKey,\n readonly value: ObjectValue,\n readonly precondition: Precondition,\n readonly fieldTransforms: FieldTransform[] = []\n ) {\n super();\n }\n\n readonly type: MutationType = MutationType.Set;\n\n getFieldMask(): FieldMask | null {\n return null;\n }\n}\n\nfunction setMutationApplyToRemoteDocument(\n mutation: SetMutation,\n document: MutableDocument,\n mutationResult: MutationResult\n): void {\n // Unlike setMutationApplyToLocalView, if we're applying a mutation to a\n // remote document the server has accepted the mutation so the precondition\n // must have held.\n const newData = mutation.value.clone();\n const transformResults = serverTransformResults(\n mutation.fieldTransforms,\n document,\n mutationResult.transformResults\n );\n newData.setAll(transformResults);\n document\n .convertToFoundDocument(mutationResult.version, newData)\n .setHasCommittedMutations();\n}\n\nfunction setMutationApplyToLocalView(\n mutation: SetMutation,\n document: MutableDocument,\n previousMask: FieldMask | null,\n localWriteTime: Timestamp\n): FieldMask | null {\n if (!preconditionIsValidForDocument(mutation.precondition, document)) {\n // The mutation failed to apply (e.g. a document ID created with add()\n // caused a name collision).\n return previousMask;\n }\n\n const newData = mutation.value.clone();\n const transformResults = localTransformResults(\n mutation.fieldTransforms,\n localWriteTime,\n document\n );\n newData.setAll(transformResults);\n document\n .convertToFoundDocument(document.version, newData)\n .setHasLocalMutations();\n return null; // SetMutation overwrites all fields.\n}\n\n/**\n * A mutation that modifies fields of the document at the given key with the\n * given values. The values are applied through a field mask:\n *\n * * When a field is in both the mask and the values, the corresponding field\n * is updated.\n * * When a field is in neither the mask nor the values, the corresponding\n * field is unmodified.\n * * When a field is in the mask but not in the values, the corresponding field\n * is deleted.\n * * When a field is not in the mask but is in the values, the values map is\n * ignored.\n */\nexport class PatchMutation extends Mutation {\n constructor(\n readonly key: DocumentKey,\n readonly data: ObjectValue,\n readonly fieldMask: FieldMask,\n readonly precondition: Precondition,\n readonly fieldTransforms: FieldTransform[] = []\n ) {\n super();\n }\n\n readonly type: MutationType = MutationType.Patch;\n\n getFieldMask(): FieldMask | null {\n return this.fieldMask;\n }\n}\n\nfunction patchMutationApplyToRemoteDocument(\n mutation: PatchMutation,\n document: MutableDocument,\n mutationResult: MutationResult\n): void {\n if (!preconditionIsValidForDocument(mutation.precondition, document)) {\n // Since the mutation was not rejected, we know that the precondition\n // matched on the backend. We therefore must not have the expected version\n // of the document in our cache and convert to an UnknownDocument with a\n // known updateTime.\n document.convertToUnknownDocument(mutationResult.version);\n return;\n }\n\n const transformResults = serverTransformResults(\n mutation.fieldTransforms,\n document,\n mutationResult.transformResults\n );\n const newData = document.data;\n newData.setAll(getPatch(mutation));\n newData.setAll(transformResults);\n document\n .convertToFoundDocument(mutationResult.version, newData)\n .setHasCommittedMutations();\n}\n\nfunction patchMutationApplyToLocalView(\n mutation: PatchMutation,\n document: MutableDocument,\n previousMask: FieldMask | null,\n localWriteTime: Timestamp\n): FieldMask | null {\n if (!preconditionIsValidForDocument(mutation.precondition, document)) {\n return previousMask;\n }\n\n const transformResults = localTransformResults(\n mutation.fieldTransforms,\n localWriteTime,\n document\n );\n const newData = document.data;\n newData.setAll(getPatch(mutation));\n newData.setAll(transformResults);\n document\n .convertToFoundDocument(document.version, newData)\n .setHasLocalMutations();\n\n if (previousMask === null) {\n return null;\n }\n\n return previousMask\n .unionWith(mutation.fieldMask.fields)\n .unionWith(mutation.fieldTransforms.map(transform => transform.field));\n}\n\n/**\n * Returns a FieldPath/Value map with the content of the PatchMutation.\n */\nfunction getPatch(mutation: PatchMutation): Map<FieldPath, ProtoValue | null> {\n const result = new Map<FieldPath, ProtoValue | null>();\n mutation.fieldMask.fields.forEach(fieldPath => {\n if (!fieldPath.isEmpty()) {\n const newValue = mutation.data.field(fieldPath);\n result.set(fieldPath, newValue);\n }\n });\n return result;\n}\n\n/**\n * Creates a list of \"transform results\" (a transform result is a field value\n * representing the result of applying a transform) for use after a mutation\n * containing transforms has been acknowledged by the server.\n *\n * @param fieldTransforms - The field transforms to apply the result to.\n * @param mutableDocument - The current state of the document after applying all\n * previous mutations.\n * @param serverTransformResults - The transform results received by the server.\n * @returns The transform results list.\n */\nfunction serverTransformResults(\n fieldTransforms: FieldTransform[],\n mutableDocument: MutableDocument,\n serverTransformResults: Array<ProtoValue | null>\n): Map<FieldPath, ProtoValue> {\n const transformResults = new Map<FieldPath, ProtoValue>();\n hardAssert(\n fieldTransforms.length === serverTransformResults.length,\n 0x7f90,\n 'server transform result count should match field transform count',\n {\n serverTransformResultCount: serverTransformResults.length,\n fieldTransformCount: fieldTransforms.length\n }\n );\n\n for (let i = 0; i < serverTransformResults.length; i++) {\n const fieldTransform = fieldTransforms[i];\n const transform = fieldTransform.transform;\n const previousValue = mutableDocument.data.field(fieldTransform.field);\n transformResults.set(\n fieldTransform.field,\n applyTransformOperationToRemoteDocument(\n transform,\n previousValue,\n serverTransformResults[i]\n )\n );\n }\n return transformResults;\n}\n\n/**\n * Creates a list of \"transform results\" (a transform result is a field value\n * representing the result of applying a transform) for use when applying a\n * transform locally.\n *\n * @param fieldTransforms - The field transforms to apply the result to.\n * @param localWriteTime - The local time of the mutation (used to\n * generate ServerTimestampValues).\n * @param mutableDocument - The document to apply transforms on.\n * @returns The transform results list.\n */\nfunction localTransformResults(\n fieldTransforms: FieldTransform[],\n localWriteTime: Timestamp,\n mutableDocument: MutableDocument\n): Map<FieldPath, ProtoValue> {\n const transformResults = new Map<FieldPath, ProtoValue>();\n for (const fieldTransform of fieldTransforms) {\n const transform = fieldTransform.transform;\n\n const previousValue = mutableDocument.data.field(fieldTransform.field);\n transformResults.set(\n fieldTransform.field,\n applyTransformOperationToLocalView(\n transform,\n previousValue,\n localWriteTime\n )\n );\n }\n return transformResults;\n}\n\n/** A mutation that deletes the document at the given key. */\nexport class DeleteMutation extends Mutation {\n constructor(readonly key: DocumentKey, readonly precondition: Precondition) {\n super();\n }\n\n readonly type: MutationType = MutationType.Delete;\n readonly fieldTransforms: FieldTransform[] = [];\n\n getFieldMask(): FieldMask | null {\n return null;\n }\n}\n\nfunction deleteMutationApplyToRemoteDocument(\n mutation: DeleteMutation,\n document: MutableDocument,\n mutationResult: MutationResult\n): void {\n debugAssert(\n mutationResult.transformResults.length === 0,\n 'Transform results received by DeleteMutation.'\n );\n\n // Unlike applyToLocalView, if we're applying a mutation to a remote\n // document the server has accepted the mutation so the precondition must\n // have held.\n document\n .convertToNoDocument(mutationResult.version)\n .setHasCommittedMutations();\n}\n\nfunction deleteMutationApplyToLocalView(\n mutation: DeleteMutation,\n document: MutableDocument,\n previousMask: FieldMask | null\n): FieldMask | null {\n debugAssert(\n document.key.isEqual(mutation.key),\n 'Can only apply mutation to document with same key'\n );\n if (preconditionIsValidForDocument(mutation.precondition, document)) {\n document.convertToNoDocument(document.version).setHasLocalMutations();\n return null;\n }\n return previousMask;\n}\n\n/**\n * A mutation that verifies the existence of the document at the given key with\n * the provided precondition.\n *\n * The `verify` operation is only used in Transactions, and this class serves\n * primarily to facilitate serialization into protos.\n */\nexport class VerifyMutation extends Mutation {\n constructor(readonly key: DocumentKey, readonly precondition: Precondition) {\n super();\n }\n\n readonly type: MutationType = MutationType.Verify;\n readonly fieldTransforms: FieldTransform[] = [];\n\n getFieldMask(): FieldMask | null {\n return null;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SnapshotVersion } from '../core/snapshot_version';\nimport { BatchId } from '../core/types';\nimport { Timestamp } from '../lite-api/timestamp';\nimport { debugAssert, hardAssert } from '../util/assert';\nimport { arrayEquals } from '../util/misc';\n\nimport {\n documentKeySet,\n DocumentKeySet,\n MutationMap,\n DocumentVersionMap,\n documentVersionMap,\n newMutationMap,\n OverlayedDocumentMap\n} from './collections';\nimport { MutableDocument } from './document';\nimport { FieldMask } from './field_mask';\nimport {\n calculateOverlayMutation,\n Mutation,\n mutationApplyToLocalView,\n mutationApplyToRemoteDocument,\n mutationEquals,\n MutationResult\n} from './mutation';\n\n/**\n * A batch of mutations that will be sent as one unit to the backend.\n */\nexport class MutationBatch {\n /**\n * @param batchId - The unique ID of this mutation batch.\n * @param localWriteTime - The original write time of this mutation.\n * @param baseMutations - Mutations that are used to populate the base\n * values when this mutation is applied locally. This can be used to locally\n * overwrite values that are persisted in the remote document cache. Base\n * mutations are never sent to the backend.\n * @param mutations - The user-provided mutations in this mutation batch.\n * User-provided mutations are applied both locally and remotely on the\n * backend.\n */\n constructor(\n public batchId: BatchId,\n public localWriteTime: Timestamp,\n public baseMutations: Mutation[],\n public mutations: Mutation[]\n ) {\n debugAssert(mutations.length > 0, 'Cannot create an empty mutation batch');\n }\n\n /**\n * Applies all the mutations in this MutationBatch to the specified document\n * to compute the state of the remote document\n *\n * @param document - The document to apply mutations to.\n * @param batchResult - The result of applying the MutationBatch to the\n * backend.\n */\n applyToRemoteDocument(\n document: MutableDocument,\n batchResult: MutationBatchResult\n ): void {\n const mutationResults = batchResult.mutationResults;\n debugAssert(\n mutationResults.length === this.mutations.length,\n `Mismatch between mutations length\n (${this.mutations.length}) and mutation results length\n (${mutationResults.length}).`\n );\n\n for (let i = 0; i < this.mutations.length; i++) {\n const mutation = this.mutations[i];\n if (mutation.key.isEqual(document.key)) {\n const mutationResult = mutationResults[i];\n mutationApplyToRemoteDocument(mutation, document, mutationResult);\n }\n }\n }\n\n /**\n * Computes the local view of a document given all the mutations in this\n * batch.\n *\n * @param document - The document to apply mutations to.\n * @param mutatedFields - Fields that have been updated before applying this mutation batch.\n * @returns A `FieldMask` representing all the fields that are mutated.\n */\n applyToLocalView(\n document: MutableDocument,\n mutatedFields: FieldMask | null\n ): FieldMask | null {\n // First, apply the base state. This allows us to apply non-idempotent\n // transform against a consistent set of values.\n for (const mutation of this.baseMutations) {\n if (mutation.key.isEqual(document.key)) {\n mutatedFields = mutationApplyToLocalView(\n mutation,\n document,\n mutatedFields,\n this.localWriteTime\n );\n }\n }\n\n // Second, apply all user-provided mutations.\n for (const mutation of this.mutations) {\n if (mutation.key.isEqual(document.key)) {\n mutatedFields = mutationApplyToLocalView(\n mutation,\n document,\n mutatedFields,\n this.localWriteTime\n );\n }\n }\n return mutatedFields;\n }\n\n /**\n * Computes the local view for all provided documents given the mutations in\n * this batch. Returns a `DocumentKey` to `Mutation` map which can be used to\n * replace all the mutation applications.\n */\n applyToLocalDocumentSet(\n documentMap: OverlayedDocumentMap,\n documentsWithoutRemoteVersion: DocumentKeySet\n ): MutationMap {\n // TODO(mrschmidt): This implementation is O(n^2). If we apply the mutations\n // directly (as done in `applyToLocalView()`), we can reduce the complexity\n // to O(n).\n const overlays = newMutationMap();\n this.mutations.forEach(m => {\n const overlayedDocument = documentMap.get(m.key)!;\n // TODO(mutabledocuments): This method should take a MutableDocumentMap\n // and we should remove this cast.\n const mutableDocument =\n overlayedDocument.overlayedDocument as MutableDocument;\n let mutatedFields = this.applyToLocalView(\n mutableDocument,\n overlayedDocument.mutatedFields\n );\n // Set mutatedFields to null if the document is only from local mutations.\n // This creates a Set or Delete mutation, instead of trying to create a\n // patch mutation as the overlay.\n mutatedFields = documentsWithoutRemoteVersion.has(m.key)\n ? null\n : mutatedFields;\n const overlay = calculateOverlayMutation(mutableDocument, mutatedFields);\n if (overlay !== null) {\n overlays.set(m.key, overlay);\n }\n\n if (!mutableDocument.isValidDocument()) {\n mutableDocument.convertToNoDocument(SnapshotVersion.min());\n }\n });\n return overlays;\n }\n\n keys(): DocumentKeySet {\n return this.mutations.reduce(\n (keys, m) => keys.add(m.key),\n documentKeySet()\n );\n }\n\n isEqual(other: MutationBatch): boolean {\n return (\n this.batchId === other.batchId &&\n arrayEquals(this.mutations, other.mutations, (l, r) =>\n mutationEquals(l, r)\n ) &&\n arrayEquals(this.baseMutations, other.baseMutations, (l, r) =>\n mutationEquals(l, r)\n )\n );\n }\n}\n\n/** The result of applying a mutation batch to the backend. */\nexport class MutationBatchResult {\n private constructor(\n readonly batch: MutationBatch,\n readonly commitVersion: SnapshotVersion,\n readonly mutationResults: MutationResult[],\n /**\n * A pre-computed mapping from each mutated document to the resulting\n * version.\n */\n readonly docVersions: DocumentVersionMap\n ) {}\n\n /**\n * Creates a new MutationBatchResult for the given batch and results. There\n * must be one result for each mutation in the batch. This static factory\n * caches a document=>version mapping (docVersions).\n */\n static from(\n batch: MutationBatch,\n commitVersion: SnapshotVersion,\n results: MutationResult[]\n ): MutationBatchResult {\n hardAssert(\n batch.mutations.length === results.length,\n 0xe5da,\n 'Mutations sent must equal results received',\n {\n mutationsSent: batch.mutations.length,\n resultsReceived: results.length\n }\n );\n\n let versionMap = documentVersionMap();\n const mutations = batch.mutations;\n for (let i = 0; i < mutations.length; i++) {\n versionMap = versionMap.insert(mutations[i].key, results[i].version);\n }\n\n return new MutationBatchResult(batch, commitVersion, results, versionMap);\n }\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DocumentKey } from './document_key';\nimport { Mutation } from './mutation';\n\n/**\n * Representation of an overlay computed by Firestore.\n *\n * Holds information about a mutation and the largest batch id in Firestore when\n * the mutation was created.\n */\nexport class Overlay {\n constructor(readonly largestBatchId: number, readonly mutation: Mutation) {}\n\n getKey(): DocumentKey {\n return this.mutation.key;\n }\n\n isEqual(other: Overlay | null): boolean {\n return other !== null && this.mutation === other.mutation;\n }\n\n toString(): string {\n return `Overlay{\n largestBatchId: ${this.largestBatchId},\n mutation: ${this.mutation.toString()}\n }`;\n }\n}\n","/**\n * @license\n * Copyright 2017 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BloomFilter as ProtoBloomFilter } from '../protos/firestore_proto_api';\n\nexport class ExistenceFilter {\n constructor(public count: number, public unchangedNames?: ProtoBloomFilter) {}\n}\n","/**\n * @license\n * Copyright 2022 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Md5, Integer } from '@firebase/webchannel-wrapper/bloom-blob';\n\nimport { newTextEncoder } from '../platform/text_serializer';\n\nconst MAX_64_BIT_UNSIGNED_INTEGER = new Integer([0xffffffff, 0xffffffff], 0);\n\n// Hash a string using md5 hashing algorithm.\nfunction getMd5HashValue(value: string): Uint8Array {\n const encodedValue = newTextEncoder().encode(value);\n const md5 = new Md5();\n md5.update(encodedValue);\n return new Uint8Array(md5.digest());\n}\n\n// Interpret the 16 bytes array as two 64-bit unsigned integers, encoded using\n// 2’s complement using little endian.\nfunction get64BitUints(Bytes: Uint8Array): [Integer, Integer] {\n const dataView = new DataView(Bytes.buffer);\n const chunk1 = dataView.getUint32(0, /* littleEndian= */ true);\n const chunk2 = dataView.getUint32(4, /* littleEndian= */ true);\n const chunk3 = dataView.getUint32(8, /* littleEndian= */ true);\n const chunk4 = dataView.getUint32(12, /* littleEndian= */ true);\n const integer1 = new Integer([chunk1, chunk2], 0);\n const integer2 = new Integer([chunk3, chunk4], 0);\n return [integer1, integer2];\n}\n\nexport class BloomFilter {\n readonly bitCount: number;\n private readonly bitCountInInteger: Integer;\n\n constructor(\n readonly bitmap: Uint8Array,\n readonly padding: number,\n readonly hashCount: number\n ) {\n if (padding < 0 || padding >= 8) {\n throw new BloomFilterError(`Invalid padding: ${padding}`);\n }\n\n if (hashCount < 0) {\n throw new BloomFilterError(`Invalid hash count: ${hashCount}`);\n }\n\n if (bitmap.length > 0 && this.hashCount === 0) {\n // Only empty bloom filter can have 0 hash count.\n throw new BloomFilterError(`Invalid hash count: ${hashCount}`);\n }\n\n if (bitmap.length === 0 && padding !== 0) {\n // Empty bloom filter should have 0 padding.\n throw new BloomFilterError(\n `Invalid padding when bitmap length is 0: ${padding}`\n );\n }\n\n this.bitCount = bitmap.length * 8 - padding;\n // Set the bit count in Integer to avoid repetition in mightContain().\n this.bitCountInInteger = Integer.fromNumber(this.bitCount);\n }\n\n // Calculate the ith hash value based on the
Showing 512.00 KB of 2.34 MB. Use Edit/Download for full content.
Directory Contents
Dirs: 16 × Files: 68