Skip to content

Commit 384b64d

Browse files
authored
Include measurementId in script tag url (#4434)
1 parent 9bac402 commit 384b64d

File tree

6 files changed

+44
-24
lines changed

6 files changed

+44
-24
lines changed

packages/analytics/index.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,12 @@ describe('FirebaseAnalytics instance tests', () => {
9393
);
9494
});
9595
it('Warns if config has no apiKey but does have a measurementId', () => {
96+
// Since this is a warning and doesn't block the rest of initialization
97+
// all the async stuff needs to be stubbed and cleaned up.
9698
const warnStub = stub(console, 'warn');
99+
const docStub = stub(document, 'createElement');
100+
stubFetch(200, { measurementId: fakeMeasurementId });
101+
stubIdbOpen();
97102
const app = getFakeApp({
98103
appId: fakeAppParams.appId,
99104
measurementId: fakeMeasurementId
@@ -104,6 +109,11 @@ describe('FirebaseAnalytics instance tests', () => {
104109
`Falling back to the measurement ID ${fakeMeasurementId}`
105110
);
106111
warnStub.restore();
112+
docStub.restore();
113+
fetchStub.restore();
114+
idbOpenStub.restore();
115+
delete window['gtag'];
116+
delete window['dataLayer'];
107117
});
108118
it('Throws if creating an instance with already-used appId', () => {
109119
const app = getFakeApp(fakeAppParams);
@@ -210,6 +220,7 @@ describe('FirebaseAnalytics instance tests', () => {
210220
afterEach(() => {
211221
delete window['gtag'];
212222
delete window['dataLayer'];
223+
removeGtagScript();
213224
fetchStub.restore();
214225
clock.restore();
215226
warnStub.restore();

packages/analytics/src/factory.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ import {
2929
setUserProperties,
3030
setAnalyticsCollectionEnabled
3131
} from './functions';
32-
import {
33-
insertScriptTag,
34-
getOrCreateDataLayer,
35-
wrapOrCreateGtag,
36-
findGtagScriptOnPage
37-
} from './helpers';
32+
import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers';
3833
import { AnalyticsError, ERROR_FACTORY } from './errors';
3934
import { FirebaseApp } from '@firebase/app-types';
4035
import { FirebaseInstallations } from '@firebase/installations-types';
@@ -61,9 +56,9 @@ let initializationPromisesMap: {
6156
* wait on all these to be complete in order to determine if it can selectively
6257
* wait for only certain initialization (FID) promises or if it must wait for all.
6358
*/
64-
let dynamicConfigPromisesList: Array<Promise<
65-
DynamicConfig | MinimalDynamicConfig
66-
>> = [];
59+
let dynamicConfigPromisesList: Array<
60+
Promise<DynamicConfig | MinimalDynamicConfig>
61+
> = [];
6762

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

205-
// Detect if user has already put the gtag <script> tag on this page.
206-
if (!findGtagScriptOnPage()) {
207-
insertScriptTag(dataLayerName);
208-
}
209200
getOrCreateDataLayer(dataLayerName);
210201

211202
const { wrappedGtag, gtagCore } = wrapOrCreateGtag(
@@ -227,7 +218,8 @@ export function factory(
227218
dynamicConfigPromisesList,
228219
measurementIdToAppId,
229220
installations,
230-
gtagCoreFunction
221+
gtagCoreFunction,
222+
dataLayerName
231223
);
232224

233225
const analyticsInstance: FirebaseAnalyticsInternal = {

packages/analytics/src/helpers.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ describe('Gtag wrapping functions', () => {
5656

5757
it('insertScriptIfNeeded inserts script tag', () => {
5858
expect(findGtagScriptOnPage()).to.be.null;
59-
insertScriptTag('customDataLayerName');
59+
insertScriptTag('customDataLayerName', fakeMeasurementId);
6060
const scriptTag = findGtagScriptOnPage();
6161
expect(scriptTag).to.not.be.null;
6262
expect(scriptTag!.src).to.contain(`l=customDataLayerName`);
63+
expect(scriptTag!.src).to.contain(`id=${fakeMeasurementId}`);
6364
});
6465

6566
describe('wrapOrCreateGtag() when user has not previously inserted a gtag script tag on this page', () => {

packages/analytics/src/helpers.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ import { logger } from './logger';
3131
* Inserts gtag script tag into the page to asynchronously download gtag.
3232
* @param dataLayerName Name of datalayer (most often the default, "_dataLayer").
3333
*/
34-
export function insertScriptTag(dataLayerName: string): void {
34+
export function insertScriptTag(
35+
dataLayerName: string,
36+
measurementId: string
37+
): void {
3538
const script = document.createElement('script');
36-
// We are not providing an analyticsId in the URL because it would trigger a `page_view`
37-
// without fid. We will initialize ga-id using gtag (config) command together with fid.
38-
script.src = `${GTAG_URL}?l=${dataLayerName}`;
39+
script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`;
3940
script.async = true;
4041
document.head.appendChild(script);
4142
}

packages/analytics/src/initialize-ids.test.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { DynamicConfig } from '@firebase/analytics-types';
2828
import { FirebaseInstallations } from '@firebase/installations-types';
2929
import { FirebaseApp } from '@firebase/app-types';
3030
import { Deferred } from '@firebase/util';
31+
import { removeGtagScript } from '../testing/gtag-script-util';
3132

3233
const fakeMeasurementId = 'abcd-efgh-ijkl';
3334
const fakeFid = 'fid-1234-zyxw';
@@ -60,6 +61,7 @@ describe('initializeIds()', () => {
6061
});
6162
afterEach(() => {
6263
fetchStub.restore();
64+
removeGtagScript();
6365
});
6466
it('gets FID and measurement ID and calls gtag config with them', async () => {
6567
stubFetch();
@@ -68,7 +70,8 @@ describe('initializeIds()', () => {
6870
dynamicPromisesList,
6971
measurementIdToAppId,
7072
installations,
71-
gtagStub
73+
gtagStub,
74+
'dataLayer'
7275
);
7376
expect(gtagStub).to.be.calledWith(GtagCommand.CONFIG, fakeMeasurementId, {
7477
'firebase_id': fakeFid,
@@ -83,7 +86,8 @@ describe('initializeIds()', () => {
8386
dynamicPromisesList,
8487
measurementIdToAppId,
8588
installations,
86-
gtagStub
89+
gtagStub,
90+
'dataLayer'
8791
);
8892
const dynamicPromiseResult = await dynamicPromisesList[0];
8993
expect(dynamicPromiseResult.measurementId).to.equal(fakeMeasurementId);
@@ -96,7 +100,8 @@ describe('initializeIds()', () => {
96100
dynamicPromisesList,
97101
measurementIdToAppId,
98102
installations,
99-
gtagStub
103+
gtagStub,
104+
'dataLayer'
100105
);
101106
expect(measurementIdToAppId[fakeMeasurementId]).to.equal(fakeAppId);
102107
});
@@ -108,7 +113,8 @@ describe('initializeIds()', () => {
108113
dynamicPromisesList,
109114
measurementIdToAppId,
110115
installations,
111-
gtagStub
116+
gtagStub,
117+
'dataLayer'
112118
);
113119
expect(consoleStub.args[0][1]).to.include(fakeMeasurementId);
114120
expect(consoleStub.args[0][1]).to.include('old-measurement-id');

packages/analytics/src/initialize-ids.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
validateIndexedDBOpenable
3131
} from '@firebase/util';
3232
import { ERROR_FACTORY, AnalyticsError } from './errors';
33+
import { findGtagScriptOnPage, insertScriptTag } from './helpers';
3334

3435
async function validateIndexedDB(): Promise<boolean> {
3536
if (!isIndexedDBAvailable()) {
@@ -74,7 +75,8 @@ export async function initializeIds(
7475
>,
7576
measurementIdToAppId: { [key: string]: string },
7677
installations: FirebaseInstallations,
77-
gtagCore: Gtag
78+
gtagCore: Gtag,
79+
dataLayerName: string
7880
): Promise<string> {
7981
const dynamicConfigPromise = fetchDynamicConfigWithRetry(app);
8082
// Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function.
@@ -113,6 +115,11 @@ export async function initializeIds(
113115
fidPromise
114116
]);
115117

118+
// Detect if user has already put the gtag <script> tag on this page.
119+
if (!findGtagScriptOnPage()) {
120+
insertScriptTag(dataLayerName, dynamicConfig.measurementId);
121+
}
122+
116123
// This command initializes gtag.js and only needs to be called once for the entire web app,
117124
// but since it is idempotent, we can call it multiple times.
118125
// We keep it together with other initialization logic for better code structure.
@@ -131,6 +138,8 @@ export async function initializeIds(
131138

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

0 commit comments

Comments
 (0)