@ecomconsult/consentkit 0.3.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -20
- package/npm/core.mjs +4 -1
- package/npm/index.cjs +4 -1
- package/npm/index.d.ts +228 -2
- package/npm/index.mjs +14 -2
- package/npm/internal-stub.mjs +12 -1
- package/package.json +1 -1
- package/src/ck-core.js +524 -38
- package/src/ck-debug.js +137 -3
- package/src/ck-saas.js +36 -1
- package/src/ck-ui-branding.js +511 -0
- package/src/ck-ui.js +530 -457
package/src/ck-debug.js
CHANGED
|
@@ -137,7 +137,16 @@
|
|
|
137
137
|
ttlDays: d.ttlDays == null ? null : Number(d.ttlDays)
|
|
138
138
|
},
|
|
139
139
|
blocked: (d.blocked || []).map(function (b) {
|
|
140
|
-
|
|
140
|
+
// `strict` marks an entry the engine held back only because strict mode
|
|
141
|
+
// is on and the host is an unknown third party — not because the
|
|
142
|
+
// tracker database recognised it.
|
|
143
|
+
return {
|
|
144
|
+
host: b.host, path: b.path, kind: b.kind,
|
|
145
|
+
category: b.category, origin: b.origin, strict: b.strict === true,
|
|
146
|
+
// false when the visitor granted the category but the element never
|
|
147
|
+
// loaded — usually a script that was created and never appended.
|
|
148
|
+
revived: b.revived !== false
|
|
149
|
+
};
|
|
141
150
|
}),
|
|
142
151
|
requests: buildRequests(d.entries, d.consentAtMs, d.classify),
|
|
143
152
|
consentMode: (d.consentMode || []).slice(),
|
|
@@ -180,6 +189,8 @@
|
|
|
180
189
|
secBlocked: 'Заблокировано до согласия',
|
|
181
190
|
noBlocked: 'ничего не перехвачено',
|
|
182
191
|
markup: ' (разметка)',
|
|
192
|
+
strict: 'strict',
|
|
193
|
+
notRevived: ' — не ожил после согласия',
|
|
183
194
|
secRequests: 'Запросы к трекерам',
|
|
184
195
|
noRequests: 'запросов к известным трекерам не было',
|
|
185
196
|
after: 'после согласия',
|
|
@@ -188,6 +199,28 @@
|
|
|
188
199
|
note: NOTE_RU,
|
|
189
200
|
secConsentMode: 'Consent Mode / dataLayer',
|
|
190
201
|
noEvents: 'событий не было',
|
|
202
|
+
secTheme: 'Оформление',
|
|
203
|
+
themeMode: 'тема',
|
|
204
|
+
themeModeLight: 'светлая',
|
|
205
|
+
themeModeDark: 'тёмная',
|
|
206
|
+
themeFont: 'шрифт',
|
|
207
|
+
themeFontInherit: 'как на сайте',
|
|
208
|
+
themeFontSystem: 'системный',
|
|
209
|
+
themeRadius: 'скругления',
|
|
210
|
+
themeCard: 'карточка',
|
|
211
|
+
themeBtn: 'кнопки',
|
|
212
|
+
themeLink: 'Ссылки',
|
|
213
|
+
btnAccept: 'Принять всё',
|
|
214
|
+
btnReject: 'Отклонить всё',
|
|
215
|
+
btnSettings: 'Настроить',
|
|
216
|
+
btnFilled: 'залитая',
|
|
217
|
+
btnOutline: 'обводка',
|
|
218
|
+
btnText: 'текст',
|
|
219
|
+
btnBorder: 'обводка',
|
|
220
|
+
btnOn: 'на',
|
|
221
|
+
btnAdjusted: 'исправлено автоматически',
|
|
222
|
+
btnOk: 'AA',
|
|
223
|
+
btnFail: 'ниже AA',
|
|
191
224
|
secActions: 'Действия',
|
|
192
225
|
reset: 'Сбросить согласие',
|
|
193
226
|
showPrefs: 'Показать настройки',
|
|
@@ -215,6 +248,8 @@
|
|
|
215
248
|
secBlocked: 'Blocked until consent',
|
|
216
249
|
noBlocked: 'nothing intercepted',
|
|
217
250
|
markup: ' (markup)',
|
|
251
|
+
strict: 'strict',
|
|
252
|
+
notRevived: ' — did not come back after consent',
|
|
218
253
|
secRequests: 'Tracker requests',
|
|
219
254
|
noRequests: 'no requests to known trackers',
|
|
220
255
|
after: 'after consent',
|
|
@@ -223,6 +258,28 @@
|
|
|
223
258
|
note: NOTE_EN,
|
|
224
259
|
secConsentMode: 'Consent Mode / dataLayer',
|
|
225
260
|
noEvents: 'no events',
|
|
261
|
+
secTheme: 'Appearance',
|
|
262
|
+
themeMode: 'theme',
|
|
263
|
+
themeModeLight: 'light',
|
|
264
|
+
themeModeDark: 'dark',
|
|
265
|
+
themeFont: 'font',
|
|
266
|
+
themeFontInherit: 'site font',
|
|
267
|
+
themeFontSystem: 'system',
|
|
268
|
+
themeRadius: 'radii',
|
|
269
|
+
themeCard: 'card',
|
|
270
|
+
themeBtn: 'buttons',
|
|
271
|
+
themeLink: 'Links',
|
|
272
|
+
btnAccept: 'Accept all',
|
|
273
|
+
btnReject: 'Reject all',
|
|
274
|
+
btnSettings: 'Customize',
|
|
275
|
+
btnFilled: 'filled',
|
|
276
|
+
btnOutline: 'outline',
|
|
277
|
+
btnText: 'text',
|
|
278
|
+
btnBorder: 'border',
|
|
279
|
+
btnOn: 'on',
|
|
280
|
+
btnAdjusted: 'adjusted automatically',
|
|
281
|
+
btnOk: 'AA',
|
|
282
|
+
btnFail: 'below AA',
|
|
226
283
|
secActions: 'Actions',
|
|
227
284
|
reset: 'Reset consent',
|
|
228
285
|
showPrefs: 'Show preferences',
|
|
@@ -534,6 +591,13 @@
|
|
|
534
591
|
|
|
535
592
|
function tag(text, cls) { return el('span', { class: 't' + (cls ? ' ' + cls : '') }, text); }
|
|
536
593
|
|
|
594
|
+
// A contrast ratio the way WCAG tools quote it. null when the colour could
|
|
595
|
+
// not be measured (a colour name, an rgb() string) — shown as "?" rather
|
|
596
|
+
// than a made-up number.
|
|
597
|
+
function fmtRatio(r) {
|
|
598
|
+
return typeof r === 'number' && isFinite(r) ? (Math.round(r * 100) / 100) + ':1' : '?';
|
|
599
|
+
}
|
|
600
|
+
|
|
537
601
|
// Resolved lazily, not at parse time: this file runs before ConsentKit.init()
|
|
538
602
|
// has merged the site's config, so asking for the language now would always
|
|
539
603
|
// read the built-in default. Re-resolved on every render so a page that
|
|
@@ -594,8 +658,10 @@
|
|
|
594
658
|
var li = doc.createElement('li');
|
|
595
659
|
li.appendChild(tag(b.kind));
|
|
596
660
|
li.appendChild(tag(b.category || '?'));
|
|
661
|
+
if (b.strict) { li.appendChild(tag(T.strict, 'off')); }
|
|
597
662
|
li.appendChild(doc.createTextNode(b.host + b.path +
|
|
598
|
-
(b.origin === 'markup' ? T.markup : '')
|
|
663
|
+
(b.origin === 'markup' ? T.markup : '') +
|
|
664
|
+
(b.revived === false ? T.notRevived : '')));
|
|
599
665
|
u3.appendChild(li);
|
|
600
666
|
});
|
|
601
667
|
s3.appendChild(u3);
|
|
@@ -648,7 +714,75 @@
|
|
|
648
714
|
}
|
|
649
715
|
body.appendChild(s5);
|
|
650
716
|
|
|
651
|
-
// 6.
|
|
717
|
+
// 6. Appearance (SPEC V1.6 §1) — the three buttons' RESOLVED colours and
|
|
718
|
+
// their contrast ratios, read straight off ConsentKit._contrast rather than
|
|
719
|
+
// recomputed here: the banner, this panel and the cabinet's theme editor
|
|
720
|
+
// must all quote the same numbers. The mode is named explicitly because
|
|
721
|
+
// light and dark resolve differently and only one of them is on screen.
|
|
722
|
+
var contrast = CK && CK._contrast;
|
|
723
|
+
if (contrast && typeof contrast.buildThemeCss === 'function') {
|
|
724
|
+
var sT = section(T.secTheme);
|
|
725
|
+
try {
|
|
726
|
+
var built = contrast.buildThemeCss((CK && CK.config) || {});
|
|
727
|
+
// Which palette the visitor is actually looking at right now.
|
|
728
|
+
var dark = built.mode === 'dark';
|
|
729
|
+
if (built.mode === 'auto') {
|
|
730
|
+
try {
|
|
731
|
+
dark = !!(global.matchMedia && global.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
732
|
+
} catch (e) { dark = false; }
|
|
733
|
+
}
|
|
734
|
+
var resolved = dark ? built.dark : built.light;
|
|
735
|
+
|
|
736
|
+
// Fourth row: --ck-link, the accent as TEXT on the card. It is a
|
|
737
|
+
// separate number from every button's, because it is measured against
|
|
738
|
+
// the card rather than against a button fill — a brand accent can pass
|
|
739
|
+
// as a filled button's background and still be unreadable as a link.
|
|
740
|
+
var lk = resolved.link;
|
|
741
|
+
var lkTxt = '—';
|
|
742
|
+
if (lk) {
|
|
743
|
+
lkTxt = lk.color + ' ' + T.btnOn + ' ' + lk.against +
|
|
744
|
+
' · ' + fmtRatio(lk.ratio) +
|
|
745
|
+
' ' + ((typeof lk.ratio === 'number' && lk.ratio >= 4.5) ? T.btnOk : T.btnFail) +
|
|
746
|
+
(lk.adjusted ? ' · ' + T.btnAdjusted : '');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
sT.appendChild(defs([
|
|
750
|
+
[T.themeMode, (dark ? T.themeModeDark : T.themeModeLight) +
|
|
751
|
+
(built.mode === 'auto' ? ' (auto)' : '')],
|
|
752
|
+
[T.themeFont, built.font === 'inherit' ? T.themeFontInherit : T.themeFontSystem],
|
|
753
|
+
[T.themeRadius, T.themeCard + ' ' + built.radius.card + 'px · ' +
|
|
754
|
+
T.themeBtn + ' ' + built.radius.button + 'px'],
|
|
755
|
+
[T.themeLink, lkTxt]
|
|
756
|
+
]));
|
|
757
|
+
|
|
758
|
+
var labels = { accept: T.btnAccept, reject: T.btnReject, settings: T.btnSettings };
|
|
759
|
+
var uT = doc.createElement('ul');
|
|
760
|
+
['accept', 'reject', 'settings'].forEach(function (role) {
|
|
761
|
+
var b = resolved.buttons[role];
|
|
762
|
+
if (!b) { return; }
|
|
763
|
+
var li = doc.createElement('li');
|
|
764
|
+
li.appendChild(tag(b.variant === 'filled' ? T.btnFilled : T.btnOutline));
|
|
765
|
+
var ok = typeof b.ratio === 'number' && b.ratio >= 4.5;
|
|
766
|
+
li.appendChild(tag(fmtRatio(b.ratio) + ' ' + (ok ? T.btnOk : T.btnFail),
|
|
767
|
+
ok ? 'on' : 'off'));
|
|
768
|
+
li.appendChild(doc.createTextNode(labels[role] + ' — ' +
|
|
769
|
+
T.btnText + ' ' + b.fg + ' ' + T.btnOn + ' ' + b.against +
|
|
770
|
+
(b.variant === 'outline'
|
|
771
|
+
? ' · ' + T.btnBorder + ' ' + b.border + ' (' + fmtRatio(b.borderRatio) + ')'
|
|
772
|
+
: '')));
|
|
773
|
+
if (b.adjusted) {
|
|
774
|
+
li.appendChild(el('span', { class: 'mut' }, ' · ' + T.btnAdjusted));
|
|
775
|
+
}
|
|
776
|
+
uT.appendChild(li);
|
|
777
|
+
});
|
|
778
|
+
sT.appendChild(uT);
|
|
779
|
+
} catch (e) {
|
|
780
|
+
sT.appendChild(el('div', { class: 'mut' }, '—'));
|
|
781
|
+
}
|
|
782
|
+
body.appendChild(sT);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// 7. Buttons
|
|
652
786
|
var s6 = section(T.secActions);
|
|
653
787
|
var row = el('div', { class: 'row' });
|
|
654
788
|
var bReset = el('button', { type: 'button' }, T.reset);
|
package/src/ck-saas.js
CHANGED
|
@@ -122,15 +122,43 @@
|
|
|
122
122
|
// ---------------------------------------------------------------------------
|
|
123
123
|
// Boot
|
|
124
124
|
// ---------------------------------------------------------------------------
|
|
125
|
+
// The service's tracker overrides (§1.3). Applied through the core's
|
|
126
|
+
// _extendHostDb so the classification the scanner used server-side is the
|
|
127
|
+
// classification the browser uses on the same page.
|
|
128
|
+
//
|
|
129
|
+
// ORDER MATTERS: this must run BEFORE CK.init(), because init() performs the
|
|
130
|
+
// initial scan and the first applyConsentToDom() pass. A host merged after
|
|
131
|
+
// that pass would not be recognised on the scripts already in the markup.
|
|
132
|
+
function applyHostDb(config) {
|
|
133
|
+
try {
|
|
134
|
+
var map = config && config.hostdb;
|
|
135
|
+
if (!map || typeof map !== 'object') { return 0; }
|
|
136
|
+
if (typeof CK._extendHostDb !== 'function') {
|
|
137
|
+
// An older core on the page (config contract is newer than the client).
|
|
138
|
+
warn('this core does not support config.hostdb — update ck-core.js to 0.4.0 or later.');
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
return CK._extendHostDb(map);
|
|
142
|
+
} catch (e) { return 0; }
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
function initWith(config, why) {
|
|
126
146
|
activeConfig = config;
|
|
147
|
+
applyHostDb(config);
|
|
127
148
|
try { CK.init(config); } catch (e) { error('init() failed: ' + (e && e.message)); }
|
|
128
149
|
if (why) { /* reserved for diagnostics */ }
|
|
129
150
|
}
|
|
130
151
|
|
|
131
|
-
// Strict
|
|
152
|
+
// Strict FALLBACK: banner shows, every opt-in category stays off, no journal.
|
|
132
153
|
// policyVersion 'strict-fallback' deliberately mismatches any stored consent,
|
|
133
154
|
// so a previous decision is not silently reused when the server is unreachable.
|
|
155
|
+
//
|
|
156
|
+
// Not to be confused with `blocking.mode: 'strict'` (SPEC §2), which is a
|
|
157
|
+
// different thing that happens to share the word: this path deliberately does
|
|
158
|
+
// NOT set it. When the config server is unreachable we know nothing about the
|
|
159
|
+
// site, and turning on the mode that blocks every third-party script would
|
|
160
|
+
// break a page precisely when its owner has no way to configure an allowlist.
|
|
161
|
+
// Known trackers are still blocked, as always.
|
|
134
162
|
function initStrict(reason) {
|
|
135
163
|
warn('config unavailable (' + reason + ') — strict fallback: banner shown, all opt-in categories denied, journal disabled.');
|
|
136
164
|
initWith({ policyVersion: 'strict-fallback' });
|
|
@@ -194,6 +222,13 @@
|
|
|
194
222
|
// re-initialising would swap ConsentKit.config identity mid-session.
|
|
195
223
|
// It takes effect on the next page load.
|
|
196
224
|
writeCache(r.etag, r.config);
|
|
225
|
+
// `hostdb` is the deliberate exception. Extending the map has exactly the
|
|
226
|
+
// semantics _extendHostDb documents for a call after init(): nothing
|
|
227
|
+
// already inserted is re-evaluated, but everything inserted from now on
|
|
228
|
+
// is classified against the new overrides. Waiting a whole page load to
|
|
229
|
+
// start blocking a newly classified tracker would be worse, and unlike
|
|
230
|
+
// the rest of the config this cannot change ConsentKit.config identity.
|
|
231
|
+
applyHostDb(r.config);
|
|
197
232
|
}, function (reason) {
|
|
198
233
|
warn('background revalidation failed (' + reason + '); continuing with cached config.');
|
|
199
234
|
});
|