@peax/consent-core 0.6.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/LICENSE +201 -0
- package/README.md +207 -0
- package/dist/ConsentEnforcementError-Bn_ayWJI.js +16 -0
- package/dist/assertStorageIdentifier-BQuj-9bp.js +7 -0
- package/dist/createConsentIframe-Bw-cl--m.js +93 -0
- package/dist/createConsentRequestScope-D7U8uK6m.js +58 -0
- package/dist/createConsentScript-C_4ecSTX.js +102 -0
- package/dist/createDomGuard-CuIEC0HP.js +101 -0
- package/dist/enforcement/axios.d.ts +222 -0
- package/dist/enforcement/axios.js +92 -0
- package/dist/enforcement/fetch.d.ts +222 -0
- package/dist/enforcement/fetch.js +40 -0
- package/dist/enforcement/iframe.d.ts +245 -0
- package/dist/enforcement/iframe.js +2 -0
- package/dist/enforcement/integration.d.ts +230 -0
- package/dist/enforcement/integration.js +181 -0
- package/dist/enforcement/markup.d.ts +214 -0
- package/dist/enforcement/markup.js +300 -0
- package/dist/enforcement/posthog.d.ts +68 -0
- package/dist/enforcement/posthog.js +96 -0
- package/dist/enforcement/request-rules.d.ts +248 -0
- package/dist/enforcement/request-rules.js +434 -0
- package/dist/enforcement/script.d.ts +240 -0
- package/dist/enforcement/script.js +2 -0
- package/dist/index.d.ts +258 -0
- package/dist/index.js +445 -0
- package/dist/storage/localforage.d.ts +23 -0
- package/dist/storage/localforage.js +26 -0
- package/dist/storage/sync.d.ts +278 -0
- package/dist/storage/sync.js +370 -0
- package/dist/storage.d.ts +77 -0
- package/dist/storage.js +207 -0
- package/dist/validate-D16G7mdG.js +15 -0
- package/package.json +128 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
//#region src/policy/arePoliciesEqual.ts
|
|
3
|
+
/** Structural equality of validated policies; array order matters, object key order does not. */
|
|
4
|
+
function arePoliciesEqual(first, second) {
|
|
5
|
+
if (first.purposes.length !== second.purposes.length || first.uses.length !== second.uses.length) return false;
|
|
6
|
+
if (!first.purposes.every((purpose, index) => {
|
|
7
|
+
const other = second.purposes[index];
|
|
8
|
+
return other !== void 0 && purpose.id === other.id && purpose.title === other.title && purpose.description === other.description;
|
|
9
|
+
})) return false;
|
|
10
|
+
return first.uses.every((use, index) => {
|
|
11
|
+
const other = second.uses[index];
|
|
12
|
+
return other !== void 0 && use.id === other.id && use.serviceId === other.serviceId && use.categoryId === other.categoryId && use.purposeId === other.purposeId && use.consentVersion === other.consentVersion;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/utils/validation.ts
|
|
17
|
+
var nonemptyStringSchema = /* @__PURE__ */ (() => z.string({ error: "Expected a nonempty string." }).check(z.refine((value) => value.trim().length > 0, { error: "Expected a nonempty string." })))();
|
|
18
|
+
var consentVersionSchema = /* @__PURE__ */ (() => z.int({ error: "Expected a positive safe integer consent version." }).check(z.positive({ error: "Expected a positive safe integer consent version." })))();
|
|
19
|
+
/** Internal adapter: keep Zod errors out of the public result contract. */
|
|
20
|
+
function validateWithSchema(schema, input) {
|
|
21
|
+
const result = schema.safeParse(input, { error: (issue) => issue.code === "unrecognized_keys" ? `Unexpected fields: ${issue.keys.join(", ")}.` : issue.code === "invalid_type" ? `Expected ${issue.expected}.` : "Invalid consent data." });
|
|
22
|
+
if (result.success) return {
|
|
23
|
+
success: true,
|
|
24
|
+
data: result.data
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
issues: result.error.issues.map((issue) => ({
|
|
29
|
+
code: issue.code,
|
|
30
|
+
path: issue.path.map((segment) => typeof segment === "number" ? segment : String(segment)),
|
|
31
|
+
message: issue.message
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/policy/schemas.ts
|
|
37
|
+
var consentPurposeSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
38
|
+
id: nonemptyStringSchema,
|
|
39
|
+
title: nonemptyStringSchema,
|
|
40
|
+
description: nonemptyStringSchema
|
|
41
|
+
})))();
|
|
42
|
+
var consentUseSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
43
|
+
id: nonemptyStringSchema,
|
|
44
|
+
serviceId: nonemptyStringSchema,
|
|
45
|
+
categoryId: nonemptyStringSchema,
|
|
46
|
+
purposeId: nonemptyStringSchema,
|
|
47
|
+
/** Version of this use's consent scope, independent of other uses. */
|
|
48
|
+
consentVersion: consentVersionSchema
|
|
49
|
+
})))();
|
|
50
|
+
var consentPolicySchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
51
|
+
purposes: z.readonly(z.array(consentPurposeSchema).check((context) => {
|
|
52
|
+
const ids = /* @__PURE__ */ new Set();
|
|
53
|
+
context.value.forEach((purpose, index) => {
|
|
54
|
+
if (ids.has(purpose.id)) context.issues.push({
|
|
55
|
+
code: "custom",
|
|
56
|
+
path: [index, "id"],
|
|
57
|
+
message: "Consent purpose IDs must be unique.",
|
|
58
|
+
input: purpose.id
|
|
59
|
+
});
|
|
60
|
+
ids.add(purpose.id);
|
|
61
|
+
});
|
|
62
|
+
})),
|
|
63
|
+
uses: z.readonly(z.array(consentUseSchema).check((context) => {
|
|
64
|
+
const ids = /* @__PURE__ */ new Set();
|
|
65
|
+
context.value.forEach((use, index) => {
|
|
66
|
+
if (ids.has(use.id)) context.issues.push({
|
|
67
|
+
code: "custom",
|
|
68
|
+
path: [index, "id"],
|
|
69
|
+
message: "Consent use IDs must be unique.",
|
|
70
|
+
input: use.id
|
|
71
|
+
});
|
|
72
|
+
ids.add(use.id);
|
|
73
|
+
});
|
|
74
|
+
}))
|
|
75
|
+
}).check((context) => {
|
|
76
|
+
if (context.issues.length > 0) return;
|
|
77
|
+
const purposeIds = new Set(context.value.purposes.map((purpose) => purpose.id));
|
|
78
|
+
context.value.uses.forEach((use, index) => {
|
|
79
|
+
if (!purposeIds.has(use.purposeId)) context.issues.push({
|
|
80
|
+
code: "custom",
|
|
81
|
+
path: [
|
|
82
|
+
"uses",
|
|
83
|
+
index,
|
|
84
|
+
"purposeId"
|
|
85
|
+
],
|
|
86
|
+
message: "Consent use must reference a declared purpose.",
|
|
87
|
+
input: use.purposeId
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
})))();
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/policy/validateConsentPolicy.ts
|
|
93
|
+
/** Validates policy structure, not its approval or the legal adequacy of its purposes. */
|
|
94
|
+
function validateConsentPolicy(input) {
|
|
95
|
+
return validateWithSchema(consentPolicySchema, input);
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/consent/areConsentStatesEqual.ts
|
|
99
|
+
/** Compare recorded choices, not permission or policy compatibility. Choice order is preserved. */
|
|
100
|
+
function areConsentStatesEqual(first, second) {
|
|
101
|
+
return first.choices.length === second.choices.length && first.choices.every((choice, index) => {
|
|
102
|
+
const other = second.choices[index];
|
|
103
|
+
return other?.useId === choice.useId && other.consentVersion === choice.consentVersion && other.decision === choice.decision;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/consent/createConsentSubscription.ts
|
|
108
|
+
/** Synchronous invalidation notifications, not a log of every intermediate state. */
|
|
109
|
+
function createConsentSubscription() {
|
|
110
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
111
|
+
let notifying = false;
|
|
112
|
+
let pending = false;
|
|
113
|
+
return {
|
|
114
|
+
subscribe(listener) {
|
|
115
|
+
if (typeof listener !== "function") throw new TypeError("Expected a subscription listener.");
|
|
116
|
+
const subscription = () => listener();
|
|
117
|
+
listeners.add(subscription);
|
|
118
|
+
return () => {
|
|
119
|
+
listeners.delete(subscription);
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
notify() {
|
|
123
|
+
pending = true;
|
|
124
|
+
if (notifying) return;
|
|
125
|
+
notifying = true;
|
|
126
|
+
const errors = [];
|
|
127
|
+
try {
|
|
128
|
+
do {
|
|
129
|
+
pending = false;
|
|
130
|
+
for (const listener of [...listeners]) {
|
|
131
|
+
if (!listeners.has(listener)) continue;
|
|
132
|
+
try {
|
|
133
|
+
listener();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
errors.push(error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} while (pending);
|
|
139
|
+
} finally {
|
|
140
|
+
notifying = false;
|
|
141
|
+
}
|
|
142
|
+
if (errors.length > 0) throw new AggregateError(errors, "Consent subscribers failed after the change was committed.");
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/consent/evaluateConsent.ts
|
|
148
|
+
/**
|
|
149
|
+
* Evaluates one use without mutating inputs or performing enforcement.
|
|
150
|
+
* Inputs must be validated, with unique policy use IDs and at most one choice
|
|
151
|
+
* per use. Denials remain blocked regardless of their consent version.
|
|
152
|
+
* A version mismatch blocks only this use, not other unchanged uses.
|
|
153
|
+
*/
|
|
154
|
+
function evaluateConsent({ currentAppPolicy, state, useId }) {
|
|
155
|
+
const use = currentAppPolicy.uses.find((use) => use.id === useId);
|
|
156
|
+
if (!use) return {
|
|
157
|
+
allowed: false,
|
|
158
|
+
reason: "unknown-use"
|
|
159
|
+
};
|
|
160
|
+
const choice = state.choices.find((choice) => choice.useId === useId);
|
|
161
|
+
if (!choice) return {
|
|
162
|
+
allowed: false,
|
|
163
|
+
reason: "undecided"
|
|
164
|
+
};
|
|
165
|
+
if (choice.decision === "denied") return {
|
|
166
|
+
allowed: false,
|
|
167
|
+
reason: "denied"
|
|
168
|
+
};
|
|
169
|
+
if (choice.consentVersion !== use.consentVersion) return {
|
|
170
|
+
allowed: false,
|
|
171
|
+
reason: "consent-version-mismatch"
|
|
172
|
+
};
|
|
173
|
+
return { allowed: true };
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/consent/getMismatchedConsent.ts
|
|
177
|
+
/**
|
|
178
|
+
* Lists undecided uses and mismatched grants in policy order, without changing
|
|
179
|
+
* choices or prompting the user. Inputs must follow evaluateConsent's validated
|
|
180
|
+
* input contract. An empty result is not blanket permission: denials are omitted.
|
|
181
|
+
*/
|
|
182
|
+
function getMismatchedConsent({ currentAppPolicy, state }) {
|
|
183
|
+
return currentAppPolicy.uses.flatMap((use) => {
|
|
184
|
+
const evaluation = evaluateConsent({
|
|
185
|
+
currentAppPolicy,
|
|
186
|
+
state,
|
|
187
|
+
useId: use.id
|
|
188
|
+
});
|
|
189
|
+
if (evaluation.allowed) return [];
|
|
190
|
+
if (evaluation.reason !== "undecided" && evaluation.reason !== "consent-version-mismatch") return [];
|
|
191
|
+
return [{
|
|
192
|
+
useId: use.id,
|
|
193
|
+
categoryId: use.categoryId,
|
|
194
|
+
consentVersion: use.consentVersion,
|
|
195
|
+
reason: evaluation.reason
|
|
196
|
+
}];
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/consent/schemas.ts
|
|
201
|
+
var consentDecisionSchema = /* @__PURE__ */ z.enum(["granted", "denied"], { error: "Expected granted or denied." });
|
|
202
|
+
var consentChoiceSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
203
|
+
useId: nonemptyStringSchema,
|
|
204
|
+
consentVersion: consentVersionSchema,
|
|
205
|
+
decision: consentDecisionSchema
|
|
206
|
+
})))();
|
|
207
|
+
var consentStateSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({ choices: z.readonly(z.array(consentChoiceSchema).check((context) => {
|
|
208
|
+
const ids = /* @__PURE__ */ new Set();
|
|
209
|
+
context.value.forEach((choice, index) => {
|
|
210
|
+
if (ids.has(choice.useId)) context.issues.push({
|
|
211
|
+
code: "custom",
|
|
212
|
+
path: [index, "useId"],
|
|
213
|
+
message: "Only one consent choice per use is allowed.",
|
|
214
|
+
input: choice.useId
|
|
215
|
+
});
|
|
216
|
+
ids.add(choice.useId);
|
|
217
|
+
});
|
|
218
|
+
})) })))();
|
|
219
|
+
var createConsentOptionsSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
220
|
+
currentAppPolicy: consentPolicySchema,
|
|
221
|
+
initialState: consentStateSchema
|
|
222
|
+
})))();
|
|
223
|
+
var consentDecisionCommandSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
224
|
+
userConsentedPolicy: consentPolicySchema,
|
|
225
|
+
useId: nonemptyStringSchema,
|
|
226
|
+
decision: consentDecisionSchema
|
|
227
|
+
})))();
|
|
228
|
+
var categoryConsentCommandSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
229
|
+
userConsentedPolicy: consentPolicySchema,
|
|
230
|
+
categoryId: nonemptyStringSchema,
|
|
231
|
+
decision: consentDecisionSchema
|
|
232
|
+
})))();
|
|
233
|
+
var consentDecisionsCommandSchema = /* @__PURE__ */ (() => z.readonly(z.strictObject({
|
|
234
|
+
userConsentedPolicy: consentPolicySchema,
|
|
235
|
+
decisions: z.readonly(z.array(z.union([z.readonly(z.strictObject({
|
|
236
|
+
useId: nonemptyStringSchema,
|
|
237
|
+
decision: consentDecisionSchema
|
|
238
|
+
})), z.readonly(z.strictObject({
|
|
239
|
+
categoryId: nonemptyStringSchema,
|
|
240
|
+
decision: consentDecisionSchema
|
|
241
|
+
}))])))
|
|
242
|
+
})))();
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/consent/setConsent.ts
|
|
245
|
+
/**
|
|
246
|
+
* Records an explicit decision for every use in a category of the supplied policy.
|
|
247
|
+
* Pass only the policy scope presented to the user; this creates no blanket grant
|
|
248
|
+
* for future uses. Inputs follow the same validation contract as setConsentDecision.
|
|
249
|
+
* Throws when no uses match; returns the same state when all choices are unchanged.
|
|
250
|
+
*/
|
|
251
|
+
function setCategoryConsent({ userConsentedPolicy, state, categoryId, decision }) {
|
|
252
|
+
const uses = userConsentedPolicy.uses.filter((use) => use.categoryId === categoryId);
|
|
253
|
+
if (uses.length === 0) throw new Error(`Unknown consent category: ${categoryId}`);
|
|
254
|
+
return uses.reduce((nextState, use) => setConsentDecision({
|
|
255
|
+
userConsentedPolicy,
|
|
256
|
+
decision,
|
|
257
|
+
state: nextState,
|
|
258
|
+
useId: use.id
|
|
259
|
+
}), state);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Records an explicit decision for one use at the supplied policy's version.
|
|
263
|
+
* Pass the policy presented to the user, not a newer policy they have not seen.
|
|
264
|
+
* Inputs must be validated, with unique use IDs and at most one choice per use.
|
|
265
|
+
* Throws for an unknown use; returns the same state for an identical choice.
|
|
266
|
+
*/
|
|
267
|
+
function setConsentDecision({ userConsentedPolicy, state, useId, decision }) {
|
|
268
|
+
const use = userConsentedPolicy.uses.find((use) => use.id === useId);
|
|
269
|
+
if (!use) throw new Error(`Unknown consent use: ${useId}`);
|
|
270
|
+
const previous = state.choices.find((choice) => choice.useId === useId);
|
|
271
|
+
if (previous?.decision === decision && previous.consentVersion === use.consentVersion) return state;
|
|
272
|
+
const choice = {
|
|
273
|
+
useId,
|
|
274
|
+
consentVersion: use.consentVersion,
|
|
275
|
+
decision
|
|
276
|
+
};
|
|
277
|
+
return { choices: previous ? state.choices.map((existing) => existing.useId === useId ? choice : existing) : [...state.choices, choice] };
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/consent/setConsentDecisions.ts
|
|
281
|
+
/** Validated command in, one pure merged state out; no partially applied targets. */
|
|
282
|
+
function setConsentDecisions({ userConsentedPolicy, decisions }, state) {
|
|
283
|
+
const targets = /* @__PURE__ */ new Set();
|
|
284
|
+
const selected = /* @__PURE__ */ new Map();
|
|
285
|
+
const issues = [];
|
|
286
|
+
decisions.forEach((item, index) => {
|
|
287
|
+
const field = "useId" in item ? "useId" : "categoryId";
|
|
288
|
+
const id = "useId" in item ? item.useId : item.categoryId;
|
|
289
|
+
const path = [
|
|
290
|
+
"decisions",
|
|
291
|
+
index,
|
|
292
|
+
field
|
|
293
|
+
];
|
|
294
|
+
const target = `${field}:${id}`;
|
|
295
|
+
if (targets.has(target)) {
|
|
296
|
+
issues.push({
|
|
297
|
+
code: "duplicate_target",
|
|
298
|
+
path,
|
|
299
|
+
message: "Duplicate consent target."
|
|
300
|
+
});
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
targets.add(target);
|
|
304
|
+
const uses = userConsentedPolicy.uses.filter((use) => field === "useId" ? use.id === id : use.categoryId === id);
|
|
305
|
+
if (!uses.length) issues.push({
|
|
306
|
+
code: "unknown_target",
|
|
307
|
+
path,
|
|
308
|
+
message: "Unknown consent target."
|
|
309
|
+
});
|
|
310
|
+
uses.forEach((use) => {
|
|
311
|
+
if (selected.has(use.id)) issues.push({
|
|
312
|
+
code: "overlapping_target",
|
|
313
|
+
path,
|
|
314
|
+
message: "Consent targets must not overlap."
|
|
315
|
+
});
|
|
316
|
+
else selected.set(use.id, {
|
|
317
|
+
useId: use.id,
|
|
318
|
+
consentVersion: use.consentVersion,
|
|
319
|
+
decision: item.decision
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
if (issues.length) return {
|
|
324
|
+
success: false,
|
|
325
|
+
issues
|
|
326
|
+
};
|
|
327
|
+
let changed = false;
|
|
328
|
+
const choices = state.choices.map((previous) => {
|
|
329
|
+
const next = selected.get(previous.useId);
|
|
330
|
+
selected.delete(previous.useId);
|
|
331
|
+
if (!next || next.decision === previous.decision && next.consentVersion === previous.consentVersion) return previous;
|
|
332
|
+
changed = true;
|
|
333
|
+
return next;
|
|
334
|
+
});
|
|
335
|
+
userConsentedPolicy.uses.forEach((use) => {
|
|
336
|
+
const choice = selected.get(use.id);
|
|
337
|
+
if (choice) {
|
|
338
|
+
choices.push(choice);
|
|
339
|
+
changed = true;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
success: true,
|
|
344
|
+
data: changed ? { choices } : state
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/consent/createConsent.ts
|
|
349
|
+
function createSnapshot(currentAppPolicy, state) {
|
|
350
|
+
state.choices.forEach((choice) => {
|
|
351
|
+
Object.freeze(choice);
|
|
352
|
+
});
|
|
353
|
+
Object.freeze(state.choices);
|
|
354
|
+
Object.freeze(state);
|
|
355
|
+
return Object.freeze({
|
|
356
|
+
currentAppPolicy,
|
|
357
|
+
state
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function snapshotRequirements(snapshot) {
|
|
361
|
+
return Object.freeze(getMismatchedConsent(snapshot).map((requirement) => Object.freeze(requirement)));
|
|
362
|
+
}
|
|
363
|
+
/** Creates an isolated runtime from decoded data; invalid input creates no instance. */
|
|
364
|
+
function createConsent(input) {
|
|
365
|
+
const result = validateWithSchema(createConsentOptionsSchema, input);
|
|
366
|
+
if (!result.success) return result;
|
|
367
|
+
let snapshot = createSnapshot(result.data.currentAppPolicy, result.data.initialState);
|
|
368
|
+
let requirements = snapshotRequirements(snapshot);
|
|
369
|
+
const subscriptions = createConsentSubscription();
|
|
370
|
+
function commit(currentAppPolicy, state) {
|
|
371
|
+
if (snapshot.currentAppPolicy === currentAppPolicy && snapshot.state === state) return {
|
|
372
|
+
success: true,
|
|
373
|
+
data: snapshot
|
|
374
|
+
};
|
|
375
|
+
const next = createSnapshot(currentAppPolicy, state);
|
|
376
|
+
const nextRequirements = snapshotRequirements(next);
|
|
377
|
+
snapshot = next;
|
|
378
|
+
requirements = nextRequirements;
|
|
379
|
+
subscriptions.notify();
|
|
380
|
+
return {
|
|
381
|
+
success: true,
|
|
382
|
+
data: next
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
success: true,
|
|
387
|
+
data: Object.freeze({
|
|
388
|
+
getSnapshot: () => snapshot,
|
|
389
|
+
evaluate: (useId) => evaluateConsent({
|
|
390
|
+
...snapshot,
|
|
391
|
+
useId
|
|
392
|
+
}),
|
|
393
|
+
getMismatchedConsent: () => requirements,
|
|
394
|
+
restoreState(input) {
|
|
395
|
+
const parsed = validateWithSchema(consentStateSchema, input);
|
|
396
|
+
if (!parsed.success) return parsed;
|
|
397
|
+
if (areConsentStatesEqual(parsed.data, snapshot.state)) return {
|
|
398
|
+
success: true,
|
|
399
|
+
data: snapshot
|
|
400
|
+
};
|
|
401
|
+
return commit(snapshot.currentAppPolicy, parsed.data);
|
|
402
|
+
},
|
|
403
|
+
setConsentDecision(command) {
|
|
404
|
+
const parsed = validateWithSchema(consentDecisionCommandSchema, command);
|
|
405
|
+
if (!parsed.success) return parsed;
|
|
406
|
+
const { userConsentedPolicy, useId, decision } = parsed.data;
|
|
407
|
+
return commit(snapshot.currentAppPolicy, setConsentDecision({
|
|
408
|
+
userConsentedPolicy,
|
|
409
|
+
state: snapshot.state,
|
|
410
|
+
useId,
|
|
411
|
+
decision
|
|
412
|
+
}));
|
|
413
|
+
},
|
|
414
|
+
setCategoryConsent(command) {
|
|
415
|
+
const parsed = validateWithSchema(categoryConsentCommandSchema, command);
|
|
416
|
+
if (!parsed.success) return parsed;
|
|
417
|
+
const { userConsentedPolicy, categoryId, decision } = parsed.data;
|
|
418
|
+
return commit(snapshot.currentAppPolicy, setCategoryConsent({
|
|
419
|
+
userConsentedPolicy,
|
|
420
|
+
state: snapshot.state,
|
|
421
|
+
categoryId,
|
|
422
|
+
decision
|
|
423
|
+
}));
|
|
424
|
+
},
|
|
425
|
+
updatePolicy(input) {
|
|
426
|
+
const parsed = validateConsentPolicy(input);
|
|
427
|
+
if (!parsed.success) return parsed;
|
|
428
|
+
if (arePoliciesEqual(parsed.data, snapshot.currentAppPolicy)) return {
|
|
429
|
+
success: true,
|
|
430
|
+
data: snapshot
|
|
431
|
+
};
|
|
432
|
+
return commit(parsed.data, snapshot.state);
|
|
433
|
+
},
|
|
434
|
+
setConsentDecisions(command) {
|
|
435
|
+
const parsed = validateWithSchema(consentDecisionsCommandSchema, command);
|
|
436
|
+
if (!parsed.success) return parsed;
|
|
437
|
+
const result = setConsentDecisions(parsed.data, snapshot.state);
|
|
438
|
+
return result.success ? commit(snapshot.currentAppPolicy, result.data) : result;
|
|
439
|
+
},
|
|
440
|
+
subscribe: subscriptions.subscribe
|
|
441
|
+
})
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
export { createConsent };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** One record, scoped by the adapter's key or endpoint. Only null means absent. */
|
|
2
|
+
declare type ConsentStorageAdapter = Readonly<{
|
|
3
|
+
read: () => string | null | Promise<string | null>;
|
|
4
|
+
write: (record: string) => void | Promise<void>;
|
|
5
|
+
/** Optional lazy, key-scoped external invalidations; notifications contain no stored values. */
|
|
6
|
+
subscribe?: (listener: () => void) => () => void;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
export declare function createLocalForageAdapter({ key, store }: LocalForageAdapterOptions): ConsentStorageAdapter;
|
|
10
|
+
|
|
11
|
+
export declare type LocalForageAdapterOptions = Readonly<{
|
|
12
|
+
key: string;
|
|
13
|
+
/** Supply an existing configured instance, or use the lazy peax-consent/consent store. */
|
|
14
|
+
store?: LocalForageStore;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
/** Structural interface keeps optional peer declarations out of the other entry points. */
|
|
18
|
+
export declare type LocalForageStore = Readonly<{
|
|
19
|
+
getItem: (key: string) => Promise<unknown>;
|
|
20
|
+
setItem: (key: string, record: string) => Promise<unknown>;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
export { }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { t as assertStorageIdentifier } from "../assertStorageIdentifier-BQuj-9bp.js";
|
|
2
|
+
//#region src/storage/localforage/createLocalForageAdapter.ts
|
|
3
|
+
function createLocalForageAdapter({ key, store }) {
|
|
4
|
+
assertStorageIdentifier(key);
|
|
5
|
+
let pendingStore;
|
|
6
|
+
const getStore = () => {
|
|
7
|
+
if (store) return Promise.resolve(store);
|
|
8
|
+
pendingStore ??= import("localforage").then(({ default: localforage }) => localforage.createInstance({
|
|
9
|
+
name: "peax-consent",
|
|
10
|
+
storeName: "consent"
|
|
11
|
+
}));
|
|
12
|
+
return pendingStore;
|
|
13
|
+
};
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
read: async () => {
|
|
16
|
+
const record = await (await getStore()).getItem(key);
|
|
17
|
+
if (record !== null && typeof record !== "string") throw new TypeError("Expected a stored string or null.");
|
|
18
|
+
return record;
|
|
19
|
+
},
|
|
20
|
+
write: async (record) => {
|
|
21
|
+
await (await getStore()).setItem(key, record);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createLocalForageAdapter };
|