Skip to content

Commit ed4b147

Browse files
authored
Directly check for existence of gtag script tag (#2339)
1 parent efc0a78 commit ed4b147

File tree

7 files changed

+37
-60
lines changed

7 files changed

+37
-60
lines changed

packages/analytics/index.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ import {
2727
import { getFakeApp } from './testing/get-fake-app';
2828
import { FirebaseApp } from '@firebase/app-types';
2929
import { GtagCommand, EventName } from './src/constants';
30-
import {
31-
findGtagScriptOnPage,
32-
removeGtagScript
33-
} from './testing/gtag-script-util';
30+
import { findGtagScriptOnPage } from './src/helpers';
31+
import { removeGtagScript } from './testing/gtag-script-util';
3432

3533
let analyticsInstance: FirebaseAnalytics = {} as FirebaseAnalytics;
3634
const analyticsId = 'abcd-efgh';

packages/analytics/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ declare global {
3333
* Type constant for Firebase Analytics.
3434
*/
3535
const ANALYTICS_TYPE = 'analytics';
36-
3736
export function registerAnalytics(instance: _FirebaseNamespace): void {
3837
instance.INTERNAL.registerService(
3938
ANALYTICS_TYPE,

packages/analytics/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const ANALYTICS_ID_FIELD = 'measurementId';
2121
export const GA_FID_KEY = 'firebase_id';
2222
export const ORIGIN_KEY = 'origin';
2323

24+
export const GTAG_URL = 'https://www.googletagmanager.com/gtag/js';
25+
2426
export enum GtagCommand {
2527
EVENT = 'event',
2628
SET = 'set',

packages/analytics/src/factory.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
insertScriptTag,
3535
getOrCreateDataLayer,
3636
wrapOrCreateGtag,
37-
hasDataLayer
37+
findGtagScriptOnPage
3838
} from './helpers';
3939
import { ANALYTICS_ID_FIELD } from './constants';
4040
import { AnalyticsError, ERROR_FACTORY } from './errors';
@@ -122,9 +122,8 @@ export function factory(
122122
// Steps here should only be done once per page: creation or wrapping
123123
// of dataLayer and global gtag function.
124124

125-
// Presence of previously existing dataLayer used to detect if user has
126-
// already put the gtag snippet on this page.
127-
if (!hasDataLayer(dataLayerName)) {
125+
// Detect if user has already put the gtag <script> tag on this page.
126+
if (!findGtagScriptOnPage()) {
128127
insertScriptTag(dataLayerName);
129128
}
130129
getOrCreateDataLayer(dataLayerName);

packages/analytics/src/helpers.test.ts

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ import '../testing/setup';
2121
import { DataLayer, Gtag } from '@firebase/analytics-types';
2222
import {
2323
initializeGAId,
24-
hasDataLayer,
24+
getOrCreateDataLayer,
2525
insertScriptTag,
26-
wrapOrCreateGtag
26+
wrapOrCreateGtag,
27+
findGtagScriptOnPage
2728
} from './helpers';
2829
import { getFakeApp } from '../testing/get-fake-app';
2930
import { GtagCommand } from './constants';
30-
import { findGtagScriptOnPage } from '../testing/gtag-script-util';
3131
import { Deferred } from '@firebase/util';
3232

3333
const mockAnalyticsId = 'abcd-efgh-ijkl';
@@ -45,13 +45,14 @@ describe('FirebaseAnalytics methods', () => {
4545
});
4646
});
4747

48-
it('hasDataLayer is able to correctly identify an existing data layer', () => {
49-
expect(hasDataLayer('dataLayer')).to.be.false;
50-
window['dataLayer'] = [];
51-
expect(hasDataLayer('dataLayer')).to.be.true;
52-
window['dataLayer'] = 'hello';
53-
expect(hasDataLayer('dataLayer')).to.be.false;
48+
it('getOrCreateDataLayer is able to create a new data layer if none exists', () => {
5449
delete window['dataLayer'];
50+
expect(getOrCreateDataLayer('dataLayer')).to.deep.equal([]);
51+
});
52+
53+
it('getOrCreateDataLayer is able to correctly identify an existing data layer', () => {
54+
const existingDataLayer = (window['dataLayer'] = []);
55+
expect(getOrCreateDataLayer('dataLayer')).to.equal(existingDataLayer);
5556
});
5657

5758
it('insertScriptIfNeeded inserts script tag', () => {
@@ -248,27 +249,6 @@ describe('FirebaseAnalytics methods', () => {
248249
existingGtagStub.reset();
249250
});
250251

251-
// it('wrapped window.gtag function waits for initialization promises before sending events', async () => {
252-
// const deferred = new Deferred<void>();
253-
// wrapOrCreateGtag(
254-
// { [mockAnalyticsId]: deferred.promise },
255-
// 'dataLayer',
256-
// 'gtag'
257-
// );
258-
// (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', {
259-
// 'transaction_id': 'abcd123'
260-
// });
261-
// await Promise.resolve(); // Clear async event stack but not pending FID promise.
262-
// expect(existingGtagStub).to.not.be.called;
263-
// deferred.resolve(); // Resolves gaid initialization promise.
264-
// await Promise.resolve(); // wait for the next cycle
265-
// expect(existingGtagStub).to.be.calledWith(
266-
// GtagCommand.EVENT,
267-
// 'purchase',
268-
// { 'transaction_id': 'abcd123' }
269-
// );
270-
// });
271-
272252
it('new window.gtag function waits for all initialization promises before sending group events', async () => {
273253
const deferred = new Deferred<void>();
274254
const deferred2 = new Deferred<void>();

packages/analytics/src/helpers.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
GtagCommand,
2828
ANALYTICS_ID_FIELD,
2929
GA_FID_KEY,
30-
ORIGIN_KEY
30+
ORIGIN_KEY,
31+
GTAG_URL
3132
} from './constants';
3233
import '@firebase/installations';
3334

@@ -61,15 +62,11 @@ export async function initializeGAId(
6162
});
6263
}
6364

64-
export function hasDataLayer(dataLayerName: string): boolean {
65-
return Array.isArray(window[dataLayerName]);
66-
}
67-
6865
export function insertScriptTag(dataLayerName: string): void {
6966
const script = document.createElement('script');
7067
// We are not providing an analyticsId in the URL because it would trigger a `page_view`
7168
// without fid. We will initialize ga-id using gtag (config) command together with fid.
72-
script.src = `https://www.googletagmanager.com/gtag/js?l=${dataLayerName}`;
69+
script.src = `${GTAG_URL}?l=${dataLayerName}`;
7370
script.async = true;
7471
document.head.appendChild(script);
7572
}
@@ -80,10 +77,10 @@ export function insertScriptTag(dataLayerName: string): void {
8077
export function getOrCreateDataLayer(dataLayerName: string): DataLayer {
8178
// Check for existing dataLayer and create if needed.
8279
let dataLayer: DataLayer = [];
83-
if (hasDataLayer(dataLayerName)) {
80+
if (Array.isArray(window[dataLayerName])) {
8481
dataLayer = window[dataLayerName] as DataLayer;
8582
} else {
86-
dataLayer = window[dataLayerName] = [];
83+
window[dataLayerName] = dataLayer;
8784
}
8885
return dataLayer;
8986
}
@@ -206,3 +203,16 @@ export function wrapOrCreateGtag(
206203
wrappedGtag: window[gtagFunctionName] as Gtag
207204
};
208205
}
206+
207+
/**
208+
* Returns first script tag in DOM matching our gtag url pattern.
209+
*/
210+
export function findGtagScriptOnPage(): HTMLScriptElement | null {
211+
const scriptTags = window.document.getElementsByTagName('script');
212+
for (const tag of Object.values(scriptTags)) {
213+
if (tag.src && tag.src.includes(GTAG_URL)) {
214+
return tag;
215+
}
216+
}
217+
return null;
218+
}

packages/analytics/testing/gtag-script-util.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,13 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
18-
export function findGtagScriptOnPage(): HTMLScriptElement | null {
19-
const scriptTags = window.document.getElementsByTagName('script');
20-
for (const tag of Object.values(scriptTags)) {
21-
if (tag.src) {
22-
if (tag.src.includes('googletagmanager')) {
23-
return tag;
24-
}
25-
}
26-
}
27-
return null;
28-
}
17+
import { GTAG_URL } from '../src/constants';
2918

3019
export function removeGtagScript(): void {
3120
const scriptTags = window.document.getElementsByTagName('script');
3221
for (const tag of Object.values(scriptTags)) {
3322
if (tag.src) {
34-
if (tag.src.includes('googletagmanager') && tag.parentElement) {
23+
if (tag.src.includes(GTAG_URL) && tag.parentElement) {
3524
tag.parentElement!.removeChild(tag);
3625
}
3726
}

0 commit comments

Comments
 (0)