Skip to content
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

refactor: simplify GTM logic + unmount on permission removal #1047

Merged
merged 17 commits into from
Nov 22, 2022
Merged
3 changes: 3 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export const GOOGLE_TAG_MANAGER_AUTH_LIVE = process.env.NEXT_PUBLIC_GOOGLE_TAG_M
export const GOOGLE_TAG_MANAGER_AUTH_LATEST = process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_LATEST_AUTH || ''
export const GOOGLE_TAG_MANAGER_DEVELOPMENT_AUTH = process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_DEVELOPMENT_AUTH || ''

// Google Analytics
export const GOOGLE_ANALYTICS_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_MEASUREMENT_ID || ''
iamacook marked this conversation as resolved.
Show resolved Hide resolved

// Tenderly - API docs: https://www.notion.so/Simulate-API-Documentation-6f7009fe6d1a48c999ffeb7941efc104
export const TENDERLY_SIMULATE_ENDPOINT_URL = process.env.NEXT_PUBLIC_TENDERLY_SIMULATE_ENDPOINT_URL || ''
export const TENDERLY_PROJECT_NAME = process.env.NEXT_PUBLIC_TENDERLY_PROJECT_NAME || ''
Expand Down
2 changes: 2 additions & 0 deletions src/definitions.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type React from 'react'
import type { BeamerConfig, BeamerMethods } from '@services/beamer/types'
import { GOOGLE_ANALYTICS_MEASUREMENT_ID } from '@/config/constants'

declare global {
interface Window {
Expand All @@ -15,6 +16,7 @@ declare global {
Beamer?: BeamerMethods
dataLayer?: DataLayerArgs['dataLayer']
Cypress?
[`ga-disable-${GOOGLE_ANALYTICS_MEASUREMENT_ID}`]?: boolean
}
}

Expand Down
132 changes: 76 additions & 56 deletions src/services/analytics/TagManager.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,114 @@
// Based on https://github.com/alinemorelli/react-gtm
import { GOOGLE_ANALYTICS_MEASUREMENT_ID, IS_PRODUCTION } from '@/config/constants'
import Cookies from 'js-cookie'

type DataLayer = Record<string, unknown>

export type TagManagerArgs = {
/**
* GTM id, must be something like GTM-000000.
*/
// GTM id, e.g. GTM-000000
gtmId: string
/**
* Used to set environments.
*/
auth?: string | undefined
/**
* Used to set environments, something like env-00.
*/
preview?: string | undefined
/**
* Object that contains all of the information that you want to pass to Google Tag Manager.
*/
dataLayer?: DataLayer | undefined
// GTM authetication key
auth: string
// GTM environment, e.g. env-00.
preview: string
// Object that contains all of the information that you want to pass to GTM
dataLayer?: DataLayer
}

export const DATA_LAYER_NAME = 'dataLayer'

export const _getRequiredGtmArgs = ({ gtmId, dataLayer = undefined, auth = '', preview = '' }: TagManagerArgs) => {
return {
gtmId,
dataLayer,
auth: auth ? `&gtm_auth=${auth}` : '',
preview: preview ? `&gtm_preview=${preview}` : '',
}
}
const DATA_LAYER_NAME = 'dataLayer'

// Initialization scripts

export const _getGtmScript = (args: TagManagerArgs) => {
const { gtmId, auth, preview } = _getRequiredGtmArgs(args)

export const _getGtmScript = ({ gtmId, auth, preview }: TagManagerArgs) => {
const script = document.createElement('script')

const gtmScript = `
(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),
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl + '${auth}${preview}&gtm_cookies_win=x';
f.parentNode.insertBefore(j, f);
})(window, document, 'script', '${DATA_LAYER_NAME}', '${gtmId}');`
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl + '&gtm_auth=${auth}&gtm_preview=${preview}&gtm_cookies_win=x';
f.parentNode.insertBefore(j, f);
})(window, document, 'script', '${DATA_LAYER_NAME}', '${gtmId}');`

script.innerHTML = gtmScript

return script
}

// Data layer scripts

export const _getGtmDataLayerScript = (dataLayer: DataLayer) => {
const script = document.createElement('script')

const gtmDataLayerScript = `
window.${DATA_LAYER_NAME} = window.${DATA_LAYER_NAME} || [];
window.${DATA_LAYER_NAME}.push(${JSON.stringify(dataLayer)})`
// https://developers.google.com/tag-platform/devguides/privacy#turn_off_google_analytics
const GA_DISABLE_KEY = `ga-disable-${GOOGLE_ANALYTICS_MEASUREMENT_ID}`
const GTM_DISABLE_TRIGGER_COOKIE_NAME = 'google-analytics-opt-out'

script.innerHTML = gtmDataLayerScript

return script
}
// Injected GTM script singleton
let gtmScriptRef: HTMLScriptElement | null = null

const TagManager = {
isEnabled: () => {
// @ts-expect-error - Element implicitly has an 'any' type because index expression is not of type 'number'.
return Cookies.get(GTM_DISABLE_TRIGGER_COOKIE_NAME) === undefined && window[GA_DISABLE_KEY] === false
},
initialize: (args: TagManagerArgs) => {
const { dataLayer } = _getRequiredGtmArgs(args)
if (TagManager.isEnabled()) {
return
}

// Enable GA
// @ts-expect-error - Element implicitly has an 'any' type because index expression is not of type 'number'.
window[GA_DISABLE_KEY] = false

if (dataLayer) {
const gtmDataLayerScript = _getGtmDataLayerScript(dataLayer)
document.head.appendChild(gtmDataLayerScript)
// Enable GTM triggers
Cookies.remove(GTM_DISABLE_TRIGGER_COOKIE_NAME, { path: '/' })

if (gtmScriptRef) {
return
}

const gtmScript = _getGtmScript(args)
document.head.insertBefore(gtmScript, document.head.childNodes[0])
// Initialize dataLayer (with configuration)
window[DATA_LAYER_NAME] = args.dataLayer ? [args.dataLayer] : []

gtmScriptRef = _getGtmScript(args)

// Initialize GTM. This pushes the default dataLayer event:
// { "gtm.start": new Date().getTime(), event: "gtm.js" }
document.head.insertBefore(gtmScriptRef, document.head.childNodes[0])
},
dataLayer: (dataLayer: DataLayer) => {
if (window[DATA_LAYER_NAME]) {
return window[DATA_LAYER_NAME].push(dataLayer)
if (!TagManager.isEnabled()) {
return
}

window[DATA_LAYER_NAME].push(dataLayer)

if (!IS_PRODUCTION) {
console.info('[GTM] -', dataLayer)
}
},
disable: () => {
if (!TagManager.isEnabled()) {
return
}

// Disable GA
// @ts-expect-error - Element implicitly has an 'any' type because index expression is not of type 'number'.
window[GA_DISABLE_KEY] = true

// Disable GTM triggers
Cookies.set(GTM_DISABLE_TRIGGER_COOKIE_NAME, 'true', {
expires: Number.MAX_SAFE_INTEGER,
path: '/',
})

// const GTM_COOKIE_LIST = ['_ga', '_gat', '_gid']

const gtmDataLayerScript = _getGtmDataLayerScript(dataLayer)
document.head.insertBefore(gtmDataLayerScript, document.head.childNodes[0])
// GTM_COOKIE_LIST.forEach((cookie) => {
// Cookies.remove(cookie, {
// path: '/',
// domain: `.${location.host.split('.').slice(-2).join('.')}`,
// })
// })
},
}

Expand Down
Loading