UNPKG

@firebase/util

Version:

_NOTE: This is specifically tailored for Firebase JS SDK usage, if you are not a member of the Firebase team, please avoid using this package_

1,485 lines (1,466 loc) • 74.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var postinstall = require('./postinstall.js'); /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Firebase constants. Some of these (@defines) can be overridden at compile-time. */ const CONSTANTS = { /** * @define {boolean} Whether this is the client Node.js SDK. */ NODE_CLIENT: false, /** * @define {boolean} Whether this is the Admin Node.js SDK. */ NODE_ADMIN: false, /** * Firebase SDK Version */ SDK_VERSION: '${JSCORE_VERSION}' }; /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Throws an error if the provided assertion is falsy */ const assert = function (assertion, message) { if (!assertion) { throw assertionError(message); } }; /** * Returns an Error object suitable for throwing. */ const assertionError = function (message) { return new Error('Firebase Database (' + CONSTANTS.SDK_VERSION + ') INTERNAL ASSERT FAILED: ' + message); }; /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const stringToByteArray$1 = function (str) { // TODO(user): Use native implementations if/when available const out = []; let p = 0; for (let i = 0; i < str.length; i++) { let c = str.charCodeAt(i); if (c < 128) { out[p++] = c; } else if (c < 2048) { out[p++] = (c >> 6) | 192; out[p++] = (c & 63) | 128; } else if ((c & 0xfc00) === 0xd800 && i + 1 < str.length && (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00) { // Surrogate Pair c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff); out[p++] = (c >> 18) | 240; out[p++] = ((c >> 12) & 63) | 128; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } else { out[p++] = (c >> 12) | 224; out[p++] = ((c >> 6) & 63) | 128; out[p++] = (c & 63) | 128; } } return out; }; /** * Turns an array of numbers into the string given by the concatenation of the * characters to which the numbers correspond. * @param bytes Array of numbers representing characters. * @return Stringification of the array. */ const byteArrayToString = function (bytes) { // TODO(user): Use native implementations if/when available const out = []; let pos = 0, c = 0; while (pos < bytes.length) { const c1 = bytes[pos++]; if (c1 < 128) { out[c++] = String.fromCharCode(c1); } else if (c1 > 191 && c1 < 224) { const c2 = bytes[pos++]; out[c++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); } else if (c1 > 239 && c1 < 365) { // Surrogate Pair const c2 = bytes[pos++]; const c3 = bytes[pos++]; const c4 = bytes[pos++]; const u = (((c1 & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63)) - 0x10000; out[c++] = String.fromCharCode(0xd800 + (u >> 10)); out[c++] = String.fromCharCode(0xdc00 + (u & 1023)); } else { const c2 = bytes[pos++]; const c3 = bytes[pos++]; out[c++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); } } return out.join(''); }; // We define it as an object literal instead of a class because a class compiled down to es5 can't // be treeshaked. https://github.com/rollup/rollup/issues/1691 // Static lookup maps, lazily populated by init_() // TODO(dlarocque): Define this as a class, since we no longer target ES5. const base64 = { /** * Maps bytes to characters. */ byteToCharMap_: null, /** * Maps characters to bytes. */ charToByteMap_: null, /** * Maps bytes to websafe characters. * @private */ byteToCharMapWebSafe_: null, /** * Maps websafe characters to bytes. * @private */ charToByteMapWebSafe_: null, /** * Our default alphabet, shared between * ENCODED_VALS and ENCODED_VALS_WEBSAFE */ ENCODED_VALS_BASE: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789', /** * Our default alphabet. Value 64 (=) is special; it means "nothing." */ get ENCODED_VALS() { return this.ENCODED_VALS_BASE + '+/='; }, /** * Our websafe alphabet. */ get ENCODED_VALS_WEBSAFE() { return this.ENCODED_VALS_BASE + '-_.'; }, /** * Whether this browser supports the atob and btoa functions. This extension * started at Mozilla but is now implemented by many browsers. We use the * ASSUME_* variables to avoid pulling in the full useragent detection library * but still allowing the standard per-browser compilations. * */ HAS_NATIVE_SUPPORT: typeof atob === 'function', /** * Base64-encode an array of bytes. * * @param input An array of bytes (numbers with * value in [0, 255]) to encode. * @param webSafe Boolean indicating we should use the * alternative alphabet. * @return The base64 encoded string. */ encodeByteArray(input, webSafe) { if (!Array.isArray(input)) { throw Error('encodeByteArray takes an array as a parameter'); } this.init_(); const byteToCharMap = webSafe ? this.byteToCharMapWebSafe_ : this.byteToCharMap_; const output = []; for (let i = 0; i < input.length; i += 3) { const byte1 = input[i]; const haveByte2 = i + 1 < input.length; const byte2 = haveByte2 ? input[i + 1] : 0; const haveByte3 = i + 2 < input.length; const byte3 = haveByte3 ? input[i + 2] : 0; const outByte1 = byte1 >> 2; const outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4); let outByte3 = ((byte2 & 0x0f) << 2) | (byte3 >> 6); let outByte4 = byte3 & 0x3f; if (!haveByte3) { outByte4 = 64; if (!haveByte2) { outByte3 = 64; } } output.push(byteToCharMap[outByte1], byteToCharMap[outByte2], byteToCharMap[outByte3], byteToCharMap[outByte4]); } return output.join(''); }, /** * Base64-encode a string. * * @param input A string to encode. * @param webSafe If true, we should use the * alternative alphabet. * @return The base64 encoded string. */ encodeString(input, webSafe) { // Shortcut for Mozilla browsers that implement // a native base64 encoder in the form of "btoa/atob" if (this.HAS_NATIVE_SUPPORT && !webSafe) { return btoa(input); } return this.encodeByteArray(stringToByteArray$1(input), webSafe); }, /** * Base64-decode a string. * * @param input to decode. * @param webSafe True if we should use the * alternative alphabet. * @return string representing the decoded value. */ decodeString(input, webSafe) { // Shortcut for Mozilla browsers that implement // a native base64 encoder in the form of "btoa/atob" if (this.HAS_NATIVE_SUPPORT && !webSafe) { return atob(input); } return byteArrayToString(this.decodeStringToByteArray(input, webSafe)); }, /** * Base64-decode a string. * * In base-64 decoding, groups of four characters are converted into three * bytes. If the encoder did not apply padding, the input length may not * be a multiple of 4. * * In this case, the last group will have fewer than 4 characters, and * padding will be inferred. If the group has one or two characters, it decodes * to one byte. If the group has three characters, it decodes to two bytes. * * @param input Input to decode. * @param webSafe True if we should use the web-safe alphabet. * @return bytes representing the decoded value. */ decodeStringToByteArray(input, webSafe) { this.init_(); const charToByteMap = webSafe ? this.charToByteMapWebSafe_ : this.charToByteMap_; const output = []; for (let i = 0; i < input.length;) { const byte1 = charToByteMap[input.charAt(i++)]; const haveByte2 = i < input.length; const byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0; ++i; const haveByte3 = i < input.length; const byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 64; ++i; const haveByte4 = i < input.length; const byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64; ++i; if (byte1 == null || byte2 == null || byte3 == null || byte4 == null) { throw new DecodeBase64StringError(); } const outByte1 = (byte1 << 2) | (byte2 >> 4); output.push(outByte1); if (byte3 !== 64) { const outByte2 = ((byte2 << 4) & 0xf0) | (byte3 >> 2); output.push(outByte2); if (byte4 !== 64) { const outByte3 = ((byte3 << 6) & 0xc0) | byte4; output.push(outByte3); } } } return output; }, /** * Lazy static initialization function. Called before * accessing any of the static map variables. * @private */ init_() { if (!this.byteToCharMap_) { this.byteToCharMap_ = {}; this.charToByteMap_ = {}; this.byteToCharMapWebSafe_ = {}; this.charToByteMapWebSafe_ = {}; // We want quick mappings back and forth, so we precompute two maps. for (let i = 0; i < this.ENCODED_VALS.length; i++) { this.byteToCharMap_[i] = this.ENCODED_VALS.charAt(i); this.charToByteMap_[this.byteToCharMap_[i]] = i; this.byteToCharMapWebSafe_[i] = this.ENCODED_VALS_WEBSAFE.charAt(i); this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i; // Be forgiving when decoding and correctly decode both encodings. if (i >= this.ENCODED_VALS_BASE.length) { this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(i)] = i; this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(i)] = i; } } } } }; /** * An error encountered while decoding base64 string. */ class DecodeBase64StringError extends Error { constructor() { super(...arguments); this.name = 'DecodeBase64StringError'; } } /** * URL-safe base64 encoding */ const base64Encode = function (str) { const utf8Bytes = stringToByteArray$1(str); return base64.encodeByteArray(utf8Bytes, true); }; /** * URL-safe base64 encoding (without "." padding in the end). * e.g. Used in JSON Web Token (JWT) parts. */ const base64urlEncodeWithoutPadding = function (str) { // Use base64url encoding and remove padding in the end (dot characters). return base64Encode(str).replace(/\./g, ''); }; /** * URL-safe base64 decoding * * NOTE: DO NOT use the global atob() function - it does NOT support the * base64Url variant encoding. * * @param str To be decoded * @return Decoded result, if possible */ const base64Decode = function (str) { try { return base64.decodeString(str, true); } catch (e) { console.error('base64Decode failed: ', e); } return null; }; /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Do a deep-copy of basic JavaScript Objects or Arrays. */ function deepCopy(value) { return deepExtend(undefined, value); } /** * Copy properties from source to target (recursively allows extension * of Objects and Arrays). Scalar values in the target are over-written. * If target is undefined, an object of the appropriate type will be created * (and returned). * * We recursively copy all child properties of plain Objects in the source- so * that namespace- like dictionaries are merged. * * Note that the target can be a function, in which case the properties in * the source Object are copied onto it as static properties of the Function. * * Note: we don't merge __proto__ to prevent prototype pollution */ function deepExtend(target, source) { if (!(source instanceof Object)) { return source; } switch (source.constructor) { case Date: // Treat Dates like scalars; if the target date object had any child // properties - they will be lost! const dateValue = source; return new Date(dateValue.getTime()); case Object: if (target === undefined) { target = {}; } break; case Array: // Always copy the array source and overwrite the target. target = []; break; default: // Not a plain Object - treat it as a scalar. return source; } for (const prop in source) { // use isValidKey to guard against prototype pollution. See https://snyk.io/vuln/SNYK-JS-LODASH-450202 if (!source.hasOwnProperty(prop) || !isValidKey(prop)) { continue; } target[prop] = deepExtend(target[prop], source[prop]); } return target; } function isValidKey(key) { return key !== '__proto__'; } /** * @license * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Polyfill for `globalThis` object. * @returns the `globalThis` object for the given environment. * @public */ function getGlobal() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('Unable to locate global object.'); } /** * @license * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const getDefaultsFromGlobal = () => getGlobal().__FIREBASE_DEFAULTS__; /** * Attempt to read defaults from a JSON string provided to * process(.)env(.)__FIREBASE_DEFAULTS__ or a JSON file whose path is in * process(.)env(.)__FIREBASE_DEFAULTS_PATH__ * The dots are in parens because certain compilers (Vite?) cannot * handle seeing that variable in comments. * See https://github.com/firebase/firebase-js-sdk/issues/6838 */ const getDefaultsFromEnvVariable = () => { if (typeof process === 'undefined' || typeof process.env === 'undefined') { return; } const defaultsJsonString = process.env.__FIREBASE_DEFAULTS__; if (defaultsJsonString) { return JSON.parse(defaultsJsonString); } }; const getDefaultsFromCookie = () => { if (typeof document === 'undefined') { return; } let match; try { match = document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/); } catch (e) { // Some environments such as Angular Universal SSR have a // `document` object but error on accessing `document.cookie`. return; } const decoded = match && base64Decode(match[1]); return decoded && JSON.parse(decoded); }; /** * Get the __FIREBASE_DEFAULTS__ object. It checks in order: * (1) if such an object exists as a property of `globalThis` * (2) if such an object was provided on a shell environment variable * (3) if such an object exists in a cookie * @public */ const getDefaults = () => { try { return (postinstall.getDefaultsFromPostinstall() || getDefaultsFromGlobal() || getDefaultsFromEnvVariable() || getDefaultsFromCookie()); } catch (e) { /** * Catch-all for being unable to get __FIREBASE_DEFAULTS__ due * to any environment case we have not accounted for. Log to * info instead of swallowing so we can find these unknown cases * and add paths for them if needed. */ console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${e}`); return; } }; /** * Returns emulator host stored in the __FIREBASE_DEFAULTS__ object * for the given product. * @returns a URL host formatted like `127.0.0.1:9999` or `[::1]:4000` if available * @public */ const getDefaultEmulatorHost = (productName) => { var _a, _b; return (_b = (_a = getDefaults()) === null || _a === void 0 ? void 0 : _a.emulatorHosts) === null || _b === void 0 ? void 0 : _b[productName]; }; /** * Returns emulator hostname and port stored in the __FIREBASE_DEFAULTS__ object * for the given product. * @returns a pair of hostname and port like `["::1", 4000]` if available * @public */ const getDefaultEmulatorHostnameAndPort = (productName) => { const host = getDefaultEmulatorHost(productName); if (!host) { return undefined; } const separatorIndex = host.lastIndexOf(':'); // Finding the last since IPv6 addr also has colons. if (separatorIndex <= 0 || separatorIndex + 1 === host.length) { throw new Error(`Invalid host ${host} with no separate hostname and port!`); } // eslint-disable-next-line no-restricted-globals const port = parseInt(host.substring(separatorIndex + 1), 10); if (host[0] === '[') { // Bracket-quoted `[ipv6addr]:port` => return "ipv6addr" (without brackets). return [host.substring(1, separatorIndex - 1), port]; } else { return [host.substring(0, separatorIndex), port]; } }; /** * Returns Firebase app config stored in the __FIREBASE_DEFAULTS__ object. * @public */ const getDefaultAppConfig = () => { var _a; return (_a = getDefaults()) === null || _a === void 0 ? void 0 : _a.config; }; /** * Returns an experimental setting on the __FIREBASE_DEFAULTS__ object (properties * prefixed by "_") * @public */ const getExperimentalSetting = (name) => { var _a; return (_a = getDefaults()) === null || _a === void 0 ? void 0 : _a[`_${name}`]; }; /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class Deferred { constructor() { this.reject = () => { }; this.resolve = () => { }; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); } /** * Our API internals are not promisified and cannot because our callback APIs have subtle expectations around * invoking promises inline, which Promises are forbidden to do. This method accepts an optional node-style callback * and returns a node-style callback which will resolve or reject the Deferred's promise. */ wrapCallback(callback) { return (error, value) => { if (error) { this.reject(error); } else { this.resolve(value); } if (typeof callback === 'function') { // Attaching noop handler just in case developer wasn't expecting // promises this.promise.catch(() => { }); // Some of our callbacks don't expect a value and our own tests // assert that the parameter length is 1 if (callback.length === 1) { callback(error); } else { callback(error, value); } } }; } } /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function createMockUserToken(token, projectId) { if (token.uid) { throw new Error('The "uid" field is no longer supported by mockUserToken. Please use "sub" instead for Firebase Auth User ID.'); } // Unsecured JWTs use "none" as the algorithm. const header = { alg: 'none', type: 'JWT' }; const project = projectId || 'demo-project'; const iat = token.iat || 0; const sub = token.sub || token.user_id; if (!sub) { throw new Error("mockUserToken must contain 'sub' or 'user_id' field!"); } const payload = Object.assign({ // Set all required fields to decent defaults iss: `https://securetoken.google.com/${project}`, aud: project, iat, exp: iat + 3600, auth_time: iat, sub, user_id: sub, firebase: { sign_in_provider: 'custom', identities: {} } }, token); // Unsecured JWTs use the empty string as a signature. const signature = ''; return [ base64urlEncodeWithoutPadding(JSON.stringify(header)), base64urlEncodeWithoutPadding(JSON.stringify(payload)), signature ].join('.'); } /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Returns navigator.userAgent string or '' if it's not defined. * @return user agent string */ function getUA() { if (typeof navigator !== 'undefined' && typeof navigator['userAgent'] === 'string') { return navigator['userAgent']; } else { return ''; } } /** * Detect Cordova / PhoneGap / Ionic frameworks on a mobile device. * * Deliberately does not rely on checking `file://` URLs (as this fails PhoneGap * in the Ripple emulator) nor Cordova `onDeviceReady`, which would normally * wait for a callback. */ function isMobileCordova() { return (typeof window !== 'undefined' && // @ts-ignore Setting up an broadly applicable index signature for Window // just to deal with this case would probably be a bad idea. !!(window['cordova'] || window['phonegap'] || window['PhoneGap']) && /ios|iphone|ipod|ipad|android|blackberry|iemobile/i.test(getUA())); } /** * Detect Node.js. * * @return true if Node.js environment is detected or specified. */ // Node detection logic from: https://github.com/iliakan/detect-node/ function isNode() { var _a; const forceEnvironment = (_a = getDefaults()) === null || _a === void 0 ? void 0 : _a.forceEnvironment; if (forceEnvironment === 'node') { return true; } else if (forceEnvironment === 'browser') { return false; } try { return (Object.prototype.toString.call(global.process) === '[object process]'); } catch (e) { return false; } } /** * Detect Browser Environment. * Note: This will return true for certain test frameworks that are incompletely * mimicking a browser, and should not lead to assuming all browser APIs are * available. */ function isBrowser() { return typeof window !== 'undefined' || isWebWorker(); } /** * Detect Web Worker context. */ function isWebWorker() { return (typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' && self instanceof WorkerGlobalScope); } /** * Detect Cloudflare Worker context. */ function isCloudflareWorker() { return (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers'); } function isBrowserExtension() { const runtime = typeof chrome === 'object' ? chrome.runtime : typeof browser === 'object' ? browser.runtime : undefined; return typeof runtime === 'object' && runtime.id !== undefined; } /** * Detect React Native. * * @return true if ReactNative environment is detected. */ function isReactNative() { return (typeof navigator === 'object' && navigator['product'] === 'ReactNative'); } /** Detects Electron apps. */ function isElectron() { return getUA().indexOf('Electron/') >= 0; } /** Detects Internet Explorer. */ function isIE() { const ua = getUA(); return ua.indexOf('MSIE ') >= 0 || ua.indexOf('Trident/') >= 0; } /** Detects Universal Windows Platform apps. */ function isUWP() { return getUA().indexOf('MSAppHost/') >= 0; } /** * Detect whether the current SDK build is the Node version. * * @return true if it's the Node SDK build. */ function isNodeSdk() { return CONSTANTS.NODE_CLIENT === true || CONSTANTS.NODE_ADMIN === true; } /** Returns true if we are running in Safari. */ function isSafari() { return (!isNode() && !!navigator.userAgent && navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')); } /** * This method checks if indexedDB is supported by current browser/service worker context * @return true if indexedDB is supported by current browser/service worker context */ function isIndexedDBAvailable() { try { return typeof indexedDB === 'object'; } catch (e) { return false; } } /** * This method validates browser/sw context for indexedDB by opening a dummy indexedDB database and reject * if errors occur during the database open operation. * * @throws exception if current browser/sw context can't run idb.open (ex: Safari iframe, Firefox * private browsing) */ function validateIndexedDBOpenable() { return new Promise((resolve, reject) => { try { let preExist = true; const DB_CHECK_NAME = 'validate-browser-context-for-indexeddb-analytics-module'; const request = self.indexedDB.open(DB_CHECK_NAME); request.onsuccess = () => { request.result.close(); // delete database only when it doesn't pre-exist if (!preExist) { self.indexedDB.deleteDatabase(DB_CHECK_NAME); } resolve(true); }; request.onupgradeneeded = () => { preExist = false; }; request.onerror = () => { var _a; reject(((_a = request.error) === null || _a === void 0 ? void 0 : _a.message) || ''); }; } catch (error) { reject(error); } }); } /** * * This method checks whether cookie is enabled within current browser * @return true if cookie is enabled within current browser */ function areCookiesEnabled() { if (typeof navigator === 'undefined' || !navigator.cookieEnabled) { return false; } return true; } /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Standardized Firebase Error. * * Usage: * * // TypeScript string literals for type-safe codes * type Err = * 'unknown' | * 'object-not-found' * ; * * // Closure enum for type-safe error codes * // at-enum {string} * var Err = { * UNKNOWN: 'unknown', * OBJECT_NOT_FOUND: 'object-not-found', * } * * let errors: Map<Err, string> = { * 'generic-error': "Unknown error", * 'file-not-found': "Could not find file: {$file}", * }; * * // Type-safe function - must pass a valid error code as param. * let error = new ErrorFactory<Err>('service', 'Service', errors); * * ... * throw error.create(Err.GENERIC); * ... * throw error.create(Err.FILE_NOT_FOUND, {'file': fileName}); * ... * // Service: Could not file file: foo.txt (service/file-not-found). * * catch (e) { * assert(e.message === "Could not find file: foo.txt."); * if ((e as FirebaseError)?.code === 'service/file-not-found') { * console.log("Could not read file: " + e['file']); * } * } */ const ERROR_NAME = 'FirebaseError'; // Based on code from: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types class FirebaseError extends Error { constructor( /** The error code for this error. */ code, message, /** Custom data for this error. */ customData) { super(message); this.code = code; this.customData = customData; /** The custom name for all FirebaseErrors. */ this.name = ERROR_NAME; // Fix For ES5 // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work // TODO(dlarocque): Replace this with `new.target`: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget // which we can now use since we no longer target ES5. Object.setPrototypeOf(this, FirebaseError.prototype); // Maintains proper stack trace for where our error was thrown. // Only available on V8. if (Error.captureStackTrace) { Error.captureStackTrace(this, ErrorFactory.prototype.create); } } } class ErrorFactory { constructor(service, serviceName, errors) { this.service = service; this.serviceName = serviceName; this.errors = errors; } create(code, ...data) { const customData = data[0] || {}; const fullCode = `${this.service}/${code}`; const template = this.errors[code]; const message = template ? replaceTemplate(template, customData) : 'Error'; // Service Name: Error message (service/code). const fullMessage = `${this.serviceName}: ${message} (${fullCode}).`; const error = new FirebaseError(fullCode, fullMessage, customData); return error; } } function replaceTemplate(template, data) { return template.replace(PATTERN, (_, key) => { const value = data[key]; return value != null ? String(value) : `<${key}?>`; }); } const PATTERN = /\{\$([^}]+)}/g; /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Evaluates a JSON string into a javascript object. * * @param {string} str A string containing JSON. * @return {*} The javascript object representing the specified JSON. */ function jsonEval(str) { return JSON.parse(str); } /** * Returns JSON representing a javascript object. * @param {*} data JavaScript object to be stringified. * @return {string} The JSON contents of the object. */ function stringify(data) { return JSON.stringify(data); } /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Decodes a Firebase auth. token into constituent parts. * * Notes: * - May return with invalid / incomplete claims if there's no native base64 decoding support. * - Doesn't check if the token is actually valid. */ const decode = function (token) { let header = {}, claims = {}, data = {}, signature = ''; try { const parts = token.split('.'); header = jsonEval(base64Decode(parts[0]) || ''); claims = jsonEval(base64Decode(parts[1]) || ''); signature = parts[2]; data = claims['d'] || {}; delete claims['d']; } catch (e) { } return { header, claims, data, signature }; }; /** * Decodes a Firebase auth. token and checks the validity of its time-based claims. Will return true if the * token is within the time window authorized by the 'nbf' (not-before) and 'iat' (issued-at) claims. * * Notes: * - May return a false negative if there's no native base64 decoding support. * - Doesn't check if the token is actually valid. */ const isValidTimestamp = function (token) { const claims = decode(token).claims; const now = Math.floor(new Date().getTime() / 1000); let validSince = 0, validUntil = 0; if (typeof claims === 'object') { if (claims.hasOwnProperty('nbf')) { validSince = claims['nbf']; } else if (claims.hasOwnProperty('iat')) { validSince = claims['iat']; } if (claims.hasOwnProperty('exp')) { validUntil = claims['exp']; } else { // token will expire after 24h by default validUntil = validSince + 86400; } } return (!!now && !!validSince && !!validUntil && now >= validSince && now <= validUntil); }; /** * Decodes a Firebase auth. token and returns its issued at time if valid, null otherwise. * * Notes: * - May return null if there's no native base64 decoding support. * - Doesn't check if the token is actually valid. */ const issuedAtTime = function (token) { const claims = decode(token).claims; if (typeof claims === 'object' && claims.hasOwnProperty('iat')) { return claims['iat']; } return null; }; /** * Decodes a Firebase auth. token and checks the validity of its format. Expects a valid issued-at time. * * Notes: * - May return a false negative if there's no native base64 decoding support. * - Doesn't check if the token is actually valid. */ const isValidFormat = function (token) { const decoded = decode(token), claims = decoded.claims; return !!claims && typeof claims === 'object' && claims.hasOwnProperty('iat'); }; /** * Attempts to peer into an auth token and determine if it's an admin auth token by looking at the claims portion. * * Notes: * - May return a false negative if there's no native base64 decoding support. * - Doesn't check if the token is actually valid. */ const isAdmin = function (token) { const claims = decode(token).claims; return typeof claims === 'object' && claims['admin'] === true; }; /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function contains(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); } function safeGet(obj, key) { if (Object.prototype.hasOwnProperty.call(obj, key)) { return obj[key]; } else { return undefined; } } function isEmpty(obj) { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { return false; } } return true; } function map(obj, fn, contextObj) { const res = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { res[key] = fn.call(contextObj, obj[key], key, obj); } } return res; } /** * Deep equal two objects. Support Arrays and Objects. */ function deepEqual(a, b) { if (a === b) { return true; } const aKeys = Object.keys(a); const bKeys = Object.keys(b); for (const k of aKeys) { if (!bKeys.includes(k)) { return false; } const aProp = a[k]; const bProp = b[k]; if (isObject(aProp) && isObject(bProp)) { if (!deepEqual(aProp, bProp)) { return false; } } else if (aProp !== bProp) { return false; } } for (const k of bKeys) { if (!aKeys.includes(k)) { return false; } } return true; } function isObject(thing) { return thing !== null && typeof thing === 'object'; } /** * @license * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Rejects if the given promise doesn't resolve in timeInMS milliseconds. * @internal */ function promiseWithTimeout(promise, timeInMS = 2000) { const deferredPromise = new Deferred(); setTimeout(() => deferredPromise.reject('timeout!'), timeInMS); promise.then(deferredPromise.resolve, deferredPromise.reject); return deferredPromise.promise; } /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Returns a querystring-formatted string (e.g. &arg=val&arg2=val2) from a * params object (e.g. {arg: 'val', arg2: 'val2'}) * Note: You must prepend it with ? when adding it to a URL. */ function querystring(querystringParams) { const params = []; for (const [key, value] of Object.entries(querystringParams)) { if (Array.isArray(value)) { value.forEach(arrayVal => { params.push(encodeURIComponent(key) + '=' + encodeURIComponent(arrayVal)); }); } else { params.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); } } return params.length ? '&' + params.join('&') : ''; } /** * Decodes a querystring (e.g. ?arg=val&arg2=val2) into a params object * (e.g. {arg: 'val', arg2: 'val2'}) */ function querystringDecode(querystring) { const obj = {}; const tokens = querystring.replace(/^\?/, '').split('&'); tokens.forEach(token => { if (token) { const [key, value] = token.split('='); obj[decodeURIComponent(key)] = decodeURIComponent(value); } }); return obj; } /** * Extract the query string part of a URL, including the leading question mark (if present). */ function extractQuerystring(url) { const queryStart = url.indexOf('?'); if (!queryStart) { return ''; } const fragmentStart = url.indexOf('#', queryStart); return url.substring(queryStart, fragmentStart > 0 ? fragmentStart : undefined); } /** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview SHA-1 cryptographic hash. * Variable names follow the notation in FIPS PUB 180-3: * http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf. * * Usage: * var sha1 = new sha1(); * sha1.update(bytes); * var hash = sha1.digest(); * * Performance: * Chrome 23: ~400 Mbit/s * Firefox 16: ~250 Mbit/s * */ /** * SHA-1 cryptographic hash constructor. * * The properties declared here are discussed in the above algorithm document. * @constructor * @final * @struct */ class Sha1 { constructor() { /** * Holds the previous values of accumulated variables a-e in the compress_ * function. * @private */ this.chain_ = []; /** * A buffer holding the partially computed hash result. * @private */ this.buf_ = []; /** * An array of 80 bytes, each a part of the message to be hashed. Referred to * as the message schedule in the docs. * @private */ this.W_ = []; /** * Contains data needed to pad messages less than 64 bytes. * @private */ this.pad_ = []; /** * @private {number} */ this.inbuf_ = 0; /** * @private {number} */ this.total_ = 0; this.blockSize = 512 / 8; this.pad_[0] = 128; for (let i = 1; i < this.blockSize; ++i) { this.pad_[i] = 0; } this.reset(); } reset() { this.chain_[0] = 0x67452301; this.chain_[1] = 0xefcdab89; this.chain_[2] = 0x98badcfe; this.chain_[3] = 0x10325476; this.chain_[4] = 0xc3d2e1f0; this.inbuf_ = 0; this.total_ = 0; } /** * Internal compress helper function. * @param buf Block to compress. * @param offset Offset of the block in the buffer. * @private */ compress_(buf, offset) { if (!offset) { offset = 0; } const W = this.W_; // get 16 big endian words if (typeof buf === 'string') { for (let i = 0; i < 16; i++) { // TODO(user): [bug 8140122] Recent versions of Safari for Mac OS and iOS // have a bug that turns the post-increment ++ operator into pre-increment // during JIT compilation. We have code that depends heavily on SHA-1 for // correctness and which is affected by this bug, so I've removed all uses // of post-increment ++ in which the result value is used. We can revert // this change once the Safari bug // (https://bugs.webkit.org/show_bug.cgi?id=109036) has been fixed and // most clients have been updated. W[i] = (buf.charCodeAt(offset) << 24) | (buf.charCodeAt(offset + 1) << 16) | (buf.charCodeAt(offset + 2) << 8) | buf.charCodeAt(offset + 3); offset += 4; } } else { for (let i = 0; i < 16; i++) { W[i] = (buf[offset] << 24) | (buf[offset + 1] << 16) | (buf[offset + 2] << 8) | buf[offset + 3]; offset += 4; } } // expand to 80 words for (let i = 16; i < 80; i++) { const t = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; W[i] = ((t << 1) | (t >>> 31)) & 0xffffffff; } let a = this.chain_[0]; let b = this.chain_[1]; let c = this.chain_[2]; let d = this.chain_[3]; let e = this.chain_[4]; let f, k; // TODO(user): Try to unroll this loop to speed up the computation. for (let i = 0; i < 80; i++) { if (i < 40) { if (i < 20) { f = d ^ (b & (c ^ d)); k = 0x5a827999; } else { f = b ^ c ^ d; k = 0x6ed9eba1; } } else { if (i < 60) { f = (b & c) | (d & (b | c)); k = 0x8f1bbcdc;