@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
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import * as z from 'zod/mini';
|
|
2
|
+
|
|
3
|
+
/** Disjoint category/use decisions for one scope actually presented to the user. */
|
|
4
|
+
declare type BatchCommand = z.infer<typeof consentDecisionsCommandSchema>;
|
|
5
|
+
|
|
6
|
+
/** Records choices only for category members in userConsentedPolicy, not future members. */
|
|
7
|
+
declare type CategoryConsentCommand = z.infer<typeof categoryConsentCommandSchema>;
|
|
8
|
+
|
|
9
|
+
declare const categoryConsentCommandSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
10
|
+
userConsentedPolicy: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
11
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
12
|
+
id: z.ZodMiniString<string>;
|
|
13
|
+
title: z.ZodMiniString<string>;
|
|
14
|
+
description: z.ZodMiniString<string>;
|
|
15
|
+
}, z.core.$strict>>>>;
|
|
16
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
17
|
+
id: z.ZodMiniString<string>;
|
|
18
|
+
serviceId: z.ZodMiniString<string>;
|
|
19
|
+
categoryId: z.ZodMiniString<string>;
|
|
20
|
+
purposeId: z.ZodMiniString<string>;
|
|
21
|
+
consentVersion: z.ZodMiniInt;
|
|
22
|
+
}, z.core.$strict>>>>;
|
|
23
|
+
}, z.core.$strict>>;
|
|
24
|
+
categoryId: z.ZodMiniString<string>;
|
|
25
|
+
decision: z.ZodMiniEnum<{
|
|
26
|
+
granted: "granted";
|
|
27
|
+
denied: "denied";
|
|
28
|
+
}>;
|
|
29
|
+
}, z.core.$strict>>;
|
|
30
|
+
|
|
31
|
+
/** userConsentedPolicy is the scope shown for this decision, including a denial. */
|
|
32
|
+
declare type ConsentDecisionCommand = z.infer<typeof consentDecisionCommandSchema>;
|
|
33
|
+
|
|
34
|
+
declare const consentDecisionCommandSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
35
|
+
userConsentedPolicy: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
36
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
37
|
+
id: z.ZodMiniString<string>;
|
|
38
|
+
title: z.ZodMiniString<string>;
|
|
39
|
+
description: z.ZodMiniString<string>;
|
|
40
|
+
}, z.core.$strict>>>>;
|
|
41
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
42
|
+
id: z.ZodMiniString<string>;
|
|
43
|
+
serviceId: z.ZodMiniString<string>;
|
|
44
|
+
categoryId: z.ZodMiniString<string>;
|
|
45
|
+
purposeId: z.ZodMiniString<string>;
|
|
46
|
+
consentVersion: z.ZodMiniInt;
|
|
47
|
+
}, z.core.$strict>>>>;
|
|
48
|
+
}, z.core.$strict>>;
|
|
49
|
+
useId: z.ZodMiniString<string>;
|
|
50
|
+
decision: z.ZodMiniEnum<{
|
|
51
|
+
granted: "granted";
|
|
52
|
+
denied: "denied";
|
|
53
|
+
}>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
|
|
56
|
+
declare type ConsentDecisionsCommand = Readonly<Omit<BatchCommand, 'decisions'> & {
|
|
57
|
+
decisions: readonly ExclusiveTarget<BatchCommand['decisions'][number]>[];
|
|
58
|
+
}>;
|
|
59
|
+
|
|
60
|
+
declare const consentDecisionsCommandSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
61
|
+
userConsentedPolicy: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
62
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
63
|
+
id: z.ZodMiniString<string>;
|
|
64
|
+
title: z.ZodMiniString<string>;
|
|
65
|
+
description: z.ZodMiniString<string>;
|
|
66
|
+
}, z.core.$strict>>>>;
|
|
67
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
68
|
+
id: z.ZodMiniString<string>;
|
|
69
|
+
serviceId: z.ZodMiniString<string>;
|
|
70
|
+
categoryId: z.ZodMiniString<string>;
|
|
71
|
+
purposeId: z.ZodMiniString<string>;
|
|
72
|
+
consentVersion: z.ZodMiniInt;
|
|
73
|
+
}, z.core.$strict>>>>;
|
|
74
|
+
}, z.core.$strict>>;
|
|
75
|
+
decisions: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniUnion<readonly [z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
76
|
+
useId: z.ZodMiniString<string>;
|
|
77
|
+
decision: z.ZodMiniEnum<{
|
|
78
|
+
granted: "granted";
|
|
79
|
+
denied: "denied";
|
|
80
|
+
}>;
|
|
81
|
+
}, z.core.$strict>>, z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
82
|
+
categoryId: z.ZodMiniString<string>;
|
|
83
|
+
decision: z.ZodMiniEnum<{
|
|
84
|
+
granted: "granted";
|
|
85
|
+
denied: "denied";
|
|
86
|
+
}>;
|
|
87
|
+
}, z.core.$strict>>]>>>;
|
|
88
|
+
}, z.core.$strict>>;
|
|
89
|
+
|
|
90
|
+
declare type ConsentDeniedReason = 'unknown-use' | 'undecided' | 'denied' | 'consent-version-mismatch';
|
|
91
|
+
|
|
92
|
+
declare type ConsentEvaluation = Readonly<{
|
|
93
|
+
allowed: true;
|
|
94
|
+
}> | Readonly<{
|
|
95
|
+
allowed: false;
|
|
96
|
+
reason: ConsentDeniedReason;
|
|
97
|
+
}>;
|
|
98
|
+
|
|
99
|
+
export declare type ConsentIntegrationActivation = Readonly<{
|
|
100
|
+
/** Called when consent stops allowing this use or the manager is disposed. */
|
|
101
|
+
deactivate: (reason: IntegrationDeactivationReason) => void | Promise<void>;
|
|
102
|
+
/** Executed code cannot be undone; a fresh page is required after deactivation. */
|
|
103
|
+
reloadOnDeactivate?: boolean;
|
|
104
|
+
}>;
|
|
105
|
+
|
|
106
|
+
export declare type ConsentIntegrationDefinition = Readonly<{
|
|
107
|
+
id: string;
|
|
108
|
+
useId: string;
|
|
109
|
+
activate: (signal: AbortSignal) => ConsentIntegrationActivation | Promise<ConsentIntegrationActivation>;
|
|
110
|
+
/** Optional bridge for an SDK the application initialized before this manager started. */
|
|
111
|
+
onBlocked?: () => void;
|
|
112
|
+
/** Conservatively require a reload if activation fails after partial SDK initialization. */
|
|
113
|
+
reloadOnError?: boolean;
|
|
114
|
+
}>;
|
|
115
|
+
|
|
116
|
+
export declare type ConsentIntegrationManager = Readonly<{
|
|
117
|
+
/** Listener failures are reported after the pass; successfully started work is not rolled back. */
|
|
118
|
+
start: () => void;
|
|
119
|
+
getStatus: (id: string) => ConsentIntegrationStatus;
|
|
120
|
+
/** After disposal begins, returns a no-op unsubscribe without retaining the listener. */
|
|
121
|
+
subscribe: (listener: () => void) => () => void;
|
|
122
|
+
/** Aborts/waits for all hooks and clears listeners before reporting notification failures. Never reloads. */
|
|
123
|
+
dispose: () => Promise<void>;
|
|
124
|
+
}>;
|
|
125
|
+
|
|
126
|
+
export declare type ConsentIntegrationManagerOptions = Readonly<{
|
|
127
|
+
consent: Pick<ConsentRuntime, 'evaluate' | 'subscribe'>;
|
|
128
|
+
integrations: readonly ConsentIntegrationDefinition[];
|
|
129
|
+
}>;
|
|
130
|
+
|
|
131
|
+
export declare type ConsentIntegrationStatus = Readonly<{
|
|
132
|
+
state: 'idle' | 'blocked' | 'activating' | 'active' | 'error' | 'reload-required' | 'disposed';
|
|
133
|
+
reloadRequired: boolean;
|
|
134
|
+
error?: unknown;
|
|
135
|
+
}>;
|
|
136
|
+
|
|
137
|
+
declare type ConsentPolicy = z.infer<typeof consentPolicySchema>;
|
|
138
|
+
|
|
139
|
+
declare const consentPolicySchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
140
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
141
|
+
id: z.ZodMiniString<string>;
|
|
142
|
+
title: z.ZodMiniString<string>;
|
|
143
|
+
description: z.ZodMiniString<string>;
|
|
144
|
+
}, z.core.$strict>>>>;
|
|
145
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
146
|
+
id: z.ZodMiniString<string>;
|
|
147
|
+
serviceId: z.ZodMiniString<string>;
|
|
148
|
+
categoryId: z.ZodMiniString<string>;
|
|
149
|
+
purposeId: z.ZodMiniString<string>;
|
|
150
|
+
consentVersion: z.ZodMiniInt;
|
|
151
|
+
}, z.core.$strict>>>>;
|
|
152
|
+
}, z.core.$strict>>;
|
|
153
|
+
|
|
154
|
+
/** A current use with no decision or a grant for a different consent version. */
|
|
155
|
+
declare type ConsentRequirement = Readonly<{
|
|
156
|
+
useId: string;
|
|
157
|
+
categoryId: string;
|
|
158
|
+
/** The version required by the current policy for this use. */
|
|
159
|
+
consentVersion: number;
|
|
160
|
+
reason: 'undecided' | 'consent-version-mismatch';
|
|
161
|
+
}>;
|
|
162
|
+
|
|
163
|
+
/** In-memory runtime; methods can be passed as callbacks without binding. */
|
|
164
|
+
declare type ConsentRuntime = Readonly<{
|
|
165
|
+
getSnapshot: () => ConsentSnapshot;
|
|
166
|
+
evaluate: (useId: string) => ConsentEvaluation;
|
|
167
|
+
/** Includes undecided uses and version-mismatched grants, not explicit denials. */
|
|
168
|
+
getMismatchedConsent: () => readonly ConsentRequirement[];
|
|
169
|
+
/**
|
|
170
|
+
* Replace all choices from trusted persistence, preserving unknown uses and recorded versions.
|
|
171
|
+
* Validation checks structure, not evidence of user consent; use decision methods for new choices.
|
|
172
|
+
* Does not replace policy, renew versions or perform I/O. Effective changes notify existing guards.
|
|
173
|
+
*/
|
|
174
|
+
restoreState: (state: unknown) => ValidationResult<ConsentSnapshot>;
|
|
175
|
+
setConsentDecision: (command: ConsentDecisionCommand) => ValidationResult<ConsentSnapshot>;
|
|
176
|
+
setCategoryConsent: (command: CategoryConsentCommand) => ValidationResult<ConsentSnapshot>;
|
|
177
|
+
/** Validates all targets before one commit; observer errors can throw after that commit. */
|
|
178
|
+
setConsentDecisions: (command: ConsentDecisionsCommand) => ValidationResult<ConsentSnapshot>;
|
|
179
|
+
updatePolicy: (currentAppPolicy: unknown) => ValidationResult<ConsentSnapshot>;
|
|
180
|
+
subscribe: (listener: () => void) => () => void;
|
|
181
|
+
}>;
|
|
182
|
+
|
|
183
|
+
/** One active app policy and recorded choices; not a history of policies shown to users. */
|
|
184
|
+
declare type ConsentSnapshot = Readonly<{
|
|
185
|
+
currentAppPolicy: ConsentPolicy;
|
|
186
|
+
state: ConsentState;
|
|
187
|
+
}>;
|
|
188
|
+
|
|
189
|
+
declare type ConsentState = z.infer<typeof consentStateSchema>;
|
|
190
|
+
|
|
191
|
+
declare const consentStateSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
192
|
+
choices: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
193
|
+
useId: z.ZodMiniString<string>;
|
|
194
|
+
consentVersion: z.ZodMiniInt;
|
|
195
|
+
decision: z.ZodMiniEnum<{
|
|
196
|
+
granted: "granted";
|
|
197
|
+
denied: "denied";
|
|
198
|
+
}>;
|
|
199
|
+
}, z.core.$strict>>>>;
|
|
200
|
+
}, z.core.$strict>>;
|
|
201
|
+
|
|
202
|
+
/** Starts only declared integrations whose exact use is currently allowed. */
|
|
203
|
+
export declare function createConsentIntegrationManager({ consent, integrations, }: ConsentIntegrationManagerOptions): ConsentIntegrationManager;
|
|
204
|
+
|
|
205
|
+
declare type ExclusiveTarget<T> = T extends {
|
|
206
|
+
useId: string;
|
|
207
|
+
} ? T & {
|
|
208
|
+
readonly categoryId?: never;
|
|
209
|
+
} : T & {
|
|
210
|
+
readonly useId?: never;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export declare type IntegrationDeactivationReason = 'blocked' | 'disposed';
|
|
214
|
+
|
|
215
|
+
/** A validation issue without a copy of the rejected input. */
|
|
216
|
+
declare type ValidationIssue = Readonly<{
|
|
217
|
+
code: string;
|
|
218
|
+
path: readonly (string | number)[];
|
|
219
|
+
message: string;
|
|
220
|
+
}>;
|
|
221
|
+
|
|
222
|
+
declare type ValidationResult<T> = Readonly<{
|
|
223
|
+
success: true;
|
|
224
|
+
data: T;
|
|
225
|
+
}> | Readonly<{
|
|
226
|
+
success: false;
|
|
227
|
+
issues: readonly ValidationIssue[];
|
|
228
|
+
}>;
|
|
229
|
+
|
|
230
|
+
export { }
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { t as requireConsent } from "../validate-D16G7mdG.js";
|
|
2
|
+
//#region src/enforcement/integration/createConsentIntegrationManager.ts
|
|
3
|
+
var status = (state, reloadRequired = false, error) => Object.freeze({
|
|
4
|
+
state,
|
|
5
|
+
reloadRequired,
|
|
6
|
+
...error === void 0 ? {} : { error }
|
|
7
|
+
});
|
|
8
|
+
/** Starts only declared integrations whose exact use is currently allowed. */
|
|
9
|
+
function createConsentIntegrationManager({ consent, integrations }) {
|
|
10
|
+
requireConsent(consent);
|
|
11
|
+
const entries = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const definition of integrations) {
|
|
13
|
+
if (!definition || typeof definition.id !== "string" || !definition.id.trim()) throw new TypeError("Expected a non blank integration ID.");
|
|
14
|
+
if (typeof definition.useId !== "string" || !definition.useId.trim() || typeof definition.activate !== "function") throw new TypeError("Expected an integration use ID and activate function.");
|
|
15
|
+
if (entries.has(definition.id)) throw new TypeError(`Duplicate integration ID: ${definition.id}`);
|
|
16
|
+
entries.set(definition.id, {
|
|
17
|
+
definition,
|
|
18
|
+
status: status("idle"),
|
|
19
|
+
generation: 0,
|
|
20
|
+
blocked: false,
|
|
21
|
+
controller: void 0,
|
|
22
|
+
activation: void 0,
|
|
23
|
+
pending: void 0,
|
|
24
|
+
cleanup: void 0
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
let started = false;
|
|
28
|
+
let disposed = false;
|
|
29
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
30
|
+
let unsubscribe;
|
|
31
|
+
function publish(entry, next, failures) {
|
|
32
|
+
if (entry.status.state === next.state && entry.status.reloadRequired === next.reloadRequired && entry.status.error === next.error) return;
|
|
33
|
+
entry.status = next;
|
|
34
|
+
const errors = [];
|
|
35
|
+
for (const listener of [...listeners]) {
|
|
36
|
+
if (!listeners.has(listener)) continue;
|
|
37
|
+
try {
|
|
38
|
+
listener();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
errors.push(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!errors.length) return;
|
|
44
|
+
if (failures) failures.push(...errors);
|
|
45
|
+
else {
|
|
46
|
+
const current = entry.status;
|
|
47
|
+
entry.status = status(current.state, current.reloadRequired, new AggregateError([...current.error === void 0 ? [] : [current.error], ...errors], "Integration notification failed."));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function reportTaskError(entry, error, failures) {
|
|
51
|
+
publish(entry, status(disposed ? "disposed" : "error", true, error), failures);
|
|
52
|
+
}
|
|
53
|
+
function completeTask(entry, key, task) {
|
|
54
|
+
return task.then(() => {
|
|
55
|
+
entry[key] = void 0;
|
|
56
|
+
if (!disposed) reconcile(entry);
|
|
57
|
+
}).catch((error) => {
|
|
58
|
+
entry[key] = void 0;
|
|
59
|
+
reportTaskError(entry, error);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function deactivate(entry, reason, failures) {
|
|
63
|
+
entry.generation += 1;
|
|
64
|
+
entry.controller?.abort();
|
|
65
|
+
entry.controller = void 0;
|
|
66
|
+
const activation = entry.activation;
|
|
67
|
+
entry.activation = void 0;
|
|
68
|
+
const reloadRequired = entry.status.reloadRequired || Boolean(activation?.reloadOnDeactivate);
|
|
69
|
+
publish(entry, status(reason === "disposed" ? "disposed" : reloadRequired ? "reload-required" : "blocked", reloadRequired), failures);
|
|
70
|
+
if (!activation) return;
|
|
71
|
+
try {
|
|
72
|
+
entry.cleanup = completeTask(entry, "cleanup", Promise.resolve(activation.deactivate(reason)));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
reportTaskError(entry, error, failures);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function activate(entry, failures) {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
entry.controller = controller;
|
|
80
|
+
const generation = ++entry.generation;
|
|
81
|
+
publish(entry, status("activating"), failures);
|
|
82
|
+
entry.pending = completeTask(entry, "pending", (async () => {
|
|
83
|
+
try {
|
|
84
|
+
const activation = await Promise.resolve().then(() => {
|
|
85
|
+
if (controller.signal.aborted || !consent.evaluate(entry.definition.useId).allowed) return { deactivate: () => {} };
|
|
86
|
+
return entry.definition.activate(controller.signal);
|
|
87
|
+
});
|
|
88
|
+
if (!activation || typeof activation.deactivate !== "function") throw new TypeError("Integration activation must return a deactivation hook.");
|
|
89
|
+
if (disposed || generation !== entry.generation || controller.signal.aborted || !consent.evaluate(entry.definition.useId).allowed) {
|
|
90
|
+
if (activation.reloadOnDeactivate) publish(entry, status(disposed ? "disposed" : "reload-required", true));
|
|
91
|
+
try {
|
|
92
|
+
await activation.deactivate(disposed ? "disposed" : "blocked");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
reportTaskError(entry, error);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
entry.activation = activation;
|
|
99
|
+
publish(entry, status("active"));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (disposed || generation !== entry.generation || controller.signal.aborted) return;
|
|
102
|
+
publish(entry, status("error", entry.status.reloadRequired || Boolean(entry.definition.reloadOnError), error));
|
|
103
|
+
}
|
|
104
|
+
})());
|
|
105
|
+
}
|
|
106
|
+
function reconcile(entry, failures) {
|
|
107
|
+
if (!started || disposed) return;
|
|
108
|
+
if (!consent.evaluate(entry.definition.useId).allowed) {
|
|
109
|
+
let blockedError;
|
|
110
|
+
if (!entry.blocked) {
|
|
111
|
+
entry.blocked = true;
|
|
112
|
+
try {
|
|
113
|
+
entry.definition.onBlocked?.();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
blockedError = error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (entry.activation || entry.controller) deactivate(entry, "blocked", failures);
|
|
119
|
+
else if (entry.status.state !== "error") publish(entry, status(entry.status.reloadRequired ? "reload-required" : "blocked", entry.status.reloadRequired), failures);
|
|
120
|
+
if (blockedError !== void 0) publish(entry, status("error", true, blockedError), failures);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
entry.blocked = false;
|
|
124
|
+
if (entry.status.reloadRequired || entry.status.state === "error" || entry.activation || entry.controller || entry.pending || entry.cleanup) return;
|
|
125
|
+
activate(entry, failures);
|
|
126
|
+
}
|
|
127
|
+
function reconcileAll() {
|
|
128
|
+
const failures = [];
|
|
129
|
+
for (const entry of entries.values()) try {
|
|
130
|
+
reconcile(entry, failures);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
failures.push(error);
|
|
133
|
+
}
|
|
134
|
+
if (failures.length) throw new AggregateError(failures, "Integration lifecycle failed after reconciliation.");
|
|
135
|
+
}
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
start() {
|
|
138
|
+
if (disposed) throw new Error("Integration manager is disposed.");
|
|
139
|
+
if (started) return;
|
|
140
|
+
started = true;
|
|
141
|
+
unsubscribe = consent.subscribe(reconcileAll);
|
|
142
|
+
reconcileAll();
|
|
143
|
+
},
|
|
144
|
+
getStatus(id) {
|
|
145
|
+
const entry = entries.get(id);
|
|
146
|
+
if (!entry) throw new TypeError(`Unknown integration ID: ${id}`);
|
|
147
|
+
return entry.status;
|
|
148
|
+
},
|
|
149
|
+
subscribe(listener) {
|
|
150
|
+
if (typeof listener !== "function") throw new TypeError("Expected a status listener.");
|
|
151
|
+
if (disposed) return () => {};
|
|
152
|
+
listeners.add(listener);
|
|
153
|
+
return () => {
|
|
154
|
+
listeners.delete(listener);
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
async dispose() {
|
|
158
|
+
if (disposed) return;
|
|
159
|
+
disposed = true;
|
|
160
|
+
const failures = [];
|
|
161
|
+
try {
|
|
162
|
+
unsubscribe?.();
|
|
163
|
+
} catch (error) {
|
|
164
|
+
failures.push(error);
|
|
165
|
+
}
|
|
166
|
+
for (const entry of entries.values()) try {
|
|
167
|
+
deactivate(entry, "disposed", failures);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
failures.push(error);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await Promise.all([...entries.values()].flatMap((entry) => [entry.pending, entry.cleanup]));
|
|
173
|
+
} finally {
|
|
174
|
+
listeners.clear();
|
|
175
|
+
}
|
|
176
|
+
if (failures.length) throw new AggregateError(failures, "Integration lifecycle failed after disposal.");
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
export { createConsentIntegrationManager };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import * as z from 'zod/mini';
|
|
2
|
+
|
|
3
|
+
/** Disjoint category/use decisions for one scope actually presented to the user. */
|
|
4
|
+
declare type BatchCommand = z.infer<typeof consentDecisionsCommandSchema>;
|
|
5
|
+
|
|
6
|
+
/** Records choices only for category members in userConsentedPolicy, not future members. */
|
|
7
|
+
declare type CategoryConsentCommand = z.infer<typeof categoryConsentCommandSchema>;
|
|
8
|
+
|
|
9
|
+
declare const categoryConsentCommandSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
10
|
+
userConsentedPolicy: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
11
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
12
|
+
id: z.ZodMiniString<string>;
|
|
13
|
+
title: z.ZodMiniString<string>;
|
|
14
|
+
description: z.ZodMiniString<string>;
|
|
15
|
+
}, z.core.$strict>>>>;
|
|
16
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
17
|
+
id: z.ZodMiniString<string>;
|
|
18
|
+
serviceId: z.ZodMiniString<string>;
|
|
19
|
+
categoryId: z.ZodMiniString<string>;
|
|
20
|
+
purposeId: z.ZodMiniString<string>;
|
|
21
|
+
consentVersion: z.ZodMiniInt;
|
|
22
|
+
}, z.core.$strict>>>>;
|
|
23
|
+
}, z.core.$strict>>;
|
|
24
|
+
categoryId: z.ZodMiniString<string>;
|
|
25
|
+
decision: z.ZodMiniEnum<{
|
|
26
|
+
granted: "granted";
|
|
27
|
+
denied: "denied";
|
|
28
|
+
}>;
|
|
29
|
+
}, z.core.$strict>>;
|
|
30
|
+
|
|
31
|
+
/** userConsentedPolicy is the scope shown for this decision, including a denial. */
|
|
32
|
+
declare type ConsentDecisionCommand = z.infer<typeof consentDecisionCommandSchema>;
|
|
33
|
+
|
|
34
|
+
declare const consentDecisionCommandSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
35
|
+
userConsentedPolicy: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
36
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
37
|
+
id: z.ZodMiniString<string>;
|
|
38
|
+
title: z.ZodMiniString<string>;
|
|
39
|
+
description: z.ZodMiniString<string>;
|
|
40
|
+
}, z.core.$strict>>>>;
|
|
41
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
42
|
+
id: z.ZodMiniString<string>;
|
|
43
|
+
serviceId: z.ZodMiniString<string>;
|
|
44
|
+
categoryId: z.ZodMiniString<string>;
|
|
45
|
+
purposeId: z.ZodMiniString<string>;
|
|
46
|
+
consentVersion: z.ZodMiniInt;
|
|
47
|
+
}, z.core.$strict>>>>;
|
|
48
|
+
}, z.core.$strict>>;
|
|
49
|
+
useId: z.ZodMiniString<string>;
|
|
50
|
+
decision: z.ZodMiniEnum<{
|
|
51
|
+
granted: "granted";
|
|
52
|
+
denied: "denied";
|
|
53
|
+
}>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
|
|
56
|
+
declare type ConsentDecisionsCommand = Readonly<Omit<BatchCommand, 'decisions'> & {
|
|
57
|
+
decisions: readonly ExclusiveTarget<BatchCommand['decisions'][number]>[];
|
|
58
|
+
}>;
|
|
59
|
+
|
|
60
|
+
declare const consentDecisionsCommandSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
61
|
+
userConsentedPolicy: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
62
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
63
|
+
id: z.ZodMiniString<string>;
|
|
64
|
+
title: z.ZodMiniString<string>;
|
|
65
|
+
description: z.ZodMiniString<string>;
|
|
66
|
+
}, z.core.$strict>>>>;
|
|
67
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
68
|
+
id: z.ZodMiniString<string>;
|
|
69
|
+
serviceId: z.ZodMiniString<string>;
|
|
70
|
+
categoryId: z.ZodMiniString<string>;
|
|
71
|
+
purposeId: z.ZodMiniString<string>;
|
|
72
|
+
consentVersion: z.ZodMiniInt;
|
|
73
|
+
}, z.core.$strict>>>>;
|
|
74
|
+
}, z.core.$strict>>;
|
|
75
|
+
decisions: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniUnion<readonly [z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
76
|
+
useId: z.ZodMiniString<string>;
|
|
77
|
+
decision: z.ZodMiniEnum<{
|
|
78
|
+
granted: "granted";
|
|
79
|
+
denied: "denied";
|
|
80
|
+
}>;
|
|
81
|
+
}, z.core.$strict>>, z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
82
|
+
categoryId: z.ZodMiniString<string>;
|
|
83
|
+
decision: z.ZodMiniEnum<{
|
|
84
|
+
granted: "granted";
|
|
85
|
+
denied: "denied";
|
|
86
|
+
}>;
|
|
87
|
+
}, z.core.$strict>>]>>>;
|
|
88
|
+
}, z.core.$strict>>;
|
|
89
|
+
|
|
90
|
+
declare type ConsentDeniedReason = 'unknown-use' | 'undecided' | 'denied' | 'consent-version-mismatch';
|
|
91
|
+
|
|
92
|
+
declare type ConsentEvaluation = Readonly<{
|
|
93
|
+
allowed: true;
|
|
94
|
+
}> | Readonly<{
|
|
95
|
+
allowed: false;
|
|
96
|
+
reason: ConsentDeniedReason;
|
|
97
|
+
}>;
|
|
98
|
+
|
|
99
|
+
export declare type ConsentMarkupGuard = Readonly<{
|
|
100
|
+
/** Failed initial scans roll back resources and preserve the original error; retry never replays scripts. */
|
|
101
|
+
start: () => void;
|
|
102
|
+
/** Sticky until page reload; removing an executed script cannot undo it. */
|
|
103
|
+
getReloadRequiredUseIds: () => readonly string[];
|
|
104
|
+
/** After disposal, returns a no-op unsubscribe without retaining the listener. */
|
|
105
|
+
subscribe: (listener: () => void) => () => void;
|
|
106
|
+
/** Cleans up every managed resource before throwing collected listener/cleanup errors. */
|
|
107
|
+
dispose: () => void;
|
|
108
|
+
}>;
|
|
109
|
+
|
|
110
|
+
export declare type ConsentMarkupIssue = Readonly<{
|
|
111
|
+
element: HTMLScriptElement | HTMLIFrameElement;
|
|
112
|
+
reason: 'invalid-marker' | 'load-error';
|
|
113
|
+
error: unknown;
|
|
114
|
+
}>;
|
|
115
|
+
|
|
116
|
+
export declare type ConsentMarkupOptions = Readonly<{
|
|
117
|
+
consent: Pick<ConsentRuntime, 'evaluate' | 'subscribe'>;
|
|
118
|
+
/** Only descendants of this document or connected element are managed. */
|
|
119
|
+
root: Document | HTMLElement;
|
|
120
|
+
onIssue?: (issue: ConsentMarkupIssue) => void;
|
|
121
|
+
}>;
|
|
122
|
+
|
|
123
|
+
declare type ConsentPolicy = z.infer<typeof consentPolicySchema>;
|
|
124
|
+
|
|
125
|
+
declare const consentPolicySchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
126
|
+
purposes: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
127
|
+
id: z.ZodMiniString<string>;
|
|
128
|
+
title: z.ZodMiniString<string>;
|
|
129
|
+
description: z.ZodMiniString<string>;
|
|
130
|
+
}, z.core.$strict>>>>;
|
|
131
|
+
uses: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
132
|
+
id: z.ZodMiniString<string>;
|
|
133
|
+
serviceId: z.ZodMiniString<string>;
|
|
134
|
+
categoryId: z.ZodMiniString<string>;
|
|
135
|
+
purposeId: z.ZodMiniString<string>;
|
|
136
|
+
consentVersion: z.ZodMiniInt;
|
|
137
|
+
}, z.core.$strict>>>>;
|
|
138
|
+
}, z.core.$strict>>;
|
|
139
|
+
|
|
140
|
+
/** A current use with no decision or a grant for a different consent version. */
|
|
141
|
+
declare type ConsentRequirement = Readonly<{
|
|
142
|
+
useId: string;
|
|
143
|
+
categoryId: string;
|
|
144
|
+
/** The version required by the current policy for this use. */
|
|
145
|
+
consentVersion: number;
|
|
146
|
+
reason: 'undecided' | 'consent-version-mismatch';
|
|
147
|
+
}>;
|
|
148
|
+
|
|
149
|
+
/** In-memory runtime; methods can be passed as callbacks without binding. */
|
|
150
|
+
declare type ConsentRuntime = Readonly<{
|
|
151
|
+
getSnapshot: () => ConsentSnapshot;
|
|
152
|
+
evaluate: (useId: string) => ConsentEvaluation;
|
|
153
|
+
/** Includes undecided uses and version-mismatched grants, not explicit denials. */
|
|
154
|
+
getMismatchedConsent: () => readonly ConsentRequirement[];
|
|
155
|
+
/**
|
|
156
|
+
* Replace all choices from trusted persistence, preserving unknown uses and recorded versions.
|
|
157
|
+
* Validation checks structure, not evidence of user consent; use decision methods for new choices.
|
|
158
|
+
* Does not replace policy, renew versions or perform I/O. Effective changes notify existing guards.
|
|
159
|
+
*/
|
|
160
|
+
restoreState: (state: unknown) => ValidationResult<ConsentSnapshot>;
|
|
161
|
+
setConsentDecision: (command: ConsentDecisionCommand) => ValidationResult<ConsentSnapshot>;
|
|
162
|
+
setCategoryConsent: (command: CategoryConsentCommand) => ValidationResult<ConsentSnapshot>;
|
|
163
|
+
/** Validates all targets before one commit; observer errors can throw after that commit. */
|
|
164
|
+
setConsentDecisions: (command: ConsentDecisionsCommand) => ValidationResult<ConsentSnapshot>;
|
|
165
|
+
updatePolicy: (currentAppPolicy: unknown) => ValidationResult<ConsentSnapshot>;
|
|
166
|
+
subscribe: (listener: () => void) => () => void;
|
|
167
|
+
}>;
|
|
168
|
+
|
|
169
|
+
/** One active app policy and recorded choices; not a history of policies shown to users. */
|
|
170
|
+
declare type ConsentSnapshot = Readonly<{
|
|
171
|
+
currentAppPolicy: ConsentPolicy;
|
|
172
|
+
state: ConsentState;
|
|
173
|
+
}>;
|
|
174
|
+
|
|
175
|
+
declare type ConsentState = z.infer<typeof consentStateSchema>;
|
|
176
|
+
|
|
177
|
+
declare const consentStateSchema: z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
178
|
+
choices: z.ZodMiniReadonly<z.ZodMiniArray<z.ZodMiniReadonly<z.ZodMiniObject<{
|
|
179
|
+
useId: z.ZodMiniString<string>;
|
|
180
|
+
consentVersion: z.ZodMiniInt;
|
|
181
|
+
decision: z.ZodMiniEnum<{
|
|
182
|
+
granted: "granted";
|
|
183
|
+
denied: "denied";
|
|
184
|
+
}>;
|
|
185
|
+
}, z.core.$strict>>>>;
|
|
186
|
+
}, z.core.$strict>>;
|
|
187
|
+
|
|
188
|
+
/** Framework-neutral, opt-in management of inert marked nodes only. */
|
|
189
|
+
export declare function createConsentMarkupGuard({ consent, root, onIssue }: ConsentMarkupOptions): ConsentMarkupGuard;
|
|
190
|
+
|
|
191
|
+
declare type ExclusiveTarget<T> = T extends {
|
|
192
|
+
useId: string;
|
|
193
|
+
} ? T & {
|
|
194
|
+
readonly categoryId?: never;
|
|
195
|
+
} : T & {
|
|
196
|
+
readonly useId?: never;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/** A validation issue without a copy of the rejected input. */
|
|
200
|
+
declare type ValidationIssue = Readonly<{
|
|
201
|
+
code: string;
|
|
202
|
+
path: readonly (string | number)[];
|
|
203
|
+
message: string;
|
|
204
|
+
}>;
|
|
205
|
+
|
|
206
|
+
declare type ValidationResult<T> = Readonly<{
|
|
207
|
+
success: true;
|
|
208
|
+
data: T;
|
|
209
|
+
}> | Readonly<{
|
|
210
|
+
success: false;
|
|
211
|
+
issues: readonly ValidationIssue[];
|
|
212
|
+
}>;
|
|
213
|
+
|
|
214
|
+
export { }
|