Update gtm.js

Unused variable, import, function or class
This commit is contained in:
2026-01-17 17:32:28 -06:00
parent 46ce1328ff
commit 1b252df6d6

View File

@@ -19,322 +19,320 @@
BRIEF: Safe, configurable Google Tag Manager loader for Moko-Cassiopeia. BRIEF: Safe, configurable Google Tag Manager loader for Moko-Cassiopeia.
PATH: ./media/templates/site/moko-cassiopeia/js/gtm.js PATH: ./media/templates/site/moko-cassiopeia/js/gtm.js
NOTE: Place the <noscript> fallback iframe in your HTML template (index.php). A JS file NOTE: Place the <noscript> fallback iframe in your HTML template (index.php). A JS file
cannot provide a true no-JS fallback by definition. cannot provide a true no-JS fallback by definition.
VARIABLES: VARIABLES:
- window.MOKO_GTM_ID (string) // Optional global GTM container ID (e.g., "GTM-XXXXXXX") - window.MOKO_GTM_ID (string) // Optional global GTM container ID (e.g., "GTM-XXXXXXX")
- window.MOKO_GTM_OPTIONS (object) // Optional global options (see JSDoc below) - window.MOKO_GTM_OPTIONS (object) // Optional global options (see JSDoc below)
- data- attributes on the script tag or <html>/<body>: - data- attributes on the script tag or <html>/<body>:
data-gtm-id, data-data-layer, data-debug, data-ignore-dnt, data-gtm-id, data-data-layer, data-debug, data-ignore-dnt,
data-env-auth, data-env-preview, data-block-on-dev data-env-auth, data-env-preview, data-block-on-dev
*/ */
/* global window, document, navigator */ /* global window, document, navigator */
(() => { (() => {
"use strict"; "use strict";
/** /**
* @typedef {Object} MokoGtmOptions * @typedef {Object} MokoGtmOptions
* @property {string} [id] GTM container ID (e.g., "GTM-XXXXXXX") * @property {string} [id] GTM container ID (e.g., "GTM-XXXXXXX")
* @property {string} [dataLayerName] Custom dataLayer name (default: "dataLayer") * @property {string} [dataLayerName] Custom dataLayer name (default: "dataLayer")
* @property {boolean} [debug] Log debug messages to console (default: false) * @property {boolean} [debug] Log debug messages to console (default: false)
* @property {boolean} [ignoreDNT] Ignore Do Not Track and always load (default: false) * @property {boolean} [ignoreDNT] Ignore Do Not Track and always load (default: false)
* @property {boolean} [blockOnDev] Block loading on localhost/*.test/127.0.0.1 (default: true) * @property {boolean} [blockOnDev] Block loading on localhost/*.test/127.0.0.1 (default: true)
* @property {string} [envAuth] GTM Environment auth string (optional) * @property {string} [envAuth] GTM Environment auth string (optional)
* @property {string} [envPreview] GTM Environment preview name (optional) * @property {string} [envPreview] GTM Environment preview name (optional)
* @property {Record<string,'granted'|'denied'>} [consentDefault] * @property {Record<string,'granted'|'denied'>} [consentDefault]
* Default Consent Mode v2 map. Keys like: * Default Consent Mode v2 map. Keys like:
* analytics_storage, ad_storage, ad_user_data, ad_personalization, functionality_storage, security_storage * analytics_storage, ad_storage, ad_user_data, ad_personalization, functionality_storage, security_storage
* (default: {analytics_storage:'granted', functionality_storage:'granted', security_storage:'granted'}) * (default: {analytics_storage:'granted', functionality_storage:'granted', security_storage:'granted'})
* @property {() => (Record<string, any>|void)} [pageVars] * @property {() => (Record<string, any>|void)} [pageVars]
* Function returning extra page variables to push on init (optional) * Function returning extra page variables to push on init (optional)
*/ */
const PKG = "moko-gtm"; const PKG = "moko-gtm";
const PREFIX = `[${PKG}]`; const PREFIX = `[${PKG}]`;
const WIN = window; const WIN = window;
// Public API placeholder (attached to window at the end) // Public API placeholder (attached to window at the end)
/** @type {{ /** @type {{
* init: (opts?: Partial<MokoGtmOptions>) => void, * init: (opts?: Partial<MokoGtmOptions>) => void,
* setConsent: (updates: Record<string,'granted'|'denied'>) => void, * setConsent: (updates: Record<string,'granted'|'denied'>) => void,
* push: (...args:any[]) => void, * push: (...args:any[]) => void,
* isLoaded: () => boolean, * isLoaded: () => boolean,
* config: () => Required<MokoGtmOptions> * config: () => Required<MokoGtmOptions>
* }} */ * }} */
const API = {}; const API = {};
// ---- Utilities --------------------------------------------------------- // ---- Utilities ---------------------------------------------------------
const isDevHost = () => { const isDevHost = () => {
const h = WIN.location && WIN.location.hostname || ""; const h = WIN.location && WIN.location.hostname || "";
return ( return (
h === "localhost" || h === "localhost" ||
h === "127.0.0.1" || h === "127.0.0.1" ||
h.endsWith(".local") || h.endsWith(".local") ||
h.endsWith(".test") h.endsWith(".test")
); );
}; };
const dntEnabled = () => { const dntEnabled = () => {
// Different browsers expose DNT differently; treat "1" or "yes" as enabled. // Different browsers expose DNT differently; treat "1" or "yes" as enabled.
const n = navigator; const n = navigator;
const v = (n.doNotTrack || n.msDoNotTrack || (n.navigator && n.navigator.doNotTrack) || "").toString().toLowerCase(); const v = (n.doNotTrack || n.msDoNotTrack || (n.navigator && n.navigator.doNotTrack) || "").toString().toLowerCase();
return v === "1" || v === "yes"; return v === "1" || v === "yes";
}; };
const getCurrentScript = () => { const getCurrentScript = () => {
// document.currentScript is best; fallback to last <script> whose src ends with /gtm.js // document.currentScript is best; fallback to last <script> whose src ends with /gtm.js
const cs = document.currentScript; const cs = document.currentScript;
if (cs) return cs; if (cs) return cs;
const scripts = Array.from(document.getElementsByTagName("script")); const scripts = Array.from(document.getElementsByTagName("script"));
return scripts.reverse().find(s => (s.getAttribute("src") || "").includes("/gtm.js")) || null; return scripts.reverse().find(s => (s.getAttribute("src") || "").includes("/gtm.js")) || null;
}; };
const getAttr = (el, name) => el ? el.getAttribute(name) : null; const readDatasetCascade = (name) => {
// Check <script>, <html>, <body>, then <meta name="moko:gtm-<name>">
const script = getCurrentScript();
const html = document.documentElement;
const body = document.body;
const meta = document.querySelector(`meta[name="moko:gtm-${name}"]`);
return (
(script && script.dataset && script.dataset[name]) ||
(html && html.dataset && html.dataset[name]) ||
(body && body.dataset && body.dataset[name]) ||
(meta && meta.getAttribute("content")) ||
null
);
};
const readDatasetCascade = (name) => { const parseBool = (v, fallback = false) => {
// Check <script>, <html>, <body>, then <meta name="moko:gtm-<name>"> if (v == null) return fallback;
const script = getCurrentScript(); const s = String(v).trim().toLowerCase();
const html = document.documentElement; if (["1","true","yes","y","on"].includes(s)) return true;
const body = document.body; if (["0","false","no","n","off"].includes(s)) return false;
const meta = document.querySelector(`meta[name="moko:gtm-${name}"]`); return fallback;
return ( };
(script && script.dataset && script.dataset[name]) ||
(html && html.dataset && html.dataset[name]) ||
(body && body.dataset && body.dataset[name]) ||
(meta && meta.getAttribute("content")) ||
null
);
};
const parseBool = (v, fallback = false) => { const debugLog = (...args) => {
if (v == null) return fallback; if (STATE.debug) {
const s = String(v).trim().toLowerCase(); try { console.info(PREFIX, ...args); } catch (_) {}
if (["1","true","yes","y","on"].includes(s)) return true; }
if (["0","false","no","n","off"].includes(s)) return false; };
return fallback;
};
const debugLog = (...args) => { // ---- Configuration & State --------------------------------------------
if (STATE.debug) {
try { console.info(PREFIX, ...args); } catch (_) {}
}
};
// ---- Configuration & State -------------------------------------------- /** @type {Required<MokoGtmOptions>} */
const STATE = {
id: "",
dataLayerName: "dataLayer",
debug: false,
ignoreDNT: false,
blockOnDev: true,
envAuth: "",
envPreview: "",
consentDefault: {
analytics_storage: "granted",
functionality_storage: "granted",
security_storage: "granted",
// The following default to "denied" unless the site explicitly opts-in:
ad_storage: "denied",
ad_user_data: "denied",
ad_personalization: "denied",
},
pageVars: () => ({})
};
/** @type {Required<MokoGtmOptions>} */ const mergeOptions = (base, extra = {}) => {
const STATE = { const out = {...base};
id: "", for (const k in extra) {
dataLayerName: "dataLayer", if (!Object.prototype.hasOwnProperty.call(extra, k)) continue;
debug: false, const v = extra[k];
ignoreDNT: false, if (v && typeof v === "object" && !Array.isArray(v)) {
blockOnDev: true, out[k] = {...(out[k] || {}), ...v};
envAuth: "", } else if (v !== undefined) {
envPreview: "", out[k] = v;
consentDefault: { }
analytics_storage: "granted", }
functionality_storage: "granted", return out;
security_storage: "granted", };
// The following default to "denied" unless the site explicitly opts-in:
ad_storage: "denied",
ad_user_data: "denied",
ad_personalization: "denied",
},
pageVars: () => ({})
};
const mergeOptions = (base, extra = {}) => { const detectOptions = () => {
const out = {...base}; // 1) Global window options
for (const k in extra) { /** @type {Partial<MokoGtmOptions>} */
if (!Object.prototype.hasOwnProperty.call(extra, k)) continue; const globalOpts = (WIN.MOKO_GTM_OPTIONS && typeof WIN.MOKO_GTM_OPTIONS === "object") ? WIN.MOKO_GTM_OPTIONS : {};
const v = extra[k];
if (v && typeof v === "object" && !Array.isArray(v)) {
out[k] = {...(out[k] || {}), ...v};
} else if (v !== undefined) {
out[k] = v;
}
}
return out;
};
const detectOptions = () => { // 2) Dataset / meta
// 1) Global window options const idFromData = readDatasetCascade("id") || WIN.MOKO_GTM_ID || "";
/** @type {Partial<MokoGtmOptions>} */ const dlFromData = readDatasetCascade("dataLayer") || "";
const globalOpts = (WIN.MOKO_GTM_OPTIONS && typeof WIN.MOKO_GTM_OPTIONS === "object") ? WIN.MOKO_GTM_OPTIONS : {}; const dbgFromData = readDatasetCascade("debug");
const dntFromData = readDatasetCascade("ignoreDnt");
const devFromData = readDatasetCascade("blockOnDev");
const authFromData = readDatasetCascade("envAuth") || "";
const prevFromData = readDatasetCascade("envPreview") || "";
// 2) Dataset / meta // 3) Combine
const idFromData = readDatasetCascade("id") || WIN.MOKO_GTM_ID || ""; /** @type {Partial<MokoGtmOptions>} */
const dlFromData = readDatasetCascade("dataLayer") || ""; const detected = {
const dbgFromData = readDatasetCascade("debug"); id: idFromData || globalOpts.id || "",
const dntFromData = readDatasetCascade("ignoreDnt"); dataLayerName: dlFromData || globalOpts.dataLayerName || undefined,
const devFromData = readDatasetCascade("blockOnDev"); debug: parseBool(dbgFromData, !!globalOpts.debug),
const authFromData = readDatasetCascade("envAuth") || ""; ignoreDNT: parseBool(dntFromData, !!globalOpts.ignoreDNT),
const prevFromData = readDatasetCascade("envPreview") || ""; blockOnDev: parseBool(devFromData, (globalOpts.blockOnDev ?? true)),
envAuth: authFromData || globalOpts.envAuth || "",
envPreview: prevFromData || globalOpts.envPreview || "",
consentDefault: globalOpts.consentDefault || undefined,
pageVars: typeof globalOpts.pageVars === "function" ? globalOpts.pageVars : undefined
};
// 3) Combine return detected;
/** @type {Partial<MokoGtmOptions>} */ };
const detected = {
id: idFromData || globalOpts.id || "",
dataLayerName: dlFromData || globalOpts.dataLayerName || undefined,
debug: parseBool(dbgFromData, !!globalOpts.debug),
ignoreDNT: parseBool(dntFromData, !!globalOpts.ignoreDNT),
blockOnDev: parseBool(devFromData, (globalOpts.blockOnDev ?? true)),
envAuth: authFromData || globalOpts.envAuth || "",
envPreview: prevFromData || globalOpts.envPreview || "",
consentDefault: globalOpts.consentDefault || undefined,
pageVars: typeof globalOpts.pageVars === "function" ? globalOpts.pageVars : undefined
};
return detected; // ---- dataLayer / gtag helpers -----------------------------------------
};
// ---- dataLayer / gtag helpers ----------------------------------------- const ensureDataLayer = () => {
const l = STATE.dataLayerName;
WIN[l] = WIN[l] || [];
return WIN[l];
};
const ensureDataLayer = () => { /** gtag wrapper backed by dataLayer. */
const l = STATE.dataLayerName; const gtag = (...args) => {
WIN[l] = WIN[l] || []; const dl = ensureDataLayer();
return WIN[l]; dl.push(arguments.length > 1 ? args : args[0]);
}; debugLog("gtag push:", args);
};
/** gtag wrapper backed by dataLayer. */ API.push = (...args) => gtag(...args);
const gtag = (...args) => {
const dl = ensureDataLayer();
dl.push(arguments.length > 1 ? args : args[0]);
debugLog("gtag push:", args);
};
API.push = (...args) => gtag(...args); API.setConsent = (updates) => {
gtag("consent", "update", updates || {});
};
API.setConsent = (updates) => { API.isLoaded = () => {
gtag("consent", "update", updates || {}); const hasScript = !!document.querySelector('script[src*="googletagmanager.com/gtm.js"]');
}; return hasScript;
};
API.isLoaded = () => { API.config = () => ({...STATE});
const hasScript = !!document.querySelector('script[src*="googletagmanager.com/gtm.js"]');
return hasScript;
};
API.config = () => ({...STATE}); // ---- Loader ------------------------------------------------------------
// ---- Loader ------------------------------------------------------------ const buildEnvQuery = () => {
const qp = [];
if (STATE.envAuth) qp.push(`gtm_auth=${encodeURIComponent(STATE.envAuth)}`);
if (STATE.envPreview) qp.push(`gtm_preview=${encodeURIComponent(STATE.envPreview)}`, "gtm_cookies_win=x");
return qp.length ? `&${qp.join("&")}` : "";
};
const buildEnvQuery = () => { const injectScript = () => {
const qp = []; if (!STATE.id) {
if (STATE.envAuth) qp.push(`gtm_auth=${encodeURIComponent(STATE.envAuth)}`); debugLog("GTM ID missing; aborting load.");
if (STATE.envPreview) qp.push(`gtm_preview=${encodeURIComponent(STATE.envPreview)}`, "gtm_cookies_win=x"); return;
return qp.length ? `&${qp.join("&")}` : ""; }
}; if (API.isLoaded()) {
debugLog("GTM already loaded; skipping duplicate injection.");
return;
}
const injectScript = () => { // Standard GTM bootstrap timing event
if (!STATE.id) { const dl = ensureDataLayer();
debugLog("GTM ID missing; aborting load."); dl.push({ "gtm.start": new Date().getTime(), event: "gtm.js" });
return;
}
if (API.isLoaded()) {
debugLog("GTM already loaded; skipping duplicate injection.");
return;
}
// Standard GTM bootstrap timing event const f = document.getElementsByTagName("script")[0];
const dl = ensureDataLayer(); const j = document.createElement("script");
dl.push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); j.async = true;
j.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(STATE.id)}${STATE.dataLayerName !== "dataLayer" ? `&l=${encodeURIComponent(STATE.dataLayerName)}` : ""}${buildEnvQuery()}`;
if (f && f.parentNode) {
f.parentNode.insertBefore(j, f);
} else {
(document.head || document.documentElement).appendChild(j);
}
debugLog("Injected GTM script:", j.src);
};
const f = document.getElementsByTagName("script")[0]; const applyDefaultConsent = () => {
const j = document.createElement("script"); // Consent Mode v2 default
j.async = true; gtag("consent", "default", STATE.consentDefault);
j.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(STATE.id)}${STATE.dataLayerName !== "dataLayer" ? `&l=${encodeURIComponent(STATE.dataLayerName)}` : ""}${buildEnvQuery()}`; debugLog("Applied default consent:", STATE.consentDefault);
if (f && f.parentNode) { };
f.parentNode.insertBefore(j, f);
} else {
(document.head || document.documentElement).appendChild(j);
}
debugLog("Injected GTM script:", j.src);
};
const applyDefaultConsent = () => { const pushInitialVars = () => {
// Consent Mode v2 default // Minimal page vars; allow site to add more via pageVars()
gtag("consent", "default", STATE.consentDefault); const vars = {
debugLog("Applied default consent:", STATE.consentDefault); event: "moko.page_init",
}; page_title: document.title || "",
page_language: (document.documentElement && document.documentElement.lang) || "",
...(typeof STATE.pageVars === "function" ? (STATE.pageVars() || {}) : {})
};
gtag(vars);
};
const pushInitialVars = () => { const shouldLoad = () => {
// Minimal page vars; allow site to add more via pageVars() if (!STATE.ignoreDNT && dntEnabled()) {
const vars = { debugLog("DNT is enabled; blocking GTM load (set ignoreDNT=true to override).");
event: "moko.page_init", return false;
page_title: document.title || "", }
page_language: (document.documentElement && document.documentElement.lang) || "", if (STATE.blockOnDev && isDevHost()) {
...(typeof STATE.pageVars === "function" ? (STATE.pageVars() || {}) : {}) debugLog("Development host detected; blocking GTM load (set blockOnDev=false to override).");
}; return false;
gtag(vars); }
}; return true;
};
const shouldLoad = () => { // ---- Public init -------------------------------------------------------
if (!STATE.ignoreDNT && dntEnabled()) {
debugLog("DNT is enabled; blocking GTM load (set ignoreDNT=true to override).");
return false;
}
if (STATE.blockOnDev && isDevHost()) {
debugLog("Development host detected; blocking GTM load (set blockOnDev=false to override).");
return false;
}
return true;
};
// ---- Public init ------------------------------------------------------- API.init = (opts = {}) => {
// Merge: defaults <- detected <- passed opts
const detected = detectOptions();
const merged = mergeOptions(STATE, mergeOptions(detected, opts));
API.init = (opts = {}) => { // Commit back to STATE
// Merge: defaults <- detected <- passed opts Object.assign(STATE, merged);
const detected = detectOptions();
const merged = mergeOptions(STATE, mergeOptions(detected, opts));
// Commit back to STATE debugLog("Config:", STATE);
Object.assign(STATE, merged);
debugLog("Config:", STATE); // Prepare dataLayer/gtag and consent
ensureDataLayer();
applyDefaultConsent();
pushInitialVars();
// Prepare dataLayer/gtag and consent // Load GTM if allowed
ensureDataLayer(); if (shouldLoad()) {
applyDefaultConsent(); injectScript();
pushInitialVars(); } else {
debugLog("GTM load prevented by configuration or environment.");
}
};
// Load GTM if allowed // ---- Auto-init on DOMContentLoaded (safe even if deferred) -------------
if (shouldLoad()) {
injectScript();
} else {
debugLog("GTM load prevented by configuration or environment.");
}
};
// ---- Auto-init on DOMContentLoaded (safe even if deferred) ------------- const autoInit = () => {
// Only auto-init if we have some ID from globals/datasets.
const detected = detectOptions();
const hasId = !!(detected.id || WIN.MOKO_GTM_ID);
if (hasId) {
API.init(); // use detected/global defaults
} else {
debugLog("No GTM ID detected; awaiting manual init via window.mokoGTM.init({ id: 'GTM-XXXXXXX' }).");
}
};
const autoInit = () => { if (document.readyState === "complete" || document.readyState === "interactive") {
// Only auto-init if we have some ID from globals/datasets. // Defer to ensure <body> exists for any late consumers.
const detected = detectOptions(); setTimeout(autoInit, 0);
const hasId = !!(detected.id || WIN.MOKO_GTM_ID); } else {
if (hasId) { document.addEventListener("DOMContentLoaded", autoInit, { once: true });
API.init(); // use detected/global defaults }
} else {
debugLog("No GTM ID detected; awaiting manual init via window.mokoGTM.init({ id: 'GTM-XXXXXXX' }).");
}
};
if (document.readyState === "complete" || document.readyState === "interactive") { // Expose API
// Defer to ensure <body> exists for any late consumers. WIN.mokoGTM = API;
setTimeout(autoInit, 0);
} else {
document.addEventListener("DOMContentLoaded", autoInit, { once: true });
}
// Expose API // Helpful console hint (only if debug true after detection)
WIN.mokoGTM = API; try {
const detected = detectOptions();
// Helpful console hint (only if debug true after detection) if (parseBool(detected.debug, false)) {
try { STATE.debug = true;
const detected = detectOptions(); debugLog("Ready. You can call window.mokoGTM.init({ id: 'GTM-XXXXXXX' }).");
if (parseBool(detected.debug, false)) { }
STATE.debug = true; } catch (_) {}
debugLog("Ready. You can call window.mokoGTM.init({ id: 'GTM-XXXXXXX' }).");
}
} catch (_) {}
})(); })();