@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/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
- return { host: b.host, path: b.path, kind: b.kind, category: b.category, origin: b.origin };
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. Buttons
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 mode: banner shows, every opt-in category stays off, no journal.
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
  });