@consenti/api 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.
- package/README.md +578 -143
- package/dist/chunk-COR72NFQ.js +2 -0
- package/dist/chunk-COR72NFQ.js.map +1 -0
- package/dist/chunk-S2IEYESR.js +2 -0
- package/dist/chunk-S2IEYESR.js.map +1 -0
- package/dist/dashboard/assets/ApiConfig-CZMtYRfK.js +1 -0
- package/dist/dashboard/assets/ArchivedProfileList-BlQ2gEWz.js +1 -0
- package/dist/dashboard/assets/AuditLog-BpZoAFOe.js +1 -0
- package/dist/dashboard/assets/ChangePassword-BZkP2dXQ.js +1 -0
- package/dist/dashboard/assets/ConfirmDialog-BDlnfTdx.js +1 -0
- package/dist/dashboard/assets/ConsentList-DgJMrhih.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateEditor-DA7B5aI_.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateList-DMPd2y5s.js +1 -0
- package/dist/dashboard/assets/HeadingTagInput-D2S9xDMJ.js +1 -0
- package/dist/dashboard/assets/HowItWorks-CJCrdr3o.js +1 -0
- package/dist/dashboard/assets/PermissionGate-C69dhwV1.js +1 -0
- package/dist/dashboard/assets/PreviewPane-D7mDeBT8.js +38 -0
- package/dist/dashboard/assets/ProfileEditor-CSFdOweN.js +2 -0
- package/dist/dashboard/assets/ProfileList-C4cIDrM6.js +1 -0
- package/dist/dashboard/assets/ProfileVersionHistory-CX5-Oupy.js +3 -0
- package/dist/dashboard/assets/RecordDetailModal-DIpGxfqA.js +1 -0
- package/dist/dashboard/assets/Reports-B1Ww0ovT.js +1 -0
- package/dist/dashboard/assets/RoleList-CQ1GjR0W.js +1 -0
- package/dist/dashboard/assets/Select-C_JusbT5.js +1 -0
- package/dist/dashboard/assets/Settings-sIMVWQ0V.js +1 -0
- package/dist/dashboard/assets/SetupWizard-ChFBxqo3.js +1 -0
- package/dist/dashboard/assets/Table-BUxoz0YR.js +1 -0
- package/dist/dashboard/assets/TenantList-DCsT7ghb.js +1 -0
- package/dist/dashboard/assets/UITemplateEditor-BAaMnuOU.js +2 -0
- package/dist/dashboard/assets/UITemplateList-C3UMbnfs.js +1 -0
- package/dist/dashboard/assets/UserList-CHGNnS50.js +1 -0
- package/dist/dashboard/assets/VendorList-C-sswM1A.js +1 -0
- package/dist/dashboard/assets/VisitorList-DNB9Ia72.js +1 -0
- package/dist/dashboard/assets/check-D0g4XvmN.js +1 -0
- package/dist/dashboard/assets/client-CYntrO2g.js +1 -0
- package/dist/dashboard/assets/copy-DEi_W2TM.js +1 -0
- package/dist/dashboard/assets/createLucideIcon-DAiYBrnd.js +1 -0
- package/dist/dashboard/assets/de-jwngydrC.js +25 -0
- package/dist/dashboard/assets/debounce-DlvAAT2V.js +1 -0
- package/dist/dashboard/assets/dist-GoiMJCTt.js +1 -0
- package/dist/dashboard/assets/es-B6Jp0OPe.js +25 -0
- package/dist/dashboard/assets/external-link-CFZxxe3W.js +1 -0
- package/dist/dashboard/assets/fr-CshYcQPW.js +25 -0
- package/dist/dashboard/assets/index-Cr5M1Qy_.css +2 -0
- package/dist/dashboard/assets/index-Tr_BcjkN.js +26 -0
- package/dist/dashboard/assets/ja-CgjZNBrc.js +25 -0
- package/dist/dashboard/assets/profiles-C_SuoLJD.js +1 -0
- package/dist/dashboard/assets/roles-D7L936FV.js +1 -0
- package/dist/dashboard/assets/rotate-ccw-clock-6y2NDrJ7.js +1 -0
- package/dist/dashboard/assets/search-DKwfLBOv.js +1 -0
- package/dist/dashboard/assets/templates-CIyMjskA.js +1 -0
- package/dist/dashboard/assets/templates-DY68xNys.js +1 -0
- package/dist/dashboard/assets/trash-2-eX9s6x8V.js +1 -0
- package/dist/dashboard/assets/trash-O30ycMcz.js +1 -0
- package/dist/dashboard/assets/useHashQuery-BDjUPXDR.js +1 -0
- package/dist/dashboard/assets/users-BRrNOK0W.js +1 -0
- package/dist/dashboard/assets/x-BhZiH8AL.js +1 -0
- package/dist/dashboard/assets/zh-CN-BnLZkfBC.js +25 -0
- package/dist/dashboard/index.html +4 -2
- package/dist/dist-QJYW5KUK.js +2 -0
- package/dist/dist-QJYW5KUK.js.map +1 -0
- package/dist/index.cjs +90 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +847 -150
- package/dist/index.d.ts +847 -150
- package/dist/index.js +90 -101
- package/dist/index.js.map +1 -1
- package/dist/profiles-FCX454HS.js +2 -0
- package/dist/profiles-FCX454HS.js.map +1 -0
- package/dist/widget/widget.css +1 -1
- package/dist/widget/widget.js +1 -1
- package/package.json +32 -17
- package/dist/dashboard/assets/de-DwE2aQfW.js +0 -5
- package/dist/dashboard/assets/es-Dq88TWkB.js +0 -5
- package/dist/dashboard/assets/fr-o_Pyg8eR.js +0 -5
- package/dist/dashboard/assets/index-BSsYDdb9.js +0 -40
- package/dist/dashboard/assets/index-bOTGevzl.css +0 -2
- package/dist/dashboard/assets/ja-CwgXYmpD.js +0 -5
- package/dist/dashboard/assets/zh-CN-ety9KoQ0.js +0 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
3
|
|
|
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
|
+
*/
|
|
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 ? {
|
|
13
|
+
[Key in keyof T]?: DeepPartial<T[Key]> | null;
|
|
14
|
+
} : T;
|
|
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
|
+
*/
|
|
4
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"];
|
|
5
44
|
|
|
6
45
|
/**
|
|
7
46
|
* Compliance-related literal types, derived from the regulation/group ID data
|
|
@@ -18,6 +57,20 @@ declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-stri
|
|
|
18
57
|
|
|
19
58
|
type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
|
|
20
59
|
|
|
60
|
+
/** Resolved content for one non-default locale — template shape merged with that locale's
|
|
61
|
+
* author text, computed client-side (dashboard) the same way the default locale's content on
|
|
62
|
+
* `StoredProfileJson` is. Never persisted in the DB row; `ProfileService` writes it straight to
|
|
63
|
+
* that locale's on-disk version file. Keyed by locale code on `CreateProfileInput`/
|
|
64
|
+
* `UpdateProfileInput.localeContent` — only locales actually touched this save need an entry;
|
|
65
|
+
* any other locale already in `profileJson.locales` is carried forward unchanged from the
|
|
66
|
+
* previous version. */
|
|
67
|
+
interface LocaleContentInput {
|
|
68
|
+
mainBanner: MainBanner;
|
|
69
|
+
gpcBanner?: GpcBanner;
|
|
70
|
+
preferenceModal: PreferenceModal;
|
|
71
|
+
/** Only meaningful when the profile's `ageGate.enabled` is true. */
|
|
72
|
+
ageGateModal?: AgeGateModalContent;
|
|
73
|
+
}
|
|
21
74
|
interface StorageConfig {
|
|
22
75
|
driver: 'sqlite' | 'better-sqlite3' | 'node-sqlite3-wasm' | 'node:sqlite' | 'mongodb' | 'mysql' | 'postgresql' | 'json';
|
|
23
76
|
path?: string;
|
|
@@ -27,6 +80,20 @@ interface StorageConfig {
|
|
|
27
80
|
port?: number;
|
|
28
81
|
user?: string;
|
|
29
82
|
password?: string;
|
|
83
|
+
/** Postgres/MySQL only. Max pool connections. Default: 10 (both drivers' own default — set
|
|
84
|
+
* explicitly here so it's a documented, tunable value rather than an implicit library default). */
|
|
85
|
+
poolMax?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Postgres/MySQL only. Milliseconds before an in-flight query is aborted, so one slow/runaway
|
|
88
|
+
* query can't hold a pool connection indefinitely. Unset by default: a forced default could
|
|
89
|
+
* silently break legitimate long-running operations (e.g. exporting millions of records) that
|
|
90
|
+
* this project has no way to load-test — opt in once you've measured your own worst-case query.
|
|
91
|
+
*/
|
|
92
|
+
statementTimeoutMs?: number;
|
|
93
|
+
/** Postgres only. Milliseconds a client may sit idle inside an open transaction before being
|
|
94
|
+
* terminated — protects against a `BEGIN` with no matching `COMMIT`/`ROLLBACK` holding a
|
|
95
|
+
* connection forever. Unset by default, same reasoning as `statementTimeoutMs`. */
|
|
96
|
+
idleInTransactionTimeoutMs?: number;
|
|
30
97
|
}
|
|
31
98
|
interface OidcConfig {
|
|
32
99
|
issuer: string;
|
|
@@ -46,7 +113,7 @@ interface SamlConfig {
|
|
|
46
113
|
}
|
|
47
114
|
interface AuthConfig {
|
|
48
115
|
mode: 'local' | 'jwt' | 'custom' | 'oidc' | 'saml';
|
|
49
|
-
|
|
116
|
+
masterSecret?: string;
|
|
50
117
|
validateUser?: (req: unknown) => Promise<AdminUser | null>;
|
|
51
118
|
adminEmail?: string;
|
|
52
119
|
adminPassword?: string;
|
|
@@ -62,6 +129,14 @@ type GeoResult = {
|
|
|
62
129
|
country: string | null;
|
|
63
130
|
region: string | null;
|
|
64
131
|
locale: string | null;
|
|
132
|
+
/** Optional escape hatch: when set, `GeoResolverService` uses this compliance group directly
|
|
133
|
+
* instead of computing one from `country`/`region` against the jurisdiction map — for a
|
|
134
|
+
* provider that already carries legal-grade jurisdiction data (e.g. a paid geo-IP service) and
|
|
135
|
+
* needs to route a visitor into an operator-defined custom group the built-in map can't express.
|
|
136
|
+
* `country`/`region`/`locale` are still used for locale resolution and the `requiresSensitiveOptIn`
|
|
137
|
+
* region carve-out, which are independent of which group applies. Optional — omitting it (or
|
|
138
|
+
* returning `undefined`) preserves the existing country/region-map resolution exactly as before. */
|
|
139
|
+
complianceGroup?: string;
|
|
65
140
|
};
|
|
66
141
|
type CountryResolverFn = (ctx: {
|
|
67
142
|
ip: string;
|
|
@@ -81,18 +156,33 @@ interface ProfileSummary {
|
|
|
81
156
|
name: string;
|
|
82
157
|
defaultLocale: string;
|
|
83
158
|
complianceGroup: ComplianceGroupId | null;
|
|
84
|
-
|
|
159
|
+
customComplianceGroup: string | null;
|
|
85
160
|
isActive: boolean;
|
|
86
|
-
|
|
161
|
+
consentTemplateName: string | null;
|
|
87
162
|
uiTemplateName: string | null;
|
|
88
163
|
createdAt: string;
|
|
89
164
|
updatedAt: string;
|
|
90
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* One snapshot in a profile's edit history. `Profile.id` is stable across edits; each save
|
|
168
|
+
* increments `Profile.version` in place and writes a new snapshot under that version number.
|
|
169
|
+
*/
|
|
91
170
|
interface ProfileVersionEntry {
|
|
92
171
|
version: number;
|
|
93
172
|
createdAt: string;
|
|
94
173
|
locales: string[];
|
|
95
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* A profile-id directory found on disk with no matching DB row — the profile was deleted
|
|
177
|
+
* (or predates the current versioning model) but its version-snapshot history wasn't. Built
|
|
178
|
+
* from a directory listing only (id, version count, last-modified) — no file content is read
|
|
179
|
+
* until a specific version is opened via the normal version-history endpoints.
|
|
180
|
+
*/
|
|
181
|
+
interface ArchivedProfileSummary {
|
|
182
|
+
id: string;
|
|
183
|
+
versionCount: number;
|
|
184
|
+
lastModified: string;
|
|
185
|
+
}
|
|
96
186
|
interface OptInFilters {
|
|
97
187
|
profileId?: string;
|
|
98
188
|
complianceGroup?: string;
|
|
@@ -124,31 +214,49 @@ interface OptInStats {
|
|
|
124
214
|
byLocale: Record<string, OptInByLocale>;
|
|
125
215
|
byDate: OptInByDate[];
|
|
126
216
|
}
|
|
127
|
-
type ComplianceMapOverride = 'default' | ComplianceMapData | 'auto';
|
|
128
217
|
interface ComplianceMapData {
|
|
129
218
|
version: string;
|
|
130
219
|
countries: Record<string, CountryComplianceEntry>;
|
|
131
220
|
}
|
|
132
221
|
interface CountryComplianceEntry {
|
|
133
222
|
complianceGroup: ComplianceGroupId;
|
|
134
|
-
|
|
135
|
-
|
|
223
|
+
/** Fallback group used when only the country (not the region) is known. Defaults to
|
|
224
|
+
* `complianceGroup` when omitted — only set this explicitly when a region within this
|
|
225
|
+
* country can be stricter than the country-wide baseline (see `overriddenRegions`). */
|
|
226
|
+
default?: ComplianceGroupId;
|
|
227
|
+
description?: string;
|
|
136
228
|
overriddenRegions?: Record<string, {
|
|
137
229
|
complianceGroup: ComplianceGroupId;
|
|
138
|
-
description
|
|
230
|
+
description?: string;
|
|
139
231
|
}>;
|
|
140
232
|
}
|
|
141
233
|
interface ComplianceConfig {
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
ccpa?: boolean;
|
|
234
|
+
/** Server-side GPC enforcement on `POST`/`PUT /consent`: re-applies GPC-driven denial when a
|
|
235
|
+
* write carries `gpcDetected`, independent of the widget's own client-side `gpcMode` handling.
|
|
236
|
+
* `false` disables this defense-in-depth check entirely. Default: enabled. */
|
|
146
237
|
gpc?: boolean | 'strict';
|
|
147
238
|
type?: ComplianceGroupId | 'auto';
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
239
|
+
geoDataProvider?: 'default' | 'maxmind' | 'geoip' | 'hosted-geoip-lite' | CountryResolverFn;
|
|
240
|
+
/** `'default'` (embedded map, the default), a URL to fetch a JSON `ComplianceMapData` document
|
|
241
|
+
* from (refreshed in the background per the response's `Cache-Control`/`Expires` header, 24h
|
|
242
|
+
* fallback if absent), or an inline operator-supplied `ComplianceMapData` object. Invalid data
|
|
243
|
+
* from either a URL or an object logs an error and falls back to `'default'`. */
|
|
244
|
+
complianceMap?: 'default' | string | ComplianceMapData;
|
|
245
|
+
tcf?: TcfConfig;
|
|
246
|
+
gpp?: GppConfig;
|
|
247
|
+
/** Signs server-stored consent records (HMAC-SHA256 at create/update time, hex-encoded into
|
|
248
|
+
* `signature`, checked on `GET /consent/:visitorId/verify`), ownership cookies, and
|
|
249
|
+
* parental-consent request/resolve tokens (see `POST /consent/:visitorId/parental-consent-request`),
|
|
250
|
+
* and salts hashed IPs — independent of the widget's own cookie-signing
|
|
251
|
+
* (`core.cookieSigningKey`). Auto-generated in memory when unset, so all of the above are
|
|
252
|
+
* always active — but an auto-generated value isn't persisted, so a restart invalidates
|
|
253
|
+
* outstanding ownership cookies, in-flight parental-consent tokens, and (if compared across
|
|
254
|
+
* restarts) hashed-IP continuity. Set this explicitly for anything beyond local dev. */
|
|
255
|
+
dataSigningHash?: string;
|
|
256
|
+
dataRetention?: DataRetentionConfig;
|
|
257
|
+
/** Expiry window for parental-consent tokens issued by `POST /consent/:visitorId/parental-consent-request`.
|
|
258
|
+
* Default: 7 days. */
|
|
259
|
+
parentalConsentTokenTtlDays?: number;
|
|
152
260
|
}
|
|
153
261
|
interface DashboardConfig {
|
|
154
262
|
enabled?: boolean;
|
|
@@ -157,15 +265,32 @@ interface DashboardConfig {
|
|
|
157
265
|
interface MultiTenantConfig {
|
|
158
266
|
enabled: boolean;
|
|
159
267
|
}
|
|
160
|
-
interface AgeGateConfig {
|
|
161
|
-
enabled: boolean;
|
|
162
|
-
minimumAge: number;
|
|
163
|
-
requireParentalConsent?: boolean;
|
|
164
|
-
}
|
|
165
268
|
interface TcfConfig {
|
|
166
269
|
enabled: boolean;
|
|
167
270
|
cmpId: number;
|
|
168
271
|
cmpVersion: number;
|
|
272
|
+
/** ISO 3166-1 alpha-2 country code of the publisher (e.g. 'DE', 'FR'). Required for
|
|
273
|
+
* spec-correct binary TC-string encoding — without it, real encoding is skipped and
|
|
274
|
+
* Consenti falls back to the simplified (non-spec) TC string. No default: there is no
|
|
275
|
+
* honest country to assume on a publisher's behalf. */
|
|
276
|
+
publisherCC?: string;
|
|
277
|
+
}
|
|
278
|
+
/** IAB Global Privacy Platform (GPP), US National ("usnat") section only — the umbrella section
|
|
279
|
+
* most MSPA/programmatic US bidders read. Individual state sections (US-CA, US-CO, etc.) aren't
|
|
280
|
+
* encoded yet. Requires the optional `@iabgpp/cmpapi` peer dependency (see `gpp/gpp-string.ts`) —
|
|
281
|
+
* unlike TCF there's no simplified fallback format, since a non-spec GPP string has no consumer
|
|
282
|
+
* that would accept it: when the dependency isn't installed, `gppString`/`window.__gpp` are
|
|
283
|
+
* simply not produced. */
|
|
284
|
+
interface GppConfig {
|
|
285
|
+
enabled: boolean;
|
|
286
|
+
cmpId: number;
|
|
287
|
+
cmpVersion: number;
|
|
288
|
+
/** MSPA "covered transaction" — whether this deployment's data transactions fall under MSPA
|
|
289
|
+
* signatory obligations. No honest default, so it's required rather than optional. */
|
|
290
|
+
mspaCoveredTransaction: boolean;
|
|
291
|
+
/** IAB's tri-state encoding for both MSPA fields: 0 = not applicable, 1 = yes, 2 = no. */
|
|
292
|
+
mspaOptOutOptionMode: 0 | 1 | 2;
|
|
293
|
+
mspaServiceProviderMode: 0 | 1 | 2;
|
|
169
294
|
}
|
|
170
295
|
interface DataRetentionConfig {
|
|
171
296
|
purgeAfterDays: number;
|
|
@@ -191,15 +316,12 @@ interface ConsentiServerConfig {
|
|
|
191
316
|
compliance?: ComplianceConfig;
|
|
192
317
|
multiTenant?: MultiTenantConfig;
|
|
193
318
|
plugins?: ConsentiServerPlugin[];
|
|
194
|
-
ageGate?: AgeGateConfig;
|
|
195
|
-
tcf?: TcfConfig;
|
|
196
|
-
dataRetention?: DataRetentionConfig;
|
|
197
319
|
maxBodySize?: number;
|
|
198
320
|
trustedProxies?: string[];
|
|
199
321
|
branding?: BrandingConfig;
|
|
200
322
|
s3Api?: S3ApiConfig;
|
|
201
323
|
/** Called after every profile activate/deactivate/delete. isPurge=true means invalidate; false means warm. */
|
|
202
|
-
handleCache?: (paths: string[],
|
|
324
|
+
handleCache?: (paths: string[], profileId: string, isPurge: boolean) => void;
|
|
203
325
|
}
|
|
204
326
|
interface Tenant {
|
|
205
327
|
id: string;
|
|
@@ -208,21 +330,69 @@ interface Tenant {
|
|
|
208
330
|
createdAt: string;
|
|
209
331
|
updatedAt: string;
|
|
210
332
|
}
|
|
333
|
+
/** Tenant-wide dashboard-managed defaults, shown on the API Config page. */
|
|
334
|
+
interface TenantSettings {
|
|
335
|
+
/** Public API origin allowlist. Distinct from `ProfileConfig.allowedOrigins`, which the
|
|
336
|
+
* origin check on `POST /consent` prefers when a profile sets its own list; this is the
|
|
337
|
+
* fallback. The public API has no auth token, so this is its only access gate. */
|
|
338
|
+
allowedOrigins?: string[];
|
|
339
|
+
/** Admin API origin allowlist — an additional CORS-layer check on top of Bearer-token auth
|
|
340
|
+
* for browser-originated `/consenti/admin/v1/*` requests. Empty/unset means no restriction
|
|
341
|
+
* (Bearer token auth alone still applies); only enforced when the request carries an
|
|
342
|
+
* `Origin` header — server-to-server callers are unaffected. */
|
|
343
|
+
adminAllowedOrigins?: string[];
|
|
344
|
+
/** Whether this tenant has completed (or skipped) the first-run setup wizard. Gates the
|
|
345
|
+
* one-time `#/setup` redirect in the dashboard — never reset once true. Default: false. */
|
|
346
|
+
setupCompleted?: boolean;
|
|
347
|
+
/** Whether `POST /setup/seed-profiles` has been called at least once for this tenant (an
|
|
348
|
+
* empty `groups: []` call counts). `POST /setup/complete` rejects until this is true, closing
|
|
349
|
+
* the bypass where the wizard's step-1 skip (or a direct API call) could mark setup complete
|
|
350
|
+
* without ever passing through config review or the profile step. */
|
|
351
|
+
profilesSeeded?: boolean;
|
|
352
|
+
/** Last-confirmed TCF `{cmpId, cmpVersion}` registration snapshot, hashed rather than stored
|
|
353
|
+
* in full — the live values always come from the static `compliance.tcf` config, never the DB.
|
|
354
|
+
* A hash mismatch (including never having confirmed) means TCF is treated as unconfirmed and
|
|
355
|
+
* fails closed: no `window.__tcfapi`, no TC string, a logged server error. Re-confirming in the
|
|
356
|
+
* dashboard (against the cached IAB CMP List) writes a fresh hash here. */
|
|
357
|
+
tcfConfirmation?: RegistrationConfirmation;
|
|
358
|
+
/** Same governance shape as `tcfConfirmation`, for the GPP US National section's own
|
|
359
|
+
* `compliance.gpp.{cmpId, cmpVersion}`. Self-attestation only — IAB doesn't publish a
|
|
360
|
+
* CMP-List equivalent for GPP, so there's no external registry to validate against. */
|
|
361
|
+
gppConfirmation?: RegistrationConfirmation;
|
|
362
|
+
}
|
|
363
|
+
/** Hash-based confirmation record — proves a specific `{cmpId, cmpVersion}` config was reviewed
|
|
364
|
+
* and confirmed as registered, without persisting the config values themselves in the DB. */
|
|
365
|
+
interface RegistrationConfirmation {
|
|
366
|
+
/** Hash of the confirmed `{cmpId, cmpVersion}` (and `publisherCC` for TCF). Compared against a
|
|
367
|
+
* fresh hash of the live static config on every check — any drift (including a first-ever
|
|
368
|
+
* check) means unconfirmed. */
|
|
369
|
+
configHash: string;
|
|
370
|
+
confirmedAt: string;
|
|
371
|
+
/** User id of the admin who checked the confirmation box. Undefined for confirmations made
|
|
372
|
+
* before this field existed. */
|
|
373
|
+
confirmedBy?: string;
|
|
374
|
+
}
|
|
211
375
|
interface ApiKey {
|
|
212
376
|
id: string;
|
|
213
377
|
tenantId: string;
|
|
214
378
|
keyHash: string;
|
|
215
379
|
name: string;
|
|
216
380
|
isActive: boolean;
|
|
381
|
+
/** User id of the admin who created this key. Undefined for keys created before this field existed. */
|
|
382
|
+
createdBy?: string;
|
|
383
|
+
/** Optional expiry set at creation. Checked lazily at auth time and on list — no background sweep. */
|
|
384
|
+
expireBy?: string;
|
|
217
385
|
createdAt: string;
|
|
386
|
+
updatedAt?: string;
|
|
218
387
|
}
|
|
219
388
|
interface Profile {
|
|
220
389
|
id: string;
|
|
221
390
|
tenantId: string;
|
|
222
391
|
name: string;
|
|
223
392
|
defaultLocale: string;
|
|
393
|
+
/** Starts at 1, incremented in place on every save. The row itself is mutated, never replaced — see {@link ProfileVersionEntry} for the on-disk snapshot history this produces. */
|
|
224
394
|
version: number;
|
|
225
|
-
profileJson:
|
|
395
|
+
profileJson: StoredProfileJson;
|
|
226
396
|
createdAt: string;
|
|
227
397
|
updatedAt: string;
|
|
228
398
|
}
|
|
@@ -231,7 +401,6 @@ interface ConsentDbRecord {
|
|
|
231
401
|
tenantId: string;
|
|
232
402
|
visitorId: string;
|
|
233
403
|
profileId: string;
|
|
234
|
-
profileVersion: number;
|
|
235
404
|
locale: string;
|
|
236
405
|
consentJson: ConsentValue;
|
|
237
406
|
gpcDetected: boolean;
|
|
@@ -239,6 +408,11 @@ interface ConsentDbRecord {
|
|
|
239
408
|
ageVerified?: boolean;
|
|
240
409
|
parentalConsentToken?: string;
|
|
241
410
|
tcfString?: string;
|
|
411
|
+
gppString?: string;
|
|
412
|
+
/** HMAC-SHA256 signature over the record's core fields, hex-encoded — tamper-evidence for
|
|
413
|
+
* server-stored records via `dataSigningHash` (auto-generated when unset), independent of the
|
|
414
|
+
* widget's own cookie-signing (`core.cookieSigningKey`). */
|
|
415
|
+
signature?: string;
|
|
242
416
|
createdAt: string;
|
|
243
417
|
updatedAt: string;
|
|
244
418
|
}
|
|
@@ -289,6 +463,22 @@ interface Permission {
|
|
|
289
463
|
name: string;
|
|
290
464
|
description?: string;
|
|
291
465
|
}
|
|
466
|
+
/** Proof that the consent banner was rendered to a visitor — recorded even if they never
|
|
467
|
+
* interact with it, distinct from (and never merged into) an actual consent decision. */
|
|
468
|
+
interface NoticeShownRecord {
|
|
469
|
+
id: string;
|
|
470
|
+
tenantId: string;
|
|
471
|
+
visitorId: string;
|
|
472
|
+
profileId: string;
|
|
473
|
+
locale: string;
|
|
474
|
+
createdAt: string;
|
|
475
|
+
}
|
|
476
|
+
interface CreateNoticeShownInput {
|
|
477
|
+
tenantId: string;
|
|
478
|
+
visitorId: string;
|
|
479
|
+
profileId: string;
|
|
480
|
+
locale: string;
|
|
481
|
+
}
|
|
292
482
|
interface AuditLog {
|
|
293
483
|
id: string;
|
|
294
484
|
tenantId: string;
|
|
@@ -300,22 +490,39 @@ interface AuditLog {
|
|
|
300
490
|
newData?: unknown;
|
|
301
491
|
createdAt: string;
|
|
302
492
|
}
|
|
493
|
+
/** List-view shape for consents — omits the fields not shown in a table row (`consentJson`,
|
|
494
|
+
* `parentalConsentToken`, `tcfString`, `gppString`, `signature`), so paginated list queries don't
|
|
495
|
+
* have to pull those blobs off disk for every row. Fetch the full `ConsentDbRecord` via
|
|
496
|
+
* `getConsent(visitorId)` when a single record's full detail is actually needed (e.g. opening a
|
|
497
|
+
* detail modal). */
|
|
498
|
+
type ConsentSummary = Omit<ConsentDbRecord, 'consentJson' | 'parentalConsentToken' | 'tcfString' | 'gppString' | 'signature'>;
|
|
499
|
+
/** List-view shape for audit logs — omits `oldData`/`newData` (full before/after snapshots) for
|
|
500
|
+
* the same reason as `ConsentSummary`. Fetch the full `AuditLog` via `getAuditLogById(id)` when a
|
|
501
|
+
* single entry's full detail is needed. */
|
|
502
|
+
type AuditLogSummary = Omit<AuditLog, 'oldData' | 'newData'>;
|
|
303
503
|
interface CreateProfileInput {
|
|
304
504
|
tenantId: string;
|
|
305
505
|
name: string;
|
|
306
506
|
defaultLocale: string;
|
|
307
|
-
profileJson:
|
|
507
|
+
profileJson: StoredProfileJson;
|
|
508
|
+
/** Non-default locales' resolved content for this save — see {@link LocaleContentInput}. */
|
|
509
|
+
localeContent?: Record<string, LocaleContentInput>;
|
|
308
510
|
}
|
|
309
511
|
interface UpdateProfileInput {
|
|
310
512
|
name?: string;
|
|
311
513
|
defaultLocale?: string;
|
|
312
|
-
profileJson?:
|
|
514
|
+
profileJson?: StoredProfileJson;
|
|
515
|
+
/** Explicit new version number — set by `ProfileService.update()` to `old.version + 1` for a content edit. Omitted for isActive-only flips (activate/deactivate), which leave the version unchanged. */
|
|
516
|
+
version?: number;
|
|
517
|
+
/** Non-default locales' resolved content for this save — see {@link LocaleContentInput}. Any
|
|
518
|
+
* locale in `profileJson.locales` not present here is carried forward unchanged from the
|
|
519
|
+
* previous version's on-disk file. */
|
|
520
|
+
localeContent?: Record<string, LocaleContentInput>;
|
|
313
521
|
}
|
|
314
522
|
interface CreateConsentInput {
|
|
315
523
|
tenantId: string;
|
|
316
524
|
visitorId: string;
|
|
317
525
|
profileId: string;
|
|
318
|
-
profileVersion: number;
|
|
319
526
|
locale: string;
|
|
320
527
|
consentJson: ConsentValue;
|
|
321
528
|
gpcDetected: boolean;
|
|
@@ -323,11 +530,14 @@ interface CreateConsentInput {
|
|
|
323
530
|
ageVerified?: boolean;
|
|
324
531
|
parentalConsentToken?: string;
|
|
325
532
|
tcfString?: string;
|
|
533
|
+
gppString?: string;
|
|
534
|
+
signature?: string;
|
|
326
535
|
}
|
|
327
536
|
interface UpdateConsentInput {
|
|
328
537
|
consentJson: ConsentValue;
|
|
329
538
|
locale?: string;
|
|
330
539
|
gpcDetected?: boolean;
|
|
540
|
+
signature?: string;
|
|
331
541
|
}
|
|
332
542
|
interface CreateVisitorInput {
|
|
333
543
|
tenantId: string;
|
|
@@ -376,6 +586,8 @@ interface ConsentFilters {
|
|
|
376
586
|
to?: string;
|
|
377
587
|
page?: number;
|
|
378
588
|
limit?: number;
|
|
589
|
+
/** Free-text search across visitorId, profileId, locale, source. */
|
|
590
|
+
q?: string;
|
|
379
591
|
}
|
|
380
592
|
interface VisitorFilters {
|
|
381
593
|
tenantId: string;
|
|
@@ -383,6 +595,8 @@ interface VisitorFilters {
|
|
|
383
595
|
to?: string;
|
|
384
596
|
page?: number;
|
|
385
597
|
limit?: number;
|
|
598
|
+
/** Free-text search across visitorId, country. */
|
|
599
|
+
q?: string;
|
|
386
600
|
}
|
|
387
601
|
interface AuditFilters {
|
|
388
602
|
tenantId: string;
|
|
@@ -392,12 +606,24 @@ interface AuditFilters {
|
|
|
392
606
|
to?: string;
|
|
393
607
|
page?: number;
|
|
394
608
|
limit?: number;
|
|
609
|
+
/** Free-text search across action, resourceType, resourceId, userId. */
|
|
610
|
+
q?: string;
|
|
611
|
+
}
|
|
612
|
+
/** Page of results plus the total row count matching the same filters (ignoring page/limit) —
|
|
613
|
+
* lets callers compute total page count for numbered pagination without a second round trip. */
|
|
614
|
+
interface PagedResult<T> {
|
|
615
|
+
items: T[];
|
|
616
|
+
total: number;
|
|
617
|
+
page: number;
|
|
618
|
+
limit: number;
|
|
395
619
|
}
|
|
396
620
|
interface ConsentVerifyResult {
|
|
397
621
|
valid: boolean;
|
|
398
|
-
reasons: Array<'
|
|
399
|
-
|
|
400
|
-
|
|
622
|
+
reasons: Array<'profile_changed' | 'consent_expired' | 'hmac_invalid'>;
|
|
623
|
+
/** The id of the profile currently active for this consent's compliance group, if any. */
|
|
624
|
+
currentProfileId?: string;
|
|
625
|
+
/** The id of the profile this consent was originally collected against. */
|
|
626
|
+
consentProfileId?: string;
|
|
401
627
|
}
|
|
402
628
|
type SafeAdminUser = Omit<AdminUser, 'passwordHash'>;
|
|
403
629
|
interface CreateRoleInput {
|
|
@@ -438,6 +664,8 @@ interface CreateApiKeyInput {
|
|
|
438
664
|
tenantId: string;
|
|
439
665
|
name: string;
|
|
440
666
|
keyHash: string;
|
|
667
|
+
createdBy?: string;
|
|
668
|
+
expireBy?: string;
|
|
441
669
|
}
|
|
442
670
|
interface CreateTenantInput {
|
|
443
671
|
name: string;
|
|
@@ -459,22 +687,24 @@ interface StorageAdapter {
|
|
|
459
687
|
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
460
688
|
/** Lightweight list — no profile_json blob; single JOIN for template names. */
|
|
461
689
|
listProfilesSummary(tenantId: string): Promise<ProfileSummary[]>;
|
|
462
|
-
|
|
690
|
+
findProfilesUsingConsentTemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
463
691
|
findProfilesUsingUITemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
464
692
|
getOptInStats(tenantId: string, filters: OptInFilters): Promise<OptInStats>;
|
|
465
693
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
466
694
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
467
695
|
deleteConsent(visitorId: string): Promise<void>;
|
|
468
696
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
469
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
697
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
470
698
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
471
699
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
472
700
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
473
701
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
474
702
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
475
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
703
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
476
704
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
477
705
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
706
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
707
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
478
708
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
479
709
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
480
710
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -484,17 +714,25 @@ interface StorageAdapter {
|
|
|
484
714
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
485
715
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
486
716
|
deleteTenant(id: string): Promise<void>;
|
|
717
|
+
/** Returns `{}` (all fields unset) if no settings row exists yet for this tenant. */
|
|
718
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
719
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
487
720
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
488
721
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
489
722
|
revokeApiKey(id: string): Promise<void>;
|
|
723
|
+
/** Re-enables a previously revoked key (same hash, no new secret). Clears `expireBy` if it's
|
|
724
|
+
* in the past, so a lazily-expired key doesn't immediately re-expire on the next check. */
|
|
725
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
726
|
+
/** Permanently removes the key row — unlike `revokeApiKey`, this cannot be undone. */
|
|
727
|
+
deleteApiKey(id: string): Promise<void>;
|
|
490
728
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
491
729
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
730
|
+
createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
731
|
+
updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
732
|
+
deleteConsentTemplate(id: string): Promise<void>;
|
|
733
|
+
getConsentTemplate(id: string): Promise<ServerConsentTemplate | null>;
|
|
734
|
+
getConsentTemplates(tenantId: string): Promise<ServerConsentTemplate[]>;
|
|
735
|
+
copyConsentTemplate(id: string, newName: string): Promise<ServerConsentTemplate>;
|
|
498
736
|
createUITemplate(data: CreateUITemplateInput): Promise<ServerUITemplate>;
|
|
499
737
|
updateUITemplate(id: string, data: UpdateUITemplateInput): Promise<ServerUITemplate>;
|
|
500
738
|
deleteUITemplate(id: string): Promise<void>;
|
|
@@ -520,7 +758,8 @@ interface StorageAdapter {
|
|
|
520
758
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
521
759
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
522
760
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
523
|
-
getLogs(filters: AuditFilters): Promise<
|
|
761
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
762
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
524
763
|
}
|
|
525
764
|
interface PluginContext {
|
|
526
765
|
storage: StorageAdapter;
|
|
@@ -539,23 +778,54 @@ declare abstract class ConsentiServerPlugin {
|
|
|
539
778
|
beforeUserCreate?(data: CreateUserInput): Promise<CreateUserInput>;
|
|
540
779
|
afterUserCreate?(user: AdminUser): Promise<void>;
|
|
541
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* The server's stored shape of `Profile.profileJson` — extends the shared `ProfileConfig`
|
|
783
|
+
* (`./ui`, also used by `apps/ui`'s local `ConsentiProfile`/`registerProfile()` authoring, which
|
|
784
|
+
* genuinely needs the full multi-locale `translations`/`localeContents` blob for its own
|
|
785
|
+
* no-backend use case and must stay untouched).
|
|
786
|
+
*
|
|
787
|
+
* On the server, only `defaultLocale`'s own resolved content is ever stored here —
|
|
788
|
+
* `mainBanner`/`gpcBanner`/`preferenceModal` (inherited from `ProfileConfig` as-is: template shape
|
|
789
|
+
* merged with the author's text, optional fields simply omitted rather than blank — never a
|
|
790
|
+
* multi-locale map). Every other locale's content lives only in its own per-version JSON file on
|
|
791
|
+
* disk (`profiles/{tenant}/{id}/{version}/{locale}.json`, see `ProfileService`), never in the DB
|
|
792
|
+
* row — `translations`/`localeContents` (both full multi-locale blobs) are dropped entirely.
|
|
793
|
+
*/
|
|
794
|
+
interface StoredProfileJson extends Omit<ProfileConfig, 'translations' | 'localeContents'> {
|
|
795
|
+
/** Every locale this profile has content for — drives the dashboard's tab list. Content for any
|
|
796
|
+
* locale other than `defaultLocale` lives only in that locale's on-disk version file, not here. */
|
|
797
|
+
locales: string[];
|
|
798
|
+
}
|
|
542
799
|
|
|
543
800
|
type ConsentStatus = 'granted' | 'denied' | 'objected';
|
|
544
801
|
type ConsentValue = Record<string, ConsentStatus>;
|
|
802
|
+
type GpcMode = 'ignore' | 'honor' | 'strict';
|
|
803
|
+
type LegalBasis = 'mandatory' | 'consent' | 'legitimate_interest';
|
|
804
|
+
type CookiePurpose = typeof COOKIE_PURPOSE_IDS[number];
|
|
805
|
+
/**
|
|
806
|
+
* A single consent parameter (tracker/cookie definition).
|
|
807
|
+
* Keyed by its own id inside `Record<string, Cookie>` — the map key IS the id,
|
|
808
|
+
* there is no `id` field on the value itself.
|
|
809
|
+
*/
|
|
545
810
|
interface Cookie {
|
|
546
|
-
|
|
547
|
-
legalBasis?: 'mandatory' | 'consent' | 'legitimate_interest';
|
|
548
|
-
/** @deprecated use legalBasis: 'mandatory' */
|
|
549
|
-
mandatory?: boolean;
|
|
550
|
-
/** @deprecated use legalBasis */
|
|
551
|
-
type?: 'consent' | 'legitimate_interest';
|
|
811
|
+
purpose?: CookiePurpose;
|
|
552
812
|
listenGpc?: boolean;
|
|
553
|
-
|
|
813
|
+
/**
|
|
814
|
+
* Pre-grant this parameter's default consent to 'granted' (instead of the
|
|
815
|
+
* compliance-group-driven default) when no stored decision exists yet.
|
|
816
|
+
* Only meaningful when the owning category's `legalBasis === 'consent'` —
|
|
817
|
+
* `mandatory`/`legitimate_interest` categories are already effectively
|
|
818
|
+
* pre-granted regardless of this flag. Never overrides an active GPC signal.
|
|
819
|
+
* Default: false.
|
|
820
|
+
*/
|
|
821
|
+
preGrant?: boolean;
|
|
554
822
|
tcfVendorId?: number;
|
|
555
823
|
tcfPurposes?: number[];
|
|
556
824
|
tcfSpecialFeatures?: number[];
|
|
557
825
|
cpraCategory?: 'sale' | 'sharing' | 'sensitive';
|
|
558
826
|
}
|
|
827
|
+
/** `Cookie` keyed by its own id — the standard shape used everywhere a parameter list is passed around. */
|
|
828
|
+
type CookieMap = Record<string, Cookie>;
|
|
559
829
|
type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
|
|
560
830
|
type ButtonAction = 'submit' | 'manage' | 'close' | 'custom' | 'link';
|
|
561
831
|
interface Button {
|
|
@@ -564,8 +834,11 @@ interface Button {
|
|
|
564
834
|
action: ButtonAction;
|
|
565
835
|
cookies?: string[] | '*' | '!';
|
|
566
836
|
url?: string;
|
|
567
|
-
type?: string;
|
|
568
837
|
}
|
|
838
|
+
/** `Button` keyed by its own id — e.g. `'accept-all'`. The map key IS the id, rendered as the DOM
|
|
839
|
+
* `id` (`consenti-btn-{id}`) so integrators can target specific buttons, and is what
|
|
840
|
+
* `profileOverride`'s `deepMerge` deletes by (`{ 'reject-optional': null }` removes one button). */
|
|
841
|
+
type ButtonMap = Record<string, Button>;
|
|
569
842
|
interface MainBanner {
|
|
570
843
|
position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
|
|
571
844
|
overlayOpacity?: number;
|
|
@@ -574,34 +847,100 @@ interface MainBanner {
|
|
|
574
847
|
heading?: string;
|
|
575
848
|
headingTag?: string;
|
|
576
849
|
htmlText: string;
|
|
577
|
-
buttons:
|
|
850
|
+
buttons: ButtonMap;
|
|
851
|
+
/** Stack buttons vertically below this viewport width in px. 0 or undefined = disabled. Default: 576. */
|
|
852
|
+
stackButtonsOnBreakpoint?: number;
|
|
853
|
+
/** Trap keyboard Tab focus within the consent root while this banner is visible. */
|
|
854
|
+
trapFocus?: boolean;
|
|
578
855
|
}
|
|
579
856
|
type GpcBanner = MainBanner;
|
|
857
|
+
/**
|
|
858
|
+
* A consent category — the single source of legal basis for every parameter
|
|
859
|
+
* it lists in `cookies`. Keyed by its own id inside `Record<string, Category>` —
|
|
860
|
+
* the map key IS the id, there is no `id` field on the value itself.
|
|
861
|
+
*
|
|
862
|
+
* A parameter must belong to exactly one category (enforced at authoring time,
|
|
863
|
+
* not by this type) — that relationship is what `getCookieLegalBasis`/
|
|
864
|
+
* `isMandatoryCookie` (`@consenti/utils`) resolve at runtime.
|
|
865
|
+
*/
|
|
580
866
|
interface Category {
|
|
581
|
-
id: string;
|
|
582
867
|
heading: string;
|
|
583
868
|
headingTag?: string;
|
|
584
869
|
htmlText: string;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
description?: string;
|
|
590
|
-
};
|
|
870
|
+
legalBasis: LegalBasis;
|
|
871
|
+
/** Only meaningful when legalBasis === 'legitimate_interest' — the GDPR balancing-test justification shown to visitors. */
|
|
872
|
+
legitimateInterestDescription?: string;
|
|
873
|
+
/** IDs of the parameters (from the profile's `cookies` map) that belong to this category. */
|
|
591
874
|
cookies: string[];
|
|
592
875
|
}
|
|
876
|
+
/** `Category` keyed by its own id — the standard shape used everywhere a category list is passed around. */
|
|
877
|
+
type CategoryMap = Record<string, Category>;
|
|
593
878
|
interface PreferenceModal extends Omit<MainBanner, "position"> {
|
|
594
879
|
position?: 'left' | 'right' | 'center';
|
|
595
880
|
subheading?: string;
|
|
596
|
-
categories:
|
|
881
|
+
categories: CategoryMap;
|
|
597
882
|
persistent?: boolean;
|
|
598
883
|
/** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
|
|
599
884
|
mobileFullScreenBreakpoint?: number;
|
|
885
|
+
/** Label for the optional consent-receipt download checkbox (shown when `allowReceipt` is true). Falls back to a default when omitted. */
|
|
886
|
+
receiptLabel?: string;
|
|
887
|
+
/** Description shown beneath the consent-receipt checkbox. Falls back to a default when omitted. */
|
|
888
|
+
receiptDescription?: string;
|
|
889
|
+
/**
|
|
890
|
+
* Renders a "Forget me" control in the modal body, below the categories — only shown to
|
|
891
|
+
* visitors who already have a stored consent decision (nothing to erase otherwise). Wired to
|
|
892
|
+
* the same `DELETE /consent/:visitorId` erasure endpoint as `widget.deleteConsent()`, giving
|
|
893
|
+
* visitors self-service access to GDPR Art. 17 / CCPA-CPRA / LGPD Art. 18 / UK GDPR / DPDPA /
|
|
894
|
+
* KVKK / POPIA / PDPA-TH / APPI erasure-equivalent rights without the host wiring a link
|
|
895
|
+
* themselves. See the "Right to erasure" guide for the full compliance mapping.
|
|
896
|
+
*/
|
|
897
|
+
showForgetMe?: boolean;
|
|
898
|
+
/** Label for the "Forget me" button (shown when `showForgetMe` is true). Falls back to a default when omitted. */
|
|
899
|
+
forgetMeLabel?: string;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* A UI-template button definition. Keyed by its own machine id (e.g. `'accept-all'`) inside
|
|
903
|
+
* `TemplateButtonMap` — the map key IS the id, not display text. UI templates own layout/behavior
|
|
904
|
+
* only, never text. The visitor-facing label is authored per-locale on the profile instead
|
|
905
|
+
* (`LocaleTextContent.*.buttonLabels`, keyed by the same button id) and composed into a resolved
|
|
906
|
+
* `Button.text` at profile-build time. Distinct from `Button` (the resolved-profile shape), which
|
|
907
|
+
* always carries real `text`.
|
|
908
|
+
*/
|
|
909
|
+
interface TemplateButtonDef {
|
|
910
|
+
type?: ButtonStyle;
|
|
911
|
+
action: ButtonAction;
|
|
912
|
+
cookies?: string[] | '*' | '!';
|
|
913
|
+
url?: string;
|
|
914
|
+
}
|
|
915
|
+
/** `TemplateButtonDef` keyed by its own id — the standard shape everywhere a UI template's
|
|
916
|
+
* button set is passed around. */
|
|
917
|
+
type TemplateButtonMap = Record<string, TemplateButtonDef>;
|
|
918
|
+
interface TemplateBannerDef {
|
|
919
|
+
position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
|
|
920
|
+
overlayOpacity?: number;
|
|
921
|
+
showClose?: boolean;
|
|
922
|
+
showLocaleSwitcher?: boolean;
|
|
923
|
+
headingTag?: string;
|
|
924
|
+
buttons: TemplateButtonMap;
|
|
925
|
+
stackButtonsOnBreakpoint?: number;
|
|
926
|
+
trapFocus?: boolean;
|
|
927
|
+
}
|
|
928
|
+
type TemplateGpcBannerDef = TemplateBannerDef;
|
|
929
|
+
interface TemplateModalDef extends Omit<TemplateBannerDef, 'position'> {
|
|
930
|
+
position?: 'left' | 'right' | 'center';
|
|
931
|
+
persistent?: boolean;
|
|
932
|
+
hasSubheading?: boolean;
|
|
933
|
+
mobileFullScreenBreakpoint?: number;
|
|
934
|
+
/** Renders a "Forget me" control below the categories, for visitors who already have a stored
|
|
935
|
+
* consent decision. See {@link PreferenceModal.showForgetMe}. */
|
|
936
|
+
showForgetMe?: boolean;
|
|
600
937
|
}
|
|
601
938
|
interface LocaleTranslations {
|
|
602
|
-
mainBanner: MainBanner
|
|
603
|
-
gpcBanner?: GpcBanner
|
|
604
|
-
preferenceModal: PreferenceModal
|
|
939
|
+
mainBanner: DeepPartial<MainBanner>;
|
|
940
|
+
gpcBanner?: DeepPartial<GpcBanner>;
|
|
941
|
+
preferenceModal: DeepPartial<PreferenceModal>;
|
|
942
|
+
/** Only meaningful when the profile's `ageGate.enabled` is true. */
|
|
943
|
+
ageGateModal?: DeepPartial<AgeGateModalContent>;
|
|
605
944
|
}
|
|
606
945
|
interface ProfileTranslations {
|
|
607
946
|
[locale: string]: LocaleTranslations;
|
|
@@ -611,113 +950,201 @@ interface DpdpaConfig {
|
|
|
611
950
|
grievanceEmail: string;
|
|
612
951
|
purposeDescription?: string;
|
|
613
952
|
}
|
|
953
|
+
/**
|
|
954
|
+
* Per-profile age gate — GDPR Art. 8's consent age varies 13–16 by EU member state and DPDPA has
|
|
955
|
+
* its own child-data age rules, so one global age (the old `ConsentiServerConfig.ageGate`) was
|
|
956
|
+
* wrong for a multi-region deployment. Mirrors `dpdpa` exactly: profile-scoped, not global.
|
|
957
|
+
*/
|
|
958
|
+
interface AgeGateConfig {
|
|
959
|
+
enabled: boolean;
|
|
960
|
+
minimumAge: number;
|
|
961
|
+
requireParentalConsent?: boolean;
|
|
962
|
+
}
|
|
963
|
+
/** Per-locale age-gate modal text — translated the same way `mainBanner`/`preferenceModal`
|
|
964
|
+
* content is, unlike the widget's own hardcoded UI chrome (close button, locale switcher). */
|
|
965
|
+
interface AgeGateModalContent {
|
|
966
|
+
heading?: string;
|
|
967
|
+
htmlText: string;
|
|
968
|
+
confirmButtonLabel: string;
|
|
969
|
+
denyButtonLabel: string;
|
|
970
|
+
parentalConsent: {
|
|
971
|
+
heading?: string;
|
|
972
|
+
htmlText: string;
|
|
973
|
+
confirmButtonLabel: string;
|
|
974
|
+
};
|
|
975
|
+
}
|
|
614
976
|
interface LocaleTextContent {
|
|
615
977
|
mainBanner: {
|
|
616
978
|
heading?: string;
|
|
617
979
|
htmlText: string;
|
|
618
|
-
|
|
980
|
+
/** Keyed by the linked UI template's button id — was a positional array matched to
|
|
981
|
+
* `buttons[i]` by index; now matched by key, robust against button reordering. */
|
|
982
|
+
buttonLabels?: Record<string, string>;
|
|
619
983
|
};
|
|
620
984
|
gpcBanner: {
|
|
621
985
|
heading?: string;
|
|
622
986
|
htmlText: string;
|
|
623
|
-
buttonLabels?: string
|
|
987
|
+
buttonLabels?: Record<string, string>;
|
|
624
988
|
};
|
|
625
989
|
preferenceModal: {
|
|
626
990
|
heading: string;
|
|
627
991
|
subheading?: string;
|
|
628
992
|
htmlText?: string;
|
|
629
|
-
buttonLabels?: string
|
|
630
|
-
categories
|
|
631
|
-
|
|
993
|
+
buttonLabels?: Record<string, string>;
|
|
994
|
+
/** `legitimateInterestDescription` is only meaningful for categories whose legalBasis is 'legitimate_interest'; optional GDPR balancing-test text, translated per locale like heading/htmlText. */
|
|
995
|
+
categories: Record<string, {
|
|
632
996
|
heading: string;
|
|
633
997
|
htmlText: string;
|
|
634
|
-
|
|
998
|
+
legitimateInterestDescription?: string;
|
|
999
|
+
}>;
|
|
1000
|
+
receiptLabel?: string;
|
|
1001
|
+
receiptDescription?: string;
|
|
1002
|
+
/** Per-locale override of `PreferenceModal.forgetMeLabel`; only meaningful when `showForgetMe` is true. */
|
|
1003
|
+
forgetMeLabel?: string;
|
|
635
1004
|
};
|
|
1005
|
+
/** Only meaningful when the profile's `ageGate.enabled` is true. */
|
|
1006
|
+
ageGateModal?: AgeGateModalContent;
|
|
636
1007
|
}
|
|
637
1008
|
interface ProfileConfig {
|
|
638
|
-
|
|
1009
|
+
id: string;
|
|
1010
|
+
cookies?: CookieMap;
|
|
639
1011
|
defaultLocale: string;
|
|
1012
|
+
/** Days until consent expires and the visitor is asked again (profile-wide). Default: 365. */
|
|
1013
|
+
expiryDays?: number;
|
|
640
1014
|
translations?: ProfileTranslations;
|
|
641
|
-
|
|
1015
|
+
mainBanner: MainBanner;
|
|
1016
|
+
gpcBanner?: GpcBanner;
|
|
1017
|
+
preferenceModal: PreferenceModal;
|
|
642
1018
|
darkMode?: boolean;
|
|
643
1019
|
allowedOrigins?: string[];
|
|
644
1020
|
dpdpa?: DpdpaConfig;
|
|
1021
|
+
ageGate?: AgeGateConfig;
|
|
1022
|
+
/** Default locale's age-gate modal text — mirrors `mainBanner`/`preferenceModal` (inline,
|
|
1023
|
+
* required-shape-when-present), not `dpdpa` (which has no locale text at all). Every other
|
|
1024
|
+
* locale's `ageGateModal` lives in `localeContents`/`LocaleContentInput` like the rest of
|
|
1025
|
+
* that locale's content. */
|
|
1026
|
+
ageGateModal?: AgeGateModalContent;
|
|
645
1027
|
regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
|
|
646
1028
|
regulations?: string[];
|
|
647
|
-
|
|
1029
|
+
consentTemplateId?: string;
|
|
648
1030
|
uiTemplateId?: string;
|
|
649
1031
|
localeContents?: Record<string, LocaleTextContent>;
|
|
650
|
-
complianceGroup?:
|
|
651
|
-
/**
|
|
652
|
-
|
|
653
|
-
|
|
1032
|
+
complianceGroup?: ComplianceGroupId;
|
|
1033
|
+
/**
|
|
1034
|
+
* Free-form identifier (lower-kebab-case) for profiles that don't map to one of the
|
|
1035
|
+
* built-in `complianceGroup` values — the identifier the widget's `compliance.type`
|
|
1036
|
+
* config targets to fetch this profile. Participates in activation, deactivation, and
|
|
1037
|
+
* "one active profile per group" conflict detection the same way `complianceGroup` does;
|
|
1038
|
+
* no `COMPLIANCE_GROUPS` validation rules or GPC defaults apply to it, since none exist
|
|
1039
|
+
* for a free-form name.
|
|
1040
|
+
*/
|
|
1041
|
+
customComplianceGroup?: string;
|
|
1042
|
+
/**
|
|
1043
|
+
* Per-profile deltas applied on top of the consent template's authored `Cookie` values
|
|
1044
|
+
* (e.g. an overridden `preGrant`) when resolving this profile — see the dashboard's Step 2
|
|
1045
|
+
* pre-grant override UX. Keyed by cookie id; only the overridden fields need to be present.
|
|
1046
|
+
*/
|
|
1047
|
+
cookiesOverride?: Record<string, Partial<Cookie>>;
|
|
1048
|
+
/**
|
|
1049
|
+
* Reserved for a future phase — stored but not yet applied when resolving a profile.
|
|
1050
|
+
* Intended to mirror `cookiesOverride` for per-category deltas.
|
|
1051
|
+
*/
|
|
1052
|
+
categoriesOverride?: Record<string, Partial<Category>>;
|
|
1053
|
+
/**
|
|
1054
|
+
* Reserved for a future phase — stored but not yet applied when resolving a profile.
|
|
1055
|
+
* Intended to hold per-profile UI-template deltas.
|
|
1056
|
+
*/
|
|
1057
|
+
uiOverride?: Record<string, unknown>;
|
|
1058
|
+
/**
|
|
1059
|
+
* When registered via `registerProfile()` and `complianceGroup` matches one of
|
|
1060
|
+
* Consenti's built-in groups: `false` (default) fully replaces the built-in
|
|
1061
|
+
* embedded profile for that group; `true` deep-merges this config onto the
|
|
1062
|
+
* built-in profile instead (only meaningful together with `complianceGroup`).
|
|
1063
|
+
*/
|
|
1064
|
+
deepMerge?: boolean;
|
|
1065
|
+
gpcMode?: GpcMode;
|
|
654
1066
|
isActive?: boolean;
|
|
655
1067
|
hidePoweredBy?: boolean;
|
|
656
1068
|
allowReceipt?: boolean;
|
|
657
1069
|
/** Per-compliance extra config (e.g. DPDPA data fiduciary name). */
|
|
658
1070
|
complianceConfig?: Record<string, string>;
|
|
1071
|
+
/** Show a metadata footer strip in the banner/modal with Consent ID, Date, Version, Privacy Settings link. */
|
|
1072
|
+
showFooterMetadata?: boolean;
|
|
1073
|
+
/** Apply WCAG 2.1 AA button sizing (44px min-height), visible focus rings, and screen-reader labels. */
|
|
1074
|
+
enhanceAccessibility?: boolean;
|
|
659
1075
|
}
|
|
660
1076
|
interface ServerUITemplate {
|
|
661
1077
|
id: string;
|
|
662
1078
|
tenantId: string;
|
|
663
1079
|
name: string;
|
|
664
|
-
mainBanner:
|
|
665
|
-
gpcBanner:
|
|
666
|
-
preferenceModal:
|
|
1080
|
+
mainBanner: TemplateBannerDef;
|
|
1081
|
+
gpcBanner: TemplateGpcBannerDef;
|
|
1082
|
+
preferenceModal: TemplateModalDef;
|
|
667
1083
|
createdAt: string;
|
|
668
1084
|
updatedAt: string;
|
|
669
1085
|
}
|
|
670
|
-
interface
|
|
1086
|
+
interface ServerConsentTemplate {
|
|
671
1087
|
id: string;
|
|
672
1088
|
tenantId: string;
|
|
673
1089
|
name: string;
|
|
674
|
-
cookies:
|
|
1090
|
+
cookies: CookieMap;
|
|
1091
|
+
categories: CategoryMap;
|
|
675
1092
|
createdAt: string;
|
|
676
1093
|
updatedAt: string;
|
|
677
1094
|
}
|
|
678
|
-
interface
|
|
1095
|
+
interface CreateConsentTemplateInput {
|
|
679
1096
|
tenantId: string;
|
|
680
1097
|
name: string;
|
|
681
|
-
cookies:
|
|
1098
|
+
cookies: CookieMap;
|
|
1099
|
+
categories: CategoryMap;
|
|
682
1100
|
}
|
|
683
|
-
interface
|
|
1101
|
+
interface UpdateConsentTemplateInput {
|
|
684
1102
|
name?: string;
|
|
685
|
-
cookies?:
|
|
1103
|
+
cookies?: CookieMap;
|
|
1104
|
+
categories?: CategoryMap;
|
|
686
1105
|
}
|
|
687
1106
|
interface CreateUITemplateInput {
|
|
688
1107
|
tenantId: string;
|
|
689
1108
|
name: string;
|
|
690
|
-
mainBanner:
|
|
691
|
-
gpcBanner:
|
|
692
|
-
preferenceModal:
|
|
1109
|
+
mainBanner: TemplateBannerDef;
|
|
1110
|
+
gpcBanner: TemplateGpcBannerDef;
|
|
1111
|
+
preferenceModal: TemplateModalDef;
|
|
693
1112
|
}
|
|
694
1113
|
interface UpdateUITemplateInput {
|
|
695
1114
|
name?: string;
|
|
696
|
-
mainBanner?:
|
|
697
|
-
gpcBanner?:
|
|
698
|
-
preferenceModal?:
|
|
1115
|
+
mainBanner?: TemplateBannerDef;
|
|
1116
|
+
gpcBanner?: TemplateGpcBannerDef;
|
|
1117
|
+
preferenceModal?: TemplateModalDef;
|
|
699
1118
|
}
|
|
700
1119
|
interface PublicProfileResponse {
|
|
701
1120
|
id: string;
|
|
1121
|
+
/** Incremented in place on every save of this profile. */
|
|
1122
|
+
version: number;
|
|
702
1123
|
name?: string;
|
|
703
1124
|
tenantId?: string;
|
|
704
|
-
version: number;
|
|
705
1125
|
defaultLocale: string;
|
|
706
1126
|
currentLocale: string;
|
|
707
1127
|
locales: string[];
|
|
708
|
-
cookies:
|
|
1128
|
+
cookies: CookieMap;
|
|
1129
|
+
/** Days until consent expires and the visitor is asked again (profile-wide). */
|
|
1130
|
+
expiryDays?: number;
|
|
709
1131
|
mainBanner: MainBanner;
|
|
710
1132
|
gpcBanner?: GpcBanner;
|
|
711
1133
|
preferenceModal: PreferenceModal;
|
|
712
1134
|
resolvedComplianceGroup?: string;
|
|
713
|
-
complianceGroup?:
|
|
1135
|
+
complianceGroup?: ComplianceGroupId;
|
|
714
1136
|
compliances?: string[];
|
|
715
1137
|
complianceConfig?: Record<string, string>;
|
|
716
1138
|
allowedOrigins?: string[];
|
|
717
|
-
gpcMode?:
|
|
1139
|
+
gpcMode?: GpcMode;
|
|
718
1140
|
hidePoweredBy?: boolean;
|
|
719
1141
|
allowReceipt?: boolean;
|
|
720
1142
|
darkMode?: boolean;
|
|
1143
|
+
dpdpa?: DpdpaConfig;
|
|
1144
|
+
ageGate?: AgeGateConfig;
|
|
1145
|
+
ageGateModal?: AgeGateModalContent;
|
|
1146
|
+
showFooterMetadata?: boolean;
|
|
1147
|
+
enhanceAccessibility?: boolean;
|
|
721
1148
|
createdAt?: string;
|
|
722
1149
|
updatedAt?: string;
|
|
723
1150
|
}
|
|
@@ -737,7 +1164,8 @@ declare class AuditRepo {
|
|
|
737
1164
|
private adapter;
|
|
738
1165
|
constructor(adapter: StorageAdapter);
|
|
739
1166
|
log(data: CreateAuditLogInput): Promise<void>;
|
|
740
|
-
list(filters: AuditFilters): Promise<
|
|
1167
|
+
list(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1168
|
+
get(id: string): Promise<AuditLog | null>;
|
|
741
1169
|
}
|
|
742
1170
|
|
|
743
1171
|
declare class LocaleJsonCacheService {
|
|
@@ -760,23 +1188,118 @@ declare class ProfileService {
|
|
|
760
1188
|
private profilesDir?;
|
|
761
1189
|
private handleCache?;
|
|
762
1190
|
private s3Config?;
|
|
763
|
-
constructor(profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, storage?: StorageAdapter | undefined, eventBus?: EventEmitter | undefined, localeCache?: LocaleJsonCacheService | undefined, profilesDir?: string | undefined, handleCache?: ((paths: string[],
|
|
764
|
-
|
|
1191
|
+
constructor(profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, storage?: StorageAdapter | undefined, eventBus?: EventEmitter | undefined, localeCache?: LocaleJsonCacheService | undefined, profilesDir?: string | undefined, handleCache?: ((paths: string[], profileId: string, isPurge: boolean) => void) | undefined, s3Config?: S3ApiConfig | undefined);
|
|
1192
|
+
/** Root directory holding every version snapshot of one logical profile, keyed by its stable id. */
|
|
1193
|
+
private versionsRootDir;
|
|
765
1194
|
private complianceGroupDir;
|
|
1195
|
+
/**
|
|
1196
|
+
* The directory key used for activation/hot-serve writes and the "one active profile
|
|
1197
|
+
* per group" conflict check — the real `complianceGroup` when set, otherwise the
|
|
1198
|
+
* free-form `customComplianceGroup` (which the widget's `compliance.type` targets the
|
|
1199
|
+
* same way). Compliance *validation* still only ever runs against a real `complianceGroup`.
|
|
1200
|
+
*/
|
|
1201
|
+
private groupKey;
|
|
766
1202
|
private profileFilePaths;
|
|
1203
|
+
/** Resolves this profile's cookie map (consent-template lookup + `cookiesOverride` applied) —
|
|
1204
|
+
* the one piece of content still resolved live rather than baked at save time, since cookies
|
|
1205
|
+
* are small and never the source of the row-size problem `mainBanner`/`preferenceModal` were. */
|
|
1206
|
+
private resolveCookies;
|
|
1207
|
+
/**
|
|
1208
|
+
* Writes every locale's on-disk version file for one save (create or an edit) in a single
|
|
1209
|
+
* atomic worker call — default locale content comes from `profile.profileJson` itself, every
|
|
1210
|
+
* other touched locale from `localeContent`. A locale already in `profileJson.locales` but not
|
|
1211
|
+
* present in `localeContent` (a tab the user never opened this session) is carried forward
|
|
1212
|
+
* unchanged from `previousVersion` by the worker — never silently dropped.
|
|
1213
|
+
*/
|
|
1214
|
+
private writeLocaleFiles;
|
|
767
1215
|
create(input: Omit<CreateProfileInput, 'tenantId'>): Promise<Profile>;
|
|
1216
|
+
/**
|
|
1217
|
+
* Duplicates a profile as a brand new, always-inactive profile (own `id`, `version` starts at
|
|
1218
|
+
* 1 again). Delegates to {@link create} rather than copying on-disk files directly — the
|
|
1219
|
+
* resolved-JSON snapshot embeds its own `id`/`version`, so it has to be regenerated for the
|
|
1220
|
+
* new profile rather than byte-copied from the source's. Non-default locale content is read
|
|
1221
|
+
* back off the source's current version files so the copy carries every locale forward too.
|
|
1222
|
+
*/
|
|
1223
|
+
copy(id: string, name?: string): Promise<Profile>;
|
|
1224
|
+
/**
|
|
1225
|
+
* Mutates the existing row in place — `id` is stable across every edit, `version` is
|
|
1226
|
+
* incremented. The prior state isn't kept as a separate DB row; its resolved-JSON snapshot
|
|
1227
|
+
* on disk (see {@link listVersions}) is the audit trail for that edit.
|
|
1228
|
+
*/
|
|
768
1229
|
update(id: string, input: UpdateProfileInput): Promise<Profile>;
|
|
769
1230
|
delete(id: string): Promise<void>;
|
|
770
1231
|
activate(id: string): Promise<Profile>;
|
|
771
1232
|
deactivate(id: string): Promise<Profile>;
|
|
772
|
-
private
|
|
773
|
-
get(id: string
|
|
1233
|
+
private runDeactivate;
|
|
1234
|
+
get(id: string): Promise<Profile | null>;
|
|
1235
|
+
/**
|
|
1236
|
+
* Resolves one locale's full public response. The default locale's content lives directly on
|
|
1237
|
+
* the DB row (`profileJson.mainBanner`/`gpcBanner`/`preferenceModal`) — no template lookups, no
|
|
1238
|
+
* live resolution. Any other locale's content lives only in that locale's on-disk version file;
|
|
1239
|
+
* this reads it directly, falling back to the default locale's content if the file is missing
|
|
1240
|
+
* (e.g. a locale listed but never actually authored yet). This method is not a hot path — the
|
|
1241
|
+
* public `/profiles/:tenant/:group/:locale` route serves the static file directly and only
|
|
1242
|
+
* falls back to this for previewing an inactive/versioned profile.
|
|
1243
|
+
*/
|
|
774
1244
|
getResolved(id: string, locale?: string): Promise<PublicProfileResponse | null>;
|
|
775
1245
|
findActiveByComplianceGroup(complianceGroup: string): Promise<Profile | null>;
|
|
776
1246
|
list(): Promise<Profile[]>;
|
|
777
1247
|
listSummary(): Promise<ProfileSummary[]>;
|
|
778
|
-
|
|
779
|
-
|
|
1248
|
+
/**
|
|
1249
|
+
* Lists every version snapshot of `profileId` on disk (newest first), read straight from
|
|
1250
|
+
* the version-directory tree — no DB query, `Profile.version` is just the current pointer.
|
|
1251
|
+
* Works for archived profiles too (DB row deleted, files still on disk) — the directory
|
|
1252
|
+
* tree is the only thing this reads.
|
|
1253
|
+
*/
|
|
1254
|
+
listVersions(profileId: string): Promise<ProfileVersionEntry[]>;
|
|
1255
|
+
/** Works for archived profiles too — see `listVersions()`. */
|
|
1256
|
+
getVersionFile(profileId: string, version: string, locale: string): Promise<string | null>;
|
|
1257
|
+
/**
|
|
1258
|
+
* Profile-id directories on disk with no matching DB row — deleted profiles whose version
|
|
1259
|
+
* snapshots `delete()` never removes. Directory-only read (id, version count, last-modified
|
|
1260
|
+
* mtime) — no file content is opened here; that only happens once a specific version is
|
|
1261
|
+
* requested via `getVersionFile()`, which works for these ids too.
|
|
1262
|
+
*/
|
|
1263
|
+
listArchivedProfiles(): Promise<ArchivedProfileSummary[]>;
|
|
1264
|
+
/** Strips display text from an embedded profile's button map — a `UITemplate`'s buttons own
|
|
1265
|
+
* layout/behavior only, never text (button labels are authored per-locale on the profile
|
|
1266
|
+
* instead, see `LocaleTextContent.*.buttonLabels`). */
|
|
1267
|
+
private toTemplateButtonMap;
|
|
1268
|
+
/** Builds a `UITemplate`'s structural definitions (position, buttons, close/locale-switcher
|
|
1269
|
+
* flags) from an embedded profile's English translation — no heading/htmlText/button text,
|
|
1270
|
+
* which stay inline on the seeded profile as per-locale content, never on the template. */
|
|
1271
|
+
private buildSeedUiTemplatePayload;
|
|
1272
|
+
/**
|
|
1273
|
+
* Idempotently gets-or-creates the `ConsentTemplate` + `UITemplate` pair backing one seeded
|
|
1274
|
+
* compliance group's profile, named consistently with the profile itself (`Default — {group}`).
|
|
1275
|
+
* Looked up by name/tenant first so re-running the setup wizard never creates duplicates.
|
|
1276
|
+
*/
|
|
1277
|
+
private getOrCreateSeedTemplates;
|
|
1278
|
+
/**
|
|
1279
|
+
* Seeds a default profile for a compliance group from the embedded English profile in
|
|
1280
|
+
* `@consenti/utils`, merged with locale text overlays for de/es/fr/ja. Every locale — including
|
|
1281
|
+
* non-English ones — is passed through `create()`'s normal `localeContent` path, the same as an
|
|
1282
|
+
* author-submitted profile; there is no separate "seeded profile" code path for locale content
|
|
1283
|
+
* anymore, which is what previously let a locale silently never make it into the served file.
|
|
1284
|
+
* Only creates the profile if no active profile already exists for the compliance group.
|
|
1285
|
+
* Safe to call repeatedly (idempotent).
|
|
1286
|
+
*
|
|
1287
|
+
* Also seeds (idempotently) a `ConsentTemplate`/`UITemplate` pair for the group and links the
|
|
1288
|
+
* profile to both via `consentTemplateId`/`uiTemplateId` — same as a dashboard-authored profile
|
|
1289
|
+
* is required to. The profile is a *snapshot*, not a live pointer: `cookies`/`mainBanner`/
|
|
1290
|
+
* `gpcBanner`/`preferenceModal` stay fully resolved inline (DB row + on-disk locale files)
|
|
1291
|
+
* exactly as before; the template ids are additive, purely for traceability and so the editor
|
|
1292
|
+
* knows which template to re-offer on next edit. Editing the template later never silently
|
|
1293
|
+
* changes this profile — only an explicit re-save of the profile itself does.
|
|
1294
|
+
*
|
|
1295
|
+
* @param complianceGroup - One of the 8 compliance group IDs.
|
|
1296
|
+
*/
|
|
1297
|
+
seedDefaultProfile(complianceGroup: string): Promise<void>;
|
|
1298
|
+
/**
|
|
1299
|
+
* Seeds default profiles for all 8 compliance groups.
|
|
1300
|
+
* Skips any group that already has an active profile (idempotent).
|
|
1301
|
+
*/
|
|
1302
|
+
seedAllDefaults(): Promise<void>;
|
|
780
1303
|
}
|
|
781
1304
|
|
|
782
1305
|
declare class ConsentRepo {
|
|
@@ -786,7 +1309,7 @@ declare class ConsentRepo {
|
|
|
786
1309
|
update(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
787
1310
|
delete(visitorId: string): Promise<void>;
|
|
788
1311
|
get(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
789
|
-
list(filters: ConsentFilters): Promise<
|
|
1312
|
+
list(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
790
1313
|
}
|
|
791
1314
|
|
|
792
1315
|
declare class VisitorRepo {
|
|
@@ -822,7 +1345,25 @@ declare class ConsentService {
|
|
|
822
1345
|
private pluginEngine?;
|
|
823
1346
|
private eventBus?;
|
|
824
1347
|
private tcfConfig?;
|
|
825
|
-
|
|
1348
|
+
private storage?;
|
|
1349
|
+
private dataSigningHash?;
|
|
1350
|
+
private gppConfig?;
|
|
1351
|
+
constructor(consents: ConsentRepo, visitors: VisitorRepo, profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, compliance?: ComplianceConfig | undefined, pluginEngine?: PluginEngine | undefined, eventBus?: EventEmitter | undefined, tcfConfig?: TcfConfig | undefined, storage?: StorageAdapter | undefined, dataSigningHash?: string | undefined, gppConfig?: GppConfig | undefined);
|
|
1352
|
+
private sign;
|
|
1353
|
+
/** Fails closed: no `storage` to check against means unconfirmed, never assumed confirmed. */
|
|
1354
|
+
private isTcfConfirmed;
|
|
1355
|
+
/** Same fail-closed shape as isTcfConfirmed() — see registration-confirmation.ts. */
|
|
1356
|
+
private isGppConfirmed;
|
|
1357
|
+
/**
|
|
1358
|
+
* Profiles created via the dashboard wizard reference a consent template (`consentTemplateId`)
|
|
1359
|
+
* rather than embedding `cookies`/`preferenceModal.categories` directly on `profileJson` — those
|
|
1360
|
+
* fields are only populated for the embedded/seeded default profiles (see
|
|
1361
|
+
* `ProfileService.seedDefaultProfile`). Cookie/category membership and `legalBasis` are
|
|
1362
|
+
* locale-independent, so the raw consent template's data is sufficient here — no need to
|
|
1363
|
+
* resolve per-locale text via `ProfileService.getResolved()`. Mirrors the same fallback
|
|
1364
|
+
* `ProfileService.getResolved()` and the admin route validators already apply.
|
|
1365
|
+
*/
|
|
1366
|
+
private resolveTemplateData;
|
|
826
1367
|
create(input: Omit<CreateConsentInput, 'tenantId'>): Promise<ConsentDbRecord>;
|
|
827
1368
|
update(visitorId: string, input: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
828
1369
|
get(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
@@ -834,7 +1375,8 @@ declare class VisitorService {
|
|
|
834
1375
|
private visitors;
|
|
835
1376
|
private tenantId;
|
|
836
1377
|
private eventBus?;
|
|
837
|
-
|
|
1378
|
+
private dataSigningHash;
|
|
1379
|
+
constructor(visitors: VisitorRepo, tenantId?: string, eventBus?: EventEmitter | undefined, dataSigningHash?: string);
|
|
838
1380
|
upsert(data: {
|
|
839
1381
|
visitorId: string;
|
|
840
1382
|
ip?: string;
|
|
@@ -904,19 +1446,23 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
904
1446
|
getProfiles(tenantId: string): Promise<Profile[]>;
|
|
905
1447
|
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
906
1448
|
private mapConsent;
|
|
1449
|
+
private static readonly CONSENT_SUMMARY_COLS;
|
|
1450
|
+
private mapConsentSummary;
|
|
907
1451
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
908
1452
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
909
1453
|
deleteConsent(visitorId: string): Promise<void>;
|
|
910
1454
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
911
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1455
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
912
1456
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
913
1457
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1458
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1459
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
914
1460
|
private mapVisitor;
|
|
915
1461
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
916
1462
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
917
1463
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
918
1464
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
919
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1465
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
920
1466
|
private mapUser;
|
|
921
1467
|
createUser(data: CreateUserInput): Promise<AdminUser>;
|
|
922
1468
|
updateUser(id: string, data: UpdateUserInput): Promise<AdminUser>;
|
|
@@ -937,7 +1483,10 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
937
1483
|
assignRole(userId: string, roleId: string, tenantId?: string): Promise<void>;
|
|
938
1484
|
revokeRole(userId: string, roleId: string, tenantId?: string): Promise<void>;
|
|
939
1485
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
940
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1486
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1487
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
1488
|
+
private static readonly AUDIT_SUMMARY_COLS;
|
|
1489
|
+
private mapAuditSummary;
|
|
941
1490
|
private mapAuditLog;
|
|
942
1491
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
943
1492
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
@@ -949,19 +1498,23 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
949
1498
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
950
1499
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
951
1500
|
deleteTenant(id: string): Promise<void>;
|
|
1501
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1502
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
952
1503
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
953
1504
|
private mapApiKey;
|
|
954
1505
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
955
1506
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
956
1507
|
revokeApiKey(id: string): Promise<void>;
|
|
1508
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1509
|
+
deleteApiKey(id: string): Promise<void>;
|
|
957
1510
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
958
|
-
private
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1511
|
+
private mapConsentTemplate;
|
|
1512
|
+
createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
1513
|
+
updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
1514
|
+
deleteConsentTemplate(id: string): Promise<void>;
|
|
1515
|
+
getConsentTemplate(id: string): Promise<ServerConsentTemplate | null>;
|
|
1516
|
+
getConsentTemplates(tenantId: string): Promise<ServerConsentTemplate[]>;
|
|
1517
|
+
copyConsentTemplate(id: string, newName: string): Promise<ServerConsentTemplate>;
|
|
965
1518
|
private mapUITemplate;
|
|
966
1519
|
createUITemplate(data: CreateUITemplateInput): Promise<ServerUITemplate>;
|
|
967
1520
|
updateUITemplate(id: string, data: UpdateUITemplateInput): Promise<ServerUITemplate>;
|
|
@@ -972,7 +1525,7 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
972
1525
|
private mapProfileSummary;
|
|
973
1526
|
private readonly PROFILE_SUMMARY_SQL;
|
|
974
1527
|
listProfilesSummary(tenantId: string): Promise<ProfileSummary[]>;
|
|
975
|
-
|
|
1528
|
+
findProfilesUsingConsentTemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
976
1529
|
findProfilesUsingUITemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
977
1530
|
getOptInStats(tenantId: string, filters: OptInFilters): Promise<OptInStats>;
|
|
978
1531
|
}
|
|
@@ -998,16 +1551,18 @@ declare class MongoDBAdapter implements StorageAdapter {
|
|
|
998
1551
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
999
1552
|
deleteConsent(visitorId: string): Promise<void>;
|
|
1000
1553
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
1001
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1554
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
1002
1555
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
1003
1556
|
private buildConsentFilter;
|
|
1004
1557
|
private writeHistory;
|
|
1005
1558
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1559
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1560
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
1006
1561
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
1007
1562
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
1008
1563
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
1009
1564
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
1010
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1565
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
1011
1566
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
1012
1567
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
1013
1568
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -1030,26 +1585,32 @@ declare class MongoDBAdapter implements StorageAdapter {
|
|
|
1030
1585
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
1031
1586
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
1032
1587
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
1033
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1588
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1589
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
1034
1590
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
1035
1591
|
private buildAuditFilter;
|
|
1036
1592
|
getCountries(tenantId: string): Promise<CountryStat[]>;
|
|
1037
1593
|
getGpcStats(tenantId: string): Promise<GpcStats>;
|
|
1038
1594
|
getTenants(): Promise<Tenant[]>;
|
|
1595
|
+
private mapApiKey;
|
|
1039
1596
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
1040
1597
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
1041
1598
|
revokeApiKey(id: string): Promise<void>;
|
|
1599
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1600
|
+
deleteApiKey(id: string): Promise<void>;
|
|
1042
1601
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
1043
1602
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
1044
1603
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
1045
1604
|
deleteTenant(id: string): Promise<void>;
|
|
1605
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1606
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1046
1607
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1608
|
+
createConsentTemplate(): Promise<never>;
|
|
1609
|
+
updateConsentTemplate(): Promise<never>;
|
|
1610
|
+
deleteConsentTemplate(): Promise<void>;
|
|
1611
|
+
getConsentTemplate(): Promise<never>;
|
|
1612
|
+
getConsentTemplates(): Promise<never>;
|
|
1613
|
+
copyConsentTemplate(): Promise<never>;
|
|
1053
1614
|
createUITemplate(): Promise<never>;
|
|
1054
1615
|
updateUITemplate(): Promise<never>;
|
|
1055
1616
|
deleteUITemplate(): Promise<void>;
|
|
@@ -1057,7 +1618,7 @@ declare class MongoDBAdapter implements StorageAdapter {
|
|
|
1057
1618
|
getUITemplates(): Promise<never>;
|
|
1058
1619
|
copyUITemplate(): Promise<never>;
|
|
1059
1620
|
listProfilesSummary(): Promise<never[]>;
|
|
1060
|
-
|
|
1621
|
+
findProfilesUsingConsentTemplate(): Promise<never[]>;
|
|
1061
1622
|
findProfilesUsingUITemplate(): Promise<never[]>;
|
|
1062
1623
|
getOptInStats(): Promise<never>;
|
|
1063
1624
|
}
|
|
@@ -1082,16 +1643,18 @@ declare class MySQLAdapter implements StorageAdapter {
|
|
|
1082
1643
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
1083
1644
|
deleteConsent(visitorId: string): Promise<void>;
|
|
1084
1645
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
1085
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1646
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
1086
1647
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
1087
|
-
private
|
|
1648
|
+
private buildConsentWhere;
|
|
1088
1649
|
private writeHistory;
|
|
1089
1650
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1651
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1652
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
1090
1653
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
1091
1654
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
1092
1655
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
1093
1656
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
1094
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1657
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
1095
1658
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
1096
1659
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
1097
1660
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -1114,26 +1677,32 @@ declare class MySQLAdapter implements StorageAdapter {
|
|
|
1114
1677
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
1115
1678
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
1116
1679
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
1117
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1680
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1681
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
1118
1682
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
1119
|
-
private
|
|
1683
|
+
private buildAuditWhere;
|
|
1120
1684
|
getCountries(tenantId: string): Promise<CountryStat[]>;
|
|
1121
1685
|
getGpcStats(tenantId: string): Promise<GpcStats>;
|
|
1122
1686
|
getTenants(): Promise<Tenant[]>;
|
|
1687
|
+
private mapApiKey;
|
|
1123
1688
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
1124
1689
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
1125
1690
|
revokeApiKey(id: string): Promise<void>;
|
|
1691
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1692
|
+
deleteApiKey(id: string): Promise<void>;
|
|
1126
1693
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
1127
1694
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
1128
1695
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
1696
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1697
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1129
1698
|
deleteTenant(id: string): Promise<void>;
|
|
1130
1699
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1700
|
+
createConsentTemplate(): Promise<never>;
|
|
1701
|
+
updateConsentTemplate(): Promise<never>;
|
|
1702
|
+
deleteConsentTemplate(): Promise<void>;
|
|
1703
|
+
getConsentTemplate(): Promise<never>;
|
|
1704
|
+
getConsentTemplates(): Promise<never>;
|
|
1705
|
+
copyConsentTemplate(): Promise<never>;
|
|
1137
1706
|
createUITemplate(): Promise<never>;
|
|
1138
1707
|
updateUITemplate(): Promise<never>;
|
|
1139
1708
|
deleteUITemplate(): Promise<void>;
|
|
@@ -1141,7 +1710,7 @@ declare class MySQLAdapter implements StorageAdapter {
|
|
|
1141
1710
|
getUITemplates(): Promise<never>;
|
|
1142
1711
|
copyUITemplate(): Promise<never>;
|
|
1143
1712
|
listProfilesSummary(): Promise<never[]>;
|
|
1144
|
-
|
|
1713
|
+
findProfilesUsingConsentTemplate(): Promise<never[]>;
|
|
1145
1714
|
findProfilesUsingUITemplate(): Promise<never[]>;
|
|
1146
1715
|
getOptInStats(): Promise<never>;
|
|
1147
1716
|
}
|
|
@@ -1165,16 +1734,18 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
1165
1734
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
1166
1735
|
deleteConsent(visitorId: string): Promise<void>;
|
|
1167
1736
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
1168
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1737
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
1169
1738
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
1170
|
-
private
|
|
1739
|
+
private buildConsentWhere;
|
|
1171
1740
|
private writeHistory;
|
|
1172
1741
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1742
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1743
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
1173
1744
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
1174
1745
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
1175
1746
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
1176
1747
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
1177
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1748
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
1178
1749
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
1179
1750
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
1180
1751
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -1197,26 +1768,32 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
1197
1768
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
1198
1769
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
1199
1770
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
1200
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1771
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1772
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
1201
1773
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
1202
|
-
private
|
|
1774
|
+
private buildAuditWhere;
|
|
1203
1775
|
getCountries(tenantId: string): Promise<CountryStat[]>;
|
|
1204
1776
|
getGpcStats(tenantId: string): Promise<GpcStats>;
|
|
1205
1777
|
getTenants(): Promise<Tenant[]>;
|
|
1778
|
+
private mapApiKey;
|
|
1206
1779
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
1207
1780
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
1208
1781
|
revokeApiKey(id: string): Promise<void>;
|
|
1782
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1783
|
+
deleteApiKey(id: string): Promise<void>;
|
|
1209
1784
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
1210
1785
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
1211
1786
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
1212
1787
|
deleteTenant(id: string): Promise<void>;
|
|
1788
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1789
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1213
1790
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1791
|
+
createConsentTemplate(): Promise<never>;
|
|
1792
|
+
updateConsentTemplate(): Promise<never>;
|
|
1793
|
+
deleteConsentTemplate(): Promise<void>;
|
|
1794
|
+
getConsentTemplate(): Promise<never>;
|
|
1795
|
+
getConsentTemplates(): Promise<never>;
|
|
1796
|
+
copyConsentTemplate(): Promise<never>;
|
|
1220
1797
|
createUITemplate(): Promise<never>;
|
|
1221
1798
|
updateUITemplate(): Promise<never>;
|
|
1222
1799
|
deleteUITemplate(): Promise<void>;
|
|
@@ -1224,11 +1801,131 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
1224
1801
|
getUITemplates(): Promise<never>;
|
|
1225
1802
|
copyUITemplate(): Promise<never>;
|
|
1226
1803
|
listProfilesSummary(): Promise<never[]>;
|
|
1227
|
-
|
|
1804
|
+
findProfilesUsingConsentTemplate(): Promise<never[]>;
|
|
1228
1805
|
findProfilesUsingUITemplate(): Promise<never[]>;
|
|
1229
1806
|
getOptInStats(): Promise<never>;
|
|
1230
1807
|
}
|
|
1231
1808
|
|
|
1809
|
+
/**
|
|
1810
|
+
* ConsentAction — server-side counterpart to `@consenti/ui`'s `ConsentAction`.
|
|
1811
|
+
*
|
|
1812
|
+
* Watches a single consent parameter across *every* visitor's submission (not just one
|
|
1813
|
+
* browser's own state) and fires `onGrant`/`onDeny` when that parameter's status actually
|
|
1814
|
+
* changes — via the same `eventBus` `createConsenti()` already emits `consent:created`/
|
|
1815
|
+
* `consent:updated` on. Useful for server-side integrations (CRM sync, suppression lists,
|
|
1816
|
+
* downstream webhooks) that need to react to consent decisions without polling the database.
|
|
1817
|
+
*
|
|
1818
|
+
* There is no server equivalent of the widget's `ConsentScript` — injecting a `<script>` tag
|
|
1819
|
+
* only makes sense in a browser DOM the server doesn't have. `services.consent.create()`/
|
|
1820
|
+
* `.update()` are already the server-side "submit"/"update consent" methods this hook reacts to.
|
|
1821
|
+
*
|
|
1822
|
+
* @example
|
|
1823
|
+
* ```ts
|
|
1824
|
+
* import { createConsenti, ConsentAction } from '@consenti/api'
|
|
1825
|
+
*
|
|
1826
|
+
* const { eventBus } = createConsenti({ ... })
|
|
1827
|
+
*
|
|
1828
|
+
* new ConsentAction({
|
|
1829
|
+
* id: 'analytics_storage',
|
|
1830
|
+
* eventBus,
|
|
1831
|
+
* onGrant: ({ visitorId }) => crm.optIn(visitorId),
|
|
1832
|
+
* onDeny: ({ visitorId }) => crm.optOut(visitorId),
|
|
1833
|
+
* })
|
|
1834
|
+
* ```
|
|
1835
|
+
*/
|
|
1836
|
+
|
|
1837
|
+
/** Passed to `onGrant`/`onDeny` — the transitioning parameter's status plus the full consent record. */
|
|
1838
|
+
interface ConsentActionParams {
|
|
1839
|
+
visitorId: string;
|
|
1840
|
+
cookieId: string;
|
|
1841
|
+
status: ConsentStatus;
|
|
1842
|
+
record: ConsentDbRecord;
|
|
1843
|
+
}
|
|
1844
|
+
/** Options for constructing a {@link ConsentAction} instance. */
|
|
1845
|
+
interface ConsentActionOptions {
|
|
1846
|
+
/** The consent parameter ID to watch (e.g. `'analytics_storage'`). */
|
|
1847
|
+
id: string;
|
|
1848
|
+
/** The `eventBus` returned by `createConsenti()`. */
|
|
1849
|
+
eventBus: EventEmitter;
|
|
1850
|
+
/** Called when the parameter's status transitions to `'granted'`, for any visitor. */
|
|
1851
|
+
onGrant?: (params: ConsentActionParams) => void;
|
|
1852
|
+
/** Called when the parameter's status transitions away from `'granted'` (`'denied'` or `'objected'`). */
|
|
1853
|
+
onDeny?: (params: ConsentActionParams) => void;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Watches a single consent parameter across every incoming `consent:created`/`consent:updated`
|
|
1857
|
+
* event and fires `onGrant`/`onDeny` on transitions. Call {@link ConsentAction.destroy} to
|
|
1858
|
+
* remove the event listeners when the hook is no longer needed.
|
|
1859
|
+
*/
|
|
1860
|
+
declare class ConsentAction {
|
|
1861
|
+
private readonly options;
|
|
1862
|
+
private readonly onCreated;
|
|
1863
|
+
private readonly onUpdated;
|
|
1864
|
+
constructor(options: ConsentActionOptions);
|
|
1865
|
+
private fire;
|
|
1866
|
+
/** Removes the event listeners. Call when the hook is no longer needed. */
|
|
1867
|
+
destroy(): void;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* CategoryAction — server-side counterpart to `@consenti/ui`'s `CategoryAction`.
|
|
1872
|
+
*
|
|
1873
|
+
* Same idea as {@link ConsentAction} but watches a whole category's rollup consent state
|
|
1874
|
+
* (granted only when *every* cookie in the category is granted) instead of one parameter —
|
|
1875
|
+
* matching the preference modal's tri-state rollup rule and the widget's `CategoryAction`.
|
|
1876
|
+
*
|
|
1877
|
+
* @example
|
|
1878
|
+
* ```ts
|
|
1879
|
+
* import { createConsenti, CategoryAction } from '@consenti/api'
|
|
1880
|
+
*
|
|
1881
|
+
* const { eventBus, services } = createConsenti({ ... })
|
|
1882
|
+
*
|
|
1883
|
+
* new CategoryAction({
|
|
1884
|
+
* categoryId: 'marketing',
|
|
1885
|
+
* eventBus,
|
|
1886
|
+
* profiles: services.profile,
|
|
1887
|
+
* onGrant: ({ visitorId }) => adsPlatform.optIn(visitorId),
|
|
1888
|
+
* onDeny: ({ visitorId }) => adsPlatform.optOut(visitorId),
|
|
1889
|
+
* })
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1892
|
+
|
|
1893
|
+
/** Passed to `onGrant`/`onDeny` — the category's rollup status plus the full consent record. */
|
|
1894
|
+
interface CategoryActionParams {
|
|
1895
|
+
visitorId: string;
|
|
1896
|
+
categoryId: string;
|
|
1897
|
+
status: ConsentStatus;
|
|
1898
|
+
record: ConsentDbRecord;
|
|
1899
|
+
}
|
|
1900
|
+
/** Options for constructing a {@link CategoryAction} instance. */
|
|
1901
|
+
interface CategoryActionOptions {
|
|
1902
|
+
/** The category ID to watch (e.g. `'marketing'`). */
|
|
1903
|
+
categoryId: string;
|
|
1904
|
+
/** The `eventBus` returned by `createConsenti()`. */
|
|
1905
|
+
eventBus: EventEmitter;
|
|
1906
|
+
/** The `services.profile` returned by `createConsenti()` — resolves which cookies belong to
|
|
1907
|
+
* this category for the record's profile (categories are defined on the consent template). */
|
|
1908
|
+
profiles: ProfileService;
|
|
1909
|
+
/** Called when every cookie in the category transitions to `'granted'`. */
|
|
1910
|
+
onGrant?: (params: CategoryActionParams) => void;
|
|
1911
|
+
/** Called when the category transitions away from fully granted. */
|
|
1912
|
+
onDeny?: (params: CategoryActionParams) => void;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Watches a category's rollup consent status across every incoming `consent:created`/
|
|
1916
|
+
* `consent:updated` event and fires `onGrant`/`onDeny` on transitions. Call
|
|
1917
|
+
* {@link CategoryAction.destroy} to remove the event listeners when no longer needed.
|
|
1918
|
+
*/
|
|
1919
|
+
declare class CategoryAction {
|
|
1920
|
+
private readonly options;
|
|
1921
|
+
private readonly onCreated;
|
|
1922
|
+
private readonly onUpdated;
|
|
1923
|
+
constructor(options: CategoryActionOptions);
|
|
1924
|
+
private fire;
|
|
1925
|
+
/** Removes the event listeners. Call when the hook is no longer needed. */
|
|
1926
|
+
destroy(): void;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1232
1929
|
declare const dashboardDir: string;
|
|
1233
1930
|
declare function createConsenti(userConfig: ConsentiServerConfig): {
|
|
1234
1931
|
router: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
|
|
@@ -1256,4 +1953,4 @@ declare function createConsenti(userConfig: ConsentiServerConfig): {
|
|
|
1256
1953
|
destroy: () => Promise<void>;
|
|
1257
1954
|
};
|
|
1258
1955
|
|
|
1259
|
-
export { type AdminUser, type AuditLog, type BrandingConfig, type ConsentDbRecord, type ConsentVerifyResult, type ConsentiServerConfig, MongoDBAdapter, MySQLAdapter, type Permission, PostgreSQLAdapter, type Profile, type Role, BetterSqlite3Adapter as SQLiteAdapter, type SafeAdminUser, type StorageAdapter, type Visitor, createConsenti, dashboardDir };
|
|
1956
|
+
export { type AdminUser, type AuditLog, type BrandingConfig, CategoryAction, type CategoryActionOptions, type CategoryActionParams, ConsentAction, type ConsentActionOptions, type ConsentActionParams, type ConsentDbRecord, type ConsentVerifyResult, type ConsentiServerConfig, MongoDBAdapter, MySQLAdapter, type Permission, PostgreSQLAdapter, type Profile, type Role, BetterSqlite3Adapter as SQLiteAdapter, type SafeAdminUser, type StorageAdapter, type Visitor, createConsenti, dashboardDir };
|