1 | import { getApp, _getProvider, _registerComponent, registerVersion } from '@firebase/app';
|
2 | import { Logger } from '@firebase/logger';
|
3 | import { ErrorFactory, calculateBackoffMillis, FirebaseError, isIndexedDBAvailable, validateIndexedDBOpenable, isBrowserExtension, areCookiesEnabled, getModularInstance, deepEqual } from '@firebase/util';
|
4 | import { Component } from '@firebase/component';
|
5 | import '@firebase/installations';
|
6 |
|
7 | /**
|
8 | * @license
|
9 | * Copyright 2019 Google LLC
|
10 | *
|
11 | * Licensed under the Apache License, Version 2.0 (the "License");
|
12 | * you may not use this file except in compliance with the License.
|
13 | * You may obtain a copy of the License at
|
14 | *
|
15 | * http://www.apache.org/licenses/LICENSE-2.0
|
16 | *
|
17 | * Unless required by applicable law or agreed to in writing, software
|
18 | * distributed under the License is distributed on an "AS IS" BASIS,
|
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
20 | * See the License for the specific language governing permissions and
|
21 | * limitations under the License.
|
22 | */
|
23 | /**
|
24 | * Type constant for Firebase Analytics.
|
25 | */
|
26 | const ANALYTICS_TYPE = 'analytics';
|
27 | // Key to attach FID to in gtag params.
|
28 | const GA_FID_KEY = 'firebase_id';
|
29 | const ORIGIN_KEY = 'origin';
|
30 | const FETCH_TIMEOUT_MILLIS = 60 * 1000;
|
31 | const DYNAMIC_CONFIG_URL = 'https://firebase.googleapis.com/v1alpha/projects/-/apps/{app-id}/webConfig';
|
32 | const GTAG_URL = 'https://www.googletagmanager.com/gtag/js';
|
33 |
|
34 | /**
|
35 | * @license
|
36 | * Copyright 2019 Google LLC
|
37 | *
|
38 | * Licensed under the Apache License, Version 2.0 (the "License");
|
39 | * you may not use this file except in compliance with the License.
|
40 | * You may obtain a copy of the License at
|
41 | *
|
42 | * http://www.apache.org/licenses/LICENSE-2.0
|
43 | *
|
44 | * Unless required by applicable law or agreed to in writing, software
|
45 | * distributed under the License is distributed on an "AS IS" BASIS,
|
46 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
47 | * See the License for the specific language governing permissions and
|
48 | * limitations under the License.
|
49 | */
|
50 | const logger = new Logger('@firebase/analytics');
|
51 |
|
52 | /**
|
53 | * @license
|
54 | * Copyright 2019 Google LLC
|
55 | *
|
56 | * Licensed under the Apache License, Version 2.0 (the "License");
|
57 | * you may not use this file except in compliance with the License.
|
58 | * You may obtain a copy of the License at
|
59 | *
|
60 | * http://www.apache.org/licenses/LICENSE-2.0
|
61 | *
|
62 | * Unless required by applicable law or agreed to in writing, software
|
63 | * distributed under the License is distributed on an "AS IS" BASIS,
|
64 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
65 | * See the License for the specific language governing permissions and
|
66 | * limitations under the License.
|
67 | */
|
68 | /**
|
69 | * Makeshift polyfill for Promise.allSettled(). Resolves when all promises
|
70 | * have either resolved or rejected.
|
71 | *
|
72 | * @param promises Array of promises to wait for.
|
73 | */
|
74 | function promiseAllSettled(promises) {
|
75 | return Promise.all(promises.map(promise => promise.catch(e => e)));
|
76 | }
|
77 | /**
|
78 | * Inserts gtag script tag into the page to asynchronously download gtag.
|
79 | * @param dataLayerName Name of datalayer (most often the default, "_dataLayer").
|
80 | */
|
81 | function insertScriptTag(dataLayerName, measurementId) {
|
82 | const script = document.createElement('script');
|
83 | // We are not providing an analyticsId in the URL because it would trigger a `page_view`
|
84 | // without fid. We will initialize ga-id using gtag (config) command together with fid.
|
85 | script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`;
|
86 | script.async = true;
|
87 | document.head.appendChild(script);
|
88 | }
|
89 | /**
|
90 | * Get reference to, or create, global datalayer.
|
91 | * @param dataLayerName Name of datalayer (most often the default, "_dataLayer").
|
92 | */
|
93 | function getOrCreateDataLayer(dataLayerName) {
|
94 | // Check for existing dataLayer and create if needed.
|
95 | let dataLayer = [];
|
96 | if (Array.isArray(window[dataLayerName])) {
|
97 | dataLayer = window[dataLayerName];
|
98 | }
|
99 | else {
|
100 | window[dataLayerName] = dataLayer;
|
101 | }
|
102 | return dataLayer;
|
103 | }
|
104 | /**
|
105 | * Wrapped gtag logic when gtag is called with 'config' command.
|
106 | *
|
107 | * @param gtagCore Basic gtag function that just appends to dataLayer.
|
108 | * @param initializationPromisesMap Map of appIds to their initialization promises.
|
109 | * @param dynamicConfigPromisesList Array of dynamic config fetch promises.
|
110 | * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId.
|
111 | * @param measurementId GA Measurement ID to set config for.
|
112 | * @param gtagParams Gtag config params to set.
|
113 | */
|
114 | async function gtagOnConfig(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, measurementId, gtagParams) {
|
115 | // If config is already fetched, we know the appId and can use it to look up what FID promise we
|
116 | /// are waiting for, and wait only on that one.
|
117 | const correspondingAppId = measurementIdToAppId[measurementId];
|
118 | try {
|
119 | if (correspondingAppId) {
|
120 | await initializationPromisesMap[correspondingAppId];
|
121 | }
|
122 | else {
|
123 | // If config is not fetched yet, wait for all configs (we don't know which one we need) and
|
124 | // find the appId (if any) corresponding to this measurementId. If there is one, wait on
|
125 | // that appId's initialization promise. If there is none, promise resolves and gtag
|
126 | // call goes through.
|
127 | const dynamicConfigResults = await promiseAllSettled(dynamicConfigPromisesList);
|
128 | const foundConfig = dynamicConfigResults.find(config => config.measurementId === measurementId);
|
129 | if (foundConfig) {
|
130 | await initializationPromisesMap[foundConfig.appId];
|
131 | }
|
132 | }
|
133 | }
|
134 | catch (e) {
|
135 | logger.error(e);
|
136 | }
|
137 | gtagCore("config" /* CONFIG */, measurementId, gtagParams);
|
138 | }
|
139 | /**
|
140 | * Wrapped gtag logic when gtag is called with 'event' command.
|
141 | *
|
142 | * @param gtagCore Basic gtag function that just appends to dataLayer.
|
143 | * @param initializationPromisesMap Map of appIds to their initialization promises.
|
144 | * @param dynamicConfigPromisesList Array of dynamic config fetch promises.
|
145 | * @param measurementId GA Measurement ID to log event to.
|
146 | * @param gtagParams Params to log with this event.
|
147 | */
|
148 | async function gtagOnEvent(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementId, gtagParams) {
|
149 | try {
|
150 | let initializationPromisesToWaitFor = [];
|
151 | // If there's a 'send_to' param, check if any ID specified matches
|
152 | // an initializeIds() promise we are waiting for.
|
153 | if (gtagParams && gtagParams['send_to']) {
|
154 | let gaSendToList = gtagParams['send_to'];
|
155 | // Make it an array if is isn't, so it can be dealt with the same way.
|
156 | if (!Array.isArray(gaSendToList)) {
|
157 | gaSendToList = [gaSendToList];
|
158 | }
|
159 | // Checking 'send_to' fields requires having all measurement ID results back from
|
160 | // the dynamic config fetch.
|
161 | const dynamicConfigResults = await promiseAllSettled(dynamicConfigPromisesList);
|
162 | for (const sendToId of gaSendToList) {
|
163 | // Any fetched dynamic measurement ID that matches this 'send_to' ID
|
164 | const foundConfig = dynamicConfigResults.find(config => config.measurementId === sendToId);
|
165 | const initializationPromise = foundConfig && initializationPromisesMap[foundConfig.appId];
|
166 | if (initializationPromise) {
|
167 | initializationPromisesToWaitFor.push(initializationPromise);
|
168 | }
|
169 | else {
|
170 | // Found an item in 'send_to' that is not associated
|
171 | // directly with an FID, possibly a group. Empty this array,
|
172 | // exit the loop early, and let it get populated below.
|
173 | initializationPromisesToWaitFor = [];
|
174 | break;
|
175 | }
|
176 | }
|
177 | }
|
178 | // This will be unpopulated if there was no 'send_to' field , or
|
179 | // if not all entries in the 'send_to' field could be mapped to
|
180 | // a FID. In these cases, wait on all pending initialization promises.
|
181 | if (initializationPromisesToWaitFor.length === 0) {
|
182 | initializationPromisesToWaitFor = Object.values(initializationPromisesMap);
|
183 | }
|
184 | // Run core gtag function with args after all relevant initialization
|
185 | // promises have been resolved.
|
186 | await Promise.all(initializationPromisesToWaitFor);
|
187 | // Workaround for http://b/141370449 - third argument cannot be undefined.
|
188 | gtagCore("event" /* EVENT */, measurementId, gtagParams || {});
|
189 | }
|
190 | catch (e) {
|
191 | logger.error(e);
|
192 | }
|
193 | }
|
194 | /**
|
195 | * Wraps a standard gtag function with extra code to wait for completion of
|
196 | * relevant initialization promises before sending requests.
|
197 | *
|
198 | * @param gtagCore Basic gtag function that just appends to dataLayer.
|
199 | * @param initializationPromisesMap Map of appIds to their initialization promises.
|
200 | * @param dynamicConfigPromisesList Array of dynamic config fetch promises.
|
201 | * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId.
|
202 | */
|
203 | function wrapGtag(gtagCore,
|
204 | /**
|
205 | * Allows wrapped gtag calls to wait on whichever intialization promises are required,
|
206 | * depending on the contents of the gtag params' `send_to` field, if any.
|
207 | */
|
208 | initializationPromisesMap,
|
209 | /**
|
210 | * Wrapped gtag calls sometimes require all dynamic config fetches to have returned
|
211 | * before determining what initialization promises (which include FIDs) to wait for.
|
212 | */
|
213 | dynamicConfigPromisesList,
|
214 | /**
|
215 | * Wrapped gtag config calls can narrow down which initialization promise (with FID)
|
216 | * to wait for if the measurementId is already fetched, by getting the corresponding appId,
|
217 | * which is the key for the initialization promises map.
|
218 | */
|
219 | measurementIdToAppId) {
|
220 | /**
|
221 | * Wrapper around gtag that ensures FID is sent with gtag calls.
|
222 | * @param command Gtag command type.
|
223 | * @param idOrNameOrParams Measurement ID if command is EVENT/CONFIG, params if command is SET.
|
224 | * @param gtagParams Params if event is EVENT/CONFIG.
|
225 | */
|
226 | async function gtagWrapper(command, idOrNameOrParams, gtagParams) {
|
227 | try {
|
228 | // If event, check that relevant initialization promises have completed.
|
229 | if (command === "event" /* EVENT */) {
|
230 | // If EVENT, second arg must be measurementId.
|
231 | await gtagOnEvent(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, idOrNameOrParams, gtagParams);
|
232 | }
|
233 | else if (command === "config" /* CONFIG */) {
|
234 | // If CONFIG, second arg must be measurementId.
|
235 | await gtagOnConfig(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, idOrNameOrParams, gtagParams);
|
236 | }
|
237 | else {
|
238 | // If SET, second arg must be params.
|
239 | gtagCore("set" /* SET */, idOrNameOrParams);
|
240 | }
|
241 | }
|
242 | catch (e) {
|
243 | logger.error(e);
|
244 | }
|
245 | }
|
246 | return gtagWrapper;
|
247 | }
|
248 | /**
|
249 | * Creates global gtag function or wraps existing one if found.
|
250 | * This wrapped function attaches Firebase instance ID (FID) to gtag 'config' and
|
251 | * 'event' calls that belong to the GAID associated with this Firebase instance.
|
252 | *
|
253 | * @param initializationPromisesMap Map of appIds to their initialization promises.
|
254 | * @param dynamicConfigPromisesList Array of dynamic config fetch promises.
|
255 | * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId.
|
256 | * @param dataLayerName Name of global GA datalayer array.
|
257 | * @param gtagFunctionName Name of global gtag function ("gtag" if not user-specified).
|
258 | */
|
259 | function wrapOrCreateGtag(initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, dataLayerName, gtagFunctionName) {
|
260 | // Create a basic core gtag function
|
261 | let gtagCore = function (..._args) {
|
262 | // Must push IArguments object, not an array.
|
263 | window[dataLayerName].push(arguments);
|
264 | };
|
265 | // Replace it with existing one if found
|
266 | if (window[gtagFunctionName] &&
|
267 | typeof window[gtagFunctionName] === 'function') {
|
268 | // @ts-ignore
|
269 | gtagCore = window[gtagFunctionName];
|
270 | }
|
271 | window[gtagFunctionName] = wrapGtag(gtagCore, initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId);
|
272 | return {
|
273 | gtagCore,
|
274 | wrappedGtag: window[gtagFunctionName]
|
275 | };
|
276 | }
|
277 | /**
|
278 | * Returns first script tag in DOM matching our gtag url pattern.
|
279 | */
|
280 | function findGtagScriptOnPage() {
|
281 | const scriptTags = window.document.getElementsByTagName('script');
|
282 | for (const tag of Object.values(scriptTags)) {
|
283 | if (tag.src && tag.src.includes(GTAG_URL)) {
|
284 | return tag;
|
285 | }
|
286 | }
|
287 | return null;
|
288 | }
|
289 |
|
290 | /**
|
291 | * @license
|
292 | * Copyright 2019 Google LLC
|
293 | *
|
294 | * Licensed under the Apache License, Version 2.0 (the "License");
|
295 | * you may not use this file except in compliance with the License.
|
296 | * You may obtain a copy of the License at
|
297 | *
|
298 | * http://www.apache.org/licenses/LICENSE-2.0
|
299 | *
|
300 | * Unless required by applicable law or agreed to in writing, software
|
301 | * distributed under the License is distributed on an "AS IS" BASIS,
|
302 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
303 | * See the License for the specific language governing permissions and
|
304 | * limitations under the License.
|
305 | */
|
306 | const ERRORS = {
|
307 | ["already-exists" /* ALREADY_EXISTS */]: 'A Firebase Analytics instance with the appId {$id} ' +
|
308 | ' already exists. ' +
|
309 | 'Only one Firebase Analytics instance can be created for each appId.',
|
310 | ["already-initialized" /* ALREADY_INITIALIZED */]: 'initializeAnalytics() cannot be called again with different options than those ' +
|
311 | 'it was initially called with. It can be called again with the same options to ' +
|
312 | 'return the existing instance, or getAnalytics() can be used ' +
|
313 | 'to get a reference to the already-intialized instance.',
|
314 | ["already-initialized-settings" /* ALREADY_INITIALIZED_SETTINGS */]: 'Firebase Analytics has already been initialized.' +
|
315 | 'settings() must be called before initializing any Analytics instance' +
|
316 | 'or it will have no effect.',
|
317 | ["interop-component-reg-failed" /* INTEROP_COMPONENT_REG_FAILED */]: 'Firebase Analytics Interop Component failed to instantiate: {$reason}',
|
318 | ["invalid-analytics-context" /* INVALID_ANALYTICS_CONTEXT */]: 'Firebase Analytics is not supported in this environment. ' +
|
319 | 'Wrap initialization of analytics in analytics.isSupported() ' +
|
320 | 'to prevent initialization in unsupported environments. Details: {$errorInfo}',
|
321 | ["indexeddb-unavailable" /* INDEXEDDB_UNAVAILABLE */]: 'IndexedDB unavailable or restricted in this environment. ' +
|
322 | 'Wrap initialization of analytics in analytics.isSupported() ' +
|
323 | 'to prevent initialization in unsupported environments. Details: {$errorInfo}',
|
324 | ["fetch-throttle" /* FETCH_THROTTLE */]: 'The config fetch request timed out while in an exponential backoff state.' +
|
325 | ' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.',
|
326 | ["config-fetch-failed" /* CONFIG_FETCH_FAILED */]: 'Dynamic config fetch failed: [{$httpStatus}] {$responseMessage}',
|
327 | ["no-api-key" /* NO_API_KEY */]: 'The "apiKey" field is empty in the local Firebase config. Firebase Analytics requires this field to' +
|
328 | 'contain a valid API key.',
|
329 | ["no-app-id" /* NO_APP_ID */]: 'The "appId" field is empty in the local Firebase config. Firebase Analytics requires this field to' +
|
330 | 'contain a valid app ID.'
|
331 | };
|
332 | const ERROR_FACTORY = new ErrorFactory('analytics', 'Analytics', ERRORS);
|
333 |
|
334 | /**
|
335 | * @license
|
336 | * Copyright 2020 Google LLC
|
337 | *
|
338 | * Licensed under the Apache License, Version 2.0 (the "License");
|
339 | * you may not use this file except in compliance with the License.
|
340 | * You may obtain a copy of the License at
|
341 | *
|
342 | * http://www.apache.org/licenses/LICENSE-2.0
|
343 | *
|
344 | * Unless required by applicable law or agreed to in writing, software
|
345 | * distributed under the License is distributed on an "AS IS" BASIS,
|
346 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
347 | * See the License for the specific language governing permissions and
|
348 | * limitations under the License.
|
349 | */
|
350 | /**
|
351 | * Backoff factor for 503 errors, which we want to be conservative about
|
352 | * to avoid overloading servers. Each retry interval will be
|
353 | * BASE_INTERVAL_MILLIS * LONG_RETRY_FACTOR ^ retryCount, so the second one
|
354 | * will be ~30 seconds (with fuzzing).
|
355 | */
|
356 | const LONG_RETRY_FACTOR = 30;
|
357 | /**
|
358 | * Base wait interval to multiplied by backoffFactor^backoffCount.
|
359 | */
|
360 | const BASE_INTERVAL_MILLIS = 1000;
|
361 | /**
|
362 | * Stubbable retry data storage class.
|
363 | */
|
364 | class RetryData {
|
365 | constructor(throttleMetadata = {}, intervalMillis = BASE_INTERVAL_MILLIS) {
|
366 | this.throttleMetadata = throttleMetadata;
|
367 | this.intervalMillis = intervalMillis;
|
368 | }
|
369 | getThrottleMetadata(appId) {
|
370 | return this.throttleMetadata[appId];
|
371 | }
|
372 | setThrottleMetadata(appId, metadata) {
|
373 | this.throttleMetadata[appId] = metadata;
|
374 | }
|
375 | deleteThrottleMetadata(appId) {
|
376 | delete this.throttleMetadata[appId];
|
377 | }
|
378 | }
|
379 | const defaultRetryData = new RetryData();
|
380 | /**
|
381 | * Set GET request headers.
|
382 | * @param apiKey App API key.
|
383 | */
|
384 | function getHeaders(apiKey) {
|
385 | return new Headers({
|
386 | Accept: 'application/json',
|
387 | 'x-goog-api-key': apiKey
|
388 | });
|
389 | }
|
390 | /**
|
391 | * Fetches dynamic config from backend.
|
392 | * @param app Firebase app to fetch config for.
|
393 | */
|
394 | async function fetchDynamicConfig(appFields) {
|
395 | var _a;
|
396 | const { appId, apiKey } = appFields;
|
397 | const request = {
|
398 | method: 'GET',
|
399 | headers: getHeaders(apiKey)
|
400 | };
|
401 | const appUrl = DYNAMIC_CONFIG_URL.replace('{app-id}', appId);
|
402 | const response = await fetch(appUrl, request);
|
403 | if (response.status !== 200 && response.status !== 304) {
|
404 | let errorMessage = '';
|
405 | try {
|
406 | // Try to get any error message text from server response.
|
407 | const jsonResponse = (await response.json());
|
408 | if ((_a = jsonResponse.error) === null || _a === void 0 ? void 0 : _a.message) {
|
409 | errorMessage = jsonResponse.error.message;
|
410 | }
|
411 | }
|
412 | catch (_ignored) { }
|
413 | throw ERROR_FACTORY.create("config-fetch-failed" /* CONFIG_FETCH_FAILED */, {
|
414 | httpStatus: response.status,
|
415 | responseMessage: errorMessage
|
416 | });
|
417 | }
|
418 | return response.json();
|
419 | }
|
420 | /**
|
421 | * Fetches dynamic config from backend, retrying if failed.
|
422 | * @param app Firebase app to fetch config for.
|
423 | */
|
424 | async function fetchDynamicConfigWithRetry(app,
|
425 | // retryData and timeoutMillis are parameterized to allow passing a different value for testing.
|
426 | retryData = defaultRetryData, timeoutMillis) {
|
427 | const { appId, apiKey, measurementId } = app.options;
|
428 | if (!appId) {
|
429 | throw ERROR_FACTORY.create("no-app-id" /* NO_APP_ID */);
|
430 | }
|
431 | if (!apiKey) {
|
432 | if (measurementId) {
|
433 | return {
|
434 | measurementId,
|
435 | appId
|
436 | };
|
437 | }
|
438 | throw ERROR_FACTORY.create("no-api-key" /* NO_API_KEY */);
|
439 | }
|
440 | const throttleMetadata = retryData.getThrottleMetadata(appId) || {
|
441 | backoffCount: 0,
|
442 | throttleEndTimeMillis: Date.now()
|
443 | };
|
444 | const signal = new AnalyticsAbortSignal();
|
445 | setTimeout(async () => {
|
446 | // Note a very low delay, eg < 10ms, can elapse before listeners are initialized.
|
447 | signal.abort();
|
448 | }, timeoutMillis !== undefined ? timeoutMillis : FETCH_TIMEOUT_MILLIS);
|
449 | return attemptFetchDynamicConfigWithRetry({ appId, apiKey, measurementId }, throttleMetadata, signal, retryData);
|
450 | }
|
451 | /**
|
452 | * Runs one retry attempt.
|
453 | * @param appFields Necessary app config fields.
|
454 | * @param throttleMetadata Ongoing metadata to determine throttling times.
|
455 | * @param signal Abort signal.
|
456 | */
|
457 | async function attemptFetchDynamicConfigWithRetry(appFields, { throttleEndTimeMillis, backoffCount }, signal, retryData = defaultRetryData // for testing
|
458 | ) {
|
459 | const { appId, measurementId } = appFields;
|
460 | // Starts with a (potentially zero) timeout to support resumption from stored state.
|
461 | // Ensures the throttle end time is honored if the last attempt timed out.
|
462 | // Note the SDK will never make a request if the fetch timeout expires at this point.
|
463 | try {
|
464 | await setAbortableTimeout(signal, throttleEndTimeMillis);
|
465 | }
|
466 | catch (e) {
|
467 | if (measurementId) {
|
468 | logger.warn(`Timed out fetching this Firebase app's measurement ID from the server.` +
|
469 | ` Falling back to the measurement ID ${measurementId}` +
|
470 | ` provided in the "measurementId" field in the local Firebase config. [${e.message}]`);
|
471 | return { appId, measurementId };
|
472 | }
|
473 | throw e;
|
474 | }
|
475 | try {
|
476 | const response = await fetchDynamicConfig(appFields);
|
477 | // Note the SDK only clears throttle state if response is success or non-retriable.
|
478 | retryData.deleteThrottleMetadata(appId);
|
479 | return response;
|
480 | }
|
481 | catch (e) {
|
482 | if (!isRetriableError(e)) {
|
483 | retryData.deleteThrottleMetadata(appId);
|
484 | if (measurementId) {
|
485 | logger.warn(`Failed to fetch this Firebase app's measurement ID from the server.` +
|
486 | ` Falling back to the measurement ID ${measurementId}` +
|
487 | ` provided in the "measurementId" field in the local Firebase config. [${e.message}]`);
|
488 | return { appId, measurementId };
|
489 | }
|
490 | else {
|
491 | throw e;
|
492 | }
|
493 | }
|
494 | const backoffMillis = Number(e.customData.httpStatus) === 503
|
495 | ? calculateBackoffMillis(backoffCount, retryData.intervalMillis, LONG_RETRY_FACTOR)
|
496 | : calculateBackoffMillis(backoffCount, retryData.intervalMillis);
|
497 | // Increments backoff state.
|
498 | const throttleMetadata = {
|
499 | throttleEndTimeMillis: Date.now() + backoffMillis,
|
500 | backoffCount: backoffCount + 1
|
501 | };
|
502 | // Persists state.
|
503 | retryData.setThrottleMetadata(appId, throttleMetadata);
|
504 | logger.debug(`Calling attemptFetch again in ${backoffMillis} millis`);
|
505 | return attemptFetchDynamicConfigWithRetry(appFields, throttleMetadata, signal, retryData);
|
506 | }
|
507 | }
|
508 | /**
|
509 | * Supports waiting on a backoff by:
|
510 | *
|
511 | * <ul>
|
512 | * <li>Promisifying setTimeout, so we can set a timeout in our Promise chain</li>
|
513 | * <li>Listening on a signal bus for abort events, just like the Fetch API</li>
|
514 | * <li>Failing in the same way the Fetch API fails, so timing out a live request and a throttled
|
515 | * request appear the same.</li>
|
516 | * </ul>
|
517 | *
|
518 | * <p>Visible for testing.
|
519 | */
|
520 | function setAbortableTimeout(signal, throttleEndTimeMillis) {
|
521 | return new Promise((resolve, reject) => {
|
522 | // Derives backoff from given end time, normalizing negative numbers to zero.
|
523 | const backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0);
|
524 | const timeout = setTimeout(resolve, backoffMillis);
|
525 | // Adds listener, rather than sets onabort, because signal is a shared object.
|
526 | signal.addEventListener(() => {
|
527 | clearTimeout(timeout);
|
528 | // If the request completes before this timeout, the rejection has no effect.
|
529 | reject(ERROR_FACTORY.create("fetch-throttle" /* FETCH_THROTTLE */, {
|
530 | throttleEndTimeMillis
|
531 | }));
|
532 | });
|
533 | });
|
534 | }
|
535 | /**
|
536 | * Returns true if the {@link Error} indicates a fetch request may succeed later.
|
537 | */
|
538 | function isRetriableError(e) {
|
539 | if (!(e instanceof FirebaseError) || !e.customData) {
|
540 | return false;
|
541 | }
|
542 | // Uses string index defined by ErrorData, which FirebaseError implements.
|
543 | const httpStatus = Number(e.customData['httpStatus']);
|
544 | return (httpStatus === 429 ||
|
545 | httpStatus === 500 ||
|
546 | httpStatus === 503 ||
|
547 | httpStatus === 504);
|
548 | }
|
549 | /**
|
550 | * Shims a minimal AbortSignal (copied from Remote Config).
|
551 | *
|
552 | * <p>AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects
|
553 | * of networking, such as retries. Firebase doesn't use AbortController enough to justify a
|
554 | * polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be
|
555 | * swapped out if/when we do.
|
556 | */
|
557 | class AnalyticsAbortSignal {
|
558 | constructor() {
|
559 | this.listeners = [];
|
560 | }
|
561 | addEventListener(listener) {
|
562 | this.listeners.push(listener);
|
563 | }
|
564 | abort() {
|
565 | this.listeners.forEach(listener => listener());
|
566 | }
|
567 | }
|
568 |
|
569 | /**
|
570 | * @license
|
571 | * Copyright 2020 Google LLC
|
572 | *
|
573 | * Licensed under the Apache License, Version 2.0 (the "License");
|
574 | * you may not use this file except in compliance with the License.
|
575 | * You may obtain a copy of the License at
|
576 | *
|
577 | * http://www.apache.org/licenses/LICENSE-2.0
|
578 | *
|
579 | * Unless required by applicable law or agreed to in writing, software
|
580 | * distributed under the License is distributed on an "AS IS" BASIS,
|
581 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
582 | * See the License for the specific language governing permissions and
|
583 | * limitations under the License.
|
584 | */
|
585 | async function validateIndexedDB() {
|
586 | if (!isIndexedDBAvailable()) {
|
587 | logger.warn(ERROR_FACTORY.create("indexeddb-unavailable" /* INDEXEDDB_UNAVAILABLE */, {
|
588 | errorInfo: 'IndexedDB is not available in this environment.'
|
589 | }).message);
|
590 | return false;
|
591 | }
|
592 | else {
|
593 | try {
|
594 | await validateIndexedDBOpenable();
|
595 | }
|
596 | catch (e) {
|
597 | logger.warn(ERROR_FACTORY.create("indexeddb-unavailable" /* INDEXEDDB_UNAVAILABLE */, {
|
598 | errorInfo: e
|
599 | }).message);
|
600 | return false;
|
601 | }
|
602 | }
|
603 | return true;
|
604 | }
|
605 | /**
|
606 | * Initialize the analytics instance in gtag.js by calling config command with fid.
|
607 | *
|
608 | * NOTE: We combine analytics initialization and setting fid together because we want fid to be
|
609 | * part of the `page_view` event that's sent during the initialization
|
610 | * @param app Firebase app
|
611 | * @param gtagCore The gtag function that's not wrapped.
|
612 | * @param dynamicConfigPromisesList Array of all dynamic config promises.
|
613 | * @param measurementIdToAppId Maps measurementID to appID.
|
614 | * @param installations _FirebaseInstallationsInternal instance.
|
615 | *
|
616 | * @returns Measurement ID.
|
617 | */
|
618 | async function _initializeAnalytics(app, dynamicConfigPromisesList, measurementIdToAppId, installations, gtagCore, dataLayerName, options) {
|
619 | var _a;
|
620 | const dynamicConfigPromise = fetchDynamicConfigWithRetry(app);
|
621 | // Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function.
|
622 | dynamicConfigPromise
|
623 | .then(config => {
|
624 | measurementIdToAppId[config.measurementId] = config.appId;
|
625 | if (app.options.measurementId &&
|
626 | config.measurementId !== app.options.measurementId) {
|
627 | logger.warn(`The measurement ID in the local Firebase config (${app.options.measurementId})` +
|
628 | ` does not match the measurement ID fetched from the server (${config.measurementId}).` +
|
629 | ` To ensure analytics events are always sent to the correct Analytics property,` +
|
630 | ` update the` +
|
631 | ` measurement ID field in the local config or remove it from the local config.`);
|
632 | }
|
633 | })
|
634 | .catch(e => logger.error(e));
|
635 | // Add to list to track state of all dynamic config promises.
|
636 | dynamicConfigPromisesList.push(dynamicConfigPromise);
|
637 | const fidPromise = validateIndexedDB().then(envIsValid => {
|
638 | if (envIsValid) {
|
639 | return installations.getId();
|
640 | }
|
641 | else {
|
642 | return undefined;
|
643 | }
|
644 | });
|
645 | const [dynamicConfig, fid] = await Promise.all([
|
646 | dynamicConfigPromise,
|
647 | fidPromise
|
648 | ]);
|
649 | // Detect if user has already put the gtag <script> tag on this page.
|
650 | if (!findGtagScriptOnPage()) {
|
651 | insertScriptTag(dataLayerName, dynamicConfig.measurementId);
|
652 | }
|
653 | // This command initializes gtag.js and only needs to be called once for the entire web app,
|
654 | // but since it is idempotent, we can call it multiple times.
|
655 | // We keep it together with other initialization logic for better code structure.
|
656 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
657 | gtagCore('js', new Date());
|
658 | // User config added first. We don't want users to accidentally overwrite
|
659 | // base Firebase config properties.
|
660 | const configProperties = (_a = options === null || options === void 0 ? void 0 : options.config) !== null && _a !== void 0 ? _a : {};
|
661 | // guard against developers accidentally setting properties with prefix `firebase_`
|
662 | configProperties[ORIGIN_KEY] = 'firebase';
|
663 | configProperties.update = true;
|
664 | if (fid != null) {
|
665 | configProperties[GA_FID_KEY] = fid;
|
666 | }
|
667 | // It should be the first config command called on this GA-ID
|
668 | // Initialize this GA-ID and set FID on it using the gtag config API.
|
669 | // Note: This will trigger a page_view event unless 'send_page_view' is set to false in
|
670 | // `configProperties`.
|
671 | gtagCore("config" /* CONFIG */, dynamicConfig.measurementId, configProperties);
|
672 | return dynamicConfig.measurementId;
|
673 | }
|
674 |
|
675 | /**
|
676 | * @license
|
677 | * Copyright 2019 Google LLC
|
678 | *
|
679 | * Licensed under the Apache License, Version 2.0 (the "License");
|
680 | * you may not use this file except in compliance with the License.
|
681 | * You may obtain a copy of the License at
|
682 | *
|
683 | * http://www.apache.org/licenses/LICENSE-2.0
|
684 | *
|
685 | * Unless required by applicable law or agreed to in writing, software
|
686 | * distributed under the License is distributed on an "AS IS" BASIS,
|
687 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
688 | * See the License for the specific language governing permissions and
|
689 | * limitations under the License.
|
690 | */
|
691 | /**
|
692 | * Analytics Service class.
|
693 | */
|
694 | class AnalyticsService {
|
695 | constructor(app) {
|
696 | this.app = app;
|
697 | }
|
698 | _delete() {
|
699 | delete initializationPromisesMap[this.app.options.appId];
|
700 | return Promise.resolve();
|
701 | }
|
702 | }
|
703 | /**
|
704 | * Maps appId to full initialization promise. Wrapped gtag calls must wait on
|
705 | * all or some of these, depending on the call's `send_to` param and the status
|
706 | * of the dynamic config fetches (see below).
|
707 | */
|
708 | let initializationPromisesMap = {};
|
709 | /**
|
710 | * List of dynamic config fetch promises. In certain cases, wrapped gtag calls
|
711 | * wait on all these to be complete in order to determine if it can selectively
|
712 | * wait for only certain initialization (FID) promises or if it must wait for all.
|
713 | */
|
714 | let dynamicConfigPromisesList = [];
|
715 | /**
|
716 | * Maps fetched measurementIds to appId. Populated when the app's dynamic config
|
717 | * fetch completes. If already populated, gtag config calls can use this to
|
718 | * selectively wait for only this app's initialization promise (FID) instead of all
|
719 | * initialization promises.
|
720 | */
|
721 | const measurementIdToAppId = {};
|
722 | /**
|
723 | * Name for window global data layer array used by GA: defaults to 'dataLayer'.
|
724 | */
|
725 | let dataLayerName = 'dataLayer';
|
726 | /**
|
727 | * Name for window global gtag function used by GA: defaults to 'gtag'.
|
728 | */
|
729 | let gtagName = 'gtag';
|
730 | /**
|
731 | * Reproduction of standard gtag function or reference to existing
|
732 | * gtag function on window object.
|
733 | */
|
734 | let gtagCoreFunction;
|
735 | /**
|
736 | * Wrapper around gtag function that ensures FID is sent with all
|
737 | * relevant event and config calls.
|
738 | */
|
739 | let wrappedGtagFunction;
|
740 | /**
|
741 | * Flag to ensure page initialization steps (creation or wrapping of
|
742 | * dataLayer and gtag script) are only run once per page load.
|
743 | */
|
744 | let globalInitDone = false;
|
745 | /**
|
746 | * Configures Firebase Analytics to use custom `gtag` or `dataLayer` names.
|
747 | * Intended to be used if `gtag.js` script has been installed on
|
748 | * this page independently of Firebase Analytics, and is using non-default
|
749 | * names for either the `gtag` function or for `dataLayer`.
|
750 | * Must be called before calling `getAnalytics()` or it won't
|
751 | * have any effect.
|
752 | *
|
753 | * @public
|
754 | *
|
755 | * @param options - Custom gtag and dataLayer names.
|
756 | */
|
757 | function settings(options) {
|
758 | if (globalInitDone) {
|
759 | throw ERROR_FACTORY.create("already-initialized" /* ALREADY_INITIALIZED */);
|
760 | }
|
761 | if (options.dataLayerName) {
|
762 | dataLayerName = options.dataLayerName;
|
763 | }
|
764 | if (options.gtagName) {
|
765 | gtagName = options.gtagName;
|
766 | }
|
767 | }
|
768 | /**
|
769 | * Returns true if no environment mismatch is found.
|
770 | * If environment mismatches are found, throws an INVALID_ANALYTICS_CONTEXT
|
771 | * error that also lists details for each mismatch found.
|
772 | */
|
773 | function warnOnBrowserContextMismatch() {
|
774 | const mismatchedEnvMessages = [];
|
775 | if (isBrowserExtension()) {
|
776 | mismatchedEnvMessages.push('This is a browser extension environment.');
|
777 | }
|
778 | if (!areCookiesEnabled()) {
|
779 | mismatchedEnvMessages.push('Cookies are not available.');
|
780 | }
|
781 | if (mismatchedEnvMessages.length > 0) {
|
782 | const details = mismatchedEnvMessages
|
783 | .map((message, index) => `(${index + 1}) ${message}`)
|
784 | .join(' ');
|
785 | const err = ERROR_FACTORY.create("invalid-analytics-context" /* INVALID_ANALYTICS_CONTEXT */, {
|
786 | errorInfo: details
|
787 | });
|
788 | logger.warn(err.message);
|
789 | }
|
790 | }
|
791 | /**
|
792 | * Analytics instance factory.
|
793 | * @internal
|
794 | */
|
795 | function factory(app, installations, options) {
|
796 | warnOnBrowserContextMismatch();
|
797 | const appId = app.options.appId;
|
798 | if (!appId) {
|
799 | throw ERROR_FACTORY.create("no-app-id" /* NO_APP_ID */);
|
800 | }
|
801 | if (!app.options.apiKey) {
|
802 | if (app.options.measurementId) {
|
803 | logger.warn(`The "apiKey" field is empty in the local Firebase config. This is needed to fetch the latest` +
|
804 | ` measurement ID for this Firebase app. Falling back to the measurement ID ${app.options.measurementId}` +
|
805 | ` provided in the "measurementId" field in the local Firebase config.`);
|
806 | }
|
807 | else {
|
808 | throw ERROR_FACTORY.create("no-api-key" /* NO_API_KEY */);
|
809 | }
|
810 | }
|
811 | if (initializationPromisesMap[appId] != null) {
|
812 | throw ERROR_FACTORY.create("already-exists" /* ALREADY_EXISTS */, {
|
813 | id: appId
|
814 | });
|
815 | }
|
816 | if (!globalInitDone) {
|
817 | // Steps here should only be done once per page: creation or wrapping
|
818 | // of dataLayer and global gtag function.
|
819 | getOrCreateDataLayer(dataLayerName);
|
820 | const { wrappedGtag, gtagCore } = wrapOrCreateGtag(initializationPromisesMap, dynamicConfigPromisesList, measurementIdToAppId, dataLayerName, gtagName);
|
821 | wrappedGtagFunction = wrappedGtag;
|
822 | gtagCoreFunction = gtagCore;
|
823 | globalInitDone = true;
|
824 | }
|
825 | // Async but non-blocking.
|
826 | // This map reflects the completion state of all promises for each appId.
|
827 | initializationPromisesMap[appId] = _initializeAnalytics(app, dynamicConfigPromisesList, measurementIdToAppId, installations, gtagCoreFunction, dataLayerName, options);
|
828 | const analyticsInstance = new AnalyticsService(app);
|
829 | return analyticsInstance;
|
830 | }
|
831 |
|
832 | /**
|
833 | * @license
|
834 | * Copyright 2019 Google LLC
|
835 | *
|
836 | * Licensed under the Apache License, Version 2.0 (the "License");
|
837 | * you may not use this file except in compliance with the License.
|
838 | * You may obtain a copy of the License at
|
839 | *
|
840 | * http://www.apache.org/licenses/LICENSE-2.0
|
841 | *
|
842 | * Unless required by applicable law or agreed to in writing, software
|
843 | * distributed under the License is distributed on an "AS IS" BASIS,
|
844 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
845 | * See the License for the specific language governing permissions and
|
846 | * limitations under the License.
|
847 | */
|
848 | /**
|
849 | * Logs an analytics event through the Firebase SDK.
|
850 | *
|
851 | * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event
|
852 | * @param eventName Google Analytics event name, choose from standard list or use a custom string.
|
853 | * @param eventParams Analytics event parameters.
|
854 | */
|
855 | async function logEvent$1(gtagFunction, initializationPromise, eventName, eventParams, options) {
|
856 | if (options && options.global) {
|
857 | gtagFunction("event" /* EVENT */, eventName, eventParams);
|
858 | return;
|
859 | }
|
860 | else {
|
861 | const measurementId = await initializationPromise;
|
862 | const params = Object.assign(Object.assign({}, eventParams), { 'send_to': measurementId });
|
863 | gtagFunction("event" /* EVENT */, eventName, params);
|
864 | }
|
865 | }
|
866 | /**
|
867 | * Set screen_name parameter for this Google Analytics ID.
|
868 | *
|
869 | * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event
|
870 | * @param screenName Screen name string to set.
|
871 | */
|
872 | async function setCurrentScreen$1(gtagFunction, initializationPromise, screenName, options) {
|
873 | if (options && options.global) {
|
874 | gtagFunction("set" /* SET */, { 'screen_name': screenName });
|
875 | return Promise.resolve();
|
876 | }
|
877 | else {
|
878 | const measurementId = await initializationPromise;
|
879 | gtagFunction("config" /* CONFIG */, measurementId, {
|
880 | update: true,
|
881 | 'screen_name': screenName
|
882 | });
|
883 | }
|
884 | }
|
885 | /**
|
886 | * Set user_id parameter for this Google Analytics ID.
|
887 | *
|
888 | * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event
|
889 | * @param id User ID string to set
|
890 | */
|
891 | async function setUserId$1(gtagFunction, initializationPromise, id, options) {
|
892 | if (options && options.global) {
|
893 | gtagFunction("set" /* SET */, { 'user_id': id });
|
894 | return Promise.resolve();
|
895 | }
|
896 | else {
|
897 | const measurementId = await initializationPromise;
|
898 | gtagFunction("config" /* CONFIG */, measurementId, {
|
899 | update: true,
|
900 | 'user_id': id
|
901 | });
|
902 | }
|
903 | }
|
904 | /**
|
905 | * Set all other user properties other than user_id and screen_name.
|
906 | *
|
907 | * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event
|
908 | * @param properties Map of user properties to set
|
909 | */
|
910 | async function setUserProperties$1(gtagFunction, initializationPromise, properties, options) {
|
911 | if (options && options.global) {
|
912 | const flatProperties = {};
|
913 | for (const key of Object.keys(properties)) {
|
914 | // use dot notation for merge behavior in gtag.js
|
915 | flatProperties[`user_properties.${key}`] = properties[key];
|
916 | }
|
917 | gtagFunction("set" /* SET */, flatProperties);
|
918 | return Promise.resolve();
|
919 | }
|
920 | else {
|
921 | const measurementId = await initializationPromise;
|
922 | gtagFunction("config" /* CONFIG */, measurementId, {
|
923 | update: true,
|
924 | 'user_properties': properties
|
925 | });
|
926 | }
|
927 | }
|
928 | /**
|
929 | * Set whether collection is enabled for this ID.
|
930 | *
|
931 | * @param enabled If true, collection is enabled for this ID.
|
932 | */
|
933 | async function setAnalyticsCollectionEnabled$1(initializationPromise, enabled) {
|
934 | const measurementId = await initializationPromise;
|
935 | window[`ga-disable-${measurementId}`] = !enabled;
|
936 | }
|
937 |
|
938 | /* eslint-disable @typescript-eslint/no-explicit-any */
|
939 | /**
|
940 | * Returns an {@link Analytics} instance for the given app.
|
941 | *
|
942 | * @public
|
943 | *
|
944 | * @param app - The {@link @firebase/app#FirebaseApp} to use.
|
945 | */
|
946 | function getAnalytics(app = getApp()) {
|
947 | app = getModularInstance(app);
|
948 | // Dependencies
|
949 | const analyticsProvider = _getProvider(app, ANALYTICS_TYPE);
|
950 | if (analyticsProvider.isInitialized()) {
|
951 | return analyticsProvider.getImmediate();
|
952 | }
|
953 | return initializeAnalytics(app);
|
954 | }
|
955 | /**
|
956 | * Returns an {@link Analytics} instance for the given app.
|
957 | *
|
958 | * @public
|
959 | *
|
960 | * @param app - The {@link @firebase/app#FirebaseApp} to use.
|
961 | */
|
962 | function initializeAnalytics(app, options = {}) {
|
963 | // Dependencies
|
964 | const analyticsProvider = _getProvider(app, ANALYTICS_TYPE);
|
965 | if (analyticsProvider.isInitialized()) {
|
966 | const existingInstance = analyticsProvider.getImmediate();
|
967 | if (deepEqual(options, analyticsProvider.getOptions())) {
|
968 | return existingInstance;
|
969 | }
|
970 | else {
|
971 | throw ERROR_FACTORY.create("already-initialized" /* ALREADY_INITIALIZED */);
|
972 | }
|
973 | }
|
974 | const analyticsInstance = analyticsProvider.initialize({ options });
|
975 | return analyticsInstance;
|
976 | }
|
977 | /**
|
978 | * This is a public static method provided to users that wraps four different checks:
|
979 | *
|
980 | * 1. Check if it's not a browser extension environment.
|
981 | * 2. Check if cookies are enabled in current browser.
|
982 | * 3. Check if IndexedDB is supported by the browser environment.
|
983 | * 4. Check if the current browser context is valid for using `IndexedDB.open()`.
|
984 | *
|
985 | * @public
|
986 | *
|
987 | */
|
988 | async function isSupported() {
|
989 | if (isBrowserExtension()) {
|
990 | return false;
|
991 | }
|
992 | if (!areCookiesEnabled()) {
|
993 | return false;
|
994 | }
|
995 | if (!isIndexedDBAvailable()) {
|
996 | return false;
|
997 | }
|
998 | try {
|
999 | const isDBOpenable = await validateIndexedDBOpenable();
|
1000 | return isDBOpenable;
|
1001 | }
|
1002 | catch (error) {
|
1003 | return false;
|
1004 | }
|
1005 | }
|
1006 | /**
|
1007 | * Use gtag `config` command to set `screen_name`.
|
1008 | *
|
1009 | * @public
|
1010 | *
|
1011 | * @param analyticsInstance - The {@link Analytics} instance.
|
1012 | * @param screenName - Screen name to set.
|
1013 | */
|
1014 | function setCurrentScreen(analyticsInstance, screenName, options) {
|
1015 | analyticsInstance = getModularInstance(analyticsInstance);
|
1016 | setCurrentScreen$1(wrappedGtagFunction, initializationPromisesMap[analyticsInstance.app.options.appId], screenName, options).catch(e => logger.error(e));
|
1017 | }
|
1018 | /**
|
1019 | * Use gtag `config` command to set `user_id`.
|
1020 | *
|
1021 | * @public
|
1022 | *
|
1023 | * @param analyticsInstance - The {@link Analytics} instance.
|
1024 | * @param id - User ID to set.
|
1025 | */
|
1026 | function setUserId(analyticsInstance, id, options) {
|
1027 | analyticsInstance = getModularInstance(analyticsInstance);
|
1028 | setUserId$1(wrappedGtagFunction, initializationPromisesMap[analyticsInstance.app.options.appId], id, options).catch(e => logger.error(e));
|
1029 | }
|
1030 | /**
|
1031 | * Use gtag `config` command to set all params specified.
|
1032 | *
|
1033 | * @public
|
1034 | */
|
1035 | function setUserProperties(analyticsInstance, properties, options) {
|
1036 | analyticsInstance = getModularInstance(analyticsInstance);
|
1037 | setUserProperties$1(wrappedGtagFunction, initializationPromisesMap[analyticsInstance.app.options.appId], properties, options).catch(e => logger.error(e));
|
1038 | }
|
1039 | /**
|
1040 | * Sets whether Google Analytics collection is enabled for this app on this device.
|
1041 | * Sets global `window['ga-disable-analyticsId'] = true;`
|
1042 | *
|
1043 | * @public
|
1044 | *
|
1045 | * @param analyticsInstance - The {@link Analytics} instance.
|
1046 | * @param enabled - If true, enables collection, if false, disables it.
|
1047 | */
|
1048 | function setAnalyticsCollectionEnabled(analyticsInstance, enabled) {
|
1049 | analyticsInstance = getModularInstance(analyticsInstance);
|
1050 | setAnalyticsCollectionEnabled$1(initializationPromisesMap[analyticsInstance.app.options.appId], enabled).catch(e => logger.error(e));
|
1051 | }
|
1052 | /**
|
1053 | * Sends a Google Analytics event with given `eventParams`. This method
|
1054 | * automatically associates this logged event with this Firebase web
|
1055 | * app instance on this device.
|
1056 | * List of official event parameters can be found in the gtag.js
|
1057 | * reference documentation:
|
1058 | * {@link https://developers.google.com/gtagjs/reference/ga4-events
|
1059 | * | the GA4 reference documentation}.
|
1060 | *
|
1061 | * @public
|
1062 | */
|
1063 | function logEvent(analyticsInstance, eventName, eventParams, options) {
|
1064 | analyticsInstance = getModularInstance(analyticsInstance);
|
1065 | logEvent$1(wrappedGtagFunction, initializationPromisesMap[analyticsInstance.app.options.appId], eventName, eventParams, options).catch(e => logger.error(e));
|
1066 | }
|
1067 |
|
1068 | const name = "@firebase/analytics";
|
1069 | const version = "0.7.5";
|
1070 |
|
1071 | /**
|
1072 | * Firebase Analytics
|
1073 | *
|
1074 | * @packageDocumentation
|
1075 | */
|
1076 | function registerAnalytics() {
|
1077 | _registerComponent(new Component(ANALYTICS_TYPE, (container, { options: analyticsOptions }) => {
|
1078 | // getImmediate for FirebaseApp will always succeed
|
1079 | const app = container.getProvider('app').getImmediate();
|
1080 | const installations = container
|
1081 | .getProvider('installations-internal')
|
1082 | .getImmediate();
|
1083 | return factory(app, installations, analyticsOptions);
|
1084 | }, "PUBLIC" /* PUBLIC */));
|
1085 | _registerComponent(new Component('analytics-internal', internalFactory, "PRIVATE" /* PRIVATE */));
|
1086 | registerVersion(name, version);
|
1087 | // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
|
1088 | registerVersion(name, version, 'esm2017');
|
1089 | function internalFactory(container) {
|
1090 | try {
|
1091 | const analytics = container.getProvider(ANALYTICS_TYPE).getImmediate();
|
1092 | return {
|
1093 | logEvent: (eventName, eventParams, options) => logEvent(analytics, eventName, eventParams, options)
|
1094 | };
|
1095 | }
|
1096 | catch (e) {
|
1097 | throw ERROR_FACTORY.create("interop-component-reg-failed" /* INTEROP_COMPONENT_REG_FAILED */, {
|
1098 | reason: e
|
1099 | });
|
1100 | }
|
1101 | }
|
1102 | }
|
1103 | registerAnalytics();
|
1104 |
|
1105 | export { getAnalytics, initializeAnalytics, isSupported, logEvent, setAnalyticsCollectionEnabled, setCurrentScreen, setUserId, setUserProperties, settings };
|
1106 | //# sourceMappingURL=index.esm2017.js.map
|