@ecomconsult/consentkit 0.3.4 → 0.3.5
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 +107 -7
- package/npm/core.cjs +1 -1
- package/npm/index.cjs +1 -1
- package/npm/internal-stub.mjs +1 -1
- package/package.json +1 -1
- package/src/ck-core.js +108 -2
- package/src/ck-debug-loader.js +134 -0
- package/src/ck-debug.js +847 -0
package/README.md
CHANGED
|
@@ -348,23 +348,123 @@ external requests. Rebuild them with `tools/build-inline.mjs` (see
|
|
|
348
348
|
[`tools/README.md`](tools/README.md)); each block's header records the exact
|
|
349
349
|
command that produced it.
|
|
350
350
|
|
|
351
|
-
ConsentKit 0.3.
|
|
351
|
+
ConsentKit 0.3.5, rebuilt 2026-09-04, uncompressed — gzip on the server cuts
|
|
352
352
|
this roughly three- to fourfold. Every block includes the attribution line;
|
|
353
353
|
`--no-branding` takes ~200 bytes back off:
|
|
354
354
|
|
|
355
355
|
| Block | Languages | Bytes |
|
|
356
356
|
|---|---|---|
|
|
357
|
-
| `ready/en-bar.txt` | en |
|
|
358
|
-
| `ready/ru-bar.txt` | ru, en |
|
|
359
|
-
| `ready/ru-box.txt` | ru, en |
|
|
360
|
-
| `ready/ru-box-right.txt` | ru, en |
|
|
361
|
-
| `ready/ru-modal.txt` | ru, en |
|
|
362
|
-
| `ready/eu-bar.txt` | 34 languages |
|
|
357
|
+
| `ready/en-bar.txt` | en | 111,664 |
|
|
358
|
+
| `ready/ru-bar.txt` | ru, ro, en | 113,285 |
|
|
359
|
+
| `ready/ru-box.txt` | ru, ro, en | 113,300 |
|
|
360
|
+
| `ready/ru-box-right.txt` | ru, ro, en | 113,309 |
|
|
361
|
+
| `ready/ru-modal.txt` | ru, ro, en | 113,293 |
|
|
362
|
+
| `ready/eu-bar.txt` | 34 languages | 161,939 |
|
|
363
363
|
|
|
364
364
|
Size is driven almost entirely by the bundled languages: `en` and `ru` are
|
|
365
365
|
built into the UI and cost nothing extra, while layout, position, theme and
|
|
366
366
|
accent change only a few bytes of config.
|
|
367
367
|
|
|
368
|
+
The debug panel is **not** in these numbers. Blocks carry a ~5.1 KB loader
|
|
369
|
+
(`src/ck-debug-loader.js`) which fetches the 33 KB panel only when someone opens
|
|
370
|
+
the page with `?ck_debug=1` — see [Debug mode](#debug-mode). An ordinary visitor
|
|
371
|
+
downloads the loader and nothing more.
|
|
372
|
+
|
|
373
|
+
## Debug mode
|
|
374
|
+
|
|
375
|
+
A panel that shows what the client actually did on a live page — useful when a
|
|
376
|
+
site owner asks "is this thing working?" and screenshots of the banner do not
|
|
377
|
+
answer it.
|
|
378
|
+
|
|
379
|
+
**Open it** by adding `?ck_debug=1` to the page URL (`#ck_debug` works too):
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
https://example.com/?ck_debug=1
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
A dark, monospace panel appears bottom-right in its own Shadow DOM. The flag is
|
|
386
|
+
remembered in `localStorage` for that browser, so it survives navigation.
|
|
387
|
+
|
|
388
|
+
**It shows**, in order: the client version, config source (SaaS or inline),
|
|
389
|
+
siteId and policy version / ETag; the consent status, granted categories,
|
|
390
|
+
decision time and cookie lifetime; what the blocking engine is holding back
|
|
391
|
+
(`ConsentKit._blocked()`); which requests to known trackers actually left the
|
|
392
|
+
page, each marked *before* or *after* consent; the recent `ck_*` dataLayer
|
|
393
|
+
events and `gtag('consent', …)` calls; and three buttons — reset consent
|
|
394
|
+
(withdraw, clear the cookie, reload), open preferences, and copy a JSON report.
|
|
395
|
+
|
|
396
|
+
The request list carries the same caveat as the blocking engine itself:
|
|
397
|
+
requests that left **before** ck-core.js parsed — a plain `<script src>` written
|
|
398
|
+
into the HTML — show up there but could not have been blocked. Mark those tags
|
|
399
|
+
up manually.
|
|
400
|
+
|
|
401
|
+
**Turn it off** with `?ck_debug=0`, or the × in the panel's header. The loader
|
|
402
|
+
owns the stored flag, so `?ck_debug=0` clears it whether or not the panel is on
|
|
403
|
+
the page.
|
|
404
|
+
|
|
405
|
+
**Privacy.** Nothing is sent anywhere: the panel is local to that browser and
|
|
406
|
+
that page. It never renders cookie *values* (names only) and never shows URL
|
|
407
|
+
query strings (host and path only), so the copied report is safe to paste into
|
|
408
|
+
a support ticket.
|
|
409
|
+
|
|
410
|
+
### How the panel gets onto the page
|
|
411
|
+
|
|
412
|
+
The panel is ~33 KB, and on any given page exactly one person will ever open
|
|
413
|
+
it. So `ready/*.txt` and the WordPress plugin ship **`src/ck-debug-loader.js`**
|
|
414
|
+
(~5.1 KB) instead, and the loader fetches the panel on demand. With no flag set
|
|
415
|
+
the loader creates no DOM, installs no observers and makes **no network
|
|
416
|
+
request** — it costs its own bytes and nothing else.
|
|
417
|
+
|
|
418
|
+
When the panel *is* activated, the loader resolves its URL in this order, first
|
|
419
|
+
match wins:
|
|
420
|
+
|
|
421
|
+
1. **`window.ConsentKitDebugUrl`**, if you set it — a self-hosted copy, an
|
|
422
|
+
internal mirror, or a pinned build. Set it before the loader runs.
|
|
423
|
+
2. **`<API_BASE>/client/ck-debug.js`**, when `ck-saas.js` is on the page with a
|
|
424
|
+
site id — `API_BASE` is that loader's `data-ck-api` (its `ConsentKit._saas.api`).
|
|
425
|
+
The panel then comes from the same origin as the config, so a locked-down CSP
|
|
426
|
+
needs no extra host. **A SaaS deployment is expected to serve the panel at
|
|
427
|
+
that path**; if yours does not, set `window.ConsentKitDebugUrl` instead.
|
|
428
|
+
3. **jsDelivr**, pinned to the running core version:
|
|
429
|
+
`https://cdn.jsdelivr.net/npm/@ecomconsult/consentkit@<version>/src/ck-debug.js`.
|
|
430
|
+
The version comes from `ConsentKit.version`, so the panel can never be newer
|
|
431
|
+
or older than the client it is reporting on. This path is published because
|
|
432
|
+
`package.json` lists `src` in `files`.
|
|
433
|
+
|
|
434
|
+
The script is injected `async`, and neither jsDelivr nor a ConsentKit API host
|
|
435
|
+
is in the blocking engine's tracker list, so the panel loads even while the
|
|
436
|
+
visitor has yet to decide.
|
|
437
|
+
|
|
438
|
+
### Loading the panel directly
|
|
439
|
+
|
|
440
|
+
If you host the files yourself and would rather have the panel inline, load it
|
|
441
|
+
after `ck-ui.js` (and after `ck-saas.js` if you use it) and skip the loader:
|
|
442
|
+
|
|
443
|
+
```html
|
|
444
|
+
<script src="/js/ck-core.js"></script>
|
|
445
|
+
<script src="/js/ck-locales.js"></script>
|
|
446
|
+
<script src="/js/ck-ui.js"></script>
|
|
447
|
+
<script src="/js/ck-debug.js"></script>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
`ck-debug.js` is self-contained: it repeats the loader's activation check, so it
|
|
451
|
+
works with or without the loader. Do **not** add it to a page that already ships
|
|
452
|
+
the loader (an inline block from `ready/`, or the WordPress plugin) — the loader
|
|
453
|
+
fetches the panel itself, and a second copy would mount a second panel.
|
|
454
|
+
|
|
455
|
+
From npm it is a deliberate opt-in — `@ecomconsult/consentkit` does not pull
|
|
456
|
+
it in for you. Import it yourself, after the UI:
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
import '@ecomconsult/consentkit'; // core + locales + UI
|
|
460
|
+
import '@ecomconsult/consentkit/src/ck-debug.js';
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Language.** The panel is Russian or English: it follows the banner's
|
|
464
|
+
configured `language`, falls back to `navigator.language` (`ru-*` → Russian),
|
|
465
|
+
and otherwise renders English. The JSON report it copies is language-neutral
|
|
466
|
+
whichever way the panel reads.
|
|
467
|
+
|
|
368
468
|
## TypeScript
|
|
369
469
|
|
|
370
470
|
Types ship with the package; no `@types` needed.
|
package/npm/core.cjs
CHANGED
package/npm/index.cjs
CHANGED
package/npm/internal-stub.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export function undecidedState() {
|
|
|
28
28
|
*/
|
|
29
29
|
export function createStub() {
|
|
30
30
|
const stub = {
|
|
31
|
-
version: '0.3.
|
|
31
|
+
version: '0.3.5',
|
|
32
32
|
config: {},
|
|
33
33
|
init: function () { return undecidedState(); },
|
|
34
34
|
allowed: function (cat) { return cat === 'necessary'; },
|
package/package.json
CHANGED
package/src/ck-core.js
CHANGED
|
@@ -629,7 +629,62 @@
|
|
|
629
629
|
return !allowed(cat);
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
+
// Registry of everything the engine intercepted, for the debug panel (§8.1
|
|
633
|
+
// item 3). Kept deliberately small: host + path only, никаких query strings —
|
|
634
|
+
// a tracker URL's query carries ids and, on badly built sites, PII.
|
|
635
|
+
// Capped so a page that injects trackers in a loop cannot grow it without
|
|
636
|
+
// bound; the panel shows the first BLOCKED_MAX, which is always enough to see
|
|
637
|
+
// what is happening.
|
|
638
|
+
var BLOCKED_MAX = 200;
|
|
639
|
+
var blockedLog = [];
|
|
640
|
+
|
|
641
|
+
// host + path, query and fragment dropped.
|
|
642
|
+
function safeUrlParts(src) {
|
|
643
|
+
var host = '';
|
|
644
|
+
var path = '';
|
|
645
|
+
try {
|
|
646
|
+
var base = (global.location && global.location.href) || 'http://localhost/';
|
|
647
|
+
var u = new URL(String(src), base);
|
|
648
|
+
host = (u.hostname || '').toLowerCase();
|
|
649
|
+
path = u.pathname || '';
|
|
650
|
+
} catch (e) {
|
|
651
|
+
var s = String(src || '');
|
|
652
|
+
var m = /^(?:[a-z]+:)?\/\/([^/?#]+)([^?#]*)/i.exec(s);
|
|
653
|
+
if (m) {
|
|
654
|
+
host = m[1].toLowerCase().replace(/:\d+$/, '');
|
|
655
|
+
path = m[2] || '';
|
|
656
|
+
} else {
|
|
657
|
+
path = s.split('?')[0].split('#')[0];
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return { host: host, path: path };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function noteBlocked(el, src, cat, origin) {
|
|
664
|
+
try {
|
|
665
|
+
if (blockedLog.length >= BLOCKED_MAX) { return; }
|
|
666
|
+
var kind = 'script';
|
|
667
|
+
try {
|
|
668
|
+
var tag = el && el.tagName ? String(el.tagName).toLowerCase() : '';
|
|
669
|
+
if (tag) { kind = tag === 'img' ? 'img' : tag; }
|
|
670
|
+
} catch (e2) { /* noop */ }
|
|
671
|
+
var parts = safeUrlParts(src);
|
|
672
|
+
for (var i = 0; i < blockedLog.length; i++) {
|
|
673
|
+
var p = blockedLog[i];
|
|
674
|
+
if (p.host === parts.host && p.path === parts.path && p.kind === kind) { return; }
|
|
675
|
+
}
|
|
676
|
+
blockedLog.push({
|
|
677
|
+
host: parts.host,
|
|
678
|
+
path: parts.path,
|
|
679
|
+
kind: kind,
|
|
680
|
+
category: cat || null,
|
|
681
|
+
origin: origin || 'engine'
|
|
682
|
+
});
|
|
683
|
+
} catch (e) { /* noop */ }
|
|
684
|
+
}
|
|
685
|
+
|
|
632
686
|
function markBlocked(el, src, cat) {
|
|
687
|
+
noteBlocked(el, src, cat, 'engine');
|
|
633
688
|
try {
|
|
634
689
|
if (nativeSetAttribute) {
|
|
635
690
|
nativeSetAttribute.call(el, 'data-ck-blocked', '1');
|
|
@@ -961,7 +1016,7 @@
|
|
|
961
1016
|
// Public API
|
|
962
1017
|
// ---------------------------------------------------------------------------
|
|
963
1018
|
var ConsentKit = {
|
|
964
|
-
version: '0.3.
|
|
1019
|
+
version: '0.3.5',
|
|
965
1020
|
config: config,
|
|
966
1021
|
|
|
967
1022
|
init: function (userConfig) {
|
|
@@ -1017,7 +1072,58 @@
|
|
|
1017
1072
|
|
|
1018
1073
|
// Introspection helpers for the demo status panel (read-only).
|
|
1019
1074
|
_categoryForUrl: categoryForUrl,
|
|
1020
|
-
_categories: CATEGORIES.slice()
|
|
1075
|
+
_categories: CATEGORIES.slice(),
|
|
1076
|
+
|
|
1077
|
+
// What is being held back until consent: everything the engine intercepted
|
|
1078
|
+
// (origin 'engine') plus what the site author marked up by hand (origin
|
|
1079
|
+
// 'markup'), which never goes through markBlocked. Read-only, host+path
|
|
1080
|
+
// only — no query strings, so no ids and no PII. Used by src/ck-debug.js.
|
|
1081
|
+
_blocked: function () {
|
|
1082
|
+
var out = [];
|
|
1083
|
+
var seen = {};
|
|
1084
|
+
function add(rec) {
|
|
1085
|
+
var key = rec.kind + '|' + rec.host + '|' + rec.path;
|
|
1086
|
+
if (seen[key]) { return; }
|
|
1087
|
+
seen[key] = 1;
|
|
1088
|
+
out.push(rec);
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
for (var i = 0; i < blockedLog.length; i++) {
|
|
1092
|
+
var b = blockedLog[i];
|
|
1093
|
+
// Interceptions are kept for the life of the page, but once the
|
|
1094
|
+
// category is granted the element has been revived and is no longer
|
|
1095
|
+
// held back — reporting it as still blocked would be false.
|
|
1096
|
+
if (allowed(b.category)) { continue; }
|
|
1097
|
+
add({ host: b.host, path: b.path, kind: b.kind, category: b.category, origin: b.origin });
|
|
1098
|
+
}
|
|
1099
|
+
} catch (e) { /* noop */ }
|
|
1100
|
+
// Hand-marked tags still waiting for their category.
|
|
1101
|
+
try {
|
|
1102
|
+
// Only what applyConsentToDom() can actually revive. An img is never
|
|
1103
|
+
// revived by the core, so listing hand-marked images here would show a
|
|
1104
|
+
// pending state that never clears after the visitor accepts.
|
|
1105
|
+
qsa('script[type="text/plain"][data-ck], iframe[data-ck][data-src]')
|
|
1106
|
+
.forEach(function (el) {
|
|
1107
|
+
try {
|
|
1108
|
+
if (el.getAttribute('data-ck-restored')) { return; }
|
|
1109
|
+
if (el.getAttribute('data-ck-blocked')) { return; } // already in the registry
|
|
1110
|
+
var tag = String(el.tagName || '').toLowerCase();
|
|
1111
|
+
if (tag === 'iframe' && el.getAttribute('src')) { return; }
|
|
1112
|
+
var src = el.getAttribute('data-src') || el.getAttribute('data-ck-src') || '';
|
|
1113
|
+
if (!src) { return; }
|
|
1114
|
+
var cat = el.getAttribute('data-ck') || categoryForUrl(src);
|
|
1115
|
+
if (allowed(cat)) { return; }
|
|
1116
|
+
var parts = safeUrlParts(src);
|
|
1117
|
+
add({
|
|
1118
|
+
host: parts.host, path: parts.path,
|
|
1119
|
+
kind: tag === 'iframe' ? 'iframe' : 'script',
|
|
1120
|
+
category: cat || null, origin: 'markup'
|
|
1121
|
+
});
|
|
1122
|
+
} catch (e2) { /* noop */ }
|
|
1123
|
+
});
|
|
1124
|
+
} catch (e3) { /* noop */ }
|
|
1125
|
+
return out;
|
|
1126
|
+
}
|
|
1021
1127
|
};
|
|
1022
1128
|
|
|
1023
1129
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ConsentKit debug loader — the activation switch for the debug panel.
|
|
3
|
+
*
|
|
4
|
+
* The panel itself (src/ck-debug.js) is ~30 KB and useful to exactly one
|
|
5
|
+
* person on the page: the site owner, once, while diagnosing something. This
|
|
6
|
+
* loader ships instead of it in the inline blocks and in the WordPress plugin,
|
|
7
|
+
* and fetches the panel only when the visitor asks for it. Without activation
|
|
8
|
+
* it creates no DOM, installs no observers and makes no network request.
|
|
9
|
+
*
|
|
10
|
+
* Activation (?ck_debug=1 / #ck_debug / localStorage) and the three-step
|
|
11
|
+
* resolution order for the panel URL are documented in README «Debug mode».
|
|
12
|
+
* Two things are worth knowing while reading this file:
|
|
13
|
+
*
|
|
14
|
+
* - The off switch has to work even though the panel is not loaded, so the
|
|
15
|
+
* loader — not the panel — owns the stored flag. ck-debug.js keeps its own
|
|
16
|
+
* identical copy of parseActivation() because it is loaded directly on our
|
|
17
|
+
* demo page and must stay self-contained; writing the flag twice is
|
|
18
|
+
* idempotent, and test/debug-loader.test.mjs asserts the two agree.
|
|
19
|
+
* - Nothing here runs before activation. No DOM, no request, no observers.
|
|
20
|
+
*
|
|
21
|
+
* Load order: ck-core.js -> ck-locales.js -> ck-ui.js -> [ck-saas.js] -> this.
|
|
22
|
+
*
|
|
23
|
+
* Copyright (c) 2026 E-COM CONSULT PLUS. MIT License — see LICENSE.
|
|
24
|
+
*/
|
|
25
|
+
(function (global) {
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
if (!global || typeof global !== 'object') { return; }
|
|
29
|
+
|
|
30
|
+
var LS_KEY = 'ck_debug';
|
|
31
|
+
var CDN = 'https://cdn.jsdelivr.net/npm/@ecomconsult/consentkit@';
|
|
32
|
+
|
|
33
|
+
function isOffValue(v) {
|
|
34
|
+
return v === '0' || v === 'false' || v === 'no' || v === '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Identical to src/ck-debug.js parseActivation(). Pure: no DOM, no storage.
|
|
38
|
+
function parseActivation(search, hash, stored) {
|
|
39
|
+
var on = null;
|
|
40
|
+
|
|
41
|
+
var q = String(search || '');
|
|
42
|
+
if (q.charAt(0) === '?') { q = q.slice(1); }
|
|
43
|
+
var pairs = q ? q.split('&') : [];
|
|
44
|
+
for (var i = 0; i < pairs.length; i++) {
|
|
45
|
+
var eq = pairs[i].indexOf('=');
|
|
46
|
+
var k = eq === -1 ? pairs[i] : pairs[i].slice(0, eq);
|
|
47
|
+
if (k !== LS_KEY) { continue; }
|
|
48
|
+
on = eq === -1 ? true : !isOffValue(pairs[i].slice(eq + 1));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (on === null) {
|
|
52
|
+
var h = String(hash || '');
|
|
53
|
+
if (h.charAt(0) === '#') { h = h.slice(1); }
|
|
54
|
+
if (h === LS_KEY) { on = true; }
|
|
55
|
+
else if (h.indexOf(LS_KEY + '=') === 0) { on = !isOffValue(h.slice(LS_KEY.length + 1)); }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (on === null) {
|
|
59
|
+
return { active: String(stored || '') === '1', persist: null };
|
|
60
|
+
}
|
|
61
|
+
return { active: on, persist: on ? 'on' : 'off' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pure: takes what the page happens to expose, returns the URL to load.
|
|
65
|
+
// `explicit` is window.ConsentKitDebugUrl, `saas` is ConsentKit._saas.
|
|
66
|
+
// Order: explicit URL -> SaaS API origin -> jsDelivr pinned to the running
|
|
67
|
+
// core version. See README «Debug mode».
|
|
68
|
+
function resolveUrl(explicit, saas, version) {
|
|
69
|
+
var pinned = String(explicit || '').trim();
|
|
70
|
+
if (pinned) { return pinned; }
|
|
71
|
+
|
|
72
|
+
if (saas && saas.siteId && saas.api) {
|
|
73
|
+
var base = String(saas.api).replace(/\/+$/, '');
|
|
74
|
+
if (base) { return base + '/client/ck-debug.js'; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return CDN + encodeURIComponent(String(version || 'latest')) + '/src/ck-debug.js';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var API = {
|
|
81
|
+
parseActivation: parseActivation,
|
|
82
|
+
resolveUrl: resolveUrl,
|
|
83
|
+
active: false,
|
|
84
|
+
url: null
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
if (global.ConsentKit) { global.ConsentKit._debugLoader = API; }
|
|
88
|
+
global.__ckDebugLoader = API;
|
|
89
|
+
} catch (e) { /* noop */ }
|
|
90
|
+
|
|
91
|
+
function lsGet(k) {
|
|
92
|
+
try { return global.localStorage ? global.localStorage.getItem(k) : null; } catch (e) { return null; }
|
|
93
|
+
}
|
|
94
|
+
function lsSet(k, v) {
|
|
95
|
+
try { if (global.localStorage) { global.localStorage.setItem(k, v); } } catch (e) { /* noop */ }
|
|
96
|
+
}
|
|
97
|
+
function lsDel(k) {
|
|
98
|
+
try { if (global.localStorage) { global.localStorage.removeItem(k); } } catch (e) { /* noop */ }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
var loc = global.location;
|
|
102
|
+
var act = parseActivation(loc && loc.search, loc && loc.hash, lsGet(LS_KEY));
|
|
103
|
+
// Persist BEFORE the early return: ?ck_debug=0 has to clear the stored flag
|
|
104
|
+
// even though nothing is loaded afterwards, or the panel could never be
|
|
105
|
+
// switched off again once it had been switched on.
|
|
106
|
+
if (act.persist === 'on') { lsSet(LS_KEY, '1'); }
|
|
107
|
+
if (act.persist === 'off') { lsDel(LS_KEY); }
|
|
108
|
+
if (!act.active) { return; }
|
|
109
|
+
|
|
110
|
+
var doc = global.document;
|
|
111
|
+
if (!doc || typeof doc.createElement !== 'function') { return; }
|
|
112
|
+
|
|
113
|
+
// Already loaded (the panel published its surface, or this ran twice).
|
|
114
|
+
if (global.__ckDebug) { API.active = true; return; }
|
|
115
|
+
|
|
116
|
+
var CK = global.ConsentKit;
|
|
117
|
+
var url = resolveUrl(
|
|
118
|
+
global.ConsentKitDebugUrl,
|
|
119
|
+
CK && CK._saas,
|
|
120
|
+
(CK && CK.version) || ''
|
|
121
|
+
);
|
|
122
|
+
API.active = true;
|
|
123
|
+
API.url = url;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
var s = doc.createElement('script');
|
|
127
|
+
s.src = url;
|
|
128
|
+
s.async = true;
|
|
129
|
+
// No opt-out attribute is needed: the blocking engine matches a fixed list
|
|
130
|
+
// of tracker hosts, and neither jsDelivr nor a ConsentKit API host is on
|
|
131
|
+
// it — test/debug-loader.test.mjs asserts that.
|
|
132
|
+
(doc.head || doc.body || doc.documentElement).appendChild(s);
|
|
133
|
+
} catch (e) { /* noop */ }
|
|
134
|
+
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this));
|
package/src/ck-debug.js
ADDED
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ConsentKit debug panel (opt-in) — shows what the banner actually did on this
|
|
3
|
+
* page. Off by default: without activation this file creates no DOM, installs
|
|
4
|
+
* no observers and touches nothing.
|
|
5
|
+
*
|
|
6
|
+
* Activation (this browser only, nothing is sent anywhere):
|
|
7
|
+
* ?ck_debug=1 or #ck_debug — turns it on and remembers it (localStorage)
|
|
8
|
+
* ?ck_debug=0 — turns it off and forgets it
|
|
9
|
+
* localStorage.ck_debug = '1' — same as the query parameter
|
|
10
|
+
*
|
|
11
|
+
* Load order: ck-core.js -> ck-locales.js -> ck-ui.js -> [ck-saas.js] -> ck-debug.js
|
|
12
|
+
*
|
|
13
|
+
* Copyright (c) 2026 E-COM CONSULT PLUS. MIT License — see LICENSE.
|
|
14
|
+
*/
|
|
15
|
+
(function (global) {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
if (!global || typeof global !== 'object') { return; }
|
|
19
|
+
|
|
20
|
+
var LS_KEY = 'ck_debug';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Activation (pure, testable: no DOM, no storage)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Returns { active, persist } where persist is 'on' | 'off' | null:
|
|
26
|
+
// the query/hash form is sticky so the panel survives navigation, the bare
|
|
27
|
+
// localStorage form changes nothing.
|
|
28
|
+
function isOffValue(v) {
|
|
29
|
+
return v === '0' || v === 'false' || v === 'no' || v === '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseActivation(search, hash, stored) {
|
|
33
|
+
var on = null;
|
|
34
|
+
|
|
35
|
+
var q = String(search || '');
|
|
36
|
+
if (q.charAt(0) === '?') { q = q.slice(1); }
|
|
37
|
+
var pairs = q ? q.split('&') : [];
|
|
38
|
+
for (var i = 0; i < pairs.length; i++) {
|
|
39
|
+
var eq = pairs[i].indexOf('=');
|
|
40
|
+
var k = eq === -1 ? pairs[i] : pairs[i].slice(0, eq);
|
|
41
|
+
if (k !== LS_KEY) { continue; }
|
|
42
|
+
// ?ck_debug (no "="), ?ck_debug=1, =true, =yes -> on
|
|
43
|
+
// ?ck_debug=0, =false, =no, =(empty) -> off. The empty value is treated
|
|
44
|
+
// as off deliberately: a form or a link builder that drops the value is
|
|
45
|
+
// far more likely to mean "not set" than "switch the panel on".
|
|
46
|
+
on = eq === -1 ? true : !isOffValue(pairs[i].slice(eq + 1));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (on === null) {
|
|
50
|
+
var h = String(hash || '');
|
|
51
|
+
if (h.charAt(0) === '#') { h = h.slice(1); }
|
|
52
|
+
// #ck_debug or #ck_debug=1 (a plain fragment id, not a query)
|
|
53
|
+
if (h === LS_KEY) { on = true; }
|
|
54
|
+
else if (h.indexOf(LS_KEY + '=') === 0) { on = !isOffValue(h.slice(LS_KEY.length + 1)); }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (on === null) {
|
|
58
|
+
return { active: String(stored || '') === '1', persist: null };
|
|
59
|
+
}
|
|
60
|
+
return { active: on, persist: on ? 'on' : 'off' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Report (pure, testable: takes plain data, returns plain data)
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// No PII by construction: cookie names only (never values), host + path only
|
|
67
|
+
// (never query strings — tracker URLs carry ids there).
|
|
68
|
+
function stripUrl(url) {
|
|
69
|
+
var s = String(url || '');
|
|
70
|
+
var host = '';
|
|
71
|
+
var path = '';
|
|
72
|
+
var m = /^(?:[a-z]+:)?\/\/([^/?#]+)([^?#]*)/i.exec(s);
|
|
73
|
+
if (m) {
|
|
74
|
+
host = m[1].toLowerCase().replace(/:\d+$/, '');
|
|
75
|
+
path = m[2] || '';
|
|
76
|
+
} else {
|
|
77
|
+
path = s.split('?')[0].split('#')[0];
|
|
78
|
+
}
|
|
79
|
+
return { host: host, path: path };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// entries: PerformanceResourceTiming-like [{ name, startTime, initiatorType }]
|
|
83
|
+
// consentAtMs: performance-clock ms of the decision, or null when undecided
|
|
84
|
+
// classify: url -> category | null (ConsentKit._categoryForUrl)
|
|
85
|
+
function buildRequests(entries, consentAtMs, classify) {
|
|
86
|
+
var out = [];
|
|
87
|
+
var list = entries || [];
|
|
88
|
+
var seen = {};
|
|
89
|
+
for (var i = 0; i < list.length; i++) {
|
|
90
|
+
var e = list[i];
|
|
91
|
+
if (!e || !e.name) { continue; }
|
|
92
|
+
var cat = null;
|
|
93
|
+
try { cat = classify ? classify(e.name) : null; } catch (err) { cat = null; }
|
|
94
|
+
if (!cat) { continue; } // only known trackers
|
|
95
|
+
var p = stripUrl(e.name);
|
|
96
|
+
var t = Number(e.startTime) || 0;
|
|
97
|
+
var when = consentAtMs === null || consentAtMs === undefined
|
|
98
|
+
? 'before' : (t >= consentAtMs ? 'after' : 'before');
|
|
99
|
+
var key = p.host + '|' + p.path + '|' + when;
|
|
100
|
+
if (seen[key]) { seen[key].count++; continue; }
|
|
101
|
+
var rec = {
|
|
102
|
+
host: p.host, path: p.path, category: cat, when: when,
|
|
103
|
+
at: Math.round(t), kind: String((e && e.initiatorType) || ''), count: 1
|
|
104
|
+
};
|
|
105
|
+
seen[key] = rec;
|
|
106
|
+
out.push(rec);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildReport(input) {
|
|
112
|
+
var d = input || {};
|
|
113
|
+
var st = d.state || {};
|
|
114
|
+
var cats = st.categories || {};
|
|
115
|
+
return {
|
|
116
|
+
generatedAt: d.now || null,
|
|
117
|
+
client: {
|
|
118
|
+
version: d.version || null,
|
|
119
|
+
source: d.siteId ? 'saas' : 'inline',
|
|
120
|
+
siteId: d.siteId || null,
|
|
121
|
+
policyVersion: st.policyVersion || (d.config && d.config.policyVersion) || null,
|
|
122
|
+
etag: d.etag || null
|
|
123
|
+
},
|
|
124
|
+
consent: {
|
|
125
|
+
status: !st.decided ? 'none'
|
|
126
|
+
: (cats.functional && cats.analytics && cats.marketing) ? 'accepted'
|
|
127
|
+
: (!cats.functional && !cats.analytics && !cats.marketing) ? 'rejected'
|
|
128
|
+
: 'partial',
|
|
129
|
+
categories: {
|
|
130
|
+
necessary: true,
|
|
131
|
+
functional: !!cats.functional,
|
|
132
|
+
analytics: !!cats.analytics,
|
|
133
|
+
marketing: !!cats.marketing
|
|
134
|
+
},
|
|
135
|
+
decidedAt: st.ts || null,
|
|
136
|
+
method: st.method || null,
|
|
137
|
+
ttlDays: d.ttlDays == null ? null : Number(d.ttlDays)
|
|
138
|
+
},
|
|
139
|
+
blocked: (d.blocked || []).map(function (b) {
|
|
140
|
+
return { host: b.host, path: b.path, kind: b.kind, category: b.category, origin: b.origin };
|
|
141
|
+
}),
|
|
142
|
+
requests: buildRequests(d.entries, d.consentAtMs, d.classify),
|
|
143
|
+
consentMode: (d.consentMode || []).slice(),
|
|
144
|
+
// Names only — a consent debug panel must never leak cookie contents.
|
|
145
|
+
cookieNames: (d.cookieNames || []).slice(),
|
|
146
|
+
note: NOTE_EN
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var NOTE_RU = 'Запросы, ушедшие до загрузки ConsentKit (обычный <script src> ' +
|
|
151
|
+
'в разметке), видны в этом списке, но заблокировать их клиент не может — ' +
|
|
152
|
+
'такие теги размечают вручную.';
|
|
153
|
+
var NOTE_EN = 'Requests that left before ConsentKit loaded (a plain <script src> ' +
|
|
154
|
+
'written into the HTML) show up here but cannot be blocked — mark such tags up manually.';
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Panel language (pure; the JSON report stays language-neutral either way)
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// The panel is read by whoever is debugging the site, so it follows the same
|
|
160
|
+
// language the banner resolved for this visitor rather than a build flag.
|
|
161
|
+
// Only ru and en exist: this is an internal diagnostic surface, and a
|
|
162
|
+
// half-translated one is worse than an English one.
|
|
163
|
+
var STRINGS = {
|
|
164
|
+
ru: {
|
|
165
|
+
regionLabel: 'ConsentKit — режим отладки',
|
|
166
|
+
collapse: 'Свернуть',
|
|
167
|
+
expand: 'Развернуть',
|
|
168
|
+
closeLabel: 'Закрыть и выключить режим отладки',
|
|
169
|
+
secClient: 'Клиент',
|
|
170
|
+
version: 'версия',
|
|
171
|
+
source: 'источник',
|
|
172
|
+
srcSaas: 'SaaS',
|
|
173
|
+
srcInline: 'инлайн',
|
|
174
|
+
secConsent: 'Согласие',
|
|
175
|
+
decidedAt: 'решение',
|
|
176
|
+
method: 'способ',
|
|
177
|
+
ttl: 'срок cookie',
|
|
178
|
+
days: ' дн.',
|
|
179
|
+
status: { none: 'нет решения', accepted: 'принято', rejected: 'отклонено', partial: 'частично' },
|
|
180
|
+
secBlocked: 'Заблокировано до согласия',
|
|
181
|
+
noBlocked: 'ничего не перехвачено',
|
|
182
|
+
markup: ' (разметка)',
|
|
183
|
+
secRequests: 'Запросы к трекерам',
|
|
184
|
+
noRequests: 'запросов к известным трекерам не было',
|
|
185
|
+
after: 'после согласия',
|
|
186
|
+
before: 'до согласия',
|
|
187
|
+
ms: ' мс',
|
|
188
|
+
note: NOTE_RU,
|
|
189
|
+
secConsentMode: 'Consent Mode / dataLayer',
|
|
190
|
+
noEvents: 'событий не было',
|
|
191
|
+
secActions: 'Действия',
|
|
192
|
+
reset: 'Сбросить согласие',
|
|
193
|
+
showPrefs: 'Показать настройки',
|
|
194
|
+
copy: 'Скопировать отчёт',
|
|
195
|
+
copied: 'Скопировано',
|
|
196
|
+
copyFailed: 'Не вышло',
|
|
197
|
+
footer: 'Панель видна только в этом браузере. Выключить: добавьте ?ck_debug=0 к адресу.'
|
|
198
|
+
},
|
|
199
|
+
en: {
|
|
200
|
+
regionLabel: 'ConsentKit — debug mode',
|
|
201
|
+
collapse: 'Collapse',
|
|
202
|
+
expand: 'Expand',
|
|
203
|
+
closeLabel: 'Close and turn debug mode off',
|
|
204
|
+
secClient: 'Client',
|
|
205
|
+
version: 'version',
|
|
206
|
+
source: 'source',
|
|
207
|
+
srcSaas: 'SaaS',
|
|
208
|
+
srcInline: 'inline',
|
|
209
|
+
secConsent: 'Consent',
|
|
210
|
+
decidedAt: 'decided',
|
|
211
|
+
method: 'method',
|
|
212
|
+
ttl: 'cookie lifetime',
|
|
213
|
+
days: ' days',
|
|
214
|
+
status: { none: 'no decision', accepted: 'accepted', rejected: 'rejected', partial: 'partial' },
|
|
215
|
+
secBlocked: 'Blocked until consent',
|
|
216
|
+
noBlocked: 'nothing intercepted',
|
|
217
|
+
markup: ' (markup)',
|
|
218
|
+
secRequests: 'Tracker requests',
|
|
219
|
+
noRequests: 'no requests to known trackers',
|
|
220
|
+
after: 'after consent',
|
|
221
|
+
before: 'before consent',
|
|
222
|
+
ms: ' ms',
|
|
223
|
+
note: NOTE_EN,
|
|
224
|
+
secConsentMode: 'Consent Mode / dataLayer',
|
|
225
|
+
noEvents: 'no events',
|
|
226
|
+
secActions: 'Actions',
|
|
227
|
+
reset: 'Reset consent',
|
|
228
|
+
showPrefs: 'Show preferences',
|
|
229
|
+
copy: 'Copy report',
|
|
230
|
+
copied: 'Copied',
|
|
231
|
+
copyFailed: 'Failed',
|
|
232
|
+
footer: 'This panel is visible in this browser only. To turn it off, add ?ck_debug=0 to the URL.'
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// banner language (ConsentKit config) -> navigator.language -> en.
|
|
237
|
+
// `cfgLang` is ConsentKit.config.language, which may be 'auto'.
|
|
238
|
+
function pickLang(cfgLang, navLang) {
|
|
239
|
+
var raw = String(cfgLang || '').toLowerCase();
|
|
240
|
+
if (!raw || raw === 'auto') { raw = String(navLang || '').toLowerCase(); }
|
|
241
|
+
return raw.slice(0, 2) === 'ru' ? 'ru' : 'en';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Testable surface. Published before the activation check so the inactive
|
|
245
|
+
// path is testable too; it is not a public API.
|
|
246
|
+
var API = {
|
|
247
|
+
parseActivation: parseActivation,
|
|
248
|
+
buildReport: buildReport,
|
|
249
|
+
buildRequests: buildRequests,
|
|
250
|
+
stripUrl: stripUrl,
|
|
251
|
+
pickLang: pickLang,
|
|
252
|
+
strings: STRINGS,
|
|
253
|
+
active: false
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
if (global.ConsentKit) { global.ConsentKit._debug = API; }
|
|
257
|
+
global.__ckDebug = API;
|
|
258
|
+
} catch (e) { /* noop */ }
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Everything below runs only when activated.
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
var doc = global.document;
|
|
264
|
+
var loc = global.location;
|
|
265
|
+
|
|
266
|
+
function lsGet(k) {
|
|
267
|
+
try { return global.localStorage ? global.localStorage.getItem(k) : null; } catch (e) { return null; }
|
|
268
|
+
}
|
|
269
|
+
function lsSet(k, v) {
|
|
270
|
+
try { if (global.localStorage) { global.localStorage.setItem(k, v); } } catch (e) { /* noop */ }
|
|
271
|
+
}
|
|
272
|
+
function lsDel(k) {
|
|
273
|
+
try { if (global.localStorage) { global.localStorage.removeItem(k); } } catch (e) { /* noop */ }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
var act = parseActivation(loc && loc.search, loc && loc.hash, lsGet(LS_KEY));
|
|
277
|
+
if (act.persist === 'on') { lsSet(LS_KEY, '1'); }
|
|
278
|
+
if (act.persist === 'off') { lsDel(LS_KEY); }
|
|
279
|
+
if (!act.active) { return; }
|
|
280
|
+
if (!doc || typeof doc.createElement !== 'function') { return; }
|
|
281
|
+
API.active = true;
|
|
282
|
+
|
|
283
|
+
var CK = global.ConsentKit;
|
|
284
|
+
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Observation (installed only when active)
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
var resources = [];
|
|
289
|
+
var consentAtMs = null; // performance clock, set when consent is given
|
|
290
|
+
var consentMode = []; // last ck_* dataLayer events and gtag consent calls
|
|
291
|
+
var CM_MAX = 20;
|
|
292
|
+
|
|
293
|
+
function nowMs() {
|
|
294
|
+
try {
|
|
295
|
+
if (global.performance && typeof global.performance.now === 'function') {
|
|
296
|
+
return global.performance.now();
|
|
297
|
+
}
|
|
298
|
+
} catch (e) { /* noop */ }
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// A decision restored from a previous page load happened before this page
|
|
303
|
+
// started, so every request on this load counts as "after".
|
|
304
|
+
function seedConsentTime() {
|
|
305
|
+
try {
|
|
306
|
+
var st = CK && CK.getState ? CK.getState() : null;
|
|
307
|
+
if (st && st.decided && consentAtMs === null) { consentAtMs = 0; }
|
|
308
|
+
} catch (e) { /* noop */ }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Stamp the moment the visitor decides, BEFORE the core acts on it.
|
|
312
|
+
//
|
|
313
|
+
// This cannot wait for ck:change: accept() revives the blocked scripts
|
|
314
|
+
// synchronously and dispatches ck:change afterwards, so by the time the event
|
|
315
|
+
// fires those scripts have already issued their requests. Stamping on the
|
|
316
|
+
// event would then place them *before* the cutoff and the panel would report
|
|
317
|
+
// "до согласия" for the very requests the consent just released — exactly
|
|
318
|
+
// backwards, and on the one line an owner reads most carefully.
|
|
319
|
+
//
|
|
320
|
+
// Wrapping is observe-only: the original method is called with the original
|
|
321
|
+
// arguments and its return value passed straight back.
|
|
322
|
+
function stampOnDecision() {
|
|
323
|
+
try {
|
|
324
|
+
if (!CK) { return; }
|
|
325
|
+
['accept', 'rejectAll'].forEach(function (name) {
|
|
326
|
+
var orig = CK[name];
|
|
327
|
+
if (typeof orig !== 'function' || orig.__ckDebugWrapped) { return; }
|
|
328
|
+
var wrapped = function () {
|
|
329
|
+
try { consentAtMs = nowMs(); } catch (e) { /* noop */ }
|
|
330
|
+
return orig.apply(CK, arguments);
|
|
331
|
+
};
|
|
332
|
+
wrapped.__ckDebugWrapped = true;
|
|
333
|
+
CK[name] = wrapped;
|
|
334
|
+
});
|
|
335
|
+
// withdraw() puts the page back to "no decision yet".
|
|
336
|
+
var w = CK.withdraw;
|
|
337
|
+
if (typeof w === 'function' && !w.__ckDebugWrapped) {
|
|
338
|
+
var wrappedW = function () {
|
|
339
|
+
try { consentAtMs = null; } catch (e) { /* noop */ }
|
|
340
|
+
return w.apply(CK, arguments);
|
|
341
|
+
};
|
|
342
|
+
wrappedW.__ckDebugWrapped = true;
|
|
343
|
+
CK.withdraw = wrappedW;
|
|
344
|
+
}
|
|
345
|
+
} catch (e) { /* noop */ }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function collectResources() {
|
|
349
|
+
try {
|
|
350
|
+
var perf = global.performance;
|
|
351
|
+
if (!perf) { return; }
|
|
352
|
+
if (typeof perf.getEntriesByType === 'function') {
|
|
353
|
+
var buffered = perf.getEntriesByType('resource') || [];
|
|
354
|
+
for (var i = 0; i < buffered.length; i++) { resources.push(buffered[i]); }
|
|
355
|
+
}
|
|
356
|
+
if (typeof global.PerformanceObserver === 'function') {
|
|
357
|
+
var po = new global.PerformanceObserver(function (list) {
|
|
358
|
+
try {
|
|
359
|
+
var got = list.getEntries() || [];
|
|
360
|
+
for (var j = 0; j < got.length; j++) { resources.push(got[j]); }
|
|
361
|
+
schedule();
|
|
362
|
+
} catch (e2) { /* noop */ }
|
|
363
|
+
});
|
|
364
|
+
// buffered:true re-delivers what happened before we attached; harmless
|
|
365
|
+
// duplicates are deduped when the list is rendered.
|
|
366
|
+
try { po.observe({ type: 'resource', buffered: true }); }
|
|
367
|
+
catch (e3) { try { po.observe({ entryTypes: ['resource'] }); } catch (e4) { /* noop */ } }
|
|
368
|
+
}
|
|
369
|
+
} catch (e) { /* noop */ }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Observe only: the existing dataLayer.push is called first and its return
|
|
373
|
+
// value passed through, so a GTM/other wrapper keeps working.
|
|
374
|
+
function watchDataLayer() {
|
|
375
|
+
try {
|
|
376
|
+
var dl = global.dataLayer;
|
|
377
|
+
if (!dl) { global.dataLayer = dl = []; }
|
|
378
|
+
if (typeof dl.push !== 'function' || dl.__ckDebugWatched) { return; }
|
|
379
|
+
// Whatever is already in the queue counts too.
|
|
380
|
+
for (var i = 0; i < dl.length; i++) { noteDataLayer(dl[i]); }
|
|
381
|
+
var prev = dl.push;
|
|
382
|
+
dl.push = function () {
|
|
383
|
+
try {
|
|
384
|
+
for (var j = 0; j < arguments.length; j++) { noteDataLayer(arguments[j]); }
|
|
385
|
+
} catch (e) { /* never break the host page */ }
|
|
386
|
+
return prev.apply(this, arguments);
|
|
387
|
+
};
|
|
388
|
+
dl.__ckDebugWatched = true;
|
|
389
|
+
} catch (e) { /* noop */ }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function noteDataLayer(arg) {
|
|
393
|
+
try {
|
|
394
|
+
var rec = null;
|
|
395
|
+
// gtag() pushes an arguments object: ['consent', 'default'|'update', {...}]
|
|
396
|
+
if (arg && typeof arg === 'object' && typeof arg.length === 'number' && arg[0] === 'consent') {
|
|
397
|
+
var signals = {};
|
|
398
|
+
var payload = arg[2];
|
|
399
|
+
if (payload && typeof payload === 'object') {
|
|
400
|
+
for (var k in payload) {
|
|
401
|
+
if (Object.prototype.hasOwnProperty.call(payload, k)) { signals[k] = payload[k]; }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
rec = { type: 'gtag consent ' + String(arg[1] || ''), signals: signals, at: Math.round(nowMs()) };
|
|
405
|
+
} else if (arg && typeof arg === 'object' && typeof arg.event === 'string' &&
|
|
406
|
+
arg.event.indexOf('ck_') === 0) {
|
|
407
|
+
rec = { type: arg.event, signals: null, at: Math.round(nowMs()) };
|
|
408
|
+
}
|
|
409
|
+
if (!rec) { return; }
|
|
410
|
+
consentMode.push(rec);
|
|
411
|
+
if (consentMode.length > CM_MAX) { consentMode.shift(); }
|
|
412
|
+
schedule();
|
|
413
|
+
} catch (e) { /* noop */ }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function cookieNames() {
|
|
417
|
+
var names = [];
|
|
418
|
+
try {
|
|
419
|
+
var raw = typeof doc.cookie === 'string' ? doc.cookie : '';
|
|
420
|
+
var parts = raw.split(';');
|
|
421
|
+
for (var i = 0; i < parts.length; i++) {
|
|
422
|
+
var t = parts[i].trim();
|
|
423
|
+
if (!t) { continue; }
|
|
424
|
+
var eq = t.indexOf('=');
|
|
425
|
+
names.push(eq === -1 ? t : t.slice(0, eq)); // name only, never the value
|
|
426
|
+
}
|
|
427
|
+
} catch (e) { /* noop */ }
|
|
428
|
+
return names;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function saasInfo() {
|
|
432
|
+
var out = { siteId: null, etag: null };
|
|
433
|
+
try {
|
|
434
|
+
if (CK && CK._saas) {
|
|
435
|
+
out.siteId = CK._saas.siteId || null;
|
|
436
|
+
var cached = null;
|
|
437
|
+
try {
|
|
438
|
+
var raw = lsGet('ck_cfg_' + out.siteId);
|
|
439
|
+
cached = raw ? JSON.parse(raw) : null;
|
|
440
|
+
} catch (e2) { cached = null; }
|
|
441
|
+
out.etag = cached && cached.etag ? cached.etag : null;
|
|
442
|
+
}
|
|
443
|
+
} catch (e) { /* noop */ }
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function reportInput() {
|
|
448
|
+
var s = saasInfo();
|
|
449
|
+
var cfg = (CK && CK.config) || {};
|
|
450
|
+
return {
|
|
451
|
+
now: new Date().toISOString(),
|
|
452
|
+
version: CK ? CK.version : null,
|
|
453
|
+
state: CK && CK.getState ? CK.getState() : {},
|
|
454
|
+
config: cfg,
|
|
455
|
+
siteId: s.siteId,
|
|
456
|
+
etag: s.etag,
|
|
457
|
+
ttlDays: cfg.consentTtlDays == null ? null : cfg.consentTtlDays,
|
|
458
|
+
blocked: (CK && typeof CK._blocked === 'function') ? CK._blocked() : [],
|
|
459
|
+
entries: resources,
|
|
460
|
+
consentAtMs: consentAtMs,
|
|
461
|
+
classify: CK ? CK._categoryForUrl : null,
|
|
462
|
+
consentMode: consentMode,
|
|
463
|
+
cookieNames: cookieNames()
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
// Panel (own Shadow DOM host, own styles)
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
var CSS = [
|
|
471
|
+
':host{all:initial;position:fixed;right:12px;bottom:12px;z-index:2147483646;',
|
|
472
|
+
'font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace}',
|
|
473
|
+
'*{box-sizing:border-box}',
|
|
474
|
+
'.w{width:min(360px,calc(100vw - 24px));background:#12151c;color:#dfe3ea;',
|
|
475
|
+
'border:1px solid #2b3040;border-radius:8px;box-shadow:0 6px 24px rgba(0,0,0,.4);overflow:hidden}',
|
|
476
|
+
'.hd{display:flex;align-items:center;gap:8px;padding:7px 9px;background:#1a1f2a;',
|
|
477
|
+
'border-bottom:1px solid #2b3040}',
|
|
478
|
+
'.hd b{font-weight:600;color:#fff;font-size:12px}',
|
|
479
|
+
'.hd .sp{flex:1}',
|
|
480
|
+
'button{font:inherit;color:#dfe3ea;background:#232936;border:1px solid #39415400;',
|
|
481
|
+
'border-color:#394154;border-radius:5px;padding:3px 8px;cursor:pointer}',
|
|
482
|
+
'button:hover{background:#2c3444}',
|
|
483
|
+
'button:focus-visible{outline:2px solid #7aa2ff;outline-offset:1px}',
|
|
484
|
+
'.bd{max-height:min(60vh,460px);overflow:auto;padding:2px 9px 9px}',
|
|
485
|
+
'.bd[hidden]{display:none}',
|
|
486
|
+
'section{border-top:1px solid #232936;padding:7px 0}',
|
|
487
|
+
'section:first-child{border-top:0}',
|
|
488
|
+
'h2{margin:0 0 4px;font-size:11px;font-weight:600;color:#8d96ab;text-transform:uppercase;',
|
|
489
|
+
'letter-spacing:.04em}',
|
|
490
|
+
'dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:1px 8px}',
|
|
491
|
+
'dt{color:#8d96ab}',
|
|
492
|
+
'dd{margin:0;overflow-wrap:anywhere}',
|
|
493
|
+
'ul{margin:0;padding:0;list-style:none}',
|
|
494
|
+
'li{padding:2px 0;border-top:1px dotted #262c3a;overflow-wrap:anywhere}',
|
|
495
|
+
'li:first-child{border-top:0}',
|
|
496
|
+
'.t{display:inline-block;padding:0 5px;border-radius:3px;font-size:10px;',
|
|
497
|
+
'background:#2a3142;color:#a9b3c9;margin-right:4px}',
|
|
498
|
+
'.t.on{background:#173a24;color:#7ee2a0}',
|
|
499
|
+
'.t.off{background:#3a1a1d;color:#ff9aa2}',
|
|
500
|
+
'.mut{color:#8d96ab}',
|
|
501
|
+
'.note{margin:6px 0 0;color:#8d96ab;font-size:11px;line-height:1.4}',
|
|
502
|
+
'.row{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}',
|
|
503
|
+
'@media (prefers-reduced-motion:no-preference){button{transition:background-color .12s ease}}'
|
|
504
|
+
].join('');
|
|
505
|
+
|
|
506
|
+
var host = null, root = null, body = null, toggleBtn = null, collapsed = false;
|
|
507
|
+
var frame = 0;
|
|
508
|
+
|
|
509
|
+
function el(tag, attrs, text) {
|
|
510
|
+
var n = doc.createElement(tag);
|
|
511
|
+
if (attrs) {
|
|
512
|
+
for (var k in attrs) {
|
|
513
|
+
if (Object.prototype.hasOwnProperty.call(attrs, k)) { n.setAttribute(k, attrs[k]); }
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (text != null) { n.textContent = String(text); }
|
|
517
|
+
return n;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function section(title) {
|
|
521
|
+
var s = doc.createElement('section');
|
|
522
|
+
s.appendChild(el('h2', null, title));
|
|
523
|
+
return s;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function defs(pairs) {
|
|
527
|
+
var dl = doc.createElement('dl');
|
|
528
|
+
for (var i = 0; i < pairs.length; i++) {
|
|
529
|
+
dl.appendChild(el('dt', null, pairs[i][0]));
|
|
530
|
+
dl.appendChild(el('dd', null, pairs[i][1] == null || pairs[i][1] === '' ? '—' : pairs[i][1]));
|
|
531
|
+
}
|
|
532
|
+
return dl;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function tag(text, cls) { return el('span', { class: 't' + (cls ? ' ' + cls : '') }, text); }
|
|
536
|
+
|
|
537
|
+
// Resolved lazily, not at parse time: this file runs before ConsentKit.init()
|
|
538
|
+
// has merged the site's config, so asking for the language now would always
|
|
539
|
+
// read the built-in default. Re-resolved on every render so a page that
|
|
540
|
+
// switches language at runtime switches the panel too.
|
|
541
|
+
var T = STRINGS.en;
|
|
542
|
+
function refreshLang() {
|
|
543
|
+
var nav = global.navigator;
|
|
544
|
+
T = STRINGS[pickLang(
|
|
545
|
+
(CK && CK.config && CK.config.language) || '',
|
|
546
|
+
(nav && (nav.language || nav.userLanguage)) || ''
|
|
547
|
+
)] || STRINGS.en;
|
|
548
|
+
return T;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function render() {
|
|
552
|
+
if (!body) { return; }
|
|
553
|
+
refreshLang();
|
|
554
|
+
var r = buildReport(reportInput());
|
|
555
|
+
body.textContent = '';
|
|
556
|
+
|
|
557
|
+
// 1. Client
|
|
558
|
+
var s1 = section(T.secClient);
|
|
559
|
+
s1.appendChild(defs([
|
|
560
|
+
[T.version, r.client.version],
|
|
561
|
+
[T.source, r.client.source === 'saas' ? T.srcSaas : T.srcInline],
|
|
562
|
+
['siteId', r.client.siteId],
|
|
563
|
+
['policyVersion', r.client.policyVersion],
|
|
564
|
+
['ETag', r.client.etag]
|
|
565
|
+
]));
|
|
566
|
+
body.appendChild(s1);
|
|
567
|
+
|
|
568
|
+
// 2. Consent
|
|
569
|
+
var s2 = section(T.secConsent);
|
|
570
|
+
var line = el('div');
|
|
571
|
+
line.appendChild(tag(T.status[r.consent.status] || r.consent.status,
|
|
572
|
+
r.consent.status === 'accepted' ? 'on' : r.consent.status === 'rejected' ? 'off' : ''));
|
|
573
|
+
s2.appendChild(line);
|
|
574
|
+
var cl = doc.createElement('div');
|
|
575
|
+
['necessary', 'functional', 'analytics', 'marketing'].forEach(function (c) {
|
|
576
|
+
cl.appendChild(tag((r.consent.categories[c] ? '✓ ' : '✗ ') + c,
|
|
577
|
+
r.consent.categories[c] ? 'on' : 'off'));
|
|
578
|
+
});
|
|
579
|
+
s2.appendChild(cl);
|
|
580
|
+
s2.appendChild(defs([
|
|
581
|
+
[T.decidedAt, r.consent.decidedAt],
|
|
582
|
+
[T.method, r.consent.method],
|
|
583
|
+
[T.ttl, r.consent.ttlDays == null ? null : r.consent.ttlDays + T.days]
|
|
584
|
+
]));
|
|
585
|
+
body.appendChild(s2);
|
|
586
|
+
|
|
587
|
+
// 3. Blocked until consent
|
|
588
|
+
var s3 = section(T.secBlocked + ' (' + r.blocked.length + ')');
|
|
589
|
+
if (!r.blocked.length) {
|
|
590
|
+
s3.appendChild(el('div', { class: 'mut' }, T.noBlocked));
|
|
591
|
+
} else {
|
|
592
|
+
var u3 = doc.createElement('ul');
|
|
593
|
+
r.blocked.forEach(function (b) {
|
|
594
|
+
var li = doc.createElement('li');
|
|
595
|
+
li.appendChild(tag(b.kind));
|
|
596
|
+
li.appendChild(tag(b.category || '?'));
|
|
597
|
+
li.appendChild(doc.createTextNode(b.host + b.path +
|
|
598
|
+
(b.origin === 'markup' ? T.markup : '')));
|
|
599
|
+
u3.appendChild(li);
|
|
600
|
+
});
|
|
601
|
+
s3.appendChild(u3);
|
|
602
|
+
}
|
|
603
|
+
body.appendChild(s3);
|
|
604
|
+
|
|
605
|
+
// 4. Tracker requests
|
|
606
|
+
var s4 = section(T.secRequests + ' (' + r.requests.length + ')');
|
|
607
|
+
if (!r.requests.length) {
|
|
608
|
+
s4.appendChild(el('div', { class: 'mut' }, T.noRequests));
|
|
609
|
+
} else {
|
|
610
|
+
var u4 = doc.createElement('ul');
|
|
611
|
+
r.requests.forEach(function (q) {
|
|
612
|
+
var li = doc.createElement('li');
|
|
613
|
+
li.appendChild(tag(q.when === 'after' ? T.after : T.before,
|
|
614
|
+
q.when === 'after' ? 'on' : 'off'));
|
|
615
|
+
li.appendChild(tag(q.category));
|
|
616
|
+
li.appendChild(doc.createTextNode(q.host + q.path + ' · ' + q.at + T.ms +
|
|
617
|
+
(q.count > 1 ? ' ×' + q.count : '')));
|
|
618
|
+
u4.appendChild(li);
|
|
619
|
+
});
|
|
620
|
+
s4.appendChild(u4);
|
|
621
|
+
}
|
|
622
|
+
s4.appendChild(el('p', { class: 'note' }, T.note));
|
|
623
|
+
body.appendChild(s4);
|
|
624
|
+
|
|
625
|
+
// 5. Consent Mode
|
|
626
|
+
var s5 = section(T.secConsentMode + ' (' + r.consentMode.length + ')');
|
|
627
|
+
if (!r.consentMode.length) {
|
|
628
|
+
s5.appendChild(el('div', { class: 'mut' }, T.noEvents));
|
|
629
|
+
} else {
|
|
630
|
+
var u5 = doc.createElement('ul');
|
|
631
|
+
r.consentMode.slice().reverse().forEach(function (c) {
|
|
632
|
+
var li = doc.createElement('li');
|
|
633
|
+
li.appendChild(tag(c.at + T.ms));
|
|
634
|
+
var txt = c.type;
|
|
635
|
+
if (c.signals) {
|
|
636
|
+
var bits = [];
|
|
637
|
+
for (var k in c.signals) {
|
|
638
|
+
if (Object.prototype.hasOwnProperty.call(c.signals, k)) {
|
|
639
|
+
bits.push(k + '=' + c.signals[k]);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (bits.length) { txt += ': ' + bits.join(', '); }
|
|
643
|
+
}
|
|
644
|
+
li.appendChild(doc.createTextNode(txt));
|
|
645
|
+
u5.appendChild(li);
|
|
646
|
+
});
|
|
647
|
+
s5.appendChild(u5);
|
|
648
|
+
}
|
|
649
|
+
body.appendChild(s5);
|
|
650
|
+
|
|
651
|
+
// 6. Buttons
|
|
652
|
+
var s6 = section(T.secActions);
|
|
653
|
+
var row = el('div', { class: 'row' });
|
|
654
|
+
var bReset = el('button', { type: 'button' }, T.reset);
|
|
655
|
+
bReset.addEventListener('click', resetConsent);
|
|
656
|
+
var bShow = el('button', { type: 'button' }, T.showPrefs);
|
|
657
|
+
bShow.addEventListener('click', showBanner);
|
|
658
|
+
var bCopy = el('button', { type: 'button' }, T.copy);
|
|
659
|
+
bCopy.addEventListener('click', function () { copyReport(bCopy); });
|
|
660
|
+
row.appendChild(bReset); row.appendChild(bShow); row.appendChild(bCopy);
|
|
661
|
+
s6.appendChild(row);
|
|
662
|
+
s6.appendChild(el('p', { class: 'note' }, T.footer));
|
|
663
|
+
body.appendChild(s6);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function schedule() {
|
|
667
|
+
if (frame) { return; }
|
|
668
|
+
frame = 1;
|
|
669
|
+
var run = function () { frame = 0; try { render(); } catch (e) { /* noop */ } };
|
|
670
|
+
try {
|
|
671
|
+
if (typeof global.requestAnimationFrame === 'function') { global.requestAnimationFrame(run); }
|
|
672
|
+
else { global.setTimeout(run, 50); }
|
|
673
|
+
} catch (e) { run(); }
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ---------------------------------------------------------------------------
|
|
677
|
+
// Buttons
|
|
678
|
+
// ---------------------------------------------------------------------------
|
|
679
|
+
function resetConsent() {
|
|
680
|
+
try { if (CK && CK.withdraw) { CK.withdraw(); } } catch (e) { /* noop */ }
|
|
681
|
+
// withdraw() already clears the record; belt and braces for a page whose
|
|
682
|
+
// cookie was written on a parent domain.
|
|
683
|
+
try {
|
|
684
|
+
var h = global.location && global.location.hostname ? global.location.hostname : '';
|
|
685
|
+
var variants = ['', h ? '; domain=' + h : '', h ? '; domain=.' + h : ''];
|
|
686
|
+
var labels = h ? h.split('.') : [];
|
|
687
|
+
if (labels.length > 2) { variants.push('; domain=.' + labels.slice(-2).join('.')); }
|
|
688
|
+
for (var i = 0; i < variants.length; i++) {
|
|
689
|
+
doc.cookie = 'ck_consent=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/' + variants[i];
|
|
690
|
+
}
|
|
691
|
+
} catch (e) { /* noop */ }
|
|
692
|
+
lsDel('ck_consent');
|
|
693
|
+
try { global.location.reload(); } catch (e) { /* noop */ }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function showBanner() {
|
|
697
|
+
// Non-destructive on purpose: this opens the preferences panel and leaves
|
|
698
|
+
// the stored decision alone. Erasing consent is the other button's job, and
|
|
699
|
+
// on a live site a "show banner" click must never wipe the owner's record.
|
|
700
|
+
try { if (CK && CK.show) { CK.show(); } } catch (e) { /* noop */ }
|
|
701
|
+
schedule();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function copyReport(btn) {
|
|
705
|
+
var text = '';
|
|
706
|
+
try { text = JSON.stringify(buildReport(reportInput()), null, 2); } catch (e) { text = '{}'; }
|
|
707
|
+
var done = function (ok) {
|
|
708
|
+
try {
|
|
709
|
+
btn.textContent = ok ? T.copied : T.copyFailed;
|
|
710
|
+
global.setTimeout(function () { btn.textContent = T.copy; }, 1500);
|
|
711
|
+
} catch (e2) { /* noop */ }
|
|
712
|
+
};
|
|
713
|
+
try {
|
|
714
|
+
if (global.navigator && global.navigator.clipboard && global.navigator.clipboard.writeText) {
|
|
715
|
+
global.navigator.clipboard.writeText(text).then(function () { done(true); },
|
|
716
|
+
function () { done(fallbackCopy(text)); });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
} catch (e) { /* noop */ }
|
|
720
|
+
done(fallbackCopy(text));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function fallbackCopy(text) {
|
|
724
|
+
try {
|
|
725
|
+
var ta = doc.createElement('textarea');
|
|
726
|
+
ta.value = text;
|
|
727
|
+
ta.setAttribute('style', 'position:fixed;top:-9999px;left:-9999px');
|
|
728
|
+
doc.body.appendChild(ta);
|
|
729
|
+
ta.select();
|
|
730
|
+
var ok = doc.execCommand ? doc.execCommand('copy') : false;
|
|
731
|
+
doc.body.removeChild(ta);
|
|
732
|
+
return !!ok;
|
|
733
|
+
} catch (e) { return false; }
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// ---------------------------------------------------------------------------
|
|
737
|
+
// Mount
|
|
738
|
+
// ---------------------------------------------------------------------------
|
|
739
|
+
// The banner's bar layout sits at bottom:16 across the full width and the UI's
|
|
740
|
+
// floating button at bottom-left; lift the panel above whatever is at the
|
|
741
|
+
// bottom right now so its buttons stay clickable.
|
|
742
|
+
function avoidBanner() {
|
|
743
|
+
if (!host) { return; }
|
|
744
|
+
var bottom = 12;
|
|
745
|
+
try {
|
|
746
|
+
var ckRoot = doc.getElementById('ck-root');
|
|
747
|
+
var sr = ckRoot && ckRoot.shadowRoot;
|
|
748
|
+
// The bar spans the width at bottom:16; the box-right card sits exactly
|
|
749
|
+
// where this panel does. Both must be cleared (the UI's floating button
|
|
750
|
+
// is bottom-left and never collides).
|
|
751
|
+
var banner = sr && sr.querySelector(
|
|
752
|
+
'.ck-banner--bar.ck-pos-bottom, .ck-banner--box.ck-pos-bottom-right');
|
|
753
|
+
if (banner && banner.getBoundingClientRect) {
|
|
754
|
+
var r = banner.getBoundingClientRect();
|
|
755
|
+
if (r.height > 0 && r.bottom > (global.innerHeight || 0) - r.height - 40) {
|
|
756
|
+
bottom = Math.round(r.height) + 24;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
} catch (e) { /* noop */ }
|
|
760
|
+
try { host.style.bottom = bottom + 'px'; } catch (e2) { /* noop */ }
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function mount() {
|
|
764
|
+
if (host || !doc.body) { return; }
|
|
765
|
+
// A page can carry the loader (inline block) AND a manual <script> tag for
|
|
766
|
+
// the panel: the second copy must not mount a second panel.
|
|
767
|
+
if (doc.querySelector('ck-debug')) { return; }
|
|
768
|
+
refreshLang();
|
|
769
|
+
host = doc.createElement('ck-debug');
|
|
770
|
+
host.setAttribute('aria-live', 'off');
|
|
771
|
+
root = host.attachShadow ? host.attachShadow({ mode: 'open' }) : null;
|
|
772
|
+
if (!root) { host = null; return; }
|
|
773
|
+
|
|
774
|
+
var style = doc.createElement('style');
|
|
775
|
+
style.textContent = CSS;
|
|
776
|
+
root.appendChild(style);
|
|
777
|
+
|
|
778
|
+
var wrap = el('div', { class: 'w', role: 'region', 'aria-label': T.regionLabel });
|
|
779
|
+
var head = el('div', { class: 'hd' });
|
|
780
|
+
head.appendChild(el('b', null, 'ConsentKit debug'));
|
|
781
|
+
head.appendChild(el('span', { class: 'sp' }));
|
|
782
|
+
|
|
783
|
+
toggleBtn = el('button', { type: 'button', 'aria-expanded': 'true' }, T.collapse);
|
|
784
|
+
toggleBtn.addEventListener('click', function () {
|
|
785
|
+
collapsed = !collapsed;
|
|
786
|
+
body.hidden = collapsed;
|
|
787
|
+
toggleBtn.textContent = collapsed ? T.expand : T.collapse;
|
|
788
|
+
toggleBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
789
|
+
if (!collapsed) { schedule(); }
|
|
790
|
+
});
|
|
791
|
+
head.appendChild(toggleBtn);
|
|
792
|
+
|
|
793
|
+
var closeBtn = el('button', { type: 'button', 'aria-label': T.closeLabel }, '×');
|
|
794
|
+
closeBtn.addEventListener('click', function () {
|
|
795
|
+
lsDel(LS_KEY);
|
|
796
|
+
try { host.parentNode.removeChild(host); } catch (e) { /* noop */ }
|
|
797
|
+
host = null;
|
|
798
|
+
API.active = false;
|
|
799
|
+
});
|
|
800
|
+
head.appendChild(closeBtn);
|
|
801
|
+
|
|
802
|
+
wrap.appendChild(head);
|
|
803
|
+
body = el('div', { class: 'bd' });
|
|
804
|
+
wrap.appendChild(body);
|
|
805
|
+
root.appendChild(wrap);
|
|
806
|
+
doc.body.appendChild(host);
|
|
807
|
+
|
|
808
|
+
render();
|
|
809
|
+
avoidBanner();
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function boot() {
|
|
813
|
+
seedConsentTime();
|
|
814
|
+
stampOnDecision();
|
|
815
|
+
collectResources();
|
|
816
|
+
watchDataLayer();
|
|
817
|
+
mount();
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
doc.addEventListener('ck:change', function () {
|
|
821
|
+
// Fallback for a decision made through some other path than
|
|
822
|
+
// accept()/rejectAll() (a host page calling into the core directly).
|
|
823
|
+
// stampOnDecision() has usually set this already, and its stamp is the
|
|
824
|
+
// accurate one — do not overwrite it here: entry.startTime is on the
|
|
825
|
+
// performance clock, and by now the revived scripts have already run.
|
|
826
|
+
try {
|
|
827
|
+
var st = CK && CK.getState ? CK.getState() : null;
|
|
828
|
+
if (st && st.decided) { if (consentAtMs === null) { consentAtMs = nowMs(); } }
|
|
829
|
+
else { consentAtMs = null; }
|
|
830
|
+
} catch (e2) { /* noop */ }
|
|
831
|
+
schedule();
|
|
832
|
+
global.setTimeout(avoidBanner, 60);
|
|
833
|
+
}, false);
|
|
834
|
+
doc.addEventListener('ck:init', function () { schedule(); global.setTimeout(avoidBanner, 60); }, false);
|
|
835
|
+
} catch (e) { /* noop */ }
|
|
836
|
+
|
|
837
|
+
// Late trackers and revived scripts keep arriving after the first render.
|
|
838
|
+
try { global.setInterval(schedule, 3000); } catch (e) { /* noop */ }
|
|
839
|
+
try { global.addEventListener('resize', avoidBanner, false); } catch (e) { /* noop */ }
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
if (doc.readyState === 'loading') {
|
|
844
|
+
doc.addEventListener('DOMContentLoaded', function () { try { boot(); } catch (e) { /* noop */ } }, false);
|
|
845
|
+
} else { boot(); }
|
|
846
|
+
} catch (e) { /* noop */ }
|
|
847
|
+
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this));
|