@consenti/ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -66
- package/dist/angular.d.cts +306 -130
- package/dist/angular.d.ts +306 -130
- package/dist/chunk-3EXCD24H.mjs +2 -0
- package/dist/{chunk-K2UN3O67.mjs.map → chunk-3EXCD24H.mjs.map} +1 -1
- package/dist/chunk-J4BKXUD6.mjs +2 -0
- package/dist/chunk-J4BKXUD6.mjs.map +1 -0
- package/dist/http-AQVVUCX6.mjs +2 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +616 -105
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/profiles-OEZAKEWL.mjs +2 -0
- package/dist/profiles-OEZAKEWL.mjs.map +1 -0
- package/dist/react.d.cts +285 -108
- package/dist/react.d.ts +285 -108
- package/dist/vue.d.cts +257 -57
- package/dist/vue.d.ts +257 -57
- package/package.json +8 -7
- package/dist/chunk-K2UN3O67.mjs +0 -2
- package/dist/http-UU2NIW6M.mjs +0 -2
- /package/dist/{http-UU2NIW6M.mjs.map → http-AQVVUCX6.mjs.map} +0 -0
package/README.md
CHANGED
|
@@ -114,8 +114,8 @@ const widget = new ConsentiSetup({
|
|
|
114
114
|
core: {
|
|
115
115
|
tenantId: 'acme', // identifies your tenant's profiles; default: 'default'
|
|
116
116
|
locale: 'en', // BCP 47; 'auto' = navigator.language; default: 'auto'
|
|
117
|
-
|
|
118
|
-
//
|
|
117
|
+
dir: 'auto', // 'ltr' | 'rtl' | 'auto'; 'auto' derives from locale
|
|
118
|
+
// (ar/he/fa/ur/ps/sd/ug/yi → rtl); default: 'auto'
|
|
119
119
|
storage: 'cookie', // 'cookie' | 'localStorage'; default: 'cookie'
|
|
120
120
|
cookieDomains: '.example.com', // comma-separated; first entry used as Domain attribute
|
|
121
121
|
cookieSigningKey: 'min-32-char-secret', // HMAC-SHA256 signing; implicit from presence
|
|
@@ -166,12 +166,19 @@ const widget = new ConsentiSetup({
|
|
|
166
166
|
// WidgetCountryResolverFn → custom async function: () => Promise<{ country, region, confidence }>
|
|
167
167
|
geoDataProvider: 'default',
|
|
168
168
|
|
|
169
|
-
// Age gate (optional)
|
|
169
|
+
// Age gate (optional) — see "Age Gate" section below
|
|
170
170
|
ageGate: {
|
|
171
171
|
enabled: false,
|
|
172
172
|
minimumAge: 13,
|
|
173
173
|
requireParentalConsent: false,
|
|
174
174
|
},
|
|
175
|
+
|
|
176
|
+
// TCF (IAB Transparency & Consent Framework) client stub (optional) — see "TCF" section below
|
|
177
|
+
tcf: {
|
|
178
|
+
enabled: false,
|
|
179
|
+
cmpId: 0, // must match the cmpId configured on the backend (TcfConfig)
|
|
180
|
+
cmpVersion: 1,
|
|
181
|
+
},
|
|
175
182
|
},
|
|
176
183
|
|
|
177
184
|
// ── Mount point (optional) ────────────────────────────────────────────────
|
|
@@ -211,6 +218,30 @@ const widget = new ConsentiSetup({
|
|
|
211
218
|
})
|
|
212
219
|
```
|
|
213
220
|
|
|
221
|
+
### `profileOverride` — deep-merge and delete semantics
|
|
222
|
+
|
|
223
|
+
`profileOverride` (and `widget.setProfile()` at runtime) is deep-merged onto the resolved profile
|
|
224
|
+
— server-fetched, pre-built, or locally registered — before anything renders. Merge rules:
|
|
225
|
+
|
|
226
|
+
- Omitting a key (or setting it to `undefined`) leaves the resolved profile's value untouched.
|
|
227
|
+
- An object value merges recursively, key by key — you only need to specify what differs.
|
|
228
|
+
- An array value replaces/merges by index against the base array.
|
|
229
|
+
- Setting a key to `null` **deletes it** from the merged result (JSON Merge Patch / RFC 7396
|
|
230
|
+
semantics). This is the way to remove a single entry from a keyed map — a cookie category, a
|
|
231
|
+
parameter — without having to know or repeat the rest of that map's contents:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
profileOverride: {
|
|
235
|
+
preferenceModal: {
|
|
236
|
+
categories: { marketing: null }, // removes the 'marketing' category entirely
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`{ marketing: undefined }` or `{ marketing: {} }` do **not** delete the entry — the former is a
|
|
242
|
+
no-op (same as omitting the key), the latter merges an empty object onto the existing category,
|
|
243
|
+
changing nothing. Only an explicit `null` removes it.
|
|
244
|
+
|
|
214
245
|
---
|
|
215
246
|
|
|
216
247
|
## Compliance Groups
|
|
@@ -260,7 +291,7 @@ new ConsentiSetup({
|
|
|
260
291
|
| **1A** | `api.enabled = false`, `compliance.type = 'auto'` | Timezone + language → group → pre-built profile |
|
|
261
292
|
| **1B** | `api.enabled = false`, `compliance.type = ComplianceGroupId` | Direct pre-built profile load |
|
|
262
293
|
| **2A** | `api.enabled = true`, `compliance.type = 'auto'` | `GET /resolve-profile?tz&lang&locale&tenantId` → `filePath` → static JSON |
|
|
263
|
-
| **2B** | `api.enabled = true`, `compliance.type = ComplianceGroupId` | `GET /profiles/:tenantId/:group/:locale
|
|
294
|
+
| **2B** | `api.enabled = true`, `compliance.type = ComplianceGroupId` | `GET /profiles/:tenantId/:group/:locale` |
|
|
264
295
|
| **Local** | `compliance.type = localKey` + registered `ConsentiProfile` | Locally registered profile |
|
|
265
296
|
| **Fallback** | Any fetch failure | Pre-built profile → `DEFAULT_PROFILE` |
|
|
266
297
|
|
|
@@ -276,25 +307,80 @@ Set `api.trustDomain: true` to bypass this check (useful for localhost / dev env
|
|
|
276
307
|
|
|
277
308
|
## GPC — Global Privacy Control
|
|
278
309
|
|
|
279
|
-
GPC mode is set per-profile on the server (`gpcMode: 'honor' | 'strict' | 'ignore'`). Widget config
|
|
310
|
+
GPC mode is set per-profile on the server (`gpcMode: 'honor' | 'strict' | 'ignore'`). Widget config profileOverrides overrides the profile value when explicitly set.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Age Gate
|
|
315
|
+
|
|
316
|
+
Set `compliance.ageGate.enabled: true` to require age confirmation before the visitor sees any
|
|
317
|
+
other consent UI (banner, GPC, CCPA opt-out — all of it waits behind the age gate on first visit):
|
|
280
318
|
|
|
281
319
|
```ts
|
|
282
|
-
|
|
283
|
-
|
|
320
|
+
new ConsentiSetup({
|
|
321
|
+
compliance: {
|
|
322
|
+
ageGate: { enabled: true, minimumAge: 16, requireParentalConsent: true },
|
|
323
|
+
},
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- **Confirmed** (visitor is old enough) → the normal banner/GPC/CCPA flow proceeds exactly as if
|
|
328
|
+
the age gate didn't exist, and every consent submission from then on carries `ageVerified: true`.
|
|
329
|
+
- **Declined, `requireParentalConsent: false`** → a deny-all consent is submitted immediately
|
|
330
|
+
(mandatory/strictly-necessary cookies still granted), no banner shown, `ageVerified: false`.
|
|
331
|
+
- **Declined, `requireParentalConsent: true`** → same deny-all submission, plus a
|
|
332
|
+
`parentalConsentToken` is generated and a `consenti:parentalConsentRequired` event fires
|
|
333
|
+
carrying it. There's no email-verification pipeline built into this package — the event is the
|
|
334
|
+
hook for wiring your own out-of-band parental-consent process (once verified externally, call
|
|
335
|
+
`PUT /consent/:visitorId` yourself to update the record).
|
|
284
336
|
|
|
285
|
-
|
|
286
|
-
|
|
337
|
+
The prompt only asks once per visitor per profile — it doesn't re-appear once any consent record
|
|
338
|
+
exists (same "already decided" check the banner itself uses).
|
|
287
339
|
|
|
288
|
-
|
|
289
|
-
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## TCF — IAB Transparency & Consent Framework
|
|
343
|
+
|
|
344
|
+
Set `compliance.tcf.enabled: true` to install `window.__tcfapi`, the standard IAB entry point
|
|
345
|
+
third-party ad-tech scripts call to read consent (`ping`, `getTCData`, `addEventListener`,
|
|
346
|
+
`removeEventListener`):
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
new ConsentiSetup({
|
|
350
|
+
compliance: {
|
|
351
|
+
tcf: { enabled: true, cmpId: 280, cmpVersion: 1 }, // cmpId must match the backend's TcfConfig
|
|
352
|
+
},
|
|
353
|
+
})
|
|
290
354
|
```
|
|
291
355
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
356
|
+
The stub follows the standard queue-command convention, so it's safe regardless of load order
|
|
357
|
+
relative to third-party scripts that call `__tcfapi` before Consenti has initialized. Only one CMP
|
|
358
|
+
may own `window.__tcfapi` per page — if it's already set (a real CMP, or another `ConsentiSetup`
|
|
359
|
+
instance on a multi-profile page), this stub does not overwrite it.
|
|
360
|
+
|
|
361
|
+
This is a simplified implementation: `tcString` is a base64url-encoded JSON payload (the same
|
|
362
|
+
format the backend's `tcfString` uses), not the full IAB binary bitfield encoding, and vendor-list
|
|
363
|
+
metadata (`gvlVersion`, `publisherCC`) is a placeholder since the widget doesn't fetch the GVL
|
|
364
|
+
client-side. For full IAB TCF v2.2 compliance, use the `iabtcf-core` npm package instead.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## RTL / Text Direction
|
|
369
|
+
|
|
370
|
+
`core.dir` controls the banner/modal's reading direction:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
new ConsentiSetup({ core: { locale: 'ar', dir: 'auto' } }) // → rtl, derived from locale
|
|
374
|
+
new ConsentiSetup({ core: { dir: 'rtl' } }) // explicit, independent of locale
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`'auto'` (the default) maps a known RTL-language prefix (`ar`, `he`, `fa`, `ur`, `ps`, `sd`, `ug`,
|
|
378
|
+
`yi`) to `rtl`; anything else is `ltr`. The `dir` attribute is set on the widget's root element, so
|
|
379
|
+
browser-native mirroring (flex layout, `text-align: start/end`) and the widget's own `[dir="rtl"]`
|
|
380
|
+
CSS overrides (toggle switches, locale switcher, close button) apply automatically — no separate
|
|
381
|
+
RTL stylesheet needed. Explicit banner/modal position variants (`left-bottom`, `right-bottom`,
|
|
382
|
+
modal `left`/`right` slide-in) are **not** mirrored — those are screen positions you chose
|
|
383
|
+
deliberately, independent of text direction.
|
|
298
384
|
|
|
299
385
|
---
|
|
300
386
|
|
|
@@ -327,12 +413,12 @@ import { ConsentiProfile, ConsentiSetup } from '@consenti/ui'
|
|
|
327
413
|
const profile = new ConsentiProfile({
|
|
328
414
|
defaultLocale: 'en',
|
|
329
415
|
complianceGroup: 'opt-in',
|
|
330
|
-
cookies:
|
|
331
|
-
{
|
|
332
|
-
{
|
|
333
|
-
{
|
|
416
|
+
cookies: {
|
|
417
|
+
necessary: { purpose: 'necessary', listenGpc: false },
|
|
418
|
+
analytics: { purpose: 'analytics', listenGpc: true },
|
|
419
|
+
marketing: { purpose: 'marketing', listenGpc: true, cpraCategory: 'sale' },
|
|
334
420
|
// cpraCategory: 'sale' | 'sharing' | 'sensitive' — used by CPRA group
|
|
335
|
-
|
|
421
|
+
},
|
|
336
422
|
translations: {
|
|
337
423
|
en: {
|
|
338
424
|
mainBanner: {
|
|
@@ -343,13 +429,14 @@ const profile = new ConsentiProfile({
|
|
|
343
429
|
heading: 'We value your privacy',
|
|
344
430
|
headingTag: 'h2', // HTML tag for the heading; default 'h2'
|
|
345
431
|
htmlText: 'We use cookies to improve your experience.',
|
|
346
|
-
buttons:
|
|
347
|
-
|
|
432
|
+
buttons: {
|
|
433
|
+
// key = machine id, rendered as the button's DOM id: `consenti-btn-{id}`, for targeting a specific button
|
|
348
434
|
// cookies: '*' = grant all | '!' = deny all | ['id1','id2'] = grant specific
|
|
349
|
-
{ text: '
|
|
350
|
-
{ text: '
|
|
351
|
-
{ text: '
|
|
352
|
-
|
|
435
|
+
'accept-all': { text: 'Accept All', style: 'primary', action: 'custom', cookies: '*' },
|
|
436
|
+
'reject-optional': { text: 'Reject Optional', style: 'secondary', action: 'custom', cookies: '!' },
|
|
437
|
+
customize: { text: 'Customize', style: 'secondary', action: 'manage' },
|
|
438
|
+
'privacy-policy': { text: 'Privacy Policy', style: 'text', action: 'link', url: '/privacy' },
|
|
439
|
+
},
|
|
353
440
|
},
|
|
354
441
|
gpcBanner: { // shown instead of mainBanner when GPC detected
|
|
355
442
|
position: 'bottom',
|
|
@@ -357,10 +444,10 @@ const profile = new ConsentiProfile({
|
|
|
357
444
|
headingTag: 'h2',
|
|
358
445
|
showLocaleSwitcher: false,
|
|
359
446
|
htmlText: "Your browser's GPC signal was detected. Ad cookies have been pre-denied.",
|
|
360
|
-
buttons:
|
|
361
|
-
{ text: 'Understood', style: 'primary',
|
|
362
|
-
{ text: 'Customize',
|
|
363
|
-
|
|
447
|
+
buttons: {
|
|
448
|
+
understood: { text: 'Understood', style: 'primary', action: 'custom', cookies: '!' },
|
|
449
|
+
customize: { text: 'Customize', style: 'secondary', action: 'manage' },
|
|
450
|
+
},
|
|
364
451
|
},
|
|
365
452
|
preferenceModal: {
|
|
366
453
|
heading: 'Cookie Preferences',
|
|
@@ -373,34 +460,32 @@ const profile = new ConsentiProfile({
|
|
|
373
460
|
persistent: false, // true = cannot dismiss by clicking outside
|
|
374
461
|
overlayOpacity: 50,
|
|
375
462
|
mobileFullScreenBreakpoint: 576,
|
|
376
|
-
buttons:
|
|
377
|
-
{ text: 'Accept All',
|
|
378
|
-
{ text: 'Save Preferences', style: 'primary', action: 'submit' },
|
|
379
|
-
{ text: 'Reject Optional',
|
|
380
|
-
|
|
381
|
-
categories:
|
|
382
|
-
{
|
|
383
|
-
id: 'cat-necessary',
|
|
463
|
+
buttons: {
|
|
464
|
+
'accept-all': { text: 'Accept All', style: 'primary', action: 'custom', cookies: '*' },
|
|
465
|
+
'save-preferences': { text: 'Save Preferences', style: 'primary', action: 'submit' },
|
|
466
|
+
'reject-optional': { text: 'Reject Optional', style: 'text', action: 'custom', cookies: '!' },
|
|
467
|
+
},
|
|
468
|
+
categories: {
|
|
469
|
+
necessary: {
|
|
384
470
|
heading: 'Strictly Necessary',
|
|
385
471
|
headingTag: 'h3',
|
|
386
472
|
htmlText: 'Required for the site to function. <strong>Cannot be disabled.</strong>',
|
|
387
|
-
|
|
473
|
+
legalBasis: 'mandatory',
|
|
388
474
|
cookies: ['necessary'],
|
|
389
475
|
},
|
|
390
|
-
{
|
|
391
|
-
id: 'cat-analytics',
|
|
476
|
+
analytics: {
|
|
392
477
|
heading: 'Analytics',
|
|
393
478
|
htmlText: 'Helps us understand how visitors use the site.',
|
|
394
|
-
|
|
479
|
+
legalBasis: 'consent', // 'mandatory' | 'consent' | 'legitimate_interest'
|
|
395
480
|
cookies: ['analytics'],
|
|
396
481
|
},
|
|
397
|
-
{
|
|
398
|
-
id: 'cat-marketing',
|
|
482
|
+
marketing: {
|
|
399
483
|
heading: 'Marketing',
|
|
400
484
|
htmlText: 'Used to personalise ads and measure campaign performance.',
|
|
485
|
+
legalBasis: 'consent',
|
|
401
486
|
cookies: ['marketing'],
|
|
402
487
|
},
|
|
403
|
-
|
|
488
|
+
},
|
|
404
489
|
},
|
|
405
490
|
},
|
|
406
491
|
fr: {
|
|
@@ -435,18 +520,77 @@ The widget calls `GET /resolve-profile?tz&lang&locale&tenantId`, receives the pr
|
|
|
435
520
|
|
|
436
521
|
## Cookie Format
|
|
437
522
|
|
|
438
|
-
Consent is persisted as a single cookie named `
|
|
523
|
+
Consent is persisted as a single cookie named `consenti_data` with a compact JSON value:
|
|
439
524
|
|
|
525
|
+
```json
|
|
526
|
+
{"s":1,"v":2,"i":"5huf-…","u":"","t":1751692400,"g":0,"p":0,"c":{"analytics_storage":"g","ad_storage":"d"}}
|
|
440
527
|
```
|
|
441
|
-
|
|
528
|
+
|
|
529
|
+
| Field | Type | Meaning |
|
|
530
|
+
|---|---|---|
|
|
531
|
+
| `s` | number | Profile ID |
|
|
532
|
+
| `v` | number | Profile version |
|
|
533
|
+
| `i` | string | Per-submission UUID (consent receipt ID) |
|
|
534
|
+
| `u` | string | Logged-in user ID (`""` for anonymous) |
|
|
535
|
+
| `t` | number | Unix timestamp of consent |
|
|
536
|
+
| `g` | 0 \| 1 | GPC signal detected |
|
|
537
|
+
| `p` | number | Consent source: 0 = user click, 1 = widget method |
|
|
538
|
+
| `c` | object | Consent map: cookie ID → `"g"` (granted), `"o"` (objected), `"d"` (denied) |
|
|
539
|
+
|
|
540
|
+
When the widget is upgraded from an older version, any existing `consenti_{profileId}` cookie is automatically read, migrated to the new format, and deleted — no consent loss occurs.
|
|
541
|
+
|
|
542
|
+
The consent cookie lifetime equals the shortest `cookie.expiry` value across all profile cookies (in days, converted to `max-age` seconds). When that cookie expires the browser deletes it, the widget sees no consent, and re-shows the banner.
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Display Features
|
|
547
|
+
|
|
548
|
+
### Footer Metadata
|
|
549
|
+
|
|
550
|
+
When `showFooterMetadata: true` is set in the resolved profile, a metadata strip is injected inside the banner and modal after the main content. It shows the visitor's Consent ID (truncated UUID), Consent Date, Profile Version, and a "Privacy Settings" button that opens the preference modal.
|
|
551
|
+
|
|
552
|
+
Configured in the dashboard (ProfileEditor → Step 1) or via `profileOverride`:
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
new ConsentiSetup({
|
|
556
|
+
compliance: { type: 'opt-in' },
|
|
557
|
+
profileOverride: { showFooterMetadata: true },
|
|
558
|
+
})
|
|
442
559
|
```
|
|
443
560
|
|
|
444
|
-
|
|
561
|
+
### Enhanced Accessibility
|
|
562
|
+
|
|
563
|
+
When `enhanceAccessibility: true` is set in the resolved profile, the class `.consenti--enhanced-a11y` is added to the root element. This applies:
|
|
564
|
+
|
|
565
|
+
- Minimum 44 × 44 px hit area on all buttons (WCAG 2.1 SC 2.5.5)
|
|
566
|
+
- 3 px solid focus ring (`outline-offset: 2px`) on all focusable elements
|
|
567
|
+
- No impact on default styling — opt-in only
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
profileOverride: { enhanceAccessibility: true }
|
|
445
571
|
```
|
|
446
|
-
|
|
572
|
+
|
|
573
|
+
### Responsive Button Stacking (`stackButtonsOnBreakpoint`)
|
|
574
|
+
|
|
575
|
+
Set `mainBanner.stackButtonsOnBreakpoint` (and/or `gpcBanner.stackButtonsOnBreakpoint`) to a pixel width. Below that breakpoint the banner's buttons stack vertically and stretch to full width — useful for narrow mobile screens where 3+ buttons overflow.
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
profileOverride: {
|
|
579
|
+
mainBanner: { stackButtonsOnBreakpoint: 576 }, // px; 0 or undefined = disabled
|
|
580
|
+
}
|
|
447
581
|
```
|
|
448
582
|
|
|
449
|
-
|
|
583
|
+
A scoped `<style>` element containing a single `@media (max-width: Npx)` rule is injected into the root element — no external stylesheet required, and it's removed on `destroy()`.
|
|
584
|
+
|
|
585
|
+
### Modal Focus Trap (`trapFocus`)
|
|
586
|
+
|
|
587
|
+
When `preferenceModal.trapFocus: true`, keyboard Tab / Shift+Tab navigation is confined within the consent modal while it is open. `Escape` closes the modal and returns focus to the triggering element.
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
profileOverride: {
|
|
591
|
+
preferenceModal: { trapFocus: true },
|
|
592
|
+
}
|
|
593
|
+
```
|
|
450
594
|
|
|
451
595
|
---
|
|
452
596
|
|
|
@@ -472,7 +616,13 @@ const widget = new ConsentiSetup({ compliance: { type: 'opt-in' } })
|
|
|
472
616
|
// Consent state
|
|
473
617
|
widget.hasConsent() // boolean — true if valid consent record exists
|
|
474
618
|
widget.getConsent() // Record<string, 'granted'|'denied'|'objected'> | null
|
|
475
|
-
widget.
|
|
619
|
+
widget.getConsent('google-gtm') // Google Consent Mode v2 format
|
|
620
|
+
widget.getConsent('category') // { necessary, functional, preferences, analytics, marketing }
|
|
621
|
+
widget.getConsent('adobe') // { analytics, target, manager, optimizer }
|
|
622
|
+
widget.getConsent('meta') // { pixel, api, plugins, facebookLogin }
|
|
623
|
+
widget.getConsent('microsoft-clarity') // { session, heatmaps, performance }
|
|
624
|
+
widget.getConsent('twilio-segment') // { identify, page, track, group, alias }
|
|
625
|
+
widget.getGTMConsent() // @deprecated — use getConsent('google-gtm')
|
|
476
626
|
widget.getConsentDate() // Date | false — time of last submission
|
|
477
627
|
widget.isCookieGranted('analytics') // boolean — true if that cookie is 'granted'
|
|
478
628
|
widget.isCookieGranted('analytics', true)// 'granted' | 'denied' | 'objected' | false
|
|
@@ -575,7 +725,7 @@ await widget.submitConsent({
|
|
|
575
725
|
|
|
576
726
|
// Or use the convenience methods:
|
|
577
727
|
await widget.grantAll() // accept all
|
|
578
|
-
await widget.denyAll() //
|
|
728
|
+
await widget.denyAll() // Reject Optional non-mandatory
|
|
579
729
|
```
|
|
580
730
|
|
|
581
731
|
---
|
|
@@ -591,6 +741,7 @@ Custom DOM events fire on `window` at every lifecycle step. All are prefixed `co
|
|
|
591
741
|
| `consenti:modalVisibility` | Preference modal shows or hides |
|
|
592
742
|
| `consenti:consentBeingSubmitted` | User clicked a consent button (before API call) |
|
|
593
743
|
| `consenti:consentSubmitted` | Consent saved (cookie written + API call if configured) |
|
|
744
|
+
| `consenti:parentalConsentRequired` | Age gate declined with `requireParentalConsent: true` — carries `parentalConsentToken` |
|
|
594
745
|
|
|
595
746
|
**Recommended:** use `widget.on()` / `widget.off()` for typed subscriptions (the `consenti:` prefix is optional):
|
|
596
747
|
|
|
@@ -612,39 +763,64 @@ window.addEventListener('consenti:consentSubmitted', (e: Event) => {
|
|
|
612
763
|
|
|
613
764
|
## GTM / Google Consent Mode v2
|
|
614
765
|
|
|
766
|
+
Setting `utils.gtm` to any object (even `{}`) turns on real Consent Mode signalling — no gtag.js
|
|
767
|
+
needs to already be on the page, Consenti defines the standard stub itself.
|
|
768
|
+
|
|
615
769
|
```ts
|
|
616
770
|
new ConsentiSetup({
|
|
617
771
|
compliance: { type: 'opt-in' },
|
|
618
772
|
utils: {
|
|
619
773
|
gtm: {
|
|
620
|
-
containerId: 'GTM-XXXXXX',
|
|
621
|
-
urlPassthrough: true,
|
|
622
|
-
adsDataRedaction: true,
|
|
774
|
+
containerId: 'GTM-XXXXXX', // omit if you load GTM/gtag.js yourself
|
|
775
|
+
urlPassthrough: true, // cookieless conversion modelling
|
|
776
|
+
adsDataRedaction: true, // redact ad pings when consent denied
|
|
623
777
|
},
|
|
624
778
|
},
|
|
625
779
|
})
|
|
626
780
|
```
|
|
627
781
|
|
|
628
|
-
|
|
782
|
+
**`GtmConfig` options:**
|
|
783
|
+
|
|
784
|
+
| Option | Type | Default | Description |
|
|
785
|
+
|---|---|---|---|
|
|
786
|
+
| `containerId` | `string` | `undefined` | GTM container ID, e.g. `'GTM-XXXXXX'`. When set, Consenti injects the GTM library itself — omit if you already load GTM/gtag.js separately (Consent Mode signalling works either way). |
|
|
787
|
+
| `dataLayer` | `string` | `'dataLayer'` | Name of the dataLayer array on `window`. Override only for a custom variable name. |
|
|
788
|
+
| `verbose` | `boolean` | `false` | When `true`, additionally mirrors every `consenti:*` event onto the dataLayer as a generic `{ event, content }` push — for custom, non-Consent-Mode GTM triggers. |
|
|
789
|
+
| `events` | `string[]` | `[]` | Only relevant when `verbose: true` — narrows which event names get mirrored. Empty means all. Has no effect on the core `gtag('consent', …)` calls, which always fire. |
|
|
790
|
+
| `urlPassthrough` | `boolean` | `false` | Calls `gtag('set', 'url_passthrough', true)` alongside every consent update. |
|
|
791
|
+
| `adsDataRedaction` | `boolean` | `false` | Calls `gtag('set', 'ads_data_redaction', true)` when `ad_storage` is denied. |
|
|
792
|
+
|
|
793
|
+
Consenti calls the real `gtag()` consent API — via the standard stub-queue pattern, so it works
|
|
794
|
+
whether your own gtag.js/GTM snippet loads before or after Consenti:
|
|
629
795
|
|
|
630
796
|
```js
|
|
631
797
|
// On initialisation (default denied state)
|
|
632
|
-
|
|
798
|
+
gtag('consent', 'default', { analytics_storage: 'denied', ... })
|
|
633
799
|
|
|
634
800
|
// On submission
|
|
635
|
-
|
|
801
|
+
gtag('consent', 'update', { analytics_storage: 'granted', ad_storage: 'denied', ... })
|
|
802
|
+
|
|
803
|
+
// Plus, when configured:
|
|
804
|
+
gtag('set', 'url_passthrough', true)
|
|
805
|
+
gtag('set', 'ads_data_redaction', true) // true when ad_storage is denied
|
|
636
806
|
```
|
|
637
807
|
|
|
638
|
-
`
|
|
808
|
+
Set `utils.gtm.verbose: true` to additionally mirror every `consenti:*` event onto the dataLayer
|
|
809
|
+
as a generic `{ event, content }` push — off by default.
|
|
810
|
+
|
|
811
|
+
`getConsent('google-gtm')` returns consent in Google Consent Mode v2 format. `getGTMConsent()` is a deprecated alias for the same call.
|
|
639
812
|
|
|
640
813
|
```ts
|
|
641
|
-
widget.
|
|
814
|
+
widget.getConsent('google-gtm')
|
|
642
815
|
// {
|
|
643
816
|
// analytics_storage: 'granted',
|
|
644
817
|
// ad_storage: 'denied',
|
|
645
818
|
// ad_user_data: 'denied',
|
|
646
819
|
// ad_personalization: 'denied',
|
|
647
820
|
// functionality_storage: 'granted',
|
|
821
|
+
// security_storage: 'granted',
|
|
822
|
+
// ads_data_redaction: 'true',
|
|
823
|
+
// url_passthrough: 'false',
|
|
648
824
|
// }
|
|
649
825
|
```
|
|
650
826
|
|
|
@@ -870,7 +1046,7 @@ export function ConsentSetup() {
|
|
|
870
1046
|
import('@consenti/ui').then(({ ConsentiSetup }) => {
|
|
871
1047
|
widget = new ConsentiSetup({
|
|
872
1048
|
api: { enabled: true, baseUrl: process.env.NEXT_PUBLIC_API_URL },
|
|
873
|
-
core: {
|
|
1049
|
+
core: { },
|
|
874
1050
|
})
|
|
875
1051
|
widgetRef.current = widget
|
|
876
1052
|
})
|
|
@@ -1037,9 +1213,9 @@ new ConsentiSetup({
|
|
|
1037
1213
|
api: { enabled: true },
|
|
1038
1214
|
})
|
|
1039
1215
|
|
|
1040
|
-
//
|
|
1216
|
+
// GTM
|
|
1041
1217
|
new ConsentiSetup({
|
|
1042
|
-
core: {
|
|
1218
|
+
core: {},
|
|
1043
1219
|
utils: { gtm: { containerId: 'GTM-XXXXXX', adsDataRedaction: true } },
|
|
1044
1220
|
})
|
|
1045
1221
|
|
|
@@ -1047,6 +1223,19 @@ new ConsentiSetup({
|
|
|
1047
1223
|
new ConsentiSetup({
|
|
1048
1224
|
core: { console: ['error', 'warning', 'info', 'log'] },
|
|
1049
1225
|
})
|
|
1226
|
+
|
|
1227
|
+
// Arabic site — RTL derived automatically from locale
|
|
1228
|
+
new ConsentiSetup({ core: { locale: 'ar' } })
|
|
1229
|
+
|
|
1230
|
+
// COPPA — block under-13s, require parental consent
|
|
1231
|
+
new ConsentiSetup({
|
|
1232
|
+
compliance: { ageGate: { enabled: true, minimumAge: 13, requireParentalConsent: true } },
|
|
1233
|
+
})
|
|
1234
|
+
|
|
1235
|
+
// TCF — install the __tcfapi stub for ad-tech scripts (cmpId must match the backend's tcf config)
|
|
1236
|
+
new ConsentiSetup({
|
|
1237
|
+
compliance: { tcf: { enabled: true, cmpId: 280, cmpVersion: 1 } },
|
|
1238
|
+
})
|
|
1050
1239
|
```
|
|
1051
1240
|
|
|
1052
1241
|
---
|
|
@@ -1081,7 +1270,7 @@ The following `CoreConfig` fields were **removed** in v0.1.x:
|
|
|
1081
1270
|
| `core.profileId` | Use `compliance.type` with a compliance group or local profile key |
|
|
1082
1271
|
| `core.regulation` | Use `compliance.type` — compliance group is derived server-side or from pre-built profiles |
|
|
1083
1272
|
| `core.signCookies` | Implicit: set `core.cookieSigningKey` to enable signing; no separate flag needed |
|
|
1084
|
-
| `core.privacyPolicyUrl` | Add a link button directly in the profile banner/modal `buttons`
|
|
1273
|
+
| `core.privacyPolicyUrl` | Add a link button directly in the profile banner/modal `buttons` map with `action: 'link'` |
|
|
1085
1274
|
|
|
1086
1275
|
---
|
|
1087
1276
|
|