@ecomconsult/consentkit 0.5.0 → 0.5.2

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.
@@ -28,7 +28,7 @@ export function undecidedState() {
28
28
  */
29
29
  export function createStub() {
30
30
  const stub = {
31
- version: '0.5.0',
31
+ version: '0.5.2',
32
32
  config: {},
33
33
  init: function () { return undecidedState(); },
34
34
  allowed: function (cat) { return cat === 'necessary'; },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecomconsult/consentkit",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "GDPR cookie consent core with blocking engine, Shadow DOM UI and Google Consent Mode v2. Zero dependencies, no build step.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/ck-core.js CHANGED
@@ -123,7 +123,27 @@
123
123
  'jivosite.com': 'functional',
124
124
  'jivo.chat': 'functional',
125
125
  'tidio.co': 'functional',
126
- 'tidiochat.com': 'functional'
126
+ 'tidiochat.com': 'functional',
127
+ // Searchanise — site search for e-commerce. The widget IS the search box,
128
+ // so a shop whose visitor declines functional loses the feature and nothing
129
+ // else; it is not measurement. The numbered API hosts are enumerated
130
+ // because matching here is plain suffix matching (see hostMatches) with no
131
+ // pattern form: searchserverapi1.com is not a subdomain of anything.
132
+ 'searchanise.com': 'functional',
133
+ 'searchserverapi.com': 'functional',
134
+ 'searchserverapi1.com': 'functional',
135
+ 'searchserverapi2.com': 'functional',
136
+ 'searchserverapi3.com': 'functional',
137
+ // iuteCredit — a Moldovan consumer-credit calculator embedded on shop
138
+ // product pages. A third party the owner chose, offering a feature.
139
+ 'iutecredit.md': 'functional',
140
+
141
+ // --- necessary -----------------------------------------------------
142
+ // recaptcha.net is Google's alternate reCAPTCHA domain, served for regions
143
+ // where google.com is unreachable. Unlike the two path-scoped Google hosts
144
+ // in PATH_DB, this domain hosts NOTHING but the captcha, so the whole host
145
+ // is the right scope.
146
+ 'recaptcha.net': 'necessary'
127
147
  };
128
148
 
129
149
  // Runtime overrides fed in by ConsentKit._extendHostDb(map) — the SaaS
@@ -149,8 +169,20 @@
149
169
  // tags never get the chance to see consent at all.
150
170
  // gtag.js is the opposite case — a direct GA4/Ads install with no container
151
171
  // in front of it — so it stays blocked by path.
172
+ //
173
+ // reCAPTCHA is the other reason this table exists. www.google.com and
174
+ // www.gstatic.com host it, and neither can be given a category as a WHOLE
175
+ // host — that would rule on every Google property at once. Path-scoping names
176
+ // the widget and nothing else. The category is 'necessary': a form gated
177
+ // behind a captcha is unusable without it, and allowed() lets 'necessary'
178
+ // through unconditionally, so this classifies the request for the report
179
+ // without ever blocking it. (Strict mode already passed these through via
180
+ // BASE_ALLOW_PATH; what this adds is a NAME, so the audit stops filing the
181
+ // captcha under «сторонние подключения без категории».)
152
182
  var PATH_DB = {
153
183
  '/gtag/js': 'analytics', // googletagmanager.com/gtag/js?id=G-XXXX
184
+ 'www.google.com/recaptcha/': 'necessary',
185
+ 'www.gstatic.com/recaptcha/': 'necessary',
154
186
  '/trackers/ga.js': 'analytics',
155
187
  '/trackers/pixel.js': 'marketing',
156
188
  '/trackers/chat.js': 'functional'
@@ -284,8 +316,24 @@
284
316
  'cdnjs.cloudflare.com',
285
317
  'code.jquery.com',
286
318
  'ajax.googleapis.com',
319
+ 'ajax.aspnetcdn.com', // Microsoft Ajax CDN (jQuery, MVC)
320
+ 'aspnetcdn.com',
321
+ 'kxcdn.com', // KeyCDN tenants, e.g. *-ef84.kxcdn.com
322
+ 'cloudfront.net', // AWS CloudFront distributions
323
+ 'akamaihd.net', // Akamai
324
+ 'fastly.net',
325
+ 'b-cdn.net', // bunny.net
287
326
  // --- fonts ----------------------------------------------------------
288
327
  'fonts.googleapis.com',
328
+ // gstatic.com whole: Google's static-asset domain. Its subdomains serve
329
+ // fonts (fonts.gstatic.com), the reCAPTCHA widget's own code
330
+ // (www.gstatic.com/recaptcha/) and ordinary images — assets a page is
331
+ // broken without, none of which measure a visitor. Nothing on it is a
332
+ // tracker: the Google properties that DO measure live on their own hosts
333
+ // (google-analytics.com, googletagmanager.com, doubleclick.net) and are
334
+ // classified there. fonts.gstatic.com is kept below it, redundant under
335
+ // suffix matching but named because §8 and the fixtures both refer to it.
336
+ 'gstatic.com',
289
337
  'fonts.gstatic.com',
290
338
  // --- captcha ---------------------------------------------------------
291
339
  // A page whose form is gated behind a captcha is unusable without it. The
@@ -1413,7 +1461,7 @@
1413
1461
  // Public API
1414
1462
  // ---------------------------------------------------------------------------
1415
1463
  var ConsentKit = {
1416
- version: '0.5.0',
1464
+ version: '0.5.2',
1417
1465
  config: config,
1418
1466
 
1419
1467
  init: function (userConfig) {
package/src/ck-debug.js CHANGED
@@ -203,9 +203,10 @@
203
203
  themeMode: 'тема',
204
204
  themeModeLight: 'светлая',
205
205
  themeModeDark: 'тёмная',
206
- themeFont: 'шрифт',
207
- themeFontInherit: 'как на сайте',
206
+ themeFont: 'Шрифт',
207
+ themeFontInherit: 'наследуется',
208
208
  themeFontSystem: 'системный',
209
+ themeFontPage: 'со страницы',
209
210
  themeRadius: 'скругления',
210
211
  themeCard: 'карточка',
211
212
  themeBtn: 'кнопки',
@@ -262,9 +263,10 @@
262
263
  themeMode: 'theme',
263
264
  themeModeLight: 'light',
264
265
  themeModeDark: 'dark',
265
- themeFont: 'font',
266
- themeFontInherit: 'site font',
266
+ themeFont: 'Font',
267
+ themeFontInherit: 'inherited',
267
268
  themeFontSystem: 'system',
269
+ themeFontPage: 'from the page',
268
270
  themeRadius: 'radii',
269
271
  themeCard: 'card',
270
272
  themeBtn: 'buttons',
@@ -598,6 +600,21 @@
598
600
  return typeof r === 'number' && isFinite(r) ? (Math.round(r * 100) / 100) + ':1' : '?';
599
601
  }
600
602
 
603
+ /* «Шрифт: <значение> (со страницы | системный | наследуется)».
604
+ theme.font:'system' is a fixed stack; otherwise the UI samples real page
605
+ text and only falls back to CSS `inherit` when the page states no family of
606
+ its own — the three cases have to be told apart, because «наследуется» on a
607
+ site whose body is unstyled is exactly the Times bug being diagnosed. */
608
+ function fontRow(built, T) {
609
+ if (built.font !== 'inherit') return built.font + ' (' + T.themeFontSystem + ')';
610
+ var page = null;
611
+ try {
612
+ if (CK && typeof CK._resolvePageFont === 'function') page = CK._resolvePageFont();
613
+ } catch (e) { page = null; }
614
+ return page ? page + ' (' + T.themeFontPage + ')'
615
+ : 'inherit (' + T.themeFontInherit + ')';
616
+ }
617
+
601
618
  // Resolved lazily, not at parse time: this file runs before ConsentKit.init()
602
619
  // has merged the site's config, so asking for the language now would always
603
620
  // read the built-in default. Re-resolved on every render so a page that
@@ -749,7 +766,7 @@
749
766
  sT.appendChild(defs([
750
767
  [T.themeMode, (dark ? T.themeModeDark : T.themeModeLight) +
751
768
  (built.mode === 'auto' ? ' (auto)' : '')],
752
- [T.themeFont, built.font === 'inherit' ? T.themeFontInherit : T.themeFontSystem],
769
+ [T.themeFont, fontRow(built, T)],
753
770
  [T.themeRadius, T.themeCard + ' ' + built.radius.card + 'px · ' +
754
771
  T.themeBtn + ' ' + built.radius.button + 'px'],
755
772
  [T.themeLink, lkTxt]
package/src/ck-ui.js CHANGED
@@ -663,6 +663,109 @@
663
663
  return (theme && theme.font === 'system') ? SYSTEM_FONT : 'inherit';
664
664
  }
665
665
 
666
+ /* CSS `inherit` on the shadow root takes the family of the HOST <div>, which
667
+ hangs off <body>. On a site that sets its typeface on inner blocks and not
668
+ on body/html, body's computed family IS the browser default (Times), so the
669
+ banner came out in Times on a page that is entirely sans-serif. Reading the
670
+ family off real page TEXT instead is what «как на сайте» was always meant
671
+ to mean.
672
+
673
+ Split in two: pickPageFont is pure arithmetic over already-collected
674
+ samples (unit-testable in node, published on _contrast), resolvePageFont
675
+ does the DOM reading. */
676
+
677
+ // Families are compared as CSS grammar, not as strings: '"Google Sans"' and
678
+ // 'google sans' name the same face, and only the UA default must be rejected.
679
+ function normFamily(v) {
680
+ if (typeof v !== 'string') return '';
681
+ return v.replace(/["']/g, '').replace(/\s*,\s*/g, ',').trim().toLowerCase();
682
+ }
683
+
684
+ /* First sample whose family differs from the UA default wins; null when the
685
+ page never states a family of its own (the caller then keeps `inherit`).
686
+ `samples` is [{ family }] in priority order. */
687
+ function pickPageFont(samples, uaDefault) {
688
+ if (!samples || typeof samples.length !== 'number') return null;
689
+ var ua = normFamily(uaDefault);
690
+ for (var i = 0; i < samples.length; i++) {
691
+ var s = samples[i];
692
+ var fam = s && typeof s.family === 'string' ? s.family.trim() : '';
693
+ if (!fam) continue; // unstyled / detached element
694
+ var norm = normFamily(fam);
695
+ if (!norm) continue;
696
+ if (ua && norm === ua) continue; // still the browser default
697
+ // The value lands in a generated stylesheet. getComputedStyle normalises,
698
+ // but a family that could close the declaration is never worth the risk.
699
+ if (/[;{}]|\/\*/.test(fam)) continue;
700
+ return fam;
701
+ }
702
+ return null;
703
+ }
704
+
705
+ // Ordered so the first hit is the most representative body text on the page.
706
+ var FONT_PROBES = ['main p', 'article p', '[role=main] p', 'p', 'h1', 'h2', 'a', 'button', 'li'];
707
+
708
+ function visibleOutsideHost(el) {
709
+ if (!el) return false;
710
+ if (host && (el === host || (host.contains && host.contains(el)))) return false;
711
+ try {
712
+ if (el.offsetParent !== null) return true;
713
+ return !!(el.getClientRects && el.getClientRects().length > 0);
714
+ } catch (e) { return false; }
715
+ }
716
+
717
+ // The UA default is whatever an element states no opinion about, measured
718
+ // INSIDE the shadow root so `:host{all:initial}` is already in force.
719
+ function uaDefaultFamily() {
720
+ if (!root || typeof document === 'undefined') return '';
721
+ var probe = null;
722
+ try {
723
+ probe = document.createElement('span');
724
+ probe.style.setProperty('font-family', 'initial');
725
+ root.appendChild(probe);
726
+ return window.getComputedStyle(probe).fontFamily || '';
727
+ } catch (e) {
728
+ return '';
729
+ } finally {
730
+ try { if (probe && probe.parentNode) probe.parentNode.removeChild(probe); } catch (e2) { /* noop */ }
731
+ }
732
+ }
733
+
734
+ // Cached at applyTheme time so the debug panel (which may open long before or
735
+ // after a remount) quotes the value the banner actually painted.
736
+ var pageFont = null;
737
+
738
+ function resolvePageFont() {
739
+ if (typeof document === 'undefined') return pageFont;
740
+ try {
741
+ var ua = uaDefaultFamily();
742
+ var samples = [];
743
+ var body = document.body;
744
+ if (body) {
745
+ try { samples.push({ family: window.getComputedStyle(body).fontFamily }); } catch (e) { /* noop */ }
746
+ }
747
+ for (var i = 0; i < FONT_PROBES.length; i++) {
748
+ // First VISIBLE match, not the first match if it happens to be visible:
749
+ // the leading <p> on a real page is often hidden a11y text, a template
750
+ // or an inert modal, and skipping straight to the next selector would
751
+ // throw away the page's body copy. Capped so a long article does not
752
+ // cost a layout read per node.
753
+ var el = null, list = null;
754
+ try { list = document.querySelectorAll(FONT_PROBES[i]); } catch (e) { list = null; }
755
+ if (!list) continue;
756
+ for (var j = 0; j < list.length && j < 5; j++) {
757
+ if (visibleOutsideHost(list[j])) { el = list[j]; break; }
758
+ }
759
+ if (!el) continue;
760
+ try { samples.push({ family: window.getComputedStyle(el).fontFamily }); } catch (e) { /* noop */ }
761
+ }
762
+ pageFont = pickPageFont(samples, ua);
763
+ } catch (e) {
764
+ pageFont = null;
765
+ }
766
+ return pageFont;
767
+ }
768
+
666
769
  /* --------------------------------------------------- button resolution */
667
770
 
668
771
  var BTN_ROLES = ['accept', 'reject', 'settings'];
@@ -919,9 +1022,21 @@
919
1022
  function applyTheme(cfg) {
920
1023
  if (!host || !nodes.themeStyle) return;
921
1024
  var built = buildThemeCss(cfg);
1025
+ // `inherit` would take the family of the host <div> — i.e. of <body>, which
1026
+ // on many sites still carries the browser default. Overwrite the token with
1027
+ // the family real page text is set in. Appended AFTER built.css so the two
1028
+ // equal-specificity :host rules resolve in source order; buildThemeCss stays
1029
+ // pure and DOM-free (the theme editor and the debug panel both call it).
1030
+ var fontCss = '';
1031
+ if (built.font === 'inherit') {
1032
+ var page = resolvePageFont();
1033
+ if (page) fontCss = '\n:host{--ck-font:' + page + '}';
1034
+ } else {
1035
+ pageFont = null; // theme.font:'system' opts out
1036
+ }
922
1037
  // Brand rules ride along in the same sheet: the dark/light logo swap depends
923
1038
  // on theme.mode, so it must be rebuilt whenever the palette is.
924
- nodes.themeStyle.textContent = built.css + '\n' + buildBrandCss(cfg); // replace, never append
1039
+ nodes.themeStyle.textContent = built.css + fontCss + '\n' + buildBrandCss(cfg); // replace, never append
925
1040
  host.classList.remove('ck-mode-dark', 'ck-mode-light');
926
1041
  if (built.mode === 'dark') host.classList.add('ck-mode-dark');
927
1042
  else if (built.mode === 'light') host.classList.add('ck-mode-light');
@@ -1437,9 +1552,17 @@
1437
1552
  resolveButtonStyles: resolveButtonStyles,
1438
1553
  resolveRadius: resolveRadius,
1439
1554
  resolveFont: resolveFont,
1555
+ pickPageFont: pickPageFont,
1440
1556
  resolveDetails: resolveDetails,
1441
1557
  buildThemeCss: buildThemeCss
1442
1558
  };
1559
+ // The page-font probe reads the DOM, so it is not part of the pure block —
1560
+ // but the debug panel must be able to quote the family the banner painted
1561
+ // rather than guess at it. Safe to call before mount: with no shadow root
1562
+ // to measure the UA default against it simply returns the cache (null).
1563
+ ck._resolvePageFont = function () {
1564
+ return (typeof document === 'undefined' || !root) ? pageFont : resolvePageFont();
1565
+ };
1443
1566
  })();
1444
1567
 
1445
1568
  // SSR-safe: with no DOM there is nothing to render or listen to, so importing