@owlmeans/web-gtm 0.1.18-rc.32 → 0.1.18-rc.34

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 CHANGED
@@ -6,7 +6,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
6
6
  your project's skill store (`.agents/skills/`):
7
7
 
8
8
  ```sh
9
- npx @owlmeans/agent-skills@^0.1.18-rc.40
9
+ npx @owlmeans/agent-skills@^0.1.18-rc.48
10
10
  ```
11
11
 
12
12
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/web-gtm",
4
- "version": "0.1.18-rc.32",
5
- "generatedAt": "2026-09-25T21:19:37.947Z",
4
+ "version": "0.1.18-rc.34",
5
+ "generatedAt": "2026-10-05T08:32:19.091Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/web-gtm
9
9
 
10
10
  **Layer:** Web
11
- **Install:** `"@owlmeans/web-gtm": "^0.1.18-rc.32"` in `dependencies`
11
+ **Install:** `"@owlmeans/web-gtm": "^0.1.18-rc.34"` in `dependencies`
12
12
 
13
13
  The tag half of the consent set. It emits **strings and data**, not components, and it holds no
14
14
  state — the decision lives in `@owlmeans/consent`, which this package reads through
@@ -165,6 +165,13 @@ page says so.
165
165
  **built-in tag templates** (GA4, Google Ads, Floodlight, Conversion Linker) and Community
166
166
  Gallery templates, which load from the allowed hosts.
167
167
 
168
+ ## Sending events
169
+
170
+ This package emits the head snippet and decides when the loader may run; it has no event-push API. An
171
+ application sends its own events through `@owlmeans/log` calls that carry an `analytics` option, and
172
+ `@owlmeans/web-log`'s `gtmAnalyticsPlugin` pushes them onto `window.dataLayer` only while analytics
173
+ consent is granted (`/web-log`).
174
+
168
175
  ## Disclosing it
169
176
 
170
177
  `googleTagServices(id)` returns what the cookie policy lists under each category, read off the
@@ -0,0 +1,43 @@
1
+ import type { GoogleTagMode } from './types.js';
2
+ /**
3
+ * The platform default for {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}.
4
+ *
5
+ * One named constant so flipping it later is a one-line change.
6
+ *
7
+ * - `'basic'` withholds the loader until a signal-bearing category is granted — which holds back
8
+ * Google's own IP receipt too, not only what a granted tag may then do with it.
9
+ * - `'advanced'` loads immediately with Consent Mode signals denied — today's only behavior, and
10
+ * Google's own recommended default for its own conversion modeling.
11
+ *
12
+ * Default is `'basic'`: read it as the EU/DE worst-case reading of ePrivacy Art. 5(3) — no third
13
+ * party receives a connection before consent.
14
+ */
15
+ export declare const GOOGLE_TAG_DEFAULT_MODE: GoogleTagMode;
16
+ /**
17
+ * The hosts a page running a Google tag has to allow in `script-src`, `connect-src` and
18
+ * `img-src`, for Google Analytics 4, Tag Manager and Google Ads together.
19
+ *
20
+ * Taken from Google's CSP guide (developers.google.com/tag-platform/security/guides/csp) and
21
+ * folded into one list, because the platform adds one list to all three directives:
22
+ *
23
+ * - `*.googletagmanager.com` — gtm.js and gtag/js (`www.`), and their measurement pings;
24
+ * - `*.google-analytics.com` — GA4 collection (`region1.`, `www.`);
25
+ * - `*.g.doubleclick.net` — GA4 with Google signals and Ads (`stats.g.`, `googleads.g.`);
26
+ * - `ad.doubleclick.net` — Ads and Floodlight conversions;
27
+ * - `www.googleadservices.com` — the Ads conversion script;
28
+ * - `pagead2.googlesyndication.com` — Ads and GA4-with-ads pings;
29
+ * - `*.google.com` — `www.google.com` (the Ads script, consent-mode pings), `adservice.`, and the
30
+ * `analytics.google.com` collection endpoints.
31
+ *
32
+ * Every entry is an `https://` scheme, an optional `*.` label and a dotted host — the only shape
33
+ * the publisher's CSP filter lets through — and there are few enough to stay inside its per-slot
34
+ * cap. Google's country domains (`google.<TLD>`) cannot be named in that shape and are left out:
35
+ * what is lost is a remarketing ping from visitors on those domains, never the tag itself.
36
+ */
37
+ export declare const GOOGLE_TAG_CSP_SOURCES: readonly string[];
38
+ /**
39
+ * The hosts a Google tag frames: Tag Manager's service-worker and preview frames, and the Ads
40
+ * remarketing frame. For `frame-src`, which otherwise stays `'none'`.
41
+ */
42
+ export declare const GOOGLE_TAG_FRAME_SOURCES: readonly string[];
43
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAuB,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAQlD,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAGpD,CAAA"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The platform default for {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}.
3
+ *
4
+ * One named constant so flipping it later is a one-line change.
5
+ *
6
+ * - `'basic'` withholds the loader until a signal-bearing category is granted — which holds back
7
+ * Google's own IP receipt too, not only what a granted tag may then do with it.
8
+ * - `'advanced'` loads immediately with Consent Mode signals denied — today's only behavior, and
9
+ * Google's own recommended default for its own conversion modeling.
10
+ *
11
+ * Default is `'basic'`: read it as the EU/DE worst-case reading of ePrivacy Art. 5(3) — no third
12
+ * party receives a connection before consent.
13
+ */
14
+ export const GOOGLE_TAG_DEFAULT_MODE = 'basic';
15
+ /**
16
+ * The hosts a page running a Google tag has to allow in `script-src`, `connect-src` and
17
+ * `img-src`, for Google Analytics 4, Tag Manager and Google Ads together.
18
+ *
19
+ * Taken from Google's CSP guide (developers.google.com/tag-platform/security/guides/csp) and
20
+ * folded into one list, because the platform adds one list to all three directives:
21
+ *
22
+ * - `*.googletagmanager.com` — gtm.js and gtag/js (`www.`), and their measurement pings;
23
+ * - `*.google-analytics.com` — GA4 collection (`region1.`, `www.`);
24
+ * - `*.g.doubleclick.net` — GA4 with Google signals and Ads (`stats.g.`, `googleads.g.`);
25
+ * - `ad.doubleclick.net` — Ads and Floodlight conversions;
26
+ * - `www.googleadservices.com` — the Ads conversion script;
27
+ * - `pagead2.googlesyndication.com` — Ads and GA4-with-ads pings;
28
+ * - `*.google.com` — `www.google.com` (the Ads script, consent-mode pings), `adservice.`, and the
29
+ * `analytics.google.com` collection endpoints.
30
+ *
31
+ * Every entry is an `https://` scheme, an optional `*.` label and a dotted host — the only shape
32
+ * the publisher's CSP filter lets through — and there are few enough to stay inside its per-slot
33
+ * cap. Google's country domains (`google.<TLD>`) cannot be named in that shape and are left out:
34
+ * what is lost is a remarketing ping from visitors on those domains, never the tag itself.
35
+ */
36
+ export const GOOGLE_TAG_CSP_SOURCES = Object.freeze([
37
+ 'https://*.googletagmanager.com',
38
+ 'https://*.google-analytics.com',
39
+ 'https://*.g.doubleclick.net',
40
+ 'https://ad.doubleclick.net',
41
+ 'https://www.googleadservices.com',
42
+ 'https://pagead2.googlesyndication.com',
43
+ 'https://*.google.com',
44
+ ]);
45
+ /**
46
+ * The hosts a Google tag frames: Tag Manager's service-worker and preview frames, and the Ads
47
+ * remarketing frame. For `frame-src`, which otherwise stays `'none'`.
48
+ */
49
+ export const GOOGLE_TAG_FRAME_SOURCES = Object.freeze([
50
+ 'https://www.googletagmanager.com',
51
+ 'https://td.doubleclick.net',
52
+ ]);
53
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAkB,OAAO,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACrE,gCAAgC;IAChC,gCAAgC;IAChC,6BAA6B;IAC7B,4BAA4B;IAC5B,kCAAkC;IAClC,uCAAuC;IACvC,sBAAsB;CACvB,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACvE,kCAAkC;IAClC,4BAA4B;CAC7B,CAAC,CAAA"}
@@ -0,0 +1,18 @@
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 declare const GOOGLE_TAG_ID: RegExp;
12
+ /** A queue name has to be a plain identifier: it becomes `window[name]` in the emitted script. */
13
+ export declare const JS_IDENTIFIER: RegExp;
14
+ export declare const GOOGLE = "Google LLC";
15
+ export declare const GOOGLE_PRIVACY = "https://policies.google.com/privacy";
16
+ export declare const UNTIL_ANALYTICS: string;
17
+ export declare const UNTIL_MARKETING: string;
18
+ //# sourceMappingURL=consts.local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAuD,CAAA;AAEjF,kGAAkG;AAClG,eAAO,MAAM,aAAa,QAA+B,CAAA;AAEzD,eAAO,MAAM,MAAM,eAAe,CAAA;AAElC,eAAO,MAAM,cAAc,wCAAwC,CAAA;AAEnE,eAAO,MAAM,eAAe,QACS,CAAA;AAErC,eAAO,MAAM,eAAe,QACG,CAAA"}
@@ -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,28 +1,5 @@
1
- import type { ConsentOptions, ConsentService } from '@owlmeans/consent';
2
- /** `'gtm'` container / `'gtag'` — see {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}. */
3
- export type GoogleTagMode = 'basic' | 'advanced';
4
- /**
5
- * The platform default for {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}.
6
- *
7
- * One named constant so flipping it later is a one-line change.
8
- *
9
- * - `'basic'` withholds the loader until a signal-bearing category is granted — which holds back
10
- * Google's own IP receipt too, not only what a granted tag may then do with it.
11
- * - `'advanced'` loads immediately with Consent Mode signals denied — today's only behavior, and
12
- * Google's own recommended default for its own conversion modeling.
13
- *
14
- * Default is `'basic'`: read it as the EU/DE worst-case reading of ePrivacy Art. 5(3) — no third
15
- * party receives a connection before consent.
16
- */
17
- export declare const GOOGLE_TAG_DEFAULT_MODE: GoogleTagMode;
18
- export interface GtmOptions extends ConsentOptions {
19
- /** Container id, e.g. `GTM-XXXXXXX`. */
20
- id: string;
21
- /** The queue name, when a page runs more than one container. */
22
- dataLayerName?: string;
23
- /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
24
- mode?: GoogleTagMode;
25
- }
1
+ import { type ConsentService } from '@owlmeans/consent';
2
+ import type { GoogleTagKind, GoogleTagOptions, GtmOptions } from './types.js';
26
3
  /**
27
4
  * The inline `<head>` snippet, consent first and the container second.
28
5
  *
@@ -61,28 +38,10 @@ export declare const gtmNoscriptFrame: (opts: GtmOptions) => string;
61
38
  * update where it becomes true and unsubscribes. `'advanced'` appends it immediately, as before.
62
39
  */
63
40
  export declare const loadGtm: (opts: GtmOptions) => void;
64
- /**
65
- * Which loader an id takes: `gtm` is the Tag Manager container (`gtm.js`), `gtag` the Google tag
66
- * (`gtag.js`) — Google Analytics 4 (`G-`), a Google tag (`GT-`), Google Ads (`AW-`) and
67
- * Floodlight (`DC-`).
68
- */
69
- export type GoogleTagKind = 'gtm' | 'gtag';
70
41
  /** Whether `id` is a Tag Manager container or Google tag id this package can load. */
71
42
  export declare const isGoogleTagId: (id: string) => boolean;
72
43
  /** The loader `id` takes, or `null` when it is not a loadable id at all. */
73
44
  export declare const googleTagKind: (id: string) => GoogleTagKind | null;
74
- export interface GoogleTagOptions extends ConsentOptions {
75
- /** `GTM-…`, `G-…`, `GT-…`, `AW-…` or `DC-…` — see {@link isGoogleTagId}. */
76
- id: string;
77
- /**
78
- * The queue name, when a page runs more than one tag. Consent Mode from `@owlmeans/consent` —
79
- * the bootstrap AND the dialog's later updates — always speaks on `window.dataLayer`, so a tag
80
- * given another queue never hears a consent command. Leave it unset on a consent-gated page.
81
- */
82
- dataLayerName?: string;
83
- /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
84
- mode?: GoogleTagMode;
85
- }
86
45
  /**
87
46
  * The one inline `<head>` script for a Google tag of any kind: the consent bootstrap FIRST, then
88
47
  * ads redaction, then the loader the id's prefix calls for — the Tag Manager container for `GTM-`,
@@ -106,33 +65,6 @@ export interface GoogleTagOptions extends ConsentOptions {
106
65
  * `mode: 'advanced'` for the original, unconditional load.
107
66
  */
108
67
  export declare const googleTagHeadScript: (opts: GoogleTagOptions) => string;
109
- /**
110
- * The hosts a page running a Google tag has to allow in `script-src`, `connect-src` and
111
- * `img-src`, for Google Analytics 4, Tag Manager and Google Ads together.
112
- *
113
- * Taken from Google's CSP guide (developers.google.com/tag-platform/security/guides/csp) and
114
- * folded into one list, because the platform adds one list to all three directives:
115
- *
116
- * - `*.googletagmanager.com` — gtm.js and gtag/js (`www.`), and their measurement pings;
117
- * - `*.google-analytics.com` — GA4 collection (`region1.`, `www.`);
118
- * - `*.g.doubleclick.net` — GA4 with Google signals and Ads (`stats.g.`, `googleads.g.`);
119
- * - `ad.doubleclick.net` — Ads and Floodlight conversions;
120
- * - `www.googleadservices.com` — the Ads conversion script;
121
- * - `pagead2.googlesyndication.com` — Ads and GA4-with-ads pings;
122
- * - `*.google.com` — `www.google.com` (the Ads script, consent-mode pings), `adservice.`, and the
123
- * `analytics.google.com` collection endpoints.
124
- *
125
- * Every entry is an `https://` scheme, an optional `*.` label and a dotted host — the only shape
126
- * the publisher's CSP filter lets through — and there are few enough to stay inside its per-slot
127
- * cap. Google's country domains (`google.<TLD>`) cannot be named in that shape and are left out:
128
- * what is lost is a remarketing ping from visitors on those domains, never the tag itself.
129
- */
130
- export declare const GOOGLE_TAG_CSP_SOURCES: readonly string[];
131
- /**
132
- * The hosts a Google tag frames: Tag Manager's service-worker and preview frames, and the Ads
133
- * remarketing frame. For `frame-src`, which otherwise stays `'none'`.
134
- */
135
- export declare const GOOGLE_TAG_FRAME_SOURCES: readonly string[];
136
68
  /**
137
69
  * What a Google tag discloses on the cookie policy, for `CookiePolicy`'s `services`.
138
70
  *
@@ -144,4 +76,6 @@ export declare const GOOGLE_TAG_FRAME_SOURCES: readonly string[];
144
76
  * {@link googleTagHeadScript}, which loads nothing for it.
145
77
  */
146
78
  export declare const googleTagServices: (id: string) => ConsentService[];
79
+ export type { GoogleTagKind, GoogleTagMode, GoogleTagOptions, GtmOptions } from './types.js';
80
+ export { GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_DEFAULT_MODE, GOOGLE_TAG_FRAME_SOURCES } from './consts.js';
147
81
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvE,kGAAkG;AAClG,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAuB,CAAA;AAE7D,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;AAYD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,SAAU,UAAU,KAAG,MAShD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,SAAU,UAAU,KAAG,MAOnD,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,SAAU,UAAU,KAAG,IA0C1C,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAA;AAc1C,sFAAsF;AACtF,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,OACO,CAAA;AAElD,4EAA4E;AAC5E,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,aAAa,GAAG,IACS,CAAA;AAEpE,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;AAyDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,mBAAmB,SAAU,gBAAgB,KAAG,MAe5D,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAQlD,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAGpD,CAAA;AAkBF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,OAAQ,MAAM,KAAG,cAAc,EA8C5D,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkH,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGvK,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAY7E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,SAAU,UAAU,KAAG,MAShD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,SAAU,UAAU,KAAG,MAOnD,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,SAAU,UAAU,KAAG,IA0C1C,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,OACO,CAAA;AAElD,4EAA4E;AAC5E,eAAO,MAAM,aAAa,OAAQ,MAAM,KAAG,aAAa,GAAG,IACS,CAAA;AAsDpE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,mBAAmB,SAAU,gBAAgB,KAAG,MAe5D,CAAA;AAYD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,OAAQ,MAAM,KAAG,cAAc,EA8C5D,CAAA;AAED,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,18 +1,6 @@
1
- import { CONSENT_ANALYTICS, CONSENT_MARKETING, consentBootstrapScript, consentGateScript, consentStore, trackingGranted, } from '@owlmeans/consent';
2
- /**
3
- * The platform default for {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}.
4
- *
5
- * One named constant so flipping it later is a one-line change.
6
- *
7
- * - `'basic'` withholds the loader until a signal-bearing category is granted — which holds back
8
- * Google's own IP receipt too, not only what a granted tag may then do with it.
9
- * - `'advanced'` loads immediately with Consent Mode signals denied — today's only behavior, and
10
- * Google's own recommended default for its own conversion modeling.
11
- *
12
- * Default is `'basic'`: read it as the EU/DE worst-case reading of ePrivacy Art. 5(3) — no third
13
- * party receives a connection before consent.
14
- */
15
- export const GOOGLE_TAG_DEFAULT_MODE = 'basic';
1
+ import { CONSENT_ANALYTICS, CONSENT_MARKETING, consentBootstrapScript, consentGateScript, consentStore, trackingGranted } 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';
16
4
  /** Google's own container IIFE, the id and queue name injected as JSON data. */
17
5
  const gtmContainerScript = (layer, id) => `(function(w,d,s,l,i){w[l]=w[l]||[];` +
18
6
  `w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` +
@@ -108,23 +96,10 @@ export const loadGtm = (opts) => {
108
96
  }
109
97
  });
110
98
  };
111
- /**
112
- * The id shapes Google issues today, and nothing else.
113
- *
114
- * Deliberately strict — upper case, a known prefix, a bounded run of letters and digits — because
115
- * the id is typed by a person into a settings form and ends up inside an inline script and a
116
- * script URL. A value this pattern accepts cannot carry a quote, a tag or a second parameter, so
117
- * the check is what makes it safe to store, not only to emit. `UA-` is not here: Universal
118
- * Analytics stopped processing data in 2024, and accepting its ids would promise measurement that
119
- * never arrives.
120
- */
121
- const GOOGLE_TAG_ID = /^(GTM-[A-Z0-9]{4,12}|(G|GT|AW|DC)-[A-Z0-9]{4,16})$/;
122
99
  /** Whether `id` is a Tag Manager container or Google tag id this package can load. */
123
100
  export const isGoogleTagId = (id) => typeof id === 'string' && GOOGLE_TAG_ID.test(id);
124
101
  /** The loader `id` takes, or `null` when it is not a loadable id at all. */
125
102
  export const googleTagKind = (id) => !isGoogleTagId(id) ? null : id.startsWith('GTM-') ? 'gtm' : 'gtag';
126
- /** A queue name has to be a plain identifier: it becomes `window[name]` in the emitted script. */
127
- const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
128
103
  const layerOf = (opts) => opts.dataLayerName != null && JS_IDENTIFIER.test(opts.dataLayerName)
129
104
  ? opts.dataLayerName
130
105
  : 'dataLayer';
@@ -205,56 +180,12 @@ export const googleTagHeadScript = (opts) => {
205
180
  }
206
181
  return inlineSafe(`${bootstrap};${redaction};${loader}`);
207
182
  };
208
- /**
209
- * The hosts a page running a Google tag has to allow in `script-src`, `connect-src` and
210
- * `img-src`, for Google Analytics 4, Tag Manager and Google Ads together.
211
- *
212
- * Taken from Google's CSP guide (developers.google.com/tag-platform/security/guides/csp) and
213
- * folded into one list, because the platform adds one list to all three directives:
214
- *
215
- * - `*.googletagmanager.com` — gtm.js and gtag/js (`www.`), and their measurement pings;
216
- * - `*.google-analytics.com` — GA4 collection (`region1.`, `www.`);
217
- * - `*.g.doubleclick.net` — GA4 with Google signals and Ads (`stats.g.`, `googleads.g.`);
218
- * - `ad.doubleclick.net` — Ads and Floodlight conversions;
219
- * - `www.googleadservices.com` — the Ads conversion script;
220
- * - `pagead2.googlesyndication.com` — Ads and GA4-with-ads pings;
221
- * - `*.google.com` — `www.google.com` (the Ads script, consent-mode pings), `adservice.`, and the
222
- * `analytics.google.com` collection endpoints.
223
- *
224
- * Every entry is an `https://` scheme, an optional `*.` label and a dotted host — the only shape
225
- * the publisher's CSP filter lets through — and there are few enough to stay inside its per-slot
226
- * cap. Google's country domains (`google.<TLD>`) cannot be named in that shape and are left out:
227
- * what is lost is a remarketing ping from visitors on those domains, never the tag itself.
228
- */
229
- export const GOOGLE_TAG_CSP_SOURCES = Object.freeze([
230
- 'https://*.googletagmanager.com',
231
- 'https://*.google-analytics.com',
232
- 'https://*.g.doubleclick.net',
233
- 'https://ad.doubleclick.net',
234
- 'https://www.googleadservices.com',
235
- 'https://pagead2.googlesyndication.com',
236
- 'https://*.google.com',
237
- ]);
238
- /**
239
- * The hosts a Google tag frames: Tag Manager's service-worker and preview frames, and the Ads
240
- * remarketing frame. For `frame-src`, which otherwise stays `'none'`.
241
- */
242
- export const GOOGLE_TAG_FRAME_SOURCES = Object.freeze([
243
- 'https://www.googletagmanager.com',
244
- 'https://td.doubleclick.net',
245
- ]);
246
- const GOOGLE = 'Google LLC';
247
- const GOOGLE_PRIVACY = 'https://policies.google.com/privacy';
248
183
  const analyticsService = (name, purpose, cookies) => ({
249
184
  name, provider: GOOGLE, category: CONSENT_ANALYTICS, purpose, cookies, privacyHref: GOOGLE_PRIVACY,
250
185
  });
251
186
  const adsService = (name, purpose, cookies) => ({
252
187
  name, provider: GOOGLE, category: CONSENT_MARKETING, purpose, cookies, privacyHref: GOOGLE_PRIVACY,
253
188
  });
254
- const UNTIL_ANALYTICS = 'Until analytics cookies are accepted it sets no cookies and sends only '
255
- + 'cookieless measurement signals.';
256
- const UNTIL_MARKETING = 'Until marketing cookies are accepted it sets no cookies and ad click '
257
- + 'identifiers are redacted.';
258
189
  /**
259
190
  * What a Google tag discloses on the cookie policy, for `CookiePolicy`'s `services`.
260
191
  *
@@ -291,4 +222,5 @@ export const googleTagServices = (id) => {
291
222
  }
292
223
  }
293
224
  };
225
+ export { GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_DEFAULT_MODE, GOOGLE_TAG_FRAME_SOURCES } from './consts.js';
294
226
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.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.32",
3
+ "version": "0.1.18-rc.34",
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.9"
25
+ "@owlmeans/consent": "^0.1.18-rc.10"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@owlmeans/dep-config": "workspace:*",
29
- "@types/bun": "^1.4.0",
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
+ ])
package/src/index.ts CHANGED
@@ -1,35 +1,7 @@
1
- import {
2
- CONSENT_ANALYTICS, CONSENT_MARKETING, consentBootstrapScript, consentGateScript, consentStore,
3
- trackingGranted,
4
- } from '@owlmeans/consent'
5
- import type { ConsentOptions, ConsentService } from '@owlmeans/consent'
6
-
7
- /** `'gtm'` container / `'gtag'` — see {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}. */
8
- export type GoogleTagMode = 'basic' | 'advanced'
9
-
10
- /**
11
- * The platform default for {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}.
12
- *
13
- * One named constant so flipping it later is a one-line change.
14
- *
15
- * - `'basic'` withholds the loader until a signal-bearing category is granted — which holds back
16
- * Google's own IP receipt too, not only what a granted tag may then do with it.
17
- * - `'advanced'` loads immediately with Consent Mode signals denied — today's only behavior, and
18
- * Google's own recommended default for its own conversion modeling.
19
- *
20
- * Default is `'basic'`: read it as the EU/DE worst-case reading of ePrivacy Art. 5(3) — no third
21
- * party receives a connection before consent.
22
- */
23
- export const GOOGLE_TAG_DEFAULT_MODE: GoogleTagMode = 'basic'
24
-
25
- export interface GtmOptions extends ConsentOptions {
26
- /** Container id, e.g. `GTM-XXXXXXX`. */
27
- id: string
28
- /** The queue name, when a page runs more than one container. */
29
- dataLayerName?: string
30
- /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
31
- mode?: GoogleTagMode
32
- }
1
+ import { CONSENT_ANALYTICS, CONSENT_MARKETING, consentBootstrapScript, consentGateScript, consentStore, trackingGranted, type ConsentService } 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
+ import type { GoogleTagKind, GoogleTagOptions, GtmOptions } from './types.js'
33
5
 
34
6
  /** Google's own container IIFE, the id and queue name injected as JSON data. */
35
7
  const gtmContainerScript = (layer: string, id: string): string =>
@@ -140,25 +112,6 @@ export const loadGtm = (opts: GtmOptions): void => {
140
112
  })
141
113
  }
142
114
 
143
- /**
144
- * Which loader an id takes: `gtm` is the Tag Manager container (`gtm.js`), `gtag` the Google tag
145
- * (`gtag.js`) — Google Analytics 4 (`G-`), a Google tag (`GT-`), Google Ads (`AW-`) and
146
- * Floodlight (`DC-`).
147
- */
148
- export type GoogleTagKind = 'gtm' | 'gtag'
149
-
150
- /**
151
- * The id shapes Google issues today, and nothing else.
152
- *
153
- * Deliberately strict — upper case, a known prefix, a bounded run of letters and digits — because
154
- * the id is typed by a person into a settings form and ends up inside an inline script and a
155
- * script URL. A value this pattern accepts cannot carry a quote, a tag or a second parameter, so
156
- * the check is what makes it safe to store, not only to emit. `UA-` is not here: Universal
157
- * Analytics stopped processing data in 2024, and accepting its ids would promise measurement that
158
- * never arrives.
159
- */
160
- const GOOGLE_TAG_ID = /^(GTM-[A-Z0-9]{4,12}|(G|GT|AW|DC)-[A-Z0-9]{4,16})$/
161
-
162
115
  /** Whether `id` is a Tag Manager container or Google tag id this package can load. */
163
116
  export const isGoogleTagId = (id: string): boolean =>
164
117
  typeof id === 'string' && GOOGLE_TAG_ID.test(id)
@@ -167,22 +120,6 @@ export const isGoogleTagId = (id: string): boolean =>
167
120
  export const googleTagKind = (id: string): GoogleTagKind | null =>
168
121
  !isGoogleTagId(id) ? null : id.startsWith('GTM-') ? 'gtm' : 'gtag'
169
122
 
170
- export interface GoogleTagOptions extends ConsentOptions {
171
- /** `GTM-…`, `G-…`, `GT-…`, `AW-…` or `DC-…` — see {@link isGoogleTagId}. */
172
- id: string
173
- /**
174
- * The queue name, when a page runs more than one tag. Consent Mode from `@owlmeans/consent` —
175
- * the bootstrap AND the dialog's later updates — always speaks on `window.dataLayer`, so a tag
176
- * given another queue never hears a consent command. Leave it unset on a consent-gated page.
177
- */
178
- dataLayerName?: string
179
- /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
180
- mode?: GoogleTagMode
181
- }
182
-
183
- /** A queue name has to be a plain identifier: it becomes `window[name]` in the emitted script. */
184
- const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
185
-
186
123
  const layerOf = (opts: GoogleTagOptions): string =>
187
124
  opts.dataLayerName != null && JS_IDENTIFIER.test(opts.dataLayerName)
188
125
  ? opts.dataLayerName
@@ -274,48 +211,6 @@ export const googleTagHeadScript = (opts: GoogleTagOptions): string => {
274
211
  return inlineSafe(`${bootstrap};${redaction};${loader}`)
275
212
  }
276
213
 
277
- /**
278
- * The hosts a page running a Google tag has to allow in `script-src`, `connect-src` and
279
- * `img-src`, for Google Analytics 4, Tag Manager and Google Ads together.
280
- *
281
- * Taken from Google's CSP guide (developers.google.com/tag-platform/security/guides/csp) and
282
- * folded into one list, because the platform adds one list to all three directives:
283
- *
284
- * - `*.googletagmanager.com` — gtm.js and gtag/js (`www.`), and their measurement pings;
285
- * - `*.google-analytics.com` — GA4 collection (`region1.`, `www.`);
286
- * - `*.g.doubleclick.net` — GA4 with Google signals and Ads (`stats.g.`, `googleads.g.`);
287
- * - `ad.doubleclick.net` — Ads and Floodlight conversions;
288
- * - `www.googleadservices.com` — the Ads conversion script;
289
- * - `pagead2.googlesyndication.com` — Ads and GA4-with-ads pings;
290
- * - `*.google.com` — `www.google.com` (the Ads script, consent-mode pings), `adservice.`, and the
291
- * `analytics.google.com` collection endpoints.
292
- *
293
- * Every entry is an `https://` scheme, an optional `*.` label and a dotted host — the only shape
294
- * the publisher's CSP filter lets through — and there are few enough to stay inside its per-slot
295
- * cap. Google's country domains (`google.<TLD>`) cannot be named in that shape and are left out:
296
- * what is lost is a remarketing ping from visitors on those domains, never the tag itself.
297
- */
298
- export const GOOGLE_TAG_CSP_SOURCES: readonly string[] = Object.freeze([
299
- 'https://*.googletagmanager.com',
300
- 'https://*.google-analytics.com',
301
- 'https://*.g.doubleclick.net',
302
- 'https://ad.doubleclick.net',
303
- 'https://www.googleadservices.com',
304
- 'https://pagead2.googlesyndication.com',
305
- 'https://*.google.com',
306
- ])
307
-
308
- /**
309
- * The hosts a Google tag frames: Tag Manager's service-worker and preview frames, and the Ads
310
- * remarketing frame. For `frame-src`, which otherwise stays `'none'`.
311
- */
312
- export const GOOGLE_TAG_FRAME_SOURCES: readonly string[] = Object.freeze([
313
- 'https://www.googletagmanager.com',
314
- 'https://td.doubleclick.net',
315
- ])
316
-
317
- const GOOGLE = 'Google LLC'
318
- const GOOGLE_PRIVACY = 'https://policies.google.com/privacy'
319
214
 
320
215
  const analyticsService = (name: string, purpose: string, cookies: string[]): ConsentService => ({
321
216
  name, provider: GOOGLE, category: CONSENT_ANALYTICS, purpose, cookies, privacyHref: GOOGLE_PRIVACY,
@@ -325,10 +220,6 @@ const adsService = (name: string, purpose: string, cookies: string[]): ConsentSe
325
220
  name, provider: GOOGLE, category: CONSENT_MARKETING, purpose, cookies, privacyHref: GOOGLE_PRIVACY,
326
221
  })
327
222
 
328
- const UNTIL_ANALYTICS = 'Until analytics cookies are accepted it sets no cookies and sends only '
329
- + 'cookieless measurement signals.'
330
- const UNTIL_MARKETING = 'Until marketing cookies are accepted it sets no cookies and ad click '
331
- + 'identifiers are redacted.'
332
223
 
333
224
  /**
334
225
  * What a Google tag discloses on the cookie policy, for `CookiePolicy`'s `services`.
@@ -387,3 +278,6 @@ export const googleTagServices = (id: string): ConsentService[] => {
387
278
  }
388
279
  }
389
280
  }
281
+
282
+ export type { GoogleTagKind, GoogleTagMode, GoogleTagOptions, GtmOptions } from './types.js'
283
+ export { GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_DEFAULT_MODE, GOOGLE_TAG_FRAME_SOURCES } from './consts.js'
package/src/types.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { ConsentOptions } from '@owlmeans/consent'
2
+
3
+ /** `'gtm'` container / `'gtag'` — see {@link GtmOptions.mode} / {@link GoogleTagOptions.mode}. */
4
+ export type GoogleTagMode = 'basic' | 'advanced'
5
+
6
+ export interface GtmOptions extends ConsentOptions {
7
+ /** Container id, e.g. `GTM-XXXXXXX`. */
8
+ id: string
9
+ /** The queue name, when a page runs more than one container. */
10
+ dataLayerName?: string
11
+ /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
12
+ mode?: GoogleTagMode
13
+ }
14
+
15
+ /**
16
+ * Which loader an id takes: `gtm` is the Tag Manager container (`gtm.js`), `gtag` the Google tag
17
+ * (`gtag.js`) — Google Analytics 4 (`G-`), a Google tag (`GT-`), Google Ads (`AW-`) and
18
+ * Floodlight (`DC-`).
19
+ */
20
+ export type GoogleTagKind = 'gtm' | 'gtag'
21
+
22
+ export interface GoogleTagOptions extends ConsentOptions {
23
+ /** `GTM-…`, `G-…`, `GT-…`, `AW-…` or `DC-…` — see {@link isGoogleTagId}. */
24
+ id: string
25
+ /**
26
+ * The queue name, when a page runs more than one tag. Consent Mode from `@owlmeans/consent` —
27
+ * the bootstrap AND the dialog's later updates — always speaks on `window.dataLayer`, so a tag
28
+ * given another queue never hears a consent command. Leave it unset on a consent-gated page.
29
+ */
30
+ dataLayerName?: string
31
+ /** See {@link GOOGLE_TAG_DEFAULT_MODE}. Defaults to `GOOGLE_TAG_DEFAULT_MODE` (`'basic'`). */
32
+ mode?: GoogleTagMode
33
+ }
package/tests/tag.spec.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import {
3
- CONSENT_EVENT, CONSENT_KEY, DEFAULT_CONSENT_CATEGORIES, consentBootstrapScript,
4
- } from '@owlmeans/consent'
5
- import type { ConsentCategory } from '@owlmeans/consent'
6
- import {
7
- GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_FRAME_SOURCES, googleTagHeadScript, googleTagKind,
8
- googleTagServices, isGoogleTagId,
9
- } from '../src/index.js'
2
+ import { CONSENT_EVENT, CONSENT_KEY, DEFAULT_CONSENT_CATEGORIES, consentBootstrapScript, type ConsentCategory } from '@owlmeans/consent'
3
+ import { googleTagHeadScript, googleTagKind, googleTagServices, isGoogleTagId } from '../src/index.js'
4
+ import { GOOGLE_TAG_CSP_SOURCES, GOOGLE_TAG_FRAME_SOURCES } from '../src/consts.js'
10
5
 
11
6
  /**
12
7
  * The publisher's filter for slot-supplied CSP sources (`CSP_SOURCE_SHAPE` in viable's