@owlmeans/web-gtm 0.1.18-rc.8 → 0.1.18

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The id shapes Google issues today, and nothing else.
3
+ *
4
+ * Deliberately strict — upper case, a known prefix, a bounded run of letters and digits — because
5
+ * the id is typed by a person into a settings form and ends up inside an inline script and a
6
+ * script URL. A value this pattern accepts cannot carry a quote, a tag or a second parameter, so
7
+ * the check is what makes it safe to store, not only to emit. `UA-` is not here: Universal
8
+ * Analytics stopped processing data in 2024, and accepting its ids would promise measurement that
9
+ * never arrives.
10
+ */
11
+ export const GOOGLE_TAG_ID = /^(GTM-[A-Z0-9]{4,12}|(G|GT|AW|DC)-[A-Z0-9]{4,16})$/;
12
+ /** A queue name has to be a plain identifier: it becomes `window[name]` in the emitted script. */
13
+ export const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
14
+ export const GOOGLE = 'Google LLC';
15
+ export const GOOGLE_PRIVACY = 'https://policies.google.com/privacy';
16
+ export const UNTIL_ANALYTICS = 'Until analytics cookies are accepted it sets no cookies and sends only '
17
+ + 'cookieless measurement signals.';
18
+ export const UNTIL_MARKETING = 'Until marketing cookies are accepted it sets no cookies and ad click '
19
+ + 'identifiers are redacted.';
20
+ //# sourceMappingURL=consts.local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oDAAoD,CAAA;AAEjF,kGAAkG;AAClG,MAAM,CAAC,MAAM,aAAa,GAAG,4BAA4B,CAAA;AAEzD,MAAM,CAAC,MAAM,MAAM,GAAG,YAAY,CAAA;AAElC,MAAM,CAAC,MAAM,cAAc,GAAG,qCAAqC,CAAA;AAEnE,MAAM,CAAC,MAAM,eAAe,GAAG,yEAAyE;MACpG,iCAAiC,CAAA;AAErC,MAAM,CAAC,MAAM,eAAe,GAAG,uEAAuE;MAClG,2BAA2B,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,30 +1,5 @@
1
- import type { ConsentOptions } from '@owlmeans/consent';
2
- export interface GtmOptions extends ConsentOptions {
3
- /** Container id, e.g. `GTM-XXXXXXX`. */
4
- id: string;
5
- /** The queue name, when a page runs more than one container. */
6
- dataLayerName?: string;
7
- }
8
- /**
9
- * The inline `<head>` snippet, consent first and the container second.
10
- *
11
- * The ORDER is the whole point of this package. Google Tag Manager decides what a tag may do from
12
- * the consent state present when the container loads, and a React bundle cannot get there first —
13
- * by the time an island mounts, the container has been running for hundreds of milliseconds. A
14
- * site whose defaults arrive after `gtm.js` is not configured differently; it is unconfigured for
15
- * the window that matters, and nothing in the page reports it.
16
- *
17
- * Emit this into the document head, above everything else, as an inline script.
18
- */
19
- export declare const gtmHeadScript: (opts: GtmOptions) => string;
20
- /** The `<noscript>` iframe, for the body. */
21
- export declare const gtmNoscriptFrame: (opts: GtmOptions) => string;
22
- /**
23
- * Load the container from script, for a host that cannot emit into its own head.
24
- *
25
- * The head snippet is strictly better and should be preferred; this exists for a single-page app
26
- * whose HTML is not ours to edit. It still pushes the defaults first, and it still refuses to load
27
- * a second time.
28
- */
29
- export declare const loadGtm: (opts: GtmOptions) => void;
1
+ export type * from './tag/types.js';
2
+ export * from './tag.js';
3
+ export type { GoogleTagKind, GoogleTagMode, GoogleTagOptions, GtmOptions } from './types.js';
4
+ export { GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_DEFAULT_MODE, GOOGLE_TAG_FRAME_SOURCES } from './consts.js';
30
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,SAAU,UAAU,KAAG,MAWhD,CAAA;AAED,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,SAAU,UAAU,KAAG,MAEsB,CAAA;AAE1E;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,SAAU,UAAU,KAAG,IAkB1C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,gBAAgB,CAAA;AACnC,cAAc,UAAU,CAAA;AACxB,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5F,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA"}
package/build/index.js CHANGED
@@ -1,52 +1,3 @@
1
- import { consentBootstrapScript, consentStore } from '@owlmeans/consent';
2
- /**
3
- * The inline `<head>` snippet, consent first and the container second.
4
- *
5
- * The ORDER is the whole point of this package. Google Tag Manager decides what a tag may do from
6
- * the consent state present when the container loads, and a React bundle cannot get there first —
7
- * by the time an island mounts, the container has been running for hundreds of milliseconds. A
8
- * site whose defaults arrive after `gtm.js` is not configured differently; it is unconfigured for
9
- * the window that matters, and nothing in the page reports it.
10
- *
11
- * Emit this into the document head, above everything else, as an inline script.
12
- */
13
- export const gtmHeadScript = (opts) => {
14
- const layer = opts.dataLayerName ?? 'dataLayer';
15
- return `${consentBootstrapScript(opts)};` +
16
- `(function(w,d,s,l,i){w[l]=w[l]||[];` +
17
- `w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` +
18
- `var f=d.getElementsByTagName(s)[0],j=d.createElement(s),` +
19
- `dl=l!='dataLayer'?'&l='+l:'';j.async=true;` +
20
- `j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;` +
21
- `f.parentNode.insertBefore(j,f)})` +
22
- `(window,document,'script',${JSON.stringify(layer)},${JSON.stringify(opts.id)})`;
23
- };
24
- /** The `<noscript>` iframe, for the body. */
25
- export const gtmNoscriptFrame = (opts) => `<iframe src="https://www.googletagmanager.com/ns.html?id=${encodeURIComponent(opts.id)}"` +
26
- ` height="0" width="0" style="display:none;visibility:hidden"></iframe>`;
27
- /**
28
- * Load the container from script, for a host that cannot emit into its own head.
29
- *
30
- * The head snippet is strictly better and should be preferred; this exists for a single-page app
31
- * whose HTML is not ours to edit. It still pushes the defaults first, and it still refuses to load
32
- * a second time.
33
- */
34
- export const loadGtm = (opts) => {
35
- if (typeof document === 'undefined') {
36
- return;
37
- }
38
- const marker = `owl-gtm-${opts.id}`;
39
- if (document.getElementById(marker) != null) {
40
- return;
41
- }
42
- // Defaults before the container, every time — the same rule the head snippet exists to keep.
43
- consentStore.init(opts);
44
- const layer = opts.dataLayerName ?? 'dataLayer';
45
- const script = document.createElement('script');
46
- script.id = marker;
47
- script.async = true;
48
- script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(opts.id)}`
49
- + (layer !== 'dataLayer' ? `&l=${encodeURIComponent(layer)}` : '');
50
- document.head.appendChild(script);
51
- };
1
+ export * from './tag.js';
2
+ export { GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_DEFAULT_MODE, GOOGLE_TAG_FRAME_SOURCES } from './consts.js';
52
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAUxE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAgB,EAAU,EAAE;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,WAAW,CAAA;IAE/C,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG;QACvC,qCAAqC;QACrC,+DAA+D;QAC/D,0DAA0D;QAC1D,4CAA4C;QAC5C,2DAA2D;QAC3D,kCAAkC;QAClC,6BAA6B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAA;AACpF,CAAC,CAAA;AAED,6CAA6C;AAC7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAgB,EAAU,EAAE,CAC3D,4DAA4D,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG;IAC1F,wEAAwE,CAAA;AAE1E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAgB,EAAQ,EAAE;IAChD,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAM;IACR,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,IAAI,CAAC,EAAE,EAAE,CAAA;IACnC,IAAI,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QAC5C,OAAM;IACR,CAAC;IACD,6FAA6F;IAC7F,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEvB,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,WAAW,CAAA;IAC/C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC/C,MAAM,CAAC,EAAE,GAAG,MAAM,CAAA;IAClB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,MAAM,CAAC,GAAG,GAAG,8CAA8C,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;UACpF,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACpE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;AACnC,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAA;AAExB,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,82 @@
1
+ import type { ConsentService } from '@owlmeans/consent';
2
+ import type { GoogleTagKind, GoogleTagOptions, GtmOptions } from '../types.js';
3
+ /** A Google tag behind consent: its inline head script, its fallbacks, its id rules, its disclosure. */
4
+ export interface GoogleTagHelper {
5
+ /**
6
+ * The inline `<head>` snippet, consent first and the container second.
7
+ *
8
+ * The ORDER is the whole point of this package. Google Tag Manager decides what a tag may do from
9
+ * the consent state present when the container loads, and a React bundle cannot get there first —
10
+ * by the time an island mounts, the container has been running for hundreds of milliseconds. A
11
+ * site whose defaults arrive after `gtm.js` is not configured differently; it is unconfigured for
12
+ * the window that matters, and nothing in the page reports it.
13
+ *
14
+ * Emit this into the document head, above everything else, as an inline script.
15
+ *
16
+ * In `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`) the container is not requested at all until a
17
+ * signal-bearing category is granted — {@link consentGateScript} wraps it, reading any stored
18
+ * decision immediately and otherwise waiting for `@owlmeans/consent`'s `CONSENT_EVENT`. Pass
19
+ * `mode: 'advanced'` for the original, unconditional load.
20
+ */
21
+ gtmHeadScript: (opts: GtmOptions) => string;
22
+ /**
23
+ * The `<noscript>` iframe, for the body.
24
+ *
25
+ * In `'basic'` mode this is an empty string: the whole point of gating is that an unauthenticated
26
+ * `<noscript>` iframe would defeat it — a browser with JavaScript disabled cannot have granted
27
+ * anything, so there is nothing lawful left to render. `'advanced'` keeps the frame.
28
+ */
29
+ gtmNoscriptFrame: (opts: GtmOptions) => string;
30
+ /**
31
+ * Load the container from script, for a host that cannot emit into its own head.
32
+ *
33
+ * The head snippet is strictly better and should be preferred; this exists for a single-page app
34
+ * whose HTML is not ours to edit. It still pushes the defaults first, and it still refuses to load
35
+ * a second time.
36
+ *
37
+ * In `'basic'` mode the container element is not appended until `trackingGranted` is true: either
38
+ * the stored record already grants it, or — since this runs after the bundle mounted, past the
39
+ * window the head snippet exists to close — a subscription to `consentStore` catches the first
40
+ * update where it becomes true and unsubscribes. `'advanced'` appends it immediately, as before.
41
+ */
42
+ loadGtm: (opts: GtmOptions) => void;
43
+ /** Whether `id` is a Tag Manager container or Google tag id this package can load. */
44
+ isGoogleTagId: (id: string) => boolean;
45
+ /** The loader `id` takes, or `null` when it is not a loadable id at all. */
46
+ googleTagKind: (id: string) => GoogleTagKind | null;
47
+ /**
48
+ * The one inline `<head>` script for a Google tag of any kind: the consent bootstrap FIRST, then
49
+ * ads redaction, then the loader the id's prefix calls for — the Tag Manager container for `GTM-`,
50
+ * gtag.js with `js` and `config` for `G-`, `GT-`, `AW-` and `DC-`.
51
+ *
52
+ * The order is the rule {@link gtmHeadScript} exists for, and it holds for gtag.js just the same:
53
+ * a tag decides what it may do from the consent state present when it loads. The bootstrap
54
+ * declares everything denied and applies any stored decision in the same turn, so a returning
55
+ * visitor's tag starts granted and a new visitor's starts cookieless.
56
+ *
57
+ * An id that is not a loadable id ({@link isGoogleTagId}) yields the consent bootstrap alone — a
58
+ * page must never lose its consent defaults because someone mistyped a tag. The result is safe to
59
+ * place inline in HTML as-is: `</` and `<!--` inside any configured value are escaped.
60
+ *
61
+ * In `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`) the loader itself — the last piece — is wrapped in
62
+ * `consentGateScript`: it runs immediately for a returning visitor whose stored record already
63
+ * grants a signal-bearing category, and otherwise waits for `@owlmeans/consent`'s `CONSENT_EVENT`
64
+ * before ever requesting the tag. The bootstrap and the redaction flags are unaffected and still
65
+ * run unconditionally — Consent Mode's OWN denied-by-default signals are declared either way, so a
66
+ * tag that later does load still starts from `ad_storage`/`analytics_storage` denied. Pass
67
+ * `mode: 'advanced'` for the original, unconditional load.
68
+ */
69
+ googleTagHeadScript: (opts: GoogleTagOptions) => string;
70
+ /**
71
+ * What a Google tag discloses on the cookie policy, for `CookiePolicy`'s `services`.
72
+ *
73
+ * Read off the id's prefix, because that is all the page knows: `G-` is Google Analytics 4
74
+ * (`_ga`, and `_ga_<id without G->` which carries the session), `AW-` Google Ads, `DC-` Floodlight.
75
+ * A `GTM-` container and a `GT-` Google tag route to whatever their owner configured at Google,
76
+ * so both are disclosed as an analytics AND an advertising entry, worded as configured there —
77
+ * the policy may not claim less than the tag can do. An invalid id discloses nothing, matching
78
+ * {@link googleTagHeadScript}, which loads nothing for it.
79
+ */
80
+ googleTagServices: (id: string) => ConsentService[];
81
+ }
82
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tag/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE9E,wGAAwG;AACxG,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAA;IAC3C;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAA;IAC9C;;;;;;;;;;;OAWG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAA;IACnC,sFAAsF;IACtF,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAA;IACtC,4EAA4E;IAC5E,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,aAAa,GAAG,IAAI,CAAA;IACnD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,MAAM,CAAA;IACvD;;;;;;;;;OASG;IACH,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,cAAc,EAAE,CAAA;CACpD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tag/types.ts"],"names":[],"mappings":""}
package/build/tag.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { type ConsentService } from '@owlmeans/consent';
2
+ import type { GoogleTagOptions, GtmOptions } from './types.js';
3
+ import type { GoogleTagHelper } from './tag/types.js';
4
+ export declare const createGoogleTagHelper: () => GoogleTagHelper;
5
+ export declare const googleTagHelper: GoogleTagHelper;
6
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.gtmHeadScript(…)` */
7
+ export declare const gtmHeadScript: (opts: GtmOptions) => string;
8
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.gtmNoscriptFrame(…)` */
9
+ export declare const gtmNoscriptFrame: (opts: GtmOptions) => string;
10
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.isGoogleTagId(…)` */
11
+ export declare const isGoogleTagId: (id: string) => boolean;
12
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.googleTagHeadScript(…)` */
13
+ export declare const googleTagHeadScript: (opts: GoogleTagOptions) => string;
14
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.googleTagServices(…)` */
15
+ export declare const googleTagServices: (id: string) => ConsentService[];
16
+ //# sourceMappingURL=tag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAG9H,OAAO,KAAK,EAAiB,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,eAAO,MAAM,qBAAqB,QAAO,eAiNxC,CAAA;AAED,eAAO,MAAM,eAAe,iBAA0B,CAAA;AAEtD,mFAAmF;AACnF,eAAO,MAAM,aAAa,SAAU,UAAU,KAAG,MAA6C,CAAA;AAE9F,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,SAAU,UAAU,KAAG,MAAgD,CAAA;AAEpG,mFAAmF;AACnF,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,OAA4C,CAAA;AAEvF,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,SAAU,gBAAgB,KAAG,MAAmD,CAAA;AAEhH,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,OAAQ,MAAM,KAAG,cAAc,EAA2C,CAAA"}
package/build/tag.js ADDED
@@ -0,0 +1,172 @@
1
+ import { CONSENT_ANALYTICS, CONSENT_MARKETING, consentModeHelper, consentStore } from '@owlmeans/consent';
2
+ import { GOOGLE, GOOGLE_PRIVACY, GOOGLE_TAG_ID, JS_IDENTIFIER, UNTIL_ANALYTICS, UNTIL_MARKETING } from './consts.local.js';
3
+ import { GOOGLE_TAG_DEFAULT_MODE } from './consts.js';
4
+ export const createGoogleTagHelper = () => {
5
+ /** Google's own container IIFE, the id and queue name injected as JSON data. */
6
+ const gtmContainerScript = (layer, id) => `(function(w,d,s,l,i){w[l]=w[l]||[];` +
7
+ `w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` +
8
+ `var f=d.getElementsByTagName(s)[0],j=d.createElement(s),` +
9
+ `dl=l!='dataLayer'?'&l='+l:'';j.async=true;` +
10
+ `j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;` +
11
+ `f.parentNode.insertBefore(j,f)})` +
12
+ `(window,document,'script',${JSON.stringify(layer)},${JSON.stringify(id)})`;
13
+ const gtmHeadScript = (opts) => {
14
+ const bootstrap = consentModeHelper.consentBootstrapScript(opts);
15
+ const loader = gtmContainerScript(opts.dataLayerName ?? 'dataLayer', opts.id);
16
+ if ((opts.mode ?? GOOGLE_TAG_DEFAULT_MODE) === 'basic') {
17
+ return `${bootstrap};${consentModeHelper.consentGateScript(loader, opts)}`;
18
+ }
19
+ return `${bootstrap};${loader}`;
20
+ };
21
+ const gtmNoscriptFrame = (opts) => {
22
+ if ((opts.mode ?? GOOGLE_TAG_DEFAULT_MODE) === 'basic') {
23
+ return '';
24
+ }
25
+ return `<iframe src="https://www.googletagmanager.com/ns.html?id=${encodeURIComponent(opts.id)}"` +
26
+ ` height="0" width="0" style="display:none;visibility:hidden"></iframe>`;
27
+ };
28
+ const loadGtm = (opts) => {
29
+ if (typeof document === 'undefined') {
30
+ return;
31
+ }
32
+ const marker = `owl-gtm-${opts.id}`;
33
+ if (document.getElementById(marker) != null) {
34
+ return;
35
+ }
36
+ // Defaults before the container, every time — the same rule the head snippet exists to keep.
37
+ consentStore.init(opts);
38
+ const inject = () => {
39
+ if (document.getElementById(marker) != null) {
40
+ return;
41
+ }
42
+ const layer = opts.dataLayerName ?? 'dataLayer';
43
+ const script = document.createElement('script');
44
+ script.id = marker;
45
+ script.async = true;
46
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(opts.id)}`
47
+ + (layer !== 'dataLayer' ? `&l=${encodeURIComponent(layer)}` : '');
48
+ document.head.appendChild(script);
49
+ };
50
+ if ((opts.mode ?? GOOGLE_TAG_DEFAULT_MODE) !== 'basic') {
51
+ inject();
52
+ return;
53
+ }
54
+ if (consentModeHelper.trackingGranted(consentStore.get().record, opts.categories)) {
55
+ inject();
56
+ return;
57
+ }
58
+ const unsubscribe = consentStore.subscribe(state => {
59
+ if (consentModeHelper.trackingGranted(state.record, opts.categories)) {
60
+ unsubscribe();
61
+ inject();
62
+ }
63
+ });
64
+ };
65
+ const isGoogleTagId = (id) => typeof id === 'string' && GOOGLE_TAG_ID.test(id);
66
+ const googleTagKind = (id) => !isGoogleTagId(id) ? null : id.startsWith('GTM-') ? 'gtm' : 'gtag';
67
+ const layerOf = (opts) => opts.dataLayerName != null && JS_IDENTIFIER.test(opts.dataLayerName)
68
+ ? opts.dataLayerName
69
+ : 'dataLayer';
70
+ /**
71
+ * Make a generated script safe to place between `<script>` and `</script>`.
72
+ *
73
+ * JSON encoding keeps a value inside its string literal, but the HTML parser does not read
74
+ * JavaScript: it ends the element at the first `</script` it meets, wherever it sits, and a
75
+ * `<!--` can switch it into a state where the real closing tag is swallowed. Both sequences can
76
+ * only appear inside the JSON-encoded values here — category keys, a storage key, a queue name —
77
+ * so each gets a backslash that JavaScript reads as nothing (`\/` is `/`, `\!` is `!`).
78
+ */
79
+ const inlineSafe = (script) => script.replace(/<\//g, '<\\/').replace(/<!--/g, '<\\!--');
80
+ /**
81
+ * Ads data redaction on, URL passthrough off — before any loader runs.
82
+ *
83
+ * `ads_data_redaction` makes an Ads or Floodlight tag strip ad-click identifiers and send its
84
+ * requests through a cookieless domain while `ad_storage` is denied. `url_passthrough` is set to
85
+ * its default explicitly so that nothing configured later on the page can switch on decorating
86
+ * links with click ids for a visitor who refused marketing storage. Google reads both only when
87
+ * they are set ahead of the tag's `config`.
88
+ */
89
+ const redactionScript = (layer) => `(function(w,l){w[l]=w[l]||[];function g(){w[l].push(arguments)}` +
90
+ `g('set','ads_data_redaction',true);g('set','url_passthrough',false)})` +
91
+ `(window,${JSON.stringify(layer)})`;
92
+ /**
93
+ * Google's gtag.js snippet as one inline script: the queue function, `js` and `config`, then the
94
+ * library requested from here rather than from a second `<script src>` element.
95
+ *
96
+ * One inline script instead of Google's two elements because the document it lands in is served
97
+ * with a hash-based CSP and stamped by a build that writes one head snippet — a second element
98
+ * would be a second thing to keep in order. The element carries an id derived from the tag, so a
99
+ * page that runs the snippet twice (two stampings, a hot reload) does not configure the tag twice
100
+ * and double-count every page view. `window.gtag` is published only when nothing else owns it, so
101
+ * application code can send its own events.
102
+ */
103
+ const gtagScript = (layer, id) => `(function(w,d,l,i){var m='owl-gtag-'+i;if(d.getElementById(m))return;` +
104
+ `w[l]=w[l]||[];function g(){w[l].push(arguments)}if(!w.gtag)w.gtag=g;` +
105
+ `g('js',new Date());g('config',i);` +
106
+ `var j=d.createElement('script');j.id=m;j.async=true;` +
107
+ `j.src='https://www.googletagmanager.com/gtag/js?id='+encodeURIComponent(i)` +
108
+ `+(l!='dataLayer'?'&l='+encodeURIComponent(l):'');` +
109
+ `(d.head||d.documentElement).appendChild(j)})` +
110
+ `(window,document,${JSON.stringify(layer)},${JSON.stringify(id)})`;
111
+ const googleTagHeadScript = (opts) => {
112
+ const bootstrap = consentModeHelper.consentBootstrapScript(opts);
113
+ const kind = googleTagKind(opts.id);
114
+ if (kind == null) {
115
+ return inlineSafe(bootstrap);
116
+ }
117
+ const layer = layerOf(opts);
118
+ const loader = kind === 'gtm' ? gtmContainerScript(layer, opts.id) : gtagScript(layer, opts.id);
119
+ const redaction = redactionScript(layer);
120
+ if ((opts.mode ?? GOOGLE_TAG_DEFAULT_MODE) === 'basic') {
121
+ return inlineSafe(`${bootstrap};${redaction};${consentModeHelper.consentGateScript(loader, opts)}`);
122
+ }
123
+ return inlineSafe(`${bootstrap};${redaction};${loader}`);
124
+ };
125
+ const analyticsService = (name, purpose, cookies) => ({
126
+ name, provider: GOOGLE, category: CONSENT_ANALYTICS, purpose, cookies, privacyHref: GOOGLE_PRIVACY,
127
+ });
128
+ const adsService = (name, purpose, cookies) => ({
129
+ name, provider: GOOGLE, category: CONSENT_MARKETING, purpose, cookies, privacyHref: GOOGLE_PRIVACY,
130
+ });
131
+ const googleTagServices = (id) => {
132
+ if (!isGoogleTagId(id)) {
133
+ return [];
134
+ }
135
+ const prefix = id.slice(0, id.indexOf('-'));
136
+ switch (prefix) {
137
+ case 'G':
138
+ return [analyticsService('Google Analytics', 'Measures how visitors use the site — pages viewed, time spent, device and approximate '
139
+ + `location — so it can be improved. ${UNTIL_ANALYTICS}`, ['_ga', `_ga_${id.slice(2)}`])];
140
+ case 'AW':
141
+ return [adsService('Google Ads', 'Measures which ads led to a visit or a sign-up, and may add visitors to audiences for '
142
+ + `showing them ads elsewhere. ${UNTIL_MARKETING}`, ['_gcl_au', '_gcl_aw'])];
143
+ case 'DC':
144
+ return [adsService('Google Campaign Manager 360 (Floodlight)', `Measures which ad campaigns led to a visit or a sign-up. ${UNTIL_MARKETING}`, ['_gcl_au', '_gcl_dc'])];
145
+ default: {
146
+ const source = prefix === 'GTM' ? 'Google Tag Manager' : 'Google tag';
147
+ const where = prefix === 'GTM' ? 'in its container' : 'for it at Google';
148
+ return [
149
+ analyticsService(`${source} — analytics`, `Runs the analytics tags configured ${where}, typically Google Analytics. `
150
+ + UNTIL_ANALYTICS, ['_ga', '_ga_*']),
151
+ adsService(`${source} — advertising`, `Runs the advertising tags configured ${where}, typically Google Ads conversion `
152
+ + `tracking and remarketing. ${UNTIL_MARKETING}`, ['_gcl_au', '_gcl_aw']),
153
+ ];
154
+ }
155
+ }
156
+ };
157
+ return {
158
+ gtmHeadScript, gtmNoscriptFrame, loadGtm, isGoogleTagId, googleTagKind, googleTagHeadScript, googleTagServices,
159
+ };
160
+ };
161
+ export const googleTagHelper = createGoogleTagHelper();
162
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.gtmHeadScript(…)` */
163
+ export const gtmHeadScript = (opts) => googleTagHelper.gtmHeadScript(opts);
164
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.gtmNoscriptFrame(…)` */
165
+ export const gtmNoscriptFrame = (opts) => googleTagHelper.gtmNoscriptFrame(opts);
166
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.isGoogleTagId(…)` */
167
+ export const isGoogleTagId = (id) => googleTagHelper.isGoogleTagId(id);
168
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.googleTagHeadScript(…)` */
169
+ export const googleTagHeadScript = (opts) => googleTagHelper.googleTagHeadScript(opts);
170
+ /** @deprecated compat:factory-refactor — use `googleTagHelper.googleTagServices(…)` */
171
+ export const googleTagServices = (id) => googleTagHelper.googleTagServices(id);
172
+ //# sourceMappingURL=tag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.js","sourceRoot":"","sources":["../src/tag.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { ConsentOptions } from '@owlmeans/consent';
2
+ /** `'gtm'` container / `'gtag'` — see {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}. */
3
+ export type GoogleTagMode = 'basic' | 'advanced';
4
+ export interface GtmOptions extends ConsentOptions {
5
+ /** Container id, e.g. `GTM-XXXXXXX`. */
6
+ id: string;
7
+ /** The queue name, when a page runs more than one container. */
8
+ dataLayerName?: string;
9
+ /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
10
+ mode?: GoogleTagMode;
11
+ }
12
+ /**
13
+ * Which loader an id takes: `gtm` is the Tag Manager container (`gtm.js`), `gtag` the Google tag
14
+ * (`gtag.js`) — Google Analytics 4 (`G-`), a Google tag (`GT-`), Google Ads (`AW-`) and
15
+ * Floodlight (`DC-`).
16
+ */
17
+ export type GoogleTagKind = 'gtm' | 'gtag';
18
+ export interface GoogleTagOptions extends ConsentOptions {
19
+ /** `GTM-…`, `G-…`, `GT-…`, `AW-…` or `DC-…` — see {@link isGoogleTagId}. */
20
+ id: string;
21
+ /**
22
+ * The queue name, when a page runs more than one tag. Consent Mode from `@owlmeans/consent` —
23
+ * the bootstrap AND the dialog's later updates — always speaks on `window.dataLayer`, so a tag
24
+ * given another queue never hears a consent command. Leave it unset on a consent-gated page.
25
+ */
26
+ dataLayerName?: string;
27
+ /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
28
+ mode?: GoogleTagMode;
29
+ }
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,kGAAkG;AAClG,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEhD,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,8FAA8F;IAC9F,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAA;AAE1C,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAA;IACV;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,8FAA8F;IAC9F,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/web-gtm",
3
- "version": "0.1.18-rc.8",
3
+ "version": "0.1.18",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,11 +22,11 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@owlmeans/consent": "^0.1.18-rc.4"
25
+ "@owlmeans/consent": "^0.1.18"
26
26
  },
27
27
  "devDependencies": {
28
- "@owlmeans/dep-config": "workspace:*",
29
- "@types/bun": "^1.4.0",
28
+ "@owlmeans/dep-config": "^0.1.18",
29
+ "@types/bun": "^1.4.2",
30
30
  "nodemon": "^3.1.14",
31
31
  "typescript": "^7.0.2"
32
32
  },
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The id shapes Google issues today, and nothing else.
3
+ *
4
+ * Deliberately strict — upper case, a known prefix, a bounded run of letters and digits — because
5
+ * the id is typed by a person into a settings form and ends up inside an inline script and a
6
+ * script URL. A value this pattern accepts cannot carry a quote, a tag or a second parameter, so
7
+ * the check is what makes it safe to store, not only to emit. `UA-` is not here: Universal
8
+ * Analytics stopped processing data in 2024, and accepting its ids would promise measurement that
9
+ * never arrives.
10
+ */
11
+ export const GOOGLE_TAG_ID = /^(GTM-[A-Z0-9]{4,12}|(G|GT|AW|DC)-[A-Z0-9]{4,16})$/
12
+
13
+ /** A queue name has to be a plain identifier: it becomes `window[name]` in the emitted script. */
14
+ export const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
15
+
16
+ export const GOOGLE = 'Google LLC'
17
+
18
+ export const GOOGLE_PRIVACY = 'https://policies.google.com/privacy'
19
+
20
+ export const UNTIL_ANALYTICS = 'Until analytics cookies are accepted it sets no cookies and sends only '
21
+ + 'cookieless measurement signals.'
22
+
23
+ export const UNTIL_MARKETING = 'Until marketing cookies are accepted it sets no cookies and ad click '
24
+ + 'identifiers are redacted.'
package/src/consts.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type { GoogleTagMode } from './types.js'
2
+
3
+ /**
4
+ * The platform default for {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}.
5
+ *
6
+ * One named constant so flipping it later is a one-line change.
7
+ *
8
+ * - `'basic'` withholds the loader until a signal-bearing category is granted — which holds back
9
+ * Google's own IP receipt too, not only what a granted tag may then do with it.
10
+ * - `'advanced'` loads immediately with Consent Mode signals denied — today's only behavior, and
11
+ * Google's own recommended default for its own conversion modeling.
12
+ *
13
+ * Default is `'basic'`: read it as the EU/DE worst-case reading of ePrivacy Art. 5(3) — no third
14
+ * party receives a connection before consent.
15
+ */
16
+ export const GOOGLE_TAG_DEFAULT_MODE: GoogleTagMode = 'basic'
17
+
18
+ /**
19
+ * The hosts a page running a Google tag has to allow in `script-src`, `connect-src` and
20
+ * `img-src`, for Google Analytics 4, Tag Manager and Google Ads together.
21
+ *
22
+ * Taken from Google's CSP guide (developers.google.com/tag-platform/security/guides/csp) and
23
+ * folded into one list, because the platform adds one list to all three directives:
24
+ *
25
+ * - `*.googletagmanager.com` — gtm.js and gtag/js (`www.`), and their measurement pings;
26
+ * - `*.google-analytics.com` — GA4 collection (`region1.`, `www.`);
27
+ * - `*.g.doubleclick.net` — GA4 with Google signals and Ads (`stats.g.`, `googleads.g.`);
28
+ * - `ad.doubleclick.net` — Ads and Floodlight conversions;
29
+ * - `www.googleadservices.com` — the Ads conversion script;
30
+ * - `pagead2.googlesyndication.com` — Ads and GA4-with-ads pings;
31
+ * - `*.google.com` — `www.google.com` (the Ads script, consent-mode pings), `adservice.`, and the
32
+ * `analytics.google.com` collection endpoints.
33
+ *
34
+ * Every entry is an `https://` scheme, an optional `*.` label and a dotted host — the only shape
35
+ * the publisher's CSP filter lets through — and there are few enough to stay inside its per-slot
36
+ * cap. Google's country domains (`google.<TLD>`) cannot be named in that shape and are left out:
37
+ * what is lost is a remarketing ping from visitors on those domains, never the tag itself.
38
+ */
39
+ export const GOOGLE_TAG_CSP_SOURCES: readonly string[] = Object.freeze([
40
+ 'https://*.googletagmanager.com',
41
+ 'https://*.google-analytics.com',
42
+ 'https://*.g.doubleclick.net',
43
+ 'https://ad.doubleclick.net',
44
+ 'https://www.googleadservices.com',
45
+ 'https://pagead2.googlesyndication.com',
46
+ 'https://*.google.com',
47
+ ])
48
+
49
+ /**
50
+ * The hosts a Google tag frames: Tag Manager's service-worker and preview frames, and the Ads
51
+ * remarketing frame. For `frame-src`, which otherwise stays `'none'`.
52
+ */
53
+ export const GOOGLE_TAG_FRAME_SOURCES: readonly string[] = Object.freeze([
54
+ 'https://www.googletagmanager.com',
55
+ 'https://td.doubleclick.net',
56
+ ])