@jwiedeman/gtm-kit-astro 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,6 +6,9 @@ var gtmKit = require('@jwiedeman/gtm-kit');
|
|
|
6
6
|
var isValidJsIdentifier = (value) => {
|
|
7
7
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);
|
|
8
8
|
};
|
|
9
|
+
var escapeJsString = (value) => {
|
|
10
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/</g, "\\x3c").replace(/>/g, "\\x3e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
11
|
+
};
|
|
9
12
|
var filterNullish = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null));
|
|
10
13
|
var generateScriptTags = (config) => {
|
|
11
14
|
const {
|
|
@@ -19,6 +22,11 @@ var generateScriptTags = (config) => {
|
|
|
19
22
|
if (!normalized.length) {
|
|
20
23
|
throw new Error("At least one GTM container is required.");
|
|
21
24
|
}
|
|
25
|
+
if (!isValidJsIdentifier(dataLayerName)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Invalid dataLayerName: "${dataLayerName}". Must be a valid JavaScript identifier.`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
22
30
|
return normalized.map((container) => {
|
|
23
31
|
if (!container.id) {
|
|
24
32
|
throw new Error("Container id is required.");
|
|
@@ -29,16 +37,47 @@ var generateScriptTags = (config) => {
|
|
|
29
37
|
};
|
|
30
38
|
const src = gtmKit.buildGtmScriptUrl(host, container.id, params, dataLayerName);
|
|
31
39
|
const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
|
|
40
|
+
escapeJsString(container.id);
|
|
41
|
+
const initScript = `window.${dataLayerName}=window.${dataLayerName}||[];window.${dataLayerName}.push({'gtm.start':new Date().getTime(),event:'gtm.js'});`;
|
|
32
42
|
return {
|
|
33
43
|
id: container.id,
|
|
34
44
|
src,
|
|
35
45
|
async: asyncAttr != null ? asyncAttr : true,
|
|
36
46
|
defer,
|
|
37
47
|
nonce,
|
|
38
|
-
attributes: filterNullish(restAttributes)
|
|
48
|
+
attributes: filterNullish(restAttributes),
|
|
49
|
+
initScript
|
|
39
50
|
};
|
|
40
51
|
});
|
|
41
52
|
};
|
|
53
|
+
var generateGtmScript = (config) => {
|
|
54
|
+
const {
|
|
55
|
+
containers,
|
|
56
|
+
host = gtmKit.DEFAULT_GTM_HOST,
|
|
57
|
+
defaultQueryParams,
|
|
58
|
+
dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
|
|
59
|
+
} = config;
|
|
60
|
+
const normalized = gtmKit.normalizeContainers(containers);
|
|
61
|
+
if (!normalized.length) {
|
|
62
|
+
throw new Error("At least one GTM container is required.");
|
|
63
|
+
}
|
|
64
|
+
if (!isValidJsIdentifier(dataLayerName)) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Invalid dataLayerName: "${dataLayerName}". Must be a valid JavaScript identifier.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return normalized.map((container) => {
|
|
70
|
+
if (!container.id) {
|
|
71
|
+
throw new Error("Container id is required.");
|
|
72
|
+
}
|
|
73
|
+
const escapedId = escapeJsString(container.id);
|
|
74
|
+
dataLayerName !== gtmKit.DEFAULT_DATA_LAYER_NAME ? `,'${escapeJsString(dataLayerName)}'` : "";
|
|
75
|
+
const mergedParams = { ...defaultQueryParams, ...container.queryParams };
|
|
76
|
+
const extraParams = Object.entries(mergedParams).filter(([, v]) => v != null).map(([k, v]) => `&${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("");
|
|
77
|
+
const hostUrl = (host != null ? host : gtmKit.DEFAULT_GTM_HOST).replace(/\/+$/, "");
|
|
78
|
+
return `(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='${escapeJsString(hostUrl)}/gtm.js?id='+i+dl${extraParams ? `+'${escapeJsString(extraParams)}'` : ""};f.parentNode.insertBefore(j,f);})(window,document,'script','${escapeJsString(dataLayerName)}','${escapedId}');`;
|
|
79
|
+
}).join("\n");
|
|
80
|
+
};
|
|
42
81
|
var generateNoscriptTags = (config) => {
|
|
43
82
|
const {
|
|
44
83
|
containers,
|
|
@@ -104,6 +143,7 @@ Object.defineProperty(exports, 'normalizeContainers', {
|
|
|
104
143
|
get: function () { return gtmKit.normalizeContainers; }
|
|
105
144
|
});
|
|
106
145
|
exports.generateDataLayerScript = generateDataLayerScript;
|
|
146
|
+
exports.generateGtmScript = generateGtmScript;
|
|
107
147
|
exports.generateNoscriptTags = generateNoscriptTags;
|
|
108
148
|
exports.generateScriptTags = generateScriptTags;
|
|
109
149
|
//# sourceMappingURL=out.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["normalizeContainers","normalizeContainer","buildGtmScriptUrl","buildGtmNoscriptUrl"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAiBA,IAAM,sBAAsB,CAAC,UAA2B;AAE7D,SAAO,6BAA6B,KAAK,KAAK;AAChD;AAoBA,IAAM,gBAAgB,CAAoC,QACxD,OAAO,YAAY,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC;AAuB9D,IAAM,qBAAqB,CAAC,WAA6C;AAC9E,QAAM;AAAA,IACJ;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,aAAa,oBAAoB,UAAU;AAEjD,MAAI,CAAC,WAAW,QAAQ;AACtB,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,SAAO,WAAW,IAAI,CAAC,cAAc;AACnC,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG,UAAU;AAAA,IACf;AAEA,UAAM,MAAM,kBAAkB,MAAM,UAAU,IAAI,QAAQ,aAAa;AACvE,UAAM,EAAE,OAAO,WAAW,OAAO,OAAO,GAAG,eAAe,IAAI,8CAAoB,CAAC;AAEnF,WAAO;AAAA,MACL,IAAI,UAAU;AAAA,MACd;AAAA,MACA,OAAO,gCAAa;AAAA,MACpB;AAAA,MACA;AAAA,MACA,YAAY,cAAc,cAAc;AAAA,IAC1C;AAAA,EACF,CAAC;AACH;AAYO,IAAM,uBAAuB,CAClC,WAGsB;AACtB,QAAM;AAAA,IACJ;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,aAAa,oBAAoB,UAAU;AAEjD,MAAI,CAAC,WAAW,QAAQ;AACtB,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,SAAO,WAAW,IAAI,CAAC,cAAc;AACnC,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG,UAAU;AAAA,IACf;AAEA,UAAM,MAAM,oBAAoB,MAAM,UAAU,IAAI,QAAQ,aAAa;AACzE,UAAM,mBAAmB;AAAA,MACvB,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAGA,UAAM,aAAa,OAAO;AAAA,MACxB,OAAO,QAAQ,gBAAgB,EAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAC3B,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AAAA,IACnC;AAEA,WAAO;AAAA,MACL,IAAI,UAAU;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAMO,IAAM,0BAA0B,CAAC,gBAAwB,4BAAoC;AAClG,MAAI,CAAC,oBAAoB,aAAa,GAAG;AACvC,UAAM,IAAI;AAAA,MACR,2BAA2B,aAAa;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,UAAU,aAAa,WAAW,aAAa;AACxD;;;ACnLA;AAAA,EACE,uBAAAA;AAAA,EACA,sBAAAC;AAAA,EACqB,qBAArBC;AAAA,EACuB,uBAAvBC;AAAA,OACK","sourcesContent":["import {\n DEFAULT_DATA_LAYER_NAME,\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl,\n buildGtmNoscriptUrl\n} from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\n\n// Re-export for convenience\nexport {\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n};\n\n/**\n * Validate that a string is a valid JavaScript identifier.\n * This prevents XSS attacks through dataLayerName injection.\n */\nexport const isValidJsIdentifier = (value: string): boolean => {\n // Must be a valid JS identifier: starts with letter/$/_, followed by letters/digits/$/_\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);\n};\n\n/**\n * Escape a string for safe use in JavaScript string literals.\n * Prevents XSS when interpolating user-provided values into inline scripts.\n */\nexport const escapeJsString = (value: string): string => {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/</g, '\\\\x3c')\n .replace(/>/g, '\\\\x3e')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n};\n\n/** Filter out null/undefined values from an object */\nconst filterNullish = <T extends Record<string, unknown>>(obj: T): T =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) as T;\n\nexport interface GtmScriptConfig {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nexport interface ScriptTagData {\n id: string;\n src: string;\n async: boolean;\n defer?: boolean;\n nonce?: string;\n attributes: Record<string, string | boolean>;\n}\n\n/**\n * Generate script tag data for GTM containers.\n * Used by Astro components to render script tags.\n */\nexport const generateScriptTags = (config: GtmScriptConfig): ScriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n return {\n id: container.id,\n src,\n async: asyncAttr ?? true,\n defer,\n nonce,\n attributes: filterNullish(restAttributes) as Record<string, string | boolean>\n };\n });\n};\n\nexport interface NoscriptTagData {\n id: string;\n src: string;\n attributes: Record<string, string>;\n}\n\n/**\n * Generate noscript iframe data for GTM containers.\n * Used by Astro components to render noscript fallbacks.\n */\nexport const generateNoscriptTags = (\n config: Omit<GtmScriptConfig, 'scriptAttributes'> & {\n iframeAttributes?: Record<string, string | number | boolean>;\n }\n): NoscriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);\n const mergedAttributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n // Filter nullish values and convert to strings\n const attributes = Object.fromEntries(\n Object.entries(mergedAttributes)\n .filter(([, v]) => v != null)\n .map(([k, v]) => [k, String(v)])\n ) as Record<string, string>;\n\n return {\n id: container.id,\n src,\n attributes\n };\n });\n};\n\n/**\n * Generate the dataLayer initialization script.\n * @throws {Error} If dataLayerName is not a valid JavaScript identifier\n */\nexport const generateDataLayerScript = (dataLayerName: string = DEFAULT_DATA_LAYER_NAME): string => {\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`\n );\n }\n return `window.${dataLayerName}=window.${dataLayerName}||[];`;\n};\n","export { generateScriptTags, generateNoscriptTags, generateDataLayerScript, DEFAULT_GTM_HOST } from './helpers';\n\n// Re-export URL utilities from core for backwards compatibility\nexport {\n normalizeContainers,\n normalizeContainer,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n\nexport type { GtmScriptConfig, ScriptTagData, NoscriptTagData } from './helpers';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["normalizeContainers","normalizeContainer","buildGtmScriptUrl","buildGtmNoscriptUrl"],"mappings":"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","sourcesContent":["import {\n DEFAULT_DATA_LAYER_NAME,\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl,\n buildGtmNoscriptUrl\n} from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\n\n// Re-export for convenience\nexport {\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n};\n\n/**\n * Validate that a string is a valid JavaScript identifier.\n * This prevents XSS attacks through dataLayerName injection.\n */\nexport const isValidJsIdentifier = (value: string): boolean => {\n // Must be a valid JS identifier: starts with letter/$/_, followed by letters/digits/$/_\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);\n};\n\n/**\n * Escape a string for safe use in JavaScript string literals.\n * Prevents XSS when interpolating user-provided values into inline scripts.\n */\nexport const escapeJsString = (value: string): string => {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/</g, '\\\\x3c')\n .replace(/>/g, '\\\\x3e')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n};\n\n/** Filter out null/undefined values from an object */\nconst filterNullish = <T extends Record<string, unknown>>(obj: T): T =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) as T;\n\nexport interface GtmScriptConfig {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nexport interface ScriptTagData {\n id: string;\n src: string;\n async: boolean;\n defer?: boolean;\n nonce?: string;\n attributes: Record<string, string | boolean>;\n /**\n * Inline JavaScript that must be executed before the GTM script loads.\n * Initializes the dataLayer and pushes the gtm.start event, which is\n * required for GTM's built-in \"All Pages\" trigger to fire.\n *\n * Render this as a `<script>` tag (or `<script is:inline>` in Astro)\n * immediately before the GTM `<script async src=\"...\">` tag.\n */\n initScript: string;\n}\n\n/**\n * Generate script tag data for GTM containers.\n * Used by Astro components to render script tags.\n *\n * Each tag includes an `initScript` property containing the dataLayer\n * initialization and gtm.start event push. This MUST be rendered as an\n * inline script before the GTM script tag, or GTM's \"All Pages\" trigger\n * will not fire.\n */\nexport const generateScriptTags = (config: GtmScriptConfig): ScriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier.`\n );\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n // Generate the standard GTM initialization script.\n // This pushes the gtm.start event to the dataLayer, which is required\n // for GTM to trigger its built-in \"All Pages\" / \"Container Loaded\" trigger.\n const escapedId = escapeJsString(container.id);\n const dlParam = dataLayerName !== DEFAULT_DATA_LAYER_NAME ? `'&l='+l` : `''`;\n const initScript =\n `window.${dataLayerName}=window.${dataLayerName}||[];` +\n `window.${dataLayerName}.push({'gtm.start':new Date().getTime(),event:'gtm.js'});`;\n\n return {\n id: container.id,\n src,\n async: asyncAttr ?? true,\n defer,\n nonce,\n attributes: filterNullish(restAttributes) as Record<string, string | boolean>,\n initScript\n };\n });\n};\n\n/**\n * Generate the complete standard GTM snippet as an inline script string.\n *\n * This produces the Google-recommended GTM installation code that:\n * 1. Initializes the dataLayer array\n * 2. Pushes the gtm.start event (required for \"All Pages\" trigger)\n * 3. Dynamically creates and inserts the GTM script element\n *\n * Usage in Astro:\n * ```astro\n * <script is:inline set:html={generateGtmScript({ containers: 'GTM-XXXX' })} />\n * ```\n *\n * For the noscript fallback, use `generateNoscriptTags()` separately.\n */\nexport const generateGtmScript = (config: Omit<GtmScriptConfig, 'scriptAttributes'>): string => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier.`\n );\n }\n\n // Generate one standard snippet per container\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const escapedId = escapeJsString(container.id);\n const dlArg = dataLayerName !== DEFAULT_DATA_LAYER_NAME\n ? `,'${escapeJsString(dataLayerName)}'`\n : '';\n\n // Build query params string for additional params (auth, preview, etc.)\n const mergedParams = { ...defaultQueryParams, ...container.queryParams };\n const extraParams = Object.entries(mergedParams)\n .filter(([, v]) => v != null)\n .map(([k, v]) => `&${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('');\n\n const hostUrl = (host ?? DEFAULT_GTM_HOST).replace(/\\/+$/, '');\n\n // Standard GTM snippet — matches Google's recommended installation\n return `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':` +\n `new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],` +\n `j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=` +\n `'${escapeJsString(hostUrl)}/gtm.js?id='+i+dl${extraParams ? `+'${escapeJsString(extraParams)}'` : ''};f.parentNode.insertBefore(j,f);` +\n `})(window,document,'script','${escapeJsString(dataLayerName)}','${escapedId}');`;\n }).join('\\n');\n};\n\nexport interface NoscriptTagData {\n id: string;\n src: string;\n attributes: Record<string, string>;\n}\n\n/**\n * Generate noscript iframe data for GTM containers.\n * Used by Astro components to render noscript fallbacks.\n */\nexport const generateNoscriptTags = (\n config: Omit<GtmScriptConfig, 'scriptAttributes'> & {\n iframeAttributes?: Record<string, string | number | boolean>;\n }\n): NoscriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);\n const mergedAttributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n // Filter nullish values and convert to strings\n const attributes = Object.fromEntries(\n Object.entries(mergedAttributes)\n .filter(([, v]) => v != null)\n .map(([k, v]) => [k, String(v)])\n ) as Record<string, string>;\n\n return {\n id: container.id,\n src,\n attributes\n };\n });\n};\n\n/**\n * Generate the dataLayer initialization script.\n * @throws {Error} If dataLayerName is not a valid JavaScript identifier\n */\nexport const generateDataLayerScript = (dataLayerName: string = DEFAULT_DATA_LAYER_NAME): string => {\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`\n );\n }\n return `window.${dataLayerName}=window.${dataLayerName}||[];`;\n};\n","export { generateScriptTags, generateNoscriptTags, generateDataLayerScript, generateGtmScript, DEFAULT_GTM_HOST } from './helpers';\n\n// Re-export URL utilities from core for backwards compatibility\nexport {\n normalizeContainers,\n normalizeContainer,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n\nexport type { GtmScriptConfig, ScriptTagData, NoscriptTagData } from './helpers';\n"]}
|
|
@@ -15,12 +15,42 @@ interface ScriptTagData {
|
|
|
15
15
|
defer?: boolean;
|
|
16
16
|
nonce?: string;
|
|
17
17
|
attributes: Record<string, string | boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Inline JavaScript that must be executed before the GTM script loads.
|
|
20
|
+
* Initializes the dataLayer and pushes the gtm.start event, which is
|
|
21
|
+
* required for GTM's built-in "All Pages" trigger to fire.
|
|
22
|
+
*
|
|
23
|
+
* Render this as a `<script>` tag (or `<script is:inline>` in Astro)
|
|
24
|
+
* immediately before the GTM `<script async src="...">` tag.
|
|
25
|
+
*/
|
|
26
|
+
initScript: string;
|
|
18
27
|
}
|
|
19
28
|
/**
|
|
20
29
|
* Generate script tag data for GTM containers.
|
|
21
30
|
* Used by Astro components to render script tags.
|
|
31
|
+
*
|
|
32
|
+
* Each tag includes an `initScript` property containing the dataLayer
|
|
33
|
+
* initialization and gtm.start event push. This MUST be rendered as an
|
|
34
|
+
* inline script before the GTM script tag, or GTM's "All Pages" trigger
|
|
35
|
+
* will not fire.
|
|
22
36
|
*/
|
|
23
37
|
declare const generateScriptTags: (config: GtmScriptConfig) => ScriptTagData[];
|
|
38
|
+
/**
|
|
39
|
+
* Generate the complete standard GTM snippet as an inline script string.
|
|
40
|
+
*
|
|
41
|
+
* This produces the Google-recommended GTM installation code that:
|
|
42
|
+
* 1. Initializes the dataLayer array
|
|
43
|
+
* 2. Pushes the gtm.start event (required for "All Pages" trigger)
|
|
44
|
+
* 3. Dynamically creates and inserts the GTM script element
|
|
45
|
+
*
|
|
46
|
+
* Usage in Astro:
|
|
47
|
+
* ```astro
|
|
48
|
+
* <script is:inline set:html={generateGtmScript({ containers: 'GTM-XXXX' })} />
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* For the noscript fallback, use `generateNoscriptTags()` separately.
|
|
52
|
+
*/
|
|
53
|
+
declare const generateGtmScript: (config: Omit<GtmScriptConfig, 'scriptAttributes'>) => string;
|
|
24
54
|
interface NoscriptTagData {
|
|
25
55
|
id: string;
|
|
26
56
|
src: string;
|
|
@@ -39,4 +69,4 @@ declare const generateNoscriptTags: (config: Omit<GtmScriptConfig, 'scriptAttrib
|
|
|
39
69
|
*/
|
|
40
70
|
declare const generateDataLayerScript: (dataLayerName?: string) => string;
|
|
41
71
|
|
|
42
|
-
export { GtmScriptConfig, NoscriptTagData, ScriptTagData, generateDataLayerScript, generateNoscriptTags, generateScriptTags };
|
|
72
|
+
export { GtmScriptConfig, NoscriptTagData, ScriptTagData, generateDataLayerScript, generateGtmScript, generateNoscriptTags, generateScriptTags };
|
|
@@ -15,12 +15,42 @@ interface ScriptTagData {
|
|
|
15
15
|
defer?: boolean;
|
|
16
16
|
nonce?: string;
|
|
17
17
|
attributes: Record<string, string | boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Inline JavaScript that must be executed before the GTM script loads.
|
|
20
|
+
* Initializes the dataLayer and pushes the gtm.start event, which is
|
|
21
|
+
* required for GTM's built-in "All Pages" trigger to fire.
|
|
22
|
+
*
|
|
23
|
+
* Render this as a `<script>` tag (or `<script is:inline>` in Astro)
|
|
24
|
+
* immediately before the GTM `<script async src="...">` tag.
|
|
25
|
+
*/
|
|
26
|
+
initScript: string;
|
|
18
27
|
}
|
|
19
28
|
/**
|
|
20
29
|
* Generate script tag data for GTM containers.
|
|
21
30
|
* Used by Astro components to render script tags.
|
|
31
|
+
*
|
|
32
|
+
* Each tag includes an `initScript` property containing the dataLayer
|
|
33
|
+
* initialization and gtm.start event push. This MUST be rendered as an
|
|
34
|
+
* inline script before the GTM script tag, or GTM's "All Pages" trigger
|
|
35
|
+
* will not fire.
|
|
22
36
|
*/
|
|
23
37
|
declare const generateScriptTags: (config: GtmScriptConfig) => ScriptTagData[];
|
|
38
|
+
/**
|
|
39
|
+
* Generate the complete standard GTM snippet as an inline script string.
|
|
40
|
+
*
|
|
41
|
+
* This produces the Google-recommended GTM installation code that:
|
|
42
|
+
* 1. Initializes the dataLayer array
|
|
43
|
+
* 2. Pushes the gtm.start event (required for "All Pages" trigger)
|
|
44
|
+
* 3. Dynamically creates and inserts the GTM script element
|
|
45
|
+
*
|
|
46
|
+
* Usage in Astro:
|
|
47
|
+
* ```astro
|
|
48
|
+
* <script is:inline set:html={generateGtmScript({ containers: 'GTM-XXXX' })} />
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* For the noscript fallback, use `generateNoscriptTags()` separately.
|
|
52
|
+
*/
|
|
53
|
+
declare const generateGtmScript: (config: Omit<GtmScriptConfig, 'scriptAttributes'>) => string;
|
|
24
54
|
interface NoscriptTagData {
|
|
25
55
|
id: string;
|
|
26
56
|
src: string;
|
|
@@ -39,4 +69,4 @@ declare const generateNoscriptTags: (config: Omit<GtmScriptConfig, 'scriptAttrib
|
|
|
39
69
|
*/
|
|
40
70
|
declare const generateDataLayerScript: (dataLayerName?: string) => string;
|
|
41
71
|
|
|
42
|
-
export { GtmScriptConfig, NoscriptTagData, ScriptTagData, generateDataLayerScript, generateNoscriptTags, generateScriptTags };
|
|
72
|
+
export { GtmScriptConfig, NoscriptTagData, ScriptTagData, generateDataLayerScript, generateGtmScript, generateNoscriptTags, generateScriptTags };
|
package/dist/components/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { normalizeContainers, buildGtmScriptUrl,
|
|
1
|
+
import { normalizeContainers, buildGtmScriptUrl, DEFAULT_DATA_LAYER_NAME, DEFAULT_GTM_HOST, buildGtmNoscriptUrl, DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';
|
|
2
2
|
export { DEFAULT_GTM_HOST, buildGtmNoscriptUrl as buildNoscriptUrl, buildGtmScriptUrl as buildScriptUrl, normalizeContainer, normalizeContainers } from '@jwiedeman/gtm-kit';
|
|
3
3
|
|
|
4
4
|
// src/components/helpers.ts
|
|
5
5
|
var isValidJsIdentifier = (value) => {
|
|
6
6
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);
|
|
7
7
|
};
|
|
8
|
+
var escapeJsString = (value) => {
|
|
9
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/</g, "\\x3c").replace(/>/g, "\\x3e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
10
|
+
};
|
|
8
11
|
var filterNullish = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null));
|
|
9
12
|
var generateScriptTags = (config) => {
|
|
10
13
|
const {
|
|
@@ -18,6 +21,11 @@ var generateScriptTags = (config) => {
|
|
|
18
21
|
if (!normalized.length) {
|
|
19
22
|
throw new Error("At least one GTM container is required.");
|
|
20
23
|
}
|
|
24
|
+
if (!isValidJsIdentifier(dataLayerName)) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Invalid dataLayerName: "${dataLayerName}". Must be a valid JavaScript identifier.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
21
29
|
return normalized.map((container) => {
|
|
22
30
|
if (!container.id) {
|
|
23
31
|
throw new Error("Container id is required.");
|
|
@@ -28,16 +36,47 @@ var generateScriptTags = (config) => {
|
|
|
28
36
|
};
|
|
29
37
|
const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);
|
|
30
38
|
const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
|
|
39
|
+
escapeJsString(container.id);
|
|
40
|
+
const initScript = `window.${dataLayerName}=window.${dataLayerName}||[];window.${dataLayerName}.push({'gtm.start':new Date().getTime(),event:'gtm.js'});`;
|
|
31
41
|
return {
|
|
32
42
|
id: container.id,
|
|
33
43
|
src,
|
|
34
44
|
async: asyncAttr != null ? asyncAttr : true,
|
|
35
45
|
defer,
|
|
36
46
|
nonce,
|
|
37
|
-
attributes: filterNullish(restAttributes)
|
|
47
|
+
attributes: filterNullish(restAttributes),
|
|
48
|
+
initScript
|
|
38
49
|
};
|
|
39
50
|
});
|
|
40
51
|
};
|
|
52
|
+
var generateGtmScript = (config) => {
|
|
53
|
+
const {
|
|
54
|
+
containers,
|
|
55
|
+
host = DEFAULT_GTM_HOST,
|
|
56
|
+
defaultQueryParams,
|
|
57
|
+
dataLayerName = DEFAULT_DATA_LAYER_NAME
|
|
58
|
+
} = config;
|
|
59
|
+
const normalized = normalizeContainers(containers);
|
|
60
|
+
if (!normalized.length) {
|
|
61
|
+
throw new Error("At least one GTM container is required.");
|
|
62
|
+
}
|
|
63
|
+
if (!isValidJsIdentifier(dataLayerName)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Invalid dataLayerName: "${dataLayerName}". Must be a valid JavaScript identifier.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return normalized.map((container) => {
|
|
69
|
+
if (!container.id) {
|
|
70
|
+
throw new Error("Container id is required.");
|
|
71
|
+
}
|
|
72
|
+
const escapedId = escapeJsString(container.id);
|
|
73
|
+
dataLayerName !== DEFAULT_DATA_LAYER_NAME ? `,'${escapeJsString(dataLayerName)}'` : "";
|
|
74
|
+
const mergedParams = { ...defaultQueryParams, ...container.queryParams };
|
|
75
|
+
const extraParams = Object.entries(mergedParams).filter(([, v]) => v != null).map(([k, v]) => `&${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("");
|
|
76
|
+
const hostUrl = (host != null ? host : DEFAULT_GTM_HOST).replace(/\/+$/, "");
|
|
77
|
+
return `(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='${escapeJsString(hostUrl)}/gtm.js?id='+i+dl${extraParams ? `+'${escapeJsString(extraParams)}'` : ""};f.parentNode.insertBefore(j,f);})(window,document,'script','${escapeJsString(dataLayerName)}','${escapedId}');`;
|
|
78
|
+
}).join("\n");
|
|
79
|
+
};
|
|
41
80
|
var generateNoscriptTags = (config) => {
|
|
42
81
|
const {
|
|
43
82
|
containers,
|
|
@@ -82,6 +121,6 @@ var generateDataLayerScript = (dataLayerName = DEFAULT_DATA_LAYER_NAME) => {
|
|
|
82
121
|
return `window.${dataLayerName}=window.${dataLayerName}||[];`;
|
|
83
122
|
};
|
|
84
123
|
|
|
85
|
-
export { generateDataLayerScript, generateNoscriptTags, generateScriptTags };
|
|
124
|
+
export { generateDataLayerScript, generateGtmScript, generateNoscriptTags, generateScriptTags };
|
|
86
125
|
//# sourceMappingURL=out.js.map
|
|
87
126
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["normalizeContainers","normalizeContainer","buildGtmScriptUrl","buildGtmNoscriptUrl"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAiBA,IAAM,sBAAsB,CAAC,UAA2B;AAE7D,SAAO,6BAA6B,KAAK,KAAK;AAChD;AAoBA,IAAM,gBAAgB,CAAoC,QACxD,OAAO,YAAY,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC;AAuB9D,IAAM,qBAAqB,CAAC,WAA6C;AAC9E,QAAM;AAAA,IACJ;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,aAAa,oBAAoB,UAAU;AAEjD,MAAI,CAAC,WAAW,QAAQ;AACtB,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,SAAO,WAAW,IAAI,CAAC,cAAc;AACnC,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG,UAAU;AAAA,IACf;AAEA,UAAM,MAAM,kBAAkB,MAAM,UAAU,IAAI,QAAQ,aAAa;AACvE,UAAM,EAAE,OAAO,WAAW,OAAO,OAAO,GAAG,eAAe,IAAI,8CAAoB,CAAC;AAEnF,WAAO;AAAA,MACL,IAAI,UAAU;AAAA,MACd;AAAA,MACA,OAAO,gCAAa;AAAA,MACpB;AAAA,MACA;AAAA,MACA,YAAY,cAAc,cAAc;AAAA,IAC1C;AAAA,EACF,CAAC;AACH;AAYO,IAAM,uBAAuB,CAClC,WAGsB;AACtB,QAAM;AAAA,IACJ;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,aAAa,oBAAoB,UAAU;AAEjD,MAAI,CAAC,WAAW,QAAQ;AACtB,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,SAAO,WAAW,IAAI,CAAC,cAAc;AACnC,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG,UAAU;AAAA,IACf;AAEA,UAAM,MAAM,oBAAoB,MAAM,UAAU,IAAI,QAAQ,aAAa;AACzE,UAAM,mBAAmB;AAAA,MACvB,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAGA,UAAM,aAAa,OAAO;AAAA,MACxB,OAAO,QAAQ,gBAAgB,EAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,EAC3B,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AAAA,IACnC;AAEA,WAAO;AAAA,MACL,IAAI,UAAU;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAMO,IAAM,0BAA0B,CAAC,gBAAwB,4BAAoC;AAClG,MAAI,CAAC,oBAAoB,aAAa,GAAG;AACvC,UAAM,IAAI;AAAA,MACR,2BAA2B,aAAa;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,UAAU,aAAa,WAAW,aAAa;AACxD;;;ACnLA;AAAA,EACE,uBAAAA;AAAA,EACA,sBAAAC;AAAA,EACqB,qBAArBC;AAAA,EACuB,uBAAvBC;AAAA,OACK","sourcesContent":["import {\n DEFAULT_DATA_LAYER_NAME,\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl,\n buildGtmNoscriptUrl\n} from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\n\n// Re-export for convenience\nexport {\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n};\n\n/**\n * Validate that a string is a valid JavaScript identifier.\n * This prevents XSS attacks through dataLayerName injection.\n */\nexport const isValidJsIdentifier = (value: string): boolean => {\n // Must be a valid JS identifier: starts with letter/$/_, followed by letters/digits/$/_\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);\n};\n\n/**\n * Escape a string for safe use in JavaScript string literals.\n * Prevents XSS when interpolating user-provided values into inline scripts.\n */\nexport const escapeJsString = (value: string): string => {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/</g, '\\\\x3c')\n .replace(/>/g, '\\\\x3e')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n};\n\n/** Filter out null/undefined values from an object */\nconst filterNullish = <T extends Record<string, unknown>>(obj: T): T =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) as T;\n\nexport interface GtmScriptConfig {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nexport interface ScriptTagData {\n id: string;\n src: string;\n async: boolean;\n defer?: boolean;\n nonce?: string;\n attributes: Record<string, string | boolean>;\n}\n\n/**\n * Generate script tag data for GTM containers.\n * Used by Astro components to render script tags.\n */\nexport const generateScriptTags = (config: GtmScriptConfig): ScriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n return {\n id: container.id,\n src,\n async: asyncAttr ?? true,\n defer,\n nonce,\n attributes: filterNullish(restAttributes) as Record<string, string | boolean>\n };\n });\n};\n\nexport interface NoscriptTagData {\n id: string;\n src: string;\n attributes: Record<string, string>;\n}\n\n/**\n * Generate noscript iframe data for GTM containers.\n * Used by Astro components to render noscript fallbacks.\n */\nexport const generateNoscriptTags = (\n config: Omit<GtmScriptConfig, 'scriptAttributes'> & {\n iframeAttributes?: Record<string, string | number | boolean>;\n }\n): NoscriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);\n const mergedAttributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n // Filter nullish values and convert to strings\n const attributes = Object.fromEntries(\n Object.entries(mergedAttributes)\n .filter(([, v]) => v != null)\n .map(([k, v]) => [k, String(v)])\n ) as Record<string, string>;\n\n return {\n id: container.id,\n src,\n attributes\n };\n });\n};\n\n/**\n * Generate the dataLayer initialization script.\n * @throws {Error} If dataLayerName is not a valid JavaScript identifier\n */\nexport const generateDataLayerScript = (dataLayerName: string = DEFAULT_DATA_LAYER_NAME): string => {\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`\n );\n }\n return `window.${dataLayerName}=window.${dataLayerName}||[];`;\n};\n","export { generateScriptTags, generateNoscriptTags, generateDataLayerScript, DEFAULT_GTM_HOST } from './helpers';\n\n// Re-export URL utilities from core for backwards compatibility\nexport {\n normalizeContainers,\n normalizeContainer,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n\nexport type { GtmScriptConfig, ScriptTagData, NoscriptTagData } from './helpers';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["normalizeContainers","normalizeContainer","buildGtmScriptUrl","buildGtmNoscriptUrl"],"mappings":"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","sourcesContent":["import {\n DEFAULT_DATA_LAYER_NAME,\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl,\n buildGtmNoscriptUrl\n} from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\n\n// Re-export for convenience\nexport {\n DEFAULT_GTM_HOST,\n DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n};\n\n/**\n * Validate that a string is a valid JavaScript identifier.\n * This prevents XSS attacks through dataLayerName injection.\n */\nexport const isValidJsIdentifier = (value: string): boolean => {\n // Must be a valid JS identifier: starts with letter/$/_, followed by letters/digits/$/_\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);\n};\n\n/**\n * Escape a string for safe use in JavaScript string literals.\n * Prevents XSS when interpolating user-provided values into inline scripts.\n */\nexport const escapeJsString = (value: string): string => {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/</g, '\\\\x3c')\n .replace(/>/g, '\\\\x3e')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n};\n\n/** Filter out null/undefined values from an object */\nconst filterNullish = <T extends Record<string, unknown>>(obj: T): T =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) as T;\n\nexport interface GtmScriptConfig {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nexport interface ScriptTagData {\n id: string;\n src: string;\n async: boolean;\n defer?: boolean;\n nonce?: string;\n attributes: Record<string, string | boolean>;\n /**\n * Inline JavaScript that must be executed before the GTM script loads.\n * Initializes the dataLayer and pushes the gtm.start event, which is\n * required for GTM's built-in \"All Pages\" trigger to fire.\n *\n * Render this as a `<script>` tag (or `<script is:inline>` in Astro)\n * immediately before the GTM `<script async src=\"...\">` tag.\n */\n initScript: string;\n}\n\n/**\n * Generate script tag data for GTM containers.\n * Used by Astro components to render script tags.\n *\n * Each tag includes an `initScript` property containing the dataLayer\n * initialization and gtm.start event push. This MUST be rendered as an\n * inline script before the GTM script tag, or GTM's \"All Pages\" trigger\n * will not fire.\n */\nexport const generateScriptTags = (config: GtmScriptConfig): ScriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier.`\n );\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n // Generate the standard GTM initialization script.\n // This pushes the gtm.start event to the dataLayer, which is required\n // for GTM to trigger its built-in \"All Pages\" / \"Container Loaded\" trigger.\n const escapedId = escapeJsString(container.id);\n const dlParam = dataLayerName !== DEFAULT_DATA_LAYER_NAME ? `'&l='+l` : `''`;\n const initScript =\n `window.${dataLayerName}=window.${dataLayerName}||[];` +\n `window.${dataLayerName}.push({'gtm.start':new Date().getTime(),event:'gtm.js'});`;\n\n return {\n id: container.id,\n src,\n async: asyncAttr ?? true,\n defer,\n nonce,\n attributes: filterNullish(restAttributes) as Record<string, string | boolean>,\n initScript\n };\n });\n};\n\n/**\n * Generate the complete standard GTM snippet as an inline script string.\n *\n * This produces the Google-recommended GTM installation code that:\n * 1. Initializes the dataLayer array\n * 2. Pushes the gtm.start event (required for \"All Pages\" trigger)\n * 3. Dynamically creates and inserts the GTM script element\n *\n * Usage in Astro:\n * ```astro\n * <script is:inline set:html={generateGtmScript({ containers: 'GTM-XXXX' })} />\n * ```\n *\n * For the noscript fallback, use `generateNoscriptTags()` separately.\n */\nexport const generateGtmScript = (config: Omit<GtmScriptConfig, 'scriptAttributes'>): string => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier.`\n );\n }\n\n // Generate one standard snippet per container\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const escapedId = escapeJsString(container.id);\n const dlArg = dataLayerName !== DEFAULT_DATA_LAYER_NAME\n ? `,'${escapeJsString(dataLayerName)}'`\n : '';\n\n // Build query params string for additional params (auth, preview, etc.)\n const mergedParams = { ...defaultQueryParams, ...container.queryParams };\n const extraParams = Object.entries(mergedParams)\n .filter(([, v]) => v != null)\n .map(([k, v]) => `&${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('');\n\n const hostUrl = (host ?? DEFAULT_GTM_HOST).replace(/\\/+$/, '');\n\n // Standard GTM snippet — matches Google's recommended installation\n return `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':` +\n `new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],` +\n `j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=` +\n `'${escapeJsString(hostUrl)}/gtm.js?id='+i+dl${extraParams ? `+'${escapeJsString(extraParams)}'` : ''};f.parentNode.insertBefore(j,f);` +\n `})(window,document,'script','${escapeJsString(dataLayerName)}','${escapedId}');`;\n }).join('\\n');\n};\n\nexport interface NoscriptTagData {\n id: string;\n src: string;\n attributes: Record<string, string>;\n}\n\n/**\n * Generate noscript iframe data for GTM containers.\n * Used by Astro components to render noscript fallbacks.\n */\nexport const generateNoscriptTags = (\n config: Omit<GtmScriptConfig, 'scriptAttributes'> & {\n iframeAttributes?: Record<string, string | number | boolean>;\n }\n): NoscriptTagData[] => {\n const {\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n } = config;\n\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required.');\n }\n\n return normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);\n const mergedAttributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n // Filter nullish values and convert to strings\n const attributes = Object.fromEntries(\n Object.entries(mergedAttributes)\n .filter(([, v]) => v != null)\n .map(([k, v]) => [k, String(v)])\n ) as Record<string, string>;\n\n return {\n id: container.id,\n src,\n attributes\n };\n });\n};\n\n/**\n * Generate the dataLayer initialization script.\n * @throws {Error} If dataLayerName is not a valid JavaScript identifier\n */\nexport const generateDataLayerScript = (dataLayerName: string = DEFAULT_DATA_LAYER_NAME): string => {\n if (!isValidJsIdentifier(dataLayerName)) {\n throw new Error(\n `Invalid dataLayerName: \"${dataLayerName}\". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`\n );\n }\n return `window.${dataLayerName}=window.${dataLayerName}||[];`;\n};\n","export { generateScriptTags, generateNoscriptTags, generateDataLayerScript, generateGtmScript, DEFAULT_GTM_HOST } from './helpers';\n\n// Re-export URL utilities from core for backwards compatibility\nexport {\n normalizeContainers,\n normalizeContainer,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n\nexport type { GtmScriptConfig, ScriptTagData, NoscriptTagData } from './helpers';\n"]}
|
package/package.json
CHANGED