@consenti/api 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +917 -207
- package/dist/chunk-IY4L6MNB.js +2 -0
- package/dist/chunk-IY4L6MNB.js.map +1 -0
- package/dist/chunk-O5UG4OYI.js +2 -0
- package/dist/chunk-O5UG4OYI.js.map +1 -0
- package/dist/dashboard/assets/ApiConfig-CE-Noelz.js +1 -0
- package/dist/dashboard/assets/ArchivedProfileList-Bw-fx-BD.js +1 -0
- package/dist/dashboard/assets/AuditLog-D6HiTQ1o.js +1 -0
- package/dist/dashboard/assets/ChangePassword-D2rPkzD1.js +1 -0
- package/dist/dashboard/assets/ConfirmDialog-DTtw7VJ-.js +1 -0
- package/dist/dashboard/assets/ConsentList-BWOvPOMG.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateEditor-BKIEnLmA.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateList-DCys2nM3.js +1 -0
- package/dist/dashboard/assets/HeadingTagInput-BTjrdzlS.js +1 -0
- package/dist/dashboard/assets/HowItWorks-AeyjfQGO.js +1 -0
- package/dist/dashboard/assets/PermissionGate-Bhhf7SMh.js +1 -0
- package/dist/dashboard/assets/PreviewPane-Dq5K1h2L.js +35 -0
- package/dist/dashboard/assets/ProfileEditor-BXtaE0tL.js +2 -0
- package/dist/dashboard/assets/ProfileList-CWKSAgu0.js +1 -0
- package/dist/dashboard/assets/ProfileVersionHistory-1h8sUgog.js +3 -0
- package/dist/dashboard/assets/RecordDetailModal-FaFhzQXi.js +1 -0
- package/dist/dashboard/assets/Reports-DWEjQOKh.js +1 -0
- package/dist/dashboard/assets/RoleList-BSGoqlzp.js +1 -0
- package/dist/dashboard/assets/Select-DjJNwlbY.js +1 -0
- package/dist/dashboard/assets/Settings-CVYrlyLm.js +1 -0
- package/dist/dashboard/assets/SetupWizard-4NTb5bZj.js +1 -0
- package/dist/dashboard/assets/Table-DH_jh8_N.js +1 -0
- package/dist/dashboard/assets/TenantList-5AwFtoHf.js +1 -0
- package/dist/dashboard/assets/UITemplateEditor-9A55scBe.js +1 -0
- package/dist/dashboard/assets/UITemplateList-BS7X4u4F.js +1 -0
- package/dist/dashboard/assets/UserList-SPkMyFRy.js +1 -0
- package/dist/dashboard/assets/VendorList-C5fPhr6U.js +1 -0
- package/dist/dashboard/assets/VisitorList-COP1SyNd.js +1 -0
- package/dist/dashboard/assets/check-o5cp1s2J.js +1 -0
- package/dist/dashboard/assets/client-ymc9nwWr.js +1 -0
- package/dist/dashboard/assets/copy-C01KoUUH.js +1 -0
- package/dist/dashboard/assets/createLucideIcon-MdjI3m3u.js +1 -0
- package/dist/dashboard/assets/de-DET92Jb5.js +5 -0
- package/dist/dashboard/assets/debounce-DlvAAT2V.js +1 -0
- package/dist/dashboard/assets/dist-DlhLroYC.js +1 -0
- package/dist/dashboard/assets/es-CJNunrnU.js +5 -0
- package/dist/dashboard/assets/external-link-DVe00ZBK.js +1 -0
- package/dist/dashboard/assets/fr-akfTuf5x.js +5 -0
- package/dist/dashboard/assets/history-CHTts_m2.js +1 -0
- package/dist/dashboard/assets/index-DW3qOrry.css +2 -0
- package/dist/dashboard/assets/index-H_Zm0X-u.js +6 -0
- package/dist/dashboard/assets/ja-B0nLWkOd.js +5 -0
- package/dist/dashboard/assets/profiles-DmrdGv7t.js +1 -0
- package/dist/dashboard/assets/roles-C0dJPFl4.js +1 -0
- package/dist/dashboard/assets/search-Bd2RdDRh.js +1 -0
- package/dist/dashboard/assets/templates-B2xiivh4.js +1 -0
- package/dist/dashboard/assets/templates-CnMoLavN.js +1 -0
- package/dist/dashboard/assets/trash-2-BBnkdgm_.js +1 -0
- package/dist/dashboard/assets/trash-Dj1jhzHJ.js +1 -0
- package/dist/dashboard/assets/useHashQuery-B5ytMIMp.js +1 -0
- package/dist/dashboard/assets/users-czK8LRVh.js +1 -0
- package/dist/dashboard/assets/x-BRuyBEQP.js +1 -0
- package/dist/dashboard/assets/zh-CN-Dxg3G-BL.js +5 -0
- package/dist/dashboard/index.html +4 -2
- package/dist/dist-Z5RVTVBT.js +2 -0
- package/dist/dist-Z5RVTVBT.js.map +1 -0
- package/dist/index.cjs +129 -9089
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +963 -254
- package/dist/index.d.ts +963 -254
- package/dist/index.js +129 -9043
- package/dist/index.js.map +1 -1
- package/dist/profiles-XTYPWLVC.js +2 -0
- package/dist/profiles-XTYPWLVC.js.map +1 -0
- package/dist/widget/widget.css +1 -0
- package/dist/widget/widget.js +1 -2304
- package/package.json +19 -12
- package/dist/dashboard/assets/index-BBbhHyc_.js +0 -2637
- package/dist/dashboard/assets/index-DYK73WL5.css +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,205 +1,71 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
type?: 'consent' | 'legitimate_interest';
|
|
43
|
-
legitimateInterest?: {
|
|
44
|
-
enabled?: boolean;
|
|
45
|
-
description?: string;
|
|
46
|
-
};
|
|
47
|
-
cookies: string[];
|
|
48
|
-
}
|
|
49
|
-
interface PreferenceModal {
|
|
50
|
-
position?: 'left' | 'right' | 'center';
|
|
51
|
-
overlayOpacity?: number;
|
|
52
|
-
showClose?: boolean;
|
|
53
|
-
showLocaleSwitcher?: boolean;
|
|
54
|
-
heading?: string;
|
|
55
|
-
headingTag?: string;
|
|
56
|
-
subheading?: string;
|
|
57
|
-
htmlText?: string;
|
|
58
|
-
buttons: Button[];
|
|
59
|
-
categories: Category[];
|
|
60
|
-
persistent?: boolean;
|
|
61
|
-
}
|
|
62
|
-
interface LocaleTranslations {
|
|
63
|
-
mainBanner: MainBanner;
|
|
64
|
-
gpcBanner?: GpcBanner;
|
|
65
|
-
preferenceModal: PreferenceModal;
|
|
66
|
-
}
|
|
67
|
-
interface ProfileTranslations {
|
|
68
|
-
[locale: string]: LocaleTranslations;
|
|
69
|
-
}
|
|
70
|
-
interface DpdpaConfig {
|
|
71
|
-
dataFiduciary: string;
|
|
72
|
-
grievanceEmail: string;
|
|
73
|
-
purposeDescription?: string;
|
|
74
|
-
}
|
|
75
|
-
interface LocaleTextContent {
|
|
76
|
-
mainBanner: {
|
|
77
|
-
heading: string;
|
|
78
|
-
htmlText: string;
|
|
79
|
-
buttonLabels: string[];
|
|
80
|
-
};
|
|
81
|
-
gpcBanner: {
|
|
82
|
-
heading: string;
|
|
83
|
-
htmlText: string;
|
|
84
|
-
buttonLabels: string[];
|
|
85
|
-
};
|
|
86
|
-
preferenceModal: {
|
|
87
|
-
heading: string;
|
|
88
|
-
subheading?: string;
|
|
89
|
-
htmlText?: string;
|
|
90
|
-
buttonLabels: string[];
|
|
91
|
-
categories: Array<{
|
|
92
|
-
id: string;
|
|
93
|
-
heading: string;
|
|
94
|
-
htmlText: string;
|
|
95
|
-
}>;
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
interface ProfileConfig {
|
|
99
|
-
cookies?: Cookie[];
|
|
100
|
-
defaultLocale: string;
|
|
101
|
-
translations?: ProfileTranslations;
|
|
102
|
-
gpcBanner?: boolean;
|
|
103
|
-
darkMode?: boolean;
|
|
104
|
-
allowedOrigins?: string[];
|
|
105
|
-
dpdpa?: DpdpaConfig;
|
|
106
|
-
regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
|
|
107
|
-
regulations?: string[];
|
|
108
|
-
cookieTemplateId?: string;
|
|
109
|
-
uiTemplateId?: string;
|
|
110
|
-
localeContents?: Record<string, LocaleTextContent>;
|
|
111
|
-
}
|
|
112
|
-
interface UITemplateButton {
|
|
113
|
-
text: string;
|
|
114
|
-
type: string;
|
|
115
|
-
action: 'submit' | 'custom' | 'manage' | 'close' | 'link';
|
|
116
|
-
cookies?: string | string[];
|
|
117
|
-
url?: string;
|
|
118
|
-
}
|
|
119
|
-
interface UITemplateBanner {
|
|
120
|
-
position: string;
|
|
121
|
-
overlayOpacity: number;
|
|
122
|
-
showClose: boolean;
|
|
123
|
-
showLocaleSwitcher?: boolean;
|
|
124
|
-
headingTag: string;
|
|
125
|
-
buttons: UITemplateButton[];
|
|
126
|
-
}
|
|
127
|
-
interface UITemplateCategory {
|
|
128
|
-
id: string;
|
|
129
|
-
headingTag: string;
|
|
130
|
-
mandatory: boolean;
|
|
131
|
-
type: 'consent' | 'legitimate_interest';
|
|
132
|
-
liEnabled: boolean;
|
|
133
|
-
cookies: string[];
|
|
134
|
-
}
|
|
135
|
-
interface UITemplateModal extends UITemplateBanner {
|
|
136
|
-
hasSubheading: boolean;
|
|
137
|
-
persistent: boolean;
|
|
138
|
-
showLocaleSwitcher?: boolean;
|
|
139
|
-
categories: UITemplateCategory[];
|
|
140
|
-
}
|
|
141
|
-
interface ServerUITemplate {
|
|
142
|
-
id: string;
|
|
143
|
-
tenantId: string;
|
|
144
|
-
name: string;
|
|
145
|
-
mainBanner: UITemplateBanner;
|
|
146
|
-
gpcBanner: UITemplateBanner;
|
|
147
|
-
preferenceModal: UITemplateModal;
|
|
148
|
-
createdAt: string;
|
|
149
|
-
updatedAt: string;
|
|
150
|
-
}
|
|
151
|
-
interface ServerCookieTemplate {
|
|
152
|
-
id: string;
|
|
153
|
-
tenantId: string;
|
|
154
|
-
name: string;
|
|
155
|
-
cookies: Cookie[];
|
|
156
|
-
createdAt: string;
|
|
157
|
-
updatedAt: string;
|
|
158
|
-
}
|
|
159
|
-
interface CreateCookieTemplateInput {
|
|
160
|
-
tenantId: string;
|
|
161
|
-
name: string;
|
|
162
|
-
cookies: Cookie[];
|
|
163
|
-
}
|
|
164
|
-
interface UpdateCookieTemplateInput {
|
|
165
|
-
name?: string;
|
|
166
|
-
cookies?: Cookie[];
|
|
167
|
-
}
|
|
168
|
-
interface CreateUITemplateInput {
|
|
169
|
-
tenantId: string;
|
|
170
|
-
name: string;
|
|
171
|
-
mainBanner: UITemplateBanner;
|
|
172
|
-
gpcBanner: UITemplateBanner;
|
|
173
|
-
preferenceModal: UITemplateModal;
|
|
174
|
-
}
|
|
175
|
-
interface UpdateUITemplateInput {
|
|
176
|
-
name?: string;
|
|
177
|
-
mainBanner?: UITemplateBanner;
|
|
178
|
-
gpcBanner?: UITemplateBanner;
|
|
179
|
-
preferenceModal?: UITemplateModal;
|
|
180
|
-
}
|
|
181
|
-
interface PublicProfileResponse {
|
|
182
|
-
id: string;
|
|
183
|
-
version: number;
|
|
184
|
-
defaultLocale: string;
|
|
185
|
-
currentLocale: string;
|
|
186
|
-
locales: string[];
|
|
187
|
-
cookies: Cookie[];
|
|
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
|
+
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"];
|
|
17
|
+
declare const COOKIE_PURPOSE_IDS: readonly ["necessary", "functional", "preferences", "analytics", "marketing"];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compliance-related literal types, derived from the regulation/group ID data
|
|
21
|
+
* that lives in @consenti/utils (packages/utils/src/compliance.ts).
|
|
22
|
+
*
|
|
23
|
+
* This import is type-only and fully erased at compile time — @consenti/types
|
|
24
|
+
* has no runtime dependency on @consenti/utils. The actual data (COMPLIANCE_GROUPS,
|
|
25
|
+
* EMBEDDED_COMPLIANCE_MAP, COMPLIANCE_VALIDATION_RULES, GPC_OPTIONS, etc.) lives in
|
|
26
|
+
* @consenti/utils; import it from there.
|
|
27
|
+
*
|
|
28
|
+
* HOW TO ADD A NEW REGULATION OR COMPLIANCE GROUP: see the header comment in
|
|
29
|
+
* packages/utils/src/compliance.ts — these types auto-update from that data.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
|
|
33
|
+
|
|
34
|
+
/** Resolved content for one non-default locale — template shape merged with that locale's
|
|
35
|
+
* author text, computed client-side (dashboard) the same way the default locale's content on
|
|
36
|
+
* `StoredProfileJson` is. Never persisted in the DB row; `ProfileService` writes it straight to
|
|
37
|
+
* that locale's on-disk version file. Keyed by locale code on `CreateProfileInput`/
|
|
38
|
+
* `UpdateProfileInput.localeContent` — only locales actually touched this save need an entry;
|
|
39
|
+
* any other locale already in `profileJson.locales` is carried forward unchanged from the
|
|
40
|
+
* previous version. */
|
|
41
|
+
interface LocaleContentInput {
|
|
188
42
|
mainBanner: MainBanner;
|
|
189
43
|
gpcBanner?: GpcBanner;
|
|
190
44
|
preferenceModal: PreferenceModal;
|
|
191
45
|
}
|
|
192
|
-
|
|
193
46
|
interface StorageConfig {
|
|
194
47
|
driver: 'sqlite' | 'better-sqlite3' | 'node-sqlite3-wasm' | 'node:sqlite' | 'mongodb' | 'mysql' | 'postgresql' | 'json';
|
|
195
48
|
path?: string;
|
|
196
49
|
uri?: string;
|
|
197
|
-
dbName?: string;
|
|
198
50
|
database?: string;
|
|
199
51
|
host?: string;
|
|
200
52
|
port?: number;
|
|
201
53
|
user?: string;
|
|
202
54
|
password?: string;
|
|
55
|
+
/** Postgres/MySQL only. Max pool connections. Default: 10 (both drivers' own default — set
|
|
56
|
+
* explicitly here so it's a documented, tunable value rather than an implicit library default). */
|
|
57
|
+
poolMax?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Postgres/MySQL only. Milliseconds before an in-flight query is aborted, so one slow/runaway
|
|
60
|
+
* query can't hold a pool connection indefinitely. Unset by default: a forced default could
|
|
61
|
+
* silently break legitimate long-running operations (e.g. exporting millions of records) that
|
|
62
|
+
* this project has no way to load-test — opt in once you've measured your own worst-case query.
|
|
63
|
+
*/
|
|
64
|
+
statementTimeoutMs?: number;
|
|
65
|
+
/** Postgres only. Milliseconds a client may sit idle inside an open transaction before being
|
|
66
|
+
* terminated — protects against a `BEGIN` with no matching `COMMIT`/`ROLLBACK` holding a
|
|
67
|
+
* connection forever. Unset by default, same reasoning as `statementTimeoutMs`. */
|
|
68
|
+
idleInTransactionTimeoutMs?: number;
|
|
203
69
|
}
|
|
204
70
|
interface OidcConfig {
|
|
205
71
|
issuer: string;
|
|
@@ -231,10 +97,112 @@ interface RateLimitConfig {
|
|
|
231
97
|
windowMs?: number;
|
|
232
98
|
maxRequests?: number;
|
|
233
99
|
}
|
|
100
|
+
type GeoResult = {
|
|
101
|
+
country: string | null;
|
|
102
|
+
region: string | null;
|
|
103
|
+
locale: string | null;
|
|
104
|
+
};
|
|
105
|
+
type CountryResolverFn = (ctx: {
|
|
106
|
+
ip: string;
|
|
107
|
+
language: string;
|
|
108
|
+
timezone: string;
|
|
109
|
+
}) => Promise<GeoResult>;
|
|
110
|
+
interface S3ApiConfig {
|
|
111
|
+
enabled: boolean;
|
|
112
|
+
region: string;
|
|
113
|
+
bucketName: string;
|
|
114
|
+
accessKeyId: string;
|
|
115
|
+
secretAccessKey: string;
|
|
116
|
+
sessionToken?: string;
|
|
117
|
+
}
|
|
118
|
+
interface ProfileSummary {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
defaultLocale: string;
|
|
122
|
+
complianceGroup: ComplianceGroupId | null;
|
|
123
|
+
customComplianceGroup: string | null;
|
|
124
|
+
isActive: boolean;
|
|
125
|
+
consentTemplateName: string | null;
|
|
126
|
+
uiTemplateName: string | null;
|
|
127
|
+
createdAt: string;
|
|
128
|
+
updatedAt: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* One snapshot in a profile's edit history. `Profile.id` is stable across edits; each save
|
|
132
|
+
* increments `Profile.version` in place and writes a new snapshot under that version number.
|
|
133
|
+
*/
|
|
134
|
+
interface ProfileVersionEntry {
|
|
135
|
+
version: number;
|
|
136
|
+
createdAt: string;
|
|
137
|
+
locales: string[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A profile-id directory found on disk with no matching DB row — the profile was deleted
|
|
141
|
+
* (or predates the current versioning model) but its version-snapshot history wasn't. Built
|
|
142
|
+
* from a directory listing only (id, version count, last-modified) — no file content is read
|
|
143
|
+
* until a specific version is opened via the normal version-history endpoints.
|
|
144
|
+
*/
|
|
145
|
+
interface ArchivedProfileSummary {
|
|
146
|
+
id: string;
|
|
147
|
+
versionCount: number;
|
|
148
|
+
lastModified: string;
|
|
149
|
+
}
|
|
150
|
+
interface OptInFilters {
|
|
151
|
+
profileId?: string;
|
|
152
|
+
complianceGroup?: string;
|
|
153
|
+
from?: string;
|
|
154
|
+
to?: string;
|
|
155
|
+
locale?: string;
|
|
156
|
+
}
|
|
157
|
+
interface OptInByLocale {
|
|
158
|
+
total: number;
|
|
159
|
+
granted: number;
|
|
160
|
+
denied: number;
|
|
161
|
+
managed: number;
|
|
162
|
+
}
|
|
163
|
+
interface OptInByDate {
|
|
164
|
+
date: string;
|
|
165
|
+
total: number;
|
|
166
|
+
granted: number;
|
|
167
|
+
denied: number;
|
|
168
|
+
managed: number;
|
|
169
|
+
}
|
|
170
|
+
interface OptInStats {
|
|
171
|
+
total: number;
|
|
172
|
+
granted: number;
|
|
173
|
+
denied: number;
|
|
174
|
+
managed: number;
|
|
175
|
+
grantedPct: number;
|
|
176
|
+
deniedPct: number;
|
|
177
|
+
managedPct: number;
|
|
178
|
+
byLocale: Record<string, OptInByLocale>;
|
|
179
|
+
byDate: OptInByDate[];
|
|
180
|
+
}
|
|
181
|
+
type ComplianceMapOverride = 'default' | ComplianceMapData | 'auto';
|
|
182
|
+
interface ComplianceMapData {
|
|
183
|
+
version: string;
|
|
184
|
+
countries: Record<string, CountryComplianceEntry>;
|
|
185
|
+
}
|
|
186
|
+
interface CountryComplianceEntry {
|
|
187
|
+
complianceGroup: ComplianceGroupId;
|
|
188
|
+
default: ComplianceGroupId;
|
|
189
|
+
description: string;
|
|
190
|
+
overriddenRegions?: Record<string, {
|
|
191
|
+
complianceGroup: ComplianceGroupId;
|
|
192
|
+
description: string;
|
|
193
|
+
}>;
|
|
194
|
+
}
|
|
234
195
|
interface ComplianceConfig {
|
|
196
|
+
/** @deprecated use type */
|
|
235
197
|
gdpr?: boolean;
|
|
198
|
+
/** @deprecated use type */
|
|
236
199
|
ccpa?: boolean;
|
|
237
|
-
gpc?: boolean;
|
|
200
|
+
gpc?: boolean | 'strict';
|
|
201
|
+
type?: ComplianceGroupId | 'auto';
|
|
202
|
+
/** @deprecated use 'default' instead of 'language' or 'timezone'; 'geoip' and 'hosted-geoip-lite' removed */
|
|
203
|
+
geoDataProvider?: 'default' | 'maxmind' | 'language' | 'timezone' | 'geoip' | 'hosted-geoip-lite' | CountryResolverFn;
|
|
204
|
+
autoComplianceMap?: ComplianceMapOverride;
|
|
205
|
+
complianceMapUrl?: string;
|
|
238
206
|
}
|
|
239
207
|
interface DashboardConfig {
|
|
240
208
|
enabled?: boolean;
|
|
@@ -255,6 +223,23 @@ interface TcfConfig {
|
|
|
255
223
|
}
|
|
256
224
|
interface DataRetentionConfig {
|
|
257
225
|
purgeAfterDays: number;
|
|
226
|
+
/** When set, audit log entries older than this are purged on the same daily timer as consent
|
|
227
|
+
* record retention. Independent of `purgeAfterDays` since audit trails are often required to
|
|
228
|
+
* be kept longer than the consent records they reference for compliance/investigation
|
|
229
|
+
* purposes. Unset by default — audit logs are kept indefinitely unless explicitly configured. */
|
|
230
|
+
auditLogPurgeAfterDays?: number;
|
|
231
|
+
}
|
|
232
|
+
interface BrandingConfig {
|
|
233
|
+
/** Display name shown in the dashboard (login page, header, browser tab). Default: 'Consenti'. */
|
|
234
|
+
appName?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Logo to display in the dashboard. Accepts a public URL (https://…) or a local
|
|
237
|
+
* filesystem path — when a path is given, the file is copied into the dashboard
|
|
238
|
+
* dist and served as a static asset automatically.
|
|
239
|
+
*/
|
|
240
|
+
appLogoPath?: string;
|
|
241
|
+
/** When true, hides the "Powered by Consenti" badge in the dashboard. Default: false. */
|
|
242
|
+
hidePoweredBy?: boolean;
|
|
258
243
|
}
|
|
259
244
|
interface ConsentiServerConfig {
|
|
260
245
|
basePath?: string;
|
|
@@ -270,6 +255,15 @@ interface ConsentiServerConfig {
|
|
|
270
255
|
dataRetention?: DataRetentionConfig;
|
|
271
256
|
maxBodySize?: number;
|
|
272
257
|
trustedProxies?: string[];
|
|
258
|
+
branding?: BrandingConfig;
|
|
259
|
+
s3Api?: S3ApiConfig;
|
|
260
|
+
/** When set, every consent record is HMAC-SHA256 signed at create/update time (hex-encoded,
|
|
261
|
+
* stored in `signature`) and the signature is checked on `GET /consent/:visitorId/verify`.
|
|
262
|
+
* No-op and no schema burden when unset — opt-in tamper-evidence for server-stored records,
|
|
263
|
+
* independent of the widget's own cookie-signing (`core.cookieSigningKey`). */
|
|
264
|
+
consentSigningKey?: string;
|
|
265
|
+
/** Called after every profile activate/deactivate/delete. isPurge=true means invalidate; false means warm. */
|
|
266
|
+
handleCache?: (paths: string[], profileId: string, isPurge: boolean) => void;
|
|
273
267
|
}
|
|
274
268
|
interface Tenant {
|
|
275
269
|
id: string;
|
|
@@ -278,21 +272,42 @@ interface Tenant {
|
|
|
278
272
|
createdAt: string;
|
|
279
273
|
updatedAt: string;
|
|
280
274
|
}
|
|
275
|
+
/** Tenant-wide dashboard-managed defaults, shown on the API Config page. */
|
|
276
|
+
interface TenantSettings {
|
|
277
|
+
/** Public API origin allowlist. Distinct from `ProfileConfig.allowedOrigins`, which the
|
|
278
|
+
* origin check on `POST /consent` prefers when a profile sets its own list; this is the
|
|
279
|
+
* fallback. The public API has no auth token, so this is its only access gate. */
|
|
280
|
+
allowedOrigins?: string[];
|
|
281
|
+
/** Admin API origin allowlist — an additional CORS-layer check on top of Bearer-token auth
|
|
282
|
+
* for browser-originated `/consenti/admin/*` requests. Empty/unset means no restriction
|
|
283
|
+
* (Bearer token auth alone still applies); only enforced when the request carries an
|
|
284
|
+
* `Origin` header — server-to-server callers are unaffected. */
|
|
285
|
+
adminAllowedOrigins?: string[];
|
|
286
|
+
/** Whether this tenant has completed (or skipped) the first-run setup wizard. Gates the
|
|
287
|
+
* one-time `#/setup` redirect in the dashboard — never reset once true. Default: false. */
|
|
288
|
+
setupCompleted?: boolean;
|
|
289
|
+
}
|
|
281
290
|
interface ApiKey {
|
|
282
291
|
id: string;
|
|
283
292
|
tenantId: string;
|
|
284
293
|
keyHash: string;
|
|
285
294
|
name: string;
|
|
286
295
|
isActive: boolean;
|
|
296
|
+
/** User id of the admin who created this key. Undefined for keys created before this field existed. */
|
|
297
|
+
createdBy?: string;
|
|
298
|
+
/** Optional expiry set at creation. Checked lazily at auth time and on list — no background sweep. */
|
|
299
|
+
expireBy?: string;
|
|
287
300
|
createdAt: string;
|
|
301
|
+
updatedAt?: string;
|
|
288
302
|
}
|
|
289
303
|
interface Profile {
|
|
290
304
|
id: string;
|
|
291
305
|
tenantId: string;
|
|
292
306
|
name: string;
|
|
293
307
|
defaultLocale: string;
|
|
308
|
+
/** 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. */
|
|
294
309
|
version: number;
|
|
295
|
-
profileJson:
|
|
310
|
+
profileJson: StoredProfileJson;
|
|
296
311
|
createdAt: string;
|
|
297
312
|
updatedAt: string;
|
|
298
313
|
}
|
|
@@ -301,7 +316,6 @@ interface ConsentDbRecord {
|
|
|
301
316
|
tenantId: string;
|
|
302
317
|
visitorId: string;
|
|
303
318
|
profileId: string;
|
|
304
|
-
profileVersion: number;
|
|
305
319
|
locale: string;
|
|
306
320
|
consentJson: ConsentValue;
|
|
307
321
|
gpcDetected: boolean;
|
|
@@ -309,6 +323,10 @@ interface ConsentDbRecord {
|
|
|
309
323
|
ageVerified?: boolean;
|
|
310
324
|
parentalConsentToken?: string;
|
|
311
325
|
tcfString?: string;
|
|
326
|
+
/** HMAC-SHA256 signature over the record's core fields, hex-encoded. Only present when
|
|
327
|
+
* `consentSigningKey` is configured — opt-in tamper-evidence for server-stored records,
|
|
328
|
+
* independent of the widget's own cookie-signing (`core.cookieSigningKey`). */
|
|
329
|
+
signature?: string;
|
|
312
330
|
createdAt: string;
|
|
313
331
|
updatedAt: string;
|
|
314
332
|
}
|
|
@@ -343,6 +361,8 @@ interface AdminUser {
|
|
|
343
361
|
isActive: boolean;
|
|
344
362
|
totpEnabled?: boolean;
|
|
345
363
|
totpSecret?: string;
|
|
364
|
+
/** Empty array = access to all tenants. Non-empty = restricted to listed tenant IDs. */
|
|
365
|
+
allowedTenants?: string[];
|
|
346
366
|
createdAt: string;
|
|
347
367
|
updatedAt: string;
|
|
348
368
|
}
|
|
@@ -357,6 +377,22 @@ interface Permission {
|
|
|
357
377
|
name: string;
|
|
358
378
|
description?: string;
|
|
359
379
|
}
|
|
380
|
+
/** Proof that the consent banner was rendered to a visitor — recorded even if they never
|
|
381
|
+
* interact with it, distinct from (and never merged into) an actual consent decision. */
|
|
382
|
+
interface NoticeShownRecord {
|
|
383
|
+
id: string;
|
|
384
|
+
tenantId: string;
|
|
385
|
+
visitorId: string;
|
|
386
|
+
profileId: string;
|
|
387
|
+
locale: string;
|
|
388
|
+
createdAt: string;
|
|
389
|
+
}
|
|
390
|
+
interface CreateNoticeShownInput {
|
|
391
|
+
tenantId: string;
|
|
392
|
+
visitorId: string;
|
|
393
|
+
profileId: string;
|
|
394
|
+
locale: string;
|
|
395
|
+
}
|
|
360
396
|
interface AuditLog {
|
|
361
397
|
id: string;
|
|
362
398
|
tenantId: string;
|
|
@@ -368,22 +404,38 @@ interface AuditLog {
|
|
|
368
404
|
newData?: unknown;
|
|
369
405
|
createdAt: string;
|
|
370
406
|
}
|
|
407
|
+
/** List-view shape for consents — omits the fields not shown in a table row (`consentJson`,
|
|
408
|
+
* `parentalConsentToken`, `tcfString`, `signature`), so paginated list queries don't have to pull
|
|
409
|
+
* those blobs off disk for every row. Fetch the full `ConsentDbRecord` via `getConsent(visitorId)`
|
|
410
|
+
* when a single record's full detail is actually needed (e.g. opening a detail modal). */
|
|
411
|
+
type ConsentSummary = Omit<ConsentDbRecord, 'consentJson' | 'parentalConsentToken' | 'tcfString' | 'signature'>;
|
|
412
|
+
/** List-view shape for audit logs — omits `oldData`/`newData` (full before/after snapshots) for
|
|
413
|
+
* the same reason as `ConsentSummary`. Fetch the full `AuditLog` via `getAuditLogById(id)` when a
|
|
414
|
+
* single entry's full detail is needed. */
|
|
415
|
+
type AuditLogSummary = Omit<AuditLog, 'oldData' | 'newData'>;
|
|
371
416
|
interface CreateProfileInput {
|
|
372
417
|
tenantId: string;
|
|
373
418
|
name: string;
|
|
374
419
|
defaultLocale: string;
|
|
375
|
-
profileJson:
|
|
420
|
+
profileJson: StoredProfileJson;
|
|
421
|
+
/** Non-default locales' resolved content for this save — see {@link LocaleContentInput}. */
|
|
422
|
+
localeContent?: Record<string, LocaleContentInput>;
|
|
376
423
|
}
|
|
377
424
|
interface UpdateProfileInput {
|
|
378
425
|
name?: string;
|
|
379
426
|
defaultLocale?: string;
|
|
380
|
-
profileJson?:
|
|
427
|
+
profileJson?: StoredProfileJson;
|
|
428
|
+
/** 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. */
|
|
429
|
+
version?: number;
|
|
430
|
+
/** Non-default locales' resolved content for this save — see {@link LocaleContentInput}. Any
|
|
431
|
+
* locale in `profileJson.locales` not present here is carried forward unchanged from the
|
|
432
|
+
* previous version's on-disk file. */
|
|
433
|
+
localeContent?: Record<string, LocaleContentInput>;
|
|
381
434
|
}
|
|
382
435
|
interface CreateConsentInput {
|
|
383
436
|
tenantId: string;
|
|
384
437
|
visitorId: string;
|
|
385
438
|
profileId: string;
|
|
386
|
-
profileVersion: number;
|
|
387
439
|
locale: string;
|
|
388
440
|
consentJson: ConsentValue;
|
|
389
441
|
gpcDetected: boolean;
|
|
@@ -391,11 +443,13 @@ interface CreateConsentInput {
|
|
|
391
443
|
ageVerified?: boolean;
|
|
392
444
|
parentalConsentToken?: string;
|
|
393
445
|
tcfString?: string;
|
|
446
|
+
signature?: string;
|
|
394
447
|
}
|
|
395
448
|
interface UpdateConsentInput {
|
|
396
449
|
consentJson: ConsentValue;
|
|
397
450
|
locale?: string;
|
|
398
451
|
gpcDetected?: boolean;
|
|
452
|
+
signature?: string;
|
|
399
453
|
}
|
|
400
454
|
interface CreateVisitorInput {
|
|
401
455
|
tenantId: string;
|
|
@@ -417,6 +471,7 @@ interface CreateUserInput {
|
|
|
417
471
|
name: string;
|
|
418
472
|
email: string;
|
|
419
473
|
passwordHash: string;
|
|
474
|
+
allowedTenants?: string[];
|
|
420
475
|
}
|
|
421
476
|
interface UpdateUserInput {
|
|
422
477
|
name?: string;
|
|
@@ -425,6 +480,7 @@ interface UpdateUserInput {
|
|
|
425
480
|
isActive?: boolean;
|
|
426
481
|
totpSecret?: string | null;
|
|
427
482
|
totpEnabled?: boolean;
|
|
483
|
+
allowedTenants?: string[];
|
|
428
484
|
}
|
|
429
485
|
interface CreateAuditLogInput {
|
|
430
486
|
tenantId: string;
|
|
@@ -442,6 +498,8 @@ interface ConsentFilters {
|
|
|
442
498
|
to?: string;
|
|
443
499
|
page?: number;
|
|
444
500
|
limit?: number;
|
|
501
|
+
/** Free-text search across visitorId, profileId, locale, source. */
|
|
502
|
+
q?: string;
|
|
445
503
|
}
|
|
446
504
|
interface VisitorFilters {
|
|
447
505
|
tenantId: string;
|
|
@@ -449,6 +507,8 @@ interface VisitorFilters {
|
|
|
449
507
|
to?: string;
|
|
450
508
|
page?: number;
|
|
451
509
|
limit?: number;
|
|
510
|
+
/** Free-text search across visitorId, country. */
|
|
511
|
+
q?: string;
|
|
452
512
|
}
|
|
453
513
|
interface AuditFilters {
|
|
454
514
|
tenantId: string;
|
|
@@ -458,12 +518,24 @@ interface AuditFilters {
|
|
|
458
518
|
to?: string;
|
|
459
519
|
page?: number;
|
|
460
520
|
limit?: number;
|
|
521
|
+
/** Free-text search across action, resourceType, resourceId, userId. */
|
|
522
|
+
q?: string;
|
|
523
|
+
}
|
|
524
|
+
/** Page of results plus the total row count matching the same filters (ignoring page/limit) —
|
|
525
|
+
* lets callers compute total page count for numbered pagination without a second round trip. */
|
|
526
|
+
interface PagedResult<T> {
|
|
527
|
+
items: T[];
|
|
528
|
+
total: number;
|
|
529
|
+
page: number;
|
|
530
|
+
limit: number;
|
|
461
531
|
}
|
|
462
532
|
interface ConsentVerifyResult {
|
|
463
533
|
valid: boolean;
|
|
464
|
-
reasons: Array<'
|
|
465
|
-
|
|
466
|
-
|
|
534
|
+
reasons: Array<'profile_changed' | 'consent_expired' | 'hmac_invalid'>;
|
|
535
|
+
/** The id of the profile currently active for this consent's compliance group, if any. */
|
|
536
|
+
currentProfileId?: string;
|
|
537
|
+
/** The id of the profile this consent was originally collected against. */
|
|
538
|
+
consentProfileId?: string;
|
|
467
539
|
}
|
|
468
540
|
type SafeAdminUser = Omit<AdminUser, 'passwordHash'>;
|
|
469
541
|
interface CreateRoleInput {
|
|
@@ -504,6 +576,8 @@ interface CreateApiKeyInput {
|
|
|
504
576
|
tenantId: string;
|
|
505
577
|
name: string;
|
|
506
578
|
keyHash: string;
|
|
579
|
+
createdBy?: string;
|
|
580
|
+
expireBy?: string;
|
|
507
581
|
}
|
|
508
582
|
interface CreateTenantInput {
|
|
509
583
|
name: string;
|
|
@@ -522,19 +596,27 @@ interface StorageAdapter {
|
|
|
522
596
|
deleteProfile(id: string): Promise<void>;
|
|
523
597
|
getProfile(id: string): Promise<Profile | null>;
|
|
524
598
|
getProfiles(tenantId: string): Promise<Profile[]>;
|
|
599
|
+
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
600
|
+
/** Lightweight list — no profile_json blob; single JOIN for template names. */
|
|
601
|
+
listProfilesSummary(tenantId: string): Promise<ProfileSummary[]>;
|
|
602
|
+
findProfilesUsingConsentTemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
603
|
+
findProfilesUsingUITemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
604
|
+
getOptInStats(tenantId: string, filters: OptInFilters): Promise<OptInStats>;
|
|
525
605
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
526
606
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
527
607
|
deleteConsent(visitorId: string): Promise<void>;
|
|
528
608
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
529
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
609
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
530
610
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
531
611
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
532
612
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
533
613
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
534
614
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
535
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
615
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
536
616
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
537
617
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
618
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
619
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
538
620
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
539
621
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
540
622
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -544,17 +626,26 @@ interface StorageAdapter {
|
|
|
544
626
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
545
627
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
546
628
|
deleteTenant(id: string): Promise<void>;
|
|
629
|
+
/** Returns `{}` (all fields unset) if no settings row exists yet for this tenant. */
|
|
630
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
631
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
547
632
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
548
633
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
549
634
|
revokeApiKey(id: string): Promise<void>;
|
|
635
|
+
/** Re-enables a previously revoked key (same hash, no new secret). Clears `expireBy` if it's
|
|
636
|
+
* in the past, so a lazily-expired key doesn't immediately re-expire on the next check. */
|
|
637
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
638
|
+
/** Permanently removes the key row — unlike `revokeApiKey`, this cannot be undone. */
|
|
639
|
+
deleteApiKey(id: string): Promise<void>;
|
|
550
640
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
551
641
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
642
|
+
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
643
|
+
createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
644
|
+
updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
645
|
+
deleteConsentTemplate(id: string): Promise<void>;
|
|
646
|
+
getConsentTemplate(id: string): Promise<ServerConsentTemplate | null>;
|
|
647
|
+
getConsentTemplates(tenantId: string): Promise<ServerConsentTemplate[]>;
|
|
648
|
+
copyConsentTemplate(id: string, newName: string): Promise<ServerConsentTemplate>;
|
|
558
649
|
createUITemplate(data: CreateUITemplateInput): Promise<ServerUITemplate>;
|
|
559
650
|
updateUITemplate(id: string, data: UpdateUITemplateInput): Promise<ServerUITemplate>;
|
|
560
651
|
deleteUITemplate(id: string): Promise<void>;
|
|
@@ -580,7 +671,8 @@ interface StorageAdapter {
|
|
|
580
671
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
581
672
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
582
673
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
583
|
-
getLogs(filters: AuditFilters): Promise<
|
|
674
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
675
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
584
676
|
}
|
|
585
677
|
interface PluginContext {
|
|
586
678
|
storage: StorageAdapter;
|
|
@@ -599,6 +691,325 @@ declare abstract class ConsentiServerPlugin {
|
|
|
599
691
|
beforeUserCreate?(data: CreateUserInput): Promise<CreateUserInput>;
|
|
600
692
|
afterUserCreate?(user: AdminUser): Promise<void>;
|
|
601
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* The server's stored shape of `Profile.profileJson` — extends the shared `ProfileConfig`
|
|
696
|
+
* (`./ui`, also used by `apps/ui`'s local `ConsentiProfile`/`registerProfile()` authoring, which
|
|
697
|
+
* genuinely needs the full multi-locale `translations`/`localeContents` blob for its own
|
|
698
|
+
* no-backend use case and must stay untouched).
|
|
699
|
+
*
|
|
700
|
+
* On the server, only `defaultLocale`'s own resolved content is ever stored here —
|
|
701
|
+
* `mainBanner`/`gpcBanner`/`preferenceModal` (inherited from `ProfileConfig` as-is: template shape
|
|
702
|
+
* merged with the author's text, optional fields simply omitted rather than blank — never a
|
|
703
|
+
* multi-locale map). Every other locale's content lives only in its own per-version JSON file on
|
|
704
|
+
* disk (`profiles/{tenant}/{id}/{version}/{locale}.json`, see `ProfileService`), never in the DB
|
|
705
|
+
* row — `translations`/`localeContents` (both full multi-locale blobs) are dropped entirely.
|
|
706
|
+
*/
|
|
707
|
+
interface StoredProfileJson extends Omit<ProfileConfig, 'translations' | 'localeContents'> {
|
|
708
|
+
/** Every locale this profile has content for — drives the dashboard's tab list. Content for any
|
|
709
|
+
* locale other than `defaultLocale` lives only in that locale's on-disk version file, not here. */
|
|
710
|
+
locales: string[];
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
type ConsentStatus = 'granted' | 'denied' | 'objected';
|
|
714
|
+
type ConsentValue = Record<string, ConsentStatus>;
|
|
715
|
+
type GpcMode = 'ignore' | 'honor' | 'strict';
|
|
716
|
+
type LegalBasis = 'mandatory' | 'consent' | 'legitimate_interest';
|
|
717
|
+
type CookiePurpose = typeof COOKIE_PURPOSE_IDS[number];
|
|
718
|
+
/**
|
|
719
|
+
* A single consent parameter (tracker/cookie definition).
|
|
720
|
+
* Keyed by its own id inside `Record<string, Cookie>` — the map key IS the id,
|
|
721
|
+
* there is no `id` field on the value itself.
|
|
722
|
+
*/
|
|
723
|
+
interface Cookie {
|
|
724
|
+
purpose?: CookiePurpose;
|
|
725
|
+
listenGpc?: boolean;
|
|
726
|
+
/**
|
|
727
|
+
* Pre-grant this parameter's default consent to 'granted' (instead of the
|
|
728
|
+
* compliance-group-driven default) when no stored decision exists yet.
|
|
729
|
+
* Only meaningful when the owning category's `legalBasis === 'consent'` —
|
|
730
|
+
* `mandatory`/`legitimate_interest` categories are already effectively
|
|
731
|
+
* pre-granted regardless of this flag. Never overrides an active GPC signal.
|
|
732
|
+
* Default: false.
|
|
733
|
+
*/
|
|
734
|
+
preGrant?: boolean;
|
|
735
|
+
tcfVendorId?: number;
|
|
736
|
+
tcfPurposes?: number[];
|
|
737
|
+
tcfSpecialFeatures?: number[];
|
|
738
|
+
cpraCategory?: 'sale' | 'sharing' | 'sensitive';
|
|
739
|
+
}
|
|
740
|
+
/** `Cookie` keyed by its own id — the standard shape used everywhere a parameter list is passed around. */
|
|
741
|
+
type CookieMap = Record<string, Cookie>;
|
|
742
|
+
type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
|
|
743
|
+
type ButtonAction = 'submit' | 'manage' | 'close' | 'custom' | 'link';
|
|
744
|
+
interface Button {
|
|
745
|
+
text: string;
|
|
746
|
+
style: ButtonStyle;
|
|
747
|
+
action: ButtonAction;
|
|
748
|
+
cookies?: string[] | '*' | '!';
|
|
749
|
+
url?: string;
|
|
750
|
+
}
|
|
751
|
+
/** `Button` keyed by its own id — e.g. `'accept-all'`. The map key IS the id, rendered as the DOM
|
|
752
|
+
* `id` (`consenti-btn-{id}`) so integrators can target specific buttons, and is what
|
|
753
|
+
* `profileOverride`'s `deepMerge` deletes by (`{ 'reject-optional': null }` removes one button). */
|
|
754
|
+
type ButtonMap = Record<string, Button>;
|
|
755
|
+
interface MainBanner {
|
|
756
|
+
position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
|
|
757
|
+
overlayOpacity?: number;
|
|
758
|
+
showClose?: boolean;
|
|
759
|
+
showLocaleSwitcher?: boolean;
|
|
760
|
+
heading?: string;
|
|
761
|
+
headingTag?: string;
|
|
762
|
+
htmlText: string;
|
|
763
|
+
buttons: ButtonMap;
|
|
764
|
+
/** Stack buttons vertically below this viewport width in px. 0 or undefined = disabled. Default: 576. */
|
|
765
|
+
stackButtonsOnBreakpoint?: number;
|
|
766
|
+
/** Trap keyboard Tab focus within the consent root while this banner is visible. */
|
|
767
|
+
trapFocus?: boolean;
|
|
768
|
+
}
|
|
769
|
+
type GpcBanner = MainBanner;
|
|
770
|
+
/**
|
|
771
|
+
* A consent category — the single source of legal basis for every parameter
|
|
772
|
+
* it lists in `cookies`. Keyed by its own id inside `Record<string, Category>` —
|
|
773
|
+
* the map key IS the id, there is no `id` field on the value itself.
|
|
774
|
+
*
|
|
775
|
+
* A parameter must belong to exactly one category (enforced at authoring time,
|
|
776
|
+
* not by this type) — that relationship is what `getCookieLegalBasis`/
|
|
777
|
+
* `isMandatoryCookie` (`@consenti/utils`) resolve at runtime.
|
|
778
|
+
*/
|
|
779
|
+
interface Category {
|
|
780
|
+
heading: string;
|
|
781
|
+
headingTag?: string;
|
|
782
|
+
htmlText: string;
|
|
783
|
+
legalBasis: LegalBasis;
|
|
784
|
+
/** Only meaningful when legalBasis === 'legitimate_interest' — the GDPR balancing-test justification shown to visitors. */
|
|
785
|
+
legitimateInterestDescription?: string;
|
|
786
|
+
/** IDs of the parameters (from the profile's `cookies` map) that belong to this category. */
|
|
787
|
+
cookies: string[];
|
|
788
|
+
}
|
|
789
|
+
/** `Category` keyed by its own id — the standard shape used everywhere a category list is passed around. */
|
|
790
|
+
type CategoryMap = Record<string, Category>;
|
|
791
|
+
interface PreferenceModal extends Omit<MainBanner, "position"> {
|
|
792
|
+
position?: 'left' | 'right' | 'center';
|
|
793
|
+
subheading?: string;
|
|
794
|
+
categories: CategoryMap;
|
|
795
|
+
persistent?: boolean;
|
|
796
|
+
/** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
|
|
797
|
+
mobileFullScreenBreakpoint?: number;
|
|
798
|
+
/** Label for the optional consent-receipt download checkbox (shown when `allowReceipt` is true). Falls back to a default when omitted. */
|
|
799
|
+
receiptLabel?: string;
|
|
800
|
+
/** Description shown beneath the consent-receipt checkbox. Falls back to a default when omitted. */
|
|
801
|
+
receiptDescription?: string;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* A UI-template button definition. Keyed by its own machine id (e.g. `'accept-all'`) inside
|
|
805
|
+
* `TemplateButtonMap` — the map key IS the id, not display text. UI templates own layout/behavior
|
|
806
|
+
* only, never text. The visitor-facing label is authored per-locale on the profile instead
|
|
807
|
+
* (`LocaleTextContent.*.buttonLabels`, keyed by the same button id) and composed into a resolved
|
|
808
|
+
* `Button.text` at profile-build time. Distinct from `Button` (the resolved-profile shape), which
|
|
809
|
+
* always carries real `text`.
|
|
810
|
+
*/
|
|
811
|
+
interface TemplateButtonDef {
|
|
812
|
+
type?: ButtonStyle;
|
|
813
|
+
action: ButtonAction;
|
|
814
|
+
cookies?: string[] | '*' | '!';
|
|
815
|
+
url?: string;
|
|
816
|
+
}
|
|
817
|
+
/** `TemplateButtonDef` keyed by its own id — the standard shape everywhere a UI template's
|
|
818
|
+
* button set is passed around. */
|
|
819
|
+
type TemplateButtonMap = Record<string, TemplateButtonDef>;
|
|
820
|
+
interface TemplateBannerDef {
|
|
821
|
+
position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
|
|
822
|
+
overlayOpacity?: number;
|
|
823
|
+
showClose?: boolean;
|
|
824
|
+
showLocaleSwitcher?: boolean;
|
|
825
|
+
headingTag?: string;
|
|
826
|
+
buttons: TemplateButtonMap;
|
|
827
|
+
stackButtonsOnBreakpoint?: number;
|
|
828
|
+
trapFocus?: boolean;
|
|
829
|
+
}
|
|
830
|
+
type TemplateGpcBannerDef = TemplateBannerDef;
|
|
831
|
+
interface TemplateModalDef extends Omit<TemplateBannerDef, 'position'> {
|
|
832
|
+
position?: 'left' | 'right' | 'center';
|
|
833
|
+
persistent?: boolean;
|
|
834
|
+
hasSubheading?: boolean;
|
|
835
|
+
mobileFullScreenBreakpoint?: number;
|
|
836
|
+
}
|
|
837
|
+
interface LocaleTranslations {
|
|
838
|
+
mainBanner: DeepPartial<MainBanner>;
|
|
839
|
+
gpcBanner?: DeepPartial<GpcBanner>;
|
|
840
|
+
preferenceModal: DeepPartial<PreferenceModal>;
|
|
841
|
+
}
|
|
842
|
+
interface ProfileTranslations {
|
|
843
|
+
[locale: string]: LocaleTranslations;
|
|
844
|
+
}
|
|
845
|
+
interface DpdpaConfig {
|
|
846
|
+
dataFiduciary: string;
|
|
847
|
+
grievanceEmail: string;
|
|
848
|
+
purposeDescription?: string;
|
|
849
|
+
}
|
|
850
|
+
interface LocaleTextContent {
|
|
851
|
+
mainBanner: {
|
|
852
|
+
heading?: string;
|
|
853
|
+
htmlText: string;
|
|
854
|
+
/** Keyed by the linked UI template's button id — was a positional array matched to
|
|
855
|
+
* `buttons[i]` by index; now matched by key, robust against button reordering. */
|
|
856
|
+
buttonLabels?: Record<string, string>;
|
|
857
|
+
};
|
|
858
|
+
gpcBanner: {
|
|
859
|
+
heading?: string;
|
|
860
|
+
htmlText: string;
|
|
861
|
+
buttonLabels?: Record<string, string>;
|
|
862
|
+
};
|
|
863
|
+
preferenceModal: {
|
|
864
|
+
heading: string;
|
|
865
|
+
subheading?: string;
|
|
866
|
+
htmlText?: string;
|
|
867
|
+
buttonLabels?: Record<string, string>;
|
|
868
|
+
/** `legitimateInterestDescription` is only meaningful for categories whose legalBasis is 'legitimate_interest'; optional GDPR balancing-test text, translated per locale like heading/htmlText. */
|
|
869
|
+
categories: Record<string, {
|
|
870
|
+
heading: string;
|
|
871
|
+
htmlText: string;
|
|
872
|
+
legitimateInterestDescription?: string;
|
|
873
|
+
}>;
|
|
874
|
+
receiptLabel?: string;
|
|
875
|
+
receiptDescription?: string;
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
interface ProfileConfig {
|
|
879
|
+
id: string;
|
|
880
|
+
cookies?: CookieMap;
|
|
881
|
+
defaultLocale: string;
|
|
882
|
+
/** Days until consent expires and the visitor is asked again (profile-wide). Default: 365. */
|
|
883
|
+
expiryDays?: number;
|
|
884
|
+
translations?: ProfileTranslations;
|
|
885
|
+
mainBanner: MainBanner;
|
|
886
|
+
gpcBanner?: GpcBanner;
|
|
887
|
+
preferenceModal: PreferenceModal;
|
|
888
|
+
darkMode?: boolean;
|
|
889
|
+
allowedOrigins?: string[];
|
|
890
|
+
dpdpa?: DpdpaConfig;
|
|
891
|
+
regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
|
|
892
|
+
regulations?: string[];
|
|
893
|
+
consentTemplateId?: string;
|
|
894
|
+
uiTemplateId?: string;
|
|
895
|
+
localeContents?: Record<string, LocaleTextContent>;
|
|
896
|
+
complianceGroup?: ComplianceGroupId;
|
|
897
|
+
/**
|
|
898
|
+
* Free-form identifier (lower-kebab-case) for profiles that don't map to one of the
|
|
899
|
+
* built-in `complianceGroup` values — the identifier the widget's `compliance.type`
|
|
900
|
+
* config targets to fetch this profile. Participates in activation, deactivation, and
|
|
901
|
+
* "one active profile per group" conflict detection the same way `complianceGroup` does;
|
|
902
|
+
* no `COMPLIANCE_GROUPS` validation rules or GPC defaults apply to it, since none exist
|
|
903
|
+
* for a free-form name.
|
|
904
|
+
*/
|
|
905
|
+
customComplianceGroup?: string;
|
|
906
|
+
/**
|
|
907
|
+
* Per-profile deltas applied on top of the consent template's authored `Cookie` values
|
|
908
|
+
* (e.g. an overridden `preGrant`) when resolving this profile — see the dashboard's Step 2
|
|
909
|
+
* pre-grant override UX. Keyed by cookie id; only the overridden fields need to be present.
|
|
910
|
+
*/
|
|
911
|
+
cookiesOverride?: Record<string, Partial<Cookie>>;
|
|
912
|
+
/**
|
|
913
|
+
* Reserved for a future phase — stored but not yet applied when resolving a profile.
|
|
914
|
+
* Intended to mirror `cookiesOverride` for per-category deltas.
|
|
915
|
+
*/
|
|
916
|
+
categoriesOverride?: Record<string, Partial<Category>>;
|
|
917
|
+
/**
|
|
918
|
+
* Reserved for a future phase — stored but not yet applied when resolving a profile.
|
|
919
|
+
* Intended to hold per-profile UI-template deltas.
|
|
920
|
+
*/
|
|
921
|
+
uiOverride?: Record<string, unknown>;
|
|
922
|
+
/**
|
|
923
|
+
* When registered via `registerProfile()` and `complianceGroup` matches one of
|
|
924
|
+
* Consenti's built-in groups: `false` (default) fully replaces the built-in
|
|
925
|
+
* embedded profile for that group; `true` deep-merges this config onto the
|
|
926
|
+
* built-in profile instead (only meaningful together with `complianceGroup`).
|
|
927
|
+
*/
|
|
928
|
+
deepMerge?: boolean;
|
|
929
|
+
gpcMode?: GpcMode;
|
|
930
|
+
isActive?: boolean;
|
|
931
|
+
hidePoweredBy?: boolean;
|
|
932
|
+
allowReceipt?: boolean;
|
|
933
|
+
/** Per-compliance extra config (e.g. DPDPA data fiduciary name). */
|
|
934
|
+
complianceConfig?: Record<string, string>;
|
|
935
|
+
/** Show a metadata footer strip in the banner/modal with Consent ID, Date, Version, Privacy Settings link. */
|
|
936
|
+
showFooterMetadata?: boolean;
|
|
937
|
+
/** Apply WCAG 2.1 AA button sizing (44px min-height), visible focus rings, and screen-reader labels. */
|
|
938
|
+
enhanceAccessibility?: boolean;
|
|
939
|
+
}
|
|
940
|
+
interface ServerUITemplate {
|
|
941
|
+
id: string;
|
|
942
|
+
tenantId: string;
|
|
943
|
+
name: string;
|
|
944
|
+
mainBanner: TemplateBannerDef;
|
|
945
|
+
gpcBanner: TemplateGpcBannerDef;
|
|
946
|
+
preferenceModal: TemplateModalDef;
|
|
947
|
+
createdAt: string;
|
|
948
|
+
updatedAt: string;
|
|
949
|
+
}
|
|
950
|
+
interface ServerConsentTemplate {
|
|
951
|
+
id: string;
|
|
952
|
+
tenantId: string;
|
|
953
|
+
name: string;
|
|
954
|
+
cookies: CookieMap;
|
|
955
|
+
categories: CategoryMap;
|
|
956
|
+
createdAt: string;
|
|
957
|
+
updatedAt: string;
|
|
958
|
+
}
|
|
959
|
+
interface CreateConsentTemplateInput {
|
|
960
|
+
tenantId: string;
|
|
961
|
+
name: string;
|
|
962
|
+
cookies: CookieMap;
|
|
963
|
+
categories: CategoryMap;
|
|
964
|
+
}
|
|
965
|
+
interface UpdateConsentTemplateInput {
|
|
966
|
+
name?: string;
|
|
967
|
+
cookies?: CookieMap;
|
|
968
|
+
categories?: CategoryMap;
|
|
969
|
+
}
|
|
970
|
+
interface CreateUITemplateInput {
|
|
971
|
+
tenantId: string;
|
|
972
|
+
name: string;
|
|
973
|
+
mainBanner: TemplateBannerDef;
|
|
974
|
+
gpcBanner: TemplateGpcBannerDef;
|
|
975
|
+
preferenceModal: TemplateModalDef;
|
|
976
|
+
}
|
|
977
|
+
interface UpdateUITemplateInput {
|
|
978
|
+
name?: string;
|
|
979
|
+
mainBanner?: TemplateBannerDef;
|
|
980
|
+
gpcBanner?: TemplateGpcBannerDef;
|
|
981
|
+
preferenceModal?: TemplateModalDef;
|
|
982
|
+
}
|
|
983
|
+
interface PublicProfileResponse {
|
|
984
|
+
id: string;
|
|
985
|
+
/** Incremented in place on every save of this profile. */
|
|
986
|
+
version: number;
|
|
987
|
+
name?: string;
|
|
988
|
+
tenantId?: string;
|
|
989
|
+
defaultLocale: string;
|
|
990
|
+
currentLocale: string;
|
|
991
|
+
locales: string[];
|
|
992
|
+
cookies: CookieMap;
|
|
993
|
+
/** Days until consent expires and the visitor is asked again (profile-wide). */
|
|
994
|
+
expiryDays?: number;
|
|
995
|
+
mainBanner: MainBanner;
|
|
996
|
+
gpcBanner?: GpcBanner;
|
|
997
|
+
preferenceModal: PreferenceModal;
|
|
998
|
+
resolvedComplianceGroup?: string;
|
|
999
|
+
complianceGroup?: ComplianceGroupId;
|
|
1000
|
+
compliances?: string[];
|
|
1001
|
+
complianceConfig?: Record<string, string>;
|
|
1002
|
+
allowedOrigins?: string[];
|
|
1003
|
+
gpcMode?: GpcMode;
|
|
1004
|
+
hidePoweredBy?: boolean;
|
|
1005
|
+
allowReceipt?: boolean;
|
|
1006
|
+
darkMode?: boolean;
|
|
1007
|
+
dpdpa?: DpdpaConfig;
|
|
1008
|
+
showFooterMetadata?: boolean;
|
|
1009
|
+
enhanceAccessibility?: boolean;
|
|
1010
|
+
createdAt?: string;
|
|
1011
|
+
updatedAt?: string;
|
|
1012
|
+
}
|
|
602
1013
|
|
|
603
1014
|
declare class ProfileRepo {
|
|
604
1015
|
private adapter;
|
|
@@ -608,13 +1019,25 @@ declare class ProfileRepo {
|
|
|
608
1019
|
delete(id: string): Promise<void>;
|
|
609
1020
|
get(id: string): Promise<Profile | null>;
|
|
610
1021
|
list(tenantId: string): Promise<Profile[]>;
|
|
1022
|
+
findActiveByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
611
1023
|
}
|
|
612
1024
|
|
|
613
1025
|
declare class AuditRepo {
|
|
614
1026
|
private adapter;
|
|
615
1027
|
constructor(adapter: StorageAdapter);
|
|
616
1028
|
log(data: CreateAuditLogInput): Promise<void>;
|
|
617
|
-
list(filters: AuditFilters): Promise<
|
|
1029
|
+
list(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1030
|
+
get(id: string): Promise<AuditLog | null>;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
declare class LocaleJsonCacheService {
|
|
1034
|
+
private readonly dir;
|
|
1035
|
+
constructor(dir: string);
|
|
1036
|
+
read(profileId: string, locale: string): PublicProfileResponse | null;
|
|
1037
|
+
write(profileId: string, locale: string, response: PublicProfileResponse): void;
|
|
1038
|
+
rebuild(profileId: string, responses: Map<string, PublicProfileResponse>): void;
|
|
1039
|
+
invalidate(profileId: string): void;
|
|
1040
|
+
private path;
|
|
618
1041
|
}
|
|
619
1042
|
|
|
620
1043
|
declare class ProfileService {
|
|
@@ -622,13 +1045,101 @@ declare class ProfileService {
|
|
|
622
1045
|
private audit;
|
|
623
1046
|
private tenantId;
|
|
624
1047
|
private storage?;
|
|
625
|
-
|
|
1048
|
+
private eventBus?;
|
|
1049
|
+
private localeCache?;
|
|
1050
|
+
private profilesDir?;
|
|
1051
|
+
private handleCache?;
|
|
1052
|
+
private s3Config?;
|
|
1053
|
+
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);
|
|
1054
|
+
/** Root directory holding every version snapshot of one logical profile, keyed by its stable id. */
|
|
1055
|
+
private versionsRootDir;
|
|
1056
|
+
private complianceGroupDir;
|
|
1057
|
+
/**
|
|
1058
|
+
* The directory key used for activation/hot-serve writes and the "one active profile
|
|
1059
|
+
* per group" conflict check — the real `complianceGroup` when set, otherwise the
|
|
1060
|
+
* free-form `customComplianceGroup` (which the widget's `compliance.type` targets the
|
|
1061
|
+
* same way). Compliance *validation* still only ever runs against a real `complianceGroup`.
|
|
1062
|
+
*/
|
|
1063
|
+
private groupKey;
|
|
1064
|
+
private profileFilePaths;
|
|
1065
|
+
/** Resolves this profile's cookie map (consent-template lookup + `cookiesOverride` applied) —
|
|
1066
|
+
* the one piece of content still resolved live rather than baked at save time, since cookies
|
|
1067
|
+
* are small and never the source of the row-size problem `mainBanner`/`preferenceModal` were. */
|
|
1068
|
+
private resolveCookies;
|
|
1069
|
+
/**
|
|
1070
|
+
* Writes every locale's on-disk version file for one save (create or an edit) in a single
|
|
1071
|
+
* atomic worker call — default locale content comes from `profile.profileJson` itself, every
|
|
1072
|
+
* other touched locale from `localeContent`. A locale already in `profileJson.locales` but not
|
|
1073
|
+
* present in `localeContent` (a tab the user never opened this session) is carried forward
|
|
1074
|
+
* unchanged from `previousVersion` by the worker — never silently dropped.
|
|
1075
|
+
*/
|
|
1076
|
+
private writeLocaleFiles;
|
|
626
1077
|
create(input: Omit<CreateProfileInput, 'tenantId'>): Promise<Profile>;
|
|
1078
|
+
/**
|
|
1079
|
+
* Duplicates a profile as a brand new, always-inactive profile (own `id`, `version` starts at
|
|
1080
|
+
* 1 again). Delegates to {@link create} rather than copying on-disk files directly — the
|
|
1081
|
+
* resolved-JSON snapshot embeds its own `id`/`version`, so it has to be regenerated for the
|
|
1082
|
+
* new profile rather than byte-copied from the source's. Non-default locale content is read
|
|
1083
|
+
* back off the source's current version files so the copy carries every locale forward too.
|
|
1084
|
+
*/
|
|
1085
|
+
copy(id: string, name?: string): Promise<Profile>;
|
|
1086
|
+
/**
|
|
1087
|
+
* Mutates the existing row in place — `id` is stable across every edit, `version` is
|
|
1088
|
+
* incremented. The prior state isn't kept as a separate DB row; its resolved-JSON snapshot
|
|
1089
|
+
* on disk (see {@link listVersions}) is the audit trail for that edit.
|
|
1090
|
+
*/
|
|
627
1091
|
update(id: string, input: UpdateProfileInput): Promise<Profile>;
|
|
628
1092
|
delete(id: string): Promise<void>;
|
|
629
|
-
|
|
1093
|
+
activate(id: string): Promise<Profile>;
|
|
1094
|
+
deactivate(id: string): Promise<Profile>;
|
|
1095
|
+
private runDeactivate;
|
|
1096
|
+
get(id: string): Promise<Profile | null>;
|
|
1097
|
+
/**
|
|
1098
|
+
* Resolves one locale's full public response. The default locale's content lives directly on
|
|
1099
|
+
* the DB row (`profileJson.mainBanner`/`gpcBanner`/`preferenceModal`) — no template lookups, no
|
|
1100
|
+
* live resolution. Any other locale's content lives only in that locale's on-disk version file;
|
|
1101
|
+
* this reads it directly, falling back to the default locale's content if the file is missing
|
|
1102
|
+
* (e.g. a locale listed but never actually authored yet). This method is not a hot path — the
|
|
1103
|
+
* public `/profiles/:tenant/:group/:locale` route serves the static file directly and only
|
|
1104
|
+
* falls back to this for previewing an inactive/versioned profile.
|
|
1105
|
+
*/
|
|
630
1106
|
getResolved(id: string, locale?: string): Promise<PublicProfileResponse | null>;
|
|
1107
|
+
findActiveByComplianceGroup(complianceGroup: string): Promise<Profile | null>;
|
|
631
1108
|
list(): Promise<Profile[]>;
|
|
1109
|
+
listSummary(): Promise<ProfileSummary[]>;
|
|
1110
|
+
/**
|
|
1111
|
+
* Lists every version snapshot of `profileId` on disk (newest first), read straight from
|
|
1112
|
+
* the version-directory tree — no DB query, `Profile.version` is just the current pointer.
|
|
1113
|
+
* Works for archived profiles too (DB row deleted, files still on disk) — the directory
|
|
1114
|
+
* tree is the only thing this reads.
|
|
1115
|
+
*/
|
|
1116
|
+
listVersions(profileId: string): Promise<ProfileVersionEntry[]>;
|
|
1117
|
+
/** Works for archived profiles too — see `listVersions()`. */
|
|
1118
|
+
getVersionFile(profileId: string, version: string, locale: string): Promise<string | null>;
|
|
1119
|
+
/**
|
|
1120
|
+
* Profile-id directories on disk with no matching DB row — deleted profiles whose version
|
|
1121
|
+
* snapshots `delete()` never removes. Directory-only read (id, version count, last-modified
|
|
1122
|
+
* mtime) — no file content is opened here; that only happens once a specific version is
|
|
1123
|
+
* requested via `getVersionFile()`, which works for these ids too.
|
|
1124
|
+
*/
|
|
1125
|
+
listArchivedProfiles(): Promise<ArchivedProfileSummary[]>;
|
|
1126
|
+
/**
|
|
1127
|
+
* Seeds a default profile for a compliance group from the embedded English profile in
|
|
1128
|
+
* `@consenti/utils`, merged with locale text overlays for de/es/fr/ja. Every locale — including
|
|
1129
|
+
* non-English ones — is passed through `create()`'s normal `localeContent` path, the same as an
|
|
1130
|
+
* author-submitted profile; there is no separate "seeded profile" code path for locale content
|
|
1131
|
+
* anymore, which is what previously let a locale silently never make it into the served file.
|
|
1132
|
+
* Only creates the profile if no active profile already exists for the compliance group.
|
|
1133
|
+
* Safe to call repeatedly (idempotent).
|
|
1134
|
+
*
|
|
1135
|
+
* @param complianceGroup - One of the 8 compliance group IDs.
|
|
1136
|
+
*/
|
|
1137
|
+
seedDefaultProfile(complianceGroup: string): Promise<void>;
|
|
1138
|
+
/**
|
|
1139
|
+
* Seeds default profiles for all 8 compliance groups.
|
|
1140
|
+
* Skips any group that already has an active profile (idempotent).
|
|
1141
|
+
*/
|
|
1142
|
+
seedAllDefaults(): Promise<void>;
|
|
632
1143
|
}
|
|
633
1144
|
|
|
634
1145
|
declare class ConsentRepo {
|
|
@@ -638,7 +1149,7 @@ declare class ConsentRepo {
|
|
|
638
1149
|
update(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
639
1150
|
delete(visitorId: string): Promise<void>;
|
|
640
1151
|
get(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
641
|
-
list(filters: ConsentFilters): Promise<
|
|
1152
|
+
list(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
642
1153
|
}
|
|
643
1154
|
|
|
644
1155
|
declare class VisitorRepo {
|
|
@@ -674,7 +1185,20 @@ declare class ConsentService {
|
|
|
674
1185
|
private pluginEngine?;
|
|
675
1186
|
private eventBus?;
|
|
676
1187
|
private tcfConfig?;
|
|
677
|
-
|
|
1188
|
+
private storage?;
|
|
1189
|
+
private consentSigningKey?;
|
|
1190
|
+
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, consentSigningKey?: string | undefined);
|
|
1191
|
+
private sign;
|
|
1192
|
+
/**
|
|
1193
|
+
* Profiles created via the dashboard wizard reference a consent template (`consentTemplateId`)
|
|
1194
|
+
* rather than embedding `cookies`/`preferenceModal.categories` directly on `profileJson` — those
|
|
1195
|
+
* fields are only populated for the embedded/seeded default profiles (see
|
|
1196
|
+
* `ProfileService.seedDefaultProfile`). Cookie/category membership and `legalBasis` are
|
|
1197
|
+
* locale-independent, so the raw consent template's data is sufficient here — no need to
|
|
1198
|
+
* resolve per-locale text via `ProfileService.getResolved()`. Mirrors the same fallback
|
|
1199
|
+
* `ProfileService.getResolved()` and the admin route validators already apply.
|
|
1200
|
+
*/
|
|
1201
|
+
private resolveTemplateData;
|
|
678
1202
|
create(input: Omit<CreateConsentInput, 'tenantId'>): Promise<ConsentDbRecord>;
|
|
679
1203
|
update(visitorId: string, input: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
680
1204
|
get(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
@@ -685,7 +1209,8 @@ declare class ConsentService {
|
|
|
685
1209
|
declare class VisitorService {
|
|
686
1210
|
private visitors;
|
|
687
1211
|
private tenantId;
|
|
688
|
-
|
|
1212
|
+
private eventBus?;
|
|
1213
|
+
constructor(visitors: VisitorRepo, tenantId?: string, eventBus?: EventEmitter | undefined);
|
|
689
1214
|
upsert(data: {
|
|
690
1215
|
visitorId: string;
|
|
691
1216
|
ip?: string;
|
|
@@ -721,12 +1246,14 @@ declare class UserService {
|
|
|
721
1246
|
email: string;
|
|
722
1247
|
password: string;
|
|
723
1248
|
roleId?: string;
|
|
1249
|
+
allowedTenants?: string[];
|
|
724
1250
|
}, actorId?: string): Promise<SafeAdminUser>;
|
|
725
1251
|
update(id: string, input: {
|
|
726
1252
|
name?: string;
|
|
727
1253
|
email?: string;
|
|
728
1254
|
isActive?: boolean;
|
|
729
1255
|
password?: string;
|
|
1256
|
+
allowedTenants?: string[];
|
|
730
1257
|
}, actorId?: string): Promise<SafeAdminUser>;
|
|
731
1258
|
delete(id: string, actorId?: string): Promise<void>;
|
|
732
1259
|
get(id: string): Promise<SafeAdminUser | null>;
|
|
@@ -751,20 +1278,25 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
751
1278
|
deleteProfile(id: string): Promise<void>;
|
|
752
1279
|
getProfile(id: string): Promise<Profile | null>;
|
|
753
1280
|
getProfiles(tenantId: string): Promise<Profile[]>;
|
|
1281
|
+
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
754
1282
|
private mapConsent;
|
|
1283
|
+
private static readonly CONSENT_SUMMARY_COLS;
|
|
1284
|
+
private mapConsentSummary;
|
|
755
1285
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
756
1286
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
757
1287
|
deleteConsent(visitorId: string): Promise<void>;
|
|
758
1288
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
759
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1289
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
760
1290
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
761
1291
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1292
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1293
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
762
1294
|
private mapVisitor;
|
|
763
1295
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
764
1296
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
765
1297
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
766
1298
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
767
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1299
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
768
1300
|
private mapUser;
|
|
769
1301
|
createUser(data: CreateUserInput): Promise<AdminUser>;
|
|
770
1302
|
updateUser(id: string, data: UpdateUserInput): Promise<AdminUser>;
|
|
@@ -785,7 +1317,10 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
785
1317
|
assignRole(userId: string, roleId: string, tenantId?: string): Promise<void>;
|
|
786
1318
|
revokeRole(userId: string, roleId: string, tenantId?: string): Promise<void>;
|
|
787
1319
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
788
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1320
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1321
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
1322
|
+
private static readonly AUDIT_SUMMARY_COLS;
|
|
1323
|
+
private mapAuditSummary;
|
|
789
1324
|
private mapAuditLog;
|
|
790
1325
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
791
1326
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
@@ -797,19 +1332,24 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
797
1332
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
798
1333
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
799
1334
|
deleteTenant(id: string): Promise<void>;
|
|
1335
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1336
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
800
1337
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1338
|
+
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
801
1339
|
private mapApiKey;
|
|
802
1340
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
803
1341
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
804
1342
|
revokeApiKey(id: string): Promise<void>;
|
|
1343
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1344
|
+
deleteApiKey(id: string): Promise<void>;
|
|
805
1345
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
806
|
-
private
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1346
|
+
private mapConsentTemplate;
|
|
1347
|
+
createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
1348
|
+
updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
|
|
1349
|
+
deleteConsentTemplate(id: string): Promise<void>;
|
|
1350
|
+
getConsentTemplate(id: string): Promise<ServerConsentTemplate | null>;
|
|
1351
|
+
getConsentTemplates(tenantId: string): Promise<ServerConsentTemplate[]>;
|
|
1352
|
+
copyConsentTemplate(id: string, newName: string): Promise<ServerConsentTemplate>;
|
|
813
1353
|
private mapUITemplate;
|
|
814
1354
|
createUITemplate(data: CreateUITemplateInput): Promise<ServerUITemplate>;
|
|
815
1355
|
updateUITemplate(id: string, data: UpdateUITemplateInput): Promise<ServerUITemplate>;
|
|
@@ -817,6 +1357,12 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
|
|
|
817
1357
|
getUITemplate(id: string): Promise<ServerUITemplate | null>;
|
|
818
1358
|
getUITemplates(tenantId: string): Promise<ServerUITemplate[]>;
|
|
819
1359
|
copyUITemplate(id: string, newName: string): Promise<ServerUITemplate>;
|
|
1360
|
+
private mapProfileSummary;
|
|
1361
|
+
private readonly PROFILE_SUMMARY_SQL;
|
|
1362
|
+
listProfilesSummary(tenantId: string): Promise<ProfileSummary[]>;
|
|
1363
|
+
findProfilesUsingConsentTemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
1364
|
+
findProfilesUsingUITemplate(templateId: string): Promise<ProfileSummary[]>;
|
|
1365
|
+
getOptInStats(tenantId: string, filters: OptInFilters): Promise<OptInStats>;
|
|
820
1366
|
}
|
|
821
1367
|
|
|
822
1368
|
declare class MongoDBAdapter implements StorageAdapter {
|
|
@@ -835,20 +1381,23 @@ declare class MongoDBAdapter implements StorageAdapter {
|
|
|
835
1381
|
deleteProfile(id: string): Promise<void>;
|
|
836
1382
|
getProfile(id: string): Promise<Profile | null>;
|
|
837
1383
|
getProfiles(tenantId: string): Promise<Profile[]>;
|
|
1384
|
+
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
838
1385
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
839
1386
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
840
1387
|
deleteConsent(visitorId: string): Promise<void>;
|
|
841
1388
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
842
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1389
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
843
1390
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
844
1391
|
private buildConsentFilter;
|
|
845
1392
|
private writeHistory;
|
|
846
1393
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1394
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1395
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
847
1396
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
848
1397
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
849
1398
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
850
1399
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
851
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1400
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
852
1401
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
853
1402
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
854
1403
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -871,32 +1420,43 @@ declare class MongoDBAdapter implements StorageAdapter {
|
|
|
871
1420
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
872
1421
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
873
1422
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
874
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1423
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1424
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
875
1425
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
876
1426
|
private buildAuditFilter;
|
|
877
1427
|
getCountries(tenantId: string): Promise<CountryStat[]>;
|
|
878
1428
|
getGpcStats(tenantId: string): Promise<GpcStats>;
|
|
879
1429
|
getTenants(): Promise<Tenant[]>;
|
|
1430
|
+
private mapApiKey;
|
|
880
1431
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
881
1432
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
882
1433
|
revokeApiKey(id: string): Promise<void>;
|
|
1434
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1435
|
+
deleteApiKey(id: string): Promise<void>;
|
|
883
1436
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
884
1437
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
885
1438
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
886
1439
|
deleteTenant(id: string): Promise<void>;
|
|
1440
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1441
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
887
1442
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1443
|
+
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1444
|
+
createConsentTemplate(): Promise<never>;
|
|
1445
|
+
updateConsentTemplate(): Promise<never>;
|
|
1446
|
+
deleteConsentTemplate(): Promise<void>;
|
|
1447
|
+
getConsentTemplate(): Promise<never>;
|
|
1448
|
+
getConsentTemplates(): Promise<never>;
|
|
1449
|
+
copyConsentTemplate(): Promise<never>;
|
|
894
1450
|
createUITemplate(): Promise<never>;
|
|
895
1451
|
updateUITemplate(): Promise<never>;
|
|
896
1452
|
deleteUITemplate(): Promise<void>;
|
|
897
1453
|
getUITemplate(): Promise<never>;
|
|
898
1454
|
getUITemplates(): Promise<never>;
|
|
899
1455
|
copyUITemplate(): Promise<never>;
|
|
1456
|
+
listProfilesSummary(): Promise<never[]>;
|
|
1457
|
+
findProfilesUsingConsentTemplate(): Promise<never[]>;
|
|
1458
|
+
findProfilesUsingUITemplate(): Promise<never[]>;
|
|
1459
|
+
getOptInStats(): Promise<never>;
|
|
900
1460
|
}
|
|
901
1461
|
|
|
902
1462
|
declare class MySQLAdapter implements StorageAdapter {
|
|
@@ -914,20 +1474,23 @@ declare class MySQLAdapter implements StorageAdapter {
|
|
|
914
1474
|
deleteProfile(id: string): Promise<void>;
|
|
915
1475
|
getProfile(id: string): Promise<Profile | null>;
|
|
916
1476
|
getProfiles(tenantId: string): Promise<Profile[]>;
|
|
1477
|
+
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
917
1478
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
918
1479
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
919
1480
|
deleteConsent(visitorId: string): Promise<void>;
|
|
920
1481
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
921
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1482
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
922
1483
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
923
|
-
private
|
|
1484
|
+
private buildConsentWhere;
|
|
924
1485
|
private writeHistory;
|
|
925
1486
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1487
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1488
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
926
1489
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
927
1490
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
928
1491
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
929
1492
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
930
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1493
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
931
1494
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
932
1495
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
933
1496
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -950,32 +1513,43 @@ declare class MySQLAdapter implements StorageAdapter {
|
|
|
950
1513
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
951
1514
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
952
1515
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
953
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1516
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1517
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
954
1518
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
955
|
-
private
|
|
1519
|
+
private buildAuditWhere;
|
|
956
1520
|
getCountries(tenantId: string): Promise<CountryStat[]>;
|
|
957
1521
|
getGpcStats(tenantId: string): Promise<GpcStats>;
|
|
958
1522
|
getTenants(): Promise<Tenant[]>;
|
|
1523
|
+
private mapApiKey;
|
|
959
1524
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
960
1525
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
961
1526
|
revokeApiKey(id: string): Promise<void>;
|
|
1527
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1528
|
+
deleteApiKey(id: string): Promise<void>;
|
|
962
1529
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
963
1530
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
964
1531
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
1532
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1533
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
965
1534
|
deleteTenant(id: string): Promise<void>;
|
|
966
1535
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1536
|
+
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1537
|
+
createConsentTemplate(): Promise<never>;
|
|
1538
|
+
updateConsentTemplate(): Promise<never>;
|
|
1539
|
+
deleteConsentTemplate(): Promise<void>;
|
|
1540
|
+
getConsentTemplate(): Promise<never>;
|
|
1541
|
+
getConsentTemplates(): Promise<never>;
|
|
1542
|
+
copyConsentTemplate(): Promise<never>;
|
|
973
1543
|
createUITemplate(): Promise<never>;
|
|
974
1544
|
updateUITemplate(): Promise<never>;
|
|
975
1545
|
deleteUITemplate(): Promise<void>;
|
|
976
1546
|
getUITemplate(): Promise<never>;
|
|
977
1547
|
getUITemplates(): Promise<never>;
|
|
978
1548
|
copyUITemplate(): Promise<never>;
|
|
1549
|
+
listProfilesSummary(): Promise<never[]>;
|
|
1550
|
+
findProfilesUsingConsentTemplate(): Promise<never[]>;
|
|
1551
|
+
findProfilesUsingUITemplate(): Promise<never[]>;
|
|
1552
|
+
getOptInStats(): Promise<never>;
|
|
979
1553
|
}
|
|
980
1554
|
|
|
981
1555
|
declare class PostgreSQLAdapter implements StorageAdapter {
|
|
@@ -992,20 +1566,23 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
992
1566
|
deleteProfile(id: string): Promise<void>;
|
|
993
1567
|
getProfile(id: string): Promise<Profile | null>;
|
|
994
1568
|
getProfiles(tenantId: string): Promise<Profile[]>;
|
|
1569
|
+
findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
|
|
995
1570
|
createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
|
|
996
1571
|
updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
|
|
997
1572
|
deleteConsent(visitorId: string): Promise<void>;
|
|
998
1573
|
getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
|
|
999
|
-
getConsents(filters: ConsentFilters): Promise<
|
|
1574
|
+
getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
|
|
1000
1575
|
streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
|
|
1001
|
-
private
|
|
1576
|
+
private buildConsentWhere;
|
|
1002
1577
|
private writeHistory;
|
|
1003
1578
|
getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
|
|
1579
|
+
createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
|
|
1580
|
+
getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
|
|
1004
1581
|
createVisitor(data: CreateVisitorInput): Promise<Visitor>;
|
|
1005
1582
|
updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
|
|
1006
1583
|
deleteVisitor(visitorId: string): Promise<void>;
|
|
1007
1584
|
getVisitor(visitorId: string): Promise<Visitor | null>;
|
|
1008
|
-
getVisitors(filters: VisitorFilters): Promise<Visitor
|
|
1585
|
+
getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
|
|
1009
1586
|
getOverviewStats(tenantId: string): Promise<OverviewStats>;
|
|
1010
1587
|
getCategoryStats(tenantId: string): Promise<CategoryStats>;
|
|
1011
1588
|
getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
|
|
@@ -1028,39 +1605,170 @@ declare class PostgreSQLAdapter implements StorageAdapter {
|
|
|
1028
1605
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
1029
1606
|
revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
|
|
1030
1607
|
createLog(data: CreateAuditLogInput): Promise<void>;
|
|
1031
|
-
getLogs(filters: AuditFilters): Promise<
|
|
1608
|
+
getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
|
|
1609
|
+
getAuditLogById(id: string): Promise<AuditLog | null>;
|
|
1032
1610
|
streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
|
|
1033
|
-
private
|
|
1611
|
+
private buildAuditWhere;
|
|
1034
1612
|
getCountries(tenantId: string): Promise<CountryStat[]>;
|
|
1035
1613
|
getGpcStats(tenantId: string): Promise<GpcStats>;
|
|
1036
1614
|
getTenants(): Promise<Tenant[]>;
|
|
1615
|
+
private mapApiKey;
|
|
1037
1616
|
createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
|
|
1038
1617
|
getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
|
|
1039
1618
|
revokeApiKey(id: string): Promise<void>;
|
|
1619
|
+
reactivateApiKey(id: string): Promise<void>;
|
|
1620
|
+
deleteApiKey(id: string): Promise<void>;
|
|
1040
1621
|
getApiKeys(tenantId: string): Promise<ApiKey[]>;
|
|
1041
1622
|
createTenant(data: CreateTenantInput): Promise<Tenant>;
|
|
1042
1623
|
updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
|
|
1043
1624
|
deleteTenant(id: string): Promise<void>;
|
|
1625
|
+
getSettings(tenantId: string): Promise<TenantSettings>;
|
|
1626
|
+
updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
|
|
1044
1627
|
purgeExpiredConsents(olderThanDays: number): Promise<number>;
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1628
|
+
purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
|
|
1629
|
+
createConsentTemplate(): Promise<never>;
|
|
1630
|
+
updateConsentTemplate(): Promise<never>;
|
|
1631
|
+
deleteConsentTemplate(): Promise<void>;
|
|
1632
|
+
getConsentTemplate(): Promise<never>;
|
|
1633
|
+
getConsentTemplates(): Promise<never>;
|
|
1634
|
+
copyConsentTemplate(): Promise<never>;
|
|
1051
1635
|
createUITemplate(): Promise<never>;
|
|
1052
1636
|
updateUITemplate(): Promise<never>;
|
|
1053
1637
|
deleteUITemplate(): Promise<void>;
|
|
1054
1638
|
getUITemplate(): Promise<never>;
|
|
1055
1639
|
getUITemplates(): Promise<never>;
|
|
1056
1640
|
copyUITemplate(): Promise<never>;
|
|
1641
|
+
listProfilesSummary(): Promise<never[]>;
|
|
1642
|
+
findProfilesUsingConsentTemplate(): Promise<never[]>;
|
|
1643
|
+
findProfilesUsingUITemplate(): Promise<never[]>;
|
|
1644
|
+
getOptInStats(): Promise<never>;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* ConsentAction — server-side counterpart to `@consenti/ui`'s `ConsentAction`.
|
|
1649
|
+
*
|
|
1650
|
+
* Watches a single consent parameter across *every* visitor's submission (not just one
|
|
1651
|
+
* browser's own state) and fires `onGrant`/`onDeny` when that parameter's status actually
|
|
1652
|
+
* changes — via the same `eventBus` `createConsenti()` already emits `consent.created`/
|
|
1653
|
+
* `consent.updated` on. Useful for server-side integrations (CRM sync, suppression lists,
|
|
1654
|
+
* downstream webhooks) that need to react to consent decisions without polling the database.
|
|
1655
|
+
*
|
|
1656
|
+
* There is no server equivalent of the widget's `ConsentScript` — injecting a `<script>` tag
|
|
1657
|
+
* only makes sense in a browser DOM the server doesn't have. `services.consent.create()`/
|
|
1658
|
+
* `.update()` are already the server-side "submit"/"update consent" methods this hook reacts to.
|
|
1659
|
+
*
|
|
1660
|
+
* @example
|
|
1661
|
+
* ```ts
|
|
1662
|
+
* import { createConsenti, ConsentAction } from '@consenti/api'
|
|
1663
|
+
*
|
|
1664
|
+
* const { eventBus } = createConsenti({ ... })
|
|
1665
|
+
*
|
|
1666
|
+
* new ConsentAction({
|
|
1667
|
+
* id: 'analytics_storage',
|
|
1668
|
+
* eventBus,
|
|
1669
|
+
* onGrant: ({ visitorId }) => crm.optIn(visitorId),
|
|
1670
|
+
* onDeny: ({ visitorId }) => crm.optOut(visitorId),
|
|
1671
|
+
* })
|
|
1672
|
+
* ```
|
|
1673
|
+
*/
|
|
1674
|
+
|
|
1675
|
+
/** Passed to `onGrant`/`onDeny` — the transitioning parameter's status plus the full consent record. */
|
|
1676
|
+
interface ConsentActionParams {
|
|
1677
|
+
visitorId: string;
|
|
1678
|
+
cookieId: string;
|
|
1679
|
+
status: ConsentStatus;
|
|
1680
|
+
record: ConsentDbRecord;
|
|
1681
|
+
}
|
|
1682
|
+
/** Options for constructing a {@link ConsentAction} instance. */
|
|
1683
|
+
interface ConsentActionOptions {
|
|
1684
|
+
/** The consent parameter ID to watch (e.g. `'analytics_storage'`). */
|
|
1685
|
+
id: string;
|
|
1686
|
+
/** The `eventBus` returned by `createConsenti()`. */
|
|
1687
|
+
eventBus: EventEmitter;
|
|
1688
|
+
/** Called when the parameter's status transitions to `'granted'`, for any visitor. */
|
|
1689
|
+
onGrant?: (params: ConsentActionParams) => void;
|
|
1690
|
+
/** Called when the parameter's status transitions away from `'granted'` (`'denied'` or `'objected'`). */
|
|
1691
|
+
onDeny?: (params: ConsentActionParams) => void;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Watches a single consent parameter across every incoming `consent.created`/`consent.updated`
|
|
1695
|
+
* event and fires `onGrant`/`onDeny` on transitions. Call {@link ConsentAction.destroy} to
|
|
1696
|
+
* remove the event listeners when the hook is no longer needed.
|
|
1697
|
+
*/
|
|
1698
|
+
declare class ConsentAction {
|
|
1699
|
+
private readonly options;
|
|
1700
|
+
private readonly onCreated;
|
|
1701
|
+
private readonly onUpdated;
|
|
1702
|
+
constructor(options: ConsentActionOptions);
|
|
1703
|
+
private fire;
|
|
1704
|
+
/** Removes the event listeners. Call when the hook is no longer needed. */
|
|
1705
|
+
destroy(): void;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* CategoryAction — server-side counterpart to `@consenti/ui`'s `CategoryAction`.
|
|
1710
|
+
*
|
|
1711
|
+
* Same idea as {@link ConsentAction} but watches a whole category's rollup consent state
|
|
1712
|
+
* (granted only when *every* cookie in the category is granted) instead of one parameter —
|
|
1713
|
+
* matching the preference modal's tri-state rollup rule and the widget's `CategoryAction`.
|
|
1714
|
+
*
|
|
1715
|
+
* @example
|
|
1716
|
+
* ```ts
|
|
1717
|
+
* import { createConsenti, CategoryAction } from '@consenti/api'
|
|
1718
|
+
*
|
|
1719
|
+
* const { eventBus, services } = createConsenti({ ... })
|
|
1720
|
+
*
|
|
1721
|
+
* new CategoryAction({
|
|
1722
|
+
* categoryId: 'marketing',
|
|
1723
|
+
* eventBus,
|
|
1724
|
+
* profiles: services.profile,
|
|
1725
|
+
* onGrant: ({ visitorId }) => adsPlatform.optIn(visitorId),
|
|
1726
|
+
* onDeny: ({ visitorId }) => adsPlatform.optOut(visitorId),
|
|
1727
|
+
* })
|
|
1728
|
+
* ```
|
|
1729
|
+
*/
|
|
1730
|
+
|
|
1731
|
+
/** Passed to `onGrant`/`onDeny` — the category's rollup status plus the full consent record. */
|
|
1732
|
+
interface CategoryActionParams {
|
|
1733
|
+
visitorId: string;
|
|
1734
|
+
categoryId: string;
|
|
1735
|
+
status: ConsentStatus;
|
|
1736
|
+
record: ConsentDbRecord;
|
|
1737
|
+
}
|
|
1738
|
+
/** Options for constructing a {@link CategoryAction} instance. */
|
|
1739
|
+
interface CategoryActionOptions {
|
|
1740
|
+
/** The category ID to watch (e.g. `'marketing'`). */
|
|
1741
|
+
categoryId: string;
|
|
1742
|
+
/** The `eventBus` returned by `createConsenti()`. */
|
|
1743
|
+
eventBus: EventEmitter;
|
|
1744
|
+
/** The `services.profile` returned by `createConsenti()` — resolves which cookies belong to
|
|
1745
|
+
* this category for the record's profile (categories are defined on the consent template). */
|
|
1746
|
+
profiles: ProfileService;
|
|
1747
|
+
/** Called when every cookie in the category transitions to `'granted'`. */
|
|
1748
|
+
onGrant?: (params: CategoryActionParams) => void;
|
|
1749
|
+
/** Called when the category transitions away from fully granted. */
|
|
1750
|
+
onDeny?: (params: CategoryActionParams) => void;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Watches a category's rollup consent status across every incoming `consent.created`/
|
|
1754
|
+
* `consent.updated` event and fires `onGrant`/`onDeny` on transitions. Call
|
|
1755
|
+
* {@link CategoryAction.destroy} to remove the event listeners when no longer needed.
|
|
1756
|
+
*/
|
|
1757
|
+
declare class CategoryAction {
|
|
1758
|
+
private readonly options;
|
|
1759
|
+
private readonly onCreated;
|
|
1760
|
+
private readonly onUpdated;
|
|
1761
|
+
constructor(options: CategoryActionOptions);
|
|
1762
|
+
private fire;
|
|
1763
|
+
/** Removes the event listeners. Call when the hook is no longer needed. */
|
|
1764
|
+
destroy(): void;
|
|
1057
1765
|
}
|
|
1058
1766
|
|
|
1059
1767
|
declare const dashboardDir: string;
|
|
1060
1768
|
declare function createConsenti(userConfig: ConsentiServerConfig): {
|
|
1061
1769
|
router: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
|
|
1062
1770
|
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1063
|
-
|
|
1771
|
+
fastifyHandler: (fastify: {
|
|
1064
1772
|
all: (path: string, handler: (req: {
|
|
1065
1773
|
raw: IncomingMessage;
|
|
1066
1774
|
}, reply: {
|
|
@@ -1078,8 +1786,9 @@ declare function createConsenti(userConfig: ConsentiServerConfig): {
|
|
|
1078
1786
|
user: UserService;
|
|
1079
1787
|
};
|
|
1080
1788
|
storage: StorageAdapter;
|
|
1081
|
-
eventBus: EventEmitter<
|
|
1789
|
+
eventBus: EventEmitter<any>;
|
|
1790
|
+
ready: Promise<void>;
|
|
1082
1791
|
destroy: () => Promise<void>;
|
|
1083
1792
|
};
|
|
1084
1793
|
|
|
1085
|
-
export { type AdminUser, type AuditLog, 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 };
|
|
1794
|
+
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 };
|