diff --git a/src/initialize.js b/src/initialize.js index d84f656d6..b4ed73fd5 100644 --- a/src/initialize.js +++ b/src/initialize.js @@ -68,7 +68,7 @@ import { import { configure as configureAnalytics, SegmentAnalyticsService, identifyAnonymousUser, identifyAuthenticatedUser, } from './analytics'; -import { GoogleAnalyticsLoader } from './scripts'; +import { GoogleAnalyticsLoader, GoogleTagManagerLoader } from './scripts'; import { getAuthenticatedHttpClient, configure as configureAuth, @@ -269,8 +269,8 @@ function applyOverrideHandlers(overrides) { * @param {*} [options.analyticsService=SegmentAnalyticsService] The `AnalyticsService` * implementation to use. * @param {*} [options.authMiddleware=[]] An array of middleware to apply to http clients in the auth service. - * @param {*} [options.externalScripts=[GoogleAnalyticsLoader]] An array of externalScripts. - * By default added GoogleAnalyticsLoader. + * @param {*} [options.externalScripts=[GoogleAnalyticsLoader, GoogleTagManagerLoader]] An array of externalScripts. + * By default adds GoogleAnalyticsLoader and GoogleTagManagerLoader. * @param {*} [options.requireAuthenticatedUser=false] If true, turns on automatic login * redirection for unauthenticated users. Defaults to false, meaning that by default the * application will allow anonymous/unauthenticated sessions. @@ -290,7 +290,7 @@ export async function initialize({ analyticsService = SegmentAnalyticsService, authService = AxiosJwtAuthService, authMiddleware = [], - externalScripts = [GoogleAnalyticsLoader], + externalScripts = [GoogleAnalyticsLoader, GoogleTagManagerLoader], requireAuthenticatedUser: requireUser = false, hydrateAuthenticatedUser: hydrateUser = false, messages, diff --git a/src/scripts/GoogleTagManagerLoader.js b/src/scripts/GoogleTagManagerLoader.js new file mode 100644 index 000000000..d60ac4a89 --- /dev/null +++ b/src/scripts/GoogleTagManagerLoader.js @@ -0,0 +1,48 @@ +/** + * @implements {GoogleTagManagerLoader} + * @memberof module:GoogleTagManagerLoader + */ +class GoogleTagManagerLoader { + constructor({ config }) { + this.gtmId = config.GOOGLE_TAG_MANAGER_ID; + } + + loadScript() { + if (!this.gtmId) { + return; + } + + global.google_tag_manager = global.google_tag_manager || []; + const { google_tag_manager: googleTagManager } = global; + + // If the snippet was invoked do nothing. + if (googleTagManager.invoked) { + return; + } + + // Invoked flag, to make sure the snippet + // is never invoked twice. + googleTagManager.invoked = true; + + googleTagManager.load = (id) => { + const gtmScript = document.createElement('script'); + gtmScript.type = 'text/javascript'; + gtmScript.innerHTML = ` + (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer', '${id}'); + `; + + // Insert our scripts next to the first script element. + const first = document.getElementsByTagName('script')[0]; + first.parentNode.insertBefore(gtmScript, first); + }; + + // Load gtmAnalytics. + googleTagManager.load(this.gtmId); + } +} + +export default GoogleTagManagerLoader; diff --git a/src/scripts/GoogleTagManagerLoader.test.js b/src/scripts/GoogleTagManagerLoader.test.js new file mode 100644 index 000000000..b362e8593 --- /dev/null +++ b/src/scripts/GoogleTagManagerLoader.test.js @@ -0,0 +1,49 @@ +import GoogleTagManagerLoader from './GoogleTagManagerLoader'; + +describe('GoogleTagManagerLoader', () => { + const mockGTMId = 'GOOGLE_TAG_MANAGER_ID_test_id'; + let insertBeforeMock; + + beforeEach(() => { + global.google_tag_manager = undefined; + insertBeforeMock = jest.fn(); + + document.getElementsByTagName = jest.fn(() => [ + { + parentNode: { + insertBefore: insertBeforeMock, + }, + }, + ]); + }); + + it('should load GTM script', () => { + const loader = new GoogleTagManagerLoader({ config: { GOOGLE_TAG_MANAGER_ID: mockGTMId } }); + + loader.loadScript(); + + expect(global.google_tag_manager).toBeDefined(); + expect(global.google_tag_manager.invoked).toBe(true); + + const firstScript = document.getElementsByTagName()[0]; + expect(firstScript.parentNode.insertBefore).toHaveBeenCalled(); + }); + + it('should not load script if account is not defined', () => { + const loader = new GoogleTagManagerLoader({ config: { GOOGLE_TAG_MANAGER_ID: '' } }); + + loader.loadScript(); + + expect(global.google_tag_manager).toBeUndefined(); + }); + + it('should not load script if google_tag_manager is already invoked', () => { + global.google_tag_manager = { invoked: true }; + const loader = new GoogleTagManagerLoader({ config: { GOOGLE_TAG_MANAGER_ID: mockGTMId } }); + + loader.loadScript(); + + expect(global.google_tag_manager.invoked).toBe(true); + expect(document.getElementsByTagName).not.toHaveBeenCalled(); + }); +}); diff --git a/src/scripts/index.js b/src/scripts/index.js index 3c627f0cc..b52b32e5b 100644 --- a/src/scripts/index.js +++ b/src/scripts/index.js @@ -1,2 +1,3 @@ /* eslint-disable import/prefer-default-export */ export { default as GoogleAnalyticsLoader } from './GoogleAnalyticsLoader'; +export { default as GoogleTagManagerLoader } from './GoogleTagManagerLoader';