import { getApp, _getProvider, _registerComponent, registerVersion } from '@firebase/app'; import { __awaiter, __generator, __spreadArray, __assign } from 'tslib'; import { Logger } from '@firebase/logger'; import { ErrorFactory, calculateBackoffMillis, FirebaseError, validateIndexedDBOpenable, isIndexedDBAvailable, isBrowserExtension, areCookiesEnabled, getModularInstance, deepEqual } from '@firebase/util'; import { Component } from '@firebase/component'; import '@firebase/installations'; /** * @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. */ /** * Type constant for Firebase Analytics. */ var ANALYTICS_TYPE = 'analytics'; // Key to attach FID to in gtag params. var GA_FID_KEY = 'firebase_id'; var ORIGIN_KEY = 'origin'; var FETCH_TIMEOUT_MILLIS = 60 * 1000; var DYNAMIC_CONFIG_URL = 'https://firebase.googleapis.com/v1alpha/projects/-/apps/{app-id}/webConfig'; var GTAG_URL = 'https://www.googletagmanager.com/gtag/js'; /** * @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. */ var logger = new Logger('@firebase/analytics'); /** * @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. */ var _a; var ERRORS = (_a = {}, _a["already-exists" /* AnalyticsError.ALREADY_EXISTS */] = 'A Firebase Analytics instance with the appId {$id} ' + ' already exists. ' + 'Only one Firebase Analytics instance can be created for each appId.', _a["already-initialized" /* AnalyticsError.ALREADY_INITIALIZED */] = 'initializeAnalytics() cannot be called again with different options than those ' + 'it was initially called with. It can be called again with the same options to ' + 'return the existing instance, or getAnalytics() can be used ' + 'to get a reference to the already-intialized instance.', _a["already-initialized-settings" /* AnalyticsError.ALREADY_INITIALIZED_SETTINGS */] = 'Firebase Analytics has already been initialized.' + 'settings() must be called before initializing any Analytics instance' + 'or it will have no effect.', _a["interop-component-reg-failed" /* AnalyticsError.INTEROP_COMPONENT_REG_FAILED */] = 'Firebase Analytics Interop Component failed to instantiate: {$reason}', _a["invalid-analytics-context" /* AnalyticsError.INVALID_ANALYTICS_CONTEXT */] = 'Firebase Analytics is not supported in this environment. ' + 'Wrap initialization of analytics in analytics.isSupported() ' + 'to prevent initialization in unsupported environments. Details: {$errorInfo}', _a["indexeddb-unavailable" /* AnalyticsError.INDEXEDDB_UNAVAILABLE */] = 'IndexedDB unavailable or restricted in this environment. ' + 'Wrap initialization of analytics in analytics.isSupported() ' + 'to prevent initialization in unsupported environments. Details: {$errorInfo}', _a["fetch-throttle" /* AnalyticsError.FETCH_THROTTLE */] = 'The config fetch request timed out while in an exponential backoff state.' + ' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.', _a["config-fetch-failed" /* AnalyticsError.CONFIG_FETCH_FAILED */] = 'Dynamic config fetch failed: [{$httpStatus}] {$responseMessage}', _a["no-api-key" /* AnalyticsError.NO_API_KEY */] = 'The "apiKey" field is empty in the local Firebase config. Firebase Analytics requires this field to' + 'contain a valid API key.', _a["no-app-id" /* AnalyticsError.NO_APP_ID */] = 'The "appId" field is empty in the local Firebase config. Firebase Analytics requires this field to' + 'contain a valid app ID.', _a["no-client-id" /* AnalyticsError.NO_CLIENT_ID */] = 'The "client_id" field is empty.', _a["invalid-gtag-resource" /* AnalyticsError.INVALID_GTAG_RESOURCE */] = 'Trusted Types detected an invalid gtag resource: {$gtagURL}.', _a); var ERROR_FACTORY = new ErrorFactory('analytics', 'Analytics', 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. */ /** * Verifies and creates a TrustedScriptURL. */ function createGtagTrustedTypesScriptURL(url) { if (!url.startsWith(GTAG_URL)) { var err = ERROR_FACTORY.create("invalid-gtag-resource" /* AnalyticsError.INVALID_GTAG_RESOURCE */, { gtagURL: url }); logger.warn(err.message); return ''; } return url; } /** * Makeshift polyfill for Promise.allSettled(). Resolves when all promises * have either resolved or rejected. * * @param promises Array of promises to wait for. */ function promiseAllSettled(promises) { return Promise.all(promises.map(function (promise) { return promise.catch(function (e) { return e; }); })); } /** * Creates a TrustedTypePolicy object that implements the rules passed as policyOptions. * * @param policyName A string containing the name of the policy * @param policyOptions Object containing implementations of instance methods for TrustedTypesPolicy, see {@link https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy#instance_methods * | the TrustedTypePolicy reference documentation}. */ function createTrustedTypesPolicy(policyName, policyOptions) { // Create a TrustedTypes policy that we can use for updating src // properties var trustedTypesPolicy; if (window.trustedTypes) { trustedTypesPolicy = window.trustedTypes.createPolicy(policyName, policyOptions); } return trustedTypesPolicy; } /** * Inserts gtag script tag into the page to asynchronously download gtag. * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). */ function insertScriptTag(dataLayerName, measurementId) { var trustedTypesPolicy = createTrustedTypesPolicy('firebase-js-sdk-policy', { createScriptURL: createGtagTrustedTypesScriptURL }); var script = document.createElement('script'); // We are not providing an analyticsId in the URL because it would trigger a `page_view` // without fid. We will initialize ga-id using gtag (config) command together with fid. var gtagScriptURL = "".concat(GTAG_URL, "?l=").concat(dataLayerName, "&id=").concat(measurementId); script.src = trustedTypesPolicy ? trustedTypesPolicy === null || trustedTypesPolicy === void 0 ? void 0 : trustedTypesPolicy.createScriptURL(gtagScriptURL) : gtagScriptURL; script.async = true; document.head.appendChild(script); } /** * Get reference to, or create, global datalayer. * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). */ function getOrCreateDataLayer(dataLayerName) { // Check for existing dataLayer and create if needed. var dataLayer = []; if (Array.isArray(window[dataLayerName])) { dataLayer = window[dataLayerName]; } else { window[dataLayerName] = dataLayer; } return dataLayer; } /** * Wrapped gtag logic when gtag is called with 'config' command. * * @param gtagCore Basic gtag function that just appends to dataLayer. * @param initializationPromisesMap Map of appIds to their initialization promises. * @param dynamicConfigPromisesList Array of dynamic config fetch promises. * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. * @param measurementId GA Measurement ID to set config for. * @param gtagParams Gtag config params to set. */ function gtagOnConfig(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, measurementId, gtagParams) { return __awaiter(this, void 0, void 0, function () { var correspondingAppId, dynamicConfigResults, foundConfig, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: correspondingAppId = measurementIdToAppId[measurementId]; _a.label = 1; case 1: _a.trys.push([1, 7, , 8]); if (!correspondingAppId) return [3 /*break*/, 3]; return [4 /*yield*/, initializationPromisesMap[correspondingAppId]]; case 2: _a.sent(); return [3 /*break*/, 6]; case 3: return [4 /*yield*/, promiseAllSettled(dynamicConfigPromisesList)]; case 4: dynamicConfigResults = _a.sent(); foundConfig = dynamicConfigResults.find(function (config) { return config.measurementId === measurementId; }); if (!foundConfig) return [3 /*break*/, 6]; return [4 /*yield*/, initializationPromisesMap[foundConfig.appId]]; case 5: _a.sent(); _a.label = 6; case 6: return [3 /*break*/, 8]; case 7: e_1 = _a.sent(); logger.error(e_1); return [3 /*break*/, 8]; case 8: gtagCore("config" /* GtagCommand.CONFIG */, measurementId, gtagParams); return [2 /*return*/]; } }); }); } /** * Wrapped gtag logic when gtag is called with 'event' command. * * @param gtagCore Basic gtag function that just appends to dataLayer. * @param initializationPromisesMap Map of appIds to their initialization promises. * @param dynamicConfigPromisesList Array of dynamic config fetch promises. * @param measurementId GA Measurement ID to log event to. * @param gtagParams Params to log with this event. */ function gtagOnEvent(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementId, gtagParams) { return __awaiter(this, void 0, void 0, function () { var initializationPromisesToWaitFor, gaSendToList, dynamicConfigResults, _loop_1, _i, gaSendToList_1, sendToId, state_1, e_2; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 4, , 5]); initializationPromisesToWaitFor = []; if (!(gtagParams && gtagParams['send_to'])) return [3 /*break*/, 2]; gaSendToList = gtagParams['send_to']; // Make it an array if is isn't, so it can be dealt with the same way. if (!Array.isArray(gaSendToList)) { gaSendToList = [gaSendToList]; } return [4 /*yield*/, promiseAllSettled(dynamicConfigPromisesList)]; case 1: dynamicConfigResults = _a.sent(); _loop_1 = function (sendToId) { // Any fetched dynamic measurement ID that matches this 'send_to' ID var foundConfig = dynamicConfigResults.find(function (config) { return config.measurementId === sendToId; }); var initializationPromise = foundConfig && initializationPromisesMap[foundConfig.appId]; if (initializationPromise) { initializationPromisesToWaitFor.push(initializationPromise); } else { // Found an item in 'send_to' that is not associated // directly with an FID, possibly a group. Empty this array, // exit the loop early, and let it get populated below. initializationPromisesToWaitFor = []; return "break"; } }; for (_i = 0, gaSendToList_1 = gaSendToList; _i < gaSendToList_1.length; _i++) { sendToId = gaSendToList_1[_i]; state_1 = _loop_1(sendToId); if (state_1 === "break") break; } _a.label = 2; case 2: // This will be unpopulated if there was no 'send_to' field , or // if not all entries in the 'send_to' field could be mapped to // a FID. In these cases, wait on all pending initialization promises. if (initializationPromisesToWaitFor.length === 0) { initializationPromisesToWaitFor = Object.values(initializationPromisesMap); } // Run core gtag function with args after all relevant initialization // promises have been resolved. return [4 /*yield*/, Promise.all(initializationPromisesToWaitFor)]; case 3: // Run core gtag function with args after all relevant initialization // promises have been resolved. _a.sent(); // Workaround for http://b/141370449 - third argument cannot be undefined. gtagCore("event" /* GtagCommand.EVENT */, measurementId, gtagParams || {}); return [3 /*break*/, 5]; case 4: e_2 = _a.sent(); logger.error(e_2); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); } /** * Wraps a standard gtag function with extra code to wait for completion of * relevant initialization promises before sending requests. * * @param gtagCore Basic gtag function that just appends to dataLayer. * @param initializationPromisesMap Map of appIds to their initialization promises. * @param dynamicConfigPromisesList Array of dynamic config fetch promises. * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. */ function wrapGtag(gtagCore, /** * Allows wrapped gtag calls to wait on whichever intialization promises are required, * depending on the contents of the gtag params' `send_to` field, if any. */ initializationPromisesMap, /** * Wrapped gtag calls sometimes require all dynamic config fetches to have returned * before determining what initialization promises (which include FIDs) to wait for. */ dynamicConfigPromisesList, /** * Wrapped gtag config calls can narrow down which initialization promise (with FID) * to wait for if the measurementId is already fetched, by getting the corresponding appId, * which is the key for the initialization promises map. */ measurementIdToAppId) { /** * Wrapper around gtag that ensures FID is sent with gtag calls. * @param command Gtag command type. * @param idOrNameOrParams Measurement ID if command is EVENT/CONFIG, params if command is SET. * @param gtagParams Params if event is EVENT/CONFIG. */ function gtagWrapper(command) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return __awaiter(this, void 0, void 0, function () { var measurementId, gtagParams, measurementId, gtagParams, gtagParams, measurementId, fieldName, callback, customParams, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 6, , 7]); if (!(command === "event" /* GtagCommand.EVENT */)) return [3 /*break*/, 2]; measurementId = args[0], gtagParams = args[1]; // If EVENT, second arg must be measurementId. return [4 /*yield*/, gtagOnEvent(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementId, gtagParams)]; case 1: // If EVENT, second arg must be measurementId. _a.sent(); return [3 /*break*/, 5]; case 2: if (!(command === "config" /* GtagCommand.CONFIG */)) return [3 /*break*/, 4]; measurementId = args[0], gtagParams = args[1]; // If CONFIG, second arg must be measurementId. return [4 /*yield*/, gtagOnConfig(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, measurementId, gtagParams)]; case 3: // If CONFIG, second arg must be measurementId. _a.sent(); return [3 /*break*/, 5]; case 4: if (command === "consent" /* GtagCommand.CONSENT */) { gtagParams = args[0]; gtagCore("consent" /* GtagCommand.CONSENT */, 'update', gtagParams); } else if (command === "get" /* GtagCommand.GET */) { measurementId = args[0], fieldName = args[1], callback = args[2]; gtagCore("get" /* GtagCommand.GET */, measurementId, fieldName, callback); } else if (command === "set" /* GtagCommand.SET */) { customParams = args[0]; // If SET, second arg must be params. gtagCore("set" /* GtagCommand.SET */, customParams); } else { gtagCore.apply(void 0, __spreadArray([command], args, false)); } _a.label = 5; case 5: return [3 /*break*/, 7]; case 6: e_3 = _a.sent(); logger.error(e_3); return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); } return gtagWrapper; } /** * Creates global gtag function or wraps existing one if found. * This wrapped function attaches Firebase instance ID (FID) to gtag 'config' and * 'event' calls that belong to the GAID associated with this Firebase instance. * * @param initializationPromisesMap Map of appIds to their initialization promises. * @param dynamicConfigPromisesList Array of dynamic config fetch promises. * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. * @param dataLayerName Name of global GA datalayer array. * @param gtagFunctionName Name of global gtag function ("gtag" if not user-specified). */ function wrapOrCreateGtag(initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, dataLayerName, gtagFunctionName) { // Create a basic core gtag function var gtagCore = function () { // Must push IArguments object, not an array. window[dataLayerName].push(arguments); }; // Replace it with existing one if found if (window[gtagFunctionName] && typeof window[gtagFunctionName] === 'function') { // @ts-ignore gtagCore = window[gtagFunctionName]; } window[gtagFunctionName] = wrapGtag(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId); return { gtagCore: gtagCore, wrappedGtag: window[gtagFunctionName] }; } /** * Returns the script tag in the DOM matching both the gtag url pattern * and the provided data layer name. */ function findGtagScriptOnPage(dataLayerName) { var scriptTags = window.document.getElementsByTagName('script'); for (var _i = 0, _a = Object.values(scriptTags); _i < _a.length; _i++) { var tag = _a[_i]; if (tag.src && tag.src.includes(GTAG_URL) && tag.src.includes(dataLayerName)) { return tag; } } return null; } /** * @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. */ /** * Backoff factor for 503 errors, which we want to be conservative about * to avoid overloading servers. Each retry interval will be * BASE_INTERVAL_MILLIS * LONG_RETRY_FACTOR ^ retryCount, so the second one * will be ~30 seconds (with fuzzing). */ var LONG_RETRY_FACTOR = 30; /** * Base wait interval to multiplied by backoffFactor^backoffCount. */ var BASE_INTERVAL_MILLIS = 1000; /** * Stubbable retry data storage class. */ var RetryData = /** @class */ (function () { function RetryData(throttleMetadata, intervalMillis) { if (throttleMetadata === void 0) { throttleMetadata = {}; } if (intervalMillis === void 0) { intervalMillis = BASE_INTERVAL_MILLIS; } this.throttleMetadata = throttleMetadata; this.intervalMillis = intervalMillis; } RetryData.prototype.getThrottleMetadata = function (appId) { return this.throttleMetadata[appId]; }; RetryData.prototype.setThrottleMetadata = function (appId, metadata) { this.throttleMetadata[appId] = metadata; }; RetryData.prototype.deleteThrottleMetadata = function (appId) { delete this.throttleMetadata[appId]; }; return RetryData; }()); var defaultRetryData = new RetryData(); /** * Set GET request headers. * @param apiKey App API key. */ function getHeaders(apiKey) { return new Headers({ Accept: 'application/json', 'x-goog-api-key': apiKey }); } /** * Fetches dynamic config from backend. * @param app Firebase app to fetch config for. */ function fetchDynamicConfig(appFields) { var _a; return __awaiter(this, void 0, void 0, function () { var appId, apiKey, request, appUrl, response, errorMessage, jsonResponse; return __generator(this, function (_b) { switch (_b.label) { case 0: appId = appFields.appId, apiKey = appFields.apiKey; request = { method: 'GET', headers: getHeaders(apiKey) }; appUrl = DYNAMIC_CONFIG_URL.replace('{app-id}', appId); return [4 /*yield*/, fetch(appUrl, request)]; case 1: response = _b.sent(); if (!(response.status !== 200 && response.status !== 304)) return [3 /*break*/, 6]; errorMessage = ''; _b.label = 2; case 2: _b.trys.push([2, 4, , 5]); return [4 /*yield*/, response.json()]; case 3: jsonResponse = (_b.sent()); if ((_a = jsonResponse.error) === null || _a === void 0 ? void 0 : _a.message) { errorMessage = jsonResponse.error.message; } return [3 /*break*/, 5]; case 4: _b.sent(); return [3 /*break*/, 5]; case 5: throw ERROR_FACTORY.create("config-fetch-failed" /* AnalyticsError.CONFIG_FETCH_FAILED */, { httpStatus: response.status, responseMessage: errorMessage }); case 6: return [2 /*return*/, response.json()]; } }); }); } /** * Fetches dynamic config from backend, retrying if failed. * @param app Firebase app to fetch config for. */ function fetchDynamicConfigWithRetry(app, // retryData and timeoutMillis are parameterized to allow passing a different value for testing. retryData, timeoutMillis) { if (retryData === void 0) { retryData = defaultRetryData; } return __awaiter(this, void 0, void 0, function () { var _a, appId, apiKey, measurementId, throttleMetadata, signal; var _this = this; return __generator(this, function (_b) { _a = app.options, appId = _a.appId, apiKey = _a.apiKey, measurementId = _a.measurementId; if (!appId) { throw ERROR_FACTORY.create("no-app-id" /* AnalyticsError.NO_APP_ID */); } if (!apiKey) { if (measurementId) { return [2 /*return*/, { measurementId: measurementId, appId: appId }]; } throw ERROR_FACTORY.create("no-api-key" /* AnalyticsError.NO_API_KEY */); } throttleMetadata = retryData.getThrottleMetadata(appId) || { backoffCount: 0, throttleEndTimeMillis: Date.now() }; signal = new AnalyticsAbortSignal(); setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { // Note a very low delay, eg < 10ms, can elapse before listeners are initialized. signal.abort(); return [2 /*return*/]; }); }); }, timeoutMillis !== undefined ? timeoutMillis : FETCH_TIMEOUT_MILLIS); return [2 /*return*/, attemptFetchDynamicConfigWithRetry({ appId: appId, apiKey: apiKey, measurementId: measurementId }, throttleMetadata, signal, retryData)]; }); }); } /** * Runs one retry attempt. * @param appFields Necessary app config fields. * @param throttleMetadata Ongoing metadata to determine throttling times. * @param signal Abort signal. */ function attemptFetchDynamicConfigWithRetry(appFields, _a, signal, retryData // for testing ) { var _b; var throttleEndTimeMillis = _a.throttleEndTimeMillis, backoffCount = _a.backoffCount; if (retryData === void 0) { retryData = defaultRetryData; } return __awaiter(this, void 0, void 0, function () { var appId, measurementId, e_1, response, e_2, error, backoffMillis, throttleMetadata; return __generator(this, function (_c) { switch (_c.label) { case 0: appId = appFields.appId, measurementId = appFields.measurementId; _c.label = 1; case 1: _c.trys.push([1, 3, , 4]); return [4 /*yield*/, setAbortableTimeout(signal, throttleEndTimeMillis)]; case 2: _c.sent(); return [3 /*break*/, 4]; case 3: e_1 = _c.sent(); if (measurementId) { logger.warn("Timed out fetching this Firebase app's measurement ID from the server." + " Falling back to the measurement ID ".concat(measurementId) + " provided in the \"measurementId\" field in the local Firebase config. [".concat(e_1 === null || e_1 === void 0 ? void 0 : e_1.message, "]")); return [2 /*return*/, { appId: appId, measurementId: measurementId }]; } throw e_1; case 4: _c.trys.push([4, 6, , 7]); return [4 /*yield*/, fetchDynamicConfig(appFields)]; case 5: response = _c.sent(); // Note the SDK only clears throttle state if response is success or non-retriable. retryData.deleteThrottleMetadata(appId); return [2 /*return*/, response]; case 6: e_2 = _c.sent(); error = e_2; if (!isRetriableError(error)) { retryData.deleteThrottleMetadata(appId); if (measurementId) { logger.warn("Failed to fetch this Firebase app's measurement ID from the server." + " Falling back to the measurement ID ".concat(measurementId) + " provided in the \"measurementId\" field in the local Firebase config. [".concat(error === null || error === void 0 ? void 0 : error.message, "]")); return [2 /*return*/, { appId: appId, measurementId: measurementId }]; } else { throw e_2; } } backoffMillis = Number((_b = error === null || error === void 0 ? void 0 : error.customData) === null || _b === void 0 ? void 0 : _b.httpStatus) === 503 ? calculateBackoffMillis(backoffCount, retryData.intervalMillis, LONG_RETRY_FACTOR) : calculateBackoffMillis(backoffCount, retryData.intervalMillis); throttleMetadata = { throttleEndTimeMillis: Date.now() + backoffMillis, backoffCount: backoffCount + 1 }; // Persists state. retryData.setThrottleMetadata(appId, throttleMetadata); logger.debug("Calling attemptFetch again in ".concat(backoffMillis, " millis")); return [2 /*return*/, attemptFetchDynamicConfigWithRetry(appFields, throttleMetadata, signal, retryData)]; case 7: return [2 /*return*/]; } }); }); } /** * Supports waiting on a backoff by: * * * *

Visible for testing. */ function setAbortableTimeout(signal, throttleEndTimeMillis) { return new Promise(function (resolve, reject) { // Derives backoff from given end time, normalizing negative numbers to zero. var backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0); var timeout = setTimeout(resolve, backoffMillis); // Adds listener, rather than sets onabort, because signal is a shared object. signal.addEventListener(function () { clearTimeout(timeout); // If the request completes before this timeout, the rejection has no effect. reject(ERROR_FACTORY.create("fetch-throttle" /* AnalyticsError.FETCH_THROTTLE */, { throttleEndTimeMillis: throttleEndTimeMillis })); }); }); } /** * Returns true if the {@link Error} indicates a fetch request may succeed later. */ function isRetriableError(e) { if (!(e instanceof FirebaseError) || !e.customData) { return false; } // Uses string index defined by ErrorData, which FirebaseError implements. var httpStatus = Number(e.customData['httpStatus']); return (httpStatus === 429 || httpStatus === 500 || httpStatus === 503 || httpStatus === 504); } /** * Shims a minimal AbortSignal (copied from Remote Config). * *

AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects * of networking, such as retries. Firebase doesn't use AbortController enough to justify a * polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be * swapped out if/when we do. */ var AnalyticsAbortSignal = /** @class */ (function () { function AnalyticsAbortSignal() { this.listeners = []; } AnalyticsAbortSignal.prototype.addEventListener = function (listener) { this.listeners.push(listener); }; AnalyticsAbortSignal.prototype.abort = function () { this.listeners.forEach(function (listener) { return listener(); }); }; return AnalyticsAbortSignal; }()); /** * @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. */ /** * Event parameters to set on 'gtag' during initialization. */ var defaultEventParametersForInit; /** * Logs an analytics event through the Firebase SDK. * * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event * @param eventName Google Analytics event name, choose from standard list or use a custom string. * @param eventParams Analytics event parameters. */ function logEvent$1(gtagFunction, initializationPromise, eventName, eventParams, options) { return __awaiter(this, void 0, void 0, function () { var measurementId, params; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(options && options.global)) return [3 /*break*/, 1]; gtagFunction("event" /* GtagCommand.EVENT */, eventName, eventParams); return [2 /*return*/]; case 1: return [4 /*yield*/, initializationPromise]; case 2: measurementId = _a.sent(); params = __assign(__assign({}, eventParams), { 'send_to': measurementId }); gtagFunction("event" /* GtagCommand.EVENT */, eventName, params); _a.label = 3; case 3: return [2 /*return*/]; } }); }); } /** * Set screen_name parameter for this Google Analytics ID. * * @deprecated Use {@link logEvent} with `eventName` as 'screen_view' and add relevant `eventParams`. * See {@link https://firebase.google.com/docs/analytics/screenviews | Track Screenviews}. * * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event * @param screenName Screen name string to set. */ function setCurrentScreen$1(gtagFunction, initializationPromise, screenName, options) { return __awaiter(this, void 0, void 0, function () { var measurementId; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(options && options.global)) return [3 /*break*/, 1]; gtagFunction("set" /* GtagCommand.SET */, { 'screen_name': screenName }); return [2 /*return*/, Promise.resolve()]; case 1: return [4 /*yield*/, initializationPromise]; case 2: measurementId = _a.sent(); gtagFunction("config" /* GtagCommand.CONFIG */, measurementId, { update: true, 'screen_name': screenName }); _a.label = 3; case 3: return [2 /*return*/]; } }); }); } /** * Set user_id parameter for this Google Analytics ID. * * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event * @param id User ID string to set */ function setUserId$1(gtagFunction, initializationPromise, id, options) { return __awaiter(this, void 0, void 0, function () { var measurementId; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(options && options.global)) return [3 /*break*/, 1]; gtagFunction("set" /* GtagCommand.SET */, { 'user_id': id }); return [2 /*return*/, Promise.resolve()]; case 1: return [4 /*yield*/, initializationPromise]; case 2: measurementId = _a.sent(); gtagFunction("config" /* GtagCommand.CONFIG */, measurementId, { update: true, 'user_id': id }); _a.label = 3; case 3: return [2 /*return*/]; } }); }); } /** * Set all other user properties other than user_id and screen_name. * * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event * @param properties Map of user properties to set */ function setUserProperties$1(gtagFunction, initializationPromise, properties, options) { return __awaiter(this, void 0, void 0, function () { var flatProperties, _i, _a, key, measurementId; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(options && options.global)) return [3 /*break*/, 1]; flatProperties = {}; for (_i = 0, _a = Object.keys(properties); _i < _a.length; _i++) { key = _a[_i]; // use dot notation for merge behavior in gtag.js flatProperties["user_properties.".concat(key)] = properties[key]; } gtagFunction("set" /* GtagCommand.SET */, flatProperties); return [2 /*return*/, Promise.resolve()]; case 1: return [4 /*yield*/, initializationPromise]; case 2: measurementId = _b.sent(); gtagFunction("config" /* GtagCommand.CONFIG */, measurementId, { update: true, 'user_properties': properties }); _b.label = 3; case 3: return [2 /*return*/]; } }); }); } /** * Retrieves a unique Google Analytics identifier for the web client. * See {@link https://developers.google.com/analytics/devguides/collection/ga4/reference/config#client_id | client_id}. * * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event */ function internalGetGoogleAnalyticsClientId(gtagFunction, initializationPromise) { return __awaiter(this, void 0, void 0, function () { var measurementId; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, initializationPromise]; case 1: measurementId = _a.sent(); return [2 /*return*/, new Promise(function (resolve, reject) { gtagFunction("get" /* GtagCommand.GET */, measurementId, 'client_id', function (clientId) { if (!clientId) { reject(ERROR_FACTORY.create("no-client-id" /* AnalyticsError.NO_CLIENT_ID */)); } resolve(clientId); }); })]; } }); }); } /** * Set whether collection is enabled for this ID. * * @param enabled If true, collection is enabled for this ID. */ function setAnalyticsCollectionEnabled$1(initializationPromise, enabled) { return __awaiter(this, void 0, void 0, function () { var measurementId; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, initializationPromise]; case 1: measurementId = _a.sent(); window["ga-disable-".concat(measurementId)] = !enabled; return [2 /*return*/]; } }); }); } /** * Consent parameters to default to during 'gtag' initialization. */ var defaultConsentSettingsForInit; /** * Sets the variable {@link defaultConsentSettingsForInit} for use in the initialization of * analytics. * * @param consentSettings Maps the applicable end user consent state for gtag.js. */ function _setConsentDefaultForInit(consentSettings) { defaultConsentSettingsForInit = consentSettings; } /** * Sets the variable `defaultEventParametersForInit` for use in the initialization of * analytics. * * @param customParams Any custom params the user may pass to gtag.js. */ function _setDefaultEventParametersForInit(customParams) { defaultEventParametersForInit = customParams; } /** * @license * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function validateIndexedDB() { return __awaiter(this, void 0, void 0, function () { var e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!!isIndexedDBAvailable()) return [3 /*break*/, 1]; logger.warn(ERROR_FACTORY.create("indexeddb-unavailable" /* AnalyticsError.INDEXEDDB_UNAVAILABLE */, { errorInfo: 'IndexedDB is not available in this environment.' }).message); return [2 /*return*/, false]; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, validateIndexedDBOpenable()]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: e_1 = _a.sent(); logger.warn(ERROR_FACTORY.create("indexeddb-unavailable" /* AnalyticsError.INDEXEDDB_UNAVAILABLE */, { errorInfo: e_1 === null || e_1 === void 0 ? void 0 : e_1.toString() }).message); return [2 /*return*/, false]; case 4: return [2 /*return*/, true]; } }); }); } /** * Initialize the analytics instance in gtag.js by calling config command with fid. * * NOTE: We combine analytics initialization and setting fid together because we want fid to be * part of the `page_view` event that's sent during the initialization * @param app Firebase app * @param gtagCore The gtag function that's not wrapped. * @param dynamicConfigPromisesList Array of all dynamic config promises. * @param measurementIdToAppId Maps measurementID to appID. * @param installations _FirebaseInstallationsInternal instance. * * @returns Measurement ID. */ function _initializeAnalytics(app, dynamicConfigPromisesList, measurementIdToAppId, installations, gtagCore, dataLayerName, options) { var _a; return __awaiter(this, void 0, void 0, function () { var dynamicConfigPromise, fidPromise, _b, dynamicConfig, fid, configProperties; return __generator(this, function (_c) { switch (_c.label) { case 0: dynamicConfigPromise = fetchDynamicConfigWithRetry(app); // Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function. dynamicConfigPromise .then(function (config) { measurementIdToAppId[config.measurementId] = config.appId; if (app.options.measurementId && config.measurementId !== app.options.measurementId) { logger.warn("The measurement ID in the local Firebase config (".concat(app.options.measurementId, ")") + " does not match the measurement ID fetched from the server (".concat(config.measurementId, ").") + " To ensure analytics events are always sent to the correct Analytics property," + " update the" + " measurement ID field in the local config or remove it from the local config."); } }) .catch(function (e) { return logger.error(e); }); // Add to list to track state of all dynamic config promises. dynamicConfigPromisesList.push(dynamicConfigPromise); fidPromise = validateIndexedDB().then(function (envIsValid) { if (envIsValid) { return installations.getId(); } else { return undefined; } }); return [4 /*yield*/, Promise.all([ dynamicConfigPromise, fidPromise ])]; case 1: _b = _c.sent(), dynamicConfig = _b[0], fid = _b[1]; // Detect if user has already put the gtag