@jwiedeman/gtm-kit-astro 1.1.6 → 1.2.0
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 +176 -0
- package/dist/components/index.cjs +93 -14
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +81 -2
- package/dist/components/index.js.map +1 -1
- package/dist/index.cjs +175 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -16
- package/dist/index.d.ts +0 -16
- package/dist/index.js +155 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -319,6 +319,182 @@ Page views are automatically tracked on:
|
|
|
319
319
|
|
|
320
320
|
---
|
|
321
321
|
|
|
322
|
+
## Island Architecture Compatibility
|
|
323
|
+
|
|
324
|
+
GTM Kit is fully compatible with Astro's [island architecture](https://docs.astro.build/en/concepts/islands/) (partial hydration). Here's how it works:
|
|
325
|
+
|
|
326
|
+
### How GTM Integrates with Islands
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
┌─────────────────────────────────────────────────────────┐
|
|
330
|
+
│ Static HTML Shell │
|
|
331
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
332
|
+
│ │ <head> │ │
|
|
333
|
+
│ │ <GtmHead /> ← Injects GTM script (static) │ │
|
|
334
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
335
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
336
|
+
│ │ <body> │ │
|
|
337
|
+
│ │ ┌───────────────┐ ┌───────────────────┐ │ │
|
|
338
|
+
│ │ │ Static Content│ │ 🏝️ Interactive │ │ │
|
|
339
|
+
│ │ │ │ │ Island │ │ │
|
|
340
|
+
│ │ │ │ │ │ │ │
|
|
341
|
+
│ │ │ │ │ push({ event }) │→ dataLayer
|
|
342
|
+
│ │ └───────────────┘ └───────────────────┘ │ │
|
|
343
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
344
|
+
└─────────────────────────────────────────────────────────┘
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Key Concepts
|
|
348
|
+
|
|
349
|
+
1. **GTM loads globally**: The `<GtmHead />` component injects GTM scripts into the static HTML shell. This means GTM is available to all islands without re-initialization.
|
|
350
|
+
|
|
351
|
+
2. **Islands can push events**: Any hydrated island (React, Vue, Svelte, etc.) can import and use the `push()` function to send events.
|
|
352
|
+
|
|
353
|
+
3. **No hydration mismatch**: Since GTM initialization happens in a `<script>` tag (not a component), there's no hydration mismatch concern.
|
|
354
|
+
|
|
355
|
+
4. **View Transitions work**: Page tracking integrates seamlessly with Astro's View Transitions for SPA-like navigation.
|
|
356
|
+
|
|
357
|
+
### Using GTM in Framework Islands
|
|
358
|
+
|
|
359
|
+
#### React Island
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
// src/components/AddToCart.tsx
|
|
363
|
+
import { push } from '@jwiedeman/gtm-kit-astro';
|
|
364
|
+
|
|
365
|
+
export default function AddToCart({ product }) {
|
|
366
|
+
const handleClick = () => {
|
|
367
|
+
push({
|
|
368
|
+
event: 'add_to_cart',
|
|
369
|
+
ecommerce: {
|
|
370
|
+
items: [
|
|
371
|
+
{
|
|
372
|
+
item_id: product.id,
|
|
373
|
+
item_name: product.name,
|
|
374
|
+
price: product.price
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return <button onClick={handleClick}>Add to Cart</button>;
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```astro
|
|
386
|
+
---
|
|
387
|
+
// src/pages/product.astro
|
|
388
|
+
import { GtmHead } from '@jwiedeman/gtm-kit-astro/components';
|
|
389
|
+
import AddToCart from '../components/AddToCart';
|
|
390
|
+
---
|
|
391
|
+
<html>
|
|
392
|
+
<head>
|
|
393
|
+
<GtmHead containers="GTM-XXXXXX" enablePageTracking />
|
|
394
|
+
</head>
|
|
395
|
+
<body>
|
|
396
|
+
<h1>Product Page</h1>
|
|
397
|
+
<!-- Static content -->
|
|
398
|
+
<p>Product description...</p>
|
|
399
|
+
|
|
400
|
+
<!-- Interactive island -->
|
|
401
|
+
<AddToCart client:load product={{ id: 'SKU-001', name: 'Widget', price: 29.99 }} />
|
|
402
|
+
</body>
|
|
403
|
+
</html>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### Vue Island
|
|
407
|
+
|
|
408
|
+
```vue
|
|
409
|
+
<!-- src/components/NewsletterForm.vue -->
|
|
410
|
+
<script setup>
|
|
411
|
+
import { push } from '@jwiedeman/gtm-kit-astro';
|
|
412
|
+
|
|
413
|
+
const handleSubmit = () => {
|
|
414
|
+
push({ event: 'newsletter_signup', method: 'footer_form' });
|
|
415
|
+
};
|
|
416
|
+
</script>
|
|
417
|
+
|
|
418
|
+
<template>
|
|
419
|
+
<form @submit.prevent="handleSubmit">
|
|
420
|
+
<input type="email" placeholder="Email" />
|
|
421
|
+
<button type="submit">Subscribe</button>
|
|
422
|
+
</form>
|
|
423
|
+
</template>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### Svelte Island
|
|
427
|
+
|
|
428
|
+
```svelte
|
|
429
|
+
<!-- src/components/VideoPlayer.svelte -->
|
|
430
|
+
<script>
|
|
431
|
+
import { push } from '@jwiedeman/gtm-kit-astro';
|
|
432
|
+
|
|
433
|
+
export let videoId;
|
|
434
|
+
|
|
435
|
+
function onPlay() {
|
|
436
|
+
push({ event: 'video_start', video_id: videoId });
|
|
437
|
+
}
|
|
438
|
+
</script>
|
|
439
|
+
|
|
440
|
+
<video on:play={onPlay}>
|
|
441
|
+
<source src="/videos/{videoId}.mp4" />
|
|
442
|
+
</video>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Hydration Directive Impact
|
|
446
|
+
|
|
447
|
+
Different Astro hydration directives affect when your island can push events:
|
|
448
|
+
|
|
449
|
+
| Directive | When GTM Events Work |
|
|
450
|
+
| ---------------- | ------------------------------------------------ |
|
|
451
|
+
| `client:load` | Immediately after page load |
|
|
452
|
+
| `client:idle` | After page is interactive (requestIdleCallback) |
|
|
453
|
+
| `client:visible` | When island scrolls into viewport |
|
|
454
|
+
| `client:media` | When media query matches |
|
|
455
|
+
| `client:only` | Immediately (no server render, client-side only) |
|
|
456
|
+
|
|
457
|
+
### Best Practices
|
|
458
|
+
|
|
459
|
+
1. **Initialize once in layout**: Place `<GtmHead />` in your base layout, not in individual pages.
|
|
460
|
+
|
|
461
|
+
2. **Don't re-initialize in islands**: The `push()` function works without re-initializing GTM.
|
|
462
|
+
|
|
463
|
+
3. **Use `client:load` for critical tracking**: For e-commerce or important events, use `client:load` to ensure tracking starts immediately.
|
|
464
|
+
|
|
465
|
+
4. **Defer non-critical islands**: Use `client:idle` or `client:visible` for analytics-only components to improve page performance.
|
|
466
|
+
|
|
467
|
+
5. **Handle edge cases**: If an island might render before GTM loads, events are safely queued.
|
|
468
|
+
|
|
469
|
+
### Static Site Generation (SSG)
|
|
470
|
+
|
|
471
|
+
GTM Kit works perfectly with Astro's static output:
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
// astro.config.mjs
|
|
475
|
+
export default defineConfig({
|
|
476
|
+
output: 'static' // Default - fully static
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
All GTM initialization happens client-side, so pre-rendered pages work correctly.
|
|
481
|
+
|
|
482
|
+
### Server-Side Rendering (SSR)
|
|
483
|
+
|
|
484
|
+
For SSR/hybrid mode, GTM Kit also works seamlessly:
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
// astro.config.mjs
|
|
488
|
+
export default defineConfig({
|
|
489
|
+
output: 'server', // or 'hybrid'
|
|
490
|
+
adapter: node() // or any adapter
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
The `<GtmHead />` component generates static script tags that work identically in both modes.
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
322
498
|
## Requirements
|
|
323
499
|
|
|
324
500
|
- Astro 3.0+, 4.0+, or 5.0+
|
|
@@ -2,30 +2,109 @@
|
|
|
2
2
|
|
|
3
3
|
var gtmKit = require('@jwiedeman/gtm-kit');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/components/helpers.ts
|
|
6
|
+
var isValidJsIdentifier = (value) => {
|
|
7
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);
|
|
8
|
+
};
|
|
9
|
+
var filterNullish = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null));
|
|
10
|
+
var generateScriptTags = (config) => {
|
|
11
|
+
const {
|
|
12
|
+
containers,
|
|
13
|
+
host = gtmKit.DEFAULT_GTM_HOST,
|
|
14
|
+
defaultQueryParams,
|
|
15
|
+
scriptAttributes,
|
|
16
|
+
dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
|
|
17
|
+
} = config;
|
|
18
|
+
const normalized = gtmKit.normalizeContainers(containers);
|
|
19
|
+
if (!normalized.length) {
|
|
20
|
+
throw new Error("At least one GTM container is required.");
|
|
21
|
+
}
|
|
22
|
+
return normalized.map((container) => {
|
|
23
|
+
if (!container.id) {
|
|
24
|
+
throw new Error("Container id is required.");
|
|
25
|
+
}
|
|
26
|
+
const params = {
|
|
27
|
+
...defaultQueryParams,
|
|
28
|
+
...container.queryParams
|
|
29
|
+
};
|
|
30
|
+
const src = gtmKit.buildGtmScriptUrl(host, container.id, params, dataLayerName);
|
|
31
|
+
const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
|
|
32
|
+
return {
|
|
33
|
+
id: container.id,
|
|
34
|
+
src,
|
|
35
|
+
async: asyncAttr != null ? asyncAttr : true,
|
|
36
|
+
defer,
|
|
37
|
+
nonce,
|
|
38
|
+
attributes: filterNullish(restAttributes)
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
var generateNoscriptTags = (config) => {
|
|
43
|
+
const {
|
|
44
|
+
containers,
|
|
45
|
+
host = gtmKit.DEFAULT_GTM_HOST,
|
|
46
|
+
defaultQueryParams,
|
|
47
|
+
iframeAttributes,
|
|
48
|
+
dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
|
|
49
|
+
} = config;
|
|
50
|
+
const normalized = gtmKit.normalizeContainers(containers);
|
|
51
|
+
if (!normalized.length) {
|
|
52
|
+
throw new Error("At least one GTM container is required.");
|
|
53
|
+
}
|
|
54
|
+
return normalized.map((container) => {
|
|
55
|
+
if (!container.id) {
|
|
56
|
+
throw new Error("Container id is required.");
|
|
57
|
+
}
|
|
58
|
+
const params = {
|
|
59
|
+
...defaultQueryParams,
|
|
60
|
+
...container.queryParams
|
|
61
|
+
};
|
|
62
|
+
const src = gtmKit.buildGtmNoscriptUrl(host, container.id, params, dataLayerName);
|
|
63
|
+
const mergedAttributes = {
|
|
64
|
+
...gtmKit.DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,
|
|
65
|
+
...iframeAttributes
|
|
66
|
+
};
|
|
67
|
+
const attributes = Object.fromEntries(
|
|
68
|
+
Object.entries(mergedAttributes).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
id: container.id,
|
|
72
|
+
src,
|
|
73
|
+
attributes
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
var generateDataLayerScript = (dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME) => {
|
|
78
|
+
if (!isValidJsIdentifier(dataLayerName)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Invalid dataLayerName: "${dataLayerName}". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return `window.${dataLayerName}=window.${dataLayerName}||[];`;
|
|
84
|
+
};
|
|
6
85
|
|
|
7
86
|
Object.defineProperty(exports, 'DEFAULT_GTM_HOST', {
|
|
8
|
-
|
|
9
|
-
|
|
87
|
+
enumerable: true,
|
|
88
|
+
get: function () { return gtmKit.DEFAULT_GTM_HOST; }
|
|
10
89
|
});
|
|
11
90
|
Object.defineProperty(exports, 'buildNoscriptUrl', {
|
|
12
|
-
|
|
13
|
-
|
|
91
|
+
enumerable: true,
|
|
92
|
+
get: function () { return gtmKit.buildGtmNoscriptUrl; }
|
|
14
93
|
});
|
|
15
94
|
Object.defineProperty(exports, 'buildScriptUrl', {
|
|
16
|
-
|
|
17
|
-
|
|
95
|
+
enumerable: true,
|
|
96
|
+
get: function () { return gtmKit.buildGtmScriptUrl; }
|
|
18
97
|
});
|
|
19
98
|
Object.defineProperty(exports, 'normalizeContainer', {
|
|
20
|
-
|
|
21
|
-
|
|
99
|
+
enumerable: true,
|
|
100
|
+
get: function () { return gtmKit.normalizeContainer; }
|
|
22
101
|
});
|
|
23
102
|
Object.defineProperty(exports, 'normalizeContainers', {
|
|
24
|
-
|
|
25
|
-
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function () { return gtmKit.normalizeContainers; }
|
|
26
105
|
});
|
|
27
|
-
exports.generateDataLayerScript =
|
|
28
|
-
exports.generateNoscriptTags =
|
|
29
|
-
exports.generateScriptTags =
|
|
106
|
+
exports.generateDataLayerScript = generateDataLayerScript;
|
|
107
|
+
exports.generateNoscriptTags = generateNoscriptTags;
|
|
108
|
+
exports.generateScriptTags = generateScriptTags;
|
|
30
109
|
//# sourceMappingURL=out.js.map
|
|
31
110
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["
|
|
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"]}
|
package/dist/components/index.js
CHANGED
|
@@ -1,8 +1,87 @@
|
|
|
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
|
-
|
|
4
|
+
// src/components/helpers.ts
|
|
5
|
+
var isValidJsIdentifier = (value) => {
|
|
6
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value);
|
|
7
|
+
};
|
|
8
|
+
var filterNullish = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null));
|
|
9
|
+
var generateScriptTags = (config) => {
|
|
10
|
+
const {
|
|
11
|
+
containers,
|
|
12
|
+
host = DEFAULT_GTM_HOST,
|
|
13
|
+
defaultQueryParams,
|
|
14
|
+
scriptAttributes,
|
|
15
|
+
dataLayerName = DEFAULT_DATA_LAYER_NAME
|
|
16
|
+
} = config;
|
|
17
|
+
const normalized = normalizeContainers(containers);
|
|
18
|
+
if (!normalized.length) {
|
|
19
|
+
throw new Error("At least one GTM container is required.");
|
|
20
|
+
}
|
|
21
|
+
return normalized.map((container) => {
|
|
22
|
+
if (!container.id) {
|
|
23
|
+
throw new Error("Container id is required.");
|
|
24
|
+
}
|
|
25
|
+
const params = {
|
|
26
|
+
...defaultQueryParams,
|
|
27
|
+
...container.queryParams
|
|
28
|
+
};
|
|
29
|
+
const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);
|
|
30
|
+
const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
|
|
31
|
+
return {
|
|
32
|
+
id: container.id,
|
|
33
|
+
src,
|
|
34
|
+
async: asyncAttr != null ? asyncAttr : true,
|
|
35
|
+
defer,
|
|
36
|
+
nonce,
|
|
37
|
+
attributes: filterNullish(restAttributes)
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
var generateNoscriptTags = (config) => {
|
|
42
|
+
const {
|
|
43
|
+
containers,
|
|
44
|
+
host = DEFAULT_GTM_HOST,
|
|
45
|
+
defaultQueryParams,
|
|
46
|
+
iframeAttributes,
|
|
47
|
+
dataLayerName = DEFAULT_DATA_LAYER_NAME
|
|
48
|
+
} = config;
|
|
49
|
+
const normalized = normalizeContainers(containers);
|
|
50
|
+
if (!normalized.length) {
|
|
51
|
+
throw new Error("At least one GTM container is required.");
|
|
52
|
+
}
|
|
53
|
+
return normalized.map((container) => {
|
|
54
|
+
if (!container.id) {
|
|
55
|
+
throw new Error("Container id is required.");
|
|
56
|
+
}
|
|
57
|
+
const params = {
|
|
58
|
+
...defaultQueryParams,
|
|
59
|
+
...container.queryParams
|
|
60
|
+
};
|
|
61
|
+
const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);
|
|
62
|
+
const mergedAttributes = {
|
|
63
|
+
...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,
|
|
64
|
+
...iframeAttributes
|
|
65
|
+
};
|
|
66
|
+
const attributes = Object.fromEntries(
|
|
67
|
+
Object.entries(mergedAttributes).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
id: container.id,
|
|
71
|
+
src,
|
|
72
|
+
attributes
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
var generateDataLayerScript = (dataLayerName = DEFAULT_DATA_LAYER_NAME) => {
|
|
77
|
+
if (!isValidJsIdentifier(dataLayerName)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Invalid dataLayerName: "${dataLayerName}". Must be a valid JavaScript identifier (letters, digits, $, _ only, cannot start with a digit).`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return `window.${dataLayerName}=window.${dataLayerName}||[];`;
|
|
83
|
+
};
|
|
5
84
|
|
|
6
|
-
export {
|
|
85
|
+
export { generateDataLayerScript, generateNoscriptTags, generateScriptTags };
|
|
7
86
|
//# sourceMappingURL=out.js.map
|
|
8
87
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/helpers.ts","../../src/components/index.ts"],"names":["
|
|
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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -2,38 +2,191 @@
|
|
|
2
2
|
|
|
3
3
|
var gtmKit = require('@jwiedeman/gtm-kit');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/client.ts
|
|
6
|
+
var clientInstance = null;
|
|
7
|
+
var clientConfig = null;
|
|
8
|
+
var initGtm = (options) => {
|
|
9
|
+
if (clientInstance) {
|
|
10
|
+
return clientInstance;
|
|
11
|
+
}
|
|
12
|
+
clientInstance = gtmKit.createGtmClient(options);
|
|
13
|
+
clientConfig = options;
|
|
14
|
+
clientInstance.init();
|
|
15
|
+
return clientInstance;
|
|
16
|
+
};
|
|
17
|
+
var getGtmClient = () => clientInstance;
|
|
18
|
+
var requireGtmClient = () => {
|
|
19
|
+
if (!clientInstance) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"[gtm-kit/astro] GTM client not initialized. Call initGtm() first or ensure the GTM script has loaded."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return clientInstance;
|
|
25
|
+
};
|
|
26
|
+
var getWindowDataLayer = (name) => {
|
|
27
|
+
if (typeof window === "undefined") {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
const win = window;
|
|
31
|
+
const layer = win[name];
|
|
32
|
+
return Array.isArray(layer) ? layer : void 0;
|
|
33
|
+
};
|
|
34
|
+
var push = (value) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const client = getGtmClient();
|
|
37
|
+
if (client) {
|
|
38
|
+
client.push(value);
|
|
39
|
+
return true;
|
|
40
|
+
} else if (typeof window !== "undefined") {
|
|
41
|
+
const dataLayerName = (_a = clientConfig == null ? void 0 : clientConfig.dataLayerName) != null ? _a : "dataLayer";
|
|
42
|
+
const dataLayer = getWindowDataLayer(dataLayerName);
|
|
43
|
+
if (dataLayer) {
|
|
44
|
+
dataLayer.push(value);
|
|
45
|
+
return true;
|
|
46
|
+
} else {
|
|
47
|
+
console.warn("[gtm-kit/astro] GTM not initialized and dataLayer not found.");
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
var setConsentDefaults = (state, options) => {
|
|
54
|
+
const client = getGtmClient();
|
|
55
|
+
if (client) {
|
|
56
|
+
client.setConsentDefaults(state, options);
|
|
57
|
+
return true;
|
|
58
|
+
} else {
|
|
59
|
+
console.warn("[gtm-kit/astro] GTM not initialized. Consent defaults should be set before init.");
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var updateConsent = (state, options) => {
|
|
64
|
+
const client = getGtmClient();
|
|
65
|
+
if (client) {
|
|
66
|
+
client.updateConsent(state, options);
|
|
67
|
+
return true;
|
|
68
|
+
} else {
|
|
69
|
+
console.warn("[gtm-kit/astro] GTM not initialized. Cannot update consent.");
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var whenReady = () => {
|
|
74
|
+
const client = getGtmClient();
|
|
75
|
+
if (client) {
|
|
76
|
+
return client.whenReady();
|
|
77
|
+
}
|
|
78
|
+
return Promise.reject(new Error("[gtm-kit/astro] GTM not initialized."));
|
|
79
|
+
};
|
|
80
|
+
var teardown = () => {
|
|
81
|
+
if (clientInstance) {
|
|
82
|
+
clientInstance.teardown();
|
|
83
|
+
clientInstance = null;
|
|
84
|
+
clientConfig = null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/page-tracking.ts
|
|
89
|
+
var noop = () => {
|
|
90
|
+
};
|
|
91
|
+
var getAdditionalData = (additionalData) => {
|
|
92
|
+
if (typeof additionalData === "function") {
|
|
93
|
+
return additionalData();
|
|
94
|
+
}
|
|
95
|
+
return additionalData != null ? additionalData : {};
|
|
96
|
+
};
|
|
97
|
+
var trackPageView = (options = {}) => {
|
|
98
|
+
const { eventName = "page_view", includeQueryParams = true, additionalData } = options;
|
|
99
|
+
if (typeof window === "undefined") {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const pagePath = includeQueryParams ? window.location.pathname + window.location.search : window.location.pathname;
|
|
103
|
+
const pageViewData = {
|
|
104
|
+
event: eventName,
|
|
105
|
+
page_path: pagePath,
|
|
106
|
+
page_location: window.location.href,
|
|
107
|
+
page_title: document.title,
|
|
108
|
+
...getAdditionalData(additionalData)
|
|
109
|
+
};
|
|
110
|
+
push(pageViewData);
|
|
111
|
+
};
|
|
112
|
+
var viewTransitionsSetup = false;
|
|
113
|
+
var lastTrackedPath = "";
|
|
114
|
+
var setupViewTransitions = (options = {}) => {
|
|
115
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
116
|
+
return noop;
|
|
117
|
+
}
|
|
118
|
+
if (viewTransitionsSetup) {
|
|
119
|
+
return noop;
|
|
120
|
+
}
|
|
121
|
+
viewTransitionsSetup = true;
|
|
122
|
+
lastTrackedPath = window.location.pathname + window.location.search;
|
|
123
|
+
trackPageView(options);
|
|
124
|
+
const handlePageLoad = () => {
|
|
125
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
126
|
+
if (currentPath === lastTrackedPath) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
lastTrackedPath = currentPath;
|
|
130
|
+
trackPageView(options);
|
|
131
|
+
};
|
|
132
|
+
document.addEventListener("astro:page-load", handlePageLoad);
|
|
133
|
+
return () => {
|
|
134
|
+
document.removeEventListener("astro:page-load", handlePageLoad);
|
|
135
|
+
viewTransitionsSetup = false;
|
|
136
|
+
lastTrackedPath = "";
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
var setupPageTracking = (options = {}) => {
|
|
140
|
+
if (typeof window === "undefined") {
|
|
141
|
+
return noop;
|
|
142
|
+
}
|
|
143
|
+
lastTrackedPath = window.location.pathname + window.location.search;
|
|
144
|
+
trackPageView(options);
|
|
145
|
+
const handlePopState = () => {
|
|
146
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
147
|
+
if (currentPath === lastTrackedPath) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
lastTrackedPath = currentPath;
|
|
151
|
+
trackPageView(options);
|
|
152
|
+
};
|
|
153
|
+
window.addEventListener("popstate", handlePopState);
|
|
154
|
+
return () => {
|
|
155
|
+
window.removeEventListener("popstate", handlePopState);
|
|
156
|
+
lastTrackedPath = "";
|
|
157
|
+
};
|
|
158
|
+
};
|
|
6
159
|
|
|
7
160
|
Object.defineProperty(exports, 'allGranted', {
|
|
8
|
-
|
|
9
|
-
|
|
161
|
+
enumerable: true,
|
|
162
|
+
get: function () { return gtmKit.allGranted; }
|
|
10
163
|
});
|
|
11
164
|
Object.defineProperty(exports, 'analyticsOnly', {
|
|
12
|
-
|
|
13
|
-
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function () { return gtmKit.analyticsOnly; }
|
|
14
167
|
});
|
|
15
168
|
Object.defineProperty(exports, 'consentPresets', {
|
|
16
|
-
|
|
17
|
-
|
|
169
|
+
enumerable: true,
|
|
170
|
+
get: function () { return gtmKit.consentPresets; }
|
|
18
171
|
});
|
|
19
172
|
Object.defineProperty(exports, 'eeaDefault', {
|
|
20
|
-
|
|
21
|
-
|
|
173
|
+
enumerable: true,
|
|
174
|
+
get: function () { return gtmKit.eeaDefault; }
|
|
22
175
|
});
|
|
23
176
|
Object.defineProperty(exports, 'getConsentPreset', {
|
|
24
|
-
|
|
25
|
-
|
|
177
|
+
enumerable: true,
|
|
178
|
+
get: function () { return gtmKit.getConsentPreset; }
|
|
26
179
|
});
|
|
27
|
-
exports.getGtmClient =
|
|
28
|
-
exports.initGtm =
|
|
29
|
-
exports.push =
|
|
30
|
-
exports.requireGtmClient =
|
|
31
|
-
exports.setConsentDefaults =
|
|
32
|
-
exports.setupPageTracking =
|
|
33
|
-
exports.setupViewTransitions =
|
|
34
|
-
exports.teardown =
|
|
35
|
-
exports.trackPageView =
|
|
36
|
-
exports.updateConsent =
|
|
37
|
-
exports.whenReady =
|
|
180
|
+
exports.getGtmClient = getGtmClient;
|
|
181
|
+
exports.initGtm = initGtm;
|
|
182
|
+
exports.push = push;
|
|
183
|
+
exports.requireGtmClient = requireGtmClient;
|
|
184
|
+
exports.setConsentDefaults = setConsentDefaults;
|
|
185
|
+
exports.setupPageTracking = setupPageTracking;
|
|
186
|
+
exports.setupViewTransitions = setupViewTransitions;
|
|
187
|
+
exports.teardown = teardown;
|
|
188
|
+
exports.trackPageView = trackPageView;
|
|
189
|
+
exports.updateConsent = updateConsent;
|
|
190
|
+
exports.whenReady = whenReady;
|
|
38
191
|
//# sourceMappingURL=out.js.map
|
|
39
192
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/page-tracking.ts","../src/index.ts"],"names":["createGtmClient","clientInstance","clientConfig","initGtm","options","getGtmClient","requireGtmClient","push","value","_a","client","dataLayerName","dataLayer","setConsentDefaults","state","updateConsent","whenReady","teardown","noop","getAdditionalData","additionalData","trackPageView","eventName","includeQueryParams","pagePath","pageViewData","viewTransitionsSetup","lastTrackedPath","setupViewTransitions","handlePageLoad","currentPath","setupPageTracking","handlePopState","consentPresets","getConsentPreset","eeaDefault","allGranted","analyticsOnly"],"mappings":"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","sourcesContent":["import {\n createGtmClient,\n type ConsentRegionOptions,\n type ConsentState,\n type CreateGtmClientOptions,\n type DataLayerValue,\n type GtmClient,\n type ScriptLoadState\n} from '@jwiedeman/gtm-kit';\n\nlet clientInstance: GtmClient | null = null;\nlet clientConfig: CreateGtmClientOptions | null = null;\n\n/**\n * Initialize the GTM client for use in Astro.\n * Should be called once when the page loads.\n *\n * @example\n * ```ts\n * // In a client-side script\n * import { initGtm } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * ```\n */\nexport const initGtm = (options: CreateGtmClientOptions): GtmClient => {\n // If already initialized, return existing instance\n // (common in Astro with view transitions where initGtm may be called multiple times)\n if (clientInstance) {\n return clientInstance;\n }\n\n clientInstance = createGtmClient(options);\n clientConfig = options;\n clientInstance.init();\n\n return clientInstance;\n};\n\n/**\n * Get the current GTM client instance.\n * Returns null if not initialized.\n *\n * @example\n * ```ts\n * import { getGtmClient } from '@jwiedeman/gtm-kit-astro';\n *\n * const client = getGtmClient();\n * if (client) {\n * client.push({ event: 'page_view' });\n * }\n * ```\n */\nexport const getGtmClient = (): GtmClient | null => clientInstance;\n\n/**\n * Get the GTM client or throw if not initialized.\n * Use this when you expect the client to be available.\n *\n * @throws Error if GTM client is not initialized\n */\nexport const requireGtmClient = (): GtmClient => {\n if (!clientInstance) {\n throw new Error(\n '[gtm-kit/astro] GTM client not initialized. Call initGtm() first or ensure the GTM script has loaded.'\n );\n }\n return clientInstance;\n};\n\n/**\n * Push a value to the GTM dataLayer.\n * This is a convenience function that handles the case where GTM isn't initialized.\n *\n * @returns true if the value was pushed successfully, false otherwise\n *\n * @example\n * ```ts\n * import { push } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = push({ event: 'button_click', button_name: 'hero_cta' });\n * if (!success) {\n * console.warn('GTM not ready');\n * }\n * ```\n */\nexport const push = (value: DataLayerValue): boolean => {\n const client = getGtmClient();\n if (client) {\n client.push(value);\n return true;\n } else if (typeof window !== 'undefined') {\n // Fallback: push directly to dataLayer if it exists\n const dataLayerName = clientConfig?.dataLayerName ?? 'dataLayer';\n const win = window as unknown as Record<string, unknown>;\n const dataLayer = win[dataLayerName] as DataLayerValue[] | undefined;\n if (Array.isArray(dataLayer)) {\n dataLayer.push(value);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized and dataLayer not found.');\n return false;\n }\n }\n return false;\n};\n\n/**\n * Set consent defaults (must be called before GTM loads).\n *\n * @returns true if consent defaults were set, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { setConsentDefaults } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = setConsentDefaults({\n * ad_storage: 'denied',\n * analytics_storage: 'denied'\n * });\n * ```\n */\nexport const setConsentDefaults = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.setConsentDefaults(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Consent defaults should be set before init.');\n return false;\n }\n};\n\n/**\n * Update consent state after user interaction.\n *\n * @returns true if consent was updated, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { updateConsent } from '@jwiedeman/gtm-kit-astro';\n *\n * // When user accepts cookies\n * const success = updateConsent({\n * ad_storage: 'granted',\n * analytics_storage: 'granted',\n * ad_user_data: 'granted',\n * ad_personalization: 'granted'\n * });\n * ```\n */\nexport const updateConsent = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.updateConsent(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Cannot update consent.');\n return false;\n }\n};\n\n/**\n * Wait for GTM scripts to be ready.\n *\n * @example\n * ```ts\n * import { whenReady } from '@jwiedeman/gtm-kit-astro';\n *\n * whenReady().then((states) => {\n * console.log('GTM loaded:', states);\n * });\n * ```\n */\nexport const whenReady = (): Promise<ScriptLoadState[]> => {\n const client = getGtmClient();\n if (client) {\n return client.whenReady();\n }\n return Promise.reject(new Error('[gtm-kit/astro] GTM not initialized.'));\n};\n\n/**\n * Clean up the GTM client.\n * Call this when navigating away or cleaning up.\n */\nexport const teardown = (): void => {\n if (clientInstance) {\n clientInstance.teardown();\n clientInstance = null;\n clientConfig = null;\n }\n};\n","import { push } from './client';\n\n// No-op function for SSR/cleanup returns\nconst noop = (): void => {\n /* no-op */\n};\n\nexport interface PageViewData {\n event?: string;\n page_path?: string;\n page_location?: string;\n page_title?: string;\n page_referrer?: string;\n [key: string]: unknown;\n}\n\nexport interface TrackPageViewOptions {\n /**\n * The event name to use for page views.\n * @default 'page_view'\n */\n eventName?: string;\n\n /**\n * Whether to include query parameters in the page path.\n * @default true\n */\n includeQueryParams?: boolean;\n\n /**\n * Additional data to include with each page view.\n */\n additionalData?: Record<string, unknown> | (() => Record<string, unknown>);\n}\n\nconst getAdditionalData = (additionalData: TrackPageViewOptions['additionalData']): Record<string, unknown> => {\n if (typeof additionalData === 'function') {\n return additionalData();\n }\n return additionalData ?? {};\n};\n\n/**\n * Track a page view event.\n *\n * @example\n * ```ts\n * import { trackPageView } from '@jwiedeman/gtm-kit-astro';\n *\n * // Track current page\n * trackPageView();\n *\n * // Track with custom data\n * trackPageView({\n * additionalData: { user_type: 'guest' }\n * });\n * ```\n */\nexport const trackPageView = (options: TrackPageViewOptions = {}): void => {\n const { eventName = 'page_view', includeQueryParams = true, additionalData } = options;\n\n if (typeof window === 'undefined') {\n return;\n }\n\n const pagePath = includeQueryParams ? window.location.pathname + window.location.search : window.location.pathname;\n\n const pageViewData: PageViewData = {\n event: eventName,\n page_path: pagePath,\n page_location: window.location.href,\n page_title: document.title,\n ...getAdditionalData(additionalData)\n };\n\n push(pageViewData);\n};\n\nlet viewTransitionsSetup = false;\nlet lastTrackedPath = '';\n\n/**\n * Reset page tracking state (for testing only).\n * @internal\n */\nexport const _resetPageTrackingState = (): void => {\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n};\n\n/**\n * Set up automatic page view tracking with Astro View Transitions.\n * Call this once when the page loads.\n *\n * @example\n * ```astro\n * ---\n * // src/layouts/Layout.astro\n * ---\n * <script>\n * import { initGtm, setupViewTransitions } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * setupViewTransitions();\n * </script>\n * ```\n */\nexport const setupViewTransitions = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return noop;\n }\n\n // Prevent duplicate setup\n if (viewTransitionsSetup) {\n return noop;\n }\n\n viewTransitionsSetup = true;\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle Astro View Transitions\n // The 'astro:page-load' event fires after every page load, including View Transitions\n const handlePageLoad = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n // Avoid duplicate tracking for the same path\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n document.addEventListener('astro:page-load', handlePageLoad);\n\n // Cleanup function\n return () => {\n document.removeEventListener('astro:page-load', handlePageLoad);\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n };\n};\n\n/**\n * Set up page view tracking for standard navigation (no View Transitions).\n * Tracks on popstate (back/forward) and initial load.\n *\n * @example\n * ```ts\n * import { setupPageTracking } from '@jwiedeman/gtm-kit-astro';\n *\n * setupPageTracking();\n * ```\n */\nexport const setupPageTracking = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined') {\n return noop;\n }\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle browser back/forward navigation\n const handlePopState = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n window.addEventListener('popstate', handlePopState);\n\n return () => {\n window.removeEventListener('popstate', handlePopState);\n lastTrackedPath = '';\n };\n};\n","// Client-side GTM management\nexport {\n initGtm,\n getGtmClient,\n requireGtmClient,\n push,\n setConsentDefaults,\n updateConsent,\n whenReady,\n teardown\n} from './client';\n\n// Page tracking\nexport { trackPageView, setupViewTransitions, setupPageTracking } from './page-tracking';\nexport type { PageViewData, TrackPageViewOptions } from './page-tracking';\n\n// Re-export core types for convenience\nexport type {\n ConsentState,\n ConsentRegionOptions,\n CreateGtmClientOptions,\n DataLayerValue,\n GtmClient,\n ScriptLoadState,\n ContainerConfigInput,\n ContainerDescriptor,\n ScriptAttributes\n} from '@jwiedeman/gtm-kit';\n\n// Re-export consent helpers\nexport { consentPresets, getConsentPreset, eeaDefault, allGranted, analyticsOnly } from '@jwiedeman/gtm-kit';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/page-tracking.ts","../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createGtmClient,\n type ConsentRegionOptions,\n type ConsentState,\n type CreateGtmClientOptions,\n type DataLayerValue,\n type GtmClient,\n type ScriptLoadState\n} from '@jwiedeman/gtm-kit';\n\nlet clientInstance: GtmClient | null = null;\nlet clientConfig: CreateGtmClientOptions | null = null;\n\n/**\n * Initialize the GTM client for use in Astro.\n * Should be called once when the page loads.\n *\n * @example\n * ```ts\n * // In a client-side script\n * import { initGtm } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * ```\n */\nexport const initGtm = (options: CreateGtmClientOptions): GtmClient => {\n // If already initialized, return existing instance\n // (common in Astro with view transitions where initGtm may be called multiple times)\n if (clientInstance) {\n return clientInstance;\n }\n\n clientInstance = createGtmClient(options);\n clientConfig = options;\n clientInstance.init();\n\n return clientInstance;\n};\n\n/**\n * Get the current GTM client instance.\n * Returns null if not initialized.\n *\n * @example\n * ```ts\n * import { getGtmClient } from '@jwiedeman/gtm-kit-astro';\n *\n * const client = getGtmClient();\n * if (client) {\n * client.push({ event: 'page_view' });\n * }\n * ```\n */\nexport const getGtmClient = (): GtmClient | null => clientInstance;\n\n/**\n * Get the GTM client or throw if not initialized.\n * Use this when you expect the client to be available.\n *\n * @throws Error if GTM client is not initialized\n */\nexport const requireGtmClient = (): GtmClient => {\n if (!clientInstance) {\n throw new Error(\n '[gtm-kit/astro] GTM client not initialized. Call initGtm() first or ensure the GTM script has loaded.'\n );\n }\n return clientInstance;\n};\n\n/**\n * Push a value to the GTM dataLayer.\n * This is a convenience function that handles the case where GTM isn't initialized.\n *\n * @returns true if the value was pushed successfully, false otherwise\n *\n * @example\n * ```ts\n * import { push } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = push({ event: 'button_click', button_name: 'hero_cta' });\n * if (!success) {\n * console.warn('GTM not ready');\n * }\n * ```\n */\n/**\n * Type-safe access to window properties for dataLayer.\n */\nconst getWindowDataLayer = (name: string): DataLayerValue[] | undefined => {\n if (typeof window === 'undefined') {\n return undefined;\n }\n const win = window as typeof window & Record<string, unknown>;\n const layer = win[name];\n return Array.isArray(layer) ? (layer as DataLayerValue[]) : undefined;\n};\n\nexport const push = (value: DataLayerValue): boolean => {\n const client = getGtmClient();\n if (client) {\n client.push(value);\n return true;\n } else if (typeof window !== 'undefined') {\n // Fallback: push directly to dataLayer if it exists\n const dataLayerName = clientConfig?.dataLayerName ?? 'dataLayer';\n const dataLayer = getWindowDataLayer(dataLayerName);\n if (dataLayer) {\n dataLayer.push(value);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized and dataLayer not found.');\n return false;\n }\n }\n return false;\n};\n\n/**\n * Set consent defaults (must be called before GTM loads).\n *\n * @returns true if consent defaults were set, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { setConsentDefaults } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = setConsentDefaults({\n * ad_storage: 'denied',\n * analytics_storage: 'denied'\n * });\n * ```\n */\nexport const setConsentDefaults = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.setConsentDefaults(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Consent defaults should be set before init.');\n return false;\n }\n};\n\n/**\n * Update consent state after user interaction.\n *\n * @returns true if consent was updated, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { updateConsent } from '@jwiedeman/gtm-kit-astro';\n *\n * // When user accepts cookies\n * const success = updateConsent({\n * ad_storage: 'granted',\n * analytics_storage: 'granted',\n * ad_user_data: 'granted',\n * ad_personalization: 'granted'\n * });\n * ```\n */\nexport const updateConsent = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.updateConsent(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Cannot update consent.');\n return false;\n }\n};\n\n/**\n * Wait for GTM scripts to be ready.\n *\n * @example\n * ```ts\n * import { whenReady } from '@jwiedeman/gtm-kit-astro';\n *\n * whenReady().then((states) => {\n * console.log('GTM loaded:', states);\n * });\n * ```\n */\nexport const whenReady = (): Promise<ScriptLoadState[]> => {\n const client = getGtmClient();\n if (client) {\n return client.whenReady();\n }\n return Promise.reject(new Error('[gtm-kit/astro] GTM not initialized.'));\n};\n\n/**\n * Clean up the GTM client.\n * Call this when navigating away or cleaning up.\n */\nexport const teardown = (): void => {\n if (clientInstance) {\n clientInstance.teardown();\n clientInstance = null;\n clientConfig = null;\n }\n};\n","import { push } from './client';\n\n// No-op function for SSR/cleanup returns\nconst noop = (): void => {\n /* no-op */\n};\n\nexport interface PageViewData {\n event?: string;\n page_path?: string;\n page_location?: string;\n page_title?: string;\n page_referrer?: string;\n [key: string]: unknown;\n}\n\nexport interface TrackPageViewOptions {\n /**\n * The event name to use for page views.\n * @default 'page_view'\n */\n eventName?: string;\n\n /**\n * Whether to include query parameters in the page path.\n * @default true\n */\n includeQueryParams?: boolean;\n\n /**\n * Additional data to include with each page view.\n */\n additionalData?: Record<string, unknown> | (() => Record<string, unknown>);\n}\n\nconst getAdditionalData = (additionalData: TrackPageViewOptions['additionalData']): Record<string, unknown> => {\n if (typeof additionalData === 'function') {\n return additionalData();\n }\n return additionalData ?? {};\n};\n\n/**\n * Track a page view event.\n *\n * @example\n * ```ts\n * import { trackPageView } from '@jwiedeman/gtm-kit-astro';\n *\n * // Track current page\n * trackPageView();\n *\n * // Track with custom data\n * trackPageView({\n * additionalData: { user_type: 'guest' }\n * });\n * ```\n */\nexport const trackPageView = (options: TrackPageViewOptions = {}): void => {\n const { eventName = 'page_view', includeQueryParams = true, additionalData } = options;\n\n if (typeof window === 'undefined') {\n return;\n }\n\n const pagePath = includeQueryParams ? window.location.pathname + window.location.search : window.location.pathname;\n\n const pageViewData: PageViewData = {\n event: eventName,\n page_path: pagePath,\n page_location: window.location.href,\n page_title: document.title,\n ...getAdditionalData(additionalData)\n };\n\n push(pageViewData);\n};\n\nlet viewTransitionsSetup = false;\nlet lastTrackedPath = '';\n\n/**\n * Reset page tracking state (for testing only).\n * @internal\n */\nexport const _resetPageTrackingState = (): void => {\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n};\n\n/**\n * Set up automatic page view tracking with Astro View Transitions.\n * Call this once when the page loads.\n *\n * @example\n * ```astro\n * ---\n * // src/layouts/Layout.astro\n * ---\n * <script>\n * import { initGtm, setupViewTransitions } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * setupViewTransitions();\n * </script>\n * ```\n */\nexport const setupViewTransitions = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return noop;\n }\n\n // Prevent duplicate setup\n if (viewTransitionsSetup) {\n return noop;\n }\n\n viewTransitionsSetup = true;\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle Astro View Transitions\n // The 'astro:page-load' event fires after every page load, including View Transitions\n const handlePageLoad = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n // Avoid duplicate tracking for the same path\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n document.addEventListener('astro:page-load', handlePageLoad);\n\n // Cleanup function\n return () => {\n document.removeEventListener('astro:page-load', handlePageLoad);\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n };\n};\n\n/**\n * Set up page view tracking for standard navigation (no View Transitions).\n * Tracks on popstate (back/forward) and initial load.\n *\n * @example\n * ```ts\n * import { setupPageTracking } from '@jwiedeman/gtm-kit-astro';\n *\n * setupPageTracking();\n * ```\n */\nexport const setupPageTracking = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined') {\n return noop;\n }\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle browser back/forward navigation\n const handlePopState = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n window.addEventListener('popstate', handlePopState);\n\n return () => {\n window.removeEventListener('popstate', handlePopState);\n lastTrackedPath = '';\n };\n};\n","// Client-side GTM management\nexport {\n initGtm,\n getGtmClient,\n requireGtmClient,\n push,\n setConsentDefaults,\n updateConsent,\n whenReady,\n teardown\n} from './client';\n\n// Page tracking\nexport { trackPageView, setupViewTransitions, setupPageTracking } from './page-tracking';\nexport type { PageViewData, TrackPageViewOptions } from './page-tracking';\n\n// Re-export core types for convenience\nexport type {\n ConsentState,\n ConsentRegionOptions,\n CreateGtmClientOptions,\n DataLayerValue,\n GtmClient,\n ScriptLoadState,\n ContainerConfigInput,\n ContainerDescriptor,\n ScriptAttributes\n} from '@jwiedeman/gtm-kit';\n\n// Re-export consent helpers\nexport { consentPresets, getConsentPreset, eeaDefault, allGranted, analyticsOnly } from '@jwiedeman/gtm-kit';\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -36,22 +36,6 @@ declare const getGtmClient: () => GtmClient | null;
|
|
|
36
36
|
* @throws Error if GTM client is not initialized
|
|
37
37
|
*/
|
|
38
38
|
declare const requireGtmClient: () => GtmClient;
|
|
39
|
-
/**
|
|
40
|
-
* Push a value to the GTM dataLayer.
|
|
41
|
-
* This is a convenience function that handles the case where GTM isn't initialized.
|
|
42
|
-
*
|
|
43
|
-
* @returns true if the value was pushed successfully, false otherwise
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* import { push } from '@jwiedeman/gtm-kit-astro';
|
|
48
|
-
*
|
|
49
|
-
* const success = push({ event: 'button_click', button_name: 'hero_cta' });
|
|
50
|
-
* if (!success) {
|
|
51
|
-
* console.warn('GTM not ready');
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
39
|
declare const push: (value: DataLayerValue) => boolean;
|
|
56
40
|
/**
|
|
57
41
|
* Set consent defaults (must be called before GTM loads).
|
package/dist/index.d.ts
CHANGED
|
@@ -36,22 +36,6 @@ declare const getGtmClient: () => GtmClient | null;
|
|
|
36
36
|
* @throws Error if GTM client is not initialized
|
|
37
37
|
*/
|
|
38
38
|
declare const requireGtmClient: () => GtmClient;
|
|
39
|
-
/**
|
|
40
|
-
* Push a value to the GTM dataLayer.
|
|
41
|
-
* This is a convenience function that handles the case where GTM isn't initialized.
|
|
42
|
-
*
|
|
43
|
-
* @returns true if the value was pushed successfully, false otherwise
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* import { push } from '@jwiedeman/gtm-kit-astro';
|
|
48
|
-
*
|
|
49
|
-
* const success = push({ event: 'button_click', button_name: 'hero_cta' });
|
|
50
|
-
* if (!success) {
|
|
51
|
-
* console.warn('GTM not ready');
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
39
|
declare const push: (value: DataLayerValue) => boolean;
|
|
56
40
|
/**
|
|
57
41
|
* Set consent defaults (must be called before GTM loads).
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,161 @@
|
|
|
1
1
|
import { createGtmClient } from '@jwiedeman/gtm-kit';
|
|
2
2
|
export { allGranted, analyticsOnly, consentPresets, eeaDefault, getConsentPreset } from '@jwiedeman/gtm-kit';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/client.ts
|
|
5
|
+
var clientInstance = null;
|
|
6
|
+
var clientConfig = null;
|
|
7
|
+
var initGtm = (options) => {
|
|
8
|
+
if (clientInstance) {
|
|
9
|
+
return clientInstance;
|
|
10
|
+
}
|
|
11
|
+
clientInstance = createGtmClient(options);
|
|
12
|
+
clientConfig = options;
|
|
13
|
+
clientInstance.init();
|
|
14
|
+
return clientInstance;
|
|
15
|
+
};
|
|
16
|
+
var getGtmClient = () => clientInstance;
|
|
17
|
+
var requireGtmClient = () => {
|
|
18
|
+
if (!clientInstance) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"[gtm-kit/astro] GTM client not initialized. Call initGtm() first or ensure the GTM script has loaded."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return clientInstance;
|
|
24
|
+
};
|
|
25
|
+
var getWindowDataLayer = (name) => {
|
|
26
|
+
if (typeof window === "undefined") {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
const win = window;
|
|
30
|
+
const layer = win[name];
|
|
31
|
+
return Array.isArray(layer) ? layer : void 0;
|
|
32
|
+
};
|
|
33
|
+
var push = (value) => {
|
|
34
|
+
var _a;
|
|
35
|
+
const client = getGtmClient();
|
|
36
|
+
if (client) {
|
|
37
|
+
client.push(value);
|
|
38
|
+
return true;
|
|
39
|
+
} else if (typeof window !== "undefined") {
|
|
40
|
+
const dataLayerName = (_a = clientConfig == null ? void 0 : clientConfig.dataLayerName) != null ? _a : "dataLayer";
|
|
41
|
+
const dataLayer = getWindowDataLayer(dataLayerName);
|
|
42
|
+
if (dataLayer) {
|
|
43
|
+
dataLayer.push(value);
|
|
44
|
+
return true;
|
|
45
|
+
} else {
|
|
46
|
+
console.warn("[gtm-kit/astro] GTM not initialized and dataLayer not found.");
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
};
|
|
52
|
+
var setConsentDefaults = (state, options) => {
|
|
53
|
+
const client = getGtmClient();
|
|
54
|
+
if (client) {
|
|
55
|
+
client.setConsentDefaults(state, options);
|
|
56
|
+
return true;
|
|
57
|
+
} else {
|
|
58
|
+
console.warn("[gtm-kit/astro] GTM not initialized. Consent defaults should be set before init.");
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var updateConsent = (state, options) => {
|
|
63
|
+
const client = getGtmClient();
|
|
64
|
+
if (client) {
|
|
65
|
+
client.updateConsent(state, options);
|
|
66
|
+
return true;
|
|
67
|
+
} else {
|
|
68
|
+
console.warn("[gtm-kit/astro] GTM not initialized. Cannot update consent.");
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var whenReady = () => {
|
|
73
|
+
const client = getGtmClient();
|
|
74
|
+
if (client) {
|
|
75
|
+
return client.whenReady();
|
|
76
|
+
}
|
|
77
|
+
return Promise.reject(new Error("[gtm-kit/astro] GTM not initialized."));
|
|
78
|
+
};
|
|
79
|
+
var teardown = () => {
|
|
80
|
+
if (clientInstance) {
|
|
81
|
+
clientInstance.teardown();
|
|
82
|
+
clientInstance = null;
|
|
83
|
+
clientConfig = null;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
5
86
|
|
|
6
|
-
|
|
87
|
+
// src/page-tracking.ts
|
|
88
|
+
var noop = () => {
|
|
89
|
+
};
|
|
90
|
+
var getAdditionalData = (additionalData) => {
|
|
91
|
+
if (typeof additionalData === "function") {
|
|
92
|
+
return additionalData();
|
|
93
|
+
}
|
|
94
|
+
return additionalData != null ? additionalData : {};
|
|
95
|
+
};
|
|
96
|
+
var trackPageView = (options = {}) => {
|
|
97
|
+
const { eventName = "page_view", includeQueryParams = true, additionalData } = options;
|
|
98
|
+
if (typeof window === "undefined") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const pagePath = includeQueryParams ? window.location.pathname + window.location.search : window.location.pathname;
|
|
102
|
+
const pageViewData = {
|
|
103
|
+
event: eventName,
|
|
104
|
+
page_path: pagePath,
|
|
105
|
+
page_location: window.location.href,
|
|
106
|
+
page_title: document.title,
|
|
107
|
+
...getAdditionalData(additionalData)
|
|
108
|
+
};
|
|
109
|
+
push(pageViewData);
|
|
110
|
+
};
|
|
111
|
+
var viewTransitionsSetup = false;
|
|
112
|
+
var lastTrackedPath = "";
|
|
113
|
+
var setupViewTransitions = (options = {}) => {
|
|
114
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
115
|
+
return noop;
|
|
116
|
+
}
|
|
117
|
+
if (viewTransitionsSetup) {
|
|
118
|
+
return noop;
|
|
119
|
+
}
|
|
120
|
+
viewTransitionsSetup = true;
|
|
121
|
+
lastTrackedPath = window.location.pathname + window.location.search;
|
|
122
|
+
trackPageView(options);
|
|
123
|
+
const handlePageLoad = () => {
|
|
124
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
125
|
+
if (currentPath === lastTrackedPath) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
lastTrackedPath = currentPath;
|
|
129
|
+
trackPageView(options);
|
|
130
|
+
};
|
|
131
|
+
document.addEventListener("astro:page-load", handlePageLoad);
|
|
132
|
+
return () => {
|
|
133
|
+
document.removeEventListener("astro:page-load", handlePageLoad);
|
|
134
|
+
viewTransitionsSetup = false;
|
|
135
|
+
lastTrackedPath = "";
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
var setupPageTracking = (options = {}) => {
|
|
139
|
+
if (typeof window === "undefined") {
|
|
140
|
+
return noop;
|
|
141
|
+
}
|
|
142
|
+
lastTrackedPath = window.location.pathname + window.location.search;
|
|
143
|
+
trackPageView(options);
|
|
144
|
+
const handlePopState = () => {
|
|
145
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
146
|
+
if (currentPath === lastTrackedPath) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
lastTrackedPath = currentPath;
|
|
150
|
+
trackPageView(options);
|
|
151
|
+
};
|
|
152
|
+
window.addEventListener("popstate", handlePopState);
|
|
153
|
+
return () => {
|
|
154
|
+
window.removeEventListener("popstate", handlePopState);
|
|
155
|
+
lastTrackedPath = "";
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export { getGtmClient, initGtm, push, requireGtmClient, setConsentDefaults, setupPageTracking, setupViewTransitions, teardown, trackPageView, updateConsent, whenReady };
|
|
7
160
|
//# sourceMappingURL=out.js.map
|
|
8
161
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/page-tracking.ts","../src/index.ts"],"names":["createGtmClient","clientInstance","clientConfig","initGtm","options","getGtmClient","requireGtmClient","push","value","_a","client","dataLayerName","dataLayer","setConsentDefaults","state","updateConsent","whenReady","teardown","noop","getAdditionalData","additionalData","trackPageView","eventName","includeQueryParams","pagePath","pageViewData","viewTransitionsSetup","lastTrackedPath","setupViewTransitions","handlePageLoad","currentPath","setupPageTracking","handlePopState","consentPresets","getConsentPreset","eeaDefault","allGranted","analyticsOnly"],"mappings":"AAAA,OACE,mBAAAA,MAOK,qBAEP,IAAIC,EAAmC,KACnCC,EAA8C,KAcrCC,EAAWC,GAGlBH,IAIJA,EAAiBD,EAAgBI,CAAO,EACxCF,EAAeE,EACfH,EAAe,KAAK,EAEbA,GAiBII,EAAe,IAAwBJ,EAQvCK,EAAmB,IAAiB,CAC/C,GAAI,CAACL,EACH,MAAM,IAAI,MACR,uGACF,EAEF,OAAOA,CACT,EAkBaM,EAAQC,GAAmC,CAtFxD,IAAAC,EAuFE,IAAMC,EAASL,EAAa,EAC5B,GAAIK,EACF,OAAAA,EAAO,KAAKF,CAAK,EACV,GACF,GAAI,OAAO,QAAW,YAAa,CAExC,IAAMG,GAAgBF,EAAAP,GAAA,YAAAA,EAAc,gBAAd,KAAAO,EAA+B,YAE/CG,EADM,OACUD,CAAa,EACnC,OAAI,MAAM,QAAQC,CAAS,GACzBA,EAAU,KAAKJ,CAAK,EACb,KAEP,QAAQ,KAAK,8DAA8D,EACpE,GAEX,CACA,MAAO,EACT,EAiBaK,EAAqB,CAACC,EAAqBV,IAA4C,CAClG,IAAMM,EAASL,EAAa,EAC5B,OAAIK,GACFA,EAAO,mBAAmBI,EAAOV,CAAO,EACjC,KAEP,QAAQ,KAAK,kFAAkF,EACxF,GAEX,EAoBaW,EAAgB,CAACD,EAAqBV,IAA4C,CAC7F,IAAMM,EAASL,EAAa,EAC5B,OAAIK,GACFA,EAAO,cAAcI,EAAOV,CAAO,EAC5B,KAEP,QAAQ,KAAK,6DAA6D,EACnE,GAEX,EAcaY,EAAY,IAAkC,CACzD,IAAMN,EAASL,EAAa,EAC5B,OAAIK,EACKA,EAAO,UAAU,EAEnB,QAAQ,OAAO,IAAI,MAAM,sCAAsC,CAAC,CACzE,EAMaO,EAAW,IAAY,CAC9BhB,IACFA,EAAe,SAAS,EACxBA,EAAiB,KACjBC,EAAe,KAEnB,EC7LA,IAAMgB,EAAO,IAAY,CAEzB,EA8BMC,EAAqBC,GACrB,OAAOA,GAAmB,WACrBA,EAAe,EAEjBA,GAAA,KAAAA,EAAkB,CAAC,EAmBfC,EAAgB,CAACjB,EAAgC,CAAC,IAAY,CACzE,GAAM,CAAE,UAAAkB,EAAY,YAAa,mBAAAC,EAAqB,GAAM,eAAAH,CAAe,EAAIhB,EAE/E,GAAI,OAAO,QAAW,YACpB,OAGF,IAAMoB,EAAWD,EAAqB,OAAO,SAAS,SAAW,OAAO,SAAS,OAAS,OAAO,SAAS,SAEpGE,EAA6B,CACjC,MAAOH,EACP,UAAWE,EACX,cAAe,OAAO,SAAS,KAC/B,WAAY,SAAS,MACrB,GAAGL,EAAkBC,CAAc,CACrC,EAEAb,EAAKkB,CAAY,CACnB,EAEIC,EAAuB,GACvBC,EAAkB,GA4Bf,IAAMC,EAAuB,CAACxB,EAAgC,CAAC,IAAoB,CAMxF,GALI,OAAO,QAAW,aAAe,OAAO,UAAa,aAKrDsB,EACF,OAAOR,EAGTQ,EAAuB,GAGvBC,EAAkB,OAAO,SAAS,SAAW,OAAO,SAAS,OAC7DN,EAAcjB,CAAO,EAIrB,IAAMyB,EAAiB,IAAM,CAC3B,IAAMC,EAAc,OAAO,SAAS,SAAW,OAAO,SAAS,OAG3DA,IAAgBH,IAIpBA,EAAkBG,EAClBT,EAAcjB,CAAO,EACvB,EAEA,gBAAS,iBAAiB,kBAAmByB,CAAc,EAGpD,IAAM,CACX,SAAS,oBAAoB,kBAAmBA,CAAc,EAC9DH,EAAuB,GACvBC,EAAkB,EACpB,CACF,EAaaI,EAAoB,CAAC3B,EAAgC,CAAC,IAAoB,CACrF,GAAI,OAAO,QAAW,YACpB,OAAOc,EAITS,EAAkB,OAAO,SAAS,SAAW,OAAO,SAAS,OAC7DN,EAAcjB,CAAO,EAGrB,IAAM4B,EAAiB,IAAM,CAC3B,IAAMF,EAAc,OAAO,SAAS,SAAW,OAAO,SAAS,OAE3DA,IAAgBH,IAIpBA,EAAkBG,EAClBT,EAAcjB,CAAO,EACvB,EAEA,cAAO,iBAAiB,WAAY4B,CAAc,EAE3C,IAAM,CACX,OAAO,oBAAoB,WAAYA,CAAc,EACrDL,EAAkB,EACpB,CACF,EC3JA,OAAS,kBAAAM,EAAgB,oBAAAC,EAAkB,cAAAC,EAAY,cAAAC,EAAY,iBAAAC,MAAqB","sourcesContent":["import {\n createGtmClient,\n type ConsentRegionOptions,\n type ConsentState,\n type CreateGtmClientOptions,\n type DataLayerValue,\n type GtmClient,\n type ScriptLoadState\n} from '@jwiedeman/gtm-kit';\n\nlet clientInstance: GtmClient | null = null;\nlet clientConfig: CreateGtmClientOptions | null = null;\n\n/**\n * Initialize the GTM client for use in Astro.\n * Should be called once when the page loads.\n *\n * @example\n * ```ts\n * // In a client-side script\n * import { initGtm } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * ```\n */\nexport const initGtm = (options: CreateGtmClientOptions): GtmClient => {\n // If already initialized, return existing instance\n // (common in Astro with view transitions where initGtm may be called multiple times)\n if (clientInstance) {\n return clientInstance;\n }\n\n clientInstance = createGtmClient(options);\n clientConfig = options;\n clientInstance.init();\n\n return clientInstance;\n};\n\n/**\n * Get the current GTM client instance.\n * Returns null if not initialized.\n *\n * @example\n * ```ts\n * import { getGtmClient } from '@jwiedeman/gtm-kit-astro';\n *\n * const client = getGtmClient();\n * if (client) {\n * client.push({ event: 'page_view' });\n * }\n * ```\n */\nexport const getGtmClient = (): GtmClient | null => clientInstance;\n\n/**\n * Get the GTM client or throw if not initialized.\n * Use this when you expect the client to be available.\n *\n * @throws Error if GTM client is not initialized\n */\nexport const requireGtmClient = (): GtmClient => {\n if (!clientInstance) {\n throw new Error(\n '[gtm-kit/astro] GTM client not initialized. Call initGtm() first or ensure the GTM script has loaded.'\n );\n }\n return clientInstance;\n};\n\n/**\n * Push a value to the GTM dataLayer.\n * This is a convenience function that handles the case where GTM isn't initialized.\n *\n * @returns true if the value was pushed successfully, false otherwise\n *\n * @example\n * ```ts\n * import { push } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = push({ event: 'button_click', button_name: 'hero_cta' });\n * if (!success) {\n * console.warn('GTM not ready');\n * }\n * ```\n */\nexport const push = (value: DataLayerValue): boolean => {\n const client = getGtmClient();\n if (client) {\n client.push(value);\n return true;\n } else if (typeof window !== 'undefined') {\n // Fallback: push directly to dataLayer if it exists\n const dataLayerName = clientConfig?.dataLayerName ?? 'dataLayer';\n const win = window as unknown as Record<string, unknown>;\n const dataLayer = win[dataLayerName] as DataLayerValue[] | undefined;\n if (Array.isArray(dataLayer)) {\n dataLayer.push(value);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized and dataLayer not found.');\n return false;\n }\n }\n return false;\n};\n\n/**\n * Set consent defaults (must be called before GTM loads).\n *\n * @returns true if consent defaults were set, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { setConsentDefaults } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = setConsentDefaults({\n * ad_storage: 'denied',\n * analytics_storage: 'denied'\n * });\n * ```\n */\nexport const setConsentDefaults = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.setConsentDefaults(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Consent defaults should be set before init.');\n return false;\n }\n};\n\n/**\n * Update consent state after user interaction.\n *\n * @returns true if consent was updated, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { updateConsent } from '@jwiedeman/gtm-kit-astro';\n *\n * // When user accepts cookies\n * const success = updateConsent({\n * ad_storage: 'granted',\n * analytics_storage: 'granted',\n * ad_user_data: 'granted',\n * ad_personalization: 'granted'\n * });\n * ```\n */\nexport const updateConsent = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.updateConsent(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Cannot update consent.');\n return false;\n }\n};\n\n/**\n * Wait for GTM scripts to be ready.\n *\n * @example\n * ```ts\n * import { whenReady } from '@jwiedeman/gtm-kit-astro';\n *\n * whenReady().then((states) => {\n * console.log('GTM loaded:', states);\n * });\n * ```\n */\nexport const whenReady = (): Promise<ScriptLoadState[]> => {\n const client = getGtmClient();\n if (client) {\n return client.whenReady();\n }\n return Promise.reject(new Error('[gtm-kit/astro] GTM not initialized.'));\n};\n\n/**\n * Clean up the GTM client.\n * Call this when navigating away or cleaning up.\n */\nexport const teardown = (): void => {\n if (clientInstance) {\n clientInstance.teardown();\n clientInstance = null;\n clientConfig = null;\n }\n};\n","import { push } from './client';\n\n// No-op function for SSR/cleanup returns\nconst noop = (): void => {\n /* no-op */\n};\n\nexport interface PageViewData {\n event?: string;\n page_path?: string;\n page_location?: string;\n page_title?: string;\n page_referrer?: string;\n [key: string]: unknown;\n}\n\nexport interface TrackPageViewOptions {\n /**\n * The event name to use for page views.\n * @default 'page_view'\n */\n eventName?: string;\n\n /**\n * Whether to include query parameters in the page path.\n * @default true\n */\n includeQueryParams?: boolean;\n\n /**\n * Additional data to include with each page view.\n */\n additionalData?: Record<string, unknown> | (() => Record<string, unknown>);\n}\n\nconst getAdditionalData = (additionalData: TrackPageViewOptions['additionalData']): Record<string, unknown> => {\n if (typeof additionalData === 'function') {\n return additionalData();\n }\n return additionalData ?? {};\n};\n\n/**\n * Track a page view event.\n *\n * @example\n * ```ts\n * import { trackPageView } from '@jwiedeman/gtm-kit-astro';\n *\n * // Track current page\n * trackPageView();\n *\n * // Track with custom data\n * trackPageView({\n * additionalData: { user_type: 'guest' }\n * });\n * ```\n */\nexport const trackPageView = (options: TrackPageViewOptions = {}): void => {\n const { eventName = 'page_view', includeQueryParams = true, additionalData } = options;\n\n if (typeof window === 'undefined') {\n return;\n }\n\n const pagePath = includeQueryParams ? window.location.pathname + window.location.search : window.location.pathname;\n\n const pageViewData: PageViewData = {\n event: eventName,\n page_path: pagePath,\n page_location: window.location.href,\n page_title: document.title,\n ...getAdditionalData(additionalData)\n };\n\n push(pageViewData);\n};\n\nlet viewTransitionsSetup = false;\nlet lastTrackedPath = '';\n\n/**\n * Reset page tracking state (for testing only).\n * @internal\n */\nexport const _resetPageTrackingState = (): void => {\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n};\n\n/**\n * Set up automatic page view tracking with Astro View Transitions.\n * Call this once when the page loads.\n *\n * @example\n * ```astro\n * ---\n * // src/layouts/Layout.astro\n * ---\n * <script>\n * import { initGtm, setupViewTransitions } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * setupViewTransitions();\n * </script>\n * ```\n */\nexport const setupViewTransitions = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return noop;\n }\n\n // Prevent duplicate setup\n if (viewTransitionsSetup) {\n return noop;\n }\n\n viewTransitionsSetup = true;\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle Astro View Transitions\n // The 'astro:page-load' event fires after every page load, including View Transitions\n const handlePageLoad = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n // Avoid duplicate tracking for the same path\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n document.addEventListener('astro:page-load', handlePageLoad);\n\n // Cleanup function\n return () => {\n document.removeEventListener('astro:page-load', handlePageLoad);\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n };\n};\n\n/**\n * Set up page view tracking for standard navigation (no View Transitions).\n * Tracks on popstate (back/forward) and initial load.\n *\n * @example\n * ```ts\n * import { setupPageTracking } from '@jwiedeman/gtm-kit-astro';\n *\n * setupPageTracking();\n * ```\n */\nexport const setupPageTracking = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined') {\n return noop;\n }\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle browser back/forward navigation\n const handlePopState = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n window.addEventListener('popstate', handlePopState);\n\n return () => {\n window.removeEventListener('popstate', handlePopState);\n lastTrackedPath = '';\n };\n};\n","// Client-side GTM management\nexport {\n initGtm,\n getGtmClient,\n requireGtmClient,\n push,\n setConsentDefaults,\n updateConsent,\n whenReady,\n teardown\n} from './client';\n\n// Page tracking\nexport { trackPageView, setupViewTransitions, setupPageTracking } from './page-tracking';\nexport type { PageViewData, TrackPageViewOptions } from './page-tracking';\n\n// Re-export core types for convenience\nexport type {\n ConsentState,\n ConsentRegionOptions,\n CreateGtmClientOptions,\n DataLayerValue,\n GtmClient,\n ScriptLoadState,\n ContainerConfigInput,\n ContainerDescriptor,\n ScriptAttributes\n} from '@jwiedeman/gtm-kit';\n\n// Re-export consent helpers\nexport { consentPresets, getConsentPreset, eeaDefault, allGranted, analyticsOnly } from '@jwiedeman/gtm-kit';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/page-tracking.ts","../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createGtmClient,\n type ConsentRegionOptions,\n type ConsentState,\n type CreateGtmClientOptions,\n type DataLayerValue,\n type GtmClient,\n type ScriptLoadState\n} from '@jwiedeman/gtm-kit';\n\nlet clientInstance: GtmClient | null = null;\nlet clientConfig: CreateGtmClientOptions | null = null;\n\n/**\n * Initialize the GTM client for use in Astro.\n * Should be called once when the page loads.\n *\n * @example\n * ```ts\n * // In a client-side script\n * import { initGtm } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * ```\n */\nexport const initGtm = (options: CreateGtmClientOptions): GtmClient => {\n // If already initialized, return existing instance\n // (common in Astro with view transitions where initGtm may be called multiple times)\n if (clientInstance) {\n return clientInstance;\n }\n\n clientInstance = createGtmClient(options);\n clientConfig = options;\n clientInstance.init();\n\n return clientInstance;\n};\n\n/**\n * Get the current GTM client instance.\n * Returns null if not initialized.\n *\n * @example\n * ```ts\n * import { getGtmClient } from '@jwiedeman/gtm-kit-astro';\n *\n * const client = getGtmClient();\n * if (client) {\n * client.push({ event: 'page_view' });\n * }\n * ```\n */\nexport const getGtmClient = (): GtmClient | null => clientInstance;\n\n/**\n * Get the GTM client or throw if not initialized.\n * Use this when you expect the client to be available.\n *\n * @throws Error if GTM client is not initialized\n */\nexport const requireGtmClient = (): GtmClient => {\n if (!clientInstance) {\n throw new Error(\n '[gtm-kit/astro] GTM client not initialized. Call initGtm() first or ensure the GTM script has loaded.'\n );\n }\n return clientInstance;\n};\n\n/**\n * Push a value to the GTM dataLayer.\n * This is a convenience function that handles the case where GTM isn't initialized.\n *\n * @returns true if the value was pushed successfully, false otherwise\n *\n * @example\n * ```ts\n * import { push } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = push({ event: 'button_click', button_name: 'hero_cta' });\n * if (!success) {\n * console.warn('GTM not ready');\n * }\n * ```\n */\n/**\n * Type-safe access to window properties for dataLayer.\n */\nconst getWindowDataLayer = (name: string): DataLayerValue[] | undefined => {\n if (typeof window === 'undefined') {\n return undefined;\n }\n const win = window as typeof window & Record<string, unknown>;\n const layer = win[name];\n return Array.isArray(layer) ? (layer as DataLayerValue[]) : undefined;\n};\n\nexport const push = (value: DataLayerValue): boolean => {\n const client = getGtmClient();\n if (client) {\n client.push(value);\n return true;\n } else if (typeof window !== 'undefined') {\n // Fallback: push directly to dataLayer if it exists\n const dataLayerName = clientConfig?.dataLayerName ?? 'dataLayer';\n const dataLayer = getWindowDataLayer(dataLayerName);\n if (dataLayer) {\n dataLayer.push(value);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized and dataLayer not found.');\n return false;\n }\n }\n return false;\n};\n\n/**\n * Set consent defaults (must be called before GTM loads).\n *\n * @returns true if consent defaults were set, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { setConsentDefaults } from '@jwiedeman/gtm-kit-astro';\n *\n * const success = setConsentDefaults({\n * ad_storage: 'denied',\n * analytics_storage: 'denied'\n * });\n * ```\n */\nexport const setConsentDefaults = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.setConsentDefaults(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Consent defaults should be set before init.');\n return false;\n }\n};\n\n/**\n * Update consent state after user interaction.\n *\n * @returns true if consent was updated, false if GTM not initialized\n *\n * @example\n * ```ts\n * import { updateConsent } from '@jwiedeman/gtm-kit-astro';\n *\n * // When user accepts cookies\n * const success = updateConsent({\n * ad_storage: 'granted',\n * analytics_storage: 'granted',\n * ad_user_data: 'granted',\n * ad_personalization: 'granted'\n * });\n * ```\n */\nexport const updateConsent = (state: ConsentState, options?: ConsentRegionOptions): boolean => {\n const client = getGtmClient();\n if (client) {\n client.updateConsent(state, options);\n return true;\n } else {\n console.warn('[gtm-kit/astro] GTM not initialized. Cannot update consent.');\n return false;\n }\n};\n\n/**\n * Wait for GTM scripts to be ready.\n *\n * @example\n * ```ts\n * import { whenReady } from '@jwiedeman/gtm-kit-astro';\n *\n * whenReady().then((states) => {\n * console.log('GTM loaded:', states);\n * });\n * ```\n */\nexport const whenReady = (): Promise<ScriptLoadState[]> => {\n const client = getGtmClient();\n if (client) {\n return client.whenReady();\n }\n return Promise.reject(new Error('[gtm-kit/astro] GTM not initialized.'));\n};\n\n/**\n * Clean up the GTM client.\n * Call this when navigating away or cleaning up.\n */\nexport const teardown = (): void => {\n if (clientInstance) {\n clientInstance.teardown();\n clientInstance = null;\n clientConfig = null;\n }\n};\n","import { push } from './client';\n\n// No-op function for SSR/cleanup returns\nconst noop = (): void => {\n /* no-op */\n};\n\nexport interface PageViewData {\n event?: string;\n page_path?: string;\n page_location?: string;\n page_title?: string;\n page_referrer?: string;\n [key: string]: unknown;\n}\n\nexport interface TrackPageViewOptions {\n /**\n * The event name to use for page views.\n * @default 'page_view'\n */\n eventName?: string;\n\n /**\n * Whether to include query parameters in the page path.\n * @default true\n */\n includeQueryParams?: boolean;\n\n /**\n * Additional data to include with each page view.\n */\n additionalData?: Record<string, unknown> | (() => Record<string, unknown>);\n}\n\nconst getAdditionalData = (additionalData: TrackPageViewOptions['additionalData']): Record<string, unknown> => {\n if (typeof additionalData === 'function') {\n return additionalData();\n }\n return additionalData ?? {};\n};\n\n/**\n * Track a page view event.\n *\n * @example\n * ```ts\n * import { trackPageView } from '@jwiedeman/gtm-kit-astro';\n *\n * // Track current page\n * trackPageView();\n *\n * // Track with custom data\n * trackPageView({\n * additionalData: { user_type: 'guest' }\n * });\n * ```\n */\nexport const trackPageView = (options: TrackPageViewOptions = {}): void => {\n const { eventName = 'page_view', includeQueryParams = true, additionalData } = options;\n\n if (typeof window === 'undefined') {\n return;\n }\n\n const pagePath = includeQueryParams ? window.location.pathname + window.location.search : window.location.pathname;\n\n const pageViewData: PageViewData = {\n event: eventName,\n page_path: pagePath,\n page_location: window.location.href,\n page_title: document.title,\n ...getAdditionalData(additionalData)\n };\n\n push(pageViewData);\n};\n\nlet viewTransitionsSetup = false;\nlet lastTrackedPath = '';\n\n/**\n * Reset page tracking state (for testing only).\n * @internal\n */\nexport const _resetPageTrackingState = (): void => {\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n};\n\n/**\n * Set up automatic page view tracking with Astro View Transitions.\n * Call this once when the page loads.\n *\n * @example\n * ```astro\n * ---\n * // src/layouts/Layout.astro\n * ---\n * <script>\n * import { initGtm, setupViewTransitions } from '@jwiedeman/gtm-kit-astro';\n *\n * initGtm({ containers: 'GTM-XXXXXX' });\n * setupViewTransitions();\n * </script>\n * ```\n */\nexport const setupViewTransitions = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return noop;\n }\n\n // Prevent duplicate setup\n if (viewTransitionsSetup) {\n return noop;\n }\n\n viewTransitionsSetup = true;\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle Astro View Transitions\n // The 'astro:page-load' event fires after every page load, including View Transitions\n const handlePageLoad = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n // Avoid duplicate tracking for the same path\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n document.addEventListener('astro:page-load', handlePageLoad);\n\n // Cleanup function\n return () => {\n document.removeEventListener('astro:page-load', handlePageLoad);\n viewTransitionsSetup = false;\n lastTrackedPath = '';\n };\n};\n\n/**\n * Set up page view tracking for standard navigation (no View Transitions).\n * Tracks on popstate (back/forward) and initial load.\n *\n * @example\n * ```ts\n * import { setupPageTracking } from '@jwiedeman/gtm-kit-astro';\n *\n * setupPageTracking();\n * ```\n */\nexport const setupPageTracking = (options: TrackPageViewOptions = {}): (() => void) => {\n if (typeof window === 'undefined') {\n return noop;\n }\n\n // Track initial page view\n lastTrackedPath = window.location.pathname + window.location.search;\n trackPageView(options);\n\n // Handle browser back/forward navigation\n const handlePopState = () => {\n const currentPath = window.location.pathname + window.location.search;\n\n if (currentPath === lastTrackedPath) {\n return;\n }\n\n lastTrackedPath = currentPath;\n trackPageView(options);\n };\n\n window.addEventListener('popstate', handlePopState);\n\n return () => {\n window.removeEventListener('popstate', handlePopState);\n lastTrackedPath = '';\n };\n};\n","// Client-side GTM management\nexport {\n initGtm,\n getGtmClient,\n requireGtmClient,\n push,\n setConsentDefaults,\n updateConsent,\n whenReady,\n teardown\n} from './client';\n\n// Page tracking\nexport { trackPageView, setupViewTransitions, setupPageTracking } from './page-tracking';\nexport type { PageViewData, TrackPageViewOptions } from './page-tracking';\n\n// Re-export core types for convenience\nexport type {\n ConsentState,\n ConsentRegionOptions,\n CreateGtmClientOptions,\n DataLayerValue,\n GtmClient,\n ScriptLoadState,\n ContainerConfigInput,\n ContainerDescriptor,\n ScriptAttributes\n} from '@jwiedeman/gtm-kit';\n\n// Re-export consent helpers\nexport { consentPresets, getConsentPreset, eeaDefault, allGranted, analyticsOnly } from '@jwiedeman/gtm-kit';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwiedeman/gtm-kit-astro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Astro components and helpers for GTM Kit - Google Tag Manager integration with View Transitions support.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"typecheck": "tsc --noEmit"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@jwiedeman/gtm-kit": "
|
|
58
|
+
"@jwiedeman/gtm-kit": "workspace:*"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"astro": "^3.0.0 || ^4.0.0 || ^5.0.0"
|