Skip to content

Send Analytics measurement ID with script tag request #4434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/analytics/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ describe('FirebaseAnalytics instance tests', () => {
);
});
it('Warns if config has no apiKey but does have a measurementId', () => {
// Since this is a warning and doesn't block the rest of initialization
// all the async stuff needs to be stubbed and cleaned up.
const warnStub = stub(console, 'warn');
const docStub = stub(document, 'createElement');
stubFetch(200, { measurementId: fakeMeasurementId });
stubIdbOpen();
const app = getFakeApp({
appId: fakeAppParams.appId,
measurementId: fakeMeasurementId
Expand All @@ -104,6 +109,11 @@ describe('FirebaseAnalytics instance tests', () => {
`Falling back to the measurement ID ${fakeMeasurementId}`
);
warnStub.restore();
docStub.restore();
fetchStub.restore();
idbOpenStub.restore();
delete window['gtag'];
delete window['dataLayer'];
});
it('Throws if creating an instance with already-used appId', () => {
const app = getFakeApp(fakeAppParams);
Expand Down Expand Up @@ -210,6 +220,7 @@ describe('FirebaseAnalytics instance tests', () => {
afterEach(() => {
delete window['gtag'];
delete window['dataLayer'];
removeGtagScript();
fetchStub.restore();
clock.restore();
warnStub.restore();
Expand Down
20 changes: 6 additions & 14 deletions packages/analytics/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ import {
setUserProperties,
setAnalyticsCollectionEnabled
} from './functions';
import {
insertScriptTag,
getOrCreateDataLayer,
wrapOrCreateGtag,
findGtagScriptOnPage
} from './helpers';
import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers';
import { AnalyticsError, ERROR_FACTORY } from './errors';
import { FirebaseApp } from '@firebase/app-types';
import { FirebaseInstallations } from '@firebase/installations-types';
Expand All @@ -61,9 +56,9 @@ let initializationPromisesMap: {
* wait on all these to be complete in order to determine if it can selectively
* wait for only certain initialization (FID) promises or if it must wait for all.
*/
let dynamicConfigPromisesList: Array<Promise<
DynamicConfig | MinimalDynamicConfig
>> = [];
let dynamicConfigPromisesList: Array<
Promise<DynamicConfig | MinimalDynamicConfig>
> = [];

/**
* Maps fetched measurementIds to appId. Populated when the app's dynamic config
Expand Down Expand Up @@ -202,10 +197,6 @@ export function factory(
// Steps here should only be done once per page: creation or wrapping
// of dataLayer and global gtag function.

// Detect if user has already put the gtag <script> tag on this page.
if (!findGtagScriptOnPage()) {
insertScriptTag(dataLayerName);
}
getOrCreateDataLayer(dataLayerName);

const { wrappedGtag, gtagCore } = wrapOrCreateGtag(
Expand All @@ -227,7 +218,8 @@ export function factory(
dynamicConfigPromisesList,
measurementIdToAppId,
installations,
gtagCoreFunction
gtagCoreFunction,
dataLayerName
);

const analyticsInstance: FirebaseAnalyticsInternal = {
Expand Down
3 changes: 2 additions & 1 deletion packages/analytics/src/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ describe('Gtag wrapping functions', () => {

it('insertScriptIfNeeded inserts script tag', () => {
expect(findGtagScriptOnPage()).to.be.null;
insertScriptTag('customDataLayerName');
insertScriptTag('customDataLayerName', fakeMeasurementId);
const scriptTag = findGtagScriptOnPage();
expect(scriptTag).to.not.be.null;
expect(scriptTag!.src).to.contain(`l=customDataLayerName`);
expect(scriptTag!.src).to.contain(`id=${fakeMeasurementId}`);
});

describe('wrapOrCreateGtag() when user has not previously inserted a gtag script tag on this page', () => {
Expand Down
9 changes: 5 additions & 4 deletions packages/analytics/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import { logger } from './logger';
* Inserts gtag script tag into the page to asynchronously download gtag.
* @param dataLayerName Name of datalayer (most often the default, "_dataLayer").
*/
export function insertScriptTag(dataLayerName: string): void {
export function insertScriptTag(
dataLayerName: string,
measurementId: string
): void {
const 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.
script.src = `${GTAG_URL}?l=${dataLayerName}`;
script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`;
script.async = true;
document.head.appendChild(script);
}
Expand Down
14 changes: 10 additions & 4 deletions packages/analytics/src/initialize-ids.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { DynamicConfig } from '@firebase/analytics-types';
import { FirebaseInstallations } from '@firebase/installations-types';
import { FirebaseApp } from '@firebase/app-types';
import { Deferred } from '@firebase/util';
import { removeGtagScript } from '../testing/gtag-script-util';

const fakeMeasurementId = 'abcd-efgh-ijkl';
const fakeFid = 'fid-1234-zyxw';
Expand Down Expand Up @@ -60,6 +61,7 @@ describe('initializeIds()', () => {
});
afterEach(() => {
fetchStub.restore();
removeGtagScript();
});
it('gets FID and measurement ID and calls gtag config with them', async () => {
stubFetch();
Expand All @@ -68,7 +70,8 @@ describe('initializeIds()', () => {
dynamicPromisesList,
measurementIdToAppId,
installations,
gtagStub
gtagStub,
'dataLayer'
);
expect(gtagStub).to.be.calledWith(GtagCommand.CONFIG, fakeMeasurementId, {
'firebase_id': fakeFid,
Expand All @@ -83,7 +86,8 @@ describe('initializeIds()', () => {
dynamicPromisesList,
measurementIdToAppId,
installations,
gtagStub
gtagStub,
'dataLayer'
);
const dynamicPromiseResult = await dynamicPromisesList[0];
expect(dynamicPromiseResult.measurementId).to.equal(fakeMeasurementId);
Expand All @@ -96,7 +100,8 @@ describe('initializeIds()', () => {
dynamicPromisesList,
measurementIdToAppId,
installations,
gtagStub
gtagStub,
'dataLayer'
);
expect(measurementIdToAppId[fakeMeasurementId]).to.equal(fakeAppId);
});
Expand All @@ -108,7 +113,8 @@ describe('initializeIds()', () => {
dynamicPromisesList,
measurementIdToAppId,
installations,
gtagStub
gtagStub,
'dataLayer'
);
expect(consoleStub.args[0][1]).to.include(fakeMeasurementId);
expect(consoleStub.args[0][1]).to.include('old-measurement-id');
Expand Down
11 changes: 10 additions & 1 deletion packages/analytics/src/initialize-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
validateIndexedDBOpenable
} from '@firebase/util';
import { ERROR_FACTORY, AnalyticsError } from './errors';
import { findGtagScriptOnPage, insertScriptTag } from './helpers';

async function validateIndexedDB(): Promise<boolean> {
if (!isIndexedDBAvailable()) {
Expand Down Expand Up @@ -74,7 +75,8 @@ export async function initializeIds(
>,
measurementIdToAppId: { [key: string]: string },
installations: FirebaseInstallations,
gtagCore: Gtag
gtagCore: Gtag,
dataLayerName: string
): Promise<string> {
const dynamicConfigPromise = fetchDynamicConfigWithRetry(app);
// Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function.
Expand Down Expand Up @@ -113,6 +115,11 @@ export async function initializeIds(
fidPromise
]);

// Detect if user has already put the gtag <script> tag on this page.
if (!findGtagScriptOnPage()) {
insertScriptTag(dataLayerName, dynamicConfig.measurementId);
}

// This command initializes gtag.js and only needs to be called once for the entire web app,
// but since it is idempotent, we can call it multiple times.
// We keep it together with other initialization logic for better code structure.
Expand All @@ -131,6 +138,8 @@ export async function initializeIds(

// It should be the first config command called on this GA-ID
// Initialize this GA-ID and set FID on it using the gtag config API.
// Note: This will trigger a page_view event unless 'send_page_view' is set to false in
// `configProperties`.
gtagCore(GtagCommand.CONFIG, dynamicConfig.measurementId, configProperties);
return dynamicConfig.measurementId;
}