2543 lines
92 KiB
JavaScript
2543 lines
92 KiB
JavaScript
/**
|
|
* @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.
|
|
*/
|
|
|
|
/**
|
|
* @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_()
|
|
const base64 = {
|
|
/**
|
|
* Maps bytes to characters.
|
|
*/
|
|
byteToCharMap_: null,
|
|
/**
|
|
* Maps characters to bytes.
|
|
*/
|
|
charToByteMap_: null,
|
|
/**
|
|
* Maps bytes to websafe characters.
|
|
* @private
|
|
*/
|
|
byteToCharMapWebSafe_: null,
|
|
/**
|
|
* Maps websafe characters to bytes.
|
|
* @private
|
|
*/
|
|
charToByteMapWebSafe_: null,
|
|
/**
|
|
* Our default alphabet, shared between
|
|
* ENCODED_VALS and ENCODED_VALS_WEBSAFE
|
|
*/
|
|
ENCODED_VALS_BASE: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789',
|
|
/**
|
|
* Our default alphabet. Value 64 (=) is special; it means "nothing."
|
|
*/
|
|
get ENCODED_VALS() {
|
|
return this.ENCODED_VALS_BASE + '+/=';
|
|
},
|
|
/**
|
|
* Our websafe alphabet.
|
|
*/
|
|
get ENCODED_VALS_WEBSAFE() {
|
|
return this.ENCODED_VALS_BASE + '-_.';
|
|
},
|
|
/**
|
|
* Whether this browser supports the atob and btoa functions. This extension
|
|
* started at Mozilla but is now implemented by many browsers. We use the
|
|
* ASSUME_* variables to avoid pulling in the full useragent detection library
|
|
* but still allowing the standard per-browser compilations.
|
|
*
|
|
*/
|
|
HAS_NATIVE_SUPPORT: typeof atob === 'function',
|
|
/**
|
|
* Base64-encode an array of bytes.
|
|
*
|
|
* @param input An array of bytes (numbers with
|
|
* value in [0, 255]) to encode.
|
|
* @param webSafe Boolean indicating we should use the
|
|
* alternative alphabet.
|
|
* @return The base64 encoded string.
|
|
*/
|
|
encodeByteArray(input, webSafe) {
|
|
if (!Array.isArray(input)) {
|
|
throw Error('encodeByteArray takes an array as a parameter');
|
|
}
|
|
this.init_();
|
|
const byteToCharMap = webSafe
|
|
? this.byteToCharMapWebSafe_
|
|
: this.byteToCharMap_;
|
|
const output = [];
|
|
for (let i = 0; i < input.length; i += 3) {
|
|
const byte1 = input[i];
|
|
const haveByte2 = i + 1 < input.length;
|
|
const byte2 = haveByte2 ? input[i + 1] : 0;
|
|
const haveByte3 = i + 2 < input.length;
|
|
const byte3 = haveByte3 ? input[i + 2] : 0;
|
|
const outByte1 = byte1 >> 2;
|
|
const outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
|
|
let outByte3 = ((byte2 & 0x0f) << 2) | (byte3 >> 6);
|
|
let outByte4 = byte3 & 0x3f;
|
|
if (!haveByte3) {
|
|
outByte4 = 64;
|
|
if (!haveByte2) {
|
|
outByte3 = 64;
|
|
}
|
|
}
|
|
output.push(byteToCharMap[outByte1], byteToCharMap[outByte2], byteToCharMap[outByte3], byteToCharMap[outByte4]);
|
|
}
|
|
return output.join('');
|
|
},
|
|
/**
|
|
* Base64-encode a string.
|
|
*
|
|
* @param input A string to encode.
|
|
* @param webSafe If true, we should use the
|
|
* alternative alphabet.
|
|
* @return The base64 encoded string.
|
|
*/
|
|
encodeString(input, webSafe) {
|
|
// Shortcut for Mozilla browsers that implement
|
|
// a native base64 encoder in the form of "btoa/atob"
|
|
if (this.HAS_NATIVE_SUPPORT && !webSafe) {
|
|
return btoa(input);
|
|
}
|
|
return this.encodeByteArray(stringToByteArray$1(input), webSafe);
|
|
},
|
|
/**
|
|
* Base64-decode a string.
|
|
*
|
|
* @param input to decode.
|
|
* @param webSafe True if we should use the
|
|
* alternative alphabet.
|
|
* @return string representing the decoded value.
|
|
*/
|
|
decodeString(input, webSafe) {
|
|
// Shortcut for Mozilla browsers that implement
|
|
// a native base64 encoder in the form of "btoa/atob"
|
|
if (this.HAS_NATIVE_SUPPORT && !webSafe) {
|
|
return atob(input);
|
|
}
|
|
return byteArrayToString(this.decodeStringToByteArray(input, webSafe));
|
|
},
|
|
/**
|
|
* Base64-decode a string.
|
|
*
|
|
* In base-64 decoding, groups of four characters are converted into three
|
|
* bytes. If the encoder did not apply padding, the input length may not
|
|
* be a multiple of 4.
|
|
*
|
|
* In this case, the last group will have fewer than 4 characters, and
|
|
* padding will be inferred. If the group has one or two characters, it decodes
|
|
* to one byte. If the group has three characters, it decodes to two bytes.
|
|
*
|
|
* @param input Input to decode.
|
|
* @param webSafe True if we should use the web-safe alphabet.
|
|
* @return bytes representing the decoded value.
|
|
*/
|
|
decodeStringToByteArray(input, webSafe) {
|
|
this.init_();
|
|
const charToByteMap = webSafe
|
|
? this.charToByteMapWebSafe_
|
|
: this.charToByteMap_;
|
|
const output = [];
|
|
for (let i = 0; i < input.length;) {
|
|
const byte1 = charToByteMap[input.charAt(i++)];
|
|
const haveByte2 = i < input.length;
|
|
const byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0;
|
|
++i;
|
|
const haveByte3 = i < input.length;
|
|
const byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 64;
|
|
++i;
|
|
const haveByte4 = i < input.length;
|
|
const byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 64;
|
|
++i;
|
|
if (byte1 == null || byte2 == null || byte3 == null || byte4 == null) {
|
|
throw new DecodeBase64StringError();
|
|
}
|
|
const outByte1 = (byte1 << 2) | (byte2 >> 4);
|
|
output.push(outByte1);
|
|
if (byte3 !== 64) {
|
|
const outByte2 = ((byte2 << 4) & 0xf0) | (byte3 >> 2);
|
|
output.push(outByte2);
|
|
if (byte4 !== 64) {
|
|
const outByte3 = ((byte3 << 6) & 0xc0) | byte4;
|
|
output.push(outByte3);
|
|
}
|
|
}
|
|
}
|
|
return output;
|
|
},
|
|
/**
|
|
* Lazy static initialization function. Called before
|
|
* accessing any of the static map variables.
|
|
* @private
|
|
*/
|
|
init_() {
|
|
if (!this.byteToCharMap_) {
|
|
this.byteToCharMap_ = {};
|
|
this.charToByteMap_ = {};
|
|
this.byteToCharMapWebSafe_ = {};
|
|
this.charToByteMapWebSafe_ = {};
|
|
// We want quick mappings back and forth, so we precompute two maps.
|
|
for (let i = 0; i < this.ENCODED_VALS.length; i++) {
|
|
this.byteToCharMap_[i] = this.ENCODED_VALS.charAt(i);
|
|
this.charToByteMap_[this.byteToCharMap_[i]] = i;
|
|
this.byteToCharMapWebSafe_[i] = this.ENCODED_VALS_WEBSAFE.charAt(i);
|
|
this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i;
|
|
// Be forgiving when decoding and correctly decode both encodings.
|
|
if (i >= this.ENCODED_VALS_BASE.length) {
|
|
this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(i)] = i;
|
|
this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(i)] = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* An error encountered while decoding base64 string.
|
|
*/
|
|
class DecodeBase64StringError extends Error {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.name = 'DecodeBase64StringError';
|
|
}
|
|
}
|
|
/**
|
|
* URL-safe base64 encoding
|
|
*/
|
|
const base64Encode = function (str) {
|
|
const utf8Bytes = stringToByteArray$1(str);
|
|
return base64.encodeByteArray(utf8Bytes, true);
|
|
};
|
|
/**
|
|
* URL-safe base64 encoding (without "." padding in the end).
|
|
* e.g. Used in JSON Web Token (JWT) parts.
|
|
*/
|
|
const base64urlEncodeWithoutPadding = function (str) {
|
|
// Use base64url encoding and remove padding in the end (dot characters).
|
|
return base64Encode(str).replace(/\./g, '');
|
|
};
|
|
/**
|
|
* URL-safe base64 decoding
|
|
*
|
|
* NOTE: DO NOT use the global atob() function - it does NOT support the
|
|
* base64Url variant encoding.
|
|
*
|
|
* @param str To be decoded
|
|
* @return Decoded result, if possible
|
|
*/
|
|
const base64Decode = function (str) {
|
|
try {
|
|
return base64.decodeString(str, true);
|
|
}
|
|
catch (e) {
|
|
console.error('base64Decode failed: ', e);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2022 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* Polyfill for `globalThis` object.
|
|
* @returns the `globalThis` object for the given environment.
|
|
* @public
|
|
*/
|
|
function getGlobal() {
|
|
if (typeof self !== 'undefined') {
|
|
return self;
|
|
}
|
|
if (typeof window !== 'undefined') {
|
|
return window;
|
|
}
|
|
if (typeof global !== 'undefined') {
|
|
return global;
|
|
}
|
|
throw new Error('Unable to locate global object.');
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2022 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const getDefaultsFromGlobal = () => getGlobal().__FIREBASE_DEFAULTS__;
|
|
/**
|
|
* Attempt to read defaults from a JSON string provided to
|
|
* process(.)env(.)__FIREBASE_DEFAULTS__ or a JSON file whose path is in
|
|
* process(.)env(.)__FIREBASE_DEFAULTS_PATH__
|
|
* The dots are in parens because certain compilers (Vite?) cannot
|
|
* handle seeing that variable in comments.
|
|
* See https://github.com/firebase/firebase-js-sdk/issues/6838
|
|
*/
|
|
const getDefaultsFromEnvVariable = () => {
|
|
if (typeof process === 'undefined' || typeof process.env === 'undefined') {
|
|
return;
|
|
}
|
|
const defaultsJsonString = process.env.__FIREBASE_DEFAULTS__;
|
|
if (defaultsJsonString) {
|
|
return JSON.parse(defaultsJsonString);
|
|
}
|
|
};
|
|
const getDefaultsFromCookie = () => {
|
|
if (typeof document === 'undefined') {
|
|
return;
|
|
}
|
|
let match;
|
|
try {
|
|
match = document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/);
|
|
}
|
|
catch (e) {
|
|
// Some environments such as Angular Universal SSR have a
|
|
// `document` object but error on accessing `document.cookie`.
|
|
return;
|
|
}
|
|
const decoded = match && base64Decode(match[1]);
|
|
return decoded && JSON.parse(decoded);
|
|
};
|
|
/**
|
|
* Get the __FIREBASE_DEFAULTS__ object. It checks in order:
|
|
* (1) if such an object exists as a property of `globalThis`
|
|
* (2) if such an object was provided on a shell environment variable
|
|
* (3) if such an object exists in a cookie
|
|
* @public
|
|
*/
|
|
const getDefaults = () => {
|
|
try {
|
|
return (getDefaultsFromGlobal() ||
|
|
getDefaultsFromEnvVariable() ||
|
|
getDefaultsFromCookie());
|
|
}
|
|
catch (e) {
|
|
/**
|
|
* Catch-all for being unable to get __FIREBASE_DEFAULTS__ due
|
|
* to any environment case we have not accounted for. Log to
|
|
* info instead of swallowing so we can find these unknown cases
|
|
* and add paths for them if needed.
|
|
*/
|
|
console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${e}`);
|
|
return;
|
|
}
|
|
};
|
|
/**
|
|
* Returns Firebase app config stored in the __FIREBASE_DEFAULTS__ object.
|
|
* @public
|
|
*/
|
|
const getDefaultAppConfig = () => { var _a; return (_a = getDefaults()) === null || _a === void 0 ? void 0 : _a.config; };
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
class Deferred {
|
|
constructor() {
|
|
this.reject = () => { };
|
|
this.resolve = () => { };
|
|
this.promise = new Promise((resolve, reject) => {
|
|
this.resolve = resolve;
|
|
this.reject = reject;
|
|
});
|
|
}
|
|
/**
|
|
* Our API internals are not promiseified 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);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* 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);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
Object.setPrototypeOf(this, FirebaseError.prototype);
|
|
// Maintains proper stack trace for where our error was thrown.
|
|
// Only available on V8.
|
|
if (Error.captureStackTrace) {
|
|
Error.captureStackTrace(this, ErrorFactory.prototype.create);
|
|
}
|
|
}
|
|
}
|
|
class ErrorFactory {
|
|
constructor(service, serviceName, errors) {
|
|
this.service = service;
|
|
this.serviceName = serviceName;
|
|
this.errors = errors;
|
|
}
|
|
create(code, ...data) {
|
|
const customData = data[0] || {};
|
|
const fullCode = `${this.service}/${code}`;
|
|
const template = this.errors[code];
|
|
const message = template ? replaceTemplate(template, customData) : 'Error';
|
|
// Service Name: Error message (service/code).
|
|
const fullMessage = `${this.serviceName}: ${message} (${fullCode}).`;
|
|
const error = new FirebaseError(fullCode, fullMessage, customData);
|
|
return error;
|
|
}
|
|
}
|
|
function replaceTemplate(template, data) {
|
|
return template.replace(PATTERN, (_, key) => {
|
|
const value = data[key];
|
|
return value != null ? String(value) : `<${key}?>`;
|
|
});
|
|
}
|
|
const PATTERN = /\{\$([^}]+)}/g;
|
|
/**
|
|
* Deep equal two objects. Support Arrays and Objects.
|
|
*/
|
|
function deepEqual(a, b) {
|
|
if (a === b) {
|
|
return true;
|
|
}
|
|
const aKeys = Object.keys(a);
|
|
const bKeys = Object.keys(b);
|
|
for (const k of aKeys) {
|
|
if (!bKeys.includes(k)) {
|
|
return false;
|
|
}
|
|
const aProp = a[k];
|
|
const bProp = b[k];
|
|
if (isObject(aProp) && isObject(bProp)) {
|
|
if (!deepEqual(aProp, bProp)) {
|
|
return false;
|
|
}
|
|
}
|
|
else if (aProp !== bProp) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const k of bKeys) {
|
|
if (!aKeys.includes(k)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function isObject(thing) {
|
|
return thing !== null && typeof thing === 'object';
|
|
}
|
|
|
|
/**
|
|
* Component for service name T, e.g. `auth`, `auth-internal`
|
|
*/
|
|
class Component {
|
|
/**
|
|
*
|
|
* @param name The public service name, e.g. app, auth, firestore, database
|
|
* @param instanceFactory Service factory responsible for creating the public interface
|
|
* @param type whether the service provided by the component is public or private
|
|
*/
|
|
constructor(name, instanceFactory, type) {
|
|
this.name = name;
|
|
this.instanceFactory = instanceFactory;
|
|
this.type = type;
|
|
this.multipleInstances = false;
|
|
/**
|
|
* Properties to be added to the service namespace
|
|
*/
|
|
this.serviceProps = {};
|
|
this.instantiationMode = "LAZY" /* InstantiationMode.LAZY */;
|
|
this.onInstanceCreated = null;
|
|
}
|
|
setInstantiationMode(mode) {
|
|
this.instantiationMode = mode;
|
|
return this;
|
|
}
|
|
setMultipleInstances(multipleInstances) {
|
|
this.multipleInstances = multipleInstances;
|
|
return this;
|
|
}
|
|
setServiceProps(props) {
|
|
this.serviceProps = props;
|
|
return this;
|
|
}
|
|
setInstanceCreatedCallback(callback) {
|
|
this.onInstanceCreated = callback;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const DEFAULT_ENTRY_NAME$1 = '[DEFAULT]';
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* Provider for instance for service name T, e.g. 'auth', 'auth-internal'
|
|
* NameServiceMapping[T] is an alias for the type of the instance
|
|
*/
|
|
class Provider {
|
|
constructor(name, container) {
|
|
this.name = name;
|
|
this.container = container;
|
|
this.component = null;
|
|
this.instances = new Map();
|
|
this.instancesDeferred = new Map();
|
|
this.instancesOptions = new Map();
|
|
this.onInitCallbacks = new Map();
|
|
}
|
|
/**
|
|
* @param identifier A provider can provide mulitple instances of a service
|
|
* if this.component.multipleInstances is true.
|
|
*/
|
|
get(identifier) {
|
|
// if multipleInstances is not supported, use the default name
|
|
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
|
|
if (!this.instancesDeferred.has(normalizedIdentifier)) {
|
|
const deferred = new Deferred();
|
|
this.instancesDeferred.set(normalizedIdentifier, deferred);
|
|
if (this.isInitialized(normalizedIdentifier) ||
|
|
this.shouldAutoInitialize()) {
|
|
// initialize the service if it can be auto-initialized
|
|
try {
|
|
const instance = this.getOrInitializeService({
|
|
instanceIdentifier: normalizedIdentifier
|
|
});
|
|
if (instance) {
|
|
deferred.resolve(instance);
|
|
}
|
|
}
|
|
catch (e) {
|
|
// when the instance factory throws an exception during get(), it should not cause
|
|
// a fatal error. We just return the unresolved promise in this case.
|
|
}
|
|
}
|
|
}
|
|
return this.instancesDeferred.get(normalizedIdentifier).promise;
|
|
}
|
|
getImmediate(options) {
|
|
var _a;
|
|
// if multipleInstances is not supported, use the default name
|
|
const normalizedIdentifier = this.normalizeInstanceIdentifier(options === null || options === void 0 ? void 0 : options.identifier);
|
|
const optional = (_a = options === null || options === void 0 ? void 0 : options.optional) !== null && _a !== void 0 ? _a : false;
|
|
if (this.isInitialized(normalizedIdentifier) ||
|
|
this.shouldAutoInitialize()) {
|
|
try {
|
|
return this.getOrInitializeService({
|
|
instanceIdentifier: normalizedIdentifier
|
|
});
|
|
}
|
|
catch (e) {
|
|
if (optional) {
|
|
return null;
|
|
}
|
|
else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// In case a component is not initialized and should/can not be auto-initialized at the moment, return null if the optional flag is set, or throw
|
|
if (optional) {
|
|
return null;
|
|
}
|
|
else {
|
|
throw Error(`Service ${this.name} is not available`);
|
|
}
|
|
}
|
|
}
|
|
getComponent() {
|
|
return this.component;
|
|
}
|
|
setComponent(component) {
|
|
if (component.name !== this.name) {
|
|
throw Error(`Mismatching Component ${component.name} for Provider ${this.name}.`);
|
|
}
|
|
if (this.component) {
|
|
throw Error(`Component for ${this.name} has already been provided`);
|
|
}
|
|
this.component = component;
|
|
// return early without attempting to initialize the component if the component requires explicit initialization (calling `Provider.initialize()`)
|
|
if (!this.shouldAutoInitialize()) {
|
|
return;
|
|
}
|
|
// if the service is eager, initialize the default instance
|
|
if (isComponentEager(component)) {
|
|
try {
|
|
this.getOrInitializeService({ instanceIdentifier: DEFAULT_ENTRY_NAME$1 });
|
|
}
|
|
catch (e) {
|
|
// when the instance factory for an eager Component throws an exception during the eager
|
|
// initialization, it should not cause a fatal error.
|
|
// TODO: Investigate if we need to make it configurable, because some component may want to cause
|
|
// a fatal error in this case?
|
|
}
|
|
}
|
|
// Create service instances for the pending promises and resolve them
|
|
// NOTE: if this.multipleInstances is false, only the default instance will be created
|
|
// and all promises with resolve with it regardless of the identifier.
|
|
for (const [instanceIdentifier, instanceDeferred] of this.instancesDeferred.entries()) {
|
|
const normalizedIdentifier = this.normalizeInstanceIdentifier(instanceIdentifier);
|
|
try {
|
|
// `getOrInitializeService()` should always return a valid instance since a component is guaranteed. use ! to make typescript happy.
|
|
const instance = this.getOrInitializeService({
|
|
instanceIdentifier: normalizedIdentifier
|
|
});
|
|
instanceDeferred.resolve(instance);
|
|
}
|
|
catch (e) {
|
|
// when the instance factory throws an exception, it should not cause
|
|
// a fatal error. We just leave the promise unresolved.
|
|
}
|
|
}
|
|
}
|
|
clearInstance(identifier = DEFAULT_ENTRY_NAME$1) {
|
|
this.instancesDeferred.delete(identifier);
|
|
this.instancesOptions.delete(identifier);
|
|
this.instances.delete(identifier);
|
|
}
|
|
// app.delete() will call this method on every provider to delete the services
|
|
// TODO: should we mark the provider as deleted?
|
|
async delete() {
|
|
const services = Array.from(this.instances.values());
|
|
await Promise.all([
|
|
...services
|
|
.filter(service => 'INTERNAL' in service) // legacy services
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
.map(service => service.INTERNAL.delete()),
|
|
...services
|
|
.filter(service => '_delete' in service) // modularized services
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
.map(service => service._delete())
|
|
]);
|
|
}
|
|
isComponentSet() {
|
|
return this.component != null;
|
|
}
|
|
isInitialized(identifier = DEFAULT_ENTRY_NAME$1) {
|
|
return this.instances.has(identifier);
|
|
}
|
|
getOptions(identifier = DEFAULT_ENTRY_NAME$1) {
|
|
return this.instancesOptions.get(identifier) || {};
|
|
}
|
|
initialize(opts = {}) {
|
|
const { options = {} } = opts;
|
|
const normalizedIdentifier = this.normalizeInstanceIdentifier(opts.instanceIdentifier);
|
|
if (this.isInitialized(normalizedIdentifier)) {
|
|
throw Error(`${this.name}(${normalizedIdentifier}) has already been initialized`);
|
|
}
|
|
if (!this.isComponentSet()) {
|
|
throw Error(`Component ${this.name} has not been registered yet`);
|
|
}
|
|
const instance = this.getOrInitializeService({
|
|
instanceIdentifier: normalizedIdentifier,
|
|
options
|
|
});
|
|
// resolve any pending promise waiting for the service instance
|
|
for (const [instanceIdentifier, instanceDeferred] of this.instancesDeferred.entries()) {
|
|
const normalizedDeferredIdentifier = this.normalizeInstanceIdentifier(instanceIdentifier);
|
|
if (normalizedIdentifier === normalizedDeferredIdentifier) {
|
|
instanceDeferred.resolve(instance);
|
|
}
|
|
}
|
|
return instance;
|
|
}
|
|
/**
|
|
*
|
|
* @param callback - a function that will be invoked after the provider has been initialized by calling provider.initialize().
|
|
* The function is invoked SYNCHRONOUSLY, so it should not execute any longrunning tasks in order to not block the program.
|
|
*
|
|
* @param identifier An optional instance identifier
|
|
* @returns a function to unregister the callback
|
|
*/
|
|
onInit(callback, identifier) {
|
|
var _a;
|
|
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
|
|
const existingCallbacks = (_a = this.onInitCallbacks.get(normalizedIdentifier)) !== null && _a !== void 0 ? _a : new Set();
|
|
existingCallbacks.add(callback);
|
|
this.onInitCallbacks.set(normalizedIdentifier, existingCallbacks);
|
|
const existingInstance = this.instances.get(normalizedIdentifier);
|
|
if (existingInstance) {
|
|
callback(existingInstance, normalizedIdentifier);
|
|
}
|
|
return () => {
|
|
existingCallbacks.delete(callback);
|
|
};
|
|
}
|
|
/**
|
|
* Invoke onInit callbacks synchronously
|
|
* @param instance the service instance`
|
|
*/
|
|
invokeOnInitCallbacks(instance, identifier) {
|
|
const callbacks = this.onInitCallbacks.get(identifier);
|
|
if (!callbacks) {
|
|
return;
|
|
}
|
|
for (const callback of callbacks) {
|
|
try {
|
|
callback(instance, identifier);
|
|
}
|
|
catch (_a) {
|
|
// ignore errors in the onInit callback
|
|
}
|
|
}
|
|
}
|
|
getOrInitializeService({ instanceIdentifier, options = {} }) {
|
|
let instance = this.instances.get(instanceIdentifier);
|
|
if (!instance && this.component) {
|
|
instance = this.component.instanceFactory(this.container, {
|
|
instanceIdentifier: normalizeIdentifierForFactory(instanceIdentifier),
|
|
options
|
|
});
|
|
this.instances.set(instanceIdentifier, instance);
|
|
this.instancesOptions.set(instanceIdentifier, options);
|
|
/**
|
|
* Invoke onInit listeners.
|
|
* Note this.component.onInstanceCreated is different, which is used by the component creator,
|
|
* while onInit listeners are registered by consumers of the provider.
|
|
*/
|
|
this.invokeOnInitCallbacks(instance, instanceIdentifier);
|
|
/**
|
|
* Order is important
|
|
* onInstanceCreated() should be called after this.instances.set(instanceIdentifier, instance); which
|
|
* makes `isInitialized()` return true.
|
|
*/
|
|
if (this.component.onInstanceCreated) {
|
|
try {
|
|
this.component.onInstanceCreated(this.container, instanceIdentifier, instance);
|
|
}
|
|
catch (_a) {
|
|
// ignore errors in the onInstanceCreatedCallback
|
|
}
|
|
}
|
|
}
|
|
return instance || null;
|
|
}
|
|
normalizeInstanceIdentifier(identifier = DEFAULT_ENTRY_NAME$1) {
|
|
if (this.component) {
|
|
return this.component.multipleInstances ? identifier : DEFAULT_ENTRY_NAME$1;
|
|
}
|
|
else {
|
|
return identifier; // assume multiple instances are supported before the component is provided.
|
|
}
|
|
}
|
|
shouldAutoInitialize() {
|
|
return (!!this.component &&
|
|
this.component.instantiationMode !== "EXPLICIT" /* InstantiationMode.EXPLICIT */);
|
|
}
|
|
}
|
|
// undefined should be passed to the service factory for the default instance
|
|
function normalizeIdentifierForFactory(identifier) {
|
|
return identifier === DEFAULT_ENTRY_NAME$1 ? undefined : identifier;
|
|
}
|
|
function isComponentEager(component) {
|
|
return component.instantiationMode === "EAGER" /* InstantiationMode.EAGER */;
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* ComponentContainer that provides Providers for service name T, e.g. `auth`, `auth-internal`
|
|
*/
|
|
class ComponentContainer {
|
|
constructor(name) {
|
|
this.name = name;
|
|
this.providers = new Map();
|
|
}
|
|
/**
|
|
*
|
|
* @param component Component being added
|
|
* @param overwrite When a component with the same name has already been registered,
|
|
* if overwrite is true: overwrite the existing component with the new component and create a new
|
|
* provider with the new component. It can be useful in tests where you want to use different mocks
|
|
* for different tests.
|
|
* if overwrite is false: throw an exception
|
|
*/
|
|
addComponent(component) {
|
|
const provider = this.getProvider(component.name);
|
|
if (provider.isComponentSet()) {
|
|
throw new Error(`Component ${component.name} has already been registered with ${this.name}`);
|
|
}
|
|
provider.setComponent(component);
|
|
}
|
|
addOrOverwriteComponent(component) {
|
|
const provider = this.getProvider(component.name);
|
|
if (provider.isComponentSet()) {
|
|
// delete the existing provider from the container, so we can register the new component
|
|
this.providers.delete(component.name);
|
|
}
|
|
this.addComponent(component);
|
|
}
|
|
/**
|
|
* getProvider provides a type safe interface where it can only be called with a field name
|
|
* present in NameServiceMapping interface.
|
|
*
|
|
* Firebase SDKs providing services should extend NameServiceMapping interface to register
|
|
* themselves.
|
|
*/
|
|
getProvider(name) {
|
|
if (this.providers.has(name)) {
|
|
return this.providers.get(name);
|
|
}
|
|
// create a Provider for a service that hasn't registered with Firebase
|
|
const provider = new Provider(name, this);
|
|
this.providers.set(name, provider);
|
|
return provider;
|
|
}
|
|
getProviders() {
|
|
return Array.from(this.providers.values());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* A container for all of the Logger instances
|
|
*/
|
|
const instances = [];
|
|
/**
|
|
* The JS SDK supports 5 log levels and also allows a user the ability to
|
|
* silence the logs altogether.
|
|
*
|
|
* The order is a follows:
|
|
* DEBUG < VERBOSE < INFO < WARN < ERROR
|
|
*
|
|
* All of the log types above the current log level will be captured (i.e. if
|
|
* you set the log level to `INFO`, errors will still be logged, but `DEBUG` and
|
|
* `VERBOSE` logs will not)
|
|
*/
|
|
var LogLevel;
|
|
(function (LogLevel) {
|
|
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
LogLevel[LogLevel["VERBOSE"] = 1] = "VERBOSE";
|
|
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
|
LogLevel[LogLevel["WARN"] = 3] = "WARN";
|
|
LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
|
|
LogLevel[LogLevel["SILENT"] = 5] = "SILENT";
|
|
})(LogLevel || (LogLevel = {}));
|
|
const levelStringToEnum = {
|
|
'debug': LogLevel.DEBUG,
|
|
'verbose': LogLevel.VERBOSE,
|
|
'info': LogLevel.INFO,
|
|
'warn': LogLevel.WARN,
|
|
'error': LogLevel.ERROR,
|
|
'silent': LogLevel.SILENT
|
|
};
|
|
/**
|
|
* The default log level
|
|
*/
|
|
const defaultLogLevel = LogLevel.INFO;
|
|
/**
|
|
* By default, `console.debug` is not displayed in the developer console (in
|
|
* chrome). To avoid forcing users to have to opt-in to these logs twice
|
|
* (i.e. once for firebase, and once in the console), we are sending `DEBUG`
|
|
* logs to the `console.log` function.
|
|
*/
|
|
const ConsoleMethod = {
|
|
[LogLevel.DEBUG]: 'log',
|
|
[LogLevel.VERBOSE]: 'log',
|
|
[LogLevel.INFO]: 'info',
|
|
[LogLevel.WARN]: 'warn',
|
|
[LogLevel.ERROR]: 'error'
|
|
};
|
|
/**
|
|
* The default log handler will forward DEBUG, VERBOSE, INFO, WARN, and ERROR
|
|
* messages on to their corresponding console counterparts (if the log method
|
|
* is supported by the current log level)
|
|
*/
|
|
const defaultLogHandler = (instance, logType, ...args) => {
|
|
if (logType < instance.logLevel) {
|
|
return;
|
|
}
|
|
const now = new Date().toISOString();
|
|
const method = ConsoleMethod[logType];
|
|
if (method) {
|
|
console[method](`[${now}] ${instance.name}:`, ...args);
|
|
}
|
|
else {
|
|
throw new Error(`Attempted to log a message with an invalid logType (value: ${logType})`);
|
|
}
|
|
};
|
|
class Logger {
|
|
/**
|
|
* Gives you an instance of a Logger to capture messages according to
|
|
* Firebase's logging scheme.
|
|
*
|
|
* @param name The name that the logs will be associated with
|
|
*/
|
|
constructor(name) {
|
|
this.name = name;
|
|
/**
|
|
* The log level of the given Logger instance.
|
|
*/
|
|
this._logLevel = defaultLogLevel;
|
|
/**
|
|
* The main (internal) log handler for the Logger instance.
|
|
* Can be set to a new function in internal package code but not by user.
|
|
*/
|
|
this._logHandler = defaultLogHandler;
|
|
/**
|
|
* The optional, additional, user-defined log handler for the Logger instance.
|
|
*/
|
|
this._userLogHandler = null;
|
|
/**
|
|
* Capture the current instance for later use
|
|
*/
|
|
instances.push(this);
|
|
}
|
|
get logLevel() {
|
|
return this._logLevel;
|
|
}
|
|
set logLevel(val) {
|
|
if (!(val in LogLevel)) {
|
|
throw new TypeError(`Invalid value "${val}" assigned to \`logLevel\``);
|
|
}
|
|
this._logLevel = val;
|
|
}
|
|
// Workaround for setter/getter having to be the same type.
|
|
setLogLevel(val) {
|
|
this._logLevel = typeof val === 'string' ? levelStringToEnum[val] : val;
|
|
}
|
|
get logHandler() {
|
|
return this._logHandler;
|
|
}
|
|
set logHandler(val) {
|
|
if (typeof val !== 'function') {
|
|
throw new TypeError('Value assigned to `logHandler` must be a function');
|
|
}
|
|
this._logHandler = val;
|
|
}
|
|
get userLogHandler() {
|
|
return this._userLogHandler;
|
|
}
|
|
set userLogHandler(val) {
|
|
this._userLogHandler = val;
|
|
}
|
|
/**
|
|
* The functions below are all based on the `console` interface
|
|
*/
|
|
debug(...args) {
|
|
this._userLogHandler && this._userLogHandler(this, LogLevel.DEBUG, ...args);
|
|
this._logHandler(this, LogLevel.DEBUG, ...args);
|
|
}
|
|
log(...args) {
|
|
this._userLogHandler &&
|
|
this._userLogHandler(this, LogLevel.VERBOSE, ...args);
|
|
this._logHandler(this, LogLevel.VERBOSE, ...args);
|
|
}
|
|
info(...args) {
|
|
this._userLogHandler && this._userLogHandler(this, LogLevel.INFO, ...args);
|
|
this._logHandler(this, LogLevel.INFO, ...args);
|
|
}
|
|
warn(...args) {
|
|
this._userLogHandler && this._userLogHandler(this, LogLevel.WARN, ...args);
|
|
this._logHandler(this, LogLevel.WARN, ...args);
|
|
}
|
|
error(...args) {
|
|
this._userLogHandler && this._userLogHandler(this, LogLevel.ERROR, ...args);
|
|
this._logHandler(this, LogLevel.ERROR, ...args);
|
|
}
|
|
}
|
|
function setLogLevel$1(level) {
|
|
instances.forEach(inst => {
|
|
inst.setLogLevel(level);
|
|
});
|
|
}
|
|
function setUserLogHandler(logCallback, options) {
|
|
for (const instance of instances) {
|
|
let customLogLevel = null;
|
|
if (options && options.level) {
|
|
customLogLevel = levelStringToEnum[options.level];
|
|
}
|
|
if (logCallback === null) {
|
|
instance.userLogHandler = null;
|
|
}
|
|
else {
|
|
instance.userLogHandler = (instance, level, ...args) => {
|
|
const message = args
|
|
.map(arg => {
|
|
if (arg == null) {
|
|
return null;
|
|
}
|
|
else if (typeof arg === 'string') {
|
|
return arg;
|
|
}
|
|
else if (typeof arg === 'number' || typeof arg === 'boolean') {
|
|
return arg.toString();
|
|
}
|
|
else if (arg instanceof Error) {
|
|
return arg.message;
|
|
}
|
|
else {
|
|
try {
|
|
return JSON.stringify(arg);
|
|
}
|
|
catch (ignored) {
|
|
return null;
|
|
}
|
|
}
|
|
})
|
|
.filter(arg => arg)
|
|
.join(' ');
|
|
if (level >= (customLogLevel !== null && customLogLevel !== void 0 ? customLogLevel : instance.logLevel)) {
|
|
logCallback({
|
|
level: LogLevel[level].toLowerCase(),
|
|
message,
|
|
args,
|
|
type: instance.name
|
|
});
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
|
|
|
|
let idbProxyableTypes;
|
|
let cursorAdvanceMethods;
|
|
// This is a function to prevent it throwing up in node environments.
|
|
function getIdbProxyableTypes() {
|
|
return (idbProxyableTypes ||
|
|
(idbProxyableTypes = [
|
|
IDBDatabase,
|
|
IDBObjectStore,
|
|
IDBIndex,
|
|
IDBCursor,
|
|
IDBTransaction,
|
|
]));
|
|
}
|
|
// This is a function to prevent it throwing up in node environments.
|
|
function getCursorAdvanceMethods() {
|
|
return (cursorAdvanceMethods ||
|
|
(cursorAdvanceMethods = [
|
|
IDBCursor.prototype.advance,
|
|
IDBCursor.prototype.continue,
|
|
IDBCursor.prototype.continuePrimaryKey,
|
|
]));
|
|
}
|
|
const cursorRequestMap = new WeakMap();
|
|
const transactionDoneMap = new WeakMap();
|
|
const transactionStoreNamesMap = new WeakMap();
|
|
const transformCache = new WeakMap();
|
|
const reverseTransformCache = new WeakMap();
|
|
function promisifyRequest(request) {
|
|
const promise = new Promise((resolve, reject) => {
|
|
const unlisten = () => {
|
|
request.removeEventListener('success', success);
|
|
request.removeEventListener('error', error);
|
|
};
|
|
const success = () => {
|
|
resolve(wrap(request.result));
|
|
unlisten();
|
|
};
|
|
const error = () => {
|
|
reject(request.error);
|
|
unlisten();
|
|
};
|
|
request.addEventListener('success', success);
|
|
request.addEventListener('error', error);
|
|
});
|
|
promise
|
|
.then((value) => {
|
|
// Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
|
|
// (see wrapFunction).
|
|
if (value instanceof IDBCursor) {
|
|
cursorRequestMap.set(value, request);
|
|
}
|
|
// Catching to avoid "Uncaught Promise exceptions"
|
|
})
|
|
.catch(() => { });
|
|
// This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
|
|
// is because we create many promises from a single IDBRequest.
|
|
reverseTransformCache.set(promise, request);
|
|
return promise;
|
|
}
|
|
function cacheDonePromiseForTransaction(tx) {
|
|
// Early bail if we've already created a done promise for this transaction.
|
|
if (transactionDoneMap.has(tx))
|
|
return;
|
|
const done = new Promise((resolve, reject) => {
|
|
const unlisten = () => {
|
|
tx.removeEventListener('complete', complete);
|
|
tx.removeEventListener('error', error);
|
|
tx.removeEventListener('abort', error);
|
|
};
|
|
const complete = () => {
|
|
resolve();
|
|
unlisten();
|
|
};
|
|
const error = () => {
|
|
reject(tx.error || new DOMException('AbortError', 'AbortError'));
|
|
unlisten();
|
|
};
|
|
tx.addEventListener('complete', complete);
|
|
tx.addEventListener('error', error);
|
|
tx.addEventListener('abort', error);
|
|
});
|
|
// Cache it for later retrieval.
|
|
transactionDoneMap.set(tx, done);
|
|
}
|
|
let idbProxyTraps = {
|
|
get(target, prop, receiver) {
|
|
if (target instanceof IDBTransaction) {
|
|
// Special handling for transaction.done.
|
|
if (prop === 'done')
|
|
return transactionDoneMap.get(target);
|
|
// Polyfill for objectStoreNames because of Edge.
|
|
if (prop === 'objectStoreNames') {
|
|
return target.objectStoreNames || transactionStoreNamesMap.get(target);
|
|
}
|
|
// Make tx.store return the only store in the transaction, or undefined if there are many.
|
|
if (prop === 'store') {
|
|
return receiver.objectStoreNames[1]
|
|
? undefined
|
|
: receiver.objectStore(receiver.objectStoreNames[0]);
|
|
}
|
|
}
|
|
// Else transform whatever we get back.
|
|
return wrap(target[prop]);
|
|
},
|
|
set(target, prop, value) {
|
|
target[prop] = value;
|
|
return true;
|
|
},
|
|
has(target, prop) {
|
|
if (target instanceof IDBTransaction &&
|
|
(prop === 'done' || prop === 'store')) {
|
|
return true;
|
|
}
|
|
return prop in target;
|
|
},
|
|
};
|
|
function replaceTraps(callback) {
|
|
idbProxyTraps = callback(idbProxyTraps);
|
|
}
|
|
function wrapFunction(func) {
|
|
// Due to expected object equality (which is enforced by the caching in `wrap`), we
|
|
// only create one new func per func.
|
|
// Edge doesn't support objectStoreNames (booo), so we polyfill it here.
|
|
if (func === IDBDatabase.prototype.transaction &&
|
|
!('objectStoreNames' in IDBTransaction.prototype)) {
|
|
return function (storeNames, ...args) {
|
|
const tx = func.call(unwrap(this), storeNames, ...args);
|
|
transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);
|
|
return wrap(tx);
|
|
};
|
|
}
|
|
// Cursor methods are special, as the behaviour is a little more different to standard IDB. In
|
|
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
|
|
// cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
|
|
// with real promises, so each advance methods returns a new promise for the cursor object, or
|
|
// undefined if the end of the cursor has been reached.
|
|
if (getCursorAdvanceMethods().includes(func)) {
|
|
return function (...args) {
|
|
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
|
|
// the original object.
|
|
func.apply(unwrap(this), args);
|
|
return wrap(cursorRequestMap.get(this));
|
|
};
|
|
}
|
|
return function (...args) {
|
|
// Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
|
|
// the original object.
|
|
return wrap(func.apply(unwrap(this), args));
|
|
};
|
|
}
|
|
function transformCachableValue(value) {
|
|
if (typeof value === 'function')
|
|
return wrapFunction(value);
|
|
// This doesn't return, it just creates a 'done' promise for the transaction,
|
|
// which is later returned for transaction.done (see idbObjectHandler).
|
|
if (value instanceof IDBTransaction)
|
|
cacheDonePromiseForTransaction(value);
|
|
if (instanceOfAny(value, getIdbProxyableTypes()))
|
|
return new Proxy(value, idbProxyTraps);
|
|
// Return the same value back if we're not going to transform it.
|
|
return value;
|
|
}
|
|
function wrap(value) {
|
|
// We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
|
|
// IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
|
|
if (value instanceof IDBRequest)
|
|
return promisifyRequest(value);
|
|
// If we've already transformed this value before, reuse the transformed value.
|
|
// This is faster, but it also provides object equality.
|
|
if (transformCache.has(value))
|
|
return transformCache.get(value);
|
|
const newValue = transformCachableValue(value);
|
|
// Not all types are transformed.
|
|
// These may be primitive types, so they can't be WeakMap keys.
|
|
if (newValue !== value) {
|
|
transformCache.set(value, newValue);
|
|
reverseTransformCache.set(newValue, value);
|
|
}
|
|
return newValue;
|
|
}
|
|
const unwrap = (value) => reverseTransformCache.get(value);
|
|
|
|
/**
|
|
* Open a database.
|
|
*
|
|
* @param name Name of the database.
|
|
* @param version Schema version.
|
|
* @param callbacks Additional callbacks.
|
|
*/
|
|
function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
|
|
const request = indexedDB.open(name, version);
|
|
const openPromise = wrap(request);
|
|
if (upgrade) {
|
|
request.addEventListener('upgradeneeded', (event) => {
|
|
upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
|
|
});
|
|
}
|
|
if (blocked) {
|
|
request.addEventListener('blocked', (event) => blocked(
|
|
// Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
|
|
event.oldVersion, event.newVersion, event));
|
|
}
|
|
openPromise
|
|
.then((db) => {
|
|
if (terminated)
|
|
db.addEventListener('close', () => terminated());
|
|
if (blocking) {
|
|
db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));
|
|
}
|
|
})
|
|
.catch(() => { });
|
|
return openPromise;
|
|
}
|
|
|
|
const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
|
|
const writeMethods = ['put', 'add', 'delete', 'clear'];
|
|
const cachedMethods = new Map();
|
|
function getMethod(target, prop) {
|
|
if (!(target instanceof IDBDatabase &&
|
|
!(prop in target) &&
|
|
typeof prop === 'string')) {
|
|
return;
|
|
}
|
|
if (cachedMethods.get(prop))
|
|
return cachedMethods.get(prop);
|
|
const targetFuncName = prop.replace(/FromIndex$/, '');
|
|
const useIndex = prop !== targetFuncName;
|
|
const isWrite = writeMethods.includes(targetFuncName);
|
|
if (
|
|
// Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
|
|
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||
|
|
!(isWrite || readMethods.includes(targetFuncName))) {
|
|
return;
|
|
}
|
|
const method = async function (storeName, ...args) {
|
|
// isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
|
|
const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
|
|
let target = tx.store;
|
|
if (useIndex)
|
|
target = target.index(args.shift());
|
|
// Must reject if op rejects.
|
|
// If it's a write operation, must reject if tx.done rejects.
|
|
// Must reject with op rejection first.
|
|
// Must resolve with op value.
|
|
// Must handle both promises (no unhandled rejections)
|
|
return (await Promise.all([
|
|
target[targetFuncName](...args),
|
|
isWrite && tx.done,
|
|
]))[0];
|
|
};
|
|
cachedMethods.set(prop, method);
|
|
return method;
|
|
}
|
|
replaceTraps((oldTraps) => ({
|
|
...oldTraps,
|
|
get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
|
|
has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),
|
|
}));
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
class PlatformLoggerServiceImpl {
|
|
constructor(container) {
|
|
this.container = container;
|
|
}
|
|
// In initial implementation, this will be called by installations on
|
|
// auth token refresh, and installations will send this string.
|
|
getPlatformInfoString() {
|
|
const providers = this.container.getProviders();
|
|
// Loop through providers and get library/version pairs from any that are
|
|
// version components.
|
|
return providers
|
|
.map(provider => {
|
|
if (isVersionServiceProvider(provider)) {
|
|
const service = provider.getImmediate();
|
|
return `${service.library}/${service.version}`;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
})
|
|
.filter(logString => logString)
|
|
.join(' ');
|
|
}
|
|
}
|
|
/**
|
|
*
|
|
* @param provider check if this provider provides a VersionService
|
|
*
|
|
* NOTE: Using Provider<'app-version'> is a hack to indicate that the provider
|
|
* provides VersionService. The provider is not necessarily a 'app-version'
|
|
* provider.
|
|
*/
|
|
function isVersionServiceProvider(provider) {
|
|
const component = provider.getComponent();
|
|
return (component === null || component === void 0 ? void 0 : component.type) === "VERSION" /* ComponentType.VERSION */;
|
|
}
|
|
|
|
const name$o = "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
|
|
const version$1 = "0.9.27";
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const logger = new Logger('https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js');
|
|
|
|
const name$n = "@firebase/app-compat";
|
|
|
|
const name$m = "@firebase/analytics-compat";
|
|
|
|
const name$l = "@firebase/analytics";
|
|
|
|
const name$k = "@firebase/app-check-compat";
|
|
|
|
const name$j = "@firebase/app-check";
|
|
|
|
const name$i = "@firebase/auth";
|
|
|
|
const name$h = "@firebase/auth-compat";
|
|
|
|
const name$g = "@firebase/database";
|
|
|
|
const name$f = "@firebase/database-compat";
|
|
|
|
const name$e = "@firebase/functions";
|
|
|
|
const name$d = "@firebase/functions-compat";
|
|
|
|
const name$c = "@firebase/installations";
|
|
|
|
const name$b = "@firebase/installations-compat";
|
|
|
|
const name$a = "@firebase/messaging";
|
|
|
|
const name$9 = "@firebase/messaging-compat";
|
|
|
|
const name$8 = "@firebase/performance";
|
|
|
|
const name$7 = "@firebase/performance-compat";
|
|
|
|
const name$6 = "@firebase/remote-config";
|
|
|
|
const name$5 = "@firebase/remote-config-compat";
|
|
|
|
const name$4 = "@firebase/storage";
|
|
|
|
const name$3 = "@firebase/storage-compat";
|
|
|
|
const name$2 = "@firebase/firestore";
|
|
|
|
const name$1 = "@firebase/firestore-compat";
|
|
|
|
const name$p = "firebase";
|
|
const version$2 = "10.8.0";
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* The default app name
|
|
*
|
|
* @internal
|
|
*/
|
|
const DEFAULT_ENTRY_NAME = '[DEFAULT]';
|
|
const PLATFORM_LOG_STRING = {
|
|
[name$o]: 'fire-core',
|
|
[name$n]: 'fire-core-compat',
|
|
[name$l]: 'fire-analytics',
|
|
[name$m]: 'fire-analytics-compat',
|
|
[name$j]: 'fire-app-check',
|
|
[name$k]: 'fire-app-check-compat',
|
|
[name$i]: 'fire-auth',
|
|
[name$h]: 'fire-auth-compat',
|
|
[name$g]: 'fire-rtdb',
|
|
[name$f]: 'fire-rtdb-compat',
|
|
[name$e]: 'fire-fn',
|
|
[name$d]: 'fire-fn-compat',
|
|
[name$c]: 'fire-iid',
|
|
[name$b]: 'fire-iid-compat',
|
|
[name$a]: 'fire-fcm',
|
|
[name$9]: 'fire-fcm-compat',
|
|
[name$8]: 'fire-perf',
|
|
[name$7]: 'fire-perf-compat',
|
|
[name$6]: 'fire-rc',
|
|
[name$5]: 'fire-rc-compat',
|
|
[name$4]: 'fire-gcs',
|
|
[name$3]: 'fire-gcs-compat',
|
|
[name$2]: 'fire-fst',
|
|
[name$1]: 'fire-fst-compat',
|
|
'fire-js': 'fire-js',
|
|
[name$p]: 'fire-js-all'
|
|
};
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* @internal
|
|
*/
|
|
const _apps = new Map();
|
|
/**
|
|
* Registered components.
|
|
*
|
|
* @internal
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const _components = new Map();
|
|
/**
|
|
* @param component - the component being added to this app's container
|
|
*
|
|
* @internal
|
|
*/
|
|
function _addComponent(app, component) {
|
|
try {
|
|
app.container.addComponent(component);
|
|
}
|
|
catch (e) {
|
|
logger.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e);
|
|
}
|
|
}
|
|
/**
|
|
*
|
|
* @internal
|
|
*/
|
|
function _addOrOverwriteComponent(app, component) {
|
|
app.container.addOrOverwriteComponent(component);
|
|
}
|
|
/**
|
|
*
|
|
* @param component - the component to register
|
|
* @returns whether or not the component is registered successfully
|
|
*
|
|
* @internal
|
|
*/
|
|
function _registerComponent(component) {
|
|
const componentName = component.name;
|
|
if (_components.has(componentName)) {
|
|
logger.debug(`There were multiple attempts to register component ${componentName}.`);
|
|
return false;
|
|
}
|
|
_components.set(componentName, component);
|
|
// add the component to existing app instances
|
|
for (const app of _apps.values()) {
|
|
_addComponent(app, component);
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
*
|
|
* @param app - FirebaseApp instance
|
|
* @param name - service name
|
|
*
|
|
* @returns the provider for the service with the matching name
|
|
*
|
|
* @internal
|
|
*/
|
|
function _getProvider(app, name) {
|
|
const heartbeatController = app.container
|
|
.getProvider('heartbeat')
|
|
.getImmediate({ optional: true });
|
|
if (heartbeatController) {
|
|
void heartbeatController.triggerHeartbeat();
|
|
}
|
|
return app.container.getProvider(name);
|
|
}
|
|
/**
|
|
*
|
|
* @param app - FirebaseApp instance
|
|
* @param name - service name
|
|
* @param instanceIdentifier - service instance identifier in case the service supports multiple instances
|
|
*
|
|
* @internal
|
|
*/
|
|
function _removeServiceInstance(app, name, instanceIdentifier = DEFAULT_ENTRY_NAME) {
|
|
_getProvider(app, name).clearInstance(instanceIdentifier);
|
|
}
|
|
/**
|
|
* Test only
|
|
*
|
|
* @internal
|
|
*/
|
|
function _clearComponents() {
|
|
_components.clear();
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const ERRORS = {
|
|
["no-app" /* AppError.NO_APP */]: "No Firebase App '{$appName}' has been created - " +
|
|
'call initializeApp() first',
|
|
["bad-app-name" /* AppError.BAD_APP_NAME */]: "Illegal App name: '{$appName}",
|
|
["duplicate-app" /* AppError.DUPLICATE_APP */]: "Firebase App named '{$appName}' already exists with different options or config",
|
|
["app-deleted" /* AppError.APP_DELETED */]: "Firebase App named '{$appName}' already deleted",
|
|
["no-options" /* AppError.NO_OPTIONS */]: 'Need to provide options, when not being deployed to hosting via source.',
|
|
["invalid-app-argument" /* AppError.INVALID_APP_ARGUMENT */]: 'firebase.{$appName}() takes either no argument or a ' +
|
|
'Firebase App instance.',
|
|
["invalid-log-argument" /* AppError.INVALID_LOG_ARGUMENT */]: 'First argument to `onLog` must be null or a function.',
|
|
["idb-open" /* AppError.IDB_OPEN */]: 'Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.',
|
|
["idb-get" /* AppError.IDB_GET */]: 'Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.',
|
|
["idb-set" /* AppError.IDB_WRITE */]: 'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.',
|
|
["idb-delete" /* AppError.IDB_DELETE */]: 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.'
|
|
};
|
|
const ERROR_FACTORY = new ErrorFactory('app', 'Firebase', ERRORS);
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
class FirebaseAppImpl {
|
|
constructor(options, config, container) {
|
|
this._isDeleted = false;
|
|
this._options = Object.assign({}, options);
|
|
this._config = Object.assign({}, config);
|
|
this._name = config.name;
|
|
this._automaticDataCollectionEnabled =
|
|
config.automaticDataCollectionEnabled;
|
|
this._container = container;
|
|
this.container.addComponent(new Component('app', () => this, "PUBLIC" /* ComponentType.PUBLIC */));
|
|
}
|
|
get automaticDataCollectionEnabled() {
|
|
this.checkDestroyed();
|
|
return this._automaticDataCollectionEnabled;
|
|
}
|
|
set automaticDataCollectionEnabled(val) {
|
|
this.checkDestroyed();
|
|
this._automaticDataCollectionEnabled = val;
|
|
}
|
|
get name() {
|
|
this.checkDestroyed();
|
|
return this._name;
|
|
}
|
|
get options() {
|
|
this.checkDestroyed();
|
|
return this._options;
|
|
}
|
|
get config() {
|
|
this.checkDestroyed();
|
|
return this._config;
|
|
}
|
|
get container() {
|
|
return this._container;
|
|
}
|
|
get isDeleted() {
|
|
return this._isDeleted;
|
|
}
|
|
set isDeleted(val) {
|
|
this._isDeleted = val;
|
|
}
|
|
/**
|
|
* This function will throw an Error if the App has already been deleted -
|
|
* use before performing API actions on the App.
|
|
*/
|
|
checkDestroyed() {
|
|
if (this.isDeleted) {
|
|
throw ERROR_FACTORY.create("app-deleted" /* AppError.APP_DELETED */, { appName: this._name });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/**
|
|
* The current SDK version.
|
|
*
|
|
* @public
|
|
*/
|
|
const SDK_VERSION = version$2;
|
|
function initializeApp(_options, rawConfig = {}) {
|
|
let options = _options;
|
|
if (typeof rawConfig !== 'object') {
|
|
const name = rawConfig;
|
|
rawConfig = { name };
|
|
}
|
|
const config = Object.assign({ name: DEFAULT_ENTRY_NAME, automaticDataCollectionEnabled: false }, rawConfig);
|
|
const name = config.name;
|
|
if (typeof name !== 'string' || !name) {
|
|
throw ERROR_FACTORY.create("bad-app-name" /* AppError.BAD_APP_NAME */, {
|
|
appName: String(name)
|
|
});
|
|
}
|
|
options || (options = getDefaultAppConfig());
|
|
if (!options) {
|
|
throw ERROR_FACTORY.create("no-options" /* AppError.NO_OPTIONS */);
|
|
}
|
|
const existingApp = _apps.get(name);
|
|
if (existingApp) {
|
|
// return the existing app if options and config deep equal the ones in the existing app.
|
|
if (deepEqual(options, existingApp.options) &&
|
|
deepEqual(config, existingApp.config)) {
|
|
return existingApp;
|
|
}
|
|
else {
|
|
throw ERROR_FACTORY.create("duplicate-app" /* AppError.DUPLICATE_APP */, { appName: name });
|
|
}
|
|
}
|
|
const container = new ComponentContainer(name);
|
|
for (const component of _components.values()) {
|
|
container.addComponent(component);
|
|
}
|
|
const newApp = new FirebaseAppImpl(options, config, container);
|
|
_apps.set(name, newApp);
|
|
return newApp;
|
|
}
|
|
/**
|
|
* Retrieves a {@link @firebase/app#FirebaseApp} instance.
|
|
*
|
|
* When called with no arguments, the default app is returned. When an app name
|
|
* is provided, the app corresponding to that name is returned.
|
|
*
|
|
* An exception is thrown if the app being retrieved has not yet been
|
|
* initialized.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* // Return the default app
|
|
* const app = getApp();
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* // Return a named app
|
|
* const otherApp = getApp("otherApp");
|
|
* ```
|
|
*
|
|
* @param name - Optional name of the app to return. If no name is
|
|
* provided, the default is `"[DEFAULT]"`.
|
|
*
|
|
* @returns The app corresponding to the provided app name.
|
|
* If no app name is provided, the default app is returned.
|
|
*
|
|
* @public
|
|
*/
|
|
function getApp(name = DEFAULT_ENTRY_NAME) {
|
|
const app = _apps.get(name);
|
|
if (!app && name === DEFAULT_ENTRY_NAME && getDefaultAppConfig()) {
|
|
return initializeApp();
|
|
}
|
|
if (!app) {
|
|
throw ERROR_FACTORY.create("no-app" /* AppError.NO_APP */, { appName: name });
|
|
}
|
|
return app;
|
|
}
|
|
/**
|
|
* A (read-only) array of all initialized apps.
|
|
* @public
|
|
*/
|
|
function getApps() {
|
|
return Array.from(_apps.values());
|
|
}
|
|
/**
|
|
* Renders this app unusable and frees the resources of all associated
|
|
* services.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* deleteApp(app)
|
|
* .then(function() {
|
|
* console.log("App deleted successfully");
|
|
* })
|
|
* .catch(function(error) {
|
|
* console.log("Error deleting app:", error);
|
|
* });
|
|
* ```
|
|
*
|
|
* @public
|
|
*/
|
|
async function deleteApp(app) {
|
|
const name = app.name;
|
|
if (_apps.has(name)) {
|
|
_apps.delete(name);
|
|
await Promise.all(app.container
|
|
.getProviders()
|
|
.map(provider => provider.delete()));
|
|
app.isDeleted = true;
|
|
}
|
|
}
|
|
/**
|
|
* Registers a library's name and version for platform logging purposes.
|
|
* @param library - Name of 1p or 3p library (e.g. firestore, angularfire)
|
|
* @param version - Current version of that library.
|
|
* @param variant - Bundle variant, e.g., node, rn, etc.
|
|
*
|
|
* @public
|
|
*/
|
|
function registerVersion(libraryKeyOrName, version, variant) {
|
|
var _a;
|
|
// TODO: We can use this check to whitelist strings when/if we set up
|
|
// a good whitelist system.
|
|
let library = (_a = PLATFORM_LOG_STRING[libraryKeyOrName]) !== null && _a !== void 0 ? _a : libraryKeyOrName;
|
|
if (variant) {
|
|
library += `-${variant}`;
|
|
}
|
|
const libraryMismatch = library.match(/\s|\//);
|
|
const versionMismatch = version.match(/\s|\//);
|
|
if (libraryMismatch || versionMismatch) {
|
|
const warning = [
|
|
`Unable to register library "${library}" with version "${version}":`
|
|
];
|
|
if (libraryMismatch) {
|
|
warning.push(`library name "${library}" contains illegal characters (whitespace or "/")`);
|
|
}
|
|
if (libraryMismatch && versionMismatch) {
|
|
warning.push('and');
|
|
}
|
|
if (versionMismatch) {
|
|
warning.push(`version name "${version}" contains illegal characters (whitespace or "/")`);
|
|
}
|
|
logger.warn(warning.join(' '));
|
|
return;
|
|
}
|
|
_registerComponent(new Component(`${library}-version`, () => ({ library, version }), "VERSION" /* ComponentType.VERSION */));
|
|
}
|
|
/**
|
|
* Sets log handler for all Firebase SDKs.
|
|
* @param logCallback - An optional custom log handler that executes user code whenever
|
|
* the Firebase SDK makes a logging call.
|
|
*
|
|
* @public
|
|
*/
|
|
function onLog(logCallback, options) {
|
|
if (logCallback !== null && typeof logCallback !== 'function') {
|
|
throw ERROR_FACTORY.create("invalid-log-argument" /* AppError.INVALID_LOG_ARGUMENT */);
|
|
}
|
|
setUserLogHandler(logCallback, options);
|
|
}
|
|
/**
|
|
* Sets log level for all Firebase SDKs.
|
|
*
|
|
* All of the log types above the current log level are captured (i.e. if
|
|
* you set the log level to `info`, errors are logged, but `debug` and
|
|
* `verbose` logs are not).
|
|
*
|
|
* @public
|
|
*/
|
|
function setLogLevel(logLevel) {
|
|
setLogLevel$1(logLevel);
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const DB_NAME = 'firebase-heartbeat-database';
|
|
const DB_VERSION = 1;
|
|
const STORE_NAME = 'firebase-heartbeat-store';
|
|
let dbPromise = null;
|
|
function getDbPromise() {
|
|
if (!dbPromise) {
|
|
dbPromise = openDB(DB_NAME, DB_VERSION, {
|
|
upgrade: (db, oldVersion) => {
|
|
// We don't use 'break' in this switch statement, the fall-through
|
|
// behavior is what we want, because if there are multiple versions between
|
|
// the old version and the current version, we want ALL the migrations
|
|
// that correspond to those versions to run, not only the last one.
|
|
// eslint-disable-next-line default-case
|
|
switch (oldVersion) {
|
|
case 0:
|
|
try {
|
|
db.createObjectStore(STORE_NAME);
|
|
}
|
|
catch (e) {
|
|
// Safari/iOS browsers throw occasional exceptions on
|
|
// db.createObjectStore() that may be a bug. Avoid blocking
|
|
// the rest of the app functionality.
|
|
console.warn(e);
|
|
}
|
|
}
|
|
}
|
|
}).catch(e => {
|
|
throw ERROR_FACTORY.create("idb-open" /* AppError.IDB_OPEN */, {
|
|
originalErrorMessage: e.message
|
|
});
|
|
});
|
|
}
|
|
return dbPromise;
|
|
}
|
|
async function readHeartbeatsFromIndexedDB(app) {
|
|
try {
|
|
const db = await getDbPromise();
|
|
const tx = db.transaction(STORE_NAME);
|
|
const result = await tx.objectStore(STORE_NAME).get(computeKey(app));
|
|
// We already have the value but tx.done can throw,
|
|
// so we need to await it here to catch errors
|
|
await tx.done;
|
|
return result;
|
|
}
|
|
catch (e) {
|
|
if (e instanceof FirebaseError) {
|
|
logger.warn(e.message);
|
|
}
|
|
else {
|
|
const idbGetError = ERROR_FACTORY.create("idb-get" /* AppError.IDB_GET */, {
|
|
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
});
|
|
logger.warn(idbGetError.message);
|
|
}
|
|
}
|
|
}
|
|
async function writeHeartbeatsToIndexedDB(app, heartbeatObject) {
|
|
try {
|
|
const db = await getDbPromise();
|
|
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
const objectStore = tx.objectStore(STORE_NAME);
|
|
await objectStore.put(heartbeatObject, computeKey(app));
|
|
await tx.done;
|
|
}
|
|
catch (e) {
|
|
if (e instanceof FirebaseError) {
|
|
logger.warn(e.message);
|
|
}
|
|
else {
|
|
const idbGetError = ERROR_FACTORY.create("idb-set" /* AppError.IDB_WRITE */, {
|
|
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
});
|
|
logger.warn(idbGetError.message);
|
|
}
|
|
}
|
|
}
|
|
function computeKey(app) {
|
|
return `${app.name}!${app.options.appId}`;
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const MAX_HEADER_BYTES = 1024;
|
|
// 30 days
|
|
const STORED_HEARTBEAT_RETENTION_MAX_MILLIS = 30 * 24 * 60 * 60 * 1000;
|
|
class HeartbeatServiceImpl {
|
|
constructor(container) {
|
|
this.container = container;
|
|
/**
|
|
* In-memory cache for heartbeats, used by getHeartbeatsHeader() to generate
|
|
* the header string.
|
|
* Stores one record per date. This will be consolidated into the standard
|
|
* format of one record per user agent string before being sent as a header.
|
|
* Populated from indexedDB when the controller is instantiated and should
|
|
* be kept in sync with indexedDB.
|
|
* Leave public for easier testing.
|
|
*/
|
|
this._heartbeatsCache = null;
|
|
const app = this.container.getProvider('app').getImmediate();
|
|
this._storage = new HeartbeatStorageImpl(app);
|
|
this._heartbeatsCachePromise = this._storage.read().then(result => {
|
|
this._heartbeatsCache = result;
|
|
return result;
|
|
});
|
|
}
|
|
/**
|
|
* Called to report a heartbeat. The function will generate
|
|
* a HeartbeatsByUserAgent object, update heartbeatsCache, and persist it
|
|
* to IndexedDB.
|
|
* Note that we only store one heartbeat per day. So if a heartbeat for today is
|
|
* already logged, subsequent calls to this function in the same day will be ignored.
|
|
*/
|
|
async triggerHeartbeat() {
|
|
var _a, _b;
|
|
const platformLogger = this.container
|
|
.getProvider('platform-logger')
|
|
.getImmediate();
|
|
// This is the "Firebase user agent" string from the platform logger
|
|
// service, not the browser user agent.
|
|
const agent = platformLogger.getPlatformInfoString();
|
|
const date = getUTCDateString();
|
|
if (((_a = this._heartbeatsCache) === null || _a === void 0 ? void 0 : _a.heartbeats) == null) {
|
|
this._heartbeatsCache = await this._heartbeatsCachePromise;
|
|
// If we failed to construct a heartbeats cache, then return immediately.
|
|
if (((_b = this._heartbeatsCache) === null || _b === void 0 ? void 0 : _b.heartbeats) == null) {
|
|
return;
|
|
}
|
|
}
|
|
// Do not store a heartbeat if one is already stored for this day
|
|
// or if a header has already been sent today.
|
|
if (this._heartbeatsCache.lastSentHeartbeatDate === date ||
|
|
this._heartbeatsCache.heartbeats.some(singleDateHeartbeat => singleDateHeartbeat.date === date)) {
|
|
return;
|
|
}
|
|
else {
|
|
// There is no entry for this date. Create one.
|
|
this._heartbeatsCache.heartbeats.push({ date, agent });
|
|
}
|
|
// Remove entries older than 30 days.
|
|
this._heartbeatsCache.heartbeats = this._heartbeatsCache.heartbeats.filter(singleDateHeartbeat => {
|
|
const hbTimestamp = new Date(singleDateHeartbeat.date).valueOf();
|
|
const now = Date.now();
|
|
return now - hbTimestamp <= STORED_HEARTBEAT_RETENTION_MAX_MILLIS;
|
|
});
|
|
return this._storage.overwrite(this._heartbeatsCache);
|
|
}
|
|
/**
|
|
* Returns a base64 encoded string which can be attached to the heartbeat-specific header directly.
|
|
* It also clears all heartbeats from memory as well as in IndexedDB.
|
|
*
|
|
* NOTE: Consuming product SDKs should not send the header if this method
|
|
* returns an empty string.
|
|
*/
|
|
async getHeartbeatsHeader() {
|
|
var _a;
|
|
if (this._heartbeatsCache === null) {
|
|
await this._heartbeatsCachePromise;
|
|
}
|
|
// If it's still null or the array is empty, there is no data to send.
|
|
if (((_a = this._heartbeatsCache) === null || _a === void 0 ? void 0 : _a.heartbeats) == null ||
|
|
this._heartbeatsCache.heartbeats.length === 0) {
|
|
return '';
|
|
}
|
|
const date = getUTCDateString();
|
|
// Extract as many heartbeats from the cache as will fit under the size limit.
|
|
const { heartbeatsToSend, unsentEntries } = extractHeartbeatsForHeader(this._heartbeatsCache.heartbeats);
|
|
const headerString = base64urlEncodeWithoutPadding(JSON.stringify({ version: 2, heartbeats: heartbeatsToSend }));
|
|
// Store last sent date to prevent another being logged/sent for the same day.
|
|
this._heartbeatsCache.lastSentHeartbeatDate = date;
|
|
if (unsentEntries.length > 0) {
|
|
// Store any unsent entries if they exist.
|
|
this._heartbeatsCache.heartbeats = unsentEntries;
|
|
// This seems more likely than emptying the array (below) to lead to some odd state
|
|
// since the cache isn't empty and this will be called again on the next request,
|
|
// and is probably safest if we await it.
|
|
await this._storage.overwrite(this._heartbeatsCache);
|
|
}
|
|
else {
|
|
this._heartbeatsCache.heartbeats = [];
|
|
// Do not wait for this, to reduce latency.
|
|
void this._storage.overwrite(this._heartbeatsCache);
|
|
}
|
|
return headerString;
|
|
}
|
|
}
|
|
function getUTCDateString() {
|
|
const today = new Date();
|
|
// Returns date format 'YYYY-MM-DD'
|
|
return today.toISOString().substring(0, 10);
|
|
}
|
|
function extractHeartbeatsForHeader(heartbeatsCache, maxSize = MAX_HEADER_BYTES) {
|
|
// Heartbeats grouped by user agent in the standard format to be sent in
|
|
// the header.
|
|
const heartbeatsToSend = [];
|
|
// Single date format heartbeats that are not sent.
|
|
let unsentEntries = heartbeatsCache.slice();
|
|
for (const singleDateHeartbeat of heartbeatsCache) {
|
|
// Look for an existing entry with the same user agent.
|
|
const heartbeatEntry = heartbeatsToSend.find(hb => hb.agent === singleDateHeartbeat.agent);
|
|
if (!heartbeatEntry) {
|
|
// If no entry for this user agent exists, create one.
|
|
heartbeatsToSend.push({
|
|
agent: singleDateHeartbeat.agent,
|
|
dates: [singleDateHeartbeat.date]
|
|
});
|
|
if (countBytes(heartbeatsToSend) > maxSize) {
|
|
// If the header would exceed max size, remove the added heartbeat
|
|
// entry and stop adding to the header.
|
|
heartbeatsToSend.pop();
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
heartbeatEntry.dates.push(singleDateHeartbeat.date);
|
|
// If the header would exceed max size, remove the added date
|
|
// and stop adding to the header.
|
|
if (countBytes(heartbeatsToSend) > maxSize) {
|
|
heartbeatEntry.dates.pop();
|
|
break;
|
|
}
|
|
}
|
|
// Pop unsent entry from queue. (Skipped if adding the entry exceeded
|
|
// quota and the loop breaks early.)
|
|
unsentEntries = unsentEntries.slice(1);
|
|
}
|
|
return {
|
|
heartbeatsToSend,
|
|
unsentEntries
|
|
};
|
|
}
|
|
class HeartbeatStorageImpl {
|
|
constructor(app) {
|
|
this.app = app;
|
|
this._canUseIndexedDBPromise = this.runIndexedDBEnvironmentCheck();
|
|
}
|
|
async runIndexedDBEnvironmentCheck() {
|
|
if (!isIndexedDBAvailable()) {
|
|
return false;
|
|
}
|
|
else {
|
|
return validateIndexedDBOpenable()
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
}
|
|
}
|
|
/**
|
|
* Read all heartbeats.
|
|
*/
|
|
async read() {
|
|
const canUseIndexedDB = await this._canUseIndexedDBPromise;
|
|
if (!canUseIndexedDB) {
|
|
return { heartbeats: [] };
|
|
}
|
|
else {
|
|
const idbHeartbeatObject = await readHeartbeatsFromIndexedDB(this.app);
|
|
if (idbHeartbeatObject === null || idbHeartbeatObject === void 0 ? void 0 : idbHeartbeatObject.heartbeats) {
|
|
return idbHeartbeatObject;
|
|
}
|
|
else {
|
|
return { heartbeats: [] };
|
|
}
|
|
}
|
|
}
|
|
// overwrite the storage with the provided heartbeats
|
|
async overwrite(heartbeatsObject) {
|
|
var _a;
|
|
const canUseIndexedDB = await this._canUseIndexedDBPromise;
|
|
if (!canUseIndexedDB) {
|
|
return;
|
|
}
|
|
else {
|
|
const existingHeartbeatsObject = await this.read();
|
|
return writeHeartbeatsToIndexedDB(this.app, {
|
|
lastSentHeartbeatDate: (_a = heartbeatsObject.lastSentHeartbeatDate) !== null && _a !== void 0 ? _a : existingHeartbeatsObject.lastSentHeartbeatDate,
|
|
heartbeats: heartbeatsObject.heartbeats
|
|
});
|
|
}
|
|
}
|
|
// add heartbeats
|
|
async add(heartbeatsObject) {
|
|
var _a;
|
|
const canUseIndexedDB = await this._canUseIndexedDBPromise;
|
|
if (!canUseIndexedDB) {
|
|
return;
|
|
}
|
|
else {
|
|
const existingHeartbeatsObject = await this.read();
|
|
return writeHeartbeatsToIndexedDB(this.app, {
|
|
lastSentHeartbeatDate: (_a = heartbeatsObject.lastSentHeartbeatDate) !== null && _a !== void 0 ? _a : existingHeartbeatsObject.lastSentHeartbeatDate,
|
|
heartbeats: [
|
|
...existingHeartbeatsObject.heartbeats,
|
|
...heartbeatsObject.heartbeats
|
|
]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Calculate bytes of a HeartbeatsByUserAgent array after being wrapped
|
|
* in a platform logging header JSON object, stringified, and converted
|
|
* to base 64.
|
|
*/
|
|
function countBytes(heartbeatsCache) {
|
|
// base64 has a restricted set of characters, all of which should be 1 byte.
|
|
return base64urlEncodeWithoutPadding(
|
|
// heartbeatsCache wrapper properties
|
|
JSON.stringify({ version: 2, heartbeats: heartbeatsCache })).length;
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
function registerCoreComponents(variant) {
|
|
_registerComponent(new Component('platform-logger', container => new PlatformLoggerServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */));
|
|
_registerComponent(new Component('heartbeat', container => new HeartbeatServiceImpl(container), "PRIVATE" /* ComponentType.PRIVATE */));
|
|
// Register `app` package.
|
|
registerVersion(name$o, version$1, variant);
|
|
// BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
|
|
registerVersion(name$o, version$1, 'esm2017');
|
|
// Register platform SDK identifier (no version).
|
|
registerVersion('fire-js', '');
|
|
}
|
|
|
|
/**
|
|
* Firebase App
|
|
*
|
|
* @remarks This package coordinates the communication between the different Firebase components
|
|
* @packageDocumentation
|
|
*/
|
|
registerCoreComponents('');
|
|
|
|
var name = "firebase";
|
|
var version = "10.8.0";
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2020 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
registerVersion(name, version, 'cdn');
|
|
|
|
export { FirebaseError, SDK_VERSION, DEFAULT_ENTRY_NAME as _DEFAULT_ENTRY_NAME, _addComponent, _addOrOverwriteComponent, _apps, _clearComponents, _components, _getProvider, _registerComponent, _removeServiceInstance, deleteApp, getApp, getApps, initializeApp, onLog, registerVersion, setLogLevel };
|
|
|
|
//# sourceMappingURL=firebase-app.js.map
|