@consenti/ui 0.2.0 → 0.4.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.
Files changed (42) hide show
  1. package/README.md +512 -138
  2. package/dist/angular.d.cts +610 -151
  3. package/dist/angular.d.ts +610 -151
  4. package/dist/angular.js +1 -1
  5. package/dist/angular.js.map +1 -1
  6. package/dist/angular.mjs +1 -1
  7. package/dist/angular.mjs.map +1 -1
  8. package/dist/chunk-3EXCD24H.mjs +2 -0
  9. package/dist/{chunk-K2UN3O67.mjs.map → chunk-3EXCD24H.mjs.map} +1 -1
  10. package/dist/chunk-RDE7CM6T.mjs +2 -0
  11. package/dist/chunk-RDE7CM6T.mjs.map +1 -0
  12. package/dist/http-AQVVUCX6.mjs +2 -0
  13. package/dist/index.css +1 -1
  14. package/dist/index.d.ts +971 -125
  15. package/dist/index.mjs +1 -1
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/index.umd.js +1 -1
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/profiles-5ZLWWN2L.mjs +2 -0
  20. package/dist/profiles-5ZLWWN2L.mjs.map +1 -0
  21. package/dist/react.d.cts +589 -129
  22. package/dist/react.d.ts +589 -129
  23. package/dist/react.js +1 -1
  24. package/dist/react.js.map +1 -1
  25. package/dist/react.mjs +1 -1
  26. package/dist/react.mjs.map +1 -1
  27. package/dist/testing.d.cts +1 -1
  28. package/dist/testing.d.ts +1 -1
  29. package/dist/testing.js +1 -1
  30. package/dist/testing.js.map +1 -1
  31. package/dist/testing.mjs +1 -1
  32. package/dist/testing.mjs.map +1 -1
  33. package/dist/vue.d.cts +562 -79
  34. package/dist/vue.d.ts +562 -79
  35. package/dist/vue.js +1 -1
  36. package/dist/vue.js.map +1 -1
  37. package/dist/vue.mjs +1 -1
  38. package/dist/vue.mjs.map +1 -1
  39. package/package.json +13 -8
  40. package/dist/chunk-K2UN3O67.mjs +0 -2
  41. package/dist/http-UU2NIW6M.mjs +0 -2
  42. /package/dist/{http-UU2NIW6M.mjs.map → http-AQVVUCX6.mjs.map} +0 -0
package/dist/vue.d.cts CHANGED
@@ -1,11 +1,46 @@
1
1
  import * as vue from 'vue';
2
2
 
3
3
  type NonEmptyArray<T> = [T, ...T[]];
4
+ /**
5
+ * Deep-partial with JSON Merge Patch (RFC 7396) delete semantics: any key may be
6
+ * set to `null` instead of its usual type to signal "delete this key" when the
7
+ * partial is applied with `deepMerge()` — `undefined`/omitted keeps the base
8
+ * value, `null` removes it. Only meaningful for object/map keys (e.g. removing
9
+ * a `CategoryMap`/`CookieMap` entry via `profileOverride`); array elements are
10
+ * merged positionally and don't support deletion this way.
11
+ */
4
12
  type DeepPartial<T> = T extends Function ? T : T extends Array<infer InferredArrayType> ? Array<DeepPartial<InferredArrayType>> : T extends ReadonlyArray<infer InferredReadonlyArrayType> ? ReadonlyArray<DeepPartial<InferredReadonlyArrayType>> : T extends object ? {
5
- [Key in keyof T]?: DeepPartial<T[Key]>;
13
+ [Key in keyof T]?: DeepPartial<T[Key]> | null;
6
14
  } : T;
7
15
 
16
+ /**
17
+ * Single source of truth for all compliance groups, regulations, and country mappings.
18
+ *
19
+ * This is DATA, not types. The `Compliance` and `ComplianceGroupId` literal types
20
+ * derived from the ID arrays below live in @consenti/types (packages/types/src/compliance.ts),
21
+ * via a type-only import — that keeps @consenti/types free of runtime code.
22
+ *
23
+ * ─── HOW TO UPDATE ────────────────────────────────────────────────────────────
24
+ *
25
+ * ADD A NEW REGULATION:
26
+ * 1. Add regulation ID string to `COMPLIANCE_REGULATION_IDS` below
27
+ * (the `Compliance` type in @consenti/types auto-updates from it).
28
+ * 2. Add it to the appropriate group's `compliances` array below.
29
+ * 3. Add / update the country entry in `EMBEDDED_COMPLIANCE_MAP` below.
30
+ * That is all — no other files need touching.
31
+ *
32
+ * ADD A NEW COMPLIANCE GROUP:
33
+ * 1. Add the kebab-case ID to `COMPLIANCE_GROUP_IDS` below.
34
+ * 2. Add a full group entry to `COMPLIANCE_GROUPS` below (label, compliances, flags).
35
+ * 3. Add a validation rule set to `COMPLIANCE_VALIDATION_RULES` below.
36
+ * 4. Map affected countries in `EMBEDDED_COMPLIANCE_MAP` to the new group.
37
+ *
38
+ * ADD OR UPDATE A COUNTRY:
39
+ * Edit the entry in `EMBEDDED_COMPLIANCE_MAP.countries` below. That is the ONLY
40
+ * place that needs changing for country-level jurisdiction updates.
41
+ */
8
42
  declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-strict", "opt-in-dpdpa", "opt-in-china", "opt-in-brazil", "general-privacy-consent", "notice-only"];
43
+ declare const COOKIE_PURPOSE_IDS: readonly ["necessary", "functional", "preferences", "analytics", "marketing"];
9
44
 
10
45
  /**
11
46
  * Compliance-related literal types, derived from the regulation/group ID data
@@ -21,6 +56,13 @@ declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-stri
21
56
  */
22
57
 
23
58
  type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
59
+ /**
60
+ * `(string & {})` (rather than plain `string`) keeps IDE autocomplete for the
61
+ * known `ComplianceGroupId` literals while still allowing any other string —
62
+ * used to target a profile authored against a custom (non-built-in)
63
+ * `customComplianceGroup` instead of one of the 8 built-in groups.
64
+ */
65
+ type ComplianceType = 'auto' | ComplianceGroupId | (string & {}) | Symbol;
24
66
 
25
67
  interface ComplianceMapData {
26
68
  version: string;
@@ -28,31 +70,81 @@ interface ComplianceMapData {
28
70
  }
29
71
  interface CountryComplianceEntry {
30
72
  complianceGroup: ComplianceGroupId;
31
- default: ComplianceGroupId;
32
- description: string;
73
+ /** Fallback group used when only the country (not the region) is known. Defaults to
74
+ * `complianceGroup` when omitted — only set this explicitly when a region within this
75
+ * country can be stricter than the country-wide baseline (see `overriddenRegions`). */
76
+ default?: ComplianceGroupId;
77
+ description?: string;
33
78
  overriddenRegions?: Record<string, {
34
79
  complianceGroup: ComplianceGroupId;
35
- description: string;
80
+ description?: string;
36
81
  }>;
37
82
  }
83
+ interface ConsentDbRecord {
84
+ id: string;
85
+ tenantId: string;
86
+ visitorId: string;
87
+ profileId: string;
88
+ locale: string;
89
+ consentJson: ConsentValue;
90
+ gpcDetected: boolean;
91
+ source: 'banner' | 'api' | 'import';
92
+ ageVerified?: boolean;
93
+ parentalConsentToken?: string;
94
+ tcfString?: string;
95
+ gppString?: string;
96
+ /** HMAC-SHA256 signature over the record's core fields, hex-encoded — tamper-evidence for
97
+ * server-stored records via `dataSigningHash` (auto-generated when unset), independent of the
98
+ * widget's own cookie-signing (`core.cookieSigningKey`). */
99
+ signature?: string;
100
+ createdAt: string;
101
+ updatedAt: string;
102
+ }
38
103
 
39
104
  type ConsentStatus = 'granted' | 'denied' | 'objected';
40
105
  type ConsentAction = 'accept_all' | 'reject_all' | 'custom' | 'update';
41
106
  type ConsentValue = Record<string, ConsentStatus>;
107
+ /**
108
+ * Output format for getConsent(type).
109
+ * 'default' → raw ConsentValue, keyed by every cookie parameter ID in the profile (existing behaviour)
110
+ * 'google-gtm' → Google Consent Mode v2 keys + ads_data_redaction, url_passthrough
111
+ * 'purpose' → consent per the fixed CookiePurpose taxonomy (necessary/functional/preferences/analytics/marketing)
112
+ * 'category' → consent per the tenant's own authored preference-modal category, keyed by category ID.
113
+ * 'granted' only when every member parameter is granted; 'denied'/'objected' otherwise
114
+ * (same ConsentStatus values as everything else — see getCategoryConsent for the exact rule)
115
+ * 'adobe' → { analytics, target, manager, optimizer }
116
+ * 'meta' → { pixel, api, plugins, facebookLogin }
117
+ * 'microsoft-clarity' → { session, heatmaps, performance }
118
+ * 'twilio-segment' → { identify, page, track, group, alias }
119
+ */
120
+ type ConsentType = 'default' | 'google-gtm' | 'purpose' | 'category' | 'adobe' | 'meta' | 'microsoft-clarity' | 'twilio-segment';
121
+ type GpcMode = 'ignore' | 'honor' | 'strict';
122
+ type LegalBasis = 'mandatory' | 'consent' | 'legitimate_interest';
123
+ type CookiePurpose = typeof COOKIE_PURPOSE_IDS[number];
124
+ /**
125
+ * A single consent parameter (tracker/cookie definition).
126
+ * Keyed by its own id inside `Record<string, Cookie>` — the map key IS the id,
127
+ * there is no `id` field on the value itself.
128
+ */
42
129
  interface Cookie {
43
- id: string;
44
- legalBasis?: 'mandatory' | 'consent' | 'legitimate_interest';
45
- /** @deprecated use legalBasis: 'mandatory' */
46
- mandatory?: boolean;
47
- /** @deprecated use legalBasis */
48
- type?: 'consent' | 'legitimate_interest';
130
+ purpose?: CookiePurpose;
49
131
  listenGpc?: boolean;
50
- expiry?: number;
132
+ /**
133
+ * Pre-grant this parameter's default consent to 'granted' (instead of the
134
+ * compliance-group-driven default) when no stored decision exists yet.
135
+ * Only meaningful when the owning category's `legalBasis === 'consent'` —
136
+ * `mandatory`/`legitimate_interest` categories are already effectively
137
+ * pre-granted regardless of this flag. Never overrides an active GPC signal.
138
+ * Default: false.
139
+ */
140
+ preGrant?: boolean;
51
141
  tcfVendorId?: number;
52
142
  tcfPurposes?: number[];
53
143
  tcfSpecialFeatures?: number[];
54
144
  cpraCategory?: 'sale' | 'sharing' | 'sensitive';
55
145
  }
146
+ /** `Cookie` keyed by its own id — the standard shape used everywhere a parameter list is passed around. */
147
+ type CookieMap = Record<string, Cookie>;
56
148
  type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
57
149
  type ButtonAction = 'submit' | 'manage' | 'close' | 'custom' | 'link';
58
150
  interface Button {
@@ -61,8 +153,11 @@ interface Button {
61
153
  action: ButtonAction;
62
154
  cookies?: string[] | '*' | '!';
63
155
  url?: string;
64
- type?: string;
65
156
  }
157
+ /** `Button` keyed by its own id — e.g. `'accept-all'`. The map key IS the id, rendered as the DOM
158
+ * `id` (`consenti-btn-{id}`) so integrators can target specific buttons, and is what
159
+ * `profileOverride`'s `deepMerge` deletes by (`{ 'reject-optional': null }` removes one button). */
160
+ type ButtonMap = Record<string, Button>;
66
161
  interface MainBanner {
67
162
  position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
68
163
  overlayOpacity?: number;
@@ -71,68 +166,187 @@ interface MainBanner {
71
166
  heading?: string;
72
167
  headingTag?: string;
73
168
  htmlText: string;
74
- buttons: Button[];
169
+ buttons: ButtonMap;
170
+ /** Stack buttons vertically below this viewport width in px. 0 or undefined = disabled. Default: 576. */
171
+ stackButtonsOnBreakpoint?: number;
172
+ /** Trap keyboard Tab focus within the consent root while this banner is visible. */
173
+ trapFocus?: boolean;
75
174
  }
76
175
  type GpcBanner = MainBanner;
176
+ /**
177
+ * A consent category — the single source of legal basis for every parameter
178
+ * it lists in `cookies`. Keyed by its own id inside `Record<string, Category>` —
179
+ * the map key IS the id, there is no `id` field on the value itself.
180
+ *
181
+ * A parameter must belong to exactly one category (enforced at authoring time,
182
+ * not by this type) — that relationship is what `getCookieLegalBasis`/
183
+ * `isMandatoryCookie` (`@consenti/utils`) resolve at runtime.
184
+ */
77
185
  interface Category {
78
- id: string;
79
186
  heading: string;
80
187
  headingTag?: string;
81
188
  htmlText: string;
82
- mandatory?: boolean;
83
- type?: 'consent' | 'legitimate_interest';
84
- legitimateInterest?: {
85
- enabled?: boolean;
86
- description?: string;
87
- };
189
+ legalBasis: LegalBasis;
190
+ /** Only meaningful when legalBasis === 'legitimate_interest' — the GDPR balancing-test justification shown to visitors. */
191
+ legitimateInterestDescription?: string;
192
+ /** IDs of the parameters (from the profile's `cookies` map) that belong to this category. */
88
193
  cookies: string[];
89
194
  }
195
+ /** `Category` keyed by its own id — the standard shape used everywhere a category list is passed around. */
196
+ type CategoryMap = Record<string, Category>;
90
197
  interface PreferenceModal extends Omit<MainBanner, "position"> {
91
198
  position?: 'left' | 'right' | 'center';
92
199
  subheading?: string;
93
- categories: Category[];
200
+ categories: CategoryMap;
94
201
  persistent?: boolean;
95
202
  /** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
96
203
  mobileFullScreenBreakpoint?: number;
204
+ /** Label for the optional consent-receipt download checkbox (shown when `allowReceipt` is true). Falls back to a default when omitted. */
205
+ receiptLabel?: string;
206
+ /** Description shown beneath the consent-receipt checkbox. Falls back to a default when omitted. */
207
+ receiptDescription?: string;
208
+ /**
209
+ * Renders a "Forget me" control in the modal body, below the categories — only shown to
210
+ * visitors who already have a stored consent decision (nothing to erase otherwise). Wired to
211
+ * the same `DELETE /consent/:visitorId` erasure endpoint as `widget.deleteConsent()`, giving
212
+ * visitors self-service access to GDPR Art. 17 / CCPA-CPRA / LGPD Art. 18 / UK GDPR / DPDPA /
213
+ * KVKK / POPIA / PDPA-TH / APPI erasure-equivalent rights without the host wiring a link
214
+ * themselves. See the "Right to erasure" guide for the full compliance mapping.
215
+ */
216
+ showForgetMe?: boolean;
217
+ /** Label for the "Forget me" button (shown when `showForgetMe` is true). Falls back to a default when omitted. */
218
+ forgetMeLabel?: string;
97
219
  }
98
220
  interface DpdpaConfig {
99
221
  dataFiduciary: string;
100
222
  grievanceEmail: string;
101
223
  purposeDescription?: string;
102
224
  }
225
+ /**
226
+ * Per-profile age gate — GDPR Art. 8's consent age varies 13–16 by EU member state and DPDPA has
227
+ * its own child-data age rules, so one global age (the old `ConsentiServerConfig.ageGate`) was
228
+ * wrong for a multi-region deployment. Mirrors `dpdpa` exactly: profile-scoped, not global.
229
+ */
230
+ interface AgeGateConfig {
231
+ enabled: boolean;
232
+ minimumAge: number;
233
+ requireParentalConsent?: boolean;
234
+ }
235
+ /** Per-locale age-gate modal text — translated the same way `mainBanner`/`preferenceModal`
236
+ * content is, unlike the widget's own hardcoded UI chrome (close button, locale switcher). */
237
+ interface AgeGateModalContent {
238
+ heading?: string;
239
+ htmlText: string;
240
+ confirmButtonLabel: string;
241
+ denyButtonLabel: string;
242
+ parentalConsent: {
243
+ heading?: string;
244
+ htmlText: string;
245
+ confirmButtonLabel: string;
246
+ };
247
+ }
103
248
  interface ResolvedProfile {
104
- id: number;
105
- profileUuid?: string;
106
- version: number;
249
+ id: string;
250
+ /** Incremented in place on every save of this profile. Absent for embedded/local profiles that have no server-side version counter. */
251
+ version?: number;
107
252
  defaultLocale: string;
108
253
  locales?: string[];
109
- cookies: Cookie[];
254
+ cookies: CookieMap;
255
+ /** Days until consent expires and the visitor is asked again (profile-wide). Default: 365. */
256
+ expiryDays?: number;
257
+ allowReceipt?: boolean;
110
258
  mainBanner: MainBanner;
111
259
  gpcBanner?: GpcBanner;
112
260
  preferenceModal: PreferenceModal;
113
261
  cookieSigningKey?: string;
114
262
  dpdpa?: DpdpaConfig;
263
+ ageGate?: AgeGateConfig;
264
+ ageGateModal?: AgeGateModalContent;
115
265
  darkMode?: boolean;
116
- gpcMode?: 'ignore' | 'honor' | 'strict';
117
- complianceGroup?: string;
266
+ gpcMode?: GpcMode;
267
+ /** One of the 8 built-in groups, or a custom group id (via `customComplianceGroup` /
268
+ * `ComplianceType`'s string passthrough) — this is the join key `complianceGroupsOverride`
269
+ * looks up by, so it must reflect whichever group this profile actually resolved against,
270
+ * built-in or custom. */
271
+ complianceGroup?: ComplianceGroupId | (string & {});
272
+ /** Set when the geo-resolved region carries a `requiresSensitiveOptIn` carve-out (e.g.
273
+ * Colorado under `opt-out`) — cookies tagged `cpraCategory: 'sensitive'` default to denied
274
+ * even though the rest of `complianceGroup`'s cookies default to granted. Only ever set by
275
+ * server-side geo resolution (`api.enabled: true` + a geoip/maxmind provider); the
276
+ * client-only timezone heuristic never resolves a region, so this is always unset there. */
277
+ requiresSensitiveOptIn?: boolean;
118
278
  complianceConfig?: Record<string, string>;
279
+ /** Falls back to `true` (hidden) if neither the widget config nor the profile set it. */
280
+ hidePoweredBy?: boolean;
281
+ /** Show metadata footer strip (Consent ID, Date, Version, Privacy Settings link). */
282
+ showFooterMetadata?: boolean;
283
+ /** Apply WCAG 2.1 AA accessibility enhancements to the widget. */
284
+ enhanceAccessibility?: boolean;
119
285
  }
120
286
  type WidgetCountryResolverFn = () => Promise<{
121
287
  country: string | null;
122
288
  region: string | null;
123
289
  confidence: number;
290
+ /** Optional escape hatch: when set, profile resolution uses this compliance group directly
291
+ * instead of computing one from `country`/`region` — mirrors the server-side `GeoResult`'s
292
+ * `complianceGroup` field (`@consenti/types`'s `api.ts`). `country`/`region` are still used for
293
+ * anything that reads them independently of group selection. Optional — omitting it preserves
294
+ * the existing timezone/language-based resolution exactly as before. */
295
+ complianceGroup?: string;
124
296
  }>;
125
- interface AgeGateWidgetConfig {
297
+ /** Mirrors the server-side `TcfConfig` shape (`@consenti/api`) — `cmpId`/`cmpVersion` must match
298
+ * what the backend uses to encode `tcfString`, since both feed the same simplified TC string.
299
+ *
300
+ * The client-side `__tcfapi` stub never fetches the Global Vendor List itself (multi-megabyte,
301
+ * and IAB policy requires CMPs to cache their own copy rather than have every visitor's browser
302
+ * fetch it) — `publisherCC`/`vendorListVersion`/`gvlVersion` are therefore operator-supplied
303
+ * config here, not auto-derived. Spec-correct binary TC-string encoding happens server-side in
304
+ * `@consenti/api` (see `compliance.tcf.publisherCC` there); this stub's `tcString` stays the
305
+ * simplified format documented in `@consenti/utils`'s `encodeTcString`. */
306
+ interface TcfWidgetConfig {
126
307
  enabled: boolean;
127
- minimumAge: number;
128
- requireParentalConsent?: boolean;
308
+ cmpId: number;
309
+ cmpVersion: number;
310
+ /** ISO 3166-1 alpha-2 publisher country code. No honest default exists — leave unset only if
311
+ * you don't rely on this field downstream; ad-tech vendors that read it will see 'AA'
312
+ * (an explicit "not configured" placeholder, not a real country). */
313
+ publisherCC?: string;
314
+ /** Known Global Vendor List version, if you track it. Defaults to 0 ("unknown"). */
315
+ vendorListVersion?: number;
316
+ /** Known GVL specification version, if you track it. Defaults to 0 ("unknown"). */
317
+ gvlVersion?: number;
318
+ }
319
+ /** Mirrors the server-side `GppConfig` shape (`@consenti/api`) — `cmpId`/`cmpVersion` must match
320
+ * what the backend uses to encode `gppString`. Unlike TCF, the GPP US National section needs no
321
+ * Global Vendor List, so both the widget's `window.__gpp` and the server's `gppString` use the
322
+ * same real, spec-correct encoder (the optional `@iabgpp/cmpapi` peer dependency) — there's no
323
+ * "simplified fallback" format the way TCF has, since a non-spec GPP string has no consumer that
324
+ * would accept it. When `@iabgpp/cmpapi` isn't installed, `window.__gpp` is simply not exposed. */
325
+ interface GppWidgetConfig {
326
+ enabled: boolean;
327
+ cmpId: number;
328
+ cmpVersion: number;
329
+ /** MSPA "covered transaction" — whether this deployment's data transactions fall under MSPA
330
+ * signatory obligations. No honest default, so it's required rather than optional. */
331
+ mspaCoveredTransaction: boolean;
332
+ /** IAB's tri-state encoding for both MSPA fields: 0 = not applicable, 1 = yes, 2 = no. */
333
+ mspaOptOutOptionMode: 0 | 1 | 2;
334
+ mspaServiceProviderMode: 0 | 1 | 2;
129
335
  }
130
336
  interface ComplianceWidgetConfig {
131
- type?: 'auto' | ComplianceGroupId | string;
337
+ type?: ComplianceType;
132
338
  geoDataProvider?: 'default' | WidgetCountryResolverFn;
133
- autoComplianceMap?: 'default' | ComplianceMapData | 'auto';
134
- complianceMapUrl?: string;
135
- ageGate?: AgeGateWidgetConfig;
339
+ /** Only meaningful in standalone mode (no `api.enabled`) — ignored (with a warning) when
340
+ * `api.enabled: true`, since the server resolves the compliance group in that mode.
341
+ * `'default'` (embedded map, the default), a URL to fetch a JSON `ComplianceMapData`
342
+ * document from (the browser's own HTTP cache honors whatever `Cache-Control`/`ETag` the
343
+ * response sends — no custom caching here), or an inline operator-supplied `ComplianceMapData`
344
+ * object. Invalid data from either a URL or an object logs a warning and falls back to
345
+ * `'default'`. Only overrides the country→group mapping — country/region detection itself
346
+ * always uses the embedded geo data. */
347
+ complianceMap?: 'default' | string | ComplianceMapData;
348
+ tcf?: TcfWidgetConfig;
349
+ gpp?: GppWidgetConfig;
136
350
  }
137
351
  interface GtmConfig {
138
352
  containerId?: string;
@@ -140,6 +354,7 @@ interface GtmConfig {
140
354
  dataLayer?: string;
141
355
  urlPassthrough?: boolean;
142
356
  adsDataRedaction?: boolean;
357
+ verbose?: boolean;
143
358
  }
144
359
  interface UtilsConfig {
145
360
  gtm?: GtmConfig;
@@ -149,45 +364,81 @@ interface ApiConfig {
149
364
  baseUrl?: string;
150
365
  authToken?: string;
151
366
  tenantId?: string;
152
- complianceGroup?: string;
367
+ complianceGroup?: ComplianceGroupId;
153
368
  trustDomain?: boolean;
154
369
  }
370
+ /** Field names mirror their `--consenti-*` CSS variable literally (`--consenti-color-primary`
371
+ * → `colorPrimary`) — see `applyTheme()` in `consenti-setup.ts` for the full mapping. */
155
372
  interface ThemeConfig {
156
- bgColor?: string;
157
- textColor?: string;
158
- primaryColor?: string;
159
- primaryTextColor?: string;
160
- secondaryColor?: string;
161
- secondaryTextColor?: string;
162
- borderColor?: string;
373
+ colorBg?: string;
374
+ colorText?: string;
375
+ colorTextMuted?: string;
376
+ colorPrimary?: string;
377
+ colorPrimaryText?: string;
378
+ colorSecondary?: string;
379
+ colorSecondaryText?: string;
380
+ colorBorder?: string;
381
+ colorSecondaryBorder?: string;
382
+ colorOverlay?: string;
383
+ colorAccent?: string;
384
+ colorAccentText?: string;
163
385
  fontFamily?: string;
386
+ fontFamilyMono?: string;
164
387
  fontSizeBase?: string;
165
388
  fontSizeHeading?: string;
166
389
  fontSizeMultiplier?: string;
390
+ fontWeightHeading?: string;
391
+ lineHeight?: string;
392
+ spacingXs?: string;
393
+ spacingSm?: string;
394
+ spacingMd?: string;
395
+ spacingLg?: string;
167
396
  borderRadius?: string;
168
- buttonBorderRadius?: string;
397
+ borderRadiusBtn?: string;
398
+ shadow?: string;
169
399
  toggleBgOn?: string;
400
+ toggleBgPartial?: string;
170
401
  toggleBgOff?: string;
171
- accentColor?: string;
172
- accentTextColor?: string;
402
+ toggleKnob?: string;
403
+ toggleWidth?: string;
404
+ toggleHeight?: string;
405
+ zBanner?: string;
406
+ zOverlay?: string;
407
+ zModal?: string;
173
408
  }
174
409
  interface CoreConfig {
175
410
  tenantId?: string;
176
- autoHonorGPC?: boolean | 'strict';
177
411
  locale?: string;
412
+ /** Text direction for the banner/modal root. `'auto'` (default) derives it from `locale` via
413
+ * `Intl.Locale(...).getTextInfo().direction` (falling back to a hand-maintained RTL-language
414
+ * list on engines without `getTextInfo()` support); set explicitly to override. */
415
+ dir?: 'ltr' | 'rtl' | 'auto';
178
416
  disableCssTemplate?: boolean;
179
417
  cookieSigningKey?: string;
180
418
  allowReceipt?: boolean;
181
419
  cookieDomains?: string;
420
+ /** Name of the consent cookie/localStorage key. Default: `'consenti_data'`. Not switched
421
+ * automatically by detected region — set explicitly if you want a different name (e.g.
422
+ * `'euconsent-v2'`, the IAB TCF convention, but only meaningful for operators using the
423
+ * spec-correct binary encoder — `@consenti/api` + the optional `@iabtechlabtcf/core` peer
424
+ * dependency — since the IAB name implies IAB's binary format, not Consenti's own encoding). */
425
+ cookieName?: string;
182
426
  storage?: 'cookie' | 'localStorage';
183
427
  theme?: ThemeConfig;
428
+ /** Initial logged-in application user ID; empty/anonymous when unset. Prefer `getUserId()`/
429
+ * `setUserId()` (or the `consenti:listener:identify` event) to change it after init — those
430
+ * also handle reconsent when the identity changes on a shared device. */
184
431
  userId?: string;
185
432
  usePrebuiltProfiles?: 'all' | NonEmptyArray<ComplianceGroupId>;
186
433
  cacheResolvedProfiles?: boolean;
187
434
  console?: Array<'info' | 'log' | 'warning' | 'error'>;
188
435
  }
189
436
  interface ConsentiConfig {
190
- core: CoreConfig;
437
+ verbose?: boolean;
438
+ /** Optional — every field of `CoreConfig` is itself optional, so `new ConsentiSetup({})` and
439
+ * `new ConsentiSetup({ compliance: { type: 'opt-in' } })` (omitting `core` entirely) both work,
440
+ * matching the widget's own "Minimal config" documentation. */
441
+ core?: CoreConfig;
191
442
  compliance?: ComplianceWidgetConfig;
192
443
  rootEl?: string | HTMLElement;
193
444
  darkMode?: boolean;
@@ -196,13 +447,30 @@ interface ConsentiConfig {
196
447
  utils?: UtilsConfig;
197
448
  plugins?: ConsentiPlugin[];
198
449
  profileOverride?: DeepPartial<ResolvedProfile>;
450
+ /** Per-compliance-group text/button/cookie overrides — applied on top of whichever profile the
451
+ * visitor's resolved group loads, keyed by that group's id (one of the 8 built-in
452
+ * `ComplianceGroupId`s, or any custom group id). Lets you author different copy per country
453
+ * without creating a separate profile (and separate `compliance.type` wiring) for each one —
454
+ * e.g. `{ 'opt-in': { mainBanner: { htmlText: '...' } }, 'my-custom-group': { ... } }`.
455
+ * Applied before `profileOverride`, which still always applies last as the universal catch-all.
456
+ * Works identically in standalone and server (`api.enabled`) mode. */
457
+ complianceGroupsOverride?: Partial<Record<ComplianceGroupId | (string & {}), DeepPartial<ResolvedProfile>>>;
199
458
  /** When true, suppresses the "Powered by Consenti" footer link in the banner and modal. */
200
459
  hidePoweredBy?: boolean;
201
460
  }
202
- type ConsentiEventName = 'bannerInitialized' | 'consenti:bannerInitialized' | 'bannerVisibility' | 'consenti:bannerVisibility' | 'modalVisibility' | 'consenti:modalVisibility' | 'consentBeingSubmitted' | 'consenti:consentBeingSubmitted' | 'consentSubmitted' | 'consenti:consentSubmitted';
461
+ type ConsentiEventName = 'bannerInitialized' | 'consenti:bannerInitialized' | 'bannerVisibility' | 'consenti:bannerVisibility' | 'modalVisibility' | 'consenti:modalVisibility' | 'consentBeingSubmitted' | 'consenti:consentBeingSubmitted' | 'consentSubmitted' | 'consenti:consentSubmitted' | 'parentalConsentRequired' | 'consenti:parentalConsentRequired' | 'forgetMeRequested' | 'consenti:forgetMeRequested' | 'forgotten' | 'consenti:forgotten';
462
+ /** Snapshot of the current visitor's identity. `visitorId` is `null` until a consent decision
463
+ * has actually happened — it's never minted just to answer this call, see `getVisitor()`. */
464
+ interface VisitorIdentity {
465
+ visitorId: string | null;
466
+ type: 'authenticated' | 'anonymous';
467
+ userId: string | null;
468
+ }
203
469
  interface ConsentiWidgetAPI {
204
470
  hasConsent(): boolean;
205
471
  getConsent(): ConsentValue | null;
472
+ getConsent(type: ConsentType): Record<string, string> | null;
473
+ getConsent(type?: ConsentType): ConsentValue | Record<string, string> | null;
206
474
  getConsentDate(): Date | false;
207
475
  getGTMConsent(): Record<string, string> | null;
208
476
  isCookieGranted(cookieId: string, requestValue?: boolean): boolean | ConsentStatus;
@@ -215,8 +483,8 @@ interface ConsentiWidgetAPI {
215
483
  off(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
216
484
  version(): {
217
485
  package: string;
218
- profileVersion: number | null;
219
- consentVersion: number;
486
+ profileVersion: string | null;
487
+ consentVersion: string | null;
220
488
  };
221
489
  bannerVisibility(): 'main' | 'gpc' | false;
222
490
  modalVisibility(): 'preference' | false;
@@ -225,13 +493,28 @@ interface ConsentiWidgetAPI {
225
493
  hideBanner(): void;
226
494
  showModal(triggerEl?: HTMLElement): void;
227
495
  hideModal(): void;
228
- submitConsent(consent: Partial<ConsentValue>): Promise<void>;
496
+ submitConsent(consent: Partial<ConsentValue>): Promise<ConsentDbRecord | void>;
229
497
  deleteConsent(): Promise<void>;
230
498
  reConsent(): Promise<void>;
499
+ /** Erases the stored consent record (same underlying call as `deleteConsent()`) and
500
+ * re-prompts, dispatching `consenti:forgetMeRequested` / `consenti:forgotten` around it so a
501
+ * host app can hook its own identity-verified erasure workflow. Powers the preference modal's
502
+ * "Forget me" button; also callable directly for a custom placement of the same action. */
503
+ forgetMe(resetAgeGate?: boolean): Promise<void>;
231
504
  getRootElement(): HTMLElement | null;
232
505
  getBannerElement(): HTMLElement | null;
233
506
  getModalElement(): HTMLElement | null;
234
507
  switchLocale(locale: string): void;
508
+ getUserId(): string | null;
509
+ /** Sets the logged-in application user ID (`null` for anonymous/logout). When the stored
510
+ * consent record's user differs from the new value, reconsents by default — pass
511
+ * `reConsent: false` to just update the identity without prompting again. No-ops (no
512
+ * reconsent, no warning) when there's no prior consent record to compare against. */
513
+ setUserId(userId: string | null, reConsent?: boolean): Promise<void>;
514
+ /** Snapshot of the current visitor's identity: the stable per-browser `visitorId` (`null` if
515
+ * no consent decision has happened yet — this never creates one), whether they're
516
+ * `authenticated` or `anonymous`, and the app `userId` (same as `getUserId()`). */
517
+ getVisitor(): VisitorIdentity;
235
518
  setDarkMode(enable?: boolean): void;
236
519
  setTheme(theme: Partial<ThemeConfig>): void;
237
520
  setConfig(config: DeepPartial<ConsentiConfig>): void;
@@ -249,19 +532,75 @@ declare abstract class ConsentiPlugin {
249
532
  onModalShow?(): void | Promise<void>;
250
533
  onModalHide?(): void | Promise<void>;
251
534
  }
252
- interface ConsentEvent {
253
- profileId?: number;
254
- consent?: ConsentValue;
255
- hasExistingConsent?: boolean;
256
- show?: boolean;
257
- action?: boolean;
258
- consentAction?: ConsentAction;
259
- consentId?: string;
260
- pageUrl?: string;
261
- apiResponse?: unknown;
535
+ interface BannerInitializedDetail {
536
+ profileId: string;
537
+ complianceGroup?: ComplianceType;
538
+ hasExistingConsent: boolean;
539
+ gpcDetected: boolean;
540
+ willShow: boolean;
541
+ }
542
+ interface BannerVisibilityDetail {
543
+ visible: boolean;
544
+ variant: 'main' | 'gpc';
545
+ action: boolean;
546
+ }
547
+ interface ModalVisibilityDetail {
548
+ visible: boolean;
549
+ action: boolean;
550
+ }
551
+ interface ConsentBeingSubmitted {
552
+ consentId: string;
553
+ visitorId: string;
554
+ profileId: string;
555
+ consentJson: ConsentValue;
556
+ consentAction: ConsentAction;
557
+ gpcDetected: boolean;
558
+ pageUrl: string;
559
+ timestamp: number;
262
560
  fromBroadcast?: boolean;
263
- gpcDetected?: boolean;
264
561
  }
562
+ interface ConsentSubmittedDetail extends ConsentBeingSubmitted {
563
+ apiResponse: ConsentDbRecord;
564
+ }
565
+ /** Dispatched when the age gate is declined and `requireParentalConsent` is set — see the
566
+ * profile's `AgeGateConfig`. A deny-all consent (mandatory cookies only) has already been
567
+ * submitted with `parentalConsentToken` attached; this event is the hook for the site
568
+ * owner to run their own out-of-band parental-consent verification. `parentalConsentToken` is
569
+ * server-issued (and signed, when `compliance.dataSigningHash` is configured) when `api.enabled`,
570
+ * otherwise a client-generated, unsigned fallback — see `POST /consent/:visitorId/parental-consent-request`. */
571
+ interface ParentalConsentRequiredDetail {
572
+ parentalConsentToken: string;
573
+ profileId: string;
574
+ visitorId: string;
575
+ timestamp: number;
576
+ }
577
+ /** Dispatched by the standalone `resolveParentalConsent()` util (`apps/ui/src/utils/parental-consent.ts`)
578
+ * after `POST /consent/parental-consent-resolve` succeeds — typically from a fresh page/session
579
+ * with no live `ConsentiSetup` instance (the parent's own device, not the child's). A page that
580
+ * does have a live widget instance can listen for this to react (e.g. re-show the banner/modal for
581
+ * the real consent choice); what "resolved" should actually do to consent state is host-defined. */
582
+ interface ParentalConsentGrantedDetail {
583
+ visitorId: string;
584
+ profileId: string;
585
+ }
586
+ /** Dispatched by `forgetMe()` right before the erasure call goes out — the hook for a host app
587
+ * to kick off its own identity-verified erasure workflow across other systems (CRM, DMP,
588
+ * analytics). The CMP itself only ever erases its own consent record; see the server-side
589
+ * `consent.erased` eventBus event (fired by `DELETE /consent/:visitorId`) for the same hook on
590
+ * the backend. See the "Right to erasure" guide for the full picture. */
591
+ interface ForgetMeRequestedDetail {
592
+ visitorId: string;
593
+ profileId: string;
594
+ timestamp: number;
595
+ }
596
+ /** Dispatched by `forgetMe()` after the consent record has been erased and the banner/age-gate
597
+ * has been re-prompted (see `reConsent()`, which `forgetMe()` wraps). */
598
+ interface ForgottenDetail {
599
+ visitorId: string;
600
+ profileId: string;
601
+ timestamp: number;
602
+ }
603
+ type ConsentEvent = BannerInitializedDetail | BannerVisibilityDetail | ModalVisibilityDetail | ConsentBeingSubmitted | ConsentSubmittedDetail | ParentalConsentRequiredDetail | ParentalConsentGrantedDetail | ForgetMeRequestedDetail | ForgottenDetail;
265
604
 
266
605
  /**
267
606
  * Main entry point for the Consenti widget.
@@ -281,16 +620,30 @@ interface ConsentEvent {
281
620
  */
282
621
 
283
622
  declare class ConsentiSetup implements ConsentiWidgetAPI {
284
- private config;
285
623
  private initialized;
286
624
  private initializing;
287
625
  private profile;
288
626
  private resolvedBase;
627
+ /** cookieId → Category lookup, rebuilt whenever `profile` changes. Avoids rescanning categories on every consent action. */
628
+ private categoryIndex;
629
+ /** Watchers created by the declarative `data-consenti-*` DOM scan — destroyed alongside the widget since they have no other owner. */
630
+ private scannedScripts;
289
631
  private consentStore;
632
+ /**
633
+ * Stable per-browser identifier. Created lazily the first time a consent decision actually
634
+ * happens (manual submit, GPC auto-response, or relaying a decision already made in another
635
+ * tab) — never minted before that, so an undecided visitor leaves nothing in storage. See
636
+ * `getOrCreateVisitorId()`.
637
+ */
638
+ private visitorId;
290
639
  private eventBus;
291
640
  private banner;
292
641
  private modal;
642
+ private ageGate;
293
643
  private renderer;
644
+ /** Set once the age gate (if enabled) resolves; threaded onto every `submitConsent` call thereafter. */
645
+ private _ageVerified;
646
+ private _parentalConsentToken;
294
647
  private broadcastChannel;
295
648
  private readyResolve;
296
649
  private readyPromise;
@@ -299,26 +652,118 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
299
652
  private lastSignature;
300
653
  /** Tracks which banner variant is currently visible, or null when hidden. */
301
654
  private _bannerVariant;
655
+ /** Tracks which banner variant is currently mounted in the DOM (persists when banner is hidden). */
656
+ private _bannerMounted;
657
+ /** Active focus trap keydown listener — removed when modal closes. */
658
+ private _focusTrapListener;
659
+ /** Inbound `consenti:listener:identify` window listener — installed once per init(), removed on destroy(). */
660
+ private _identifyListener;
302
661
  /** Maps original handler functions to their DOM-wrapped listeners for on/off bookkeeping. */
303
662
  private _eventHandlers;
663
+ /** `core` defaulted to `{}` in the constructor below — every one of its own fields is
664
+ * optional, so this narrows back to non-optional for every other method in this class. */
665
+ private config;
304
666
  /**
305
667
  * Creates and initialises the consent widget.
306
668
  *
307
669
  * Construction is synchronous; the async `init()` is launched in the background.
308
670
  * Use `await widget.ready` or `widget.onReady(cb)` to run code after init completes.
309
671
  *
310
- * @param config - Widget configuration. See `ConsentiConfig` for all options.
672
+ * @param config - Widget configuration. See `ConsentiConfig` for all options. `core` may be
673
+ * omitted entirely — every one of its own fields is optional too.
311
674
  */
312
675
  constructor(config: ConsentiConfig);
313
676
  private profileId;
677
+ /** Patches `base` with `complianceGroupsOverride[base.complianceGroup]`, if both exist — the
678
+ * per-group counterpart to `profileOverride`. Lets an author supply different text/buttons/
679
+ * cookies per compliance group (built-in or custom) without authoring a separate profile for
680
+ * each one. Applied before `profileOverride`, which still always wins as the final catch-all. */
681
+ private applyComplianceGroupOverride;
682
+ /**
683
+ * Whether `window.__tcfapi`/`window.__gpp` should stay uninstalled because the corresponding
684
+ * server-side registration (`compliance.tcf`/`compliance.gpp`) is unconfirmed (cmpId/cmpVersion
685
+ * changed since the dashboard's confirmation checkbox was last checked, or never confirmed).
686
+ * Standalone (no-backend) deployments have no governance layer to check against, so this always
687
+ * resolves `false` there — the client-side config is used as configured, same as before this
688
+ * feature existed. A network failure fails closed (`true`) rather than assuming the
689
+ * unverifiable state is fine.
690
+ */
691
+ private isRegistrationBlocked;
314
692
  /**
315
693
  * Starts widget initialisation. Called automatically unless `autoInit: false` is set.
316
694
  * Can be called explicitly after construction when `autoInit: false`, or after `destroy()`
317
695
  * to re-initialise the same instance.
318
696
  */
319
697
  init(): Promise<void>;
698
+ /**
699
+ * Everything after the "does consent already exist" check — GPC/CCPA/CPRA silent paths,
700
+ * then the normal opt-in banner. Extracted from `init()` so the age gate (when enabled and
701
+ * no consent exists yet) can defer this whole sequence until the visitor answers it via
702
+ * `resolveAgeGate()`, then resume here with `hasExisting` fixed at `false`.
703
+ */
704
+ private continueInit;
705
+ /** Mounts the age-gate prompt (`profile.ageGate`) in place of the normal banner flow. */
706
+ private showAgeGate;
707
+ /**
708
+ * Handles the visitor's age-gate answer.
709
+ * - Confirmed → records `ageVerified: true` for all future `submitConsent` calls and
710
+ * resumes the normal init flow via `continueInit()`.
711
+ * - Declined, no parental consent required → immediately submits a deny-all consent
712
+ * (mandatory cookies still granted), mirroring the GPC-strict silent-write pattern.
713
+ * - Declined, parental consent required → same deny-all submission, plus a
714
+ * `parentalConsentToken` and a `consenti:parentalConsentRequired` event so the site
715
+ * owner can wire their own out-of-band verification flow (no email infra exists in
716
+ * this package to do that here).
717
+ */
718
+ private resolveAgeGate;
719
+ /**
720
+ * Marks the widget ready and runs the declarative DOM-scan for
721
+ * `data-consenti-consent-script`/`data-consenti-category-script` tags. Called from
722
+ * every exit path of `init()` so the scan always runs exactly once per init cycle,
723
+ * whether `init()` was triggered automatically (`autoInit !== false`) or explicitly
724
+ * by the caller (`autoInit: false`) — it never runs before the widget actually has
725
+ * a resolved profile to read consent from.
726
+ */
727
+ private finishInit;
728
+ /** Installs the inbound `consenti:listener:identify` window listener — the event-driven
729
+ * counterpart to `setUserId()`. Removes any previously-installed listener first so repeated
730
+ * `init()` calls (e.g. from `switchLocale()`) don't stack up duplicate listeners. */
731
+ private _installIdentifyListener;
732
+ /** Returns the current logged-in application user ID, or `null` for an anonymous visitor. */
733
+ getUserId(): string | null;
734
+ /**
735
+ * Sets the logged-in application user ID (`null` for anonymous/logout). If a stored consent
736
+ * record exists and its user differs from `userId`, this is treated as a different data
737
+ * subject on a shared device: by default it reconsents (deletes the existing record and
738
+ * re-shows the banner) and logs a warning. Pass `reConsent: false` to just update the identity
739
+ * without prompting again (still logs the warning). No-ops entirely — no warning, no
740
+ * reconsent — when there is no prior consent record to compare against (true first-time visitor).
741
+ */
742
+ setUserId(userId: string | null, reConsent?: boolean): Promise<void>;
743
+ /**
744
+ * Snapshot of the current visitor's identity. `visitorId` is the stable per-browser ID —
745
+ * `null` until a consent decision has actually happened, since this never mints one just to
746
+ * answer the call (see `getOrCreateVisitorId()`). `type` is `'authenticated'` whenever a
747
+ * `userId` is set (via `setUserId()` or `core.userId`), `'anonymous'` otherwise.
748
+ */
749
+ getVisitor(): VisitorIdentity;
750
+ /** Sets the `dir` attribute on the root element from `core.dir` (default `'auto'`, derived from `core.locale`). */
751
+ private applyDirection;
320
752
  /** Applies the `consenti-root--dark` class based on config or profile flag. */
321
753
  private applyDarkMode;
754
+ /** Applies `consenti--enhanced-a11y` class when `profile.enhanceAccessibility` is set. */
755
+ private applyAccessibility;
756
+ /** Builds and appends the metadata footer strip to modal container. */
757
+ private _appendMetaFooter;
758
+ /**
759
+ * Injects a `<style>` element into the root for `stackButtonsOnBreakpoint`.
760
+ * Called once per profile load; a new style replaces the previous one.
761
+ */
762
+ private applyStackButtonsBreakpoint;
763
+ /** Installs a keyboard focus trap on `container` while the modal is open. */
764
+ private _installFocusTrap;
765
+ /** Removes the active focus trap keyboard listener. */
766
+ private _removeFocusTrap;
322
767
  /**
323
768
  * Injects CSS custom property overrides supplied via `core.theme` into the
324
769
  * Consenti root element so they cascade down to all widget elements.
@@ -348,22 +793,31 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
348
793
  */
349
794
  hasConsent(): boolean;
350
795
  /**
351
- * Returns the stored consent map, or `null` if no consent has been saved.
352
- * Always returns `null` during SSR.
796
+ * Returns the stored consent map, optionally transformed for a specific vendor.
797
+ * Pass `type` to get a vendor-ready object instead of the raw Consenti format.
798
+ * Always returns `null` during SSR or when no consent has been saved.
799
+ *
800
+ * @param type - Output format:
801
+ * - omit / `'default'` → raw ConsentValue, keyed by every cookie parameter ID in the profile
802
+ * - `'purpose'` → consent per the fixed CookiePurpose taxonomy (necessary/functional/preferences/analytics/marketing)
803
+ * - `'category'` → consent per the tenant's own authored preference-modal category, keyed by category ID
804
+ * ('granted' only when every member parameter is granted; 'denied'/'objected' otherwise)
805
+ * - `'google-gtm'` → Google Consent Mode v2 keys + ads_data_redaction, url_passthrough
806
+ * - `'adobe'` → { analytics, target, manager, optimizer }
807
+ * - `'meta'` → { pixel, api, plugins, facebookLogin }
808
+ * - `'microsoft-clarity'` → { session, heatmaps, performance }
809
+ * - `'twilio-segment'` → { identify, page, track, group, alias }
353
810
  */
354
811
  getConsent(): ConsentValue | null;
812
+ getConsent(type: ConsentType): Record<string, string> | null;
355
813
  /**
356
814
  * Returns the `Date` when consent was last saved, or `false` if no consent exists.
357
815
  * Always returns `false` during SSR.
358
816
  */
359
817
  getConsentDate(): Date | false;
360
818
  /**
361
- * Returns the consent map in Google Consent Mode v2 format, ready to pass to
362
- * `gtag('consent', 'update', ...)`. Returns `null` if no consent has been saved.
363
- *
364
- * Maps Consenti cookie IDs to the seven Google Consent Mode v2 parameters.
365
- * Cookies whose IDs exactly match a GCM v2 key are forwarded directly.
366
- * Non-GCM cookies are ignored by this method (they appear in `getConsent()` only).
819
+ * @deprecated Use `getConsent('google-gtm')` instead.
820
+ * Returns the consent map in Google Consent Mode v2 format.
367
821
  */
368
822
  getGTMConsent(): Record<string, string> | null;
369
823
  /**
@@ -375,7 +829,7 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
375
829
  * @param consent - Partial or full map of cookie ID → status.
376
830
  * @param gpcDerived - Internal flag set to `true` when called from the GPC strict path.
377
831
  */
378
- submitConsent(consent: Partial<ConsentValue>, gpcDerived?: boolean): Promise<void>;
832
+ submitConsent(consent: Partial<ConsentValue>, gpcDerived?: boolean, source?: number): Promise<ConsentDbRecord | void>;
379
833
  /**
380
834
  * Returns the currently visible banner variant, or `false` if no banner is shown.
381
835
  * Useful for programmatically checking state before calling `showBanner` / `hideBanner`.
@@ -444,8 +898,30 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
444
898
  * method so callers do not need to coordinate the async deletion with the banner display.
445
899
  *
446
900
  * Use this to power "Review my consent" links in footers or privacy dashboards.
901
+ *
902
+ * @param resetAgeGate - When `true` (default) and the profile has `ageGate.enabled`, the
903
+ * age-gate prompt is shown again instead of jumping straight to the
904
+ * banner — mirroring the original first-visit flow. Pass `false` to
905
+ * skip it and go straight to the banner, keeping the visitor's prior
906
+ * age-gate answer.
447
907
  */
448
- reConsent(): Promise<void>;
908
+ reConsent(resetAgeGate?: boolean): Promise<void>;
909
+ /**
910
+ * Self-service erasure entry point — GDPR Art. 17 / CCPA-CPRA / LGPD Art. 18 and equivalent
911
+ * "right to be forgotten" rights, exercised from the widget itself. Wraps `reConsent()` (same
912
+ * erase-then-reprompt behavior) with `consenti:forgetMeRequested` / `consenti:forgotten`
913
+ * dispatched around it, so a host app can hook its own identity-verified erasure workflow
914
+ * across other systems (CRM, DMP, analytics) — the CMP itself only ever erases its own consent
915
+ * record. See the "Right to erasure" guide for the full picture, including the server-side
916
+ * `consent:erased` eventBus event fired by `DELETE /consent/:visitorId`.
917
+ *
918
+ * Powers the preference modal's "Forget me" button (`preferenceModal.showForgetMe`, only
919
+ * rendered when the visitor already has a stored consent decision). Also callable directly —
920
+ * e.g. `widget.forgetMe()` from a custom footer link or privacy-settings page.
921
+ *
922
+ * @param resetAgeGate - Forwarded to `reConsent()` — see its docs.
923
+ */
924
+ forgetMe(resetAgeGate?: boolean): Promise<void>;
449
925
  /**
450
926
  * Returns the fully resolved profile for the current session, or `null` during the
451
927
  * async init phase (before the profile has been fetched/resolved).
@@ -544,8 +1020,8 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
544
1020
  */
545
1021
  version(): {
546
1022
  package: string;
547
- profileVersion: number | null;
548
- consentVersion: number;
1023
+ profileVersion: string;
1024
+ consentVersion: string | null;
549
1025
  };
550
1026
  /**
551
1027
  * Toggles or sets dark mode at runtime without re-initialising the widget.
@@ -623,15 +1099,22 @@ declare function useConsent(): {
623
1099
  /** Denies all non-mandatory cookies. Pass `true` to deny mandatory ones too (use with care). */
624
1100
  denyAll: (includingMandatory?: boolean) => Promise<void>;
625
1101
  /** Saves the given partial consent map. */
626
- submitConsent: (c: Partial<ConsentValue>) => Promise<void>;
627
- /** Deletes the current consent record and re-shows the banner. */
628
- reConsent: () => Promise<void>;
1102
+ submitConsent: (c: Partial<ConsentValue>) => Promise<void | ConsentDbRecord>;
1103
+ /** Deletes the current consent record and re-shows the banner. Pass `false` to skip
1104
+ * re-showing the age-gate prompt (if the profile has one) and go straight to the banner. */
1105
+ reConsent: (resetAgeGate?: boolean) => Promise<void>;
629
1106
  /** Returns `true` if the given cookie ID is `'granted'`. */
630
1107
  isCookieGranted: (id: string) => boolean;
631
1108
  /** Returns `true` if every cookie in the given category is `'granted'`. */
632
1109
  isCategoryGranted: (id: string) => boolean;
633
1110
  /** Switches the active locale and re-renders the banner and modal. */
634
1111
  switchLocale: (locale: string) => void | undefined;
1112
+ /** Returns the current logged-in application user ID, or `null` if anonymous. */
1113
+ getUserId: () => string | null;
1114
+ /** Sets the logged-in application user ID; reconsents by default when it changes. */
1115
+ setUserId: (userId: string | null, reConsent?: boolean) => Promise<void>;
1116
+ /** Snapshot of the current visitor's identity (`visitorId`, `type`, `userId`). */
1117
+ getVisitor: () => VisitorIdentity;
635
1118
  };
636
1119
 
637
1120
  export { setConsentiWidget, useConsent };