@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.
- package/npm/internal-stub.mjs +1 -1
- package/package.json +1 -1
- package/src/ck-core.js +50 -2
- package/src/ck-debug.js +22 -5
- package/src/ck-ui.js +124 -1
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.5.
|
|
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
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.
|
|
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: '
|
|
266
|
-
themeFontInherit: '
|
|
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
|
|
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
|