@jwiedeman/gtm-kit-astro 1.1.4 → 1.1.5
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.
- package/README.md +14 -0
- package/dist/components/index.cjs +3 -3
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.cts +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +2 -2
- package/dist/components/index.js.map +1 -1
- package/package.json +11 -3
- package/src/components/GtmHead.astro +12 -1
package/README.md
CHANGED
|
@@ -326,6 +326,20 @@ Page views are automatically tracked on:
|
|
|
326
326
|
|
|
327
327
|
---
|
|
328
328
|
|
|
329
|
+
## Related Packages
|
|
330
|
+
|
|
331
|
+
- **Core**: [@jwiedeman/gtm-kit](https://www.npmjs.com/package/@jwiedeman/gtm-kit) (required)
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Support
|
|
336
|
+
|
|
337
|
+
**Have a question, found a bug, or need help?**
|
|
338
|
+
|
|
339
|
+
[Open an issue on GitHub](https://github.com/jwiedeman/GTM-Kit/issues) — we're actively maintaining this project and respond quickly.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
329
343
|
## License
|
|
330
344
|
|
|
331
345
|
MIT
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var gtmKit = require('@jwiedeman/gtm-kit');
|
|
4
4
|
|
|
5
|
-
var C=
|
|
5
|
+
var w=r=>/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(r);var C=r=>Object.fromEntries(Object.entries(r).filter(([,e])=>e!=null)),x=r=>{let{containers:e,host:o=gtmKit.DEFAULT_GTM_HOST,defaultQueryParams:c,scriptAttributes:i,dataLayerName:g=gtmKit.DEFAULT_DATA_LAYER_NAME}=r,n=gtmKit.normalizeContainers(e);if(!n.length)throw new Error("At least one GTM container is required.");return n.map(t=>{if(!t.id)throw new Error("Container id is required.");let p={...c,...t.queryParams},l=gtmKit.buildGtmScriptUrl(o,t.id,p,g),{async:a,defer:u,nonce:s,...d}=i!=null?i:{};return {id:t.id,src:l,async:a!=null?a:!0,defer:u,nonce:s,attributes:C(d)}})},E=r=>{let{containers:e,host:o=gtmKit.DEFAULT_GTM_HOST,defaultQueryParams:c,iframeAttributes:i,dataLayerName:g=gtmKit.DEFAULT_DATA_LAYER_NAME}=r,n=gtmKit.normalizeContainers(e);if(!n.length)throw new Error("At least one GTM container is required.");return n.map(t=>{if(!t.id)throw new Error("Container id is required.");let p={...c,...t.queryParams},l=gtmKit.buildGtmNoscriptUrl(o,t.id,p,g),a={...gtmKit.DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,...i},u=Object.fromEntries(Object.entries(a).filter(([,s])=>s!=null).map(([s,d])=>[s,String(d)]));return {id:t.id,src:l,attributes:u}})},h=(r=gtmKit.DEFAULT_DATA_LAYER_NAME)=>{if(!w(r))throw new Error(`Invalid dataLayerName: "${r}". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`);return `window.${r}=window.${r}||[];`};
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, 'DEFAULT_GTM_HOST', {
|
|
8
8
|
enumerable: true,
|
|
@@ -24,8 +24,8 @@ Object.defineProperty(exports, 'normalizeContainers', {
|
|
|
24
24
|
enumerable: true,
|
|
25
25
|
get: function () { return gtmKit.normalizeContainers; }
|
|
26
26
|
});
|
|
27
|
-
exports.generateDataLayerScript =
|
|
27
|
+
exports.generateDataLayerScript = h;
|
|
28
28
|
exports.generateNoscriptTags = E;
|
|
29
|
-
exports.generateScriptTags =
|
|
29
|
+
exports.generateScriptTags = x;
|
|
30
30
|
//# sourceMappingURL=out.js.map
|
|
31
31
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","filterNullish","obj","v","generateScriptTags","config","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","generateNoscriptTags","iframeAttributes","mergedAttributes","attributes","k","generateDataLayerScript"],"mappings":"AAAA,OACE,2BAAAA,EACA,oBAAAC,EACA,sCAAAC,EACA,sBAAAC,EACA,uBAAAC,EACA,qBAAAC,EACA,uBAAAC,MACK,
|
|
1
|
+
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","isValidJsIdentifier","value","filterNullish","obj","v","generateScriptTags","config","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","generateNoscriptTags","iframeAttributes","mergedAttributes","attributes","k","generateDataLayerScript"],"mappings":"AAAA,OACE,2BAAAA,EACA,oBAAAC,EACA,sCAAAC,EACA,sBAAAC,EACA,uBAAAC,EACA,qBAAAC,EACA,uBAAAC,MACK,qBAiBA,IAAMC,EAAuBC,GAE3B,6BAA6B,KAAKA,CAAK,EAqBhD,IAAMC,EAAoDC,GACxD,OAAO,YAAY,OAAO,QAAQA,CAAG,EAAE,OAAO,CAAC,CAAC,CAAEC,CAAC,IAAMA,GAAK,IAAI,CAAC,EAuBxDC,EAAsBC,GAA6C,CAC9E,GAAM,CACJ,WAAAC,EACA,KAAAC,EAAOd,EACP,mBAAAe,EACA,iBAAAC,EACA,cAAAC,EAAgBlB,CAClB,EAAIa,EAEEM,EAAaf,EAAoBU,CAAU,EAEjD,GAAI,CAACK,EAAW,OACd,MAAM,IAAI,MAAM,yCAAyC,EAG3D,OAAOA,EAAW,IAAKC,GAAc,CACnC,GAAI,CAACA,EAAU,GACb,MAAM,IAAI,MAAM,2BAA2B,EAG7C,IAAMC,EAAS,CACb,GAAGL,EACH,GAAGI,EAAU,WACf,EAEME,EAAMjB,EAAkBU,EAAMK,EAAU,GAAIC,EAAQH,CAAa,EACjE,CAAE,MAAOK,EAAW,MAAAC,EAAO,MAAAC,EAAO,GAAGC,CAAe,EAAIT,GAAA,KAAAA,EAAoB,CAAC,EAEnF,MAAO,CACL,GAAIG,EAAU,GACd,IAAAE,EACA,MAAOC,GAAA,KAAAA,EAAa,GACpB,MAAAC,EACA,MAAAC,EACA,WAAYhB,EAAciB,CAAc,CAC1C,CACF,CAAC,CACH,EAYaC,EACXd,GAGsB,CACtB,GAAM,CACJ,WAAAC,EACA,KAAAC,EAAOd,EACP,mBAAAe,EACA,iBAAAY,EACA,cAAAV,EAAgBlB,CAClB,EAAIa,EAEEM,EAAaf,EAAoBU,CAAU,EAEjD,GAAI,CAACK,EAAW,OACd,MAAM,IAAI,MAAM,yCAAyC,EAG3D,OAAOA,EAAW,IAAKC,GAAc,CACnC,GAAI,CAACA,EAAU,GACb,MAAM,IAAI,MAAM,2BAA2B,EAG7C,IAAMC,EAAS,CACb,GAAGL,EACH,GAAGI,EAAU,WACf,EAEME,EAAMhB,EAAoBS,EAAMK,EAAU,GAAIC,EAAQH,CAAa,EACnEW,EAAmB,CACvB,GAAG3B,EACH,GAAG0B,CACL,EAGME,EAAa,OAAO,YACxB,OAAO,QAAQD,CAAgB,EAC5B,OAAO,CAAC,CAAC,CAAElB,CAAC,IAAMA,GAAK,IAAI,EAC3B,IAAI,CAAC,CAACoB,EAAGpB,CAAC,IAAM,CAACoB,EAAG,OAAOpB,CAAC,CAAC,CAAC,CACnC,EAEA,MAAO,CACL,GAAIS,EAAU,GACd,IAAAE,EACA,WAAAQ,CACF,CACF,CAAC,CACH,EAMaE,EAA0B,CAACd,EAAwBlB,IAAoC,CAClG,GAAI,CAACO,EAAoBW,CAAa,EACpC,MAAM,IAAI,MACR,2BAA2BA,CAAa,mGAC1C,EAEF,MAAO,UAAUA,CAAa,WAAWA,CAAa,OACxD,ECnLA,OACE,uBAAAd,EACA,sBAAAD,EACqB,qBAArBE,EACuB,uBAAvBC,MACK","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"]}
|
|
@@ -35,6 +35,7 @@ declare const generateNoscriptTags: (config: Omit<GtmScriptConfig, 'scriptAttrib
|
|
|
35
35
|
}) => NoscriptTagData[];
|
|
36
36
|
/**
|
|
37
37
|
* Generate the dataLayer initialization script.
|
|
38
|
+
* @throws {Error} If dataLayerName is not a valid JavaScript identifier
|
|
38
39
|
*/
|
|
39
40
|
declare const generateDataLayerScript: (dataLayerName?: string) => string;
|
|
40
41
|
|
|
@@ -35,6 +35,7 @@ declare const generateNoscriptTags: (config: Omit<GtmScriptConfig, 'scriptAttrib
|
|
|
35
35
|
}) => NoscriptTagData[];
|
|
36
36
|
/**
|
|
37
37
|
* Generate the dataLayer initialization script.
|
|
38
|
+
* @throws {Error} If dataLayerName is not a valid JavaScript identifier
|
|
38
39
|
*/
|
|
39
40
|
declare const generateDataLayerScript: (dataLayerName?: string) => string;
|
|
40
41
|
|
package/dist/components/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { normalizeContainers, buildGtmScriptUrl, buildGtmNoscriptUrl, DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES, DEFAULT_DATA_LAYER_NAME, DEFAULT_GTM_HOST } 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
|
-
var C=
|
|
4
|
+
var w=r=>/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(r);var C=r=>Object.fromEntries(Object.entries(r).filter(([,e])=>e!=null)),x=r=>{let{containers:e,host:o=DEFAULT_GTM_HOST,defaultQueryParams:c,scriptAttributes:i,dataLayerName:g=DEFAULT_DATA_LAYER_NAME}=r,n=normalizeContainers(e);if(!n.length)throw new Error("At least one GTM container is required.");return n.map(t=>{if(!t.id)throw new Error("Container id is required.");let p={...c,...t.queryParams},l=buildGtmScriptUrl(o,t.id,p,g),{async:a,defer:u,nonce:s,...d}=i!=null?i:{};return {id:t.id,src:l,async:a!=null?a:!0,defer:u,nonce:s,attributes:C(d)}})},E=r=>{let{containers:e,host:o=DEFAULT_GTM_HOST,defaultQueryParams:c,iframeAttributes:i,dataLayerName:g=DEFAULT_DATA_LAYER_NAME}=r,n=normalizeContainers(e);if(!n.length)throw new Error("At least one GTM container is required.");return n.map(t=>{if(!t.id)throw new Error("Container id is required.");let p={...c,...t.queryParams},l=buildGtmNoscriptUrl(o,t.id,p,g),a={...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,...i},u=Object.fromEntries(Object.entries(a).filter(([,s])=>s!=null).map(([s,d])=>[s,String(d)]));return {id:t.id,src:l,attributes:u}})},h=(r=DEFAULT_DATA_LAYER_NAME)=>{if(!w(r))throw new Error(`Invalid dataLayerName: "${r}". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`);return `window.${r}=window.${r}||[];`};
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export { h as generateDataLayerScript, E as generateNoscriptTags, x as generateScriptTags };
|
|
7
7
|
//# sourceMappingURL=out.js.map
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","filterNullish","obj","v","generateScriptTags","config","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","generateNoscriptTags","iframeAttributes","mergedAttributes","attributes","k","generateDataLayerScript"],"mappings":"AAAA,OACE,2BAAAA,EACA,oBAAAC,EACA,sCAAAC,EACA,sBAAAC,EACA,uBAAAC,EACA,qBAAAC,EACA,uBAAAC,MACK,
|
|
1
|
+
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","isValidJsIdentifier","value","filterNullish","obj","v","generateScriptTags","config","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","generateNoscriptTags","iframeAttributes","mergedAttributes","attributes","k","generateDataLayerScript"],"mappings":"AAAA,OACE,2BAAAA,EACA,oBAAAC,EACA,sCAAAC,EACA,sBAAAC,EACA,uBAAAC,EACA,qBAAAC,EACA,uBAAAC,MACK,qBAiBA,IAAMC,EAAuBC,GAE3B,6BAA6B,KAAKA,CAAK,EAqBhD,IAAMC,EAAoDC,GACxD,OAAO,YAAY,OAAO,QAAQA,CAAG,EAAE,OAAO,CAAC,CAAC,CAAEC,CAAC,IAAMA,GAAK,IAAI,CAAC,EAuBxDC,EAAsBC,GAA6C,CAC9E,GAAM,CACJ,WAAAC,EACA,KAAAC,EAAOd,EACP,mBAAAe,EACA,iBAAAC,EACA,cAAAC,EAAgBlB,CAClB,EAAIa,EAEEM,EAAaf,EAAoBU,CAAU,EAEjD,GAAI,CAACK,EAAW,OACd,MAAM,IAAI,MAAM,yCAAyC,EAG3D,OAAOA,EAAW,IAAKC,GAAc,CACnC,GAAI,CAACA,EAAU,GACb,MAAM,IAAI,MAAM,2BAA2B,EAG7C,IAAMC,EAAS,CACb,GAAGL,EACH,GAAGI,EAAU,WACf,EAEME,EAAMjB,EAAkBU,EAAMK,EAAU,GAAIC,EAAQH,CAAa,EACjE,CAAE,MAAOK,EAAW,MAAAC,EAAO,MAAAC,EAAO,GAAGC,CAAe,EAAIT,GAAA,KAAAA,EAAoB,CAAC,EAEnF,MAAO,CACL,GAAIG,EAAU,GACd,IAAAE,EACA,MAAOC,GAAA,KAAAA,EAAa,GACpB,MAAAC,EACA,MAAAC,EACA,WAAYhB,EAAciB,CAAc,CAC1C,CACF,CAAC,CACH,EAYaC,EACXd,GAGsB,CACtB,GAAM,CACJ,WAAAC,EACA,KAAAC,EAAOd,EACP,mBAAAe,EACA,iBAAAY,EACA,cAAAV,EAAgBlB,CAClB,EAAIa,EAEEM,EAAaf,EAAoBU,CAAU,EAEjD,GAAI,CAACK,EAAW,OACd,MAAM,IAAI,MAAM,yCAAyC,EAG3D,OAAOA,EAAW,IAAKC,GAAc,CACnC,GAAI,CAACA,EAAU,GACb,MAAM,IAAI,MAAM,2BAA2B,EAG7C,IAAMC,EAAS,CACb,GAAGL,EACH,GAAGI,EAAU,WACf,EAEME,EAAMhB,EAAoBS,EAAMK,EAAU,GAAIC,EAAQH,CAAa,EACnEW,EAAmB,CACvB,GAAG3B,EACH,GAAG0B,CACL,EAGME,EAAa,OAAO,YACxB,OAAO,QAAQD,CAAgB,EAC5B,OAAO,CAAC,CAAC,CAAElB,CAAC,IAAMA,GAAK,IAAI,EAC3B,IAAI,CAAC,CAACoB,EAAGpB,CAAC,IAAM,CAACoB,EAAG,OAAOpB,CAAC,CAAC,CAAC,CACnC,EAEA,MAAO,CACL,GAAIS,EAAU,GACd,IAAAE,EACA,WAAAQ,CACF,CACF,CAAC,CACH,EAMaE,EAA0B,CAACd,EAAwBlB,IAAoC,CAClG,GAAI,CAACO,EAAoBW,CAAa,EACpC,MAAM,IAAI,MACR,2BAA2BA,CAAa,mGAC1C,EAEF,MAAO,UAAUA,CAAa,WAAWA,CAAa,OACxD,ECnLA,OACE,uBAAAd,EACA,sBAAAD,EACqB,qBAArBE,EACuB,uBAAvBC,MACK","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"]}
|
package/package.json
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwiedeman/gtm-kit-astro",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Astro components and helpers for GTM Kit - Google Tag Manager integration.",
|
|
3
|
+
"version": "1.1.5",
|
|
4
|
+
"description": "Astro components and helpers for GTM Kit - Google Tag Manager integration with View Transitions support.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/jwiedeman/GTM-Kit.git",
|
|
8
8
|
"directory": "packages/astro"
|
|
9
9
|
},
|
|
10
|
+
"homepage": "https://github.com/jwiedeman/GTM-Kit/tree/main/packages/astro#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/jwiedeman/GTM-Kit/issues"
|
|
13
|
+
},
|
|
10
14
|
"author": "jwiedeman",
|
|
11
15
|
"keywords": [
|
|
12
16
|
"gtm",
|
|
13
17
|
"google-tag-manager",
|
|
14
18
|
"astro",
|
|
15
19
|
"astrojs",
|
|
16
|
-
"view-transitions"
|
|
20
|
+
"view-transitions",
|
|
21
|
+
"static-site",
|
|
22
|
+
"analytics",
|
|
23
|
+
"tracking"
|
|
17
24
|
],
|
|
18
25
|
"license": "MIT",
|
|
26
|
+
"sideEffects": false,
|
|
19
27
|
"publishConfig": {
|
|
20
28
|
"access": "public"
|
|
21
29
|
},
|
|
@@ -26,6 +26,8 @@ import type { ContainerConfigInput, ScriptAttributes, ConsentState } from '@jwie
|
|
|
26
26
|
import {
|
|
27
27
|
generateScriptTags,
|
|
28
28
|
generateDataLayerScript,
|
|
29
|
+
escapeJsString,
|
|
30
|
+
isValidJsIdentifier,
|
|
29
31
|
DEFAULT_GTM_HOST
|
|
30
32
|
} from './helpers';
|
|
31
33
|
|
|
@@ -72,8 +74,17 @@ let initScript = generateDataLayerScript(dataLayerName);
|
|
|
72
74
|
|
|
73
75
|
// Add consent defaults if provided
|
|
74
76
|
if (defaultConsent) {
|
|
77
|
+
// Validate consent keys are valid identifiers and escape values to prevent XSS
|
|
75
78
|
const consentEntries = Object.entries(defaultConsent)
|
|
76
|
-
.map(([key, value]) =>
|
|
79
|
+
.map(([key, value]) => {
|
|
80
|
+
// Consent keys should be valid identifiers (e.g., 'ad_storage', 'analytics_storage')
|
|
81
|
+
if (!isValidJsIdentifier(key)) {
|
|
82
|
+
throw new Error(`Invalid consent key: "${key}". Must be a valid JavaScript identifier.`);
|
|
83
|
+
}
|
|
84
|
+
// Escape the value to prevent XSS
|
|
85
|
+
const escapedValue = escapeJsString(String(value));
|
|
86
|
+
return `'${key}':'${escapedValue}'`;
|
|
87
|
+
})
|
|
77
88
|
.join(',');
|
|
78
89
|
initScript += `${dataLayerName}.push(['consent','default',{${consentEntries}}]);`;
|
|
79
90
|
}
|