@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/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Four ways to add ConsentKit to a site, from simplest to most integrated.
|
|
|
44
44
|
|---|---|---|---|
|
|
45
45
|
| 1 | **Script tags** — copy `src/` to your server, three `<script>` tags in `<head>` | Any site you control | [Quickstart below](#quickstart--script-tags) |
|
|
46
46
|
| 2 | **npm** — `npm install @ecomconsult/consentkit` | Bundled apps, React | [Quickstart below](#quickstart--npm) |
|
|
47
|
-
| 3 | **WordPress plugin** — copy the plugin folder to `wp-content/plugins
|
|
47
|
+
| 3 | **WordPress plugin** — copy the plugin folder to `wp-content/plugins/`; rewrites static tracker tags server-side | WordPress / WooCommerce | [`plugins/wordpress/consentkit/`](plugins/wordpress/consentkit/) |
|
|
48
48
|
| 4 | **Google Tag Manager** — import the container, trigger tags on consent events | Sites already running GTM | [`integrations/gtm/README.md`](integrations/gtm/README.md) |
|
|
49
49
|
|
|
50
50
|
```sh
|
|
@@ -66,6 +66,7 @@ must come first and should not be deferred.
|
|
|
66
66
|
```html
|
|
67
67
|
<script src="/consentkit/src/ck-core.js"></script>
|
|
68
68
|
<script src="/consentkit/src/ck-locales.js"></script><!-- optional: extra languages -->
|
|
69
|
+
<script src="/consentkit/src/ck-ui-branding.js"></script><!-- optional: logo / attribution -->
|
|
69
70
|
<script src="/consentkit/src/ck-ui.js"></script>
|
|
70
71
|
<script>
|
|
71
72
|
ConsentKit.init({
|
|
@@ -178,16 +179,23 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
|
|
|
178
179
|
|---|---|---|---|
|
|
179
180
|
| `policyVersion` | `string \| number` | `"1"` | Bump to invalidate stored consent and re-show the banner |
|
|
180
181
|
| `language` | `string` | `"auto"` | `"auto"` reads `navigator.language`. Falls back `pt-BR` → `pt` → `en` |
|
|
181
|
-
| `layout.type` | `"bar" \| "modal" \| "box"` | `"bar"` | `box` is a
|
|
182
|
+
| `layout.type` | `"bar" \| "modal" \| "box"` | `"bar"` | `box` is a corner card, up to 540px wide |
|
|
182
183
|
| `layout.position` | `string` | per type | `bar`: `bottom` (default) / `top`. `box`: `bottom-left` (default) / `bottom-right`. `modal` is always centred. A position that does not belong to the chosen type falls back to that type's default; the type itself is unaffected |
|
|
183
184
|
| `theme.accent` | `string` | `"#2B50D8"` | Exposed as `--ck-accent` |
|
|
184
|
-
| `theme.
|
|
185
|
+
| `theme.font` | `"inherit" \| "system"` | `"inherit"` | v0.5.0. `inherit` takes the host page's font family; `system` restores the pre-0.5.0 system stack. Font *sizes* are fixed either way |
|
|
186
|
+
| `theme.radius` | `{ card, button }` | `{ card: 16, button: 8 }` | v0.5.0. px, clamped 0–32. A bare string or number is the pre-0.5.0 form and still sets the card radius |
|
|
187
|
+
| `theme.buttons` | `{ accept, reject, settings }` | see below | v0.5.0. Per-button appearance. **Contrast is enforced automatically** — see [Button appearance](#button-appearance) |
|
|
185
188
|
| `theme.mode` | `"auto" \| "light" \| "dark"` | `"auto"` | `auto` follows `prefers-color-scheme` |
|
|
186
|
-
| `theme.dark` | `{ bg, ink, accent }` | built-in | Overrides the dark palette |
|
|
189
|
+
| `theme.dark` | `{ bg, ink, accent, onAccent }` | built-in | Overrides the dark palette |
|
|
190
|
+
| `texts.policyUrl` | `string` | — | v0.5.0. Cookie policy address. `http(s)` only; anything else is ignored |
|
|
191
|
+
| `texts.detailsAction` | `"policy" \| "settings" \| "hide"` | see notes | v0.5.0. What «Learn more» does. Defaults to `policy` when `policyUrl` is set, `settings` when it is not. `policy` without a usable URL falls back to `settings` rather than rendering a dead link |
|
|
187
192
|
| `categories.*.enabled` | `boolean` | `true` | Per category: `functional`, `analytics`, `marketing`. Hides the toggle when `false` |
|
|
188
193
|
| `consentTtlDays` | `number` | `365` | Lifetime of the stored decision |
|
|
189
194
|
| `integrations.gcm` | `boolean` | `true` | Google Consent Mode v2 signals |
|
|
190
195
|
| `integrations.gtmDataLayer` | `boolean` | `true` | Push consent events to `window.dataLayer` |
|
|
196
|
+
| `blocking.mode` | `"known" \| "strict"` | `"known"` | `strict` also holds back unknown third-party scripts and iframes — see [Strict mode](#strict-mode) |
|
|
197
|
+
| `blocking.allow` | `string[]` | `[]` | Hosts strict mode must never intercept. Matched by suffix, so `partner.com` also covers `cdn.partner.com` |
|
|
198
|
+
| `hostdb` | `Record<string, Category>` | — | Extra `host: category` pairs merged into the tracker database, applied before the initial scan. SaaS mode fills this from the service; `ConsentKit._extendHostDb()` does the same at any later point |
|
|
191
199
|
| `cookieTable` | `CkCookieTableEntry[]` | `[]` | Declared cookies, listed per category in the panel |
|
|
192
200
|
|
|
193
201
|
`cookieTable` entries:
|
|
@@ -196,6 +204,98 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
|
|
|
196
204
|
{ name: '_ga', category: 'analytics', vendor: 'Google', purpose: 'Visit statistics', expiry: '2 years' }
|
|
197
205
|
```
|
|
198
206
|
|
|
207
|
+
### Button appearance
|
|
208
|
+
|
|
209
|
+
Each of the three banner buttons can be styled independently:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
theme: {
|
|
213
|
+
accent: '#2B50D8',
|
|
214
|
+
font: 'inherit',
|
|
215
|
+
radius: { card: 16, button: 8 },
|
|
216
|
+
buttons: {
|
|
217
|
+
accept: { variant: 'filled' }, // accent fill
|
|
218
|
+
reject: { variant: 'filled' }, // always matches accept
|
|
219
|
+
settings: { variant: 'outline', borderWidth: 1 } // accent border
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Each entry takes `variant` (`"filled"` or `"outline"`), and optionally `bg`,
|
|
225
|
+
`fg`, `border` and `borderWidth` (`1` or `2`). Omitted colours come from
|
|
226
|
+
`theme.accent`.
|
|
227
|
+
|
|
228
|
+
**Accept and reject are always equal.** They render at the same size and weight,
|
|
229
|
+
and they always share one variant — if the two disagree in the config,
|
|
230
|
+
`accept.variant` is used for both. A reject button that looks weaker than
|
|
231
|
+
accept is a dark pattern, and consent collected through one is not freely
|
|
232
|
+
given, so the config simply cannot express it. `settings` is independent and
|
|
233
|
+
may itself be filled.
|
|
234
|
+
|
|
235
|
+
**Contrast is enforced automatically and cannot be switched off.** A colour
|
|
236
|
+
combination that would be unreadable is corrected before it reaches the
|
|
237
|
+
stylesheet:
|
|
238
|
+
|
|
239
|
+
- button text keeps the colour you set only when it clears **4.5:1** against
|
|
240
|
+
the fill it sits on; otherwise it becomes white or `#161616`, whichever
|
|
241
|
+
contrasts more;
|
|
242
|
+
- an `outline` border is darkened (light card) or lightened (dark card) in
|
|
243
|
+
small steps until it clears **3:1** against the card, so it stays
|
|
244
|
+
recognisably your colour rather than jumping to black or white;
|
|
245
|
+
- that resolved border colour is then the outline button's text colour,
|
|
246
|
+
subject to the same 4.5:1 rule.
|
|
247
|
+
|
|
248
|
+
The card is `#ffffff` in light mode and `#1c1c1e` in dark. A colour the
|
|
249
|
+
arithmetic cannot read — a CSS colour name, an `rgb()` string — is left exactly
|
|
250
|
+
as you wrote it rather than being silently replaced.
|
|
251
|
+
|
|
252
|
+
The same arithmetic is exposed as pure functions on `ConsentKit._contrast`
|
|
253
|
+
(`relativeLuminance`, `contrastRatio`, `ensureContrast`, `stepToContrast`,
|
|
254
|
+
`resolveButtonStyles`, `resolveRadius`, `resolveFont`, `resolveDetails`,
|
|
255
|
+
`buildThemeCss`) so a theme editor can show the same numbers the banner paints
|
|
256
|
+
instead of reimplementing them. It is present whenever `src/ck-ui.js` is loaded,
|
|
257
|
+
and it is safe to call in Node — nothing in it touches the DOM. The debug
|
|
258
|
+
panel's **Appearance** section reads it directly and reports each button's
|
|
259
|
+
resolved colours, its contrast ratio, and whether the value was adjusted.
|
|
260
|
+
|
|
261
|
+
### The «Learn more» link
|
|
262
|
+
|
|
263
|
+
`texts.detailsAction` decides what the link at the end of the banner copy does:
|
|
264
|
+
|
|
265
|
+
| Value | Renders |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `"policy"` | A link to `texts.policyUrl`, opened with `target="_blank" rel="noopener"` |
|
|
268
|
+
| `"settings"` | A button that opens the preferences panel |
|
|
269
|
+
| `"hide"` | Nothing at all |
|
|
270
|
+
|
|
271
|
+
The default follows `policyUrl`: `policy` when one is set, `settings` when it
|
|
272
|
+
is not — so supplying only a URL does the obvious thing.
|
|
273
|
+
|
|
274
|
+
> Before 0.5.0 this control was rendered as `<a href="#">` with no handler at
|
|
275
|
+
> all: clicking it jumped to the top of the page and nothing else. Any site
|
|
276
|
+
> running 0.4.x or earlier has a dead «Learn more» link.
|
|
277
|
+
|
|
278
|
+
### Infrastructure
|
|
279
|
+
|
|
280
|
+
Some third-party hosts are not a consent decision at all: they are where a site
|
|
281
|
+
builder or hosting platform serves the site's **own** markup, styles and
|
|
282
|
+
scripts from. A Tilda page loads its layout from `tildacdn.com`, a Wix page
|
|
283
|
+
loads its from `parastorage.com`, and a page using Google Fonts loads its
|
|
284
|
+
typefaces from `fonts.gstatic.com`. ConsentKit ships these as a separate class
|
|
285
|
+
of host — readable as `ConsentKit._infra()`, tested per URL or hostname with
|
|
286
|
+
`ConsentKit._isInfra(url)` — covering the CDNs of Tilda, Wix, Shopify,
|
|
287
|
+
Squarespace and Webflow, the general asset CDNs (`cdn.jsdelivr.net`,
|
|
288
|
+
`unpkg.com`, `cdnjs.cloudflare.com`, `code.jquery.com`, `ajax.googleapis.com`),
|
|
289
|
+
Google Fonts and hCaptcha. Strict mode never intercepts them, because blocking a
|
|
290
|
+
builder's own CDN breaks the page without protecting anyone; the hosted service
|
|
291
|
+
also leaves them out of scan reports, since there is nothing for a site owner to
|
|
292
|
+
decide. Membership is a claim that a host delivers the site's own assets, not
|
|
293
|
+
that it is harmless in general — anything that *measures* keeps a real consent
|
|
294
|
+
category instead, which is why `static.cloudflareinsights.com` (Cloudflare Web
|
|
295
|
+
Analytics) is classified as `analytics` and blocked before consent even though
|
|
296
|
+
the rest of Cloudflare's CDN is infrastructure. Both lists are matched by suffix
|
|
297
|
+
and returned as copies, so reading them cannot widen what strict mode allows.
|
|
298
|
+
|
|
199
299
|
## API
|
|
200
300
|
|
|
201
301
|
All methods are safe to call at any time and never throw.
|
|
@@ -279,6 +379,11 @@ Iframes use `data-src`, which is applied once the category is allowed:
|
|
|
279
379
|
|
|
280
380
|
`data-ck` accepts any category name: `functional`, `analytics`, `marketing`.
|
|
281
381
|
|
|
382
|
+
> On WordPress this markup is applied **automatically, server-side**, for every
|
|
383
|
+
> tracker in the built-in database — see "Server-side markup" below. Manual
|
|
384
|
+
> markup is still needed for trackers the database does not know (your own
|
|
385
|
+
> domain, an unlisted vendor) and for inline snippets.
|
|
386
|
+
|
|
282
387
|
### Automatic blocking
|
|
283
388
|
|
|
284
389
|
Scripts injected at runtime are intercepted without any markup. ConsentKit
|
|
@@ -301,6 +406,129 @@ and DoubleClick.
|
|
|
301
406
|
Because the patches install at parse time, `ck-core.js` must load before any
|
|
302
407
|
tracker — put it first in `<head>` and do not add `defer`.
|
|
303
408
|
|
|
409
|
+
`<iframe src>` is covered by the same three patches, and a blocked frame keeps
|
|
410
|
+
its URL in `data-src` until its category is granted.
|
|
411
|
+
|
|
412
|
+
### Extending the tracker database
|
|
413
|
+
|
|
414
|
+
The built-in host list is a snapshot, not an oracle. `_extendHostDb()` merges
|
|
415
|
+
extra `host: category` pairs into it at runtime:
|
|
416
|
+
|
|
417
|
+
```js
|
|
418
|
+
ConsentKit._extendHostDb({
|
|
419
|
+
'analytics.vendor.example': 'analytics',
|
|
420
|
+
'pixel.partner.example': 'marketing'
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Matching is the same as for built-in entries — a bare domain also covers its
|
|
425
|
+
subdomains — and an override wins over the shipped classification for the same
|
|
426
|
+
host. Categories outside `necessary | functional | analytics | marketing` and
|
|
427
|
+
malformed hostnames are ignored; the call returns how many pairs were accepted.
|
|
428
|
+
|
|
429
|
+
It works both **before and after** `init()`. Calling it afterwards does not
|
|
430
|
+
re-examine anything already inserted (a script that has loaded cannot be
|
|
431
|
+
unloaded), but every later insertion is classified against the extended map.
|
|
432
|
+
|
|
433
|
+
In SaaS mode this is automatic: `ck-saas.js` applies `config.hostdb` from the
|
|
434
|
+
service *before* it calls `init()`, and again when a background revalidation
|
|
435
|
+
brings a changed table. At release time `node tools/sync-hostdb.mjs` bakes the
|
|
436
|
+
same public table into `src/ck-core.js`, so inline blocks, the npm package and
|
|
437
|
+
the WordPress plugin get it too.
|
|
438
|
+
|
|
439
|
+
### Strict mode
|
|
440
|
+
|
|
441
|
+
By default ConsentKit blocks what it **recognises**. `blocking.mode: 'strict'`
|
|
442
|
+
inverts that for third parties: before consent, any `<script src>` or
|
|
443
|
+
`<iframe src>` pointing at a host that is not same-site is intercepted, whether
|
|
444
|
+
or not the tracker database has ever heard of it.
|
|
445
|
+
|
|
446
|
+
```js
|
|
447
|
+
ConsentKit.init({
|
|
448
|
+
blocking: { mode: 'strict', allow: ['widgets.partner.example'] }
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Four things are never intercepted:
|
|
453
|
+
|
|
454
|
+
1. **Same-site URLs** — the page's own host, its subdomains, and anything
|
|
455
|
+
sharing its registrable domain. The check is deliberately conservative: when
|
|
456
|
+
the answer is unclear it says same-site, because wrongly blocking a
|
|
457
|
+
first-party asset breaks the site.
|
|
458
|
+
2. **`blocking.allow`** — your own list, matched by suffix.
|
|
459
|
+
3. **The built-in allowlist**, readable as `ConsentKit._baseAllow`. Two parts:
|
|
460
|
+
**infrastructure** (`ConsentKit._infra()`, see below) and things a page is
|
|
461
|
+
unusable without (`js.stripe.com`, `pay.google.com`, `checkout.creem.io`,
|
|
462
|
+
and reCAPTCHA — scoped to `www.google.com/recaptcha` and
|
|
463
|
+
`www.gstatic.com/recaptcha`, not to those hosts at large).
|
|
464
|
+
4. **Known `necessary` / `functional` hosts** already granted, which keep their
|
|
465
|
+
real category rather than being swept up as marketing.
|
|
466
|
+
|
|
467
|
+
Anything else is filed under **`marketing`** — the strictest category — and
|
|
468
|
+
comes back only when the visitor accepts marketing.
|
|
469
|
+
|
|
470
|
+
**Read this before switching it on.** Strict mode will block third-party code
|
|
471
|
+
your site needs and that ConsentKit has no way to recognise as necessary: a
|
|
472
|
+
booking widget, a map, a review embed, a payment provider that is not on the
|
|
473
|
+
list. Turn it on, load the site with `?ck_debug=1`, and read the "Blocked until
|
|
474
|
+
consent" list in the panel — entries the engine held back only because of strict
|
|
475
|
+
mode are labelled `strict`. Everything there that the page genuinely needs
|
|
476
|
+
belongs in `blocking.allow`.
|
|
477
|
+
|
|
478
|
+
Two limits are worth stating plainly:
|
|
479
|
+
|
|
480
|
+
* **Dynamic insertions only**, exactly as for known trackers. A tag written
|
|
481
|
+
straight into the HTML starts its request before ConsentKit runs (see below).
|
|
482
|
+
The WordPress plugin's server-side rewrite currently marks up *known* trackers
|
|
483
|
+
only; extending it to strict mode is recorded as a follow-up in SPEC.md.
|
|
484
|
+
* **Strict starts when the config does.** The mode is read from `config`, so in
|
|
485
|
+
SaaS mode nothing is blocked strictly until the config has arrived. Blocking
|
|
486
|
+
of *known* trackers still begins at parse time, as always.
|
|
487
|
+
|
|
488
|
+
### Static tags: what the browser cannot catch
|
|
489
|
+
|
|
490
|
+
Runtime injection is covered by the patches above. A tracker tag written
|
|
491
|
+
**directly into the HTML** is not: the parser starts that request before the
|
|
492
|
+
first line of `ck-core.js` runs. The gap was measured (debt Д9: request at
|
|
493
|
+
14 ms, our script at 18 ms) and it is negative — no client-side technique
|
|
494
|
+
closes it. Such tags need either manual markup, or a server that rewrites them
|
|
495
|
+
before the page is sent.
|
|
496
|
+
|
|
497
|
+
### Server-side markup (WordPress plugin)
|
|
498
|
+
|
|
499
|
+
The WordPress plugin does exactly that, and it is **on by default** since 0.3.5.
|
|
500
|
+
While the page is generated, it rewrites tracker tags in the finished HTML:
|
|
501
|
+
|
|
502
|
+
```html
|
|
503
|
+
<!-- what the theme wrote -->
|
|
504
|
+
<script src="https://mc.yandex.ru/metrika/tag.js"></script>
|
|
505
|
+
|
|
506
|
+
<!-- what the browser receives -->
|
|
507
|
+
<script type="text/plain" data-ck="analytics"
|
|
508
|
+
data-ck-src="https://mc.yandex.ru/metrika/tag.js"></script>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
`<iframe src>` of a known host becomes `data-ck` + `data-src` with `src`
|
|
512
|
+
removed. The categories come from the same HOST_DB/PATH_DB as the browser
|
|
513
|
+
engine: `tools/export-hostdb.mjs` generates
|
|
514
|
+
`plugins/wordpress/consentkit/includes/hostdb.php` from `src/ck-core.js`, and
|
|
515
|
+
`test/hostdb.test.mjs` fails if the two drift.
|
|
516
|
+
|
|
517
|
+
What it skips: ConsentKit's own assets, tags carrying `data-ck-ignore`, tags
|
|
518
|
+
already marked up by hand, inline scripts (there is no URL to defer), the GTM
|
|
519
|
+
container, and anything inside comments, `<pre>` or `<textarea>`. On any error
|
|
520
|
+
the page is returned unchanged.
|
|
521
|
+
|
|
522
|
+
The `<pre>` / `<textarea>` skip keeps the *source text* byte-identical, which is
|
|
523
|
+
what a page documenting a tracker snippet needs. It does not keep such a tag
|
|
524
|
+
alive: the browser parses `<pre><script src=…>` as a real script element
|
|
525
|
+
whatever the server did, so the runtime engine may still intercept it. Caching plugins are compatible and get the
|
|
526
|
+
already-rewritten HTML, because the rewrite happens at the PHP level before the
|
|
527
|
+
page is cached.
|
|
528
|
+
|
|
529
|
+
Outside WordPress the same idea applies to any server-side template: emit the
|
|
530
|
+
`type="text/plain" data-ck` form directly, as in "Manual markup" above.
|
|
531
|
+
|
|
304
532
|
## Google Consent Mode v2
|
|
305
533
|
|
|
306
534
|
With `integrations.gcm` (default), the core pushes `consent: default` with every
|
|
@@ -348,18 +576,26 @@ external requests. Rebuild them with `tools/build-inline.mjs` (see
|
|
|
348
576
|
[`tools/README.md`](tools/README.md)); each block's header records the exact
|
|
349
577
|
command that produced it.
|
|
350
578
|
|
|
351
|
-
ConsentKit 0.
|
|
352
|
-
this roughly three- to fourfold. Every block includes the
|
|
353
|
-
`--no-branding`
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
| `ready/
|
|
359
|
-
| `ready/ru-
|
|
360
|
-
| `ready/ru-box
|
|
361
|
-
| `ready/ru-
|
|
362
|
-
| `ready/
|
|
579
|
+
ConsentKit 0.5.0, rebuilt 2026-09-05, uncompressed — gzip on the server cuts
|
|
580
|
+
this roughly three- to fourfold. Every block includes the branding extension
|
|
581
|
+
and the attribution line; `--no-branding` drops both the code and the config
|
|
582
|
+
and takes **~24 KB** back off:
|
|
583
|
+
|
|
584
|
+
| Block | Languages | Bytes | gzip | `--no-branding` |
|
|
585
|
+
|---|---|---|---|---|
|
|
586
|
+
| `ready/en-bar.txt` | en | 160,388 | 48,950 | 136,326 |
|
|
587
|
+
| `ready/ru-bar.txt` | ru, ro, en | 162,009 | 49,711 | 137,927 |
|
|
588
|
+
| `ready/ru-box.txt` | ru, ro, en | 162,024 | 49,718 | 137,942 |
|
|
589
|
+
| `ready/ru-box-right.txt` | ru, ro, en | 162,033 | 49,718 | 137,945 |
|
|
590
|
+
| `ready/ru-modal.txt` | ru, ro, en | 162,017 | 49,713 | 137,933 |
|
|
591
|
+
| `ready/eu-bar.txt` | 34 languages | 210,663 | 68,094 | 186,601 |
|
|
592
|
+
|
|
593
|
+
0.5.0 adds roughly 18.5 KB over 0.4.1 (about 6.1 KB gzipped): the contrast
|
|
594
|
+
engine, the per-button token resolution, the new stylesheet rules and the
|
|
595
|
+
comments explaining the rules the arithmetic implements. 0.4.1 had added
|
|
596
|
+
roughly 6.3 KB over 0.4.0 (the infrastructure list), and 0.4.0 roughly 16.1 KB
|
|
597
|
+
over 0.3.6: iframe interception, strict mode (same-site detection, the
|
|
598
|
+
allowlists, the public-suffix table) and the extensible tracker database.
|
|
363
599
|
|
|
364
600
|
Size is driven almost entirely by the bundled languages: `en` and `ru` are
|
|
365
601
|
built into the UI and cost nothing extra, while layout, position, theme and
|
|
@@ -540,13 +776,19 @@ has been verified and what has not.
|
|
|
540
776
|
- `dataLayer` event trace for consent restore, upgrade and withdrawal.
|
|
541
777
|
- npm entry points and TypeScript types: syntax and import smoke tests in Node
|
|
542
778
|
without a DOM.
|
|
543
|
-
- PHP files of the WordPress plugin pass `php -l` on 7.4 and 8.5.
|
|
779
|
+
- PHP files of the WordPress plugin pass `php -l` on 7.4, 8.3 and 8.5.
|
|
780
|
+
- The server-side rewriting engine has its own suite of 61 cases
|
|
781
|
+
(`plugins/wordpress/consentkit/tests/rewrite.test.php`), green on PHP 7.4,
|
|
782
|
+
8.3 and 8.5. It is a plain PHP CLI script, so it runs outside `npm test`.
|
|
544
783
|
|
|
545
784
|
### Not verified — read before production use
|
|
546
785
|
|
|
547
|
-
- **The WordPress plugin has
|
|
548
|
-
|
|
549
|
-
|
|
786
|
+
- **The WordPress plugin has been verified on a live install, but only one.**
|
|
787
|
+
It was run on WordPress 7.1 / PHP 8.3 in Docker (activation, settings
|
|
788
|
+
round-trip, shortcode, uninstall, server-side markup end to end). The declared
|
|
789
|
+
floor of WordPress 6.0 / PHP 7.4 has not been exercised live — the PHP files
|
|
790
|
+
pass `php -l` and the rewriting test suite on 7.4, 8.3 and 8.5 — and no theme
|
|
791
|
+
conflict or multisite behaviour has been observed.
|
|
550
792
|
- **The GTM container has never been through a real import.** The JSON is valid
|
|
551
793
|
and structurally modelled on the documented export format, but Tag Manager has
|
|
552
794
|
not accepted it in practice; some field names (notably GA4 config
|
package/npm/core.mjs
CHANGED
|
@@ -28,6 +28,9 @@ export const {
|
|
|
28
28
|
rejectAll,
|
|
29
29
|
withdraw,
|
|
30
30
|
show,
|
|
31
|
-
hide
|
|
31
|
+
hide,
|
|
32
|
+
// v0.4.0 (§1.3): merging service overrides into the tracker database is part
|
|
33
|
+
// of the public surface, so it must be reachable as a named import too.
|
|
34
|
+
_extendHostDb
|
|
32
35
|
} = ConsentKit;
|
|
33
36
|
export { ConsentKit };
|
package/npm/index.cjs
CHANGED
|
@@ -69,7 +69,10 @@ var hasDom = typeof document !== 'undefined' && typeof window !== 'undefined';
|
|
|
69
69
|
if (hasDom) {
|
|
70
70
|
// 2. Locales — optional language packs.
|
|
71
71
|
try { require('../src/ck-locales.js'); } catch (e) { /* optional */ }
|
|
72
|
-
// 3.
|
|
72
|
+
// 3. Branding — optional, and before the UI: it registers itself on
|
|
73
|
+
// ConsentKit._uiExtensions and must be there before the first mount().
|
|
74
|
+
try { require('../src/ck-ui-branding.js'); } catch (e) { /* optional */ }
|
|
75
|
+
// 4. UI — touches `document` at module scope, browser only.
|
|
73
76
|
try { require('../src/ck-ui.js'); } catch (e) { /* best effort */ }
|
|
74
77
|
}
|
|
75
78
|
|
package/npm/index.d.ts
CHANGED
|
@@ -59,17 +59,87 @@ export interface CkDarkTheme {
|
|
|
59
59
|
accent?: string;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** v0.5.0 (SPEC V1.6 §1). Corner radii in px, clamped to 0–32. */
|
|
63
|
+
export interface CkRadiusConfig {
|
|
64
|
+
/** Banner, panel and table corners. Default `16`. */
|
|
65
|
+
card?: number;
|
|
66
|
+
/** Button corners. Default `8`. */
|
|
67
|
+
button?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* v0.5.0 (SPEC V1.6 §1). One button's appearance.
|
|
72
|
+
*
|
|
73
|
+
* Contrast is enforced automatically and cannot be switched off: `fg` is kept
|
|
74
|
+
* only when it clears 4.5:1 against the fill it sits on, otherwise it becomes
|
|
75
|
+
* white or `#161616`, whichever wins. An `outline` `border` is darkened or
|
|
76
|
+
* lightened in steps until it clears 3:1 against the card, and that resolved
|
|
77
|
+
* border colour is then the text colour, subject to the same 4.5:1 rule.
|
|
78
|
+
* `ConsentKit._contrast` exposes the arithmetic.
|
|
79
|
+
*/
|
|
80
|
+
export interface CkButtonStyle {
|
|
81
|
+
variant?: 'filled' | 'outline';
|
|
82
|
+
/** Fill. `filled` defaults to the accent; `outline` is transparent. */
|
|
83
|
+
bg?: string;
|
|
84
|
+
/** Text. Computed from the contrast rules when omitted. */
|
|
85
|
+
fg?: string;
|
|
86
|
+
/** Border. Defaults to the accent. */
|
|
87
|
+
border?: string;
|
|
88
|
+
/** `1` (default) or `2`. */
|
|
89
|
+
borderWidth?: 1 | 2;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* v0.5.0 (SPEC V1.6 §1). Per-button appearance.
|
|
94
|
+
*
|
|
95
|
+
* `accept` and `reject` are held to the equal-buttons invariant: they always
|
|
96
|
+
* render at the same size and weight, and they always share one variant. If
|
|
97
|
+
* the two disagree, `accept.variant` is used for both.
|
|
98
|
+
*/
|
|
99
|
+
export interface CkButtonsConfig {
|
|
100
|
+
/** Default `{ variant: 'filled' }` with the accent as the fill. */
|
|
101
|
+
accept?: CkButtonStyle;
|
|
102
|
+
/** Default `{ variant: 'filled' }`; always follows `accept.variant`. */
|
|
103
|
+
reject?: CkButtonStyle;
|
|
104
|
+
/** Default `{ variant: 'outline' }` with the accent as the border. */
|
|
105
|
+
settings?: CkButtonStyle;
|
|
106
|
+
}
|
|
107
|
+
|
|
62
108
|
export interface CkThemeConfig {
|
|
63
109
|
/** Accent colour, exposed as `--ck-accent`. Default `'#2B50D8'`. */
|
|
64
110
|
accent?: string;
|
|
65
|
-
/**
|
|
66
|
-
|
|
111
|
+
/**
|
|
112
|
+
* v0.5.0. `{ card, button }` in px, 0–32. Default `{ card: 16, button: 8 }`.
|
|
113
|
+
*
|
|
114
|
+
* A bare string or number is the pre-0.5.0 form and still works: it sets the
|
|
115
|
+
* card radius and leaves the button radius at its default.
|
|
116
|
+
*/
|
|
117
|
+
radius?: CkRadiusConfig | string | number;
|
|
118
|
+
/**
|
|
119
|
+
* v0.5.0. `'inherit'` (default) takes the host page's font family — sizes
|
|
120
|
+
* stay fixed either way. `'system'` restores the pre-0.5.0 system stack.
|
|
121
|
+
*/
|
|
122
|
+
font?: 'inherit' | 'system';
|
|
123
|
+
/** v0.5.0. Per-button appearance; contrast is enforced automatically. */
|
|
124
|
+
buttons?: CkButtonsConfig;
|
|
67
125
|
/** v0.2. Default `'auto'` — follows `prefers-color-scheme`. */
|
|
68
126
|
mode?: 'auto' | 'light' | 'dark';
|
|
69
127
|
/** v0.2. Overrides the built-in dark palette. */
|
|
70
128
|
dark?: CkDarkTheme;
|
|
71
129
|
}
|
|
72
130
|
|
|
131
|
+
/** v0.5.0 (SPEC V1.6 §2). Copy and the «Learn more» control. */
|
|
132
|
+
export interface CkTextsConfig {
|
|
133
|
+
/** Cookie policy address. `http(s)` only; anything else is ignored. */
|
|
134
|
+
policyUrl?: string;
|
|
135
|
+
/**
|
|
136
|
+
* What «Learn more» does. Defaults to `'policy'` when `policyUrl` is set and
|
|
137
|
+
* `'settings'` when it is not. `'policy'` without a usable URL falls back to
|
|
138
|
+
* `'settings'` rather than rendering a dead link. `'hide'` renders nothing.
|
|
139
|
+
*/
|
|
140
|
+
detailsAction?: 'policy' | 'settings' | 'hide';
|
|
141
|
+
}
|
|
142
|
+
|
|
73
143
|
/** Whether a category is offered in the preferences panel at all. */
|
|
74
144
|
export interface CkCategoryConfig {
|
|
75
145
|
enabled?: boolean;
|
|
@@ -88,6 +158,20 @@ export interface CkIntegrationsConfig {
|
|
|
88
158
|
gtmDataLayer?: boolean;
|
|
89
159
|
}
|
|
90
160
|
|
|
161
|
+
/** v0.4.0 (§2). How much the blocking engine holds back before consent. */
|
|
162
|
+
export interface CkBlockingConfig {
|
|
163
|
+
/**
|
|
164
|
+
* `'known'` (default) blocks what the tracker database recognises.
|
|
165
|
+
* `'strict'` additionally intercepts EVERY third-party `<script src>` and
|
|
166
|
+
* `<iframe src>` that is not same-site, not in `allow`, not in the built-in
|
|
167
|
+
* allowlist (`ConsentKit._baseAllow`) and not an already-granted
|
|
168
|
+
* necessary/functional host. Interceptions are filed under `marketing`.
|
|
169
|
+
*/
|
|
170
|
+
mode?: 'known' | 'strict';
|
|
171
|
+
/** Hosts strict mode must never intercept. Suffix match: `p.com` covers `cdn.p.com`. */
|
|
172
|
+
allow?: string[];
|
|
173
|
+
}
|
|
174
|
+
|
|
91
175
|
/** One declared cookie, shown under its category in the preferences panel. */
|
|
92
176
|
export interface CkCookieTableEntry {
|
|
93
177
|
name: string;
|
|
@@ -105,10 +189,21 @@ export interface CkConfig {
|
|
|
105
189
|
language?: string;
|
|
106
190
|
layout?: CkLayoutConfig;
|
|
107
191
|
theme?: CkThemeConfig;
|
|
192
|
+
/** v0.5.0. */
|
|
193
|
+
texts?: CkTextsConfig;
|
|
108
194
|
categories?: CkCategoriesConfig;
|
|
109
195
|
/** Lifetime of the stored decision, in days. Default `365`. */
|
|
110
196
|
consentTtlDays?: number;
|
|
111
197
|
integrations?: CkIntegrationsConfig;
|
|
198
|
+
/** v0.4.0. Default `{ mode: 'known', allow: [] }`. */
|
|
199
|
+
blocking?: CkBlockingConfig;
|
|
200
|
+
/**
|
|
201
|
+
* v0.4.0 (§1.3). Extra `host: category` pairs merged into the tracker
|
|
202
|
+
* database. `init()` applies them before its initial scan, so scripts
|
|
203
|
+
* already in the markup are classified against them. In SaaS mode the
|
|
204
|
+
* service supplies this; `_extendHostDb()` does the same at any later point.
|
|
205
|
+
*/
|
|
206
|
+
hostdb?: Record<string, CkCategory>;
|
|
112
207
|
cookieTable?: CkCookieTableEntry[];
|
|
113
208
|
}
|
|
114
209
|
|
|
@@ -141,6 +236,136 @@ export interface ConsentKitApi {
|
|
|
141
236
|
show(): void;
|
|
142
237
|
/** Dispatches `ck:ui:close`. */
|
|
143
238
|
hide(): void;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* v0.4.0 (§1.3). Merges `{ host: category }` into the runtime tracker
|
|
242
|
+
* database and returns how many pairs were accepted. Matching follows the
|
|
243
|
+
* built-in table: a bare domain also covers its subdomains, and an override
|
|
244
|
+
* wins over the shipped classification for the same host.
|
|
245
|
+
*
|
|
246
|
+
* Safe before AND after `init()`. Afterwards, nothing already inserted is
|
|
247
|
+
* re-examined — a script that has loaded cannot be unloaded — but every
|
|
248
|
+
* later insertion is classified against the extended map.
|
|
249
|
+
*/
|
|
250
|
+
_extendHostDb(map: Record<string, CkCategory>): number;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* v0.4.0 (§2). The built-in strict-mode allowlist, as hosts plus a few
|
|
254
|
+
* `host/path` entries (reCAPTCHA). A copy: mutating it changes nothing.
|
|
255
|
+
*
|
|
256
|
+
* Since v0.4.1 this includes the whole of `_infra()`.
|
|
257
|
+
*/
|
|
258
|
+
readonly _baseAllow: string[];
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* v0.4.1 (§8). The infrastructure list: CDNs and static hosts of site
|
|
262
|
+
* builders and hosting platforms, general asset CDNs, fonts and captcha.
|
|
263
|
+
*
|
|
264
|
+
* A CLASS of host, not a consent category — these serve the site's own
|
|
265
|
+
* assets, so there is no consent decision to make about them. Strict mode
|
|
266
|
+
* never intercepts them, and the SaaS scanner keeps them out of the report.
|
|
267
|
+
* A copy: mutating it changes nothing.
|
|
268
|
+
*/
|
|
269
|
+
_infra(): string[];
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* v0.4.1 (§8). Whether a URL — or a bare hostname — is infrastructure.
|
|
273
|
+
*/
|
|
274
|
+
_isInfra(url: string): boolean;
|
|
275
|
+
|
|
276
|
+
/** The category the engine would assign to a URL, or `null` if unknown. */
|
|
277
|
+
_categoryForUrl(url: string): CkCategory | null;
|
|
278
|
+
|
|
279
|
+
/** What is currently held back, host+path only — never a query string. */
|
|
280
|
+
_blocked(): CkBlockedEntry[];
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* v0.5.0 (SPEC V1.6 §1). The contrast and theme arithmetic the banner
|
|
284
|
+
* actually paints with, as pure functions — so a theme editor quotes the
|
|
285
|
+
* same numbers rather than reimplementing them and drifting.
|
|
286
|
+
*
|
|
287
|
+
* Present only when `src/ck-ui.js` is loaded (the core alone does not draw).
|
|
288
|
+
* Colour arguments are hex; anything unmeasurable (a colour name, an `rgb()`
|
|
289
|
+
* string) yields `null` from the ratio functions and is returned untouched,
|
|
290
|
+
* with `adjusted: false`, by the rest.
|
|
291
|
+
*/
|
|
292
|
+
readonly _contrast?: CkContrastApi;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** One resolved button, as `ConsentKit._contrast.resolveButtonStyles()` returns it. */
|
|
296
|
+
export interface CkResolvedButton {
|
|
297
|
+
variant: 'filled' | 'outline';
|
|
298
|
+
bg: string;
|
|
299
|
+
fg: string;
|
|
300
|
+
border: string;
|
|
301
|
+
borderWidth: 1 | 2;
|
|
302
|
+
/** Text contrast against `against`. `null` when it could not be measured. */
|
|
303
|
+
ratio: number | null;
|
|
304
|
+
/** `true` when the contrast rules overrode what the config asked for. */
|
|
305
|
+
adjusted: boolean;
|
|
306
|
+
/** Border contrast against the card (`outline` only). */
|
|
307
|
+
borderRatio?: number | null;
|
|
308
|
+
borderAdjusted?: boolean;
|
|
309
|
+
/** What `ratio` was measured against: the button's own fill, or the card. */
|
|
310
|
+
against: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface CkResolvedButtons {
|
|
314
|
+
mode: 'light' | 'dark';
|
|
315
|
+
cardBg: string;
|
|
316
|
+
accent: string;
|
|
317
|
+
buttons: { accept: CkResolvedButton; reject: CkResolvedButton; settings: CkResolvedButton };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** v0.5.0. `ConsentKit._contrast` — pure, DOM-free, safe to call in Node. */
|
|
321
|
+
export interface CkContrastApi {
|
|
322
|
+
/** WCAG 2.1 relative luminance, 0–1. `null` for an unmeasurable colour. */
|
|
323
|
+
relativeLuminance(hex: string): number | null;
|
|
324
|
+
/** WCAG 2.1 contrast ratio, 1–21. `null` when either side is unmeasurable. */
|
|
325
|
+
contrastRatio(a: string, b: string): number | null;
|
|
326
|
+
/** Keeps `fg` when it clears `min`, else white or `#161616` — whichever wins. */
|
|
327
|
+
ensureContrast(fg: string, bg: string, min?: number):
|
|
328
|
+
{ color: string; adjusted: boolean; ratio: number | null };
|
|
329
|
+
/** Darkens or lightens `color` in steps until it clears `min` against `bg`. */
|
|
330
|
+
stepToContrast(color: string, bg: string, min?: number):
|
|
331
|
+
{ color: string; adjusted: boolean; ratio: number | null };
|
|
332
|
+
/** The three buttons resolved for one palette, contrast already applied. */
|
|
333
|
+
resolveButtonStyles(theme: CkThemeConfig, mode: 'light' | 'dark',
|
|
334
|
+
palette?: Record<string, string>): CkResolvedButtons;
|
|
335
|
+
resolveRadius(theme: CkThemeConfig): { card: number; button: number };
|
|
336
|
+
resolveFont(theme: CkThemeConfig): string;
|
|
337
|
+
/** Takes the whole config, not just `texts`. */
|
|
338
|
+
resolveDetails(config: CkConfig):
|
|
339
|
+
{ kind: 'policy' | 'settings' | 'hide'; href: string | null };
|
|
340
|
+
/** Takes the whole config. Returns the generated stylesheet and both palettes. */
|
|
341
|
+
buildThemeCss(config: CkConfig): {
|
|
342
|
+
css: string;
|
|
343
|
+
mode: 'auto' | 'light' | 'dark';
|
|
344
|
+
radius: { card: number; button: number };
|
|
345
|
+
font: string;
|
|
346
|
+
light: CkResolvedButtons;
|
|
347
|
+
dark: CkResolvedButtons;
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** One entry of `ConsentKit._blocked()`. */
|
|
352
|
+
export interface CkBlockedEntry {
|
|
353
|
+
host: string;
|
|
354
|
+
path: string;
|
|
355
|
+
/** `'script'`, `'iframe'`, … */
|
|
356
|
+
kind: string;
|
|
357
|
+
category: CkCategory | null;
|
|
358
|
+
/** `'engine'` — intercepted by the patches; `'markup'` — marked up by hand. */
|
|
359
|
+
origin: 'engine' | 'markup';
|
|
360
|
+
/** v0.4.0. True when strict mode held this back, i.e. the host is unknown. */
|
|
361
|
+
strict: boolean;
|
|
362
|
+
/**
|
|
363
|
+
* v0.4.0. `false` when the category was granted but the element still never
|
|
364
|
+
* loaded — typically a script created and given a `src` without ever being
|
|
365
|
+
* appended, which `applyConsentToDom()` cannot reach. Such entries stay in
|
|
366
|
+
* the report after consent precisely so they can be diagnosed.
|
|
367
|
+
*/
|
|
368
|
+
revived: boolean;
|
|
144
369
|
}
|
|
145
370
|
|
|
146
371
|
declare const ConsentKit: ConsentKitApi;
|
|
@@ -156,6 +381,7 @@ export declare function rejectAll(): CkState;
|
|
|
156
381
|
export declare function withdraw(): CkState;
|
|
157
382
|
export declare function show(): void;
|
|
158
383
|
export declare function hide(): void;
|
|
384
|
+
export declare function _extendHostDb(map: Record<string, CkCategory>): number;
|
|
159
385
|
|
|
160
386
|
declare global {
|
|
161
387
|
interface Window {
|