@owlmeans/consent 0.1.18-rc.9 → 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.
Files changed (115) hide show
  1. package/README.md +229 -0
  2. package/build/consts.d.ts +78 -3
  3. package/build/consts.d.ts.map +1 -1
  4. package/build/consts.js +104 -0
  5. package/build/consts.js.map +1 -1
  6. package/build/consts.local.d.ts +12 -0
  7. package/build/consts.local.d.ts.map +1 -0
  8. package/build/consts.local.js +12 -0
  9. package/build/consts.local.js.map +1 -0
  10. package/build/geo/types.d.ts +47 -0
  11. package/build/geo/types.d.ts.map +1 -0
  12. package/build/geo/types.js +2 -0
  13. package/build/geo/types.js.map +1 -0
  14. package/build/geo.d.ts +4 -0
  15. package/build/geo.d.ts.map +1 -0
  16. package/build/geo.js +96 -0
  17. package/build/geo.js.map +1 -0
  18. package/build/gtobserver/types.d.ts +75 -0
  19. package/build/gtobserver/types.d.ts.map +1 -0
  20. package/build/gtobserver/types.js +2 -0
  21. package/build/gtobserver/types.js.map +1 -0
  22. package/build/gtobserver.d.ts +7 -0
  23. package/build/gtobserver.d.ts.map +1 -0
  24. package/build/gtobserver.js +142 -0
  25. package/build/gtobserver.js.map +1 -0
  26. package/build/i18n/be.json +8 -1
  27. package/build/i18n/de.json +8 -1
  28. package/build/i18n/en.json +8 -1
  29. package/build/i18n/es.json +8 -1
  30. package/build/i18n/fr.json +8 -1
  31. package/build/i18n/pl.json +8 -1
  32. package/build/i18n/ru.json +8 -1
  33. package/build/i18n/types.d.ts +11 -0
  34. package/build/i18n/types.d.ts.map +1 -0
  35. package/build/i18n/types.js +2 -0
  36. package/build/i18n/types.js.map +1 -0
  37. package/build/i18n/uk.json +8 -1
  38. package/build/i18n.d.ts +4 -14
  39. package/build/i18n.d.ts.map +1 -1
  40. package/build/i18n.js +12 -27
  41. package/build/i18n.js.map +1 -1
  42. package/build/index.d.ts +13 -5
  43. package/build/index.d.ts.map +1 -1
  44. package/build/index.js +5 -3
  45. package/build/index.js.map +1 -1
  46. package/build/linker/types.d.ts +59 -0
  47. package/build/linker/types.d.ts.map +1 -0
  48. package/build/linker/types.js +2 -0
  49. package/build/linker/types.js.map +1 -0
  50. package/build/linker.d.ts +5 -74
  51. package/build/linker.d.ts.map +1 -1
  52. package/build/linker.js +290 -324
  53. package/build/linker.js.map +1 -1
  54. package/build/plugins/types.d.ts +42 -0
  55. package/build/plugins/types.d.ts.map +1 -0
  56. package/build/plugins/types.js +2 -0
  57. package/build/plugins/types.js.map +1 -0
  58. package/build/plugins.d.ts +4 -68
  59. package/build/plugins.d.ts.map +1 -1
  60. package/build/plugins.js +73 -61
  61. package/build/plugins.js.map +1 -1
  62. package/build/storage/types.d.ts +31 -0
  63. package/build/storage/types.d.ts.map +1 -0
  64. package/build/storage/types.js +2 -0
  65. package/build/storage/types.js.map +1 -0
  66. package/build/storage.d.ts +6 -22
  67. package/build/storage.d.ts.map +1 -1
  68. package/build/storage.js +72 -81
  69. package/build/storage.js.map +1 -1
  70. package/build/store.d.ts +2 -2
  71. package/build/store.d.ts.map +1 -1
  72. package/build/store.js +115 -31
  73. package/build/store.js.map +1 -1
  74. package/build/types.d.ts +141 -5
  75. package/build/types.d.ts.map +1 -1
  76. package/build/types.local.d.ts +9 -0
  77. package/build/types.local.d.ts.map +1 -0
  78. package/build/types.local.js +2 -0
  79. package/build/types.local.js.map +1 -0
  80. package/package.json +3 -3
  81. package/src/consts.local.ts +16 -0
  82. package/src/consts.ts +120 -3
  83. package/src/geo/types.ts +49 -0
  84. package/src/geo.ts +122 -0
  85. package/src/gtobserver/types.ts +73 -0
  86. package/src/gtobserver.ts +182 -0
  87. package/src/i18n/be.json +8 -1
  88. package/src/i18n/de.json +8 -1
  89. package/src/i18n/en.json +8 -1
  90. package/src/i18n/es.json +8 -1
  91. package/src/i18n/fr.json +8 -1
  92. package/src/i18n/pl.json +8 -1
  93. package/src/i18n/ru.json +8 -1
  94. package/src/i18n/types.ts +11 -0
  95. package/src/i18n/uk.json +8 -1
  96. package/src/i18n.ts +20 -32
  97. package/src/index.ts +13 -12
  98. package/src/linker/types.ts +59 -0
  99. package/src/linker.ts +301 -349
  100. package/src/plugins/types.ts +42 -0
  101. package/src/plugins.ts +82 -105
  102. package/src/storage/types.ts +31 -0
  103. package/src/storage.ts +75 -77
  104. package/src/store.ts +125 -31
  105. package/src/types.local.ts +9 -0
  106. package/src/types.ts +148 -5
  107. package/tests/consent.spec.ts +26 -28
  108. package/tests/geo.spec.ts +474 -0
  109. package/tests/linker.spec.ts +68 -74
  110. package/tests/tracking.spec.ts +14 -14
  111. package/build/gtm.d.ts +0 -72
  112. package/build/gtm.d.ts.map +0 -1
  113. package/build/gtm.js +0 -182
  114. package/build/gtm.js.map +0 -1
  115. package/src/gtm.ts +0 -220
package/build/gtm.js DELETED
@@ -1,182 +0,0 @@
1
- import { CONSENT_EVENT, CONSENT_KEY, CONSENT_SETUP_FLAG, CONSENT_SIGNAL_DEFAULTS, DEFAULT_CONSENT_CATEGORIES, } from './consts.js';
2
- import { consentLinkerScript } from './linker.js';
3
- const global = () => typeof window !== 'undefined' ? window : null;
4
- /**
5
- * Push onto the tag-manager queue in the shape Google's own snippet uses.
6
- *
7
- * `arguments`, not an array literal. Both work with today's Consent API, but `arguments` is what
8
- * `gtag.js` itself emits, and a page that carries both this and Google's snippet should not have
9
- * two shapes in one queue.
10
- */
11
- export function gtagConsent(..._args) {
12
- const win = global();
13
- if (win == null) {
14
- return;
15
- }
16
- win.dataLayer = win.dataLayer ?? [];
17
- // `arguments`, not `_args`. The rest parameter is only here to type the call sites; what goes on
18
- // the queue must be the arguments object itself.
19
- // eslint-disable-next-line prefer-rest-params
20
- win.dataLayer.push(arguments);
21
- }
22
- const categoriesOf = (opts) => opts?.categories ?? DEFAULT_CONSENT_CATEGORIES;
23
- /** Every signal the configured categories drive, at its pre-consent value. */
24
- export const consentDefaults = (categories) => {
25
- const used = new Set((categories ?? DEFAULT_CONSENT_CATEGORIES)
26
- .flatMap(category => category.signals ?? []));
27
- return Object.fromEntries(Object.entries(CONSENT_SIGNAL_DEFAULTS)
28
- .filter(([signal]) => used.has(signal)));
29
- };
30
- /** Every signal, at the value this record implies. */
31
- export const consentUpdate = (record, categories) => {
32
- const update = {};
33
- for (const category of categories ?? DEFAULT_CONSENT_CATEGORIES) {
34
- const granted = category.required === true || record[category.key] === true;
35
- for (const signal of category.signals ?? []) {
36
- // A signal named by two categories is granted only when every one of them is.
37
- update[signal] = granted && update[signal] !== 'denied' ? 'granted' : 'denied';
38
- }
39
- }
40
- return update;
41
- };
42
- /**
43
- * Declare what is denied, before any tag can act.
44
- *
45
- * Idempotent through a window flag, because the page may carry this call twice — once inline in
46
- * the document head, once from whatever bundle mounts the dialog — and a second `default` after a
47
- * tag has loaded is worse than none: it can widen what was already narrowed.
48
- */
49
- export const pushConsentDefaults = (opts) => {
50
- const win = global();
51
- if (win == null || opts?.silent === true || win[CONSENT_SETUP_FLAG] === true) {
52
- return;
53
- }
54
- gtagConsent('consent', 'default', consentDefaults(categoriesOf(opts)));
55
- win[CONSENT_SETUP_FLAG] = true;
56
- };
57
- /**
58
- * Apply a decision: globals first, then the signal update, then any per-category events.
59
- *
60
- * The order is load-bearing. A tag that fires on the update event reads the globals in the same
61
- * turn, so writing them afterwards would let the first firing see the previous answer.
62
- */
63
- export const applyConsent = (record, opts) => {
64
- const win = global();
65
- if (win == null || opts?.silent === true) {
66
- return;
67
- }
68
- const categories = categoriesOf(opts);
69
- for (const category of categories) {
70
- if (category.globalVar != null) {
71
- win[category.globalVar] = category.required === true || record[category.key] === true;
72
- }
73
- }
74
- gtagConsent('consent', 'update', consentUpdate(record, categories));
75
- for (const category of categories) {
76
- if (category.event != null && (category.required === true || record[category.key] === true)) {
77
- win.dataLayer = win.dataLayer ?? [];
78
- win.dataLayer.push({ event: category.event });
79
- }
80
- }
81
- // A gate script (`consentGateScript`) that already decided NOT to load, because nothing was
82
- // granted at the time it ran, has no other way to hear this. Consent Mode itself speaks only on
83
- // `dataLayer`, which a tag that never loaded is not listening to.
84
- if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function'
85
- && typeof CustomEvent === 'function') {
86
- window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: { record } }));
87
- }
88
- };
89
- /**
90
- * Whether a stored decision grants tracking at all — a signal-bearing category, not merely the
91
- * required essential one.
92
- *
93
- * "Signal-bearing" is what a required category can never be: it exists to gate whether a LOADER
94
- * runs, and a required category is disclosure, not a question — everyone gets it. Only a category
95
- * that both is optional and drives at least one Consent Mode signal counts, which is exactly what
96
- * `googleTagHeadScript`'s `'basic'` mode gates a Google tag behind.
97
- */
98
- export const trackingGranted = (record, categories = DEFAULT_CONSENT_CATEGORIES) => record != null && categories.some(category => !category.required && (category.signals?.length ?? 0) > 0 && record[category.key] === true);
99
- /**
100
- * The inline script a document stamps ABOVE its tag-manager snippet.
101
- *
102
- * It exists because the defaults have to be on the queue before `gtm.js` loads, and a React bundle
103
- * cannot be: by the time an island mounts, the container has been running for hundreds of
104
- * milliseconds and has already decided what it may do. Emitting it from the HTML is the only
105
- * ordering that holds — and it reads the stored record too, so a returning visitor's tags are not
106
- * denied for the first paint of every page.
107
- */
108
- export const consentBootstrapScript = (opts) => {
109
- const defaults = JSON.stringify(consentDefaults(categoriesOf(opts)));
110
- const categories = JSON.stringify(categoriesOf(opts).map(category => ({
111
- key: category.key,
112
- required: category.required === true,
113
- signals: category.signals ?? [],
114
- globalVar: category.globalVar ?? null,
115
- })));
116
- const storageKey = JSON.stringify(opts?.storageKey ?? CONSENT_KEY);
117
- const flag = JSON.stringify(CONSENT_SETUP_FLAG);
118
- // Right after the default is pushed and before EITHER storage read below (this one, and the
119
- // `consentGateScript` that is concatenated after this whole IIFE) — an adopted decision must
120
- // already be in storage by the time anything reads it, so `gtm.js`'s own `page_view` never sees
121
- // the parameter and a granted decision loads the tag on arrival. A page with no `opts.linker`
122
- // gets an empty string here, unchanged from before this existed.
123
- const linker = opts?.linker != null ? `${consentLinkerScript(opts)};` : '';
124
- return `(function(w,d){` +
125
- `w.dataLayer=w.dataLayer||[];function g(){w.dataLayer.push(arguments)}` +
126
- `if(w[${flag}])return;g('consent','default',${defaults});w[${flag}]=true;` +
127
- linker +
128
- `var raw=null;try{raw=w.localStorage.getItem(${storageKey})}catch(e){}` +
129
- `if(!raw){var p=('; '+d.cookie).split('; '+${storageKey}+'=');` +
130
- `if(p.length===2){raw=p.pop().split(';').shift()}}` +
131
- `if(!raw)return;var r;try{r=JSON.parse(raw)}catch(e){return}` +
132
- `var cs=${categories},u={};` +
133
- `for(var i=0;i<cs.length;i++){var c=cs[i];var ok=c.required||r[c.key]===true;` +
134
- `if(c.globalVar){w[c.globalVar]=ok}` +
135
- `for(var j=0;j<c.signals.length;j++){var s=c.signals[j];` +
136
- `u[s]=ok&&u[s]!=='denied'?'granted':'denied'}}` +
137
- `g('consent','update',u)})(window,document)`;
138
- };
139
- /**
140
- * Withhold a loader until a stored or later decision grants tracking.
141
- *
142
- * The counterpart to `consentBootstrapScript` for the `'basic'` gated loading mode
143
- * (`@owlmeans/web-gtm`'s `GOOGLE_TAG_DEFAULT_MODE`): where the bootstrap always declares Consent
144
- * Mode's defaults so a tag may load cookieless, this withholds the tag itself — the loader string
145
- * is only ever reached once `trackingGranted` is true, either because a returning visitor's stored
146
- * record already says so, or because `applyConsent` later dispatches {@link CONSENT_EVENT} with
147
- * one that does. The listener removes itself on the first passing event, so the loader — which
148
- * marks its own element and refuses to run twice — is asked to run at most once from here.
149
- *
150
- * `loaderExpr` is a complete statement or expression that runs the loader when reached — the
151
- * self-invoking IIFE string `googleTagHeadScript`'s `gtagScript`/`gtmContainerScript` already
152
- * produce, embedded here verbatim rather than called. It is placed inline exactly as
153
- * `consentBootstrapScript`'s own output is: dynamic values (the storage key, the category shape,
154
- * the event name) are JSON-encoded so nothing configured can break out of a string literal, and
155
- * escaping the RESULT for HTML (`</script`, `<!--`) is left to whoever composes the final inline
156
- * script — the same discipline `consentBootstrapScript` follows, and `googleTagHeadScript` already
157
- * applies to the whole script it stamps.
158
- */
159
- export const consentGateScript = (loaderExpr, opts) => {
160
- const gate = JSON.stringify(categoriesOf(opts).map(category => ({
161
- key: category.key,
162
- required: category.required === true,
163
- hasSignal: (category.signals ?? []).length > 0,
164
- })));
165
- const storageKey = JSON.stringify(opts?.storageKey ?? CONSENT_KEY);
166
- const event = JSON.stringify(CONSENT_EVENT);
167
- return `(function(w,d){` +
168
- `function ok(r){if(!r)return false;var cs=${gate};` +
169
- `for(var i=0;i<cs.length;i++){var c=cs[i];` +
170
- `if(!c.required&&c.hasSignal&&r[c.key]===true)return true}return false}` +
171
- `function load(){${loaderExpr}}` +
172
- `var raw=null;try{raw=w.localStorage.getItem(${storageKey})}catch(e){}` +
173
- `if(!raw){var p=('; '+d.cookie).split('; '+${storageKey}+'=');` +
174
- `if(p.length===2){raw=p.pop().split(';').shift()}}` +
175
- `var r=null;if(raw){try{r=JSON.parse(raw)}catch(e){r=null}}` +
176
- `if(ok(r)){load();return}` +
177
- `function h(ev){if(ok(ev&&ev.detail&&ev.detail.record)){` +
178
- `w.removeEventListener(${event},h);load()}}` +
179
- `w.addEventListener(${event},h)` +
180
- `})(window,document)`;
181
- };
182
- //# sourceMappingURL=gtm.js.map
package/build/gtm.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"gtm.js","sourceRoot":"","sources":["../src/gtm.ts"],"names":[],"mappings":"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"}
package/src/gtm.ts DELETED
@@ -1,220 +0,0 @@
1
- import {
2
- CONSENT_EVENT, CONSENT_KEY, CONSENT_SETUP_FLAG, CONSENT_SIGNAL_DEFAULTS,
3
- DEFAULT_CONSENT_CATEGORIES,
4
- } from './consts.js'
5
- import { consentLinkerScript } from './linker.js'
6
- import type { ConsentCategory, ConsentOptions, ConsentRecord } from './types.js'
7
-
8
- interface ConsentWindow {
9
- dataLayer?: unknown[]
10
- [flag: string]: unknown
11
- }
12
-
13
- const global = (): ConsentWindow | null =>
14
- typeof window !== 'undefined' ? window as unknown as ConsentWindow : null
15
-
16
- /**
17
- * Push onto the tag-manager queue in the shape Google's own snippet uses.
18
- *
19
- * `arguments`, not an array literal. Both work with today's Consent API, but `arguments` is what
20
- * `gtag.js` itself emits, and a page that carries both this and Google's snippet should not have
21
- * two shapes in one queue.
22
- */
23
- export function gtagConsent(..._args: unknown[]): void {
24
- const win = global()
25
- if (win == null) {
26
- return
27
- }
28
- win.dataLayer = win.dataLayer ?? []
29
- // `arguments`, not `_args`. The rest parameter is only here to type the call sites; what goes on
30
- // the queue must be the arguments object itself.
31
- // eslint-disable-next-line prefer-rest-params
32
- win.dataLayer.push(arguments)
33
- }
34
-
35
- const categoriesOf = (opts?: ConsentOptions): ConsentCategory[] =>
36
- opts?.categories ?? DEFAULT_CONSENT_CATEGORIES
37
-
38
- /** Every signal the configured categories drive, at its pre-consent value. */
39
- export const consentDefaults = (
40
- categories?: ConsentCategory[]
41
- ): Record<string, 'granted' | 'denied'> => {
42
- const used = new Set((categories ?? DEFAULT_CONSENT_CATEGORIES)
43
- .flatMap(category => category.signals ?? []))
44
-
45
- return Object.fromEntries(Object.entries(CONSENT_SIGNAL_DEFAULTS)
46
- .filter(([signal]) => used.has(signal as never)))
47
- }
48
-
49
- /** Every signal, at the value this record implies. */
50
- export const consentUpdate = (
51
- record: ConsentRecord, categories?: ConsentCategory[]
52
- ): Record<string, 'granted' | 'denied'> => {
53
- const update: Record<string, 'granted' | 'denied'> = {}
54
- for (const category of categories ?? DEFAULT_CONSENT_CATEGORIES) {
55
- const granted = category.required === true || record[category.key] === true
56
- for (const signal of category.signals ?? []) {
57
- // A signal named by two categories is granted only when every one of them is.
58
- update[signal] = granted && update[signal] !== 'denied' ? 'granted' : 'denied'
59
- }
60
- }
61
-
62
- return update
63
- }
64
-
65
- /**
66
- * Declare what is denied, before any tag can act.
67
- *
68
- * Idempotent through a window flag, because the page may carry this call twice — once inline in
69
- * the document head, once from whatever bundle mounts the dialog — and a second `default` after a
70
- * tag has loaded is worse than none: it can widen what was already narrowed.
71
- */
72
- export const pushConsentDefaults = (opts?: ConsentOptions): void => {
73
- const win = global()
74
- if (win == null || opts?.silent === true || win[CONSENT_SETUP_FLAG] === true) {
75
- return
76
- }
77
- gtagConsent('consent', 'default', consentDefaults(categoriesOf(opts)))
78
- win[CONSENT_SETUP_FLAG] = true
79
- }
80
-
81
- /**
82
- * Apply a decision: globals first, then the signal update, then any per-category events.
83
- *
84
- * The order is load-bearing. A tag that fires on the update event reads the globals in the same
85
- * turn, so writing them afterwards would let the first firing see the previous answer.
86
- */
87
- export const applyConsent = (record: ConsentRecord, opts?: ConsentOptions): void => {
88
- const win = global()
89
- if (win == null || opts?.silent === true) {
90
- return
91
- }
92
- const categories = categoriesOf(opts)
93
-
94
- for (const category of categories) {
95
- if (category.globalVar != null) {
96
- win[category.globalVar] = category.required === true || record[category.key] === true
97
- }
98
- }
99
-
100
- gtagConsent('consent', 'update', consentUpdate(record, categories))
101
-
102
- for (const category of categories) {
103
- if (category.event != null && (category.required === true || record[category.key] === true)) {
104
- win.dataLayer = win.dataLayer ?? []
105
- win.dataLayer.push({ event: category.event })
106
- }
107
- }
108
-
109
- // A gate script (`consentGateScript`) that already decided NOT to load, because nothing was
110
- // granted at the time it ran, has no other way to hear this. Consent Mode itself speaks only on
111
- // `dataLayer`, which a tag that never loaded is not listening to.
112
- if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function'
113
- && typeof CustomEvent === 'function') {
114
- window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: { record } }))
115
- }
116
- }
117
-
118
- /**
119
- * Whether a stored decision grants tracking at all — a signal-bearing category, not merely the
120
- * required essential one.
121
- *
122
- * "Signal-bearing" is what a required category can never be: it exists to gate whether a LOADER
123
- * runs, and a required category is disclosure, not a question — everyone gets it. Only a category
124
- * that both is optional and drives at least one Consent Mode signal counts, which is exactly what
125
- * `googleTagHeadScript`'s `'basic'` mode gates a Google tag behind.
126
- */
127
- export const trackingGranted = (
128
- record: ConsentRecord | null, categories: ConsentCategory[] = DEFAULT_CONSENT_CATEGORIES
129
- ): boolean => record != null && categories.some(
130
- category => !category.required && (category.signals?.length ?? 0) > 0 && record[category.key] === true
131
- )
132
-
133
- /**
134
- * The inline script a document stamps ABOVE its tag-manager snippet.
135
- *
136
- * It exists because the defaults have to be on the queue before `gtm.js` loads, and a React bundle
137
- * cannot be: by the time an island mounts, the container has been running for hundreds of
138
- * milliseconds and has already decided what it may do. Emitting it from the HTML is the only
139
- * ordering that holds — and it reads the stored record too, so a returning visitor's tags are not
140
- * denied for the first paint of every page.
141
- */
142
- export const consentBootstrapScript = (opts?: ConsentOptions): string => {
143
- const defaults = JSON.stringify(consentDefaults(categoriesOf(opts)))
144
- const categories = JSON.stringify(categoriesOf(opts).map(category => ({
145
- key: category.key,
146
- required: category.required === true,
147
- signals: category.signals ?? [],
148
- globalVar: category.globalVar ?? null,
149
- })))
150
- const storageKey = JSON.stringify(opts?.storageKey ?? CONSENT_KEY)
151
- const flag = JSON.stringify(CONSENT_SETUP_FLAG)
152
- // Right after the default is pushed and before EITHER storage read below (this one, and the
153
- // `consentGateScript` that is concatenated after this whole IIFE) — an adopted decision must
154
- // already be in storage by the time anything reads it, so `gtm.js`'s own `page_view` never sees
155
- // the parameter and a granted decision loads the tag on arrival. A page with no `opts.linker`
156
- // gets an empty string here, unchanged from before this existed.
157
- const linker = opts?.linker != null ? `${consentLinkerScript(opts)};` : ''
158
-
159
- return `(function(w,d){` +
160
- `w.dataLayer=w.dataLayer||[];function g(){w.dataLayer.push(arguments)}` +
161
- `if(w[${flag}])return;g('consent','default',${defaults});w[${flag}]=true;` +
162
- linker +
163
- `var raw=null;try{raw=w.localStorage.getItem(${storageKey})}catch(e){}` +
164
- `if(!raw){var p=('; '+d.cookie).split('; '+${storageKey}+'=');` +
165
- `if(p.length===2){raw=p.pop().split(';').shift()}}` +
166
- `if(!raw)return;var r;try{r=JSON.parse(raw)}catch(e){return}` +
167
- `var cs=${categories},u={};` +
168
- `for(var i=0;i<cs.length;i++){var c=cs[i];var ok=c.required||r[c.key]===true;` +
169
- `if(c.globalVar){w[c.globalVar]=ok}` +
170
- `for(var j=0;j<c.signals.length;j++){var s=c.signals[j];` +
171
- `u[s]=ok&&u[s]!=='denied'?'granted':'denied'}}` +
172
- `g('consent','update',u)})(window,document)`
173
- }
174
-
175
- /**
176
- * Withhold a loader until a stored or later decision grants tracking.
177
- *
178
- * The counterpart to `consentBootstrapScript` for the `'basic'` gated loading mode
179
- * (`@owlmeans/web-gtm`'s `GOOGLE_TAG_DEFAULT_MODE`): where the bootstrap always declares Consent
180
- * Mode's defaults so a tag may load cookieless, this withholds the tag itself — the loader string
181
- * is only ever reached once `trackingGranted` is true, either because a returning visitor's stored
182
- * record already says so, or because `applyConsent` later dispatches {@link CONSENT_EVENT} with
183
- * one that does. The listener removes itself on the first passing event, so the loader — which
184
- * marks its own element and refuses to run twice — is asked to run at most once from here.
185
- *
186
- * `loaderExpr` is a complete statement or expression that runs the loader when reached — the
187
- * self-invoking IIFE string `googleTagHeadScript`'s `gtagScript`/`gtmContainerScript` already
188
- * produce, embedded here verbatim rather than called. It is placed inline exactly as
189
- * `consentBootstrapScript`'s own output is: dynamic values (the storage key, the category shape,
190
- * the event name) are JSON-encoded so nothing configured can break out of a string literal, and
191
- * escaping the RESULT for HTML (`</script`, `<!--`) is left to whoever composes the final inline
192
- * script — the same discipline `consentBootstrapScript` follows, and `googleTagHeadScript` already
193
- * applies to the whole script it stamps.
194
- */
195
- export const consentGateScript = (
196
- loaderExpr: string, opts?: ConsentOptions & { categories?: ConsentCategory[] }
197
- ): string => {
198
- const gate = JSON.stringify(categoriesOf(opts).map(category => ({
199
- key: category.key,
200
- required: category.required === true,
201
- hasSignal: (category.signals ?? []).length > 0,
202
- })))
203
- const storageKey = JSON.stringify(opts?.storageKey ?? CONSENT_KEY)
204
- const event = JSON.stringify(CONSENT_EVENT)
205
-
206
- return `(function(w,d){` +
207
- `function ok(r){if(!r)return false;var cs=${gate};` +
208
- `for(var i=0;i<cs.length;i++){var c=cs[i];` +
209
- `if(!c.required&&c.hasSignal&&r[c.key]===true)return true}return false}` +
210
- `function load(){${loaderExpr}}` +
211
- `var raw=null;try{raw=w.localStorage.getItem(${storageKey})}catch(e){}` +
212
- `if(!raw){var p=('; '+d.cookie).split('; '+${storageKey}+'=');` +
213
- `if(p.length===2){raw=p.pop().split(';').shift()}}` +
214
- `var r=null;if(raw){try{r=JSON.parse(raw)}catch(e){r=null}}` +
215
- `if(ok(r)){load();return}` +
216
- `function h(ev){if(ok(ev&&ev.detail&&ev.detail.record)){` +
217
- `w.removeEventListener(${event},h);load()}}` +
218
- `w.addEventListener(${event},h)` +
219
- `})(window,document)`
220
- }