@c15t/browser 3.0.0-alpha.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/AGENTS.md +64 -0
- package/CHANGELOG.md +27 -0
- package/README.md +128 -0
- package/dist/861.js +1079 -0
- package/dist/925.js +649 -0
- package/dist/c15t.css +213 -0
- package/dist/c15t.devtools.js +1 -0
- package/dist/c15t.headless.js +1 -0
- package/dist/c15t.iab.css +190 -0
- package/dist/c15t.iab.js +221 -0
- package/dist/c15t.js +30 -0
- package/dist/devtools.js +11 -0
- package/dist/headless.js +17 -0
- package/dist/iab.js +784 -0
- package/dist/index.js +17 -0
- package/dist-types/auto-init.d.ts +42 -0
- package/dist-types/client.d.ts +51 -0
- package/dist-types/deferred.d.ts +13 -0
- package/dist-types/devtools.d.ts +32 -0
- package/dist-types/entries/cdn-devtools.d.ts +13 -0
- package/dist-types/entries/cdn-headless.d.ts +8 -0
- package/dist-types/entries/cdn-iab.d.ts +1 -0
- package/dist-types/entries/cdn.d.ts +8 -0
- package/dist-types/gated-scripts.d.ts +35 -0
- package/dist-types/generated/iab-styles.d.ts +144 -0
- package/dist-types/generated/styles.d.ts +137 -0
- package/dist-types/global.d.ts +112 -0
- package/dist-types/headless.d.ts +41 -0
- package/dist-types/iab/mount.d.ts +8 -0
- package/dist-types/iab/preferences.d.ts +9 -0
- package/dist-types/iab/styles.d.ts +2 -0
- package/dist-types/iab/surface.d.ts +4 -0
- package/dist-types/iab/vendor-disclosures.d.ts +14 -0
- package/dist-types/iab.d.ts +20 -0
- package/dist-types/index.d.ts +43 -0
- package/dist-types/transports/manifest.d.ts +62 -0
- package/dist-types/transports/offline.d.ts +18 -0
- package/dist-types/types.d.ts +290 -0
- package/dist-types/ui/actions.d.ts +29 -0
- package/dist-types/ui/banner.d.ts +13 -0
- package/dist-types/ui/branding.d.ts +25 -0
- package/dist-types/ui/copy.d.ts +28 -0
- package/dist-types/ui/dialog.d.ts +14 -0
- package/dist-types/ui/dom.d.ts +48 -0
- package/dist-types/ui/mount.d.ts +27 -0
- package/dist-types/ui/surface.d.ts +43 -0
- package/dist-types/ui/trigger.d.ts +14 -0
- package/dist-types/ui/widget.d.ts +27 -0
- package/dist-types/version.d.ts +1 -0
- package/docs/README.md +64 -0
- package/docs/frameworks/javascript/script-loader.md +47 -0
- package/docs/frameworks/javascript/script-tag.md +375 -0
- package/docs/integrations/adobe-analytics.md +294 -0
- package/docs/integrations/ahrefs-analytics.md +291 -0
- package/docs/integrations/amplitude.md +292 -0
- package/docs/integrations/building-integrations.md +47 -0
- package/docs/integrations/clearbit.md +291 -0
- package/docs/integrations/cloudflare-web-analytics.md +292 -0
- package/docs/integrations/crisp.md +294 -0
- package/docs/integrations/databuddy.md +311 -0
- package/docs/integrations/fathom-analytics.md +293 -0
- package/docs/integrations/google-maps.md +384 -0
- package/docs/integrations/google-tag-manager.md +306 -0
- package/docs/integrations/google-tag.md +305 -0
- package/docs/integrations/heap.md +292 -0
- package/docs/integrations/hightouch.md +293 -0
- package/docs/integrations/hotjar.md +292 -0
- package/docs/integrations/intercom.md +285 -0
- package/docs/integrations/linkedin-insights.md +291 -0
- package/docs/integrations/logrocket.md +293 -0
- package/docs/integrations/matomo-analytics.md +316 -0
- package/docs/integrations/meta-pixel.md +285 -0
- package/docs/integrations/microsoft-clarity.md +292 -0
- package/docs/integrations/microsoft-uet.md +291 -0
- package/docs/integrations/mixpanel-analytics.md +295 -0
- package/docs/integrations/openai-pixel.md +441 -0
- package/docs/integrations/overview.md +111 -0
- package/docs/integrations/pirsch.md +295 -0
- package/docs/integrations/plausible-analytics.md +295 -0
- package/docs/integrations/posthog.md +397 -0
- package/docs/integrations/promptwatch.md +291 -0
- package/docs/integrations/reddit-pixel.md +294 -0
- package/docs/integrations/rudderstack.md +328 -0
- package/docs/integrations/rybbit-analytics.md +296 -0
- package/docs/integrations/segment.md +292 -0
- package/docs/integrations/snapchat-pixel.md +293 -0
- package/docs/integrations/tiktok-pixel.md +291 -0
- package/docs/integrations/umami-analytics.md +295 -0
- package/docs/integrations/vercel-analytics.md +293 -0
- package/docs/integrations/x-pixel.md +291 -0
- package/docs/integrations/youtube.md +405 -0
- package/package.json +117 -0
- package/readme.json +44 -0
package/dist/925.js
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import { allConsentNames, c15tProtocolHeaders, createHostedTransport, custom, evaluateConsent, hosted as core_hosted, mapInitOutputToInitResponse, policyRulePresets } from "@c15t/core";
|
|
2
|
+
import { createConsentRuntime } from "@c15t/core/runtime";
|
|
3
|
+
import { recommendedPolicyRules, resolveInitFromManifest, resolvePolicyRules, writePolicyResolutionWire } from "@c15t/schema/types";
|
|
4
|
+
import { enTranslations } from "@c15t/translations";
|
|
5
|
+
const deferred_createDeferred = function() {
|
|
6
|
+
const { withResolvers } = Promise;
|
|
7
|
+
if ('function' == typeof withResolvers) return withResolvers.call(Promise);
|
|
8
|
+
let resolveFn = ()=>{};
|
|
9
|
+
let rejectFn = ()=>{};
|
|
10
|
+
const promise = new Promise((resolve, reject)=>{
|
|
11
|
+
resolveFn = resolve;
|
|
12
|
+
rejectFn = reject;
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
promise,
|
|
16
|
+
reject: rejectFn,
|
|
17
|
+
resolve: resolveFn
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const CATEGORY_ATTRIBUTE = 'data-c15t-category';
|
|
21
|
+
const ACTIVATED_ATTRIBUTE = 'data-c15t-activated';
|
|
22
|
+
const COPIED_ATTRIBUTES = [
|
|
23
|
+
'src',
|
|
24
|
+
'async',
|
|
25
|
+
'defer',
|
|
26
|
+
'crossorigin',
|
|
27
|
+
'integrity',
|
|
28
|
+
'referrerpolicy',
|
|
29
|
+
'id'
|
|
30
|
+
];
|
|
31
|
+
const gated_scripts_createReplacement = function(element) {
|
|
32
|
+
const replacement = element.ownerDocument.createElement("script");
|
|
33
|
+
for (const name of COPIED_ATTRIBUTES){
|
|
34
|
+
const value = element.getAttribute(name);
|
|
35
|
+
if (null !== value) replacement.setAttribute(name, value);
|
|
36
|
+
}
|
|
37
|
+
replacement.async = element.hasAttribute('async');
|
|
38
|
+
replacement.nonce = element.nonce;
|
|
39
|
+
for (const attribute of Array.from(element.attributes))if (attribute.name.startsWith('data-') && attribute.name !== ACTIVATED_ATTRIBUTE) replacement.setAttribute(attribute.name, attribute.value);
|
|
40
|
+
replacement.setAttribute(ACTIVATED_ATTRIBUTE, 'true');
|
|
41
|
+
if (!element.hasAttribute('src')) replacement.textContent = element.textContent;
|
|
42
|
+
return replacement;
|
|
43
|
+
};
|
|
44
|
+
const gated_scripts_isAllowed = function(element, snapshot) {
|
|
45
|
+
const category = element.getAttribute(CATEGORY_ATTRIBUTE);
|
|
46
|
+
if (!category) return false;
|
|
47
|
+
if (!allConsentNames.includes(category)) {
|
|
48
|
+
console.warn(`@c15t/browser: unknown ${CATEGORY_ATTRIBUTE} "${category}". Expected one of ${allConsentNames.join(', ')}.`);
|
|
49
|
+
element.setAttribute(ACTIVATED_ATTRIBUTE, 'invalid');
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return evaluateConsent({
|
|
53
|
+
category: category
|
|
54
|
+
}, snapshot);
|
|
55
|
+
};
|
|
56
|
+
const pendingScripts = new WeakMap();
|
|
57
|
+
const trackPendingScript = (script)=>{
|
|
58
|
+
const pending = {
|
|
59
|
+
subscribers: new Set()
|
|
60
|
+
};
|
|
61
|
+
const { ownerDocument } = script;
|
|
62
|
+
const settled = ()=>{
|
|
63
|
+
script.removeEventListener('load', settled);
|
|
64
|
+
script.removeEventListener('error', settled);
|
|
65
|
+
pendingScripts.delete(ownerDocument);
|
|
66
|
+
const subscribers = Array.from(pending.subscribers);
|
|
67
|
+
pending.subscribers.clear();
|
|
68
|
+
for (const resume of subscribers)resume();
|
|
69
|
+
};
|
|
70
|
+
pendingScripts.set(ownerDocument, pending);
|
|
71
|
+
script.addEventListener('load', settled);
|
|
72
|
+
script.addEventListener('error', settled);
|
|
73
|
+
return pending;
|
|
74
|
+
};
|
|
75
|
+
const gated_scripts_createGatedScriptActivator = function(getSnapshot, root) {
|
|
76
|
+
let disposed = false;
|
|
77
|
+
let scanning = false;
|
|
78
|
+
let waiting = null;
|
|
79
|
+
let stopWaiting = null;
|
|
80
|
+
const scan = ()=>{
|
|
81
|
+
if (disposed || scanning) return 0;
|
|
82
|
+
scanning = true;
|
|
83
|
+
const waitFor = (pending)=>{
|
|
84
|
+
if (waiting === pending) return;
|
|
85
|
+
stopWaiting?.();
|
|
86
|
+
waiting = pending;
|
|
87
|
+
const resume = ()=>{
|
|
88
|
+
if (waiting !== pending) return;
|
|
89
|
+
waiting = null;
|
|
90
|
+
stopWaiting = null;
|
|
91
|
+
scan();
|
|
92
|
+
};
|
|
93
|
+
pending.subscribers.add(resume);
|
|
94
|
+
stopWaiting = ()=>pending.subscribers.delete(resume);
|
|
95
|
+
};
|
|
96
|
+
let activated = 0;
|
|
97
|
+
try {
|
|
98
|
+
const selector = `script[type="text/plain"][${CATEGORY_ATTRIBUTE}]:not([${ACTIVATED_ATTRIBUTE}])`;
|
|
99
|
+
const elements = (root ?? document).querySelectorAll(selector);
|
|
100
|
+
for (const element of elements){
|
|
101
|
+
if (disposed) break;
|
|
102
|
+
if (!element.isConnected || !gated_scripts_isAllowed(element, getSnapshot())) continue;
|
|
103
|
+
const external = element.hasAttribute('src');
|
|
104
|
+
const asynchronous = external && element.hasAttribute('async');
|
|
105
|
+
const pending = pendingScripts.get(element.ownerDocument);
|
|
106
|
+
if (pending && !asynchronous) {
|
|
107
|
+
waitFor(pending);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const replacement = gated_scripts_createReplacement(element);
|
|
111
|
+
if (external && !asynchronous) waitFor(trackPendingScript(replacement));
|
|
112
|
+
element.setAttribute(ACTIVATED_ATTRIBUTE, 'true');
|
|
113
|
+
element.replaceWith(replacement);
|
|
114
|
+
activated += 1;
|
|
115
|
+
}
|
|
116
|
+
} finally{
|
|
117
|
+
scanning = false;
|
|
118
|
+
}
|
|
119
|
+
return activated;
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
dispose () {
|
|
123
|
+
disposed = true;
|
|
124
|
+
stopWaiting?.();
|
|
125
|
+
stopWaiting = null;
|
|
126
|
+
waiting = null;
|
|
127
|
+
},
|
|
128
|
+
scan
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const standaloneActivators = new WeakMap();
|
|
132
|
+
const gated_scripts_activateGatedScripts = function(snapshot, root = document) {
|
|
133
|
+
let state = standaloneActivators.get(root);
|
|
134
|
+
if (!state) {
|
|
135
|
+
const current = {
|
|
136
|
+
activator: gated_scripts_createGatedScriptActivator(()=>current.snapshot, root),
|
|
137
|
+
snapshot
|
|
138
|
+
};
|
|
139
|
+
state = current;
|
|
140
|
+
standaloneActivators.set(root, state);
|
|
141
|
+
}
|
|
142
|
+
state.snapshot = snapshot;
|
|
143
|
+
return state.activator.scan();
|
|
144
|
+
};
|
|
145
|
+
const manifest_trimSlash = function(url) {
|
|
146
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
147
|
+
};
|
|
148
|
+
const manifest_deriveBackendURL = function(options) {
|
|
149
|
+
if (void 0 !== options.backendURL) return manifest_trimSlash(options.backendURL);
|
|
150
|
+
if (!options.manifestURL) return;
|
|
151
|
+
const withoutQuery = options.manifestURL.split(/[?#]/u)[0] ?? options.manifestURL;
|
|
152
|
+
const trimmed = manifest_trimSlash(withoutQuery);
|
|
153
|
+
if (!trimmed.endsWith('/manifest')) return;
|
|
154
|
+
try {
|
|
155
|
+
const parsed = new URL(trimmed, 'https://c15t.invalid');
|
|
156
|
+
if (![
|
|
157
|
+
'http:',
|
|
158
|
+
'https:'
|
|
159
|
+
].includes(parsed.protocol) || !parsed.pathname.endsWith('/manifest')) return;
|
|
160
|
+
} catch {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
return trimmed.slice(0, -9);
|
|
164
|
+
};
|
|
165
|
+
const manifest_manifestNeedsLocation = function(manifest) {
|
|
166
|
+
if (manifest.defaults?.disableGeoLocation) return false;
|
|
167
|
+
const packs = manifest.policyPacks ?? [];
|
|
168
|
+
if (0 === packs.length) return true;
|
|
169
|
+
return packs.some((pack)=>(pack.match.countries?.length ?? 0) > 0 || (pack.match.regions?.length ?? 0) > 0);
|
|
170
|
+
};
|
|
171
|
+
const manifest_readGlobalPrivacyControl = function() {
|
|
172
|
+
if ("u" < typeof navigator) return;
|
|
173
|
+
const value = navigator.globalPrivacyControl;
|
|
174
|
+
return 'boolean' == typeof value ? value : void 0;
|
|
175
|
+
};
|
|
176
|
+
const manifest_mergeInputs = function(base, overrides) {
|
|
177
|
+
const language = overrides.language ?? base?.language ?? ("u" < typeof navigator ? 'en' : navigator.language);
|
|
178
|
+
return {
|
|
179
|
+
country: overrides.country ?? base?.country ?? null,
|
|
180
|
+
gpc: overrides.gpc ?? base?.gpc ?? manifest_readGlobalPrivacyControl(),
|
|
181
|
+
language,
|
|
182
|
+
region: overrides.region ?? base?.region ?? null
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const browserBaseTranslations = {
|
|
186
|
+
en: enTranslations
|
|
187
|
+
};
|
|
188
|
+
const manifest_manifest = function(options) {
|
|
189
|
+
if (!(options.manifest || options.manifestURL)) throw new Error('@c15t/browser: manifest() needs `manifest` or `manifestURL`.');
|
|
190
|
+
const backendURL = manifest_deriveBackendURL(options);
|
|
191
|
+
if (void 0 === backendURL) throw new Error('@c15t/browser: manifest() needs `backendURL` unless `manifestURL` ends in `/manifest`. Pass the consent API URL, or an empty string for this origin.');
|
|
192
|
+
const hosted = createHostedTransport({
|
|
193
|
+
backendURL,
|
|
194
|
+
fetch: options.fetch
|
|
195
|
+
});
|
|
196
|
+
let cached;
|
|
197
|
+
const fetchManifest = async function() {
|
|
198
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
199
|
+
const response = await fetchImpl(options.manifestURL, {
|
|
200
|
+
headers: {
|
|
201
|
+
...c15tProtocolHeaders
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) throw new Error(`@c15t/browser: manifest request failed with ${response.status}`);
|
|
205
|
+
return await response.json();
|
|
206
|
+
};
|
|
207
|
+
const loadManifest = async function() {
|
|
208
|
+
if (options.manifest) return options.manifest;
|
|
209
|
+
cached ??= fetchManifest();
|
|
210
|
+
try {
|
|
211
|
+
return await cached;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
cached = void 0;
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const transport = {
|
|
218
|
+
identify: hosted.identify,
|
|
219
|
+
async init (ctx) {
|
|
220
|
+
const resolved = await loadManifest();
|
|
221
|
+
const inputs = manifest_mergeInputs(options.inputs, ctx.overrides);
|
|
222
|
+
if (manifest_manifestNeedsLocation(resolved) && (!inputs.country || !inputs.region && resolved.policyPacks?.some((pack)=>(pack.match.regions?.length ?? 0) > 0))) return hosted.init(ctx);
|
|
223
|
+
const output = resolveInitFromManifest(resolved, inputs, {
|
|
224
|
+
baseTranslations: browserBaseTranslations
|
|
225
|
+
});
|
|
226
|
+
return mapInitOutputToInitResponse(output, {});
|
|
227
|
+
},
|
|
228
|
+
loadSubjectRecord: hosted.loadSubjectRecord,
|
|
229
|
+
recordPrivacyOptOut: hosted.recordPrivacyOptOut,
|
|
230
|
+
save: hosted.save
|
|
231
|
+
};
|
|
232
|
+
return Object.assign(()=>transport, {
|
|
233
|
+
kind: 'custom'
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
const offline_offline = function(options = {}) {
|
|
237
|
+
return Object.assign((context)=>({
|
|
238
|
+
init: ({ overrides })=>Promise.resolve({
|
|
239
|
+
policyResolution: writePolicyResolutionWire(resolvePolicyRules({
|
|
240
|
+
countryCode: overrides.country ?? null,
|
|
241
|
+
iabEnabled: context.iabEnabled,
|
|
242
|
+
regionCode: overrides.region ?? null,
|
|
243
|
+
rules: options.policyRules ?? recommendedPolicyRules({
|
|
244
|
+
iab: context.iabEnabled
|
|
245
|
+
})
|
|
246
|
+
})),
|
|
247
|
+
translations: context.translations
|
|
248
|
+
})
|
|
249
|
+
}), {
|
|
250
|
+
kind: 'offline'
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
const ACTION_ATTRIBUTE = 'data-c15t-action';
|
|
254
|
+
const PREFERENCES_HASH = '#c15t-preferences';
|
|
255
|
+
const PAGE_ACTIONS = new Set([
|
|
256
|
+
'accept',
|
|
257
|
+
'reject',
|
|
258
|
+
'customize',
|
|
259
|
+
'close',
|
|
260
|
+
'dismiss',
|
|
261
|
+
'banner'
|
|
262
|
+
]);
|
|
263
|
+
const client_resolveRules = function(policyRules) {
|
|
264
|
+
if (!policyRules) return;
|
|
265
|
+
return policyRules.map((entry)=>{
|
|
266
|
+
if ('string' != typeof entry) return entry;
|
|
267
|
+
const preset = Object.hasOwn(policyRulePresets, entry) ? policyRulePresets[entry] : void 0;
|
|
268
|
+
if ('function' != typeof preset) throw new Error(`@c15t/browser: unknown policy preset "${entry}". Expected one of ${Object.keys(policyRulePresets).join(', ')}.`);
|
|
269
|
+
return preset();
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
const client_defaultModeName = function(options) {
|
|
273
|
+
if (options.manifest || options.manifestURL) return 'manifest';
|
|
274
|
+
return options.backendURL ? 'hosted' : 'offline';
|
|
275
|
+
};
|
|
276
|
+
const client_resolveMode = function(options) {
|
|
277
|
+
if ('function' == typeof options.mode) {
|
|
278
|
+
const { kind } = options.mode;
|
|
279
|
+
return {
|
|
280
|
+
factory: options.mode,
|
|
281
|
+
name: 'hosted' === kind || 'offline' === kind ? kind : 'custom'
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const name = options.mode ?? client_defaultModeName(options);
|
|
285
|
+
if ('hosted' === name) {
|
|
286
|
+
if (!options.backendURL) throw new Error("@c15t/browser: hosted mode needs `backendURL` (or data-backend-url on the script tag).");
|
|
287
|
+
return {
|
|
288
|
+
factory: core_hosted({
|
|
289
|
+
url: options.backendURL
|
|
290
|
+
}),
|
|
291
|
+
name
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if ('manifest' === name) return {
|
|
295
|
+
factory: manifest_manifest({
|
|
296
|
+
backendURL: options.backendURL,
|
|
297
|
+
inputs: options.overrides,
|
|
298
|
+
manifest: options.manifest,
|
|
299
|
+
manifestURL: options.manifestURL
|
|
300
|
+
}),
|
|
301
|
+
name
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
factory: offline_offline({
|
|
305
|
+
policyRules: client_resolveRules(options.policyRules)
|
|
306
|
+
}),
|
|
307
|
+
name
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
const client_resolveConsentCategories = function(snapshot, configured) {
|
|
311
|
+
const available = [
|
|
312
|
+
'necessary',
|
|
313
|
+
...snapshot.policyRule.scope
|
|
314
|
+
];
|
|
315
|
+
if (0 === configured.length) return Array.from(available);
|
|
316
|
+
const allowed = new Set(available);
|
|
317
|
+
return Array.from(new Set([
|
|
318
|
+
'necessary',
|
|
319
|
+
...configured.filter((category)=>allowed.has(category))
|
|
320
|
+
]));
|
|
321
|
+
};
|
|
322
|
+
const client_dispatchDocumentEvent = function(name, detail) {
|
|
323
|
+
if ("u" < typeof document) return;
|
|
324
|
+
document.dispatchEvent(new CustomEvent(`c15t:${name}`, {
|
|
325
|
+
detail
|
|
326
|
+
}));
|
|
327
|
+
};
|
|
328
|
+
const client_resolvePageAction = function(target) {
|
|
329
|
+
if (!(target instanceof Element)) return null;
|
|
330
|
+
const actionElement = target.closest(`[${ACTION_ATTRIBUTE}]`);
|
|
331
|
+
const action = actionElement?.getAttribute(ACTION_ATTRIBUTE);
|
|
332
|
+
if (action && PAGE_ACTIONS.has(action)) return action;
|
|
333
|
+
const link = target.closest('a[href]');
|
|
334
|
+
const href = link?.getAttribute('href') ?? '';
|
|
335
|
+
return href.endsWith(PREFERENCES_HASH) ? 'customize' : null;
|
|
336
|
+
};
|
|
337
|
+
const client_createConsentClient = function(options = {}, context = {}) {
|
|
338
|
+
if (options.iab && false !== options.iab.enabled && !context.createIAB) throw new Error('@c15t/browser: IAB requires the @c15t/browser/iab entry.');
|
|
339
|
+
const mode = client_resolveMode(options);
|
|
340
|
+
const configuredCategories = options.consentCategories ?? [];
|
|
341
|
+
const runtime = createConsentRuntime({
|
|
342
|
+
callbacks: options.callbacks,
|
|
343
|
+
consentCategories: options.consentCategories,
|
|
344
|
+
createIAB: context.createIAB,
|
|
345
|
+
enabled: options.enabled,
|
|
346
|
+
i18n: options.i18n,
|
|
347
|
+
iab: context.createIAB ? options.iab ?? {
|
|
348
|
+
enabled: true
|
|
349
|
+
} : void 0,
|
|
350
|
+
iframeBlocker: options.iframeBlocker,
|
|
351
|
+
mode: mode.factory,
|
|
352
|
+
networkBlocker: options.networkBlocker,
|
|
353
|
+
overrides: options.overrides,
|
|
354
|
+
pkg: options.pkg ?? context.pkg ?? '@c15t/browser',
|
|
355
|
+
policyRules: client_resolveRules(options.policyRules),
|
|
356
|
+
prefetch: options.prefetch,
|
|
357
|
+
presentation: options.presentation,
|
|
358
|
+
scripts: options.scripts,
|
|
359
|
+
storageConfig: options.storageConfig,
|
|
360
|
+
user: options.user,
|
|
361
|
+
windowDebug: false
|
|
362
|
+
});
|
|
363
|
+
const { kernel } = runtime;
|
|
364
|
+
const gatedScripts = gated_scripts_createGatedScriptActivator(()=>kernel.getSnapshot());
|
|
365
|
+
let startingRuntime = false;
|
|
366
|
+
let drainingRuntimeEvents = false;
|
|
367
|
+
const pendingRuntimeEvents = [];
|
|
368
|
+
const listeners = {
|
|
369
|
+
consent: new Set(),
|
|
370
|
+
error: new Set(),
|
|
371
|
+
ready: new Set(),
|
|
372
|
+
ui: new Set()
|
|
373
|
+
};
|
|
374
|
+
const dispatch = function(event, payload) {
|
|
375
|
+
for (const listener of listeners[event])listener(payload);
|
|
376
|
+
client_dispatchDocumentEvent(event, payload);
|
|
377
|
+
};
|
|
378
|
+
const emit = function(event, payload) {
|
|
379
|
+
if (startingRuntime || drainingRuntimeEvents) return void pendingRuntimeEvents.push(()=>dispatch(event, payload));
|
|
380
|
+
dispatch(event, payload);
|
|
381
|
+
};
|
|
382
|
+
const ready = deferred_createDeferred();
|
|
383
|
+
let readySnapshot = null;
|
|
384
|
+
const completeReady = function(snapshot) {
|
|
385
|
+
if (readySnapshot) return;
|
|
386
|
+
readySnapshot = snapshot;
|
|
387
|
+
ready.resolve(snapshot);
|
|
388
|
+
dispatch('ready', snapshot);
|
|
389
|
+
};
|
|
390
|
+
const markReady = function(snapshot) {
|
|
391
|
+
if (startingRuntime || drainingRuntimeEvents) return void pendingRuntimeEvents.push(()=>completeReady(snapshot));
|
|
392
|
+
completeReady(snapshot);
|
|
393
|
+
};
|
|
394
|
+
let ui = null;
|
|
395
|
+
let started = false;
|
|
396
|
+
let disposed = false;
|
|
397
|
+
let detachPageActions = null;
|
|
398
|
+
const initial = kernel.getSnapshot();
|
|
399
|
+
let lastActiveUI = initial.activeUI;
|
|
400
|
+
let lastConsents = initial.effectivePermissions;
|
|
401
|
+
let lastHasConsented = initial.explicitChoice;
|
|
402
|
+
const disposers = [
|
|
403
|
+
gatedScripts.dispose,
|
|
404
|
+
kernel.events.on('init:applied', ({ snapshot })=>{
|
|
405
|
+
markReady(snapshot);
|
|
406
|
+
}),
|
|
407
|
+
kernel.events.on('command:error', ({ error })=>{
|
|
408
|
+
emit('error', error);
|
|
409
|
+
}),
|
|
410
|
+
kernel.subscribe((snapshot)=>{
|
|
411
|
+
if (snapshot.effectivePermissions !== lastConsents || snapshot.explicitChoice !== lastHasConsented) {
|
|
412
|
+
lastConsents = snapshot.effectivePermissions;
|
|
413
|
+
lastHasConsented = snapshot.explicitChoice;
|
|
414
|
+
if (started && !startingRuntime) gatedScripts.scan();
|
|
415
|
+
emit('consent', snapshot);
|
|
416
|
+
}
|
|
417
|
+
if (snapshot.activeUI !== lastActiveUI) {
|
|
418
|
+
lastActiveUI = snapshot.activeUI;
|
|
419
|
+
emit('ui', snapshot.activeUI);
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
];
|
|
423
|
+
const categories = function() {
|
|
424
|
+
return client_resolveConsentCategories(kernel.getSnapshot(), configuredCategories);
|
|
425
|
+
};
|
|
426
|
+
let navigation = 0;
|
|
427
|
+
const closeSurfaces = ()=>{
|
|
428
|
+
navigation += 1;
|
|
429
|
+
kernel.set.activeUI('none');
|
|
430
|
+
};
|
|
431
|
+
const openDialog = ()=>{
|
|
432
|
+
navigation += 1;
|
|
433
|
+
kernel.set.activeUI('dialog');
|
|
434
|
+
};
|
|
435
|
+
const showBanner = ()=>{
|
|
436
|
+
navigation += 1;
|
|
437
|
+
kernel.set.activeUI('banner');
|
|
438
|
+
};
|
|
439
|
+
const saveSelection = async (input)=>{
|
|
440
|
+
navigation += 1;
|
|
441
|
+
const current = navigation;
|
|
442
|
+
const surface = kernel.getSnapshot().activeUI;
|
|
443
|
+
const { fingerprint } = kernel.getSnapshot().evaluationPolicy.choice;
|
|
444
|
+
const pending = kernel.commands.save(input, {
|
|
445
|
+
categories: categories()
|
|
446
|
+
});
|
|
447
|
+
kernel.set.activeUI(surface);
|
|
448
|
+
const result = await pending;
|
|
449
|
+
if (result.ok && current === navigation && fingerprint === kernel.getSnapshot().evaluationPolicy.choice.fingerprint) kernel.set.activeUI('none' === kernel.getSnapshot().promptRequirement.kind ? 'none' : 'banner');
|
|
450
|
+
return result;
|
|
451
|
+
};
|
|
452
|
+
const saveIAB = async (blanket)=>{
|
|
453
|
+
const handle = runtime.iab;
|
|
454
|
+
if (!handle || !kernel.getSnapshot().iab?.gvl || 'iab' !== kernel.getSnapshot().policyRule.model) {
|
|
455
|
+
emit('error', new Error('IAB privacy settings are not ready.'));
|
|
456
|
+
return {
|
|
457
|
+
ok: false
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
navigation += 1;
|
|
461
|
+
const current = navigation;
|
|
462
|
+
const snapshot = kernel.getSnapshot();
|
|
463
|
+
try {
|
|
464
|
+
if (true === blanket) handle.acceptAll();
|
|
465
|
+
if (false === blanket) handle.rejectAll();
|
|
466
|
+
await handle.save();
|
|
467
|
+
const next = kernel.getSnapshot();
|
|
468
|
+
if (!next.iab?.authority || next.evaluationPolicy.choice.fingerprint !== snapshot.evaluationPolicy.choice.fingerprint) return {
|
|
469
|
+
ok: false
|
|
470
|
+
};
|
|
471
|
+
if (current === navigation) kernel.set.activeUI('none');
|
|
472
|
+
return {
|
|
473
|
+
ok: true
|
|
474
|
+
};
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (current === navigation) kernel.set.activeUI(snapshot.activeUI);
|
|
477
|
+
emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
478
|
+
return {
|
|
479
|
+
ok: false
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const acceptAll = ()=>'iab' === kernel.getSnapshot().policyRule.model ? saveIAB(true) : saveSelection('all');
|
|
484
|
+
const rejectAll = ()=>'iab' === kernel.getSnapshot().policyRule.model ? saveIAB(false) : saveSelection('none');
|
|
485
|
+
const onPageClick = function(event) {
|
|
486
|
+
const action = client_resolvePageAction(event.target);
|
|
487
|
+
if (!action) return;
|
|
488
|
+
event.preventDefault();
|
|
489
|
+
switch(action){
|
|
490
|
+
case 'accept':
|
|
491
|
+
acceptAll();
|
|
492
|
+
break;
|
|
493
|
+
case 'reject':
|
|
494
|
+
rejectAll();
|
|
495
|
+
break;
|
|
496
|
+
case 'customize':
|
|
497
|
+
openDialog();
|
|
498
|
+
break;
|
|
499
|
+
case 'dismiss':
|
|
500
|
+
kernel.commands.dismissNotice();
|
|
501
|
+
break;
|
|
502
|
+
case 'banner':
|
|
503
|
+
showBanner();
|
|
504
|
+
break;
|
|
505
|
+
default:
|
|
506
|
+
closeSurfaces();
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
const client = {
|
|
510
|
+
acceptAll,
|
|
511
|
+
closeDialog: closeSurfaces,
|
|
512
|
+
get consentCategories () {
|
|
513
|
+
return categories();
|
|
514
|
+
},
|
|
515
|
+
dismissNotice: ()=>kernel.commands.dismissNotice(),
|
|
516
|
+
dispose () {
|
|
517
|
+
if (disposed) return;
|
|
518
|
+
disposed = true;
|
|
519
|
+
navigation += 1;
|
|
520
|
+
started = false;
|
|
521
|
+
detachPageActions?.();
|
|
522
|
+
detachPageActions = null;
|
|
523
|
+
ui?.destroy();
|
|
524
|
+
ui = null;
|
|
525
|
+
for (const dispose of disposers.reverse())dispose();
|
|
526
|
+
disposers.length = 0;
|
|
527
|
+
runtime.dispose();
|
|
528
|
+
},
|
|
529
|
+
getSnapshot () {
|
|
530
|
+
return kernel.getSnapshot();
|
|
531
|
+
},
|
|
532
|
+
has (condition) {
|
|
533
|
+
const snapshot = kernel.getSnapshot();
|
|
534
|
+
return evaluateConsent({
|
|
535
|
+
category: condition
|
|
536
|
+
}, snapshot);
|
|
537
|
+
},
|
|
538
|
+
hasConsented () {
|
|
539
|
+
return null !== kernel.getSnapshot().explicitChoice;
|
|
540
|
+
},
|
|
541
|
+
async identify (user) {
|
|
542
|
+
await runtime.identify(user);
|
|
543
|
+
},
|
|
544
|
+
kernel,
|
|
545
|
+
mode: mode.name,
|
|
546
|
+
mountUI (uiOptions) {
|
|
547
|
+
if (!context.mountUI) throw new Error('@c15t/browser: this build is headless. Load c15t.js, or import `mountConsentUI` from "@c15t/browser".');
|
|
548
|
+
ui?.destroy();
|
|
549
|
+
ui = context.mountUI(client, uiOptions ?? (false === options.ui ? void 0 : options.ui));
|
|
550
|
+
return ui;
|
|
551
|
+
},
|
|
552
|
+
on (event, listener) {
|
|
553
|
+
const set = listeners[event];
|
|
554
|
+
set.add(listener);
|
|
555
|
+
if ('ready' === event && readySnapshot) listener(readySnapshot);
|
|
556
|
+
if ('ui' === event && readySnapshot) listener(kernel.getSnapshot().activeUI);
|
|
557
|
+
return function() {
|
|
558
|
+
set.delete(listener);
|
|
559
|
+
};
|
|
560
|
+
},
|
|
561
|
+
openDialog,
|
|
562
|
+
options,
|
|
563
|
+
ready () {
|
|
564
|
+
return ready.promise;
|
|
565
|
+
},
|
|
566
|
+
rejectAll,
|
|
567
|
+
runtime,
|
|
568
|
+
save (consents) {
|
|
569
|
+
if ('iab' === kernel.getSnapshot().policyRule.model) {
|
|
570
|
+
emit('error', new Error('Use saveIAB() to confirm IAB preferences.'));
|
|
571
|
+
return Promise.resolve({
|
|
572
|
+
ok: false
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
const allowed = new Set(categories());
|
|
576
|
+
return saveSelection(Object.fromEntries(Object.entries(consents).filter(([name])=>allowed.has(name))));
|
|
577
|
+
},
|
|
578
|
+
saveIAB: ()=>saveIAB(),
|
|
579
|
+
setLanguage (code) {
|
|
580
|
+
kernel.set.language(code);
|
|
581
|
+
kernel.commands.init();
|
|
582
|
+
},
|
|
583
|
+
setOverrides (overrides) {
|
|
584
|
+
runtime.setOverrides(overrides);
|
|
585
|
+
},
|
|
586
|
+
showBanner,
|
|
587
|
+
start () {
|
|
588
|
+
if (started || disposed || "u" < typeof document) return;
|
|
589
|
+
started = true;
|
|
590
|
+
if (context.onStart && false !== options.enabled) disposers.push(context.onStart(options));
|
|
591
|
+
startingRuntime = true;
|
|
592
|
+
try {
|
|
593
|
+
runtime.start();
|
|
594
|
+
} finally{
|
|
595
|
+
startingRuntime = false;
|
|
596
|
+
}
|
|
597
|
+
drainingRuntimeEvents = true;
|
|
598
|
+
try {
|
|
599
|
+
for (const notify of pendingRuntimeEvents){
|
|
600
|
+
if (disposed) break;
|
|
601
|
+
notify();
|
|
602
|
+
}
|
|
603
|
+
} finally{
|
|
604
|
+
drainingRuntimeEvents = false;
|
|
605
|
+
pendingRuntimeEvents.length = 0;
|
|
606
|
+
}
|
|
607
|
+
if (disposed) return;
|
|
608
|
+
gatedScripts.scan();
|
|
609
|
+
if (disposed) return;
|
|
610
|
+
const observer = new MutationObserver(()=>gatedScripts.scan());
|
|
611
|
+
observer.observe(document.documentElement, {
|
|
612
|
+
childList: true,
|
|
613
|
+
subtree: true
|
|
614
|
+
});
|
|
615
|
+
disposers.push(()=>observer.disconnect());
|
|
616
|
+
if (false === options.enabled) {
|
|
617
|
+
markReady(kernel.getSnapshot());
|
|
618
|
+
if (disposed) return;
|
|
619
|
+
}
|
|
620
|
+
document.addEventListener('click', onPageClick);
|
|
621
|
+
detachPageActions = ()=>{
|
|
622
|
+
document.removeEventListener('click', onPageClick);
|
|
623
|
+
};
|
|
624
|
+
if (false === options.ui || !context.mountUI) return;
|
|
625
|
+
const mount = function() {
|
|
626
|
+
if (!(disposed || ui)) ui = client.mountUI();
|
|
627
|
+
};
|
|
628
|
+
if (document.body) return void mount();
|
|
629
|
+
document.addEventListener('DOMContentLoaded', mount, {
|
|
630
|
+
once: true
|
|
631
|
+
});
|
|
632
|
+
disposers.push(()=>{
|
|
633
|
+
document.removeEventListener('DOMContentLoaded', mount);
|
|
634
|
+
});
|
|
635
|
+
},
|
|
636
|
+
get started () {
|
|
637
|
+
return started;
|
|
638
|
+
},
|
|
639
|
+
subscribe (listener) {
|
|
640
|
+
return kernel.subscribe(listener);
|
|
641
|
+
},
|
|
642
|
+
get ui () {
|
|
643
|
+
return ui;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
return client;
|
|
647
|
+
};
|
|
648
|
+
const version = '3.0.0-alpha.0';
|
|
649
|
+
export { ACTION_ATTRIBUTE, ACTIVATED_ATTRIBUTE, CATEGORY_ATTRIBUTE, PREFERENCES_HASH, client_createConsentClient, client_resolveRules as resolveRules, core_hosted as hosted, custom, gated_scripts_activateGatedScripts as activateGatedScripts, manifest_manifest as manifest, manifest_manifestNeedsLocation as manifestNeedsLocation, offline_offline as offline, version };
|