@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.
- package/README.md +229 -0
- package/build/consts.d.ts +78 -3
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +104 -0
- package/build/consts.js.map +1 -1
- package/build/consts.local.d.ts +12 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +12 -0
- package/build/consts.local.js.map +1 -0
- package/build/geo/types.d.ts +47 -0
- package/build/geo/types.d.ts.map +1 -0
- package/build/geo/types.js +2 -0
- package/build/geo/types.js.map +1 -0
- package/build/geo.d.ts +4 -0
- package/build/geo.d.ts.map +1 -0
- package/build/geo.js +96 -0
- package/build/geo.js.map +1 -0
- package/build/gtobserver/types.d.ts +75 -0
- package/build/gtobserver/types.d.ts.map +1 -0
- package/build/gtobserver/types.js +2 -0
- package/build/gtobserver/types.js.map +1 -0
- package/build/gtobserver.d.ts +7 -0
- package/build/gtobserver.d.ts.map +1 -0
- package/build/gtobserver.js +142 -0
- package/build/gtobserver.js.map +1 -0
- package/build/i18n/be.json +8 -1
- package/build/i18n/de.json +8 -1
- package/build/i18n/en.json +8 -1
- package/build/i18n/es.json +8 -1
- package/build/i18n/fr.json +8 -1
- package/build/i18n/pl.json +8 -1
- package/build/i18n/ru.json +8 -1
- package/build/i18n/types.d.ts +11 -0
- package/build/i18n/types.d.ts.map +1 -0
- package/build/i18n/types.js +2 -0
- package/build/i18n/types.js.map +1 -0
- package/build/i18n/uk.json +8 -1
- package/build/i18n.d.ts +4 -14
- package/build/i18n.d.ts.map +1 -1
- package/build/i18n.js +12 -27
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +13 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -3
- package/build/index.js.map +1 -1
- package/build/linker/types.d.ts +59 -0
- package/build/linker/types.d.ts.map +1 -0
- package/build/linker/types.js +2 -0
- package/build/linker/types.js.map +1 -0
- package/build/linker.d.ts +5 -74
- package/build/linker.d.ts.map +1 -1
- package/build/linker.js +290 -324
- package/build/linker.js.map +1 -1
- package/build/plugins/types.d.ts +42 -0
- package/build/plugins/types.d.ts.map +1 -0
- package/build/plugins/types.js +2 -0
- package/build/plugins/types.js.map +1 -0
- package/build/plugins.d.ts +4 -68
- package/build/plugins.d.ts.map +1 -1
- package/build/plugins.js +73 -61
- package/build/plugins.js.map +1 -1
- package/build/storage/types.d.ts +31 -0
- package/build/storage/types.d.ts.map +1 -0
- package/build/storage/types.js +2 -0
- package/build/storage/types.js.map +1 -0
- package/build/storage.d.ts +6 -22
- package/build/storage.d.ts.map +1 -1
- package/build/storage.js +72 -81
- package/build/storage.js.map +1 -1
- package/build/store.d.ts +2 -2
- package/build/store.d.ts.map +1 -1
- package/build/store.js +115 -31
- package/build/store.js.map +1 -1
- package/build/types.d.ts +141 -5
- package/build/types.d.ts.map +1 -1
- package/build/types.local.d.ts +9 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/package.json +3 -3
- package/src/consts.local.ts +16 -0
- package/src/consts.ts +120 -3
- package/src/geo/types.ts +49 -0
- package/src/geo.ts +122 -0
- package/src/gtobserver/types.ts +73 -0
- package/src/gtobserver.ts +182 -0
- package/src/i18n/be.json +8 -1
- package/src/i18n/de.json +8 -1
- package/src/i18n/en.json +8 -1
- package/src/i18n/es.json +8 -1
- package/src/i18n/fr.json +8 -1
- package/src/i18n/pl.json +8 -1
- package/src/i18n/ru.json +8 -1
- package/src/i18n/types.ts +11 -0
- package/src/i18n/uk.json +8 -1
- package/src/i18n.ts +20 -32
- package/src/index.ts +13 -12
- package/src/linker/types.ts +59 -0
- package/src/linker.ts +301 -349
- package/src/plugins/types.ts +42 -0
- package/src/plugins.ts +82 -105
- package/src/storage/types.ts +31 -0
- package/src/storage.ts +75 -77
- package/src/store.ts +125 -31
- package/src/types.local.ts +9 -0
- package/src/types.ts +148 -5
- package/tests/consent.spec.ts +26 -28
- package/tests/geo.spec.ts +474 -0
- package/tests/linker.spec.ts +68 -74
- package/tests/tracking.spec.ts +14 -14
- package/build/gtm.d.ts +0 -72
- package/build/gtm.d.ts.map +0 -1
- package/build/gtm.js +0 -182
- package/build/gtm.js.map +0 -1
- package/src/gtm.ts +0 -220
package/tests/linker.spec.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '../src/
|
|
6
|
-
import { consentBootstrapScript } from '../src/gtm.js'
|
|
7
|
-
import {
|
|
8
|
-
consentLinker, consentLinkerScript, decodeConsentLink, encodeConsentLink, stripConsentLinkParam, writeConsentLanguage,
|
|
9
|
-
CONSENT_LINK_MAX_AGE, CONSENT_LINK_PARAM,
|
|
10
|
-
} from '../src/linker.js'
|
|
11
|
-
import { adoptConsentLanguage, consentDomains, decorateConsentUrl, registerConsentPlugin } from '../src/plugins.js'
|
|
2
|
+
import { CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_KEY, CONSENT_LANGUAGE_KEY, CONSENT_MARKETING, DEFAULT_CONSENT_CATEGORIES, CONSENT_LINK_MAX_AGE, CONSENT_LINK_PARAM } from '../src/consts.js'
|
|
3
|
+
import { consentModeHelper } from '../src/gtobserver.js'
|
|
4
|
+
import { consentLinkHelper } from '../src/linker.js'
|
|
5
|
+
import { consentPluginHelper } from '../src/plugins.js'
|
|
12
6
|
import { makeConsentStore } from '../src/store.js'
|
|
13
|
-
import {
|
|
7
|
+
import { consentStorageHelper } from '../src/storage.js'
|
|
14
8
|
import type { ConsentRecord } from '../src/types.js'
|
|
15
9
|
|
|
16
10
|
/** A minimal browser: storage, a settable referrer, and a `location`/`history` pair that actually
|
|
@@ -78,8 +72,8 @@ beforeEach(() => {
|
|
|
78
72
|
describe('encodeConsentLink / decodeConsentLink', () => {
|
|
79
73
|
test('round-trips a record through the optional categories only', () => {
|
|
80
74
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
81
|
-
const encoded = encodeConsentLink(record)
|
|
82
|
-
const decoded = decodeConsentLink(encoded)
|
|
75
|
+
const encoded = consentLinkHelper.encodeConsentLink(record)
|
|
76
|
+
const decoded = consentLinkHelper.decodeConsentLink(encoded)
|
|
83
77
|
|
|
84
78
|
expect(decoded?.v).toBe(2)
|
|
85
79
|
expect(decoded?.c).toEqual({ [CONSENT_ANALYTICS]: 1, [CONSENT_MARKETING]: 0 })
|
|
@@ -87,26 +81,26 @@ describe('encodeConsentLink / decodeConsentLink', () => {
|
|
|
87
81
|
})
|
|
88
82
|
|
|
89
83
|
test('rejects garbage rather than throwing', () => {
|
|
90
|
-
expect(decodeConsentLink('not-base64url-json')).toBeNull()
|
|
91
|
-
expect(decodeConsentLink('')).toBeNull()
|
|
84
|
+
expect(consentLinkHelper.decodeConsentLink('not-base64url-json')).toBeNull()
|
|
85
|
+
expect(consentLinkHelper.decodeConsentLink('')).toBeNull()
|
|
92
86
|
})
|
|
93
87
|
|
|
94
88
|
test('rejects a payload of the wrong version', () => {
|
|
95
89
|
const bad = Buffer.from(JSON.stringify({ v: 1, c: {}, t: nowSeconds() })).toString('base64url')
|
|
96
90
|
|
|
97
|
-
expect(decodeConsentLink(bad)).toBeNull()
|
|
91
|
+
expect(consentLinkHelper.decodeConsentLink(bad)).toBeNull()
|
|
98
92
|
})
|
|
99
93
|
})
|
|
100
94
|
|
|
101
95
|
describe('consentLinker().decorate', () => {
|
|
102
|
-
const plugin = consentLinker()
|
|
96
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
103
97
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
104
98
|
|
|
105
99
|
test('decorates a listed, foreign host', () => {
|
|
106
100
|
const decorated = plugin.decorate!(new URL('https://site.test/legal/cookies'), record, linkerOpts)
|
|
107
101
|
|
|
108
102
|
expect(decorated).not.toBeNull()
|
|
109
|
-
const payload = decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
103
|
+
const payload = consentLinkHelper.decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
110
104
|
expect(payload?.c[CONSENT_ANALYTICS]).toBe(1)
|
|
111
105
|
})
|
|
112
106
|
|
|
@@ -132,8 +126,8 @@ describe('consentLinker().decorate', () => {
|
|
|
132
126
|
})
|
|
133
127
|
|
|
134
128
|
describe('consentLinker().adopt — every trust rule', () => {
|
|
135
|
-
const plugin = consentLinker()
|
|
136
|
-
const valid = () => encodeConsentLink({ [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
129
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
130
|
+
const valid = () => consentLinkHelper.encodeConsentLink({ [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false })
|
|
137
131
|
|
|
138
132
|
test('adopts a fresh, referred, fully-covered decision', () => {
|
|
139
133
|
env.goto(`https://platform.test/dispatcher?owlcc=${valid()}`, 'https://site.test/')
|
|
@@ -199,21 +193,21 @@ describe('consentLinker().adopt — every trust rule', () => {
|
|
|
199
193
|
describe('stripConsentLinkParam', () => {
|
|
200
194
|
test('removes only the parameter, keeping the rest and the hash', () => {
|
|
201
195
|
env.goto('https://platform.test/dispatcher?owlcc=abc&next=%2Fhome&ref=site#panel')
|
|
202
|
-
stripConsentLinkParam(linkerOpts)
|
|
196
|
+
consentLinkHelper.stripConsentLinkParam(linkerOpts)
|
|
203
197
|
|
|
204
198
|
expect(env.href()).toBe('https://platform.test/dispatcher?next=%2Fhome&ref=site#panel')
|
|
205
199
|
})
|
|
206
200
|
|
|
207
201
|
test('is a no-op with no parameter present', () => {
|
|
208
202
|
env.goto('https://platform.test/dispatcher?next=%2Fhome')
|
|
209
|
-
stripConsentLinkParam(linkerOpts)
|
|
203
|
+
consentLinkHelper.stripConsentLinkParam(linkerOpts)
|
|
210
204
|
|
|
211
205
|
expect(env.href()).toBe('https://platform.test/dispatcher?next=%2Fhome')
|
|
212
206
|
})
|
|
213
207
|
|
|
214
208
|
test('is a no-op with no linker configured', () => {
|
|
215
209
|
env.goto('https://platform.test/dispatcher?owlcc=abc')
|
|
216
|
-
stripConsentLinkParam({})
|
|
210
|
+
consentLinkHelper.stripConsentLinkParam({})
|
|
217
211
|
|
|
218
212
|
expect(env.href()).toBe('https://platform.test/dispatcher?owlcc=abc')
|
|
219
213
|
})
|
|
@@ -221,8 +215,8 @@ describe('stripConsentLinkParam', () => {
|
|
|
221
215
|
|
|
222
216
|
describe('consentDomains / decorateConsentUrl (registry level)', () => {
|
|
223
217
|
test('discloses the current host plus every plugin-named domain, deduplicated', () => {
|
|
224
|
-
registerConsentPlugin(consentLinker())
|
|
225
|
-
const domains = consentDomains(linkerOpts)
|
|
218
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
219
|
+
const domains = consentPluginHelper.consentDomains(linkerOpts)
|
|
226
220
|
|
|
227
221
|
expect(domains).toContain('platform.test')
|
|
228
222
|
expect(domains).toContain('site.test')
|
|
@@ -230,18 +224,18 @@ describe('consentDomains / decorateConsentUrl (registry level)', () => {
|
|
|
230
224
|
})
|
|
231
225
|
|
|
232
226
|
test('decorateConsentUrl applies the registered plugin to a plain URL', () => {
|
|
233
|
-
registerConsentPlugin(consentLinker())
|
|
227
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
234
228
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false }
|
|
235
|
-
const decorated = decorateConsentUrl('https://site.test/legal/cookies', record, linkerOpts)
|
|
229
|
+
const decorated = consentPluginHelper.decorateConsentUrl('https://site.test/legal/cookies', record, linkerOpts)
|
|
236
230
|
|
|
237
231
|
expect(decorated.searchParams.has(CONSENT_LINK_PARAM)).toBe(true)
|
|
238
232
|
})
|
|
239
233
|
|
|
240
234
|
test('registering the same alias twice replaces rather than duplicates', () => {
|
|
241
|
-
registerConsentPlugin(consentLinker())
|
|
242
|
-
registerConsentPlugin(consentLinker())
|
|
235
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
236
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
243
237
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true }
|
|
244
|
-
const decorated = decorateConsentUrl('https://site.test/', record, linkerOpts)
|
|
238
|
+
const decorated = consentPluginHelper.decorateConsentUrl('https://site.test/', record, linkerOpts)
|
|
245
239
|
|
|
246
240
|
// A duplicate `decorate` implementation would still be idempotent (same value both times), so
|
|
247
241
|
// what this actually guards is that the registry never grows unbounded per alias.
|
|
@@ -252,7 +246,7 @@ describe('consentDomains / decorateConsentUrl (registry level)', () => {
|
|
|
252
246
|
describe('the store, with a linker configured', () => {
|
|
253
247
|
test('adopts on init when nothing is stored yet, and applies it', () => {
|
|
254
248
|
const record = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
255
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(record)}`, 'https://site.test/')
|
|
249
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(record)}`, 'https://site.test/')
|
|
256
250
|
|
|
257
251
|
const store = makeConsentStore()
|
|
258
252
|
store.init({ silent: true, linker: { domains: DOMAINS } })
|
|
@@ -261,7 +255,7 @@ describe('the store, with a linker configured', () => {
|
|
|
261
255
|
expect(store.get().record?.[CONSENT_ANALYTICS]).toBe(true)
|
|
262
256
|
expect(store.granted(CONSENT_ESSENTIAL)).toBe(true)
|
|
263
257
|
// Written through, not just held in memory — a second page load must see it too.
|
|
264
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
258
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
265
259
|
})
|
|
266
260
|
|
|
267
261
|
test('strips the parameter from the URL either way', () => {
|
|
@@ -274,9 +268,9 @@ describe('the store, with a linker configured', () => {
|
|
|
274
268
|
})
|
|
275
269
|
|
|
276
270
|
test('never overwrites a decision this document already made', () => {
|
|
277
|
-
writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
271
|
+
consentStorageHelper.writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
278
272
|
const incoming = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true }
|
|
279
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
273
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
280
274
|
|
|
281
275
|
const store = makeConsentStore()
|
|
282
276
|
store.init({ silent: true, linker: { domains: DOMAINS } })
|
|
@@ -297,7 +291,7 @@ describe('the store, with a linker configured', () => {
|
|
|
297
291
|
|
|
298
292
|
describe('consentBootstrapScript with a linker: order and shape', () => {
|
|
299
293
|
test('the linker fragment sits after the default push and before the storage read', () => {
|
|
300
|
-
const script = consentBootstrapScript({ linker: { domains: DOMAINS } })
|
|
294
|
+
const script = consentModeHelper.consentBootstrapScript({ linker: { domains: DOMAINS } })
|
|
301
295
|
const defaultAt = script.indexOf("'consent','default'")
|
|
302
296
|
const linkerAt = script.indexOf('URLSearchParams')
|
|
303
297
|
const storageAt = script.indexOf('w.localStorage.getItem')
|
|
@@ -308,11 +302,11 @@ describe('consentBootstrapScript with a linker: order and shape', () => {
|
|
|
308
302
|
})
|
|
309
303
|
|
|
310
304
|
test('emits nothing extra with no linker configured', () => {
|
|
311
|
-
expect(consentBootstrapScript()).not.toContain('URLSearchParams')
|
|
305
|
+
expect(consentModeHelper.consentBootstrapScript()).not.toContain('URLSearchParams')
|
|
312
306
|
})
|
|
313
307
|
|
|
314
308
|
test('escapes "<" inside embedded strings', () => {
|
|
315
|
-
const script = consentLinkerScript({ linker: { domains: ['<script>evil.test'] } })
|
|
309
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: ['<script>evil.test'] } })
|
|
316
310
|
|
|
317
311
|
expect(script).not.toContain('<script>evil.test')
|
|
318
312
|
expect(script).toContain('\\u003cscript>evil.test')
|
|
@@ -322,31 +316,31 @@ describe('consentBootstrapScript with a linker: order and shape', () => {
|
|
|
322
316
|
describe('consentLinkerScript, executed', () => {
|
|
323
317
|
test('adopts a valid link and strips the parameter, via new Function', () => {
|
|
324
318
|
const record = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
325
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(record)}`, 'https://site.test/')
|
|
319
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(record)}`, 'https://site.test/')
|
|
326
320
|
|
|
327
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
321
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
328
322
|
// eslint-disable-next-line no-new-func
|
|
329
323
|
new Function(script)()
|
|
330
324
|
|
|
331
325
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
332
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
326
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
333
327
|
})
|
|
334
328
|
|
|
335
329
|
test('never overwrites an existing record, and still strips', () => {
|
|
336
|
-
writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
330
|
+
consentStorageHelper.writeConsent({ [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false })
|
|
337
331
|
const incoming = { [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: true }
|
|
338
|
-
env.goto(`https://platform.test/dispatcher?owlcc=${encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
332
|
+
env.goto(`https://platform.test/dispatcher?owlcc=${consentLinkHelper.encodeConsentLink(incoming)}`, 'https://site.test/')
|
|
339
333
|
|
|
340
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
334
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS } })
|
|
341
335
|
// eslint-disable-next-line no-new-func
|
|
342
336
|
new Function(script)()
|
|
343
337
|
|
|
344
338
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
345
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
339
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
346
340
|
})
|
|
347
341
|
|
|
348
342
|
test('is an empty string with no linker configured', () => {
|
|
349
|
-
expect(consentLinkerScript({})).toBe('')
|
|
343
|
+
expect(consentLinkHelper.consentLinkerScript({})).toBe('')
|
|
350
344
|
})
|
|
351
345
|
})
|
|
352
346
|
|
|
@@ -370,7 +364,7 @@ describe('language: the link parameter', () => {
|
|
|
370
364
|
|
|
371
365
|
test('carries the page language as `l`, lower-cased, when the sender turned it on', () => {
|
|
372
366
|
env.lang('PL')
|
|
373
|
-
const decoded = decodeConsentLink(encodeConsentLink({ [CONSENT_ANALYTICS]: true }, withLanguage))
|
|
367
|
+
const decoded = consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink({ [CONSENT_ANALYTICS]: true }, withLanguage))
|
|
374
368
|
|
|
375
369
|
expect(decoded?.l).toBe('pl')
|
|
376
370
|
expect(decoded?.c).toEqual({ [CONSENT_ANALYTICS]: 1, [CONSENT_MARKETING]: 0 })
|
|
@@ -379,20 +373,20 @@ describe('language: the link parameter', () => {
|
|
|
379
373
|
test('carries no `l` when the sender did not turn it on, whatever the page says', () => {
|
|
380
374
|
env.lang('pl')
|
|
381
375
|
|
|
382
|
-
expect(decodeConsentLink(encodeConsentLink({ [CONSENT_ANALYTICS]: true }, linkerOpts))?.l).toBeUndefined()
|
|
376
|
+
expect(consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink({ [CONSENT_ANALYTICS]: true }, linkerOpts))?.l).toBeUndefined()
|
|
383
377
|
})
|
|
384
378
|
|
|
385
379
|
test('carries no `l` for a page with no usable language', () => {
|
|
386
380
|
for (const lang of ['', 'not a language!', '12']) {
|
|
387
381
|
env.lang(lang)
|
|
388
382
|
|
|
389
|
-
expect(decodeConsentLink(encodeConsentLink(null, withLanguage))?.l).toBeUndefined()
|
|
383
|
+
expect(consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink(null, withLanguage))?.l).toBeUndefined()
|
|
390
384
|
}
|
|
391
385
|
})
|
|
392
386
|
|
|
393
387
|
test('encodes no decision at all as an empty `c`', () => {
|
|
394
388
|
env.lang('de')
|
|
395
|
-
const decoded = decodeConsentLink(encodeConsentLink(null, withLanguage))
|
|
389
|
+
const decoded = consentLinkHelper.decodeConsentLink(consentLinkHelper.encodeConsentLink(null, withLanguage))
|
|
396
390
|
|
|
397
391
|
expect(decoded?.c).toEqual({})
|
|
398
392
|
expect(decoded?.l).toBe('de')
|
|
@@ -402,7 +396,7 @@ describe('language: the link parameter', () => {
|
|
|
402
396
|
const raw = Buffer.from(JSON.stringify({
|
|
403
397
|
v: 2, c: { [CONSENT_ANALYTICS]: 1 }, t: nowSeconds(), l: '../../etc',
|
|
404
398
|
})).toString('base64url')
|
|
405
|
-
const decoded = decodeConsentLink(raw)
|
|
399
|
+
const decoded = consentLinkHelper.decodeConsentLink(raw)
|
|
406
400
|
|
|
407
401
|
expect(decoded).not.toBeNull()
|
|
408
402
|
expect(decoded?.l).toBeUndefined()
|
|
@@ -411,7 +405,7 @@ describe('language: the link parameter', () => {
|
|
|
411
405
|
})
|
|
412
406
|
|
|
413
407
|
describe('language: consentLinker().decorate', () => {
|
|
414
|
-
const plugin = consentLinker()
|
|
408
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
415
409
|
const withLanguage = { linker: { domains: DOMAINS, language: {} } }
|
|
416
410
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true, [CONSENT_MARKETING]: false }
|
|
417
411
|
|
|
@@ -420,14 +414,14 @@ describe('language: consentLinker().decorate', () => {
|
|
|
420
414
|
const decorated = plugin.decorate!(new URL('https://site.test/start'), null, withLanguage)
|
|
421
415
|
|
|
422
416
|
expect(decorated).not.toBeNull()
|
|
423
|
-
const payload = decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
417
|
+
const payload = consentLinkHelper.decodeConsentLink(decorated!.searchParams.get(CONSENT_LINK_PARAM)!)
|
|
424
418
|
expect(payload?.l).toBe('uk')
|
|
425
419
|
expect(payload?.c).toEqual({})
|
|
426
420
|
})
|
|
427
421
|
|
|
428
422
|
test('carries the decision and the language together', () => {
|
|
429
423
|
env.lang('pl')
|
|
430
|
-
const payload = decodeConsentLink(
|
|
424
|
+
const payload = consentLinkHelper.decodeConsentLink(
|
|
431
425
|
plugin.decorate!(new URL('https://site.test/start'), record, withLanguage)!.searchParams.get(CONSENT_LINK_PARAM)!
|
|
432
426
|
)
|
|
433
427
|
|
|
@@ -455,12 +449,12 @@ describe('language: consentLinker().decorate', () => {
|
|
|
455
449
|
})
|
|
456
450
|
|
|
457
451
|
describe('language: consentLinker().adoptLanguage', () => {
|
|
458
|
-
const plugin = consentLinker()
|
|
452
|
+
const plugin = consentLinkHelper.consentLinker()
|
|
459
453
|
const receiving = { linker: { domains: DOMAINS, language: { supported: ['en', 'pl', 'de', 'fr'] } } }
|
|
460
454
|
const carried = (lang: string, record: ConsentRecord | null = null): string => {
|
|
461
455
|
env.lang(lang)
|
|
462
456
|
|
|
463
|
-
return encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
457
|
+
return consentLinkHelper.encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
464
458
|
}
|
|
465
459
|
const arrive = (owlcc: string, referrer = 'https://site.test/') => {
|
|
466
460
|
env.goto(`https://platform.test/dispatcher?owlcc=${owlcc}`, referrer)
|
|
@@ -526,11 +520,11 @@ describe('language: consentLinker().adoptLanguage', () => {
|
|
|
526
520
|
})
|
|
527
521
|
|
|
528
522
|
test('registry: adoptConsentLanguage asks the registered plugin', () => {
|
|
529
|
-
registerConsentPlugin(consentLinker())
|
|
523
|
+
consentPluginHelper.registerConsentPlugin(consentLinkHelper.consentLinker())
|
|
530
524
|
arrive(carried('pl'))
|
|
531
525
|
|
|
532
|
-
expect(adoptConsentLanguage(receiving)).toBe('pl')
|
|
533
|
-
expect(adoptConsentLanguage(linkerOpts)).toBeNull()
|
|
526
|
+
expect(consentPluginHelper.adoptConsentLanguage(receiving)).toBe('pl')
|
|
527
|
+
expect(consentPluginHelper.adoptConsentLanguage(linkerOpts)).toBeNull()
|
|
534
528
|
})
|
|
535
529
|
})
|
|
536
530
|
|
|
@@ -538,7 +532,7 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
538
532
|
const receiving = { silent: true, linker: { domains: DOMAINS, language: { supported: ['en', 'pl', 'de'] } } }
|
|
539
533
|
const arrive = (lang: string, record: ConsentRecord | null = null, referrer = 'https://site.test/') => {
|
|
540
534
|
env.lang(lang)
|
|
541
|
-
const owlcc = encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
535
|
+
const owlcc = consentLinkHelper.encodeConsentLink(record, { linker: { domains: DOMAINS, language: {} } })
|
|
542
536
|
env.goto(`https://platform.test/dispatcher?owlcc=${owlcc}`, referrer)
|
|
543
537
|
env.lang('en')
|
|
544
538
|
}
|
|
@@ -551,9 +545,9 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
551
545
|
for (const record of [null, refused, accepted, withRetiredKey]) {
|
|
552
546
|
env.reset()
|
|
553
547
|
if (record != null) {
|
|
554
|
-
writeConsent(record)
|
|
548
|
+
consentStorageHelper.writeConsent(record)
|
|
555
549
|
}
|
|
556
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(true)
|
|
550
|
+
expect(consentLinkHelper.writeConsentLanguage('pl', receiving)).toBe(true)
|
|
557
551
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
558
552
|
}
|
|
559
553
|
})
|
|
@@ -563,7 +557,7 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
563
557
|
const setItem = storage.setItem
|
|
564
558
|
storage.setItem = () => { throw new Error('blocked') }
|
|
565
559
|
try {
|
|
566
|
-
expect(writeConsentLanguage('pl', receiving)).toBe(false)
|
|
560
|
+
expect(consentLinkHelper.writeConsentLanguage('pl', receiving)).toBe(false)
|
|
567
561
|
} finally {
|
|
568
562
|
// The mock storage is shared by every test in the file.
|
|
569
563
|
storage.setItem = setItem
|
|
@@ -607,7 +601,7 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
607
601
|
test('a stored decision of any kind lets the link\'s language through, and still wins over its decision', () => {
|
|
608
602
|
for (const stored of [refused, accepted, withRetiredKey]) {
|
|
609
603
|
env.reset()
|
|
610
|
-
writeConsent(stored)
|
|
604
|
+
consentStorageHelper.writeConsent(stored)
|
|
611
605
|
arrive('pl', { [CONSENT_ANALYTICS]: !stored[CONSENT_ANALYTICS], [CONSENT_MARKETING]: true })
|
|
612
606
|
|
|
613
607
|
const store = makeConsentStore()
|
|
@@ -669,9 +663,9 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
669
663
|
})
|
|
670
664
|
|
|
671
665
|
describe('the inline fragment', () => {
|
|
672
|
-
const run = (opts: Parameters<typeof consentLinkerScript>[0] = receiving) => {
|
|
666
|
+
const run = (opts: Parameters<typeof consentLinkHelper.consentLinkerScript>[0] = receiving) => {
|
|
673
667
|
// eslint-disable-next-line no-new-func
|
|
674
|
-
new Function(consentLinkerScript(opts))()
|
|
668
|
+
new Function(consentLinkHelper.consentLinkerScript(opts))()
|
|
675
669
|
}
|
|
676
670
|
const all = { linker: { domains: DOMAINS, language: { supported: ['en', 'pl', 'de', 'fr'] } } }
|
|
677
671
|
|
|
@@ -680,7 +674,7 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
680
674
|
run()
|
|
681
675
|
|
|
682
676
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('pl')
|
|
683
|
-
expect(readConsent()).toBeNull()
|
|
677
|
+
expect(consentStorageHelper.readConsent()).toBeNull()
|
|
684
678
|
expect(env.href()).toBe('https://platform.test/dispatcher')
|
|
685
679
|
})
|
|
686
680
|
|
|
@@ -689,26 +683,26 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
689
683
|
run(all)
|
|
690
684
|
|
|
691
685
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('fr')
|
|
692
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
686
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(true)
|
|
693
687
|
})
|
|
694
688
|
|
|
695
689
|
test('a link that carries "reject all" adopts the decision and stores the language', () => {
|
|
696
690
|
arrive('fr', refused)
|
|
697
691
|
run(all)
|
|
698
692
|
|
|
699
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
693
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(false)
|
|
700
694
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('fr')
|
|
701
695
|
})
|
|
702
696
|
|
|
703
697
|
test('a stored record of any kind lets the language through, and the link\'s decision never overwrites it', () => {
|
|
704
698
|
for (const stored of [refused, accepted, withRetiredKey]) {
|
|
705
699
|
env.reset()
|
|
706
|
-
writeConsent(stored)
|
|
700
|
+
consentStorageHelper.writeConsent(stored)
|
|
707
701
|
arrive('de', { [CONSENT_ANALYTICS]: !stored[CONSENT_ANALYTICS], [CONSENT_MARKETING]: true })
|
|
708
702
|
run()
|
|
709
703
|
|
|
710
704
|
expect(env.store.get(CONSENT_LANGUAGE_KEY)).toBe('de')
|
|
711
|
-
expect(readConsent()?.[CONSENT_ANALYTICS]).toBe(stored[CONSENT_ANALYTICS])
|
|
705
|
+
expect(consentStorageHelper.readConsent()?.[CONSENT_ANALYTICS]).toBe(stored[CONSENT_ANALYTICS])
|
|
712
706
|
}
|
|
713
707
|
})
|
|
714
708
|
|
|
@@ -755,14 +749,14 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
755
749
|
})
|
|
756
750
|
|
|
757
751
|
test('a page that only sends carries no language code at all', () => {
|
|
758
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS, language: {} } })
|
|
752
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS, language: {} } })
|
|
759
753
|
|
|
760
754
|
expect(script).not.toContain(CONSENT_LANGUAGE_KEY)
|
|
761
|
-
expect(consentLinkerScript(linkerOpts)).toBe(script)
|
|
755
|
+
expect(consentLinkHelper.consentLinkerScript(linkerOpts)).toBe(script)
|
|
762
756
|
})
|
|
763
757
|
|
|
764
758
|
test('the bootstrap embeds it, after the defaults and before its own storage read', () => {
|
|
765
|
-
const script = consentBootstrapScript(receiving)
|
|
759
|
+
const script = consentModeHelper.consentBootstrapScript(receiving)
|
|
766
760
|
|
|
767
761
|
expect(script.indexOf(CONSENT_LANGUAGE_KEY)).toBeGreaterThan(script.indexOf("'consent','default'"))
|
|
768
762
|
// The fragment reads storage itself (to know whether a decision exists) before it writes the
|
|
@@ -771,7 +765,7 @@ describe('language: strictly necessary, stored whatever the cookie decision is',
|
|
|
771
765
|
})
|
|
772
766
|
|
|
773
767
|
test('escapes "<" in a configured language too', () => {
|
|
774
|
-
const script = consentLinkerScript({ linker: { domains: DOMAINS, language: { supported: ['<b>'], storageKey: '<k>' } } })
|
|
768
|
+
const script = consentLinkHelper.consentLinkerScript({ linker: { domains: DOMAINS, language: { supported: ['<b>'], storageKey: '<k>' } } })
|
|
775
769
|
|
|
776
770
|
expect(script).not.toContain('<b>')
|
|
777
771
|
expect(script).not.toContain('<k>')
|
package/tests/tracking.spec.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test'
|
|
2
2
|
import { CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_EVENT, CONSENT_KEY, CONSENT_MARKETING } from '../src/consts.js'
|
|
3
|
-
import {
|
|
3
|
+
import { consentModeHelper } from '../src/gtobserver.js'
|
|
4
4
|
import type { ConsentCategory, ConsentRecord } from '../src/types.js'
|
|
5
5
|
|
|
6
6
|
describe('trackingGranted', () => {
|
|
7
7
|
test('no record at all is not granted', () => {
|
|
8
|
-
expect(trackingGranted(null)).toBe(false)
|
|
8
|
+
expect(consentModeHelper.trackingGranted(null)).toBe(false)
|
|
9
9
|
})
|
|
10
10
|
|
|
11
11
|
test('an essential-only record is not granted — required categories never count', () => {
|
|
@@ -13,19 +13,19 @@ describe('trackingGranted', () => {
|
|
|
13
13
|
[CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false,
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
expect(trackingGranted(record)).toBe(false)
|
|
16
|
+
expect(consentModeHelper.trackingGranted(record)).toBe(false)
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
test('analytics granted is tracking-granted', () => {
|
|
20
20
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true }
|
|
21
21
|
|
|
22
|
-
expect(trackingGranted(record)).toBe(true)
|
|
22
|
+
expect(consentModeHelper.trackingGranted(record)).toBe(true)
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
test('marketing granted is tracking-granted too', () => {
|
|
26
26
|
const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_MARKETING]: true }
|
|
27
27
|
|
|
28
|
-
expect(trackingGranted(record)).toBe(true)
|
|
28
|
+
expect(consentModeHelper.trackingGranted(record)).toBe(true)
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
test('a required category is ignored even when the record marks it granted', () => {
|
|
@@ -39,7 +39,7 @@ describe('trackingGranted', () => {
|
|
|
39
39
|
},
|
|
40
40
|
]
|
|
41
41
|
|
|
42
|
-
expect(trackingGranted({ essential: true }, categories)).toBe(false)
|
|
42
|
+
expect(consentModeHelper.trackingGranted({ essential: true }, categories)).toBe(false)
|
|
43
43
|
})
|
|
44
44
|
|
|
45
45
|
test('a category with no signals is ignored even when granted', () => {
|
|
@@ -47,7 +47,7 @@ describe('trackingGranted', () => {
|
|
|
47
47
|
{ key: 'preferences', labelKey: 'a', descriptionKey: 'b' },
|
|
48
48
|
]
|
|
49
49
|
|
|
50
|
-
expect(trackingGranted({ preferences: true }, categories)).toBe(false)
|
|
50
|
+
expect(consentModeHelper.trackingGranted({ preferences: true }, categories)).toBe(false)
|
|
51
51
|
})
|
|
52
52
|
})
|
|
53
53
|
|
|
@@ -91,7 +91,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
91
91
|
test('a returning visitor with a stored grant runs the loader immediately, synchronously', () => {
|
|
92
92
|
const browser = makeBrowser({ record: { essential: true, analytics: true, v: 2 } })
|
|
93
93
|
browser.win.__loaded = 0
|
|
94
|
-
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
94
|
+
browser.run(consentModeHelper.consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
95
95
|
|
|
96
96
|
expect(browser.win.__loaded).toBe(1)
|
|
97
97
|
// Nothing left listening — the immediate branch never subscribes.
|
|
@@ -101,7 +101,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
101
101
|
test('a stored record that grants nothing does not run the loader immediately', () => {
|
|
102
102
|
const browser = makeBrowser({ record: { essential: true, analytics: false, v: 2 } })
|
|
103
103
|
browser.win.__loaded = 0
|
|
104
|
-
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
104
|
+
browser.run(consentModeHelper.consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
105
105
|
|
|
106
106
|
expect(browser.win.__loaded).toBe(0)
|
|
107
107
|
expect(browser.listenerCount()).toBe(1)
|
|
@@ -110,7 +110,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
110
110
|
test('no stored record at all falls to the event-listener branch and waits', () => {
|
|
111
111
|
const browser = makeBrowser()
|
|
112
112
|
browser.win.__loaded = 0
|
|
113
|
-
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
113
|
+
browser.run(consentModeHelper.consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
|
|
114
114
|
|
|
115
115
|
expect(browser.win.__loaded).toBe(0)
|
|
116
116
|
|
|
@@ -145,7 +145,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
145
145
|
const doc = { cookie: `${CONSENT_KEY}=${record}` }
|
|
146
146
|
// eslint-disable-next-line no-new-func
|
|
147
147
|
new Function(
|
|
148
|
-
'window', 'document', consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
|
|
148
|
+
'window', 'document', consentModeHelper.consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
|
|
149
149
|
)(win, doc)
|
|
150
150
|
|
|
151
151
|
expect(win.__loaded).toBe(1)
|
|
@@ -158,7 +158,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
158
158
|
]
|
|
159
159
|
const browser = makeBrowser({ record: { essential: true, analytics: false, v: 2 } })
|
|
160
160
|
browser.win.__loaded = 0
|
|
161
|
-
browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1', { categories }))
|
|
161
|
+
browser.run(consentModeHelper.consentGateScript('window.__loaded = (window.__loaded || 0) + 1', { categories }))
|
|
162
162
|
|
|
163
163
|
expect(browser.win.__loaded).toBe(0)
|
|
164
164
|
|
|
@@ -167,7 +167,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
167
167
|
})
|
|
168
168
|
|
|
169
169
|
test('the generated script carries both reachable code paths in its text', () => {
|
|
170
|
-
const script = consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
|
|
170
|
+
const script = consentModeHelper.consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
|
|
171
171
|
|
|
172
172
|
// The immediate branch: `load()` called right after the stored-record check, before any
|
|
173
173
|
// listener is attached.
|
|
@@ -182,7 +182,7 @@ describe('consentGateScript — the two reachable paths', () => {
|
|
|
182
182
|
test('a custom storage key is honoured, matching the bootstrap script\'s own lookup', () => {
|
|
183
183
|
const browser = makeBrowser({ key: 'custom_key', record: { essential: true, analytics: true, v: 2 } })
|
|
184
184
|
browser.win.__loaded = 0
|
|
185
|
-
browser.run(consentGateScript(
|
|
185
|
+
browser.run(consentModeHelper.consentGateScript(
|
|
186
186
|
'window.__loaded = (window.__loaded || 0) + 1', { storageKey: 'custom_key' }
|
|
187
187
|
))
|
|
188
188
|
|
package/build/gtm.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { ConsentCategory, ConsentOptions, ConsentRecord } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Push onto the tag-manager queue in the shape Google's own snippet uses.
|
|
4
|
-
*
|
|
5
|
-
* `arguments`, not an array literal. Both work with today's Consent API, but `arguments` is what
|
|
6
|
-
* `gtag.js` itself emits, and a page that carries both this and Google's snippet should not have
|
|
7
|
-
* two shapes in one queue.
|
|
8
|
-
*/
|
|
9
|
-
export declare function gtagConsent(..._args: unknown[]): void;
|
|
10
|
-
/** Every signal the configured categories drive, at its pre-consent value. */
|
|
11
|
-
export declare const consentDefaults: (categories?: ConsentCategory[]) => Record<string, 'granted' | 'denied'>;
|
|
12
|
-
/** Every signal, at the value this record implies. */
|
|
13
|
-
export declare const consentUpdate: (record: ConsentRecord, categories?: ConsentCategory[]) => Record<string, 'granted' | 'denied'>;
|
|
14
|
-
/**
|
|
15
|
-
* Declare what is denied, before any tag can act.
|
|
16
|
-
*
|
|
17
|
-
* Idempotent through a window flag, because the page may carry this call twice — once inline in
|
|
18
|
-
* the document head, once from whatever bundle mounts the dialog — and a second `default` after a
|
|
19
|
-
* tag has loaded is worse than none: it can widen what was already narrowed.
|
|
20
|
-
*/
|
|
21
|
-
export declare const pushConsentDefaults: (opts?: ConsentOptions) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Apply a decision: globals first, then the signal update, then any per-category events.
|
|
24
|
-
*
|
|
25
|
-
* The order is load-bearing. A tag that fires on the update event reads the globals in the same
|
|
26
|
-
* turn, so writing them afterwards would let the first firing see the previous answer.
|
|
27
|
-
*/
|
|
28
|
-
export declare const applyConsent: (record: ConsentRecord, opts?: ConsentOptions) => void;
|
|
29
|
-
/**
|
|
30
|
-
* Whether a stored decision grants tracking at all — a signal-bearing category, not merely the
|
|
31
|
-
* required essential one.
|
|
32
|
-
*
|
|
33
|
-
* "Signal-bearing" is what a required category can never be: it exists to gate whether a LOADER
|
|
34
|
-
* runs, and a required category is disclosure, not a question — everyone gets it. Only a category
|
|
35
|
-
* that both is optional and drives at least one Consent Mode signal counts, which is exactly what
|
|
36
|
-
* `googleTagHeadScript`'s `'basic'` mode gates a Google tag behind.
|
|
37
|
-
*/
|
|
38
|
-
export declare const trackingGranted: (record: ConsentRecord | null, categories?: ConsentCategory[]) => boolean;
|
|
39
|
-
/**
|
|
40
|
-
* The inline script a document stamps ABOVE its tag-manager snippet.
|
|
41
|
-
*
|
|
42
|
-
* It exists because the defaults have to be on the queue before `gtm.js` loads, and a React bundle
|
|
43
|
-
* cannot be: by the time an island mounts, the container has been running for hundreds of
|
|
44
|
-
* milliseconds and has already decided what it may do. Emitting it from the HTML is the only
|
|
45
|
-
* ordering that holds — and it reads the stored record too, so a returning visitor's tags are not
|
|
46
|
-
* denied for the first paint of every page.
|
|
47
|
-
*/
|
|
48
|
-
export declare const consentBootstrapScript: (opts?: ConsentOptions) => string;
|
|
49
|
-
/**
|
|
50
|
-
* Withhold a loader until a stored or later decision grants tracking.
|
|
51
|
-
*
|
|
52
|
-
* The counterpart to `consentBootstrapScript` for the `'basic'` gated loading mode
|
|
53
|
-
* (`@owlmeans/web-gtm`'s `GOOGLE_TAG_DEFAULT_MODE`): where the bootstrap always declares Consent
|
|
54
|
-
* Mode's defaults so a tag may load cookieless, this withholds the tag itself — the loader string
|
|
55
|
-
* is only ever reached once `trackingGranted` is true, either because a returning visitor's stored
|
|
56
|
-
* record already says so, or because `applyConsent` later dispatches {@link CONSENT_EVENT} with
|
|
57
|
-
* one that does. The listener removes itself on the first passing event, so the loader — which
|
|
58
|
-
* marks its own element and refuses to run twice — is asked to run at most once from here.
|
|
59
|
-
*
|
|
60
|
-
* `loaderExpr` is a complete statement or expression that runs the loader when reached — the
|
|
61
|
-
* self-invoking IIFE string `googleTagHeadScript`'s `gtagScript`/`gtmContainerScript` already
|
|
62
|
-
* produce, embedded here verbatim rather than called. It is placed inline exactly as
|
|
63
|
-
* `consentBootstrapScript`'s own output is: dynamic values (the storage key, the category shape,
|
|
64
|
-
* the event name) are JSON-encoded so nothing configured can break out of a string literal, and
|
|
65
|
-
* escaping the RESULT for HTML (`</script`, `<!--`) is left to whoever composes the final inline
|
|
66
|
-
* script — the same discipline `consentBootstrapScript` follows, and `googleTagHeadScript` already
|
|
67
|
-
* applies to the whole script it stamps.
|
|
68
|
-
*/
|
|
69
|
-
export declare const consentGateScript: (loaderExpr: string, opts?: ConsentOptions & {
|
|
70
|
-
categories?: ConsentCategory[];
|
|
71
|
-
}) => string;
|
|
72
|
-
//# sourceMappingURL=gtm.d.ts.map
|
package/build/gtm.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gtm.d.ts","sourceRoot":"","sources":["../src/gtm.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAUhF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAUrD;AAKD,8EAA8E;AAC9E,eAAO,MAAM,eAAe,gBACb,eAAe,EAAE,KAC7B,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAMrC,CAAA;AAED,sDAAsD;AACtD,eAAO,MAAM,aAAa,WAChB,aAAa,eAAe,eAAe,EAAE,KACpD,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAWrC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAAW,cAAc,KAAG,IAO3D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,WAAY,aAAa,SAAS,cAAc,KAAG,IA6B3E,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,WAClB,aAAa,GAAG,IAAI,eAAc,eAAe,EAAE,KAC1D,OAEF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,UAAW,cAAc,KAAG,MA+B9D,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB,eAChB,MAAM,SAAS,cAAc,GAAG;IAAE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;CAAE,KAC7E,MAuBF,CAAA"}
|