@ecomconsult/consentkit 0.5.7 → 0.5.9

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 CHANGED
@@ -199,6 +199,7 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
199
199
  | `blocking.placeholders` | `boolean` | `true` | v0.5.7. Draw a card in place of an embed held back before consent — see [Placeholders for blocked embeds](#placeholders-for-blocked-embeds). `false` restores the pre-0.5.7 behaviour: the frame is still blocked, just invisible |
200
200
  | `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 |
201
201
  | `cookieTable` | `CkCookieTableEntry[]` | `[]` | Declared cookies, listed per category in the panel |
202
+ | `services` | `CkService[]` | `[]` | v0.5.8. Third-party services the site declares. Each gets its own toggle inside its category group in the panel, and can be refused individually — see [Services](#services). At most 50 |
202
203
 
203
204
  `cookieTable` entries:
204
205
 
@@ -206,6 +207,45 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
206
207
  { name: '_ga', category: 'analytics', vendor: 'Google', purpose: 'Visit statistics', expiry: '2 years' }
207
208
  ```
208
209
 
210
+ ### Services
211
+
212
+ **v0.5.8.** A `services` row names one third party: where its resources come
213
+ from, which cookies it sets and what it is for. The preferences panel lists it
214
+ inside its category group with **its own toggle**, so a visitor can accept
215
+ analytics in general and still refuse one particular service.
216
+
217
+ ```js
218
+ services: [{
219
+ id: 'hotjar', // stable, ^[a-z0-9-]{1,64}$
220
+ name: 'Hotjar',
221
+ vendor: 'Hotjar Ltd',
222
+ category: 'analytics',
223
+ hosts: ['hotjar.com'], // suffix-matched, like the tracker database
224
+ paths: ['/hotjar-'], // optional, substring-matched
225
+ cookies: ['_hjSession', '_hjSessionUser'], // names; matched against cookieTable
226
+ privacyUrl: 'https://www.hotjar.com/privacy/', // http(s) only
227
+ purpose: { ru: '…', ro: '…', en: 'Records how visitors move around the page.' },
228
+ enabled: true // false: not shown, not blocked separately
229
+ }]
230
+ ```
231
+
232
+ What the toggle does:
233
+
234
+ - **Group off** — every service of that group is off and blocked, as before.
235
+ - **Group on** — the services come back, *except* the ones the visitor switched
236
+ off by hand. A refusal survives the group being switched off and on again.
237
+ - A refused service's resources are held back exactly as if its category had no
238
+ consent, and its cookies are deleted exactly as on a category withdrawal.
239
+ - The «Allow and show» button on a blocked embed's placeholder grants the
240
+ category **and** clears the refusal on that frame's service.
241
+
242
+ `hosts` need not already be in the tracker database: `init()` folds them into
243
+ the block map under the row's own category, so a service host ConsentKit has
244
+ never heard of is still held back.
245
+
246
+ Refusals are stored in `ck_consent` as `services: { '<id>': false }` — denials
247
+ only. An id absent from the map is allowed, subject to its category.
248
+
209
249
  ### Button appearance
210
250
 
211
251
  Each of the three banner buttons can be styled independently:
@@ -361,9 +401,10 @@ All methods are safe to call at any time and never throw.
361
401
  |---|---|---|
362
402
  | `init(config?)` | `CkState` | Idempotent. Restores stored consent, then dispatches `ck:init`. Calling again merges config only |
363
403
  | `allowed(category)` | `boolean` | `necessary` is always `true` |
404
+ | `allowedService(id)` | `boolean` | v0.5.8. May this one declared service run? True when its category is granted **and** the visitor has not refused it individually. An id the config does not declare is `true` |
364
405
  | `getState()` | `CkState` | A fresh object on every call |
365
406
  | `accept('all')` | `CkState` | Grants everything. `method: 'accept_all'` |
366
- | `accept({ ... })` | `CkState` | Per-category choice. `method: 'custom'`. Omitted categories stay denied |
407
+ | `accept({ ... })` | `CkState` | Per-category choice. `method: 'custom'`. Omitted categories stay denied. v0.5.8: an optional `services: { '<id>': false }` replaces the stored refusals wholesale; omit it to leave them untouched |
367
408
  | `rejectAll()` | `CkState` | Denies every opt-in category. `method: 'reject_all'` |
368
409
  | `withdraw()` | `CkState` | Clears storage and known cookies, sends GCM `denied`, resets to `decided: false` |
369
410
  | `show()` | `void` | Opens the preferences panel |
@@ -381,6 +422,7 @@ All methods are safe to call at any time and never throw.
381
422
  ts: null, // ISO timestamp
382
423
  policyVersion: '1',
383
424
  categories: { necessary: true, functional: false, analytics: false, marketing: false },
425
+ services: {}, // v0.5.8. Per-service refusals ONLY: { hotjar: false }
384
426
  method: null // 'accept_all' | 'reject_all' | 'custom'
385
427
  }
386
428
  ```
package/npm/core.cjs CHANGED
@@ -25,6 +25,9 @@ function createStub() {
25
25
  config: {},
26
26
  init: function () { return undecidedState(); },
27
27
  allowed: function (cat) { return cat === 'necessary'; },
28
+ // v0.5.8 (SPEC V1.12 §3). `true`: a stub means the engine never attached
29
+ // and withholds nothing, so it must not claim a refusal it cannot enforce.
30
+ allowedService: function () { return true; },
28
31
  getState: undecidedState,
29
32
  accept: function () { return undecidedState(); },
30
33
  rejectAll: function () { return undecidedState(); },
package/npm/core.mjs CHANGED
@@ -23,6 +23,10 @@ export default ConsentKit;
23
23
  export const {
24
24
  init,
25
25
  allowed,
26
+ // v0.5.8 (SPEC V1.12 §3): «may this ONE declared service run?» — the category
27
+ // check plus the visitor's per-service refusal, which getState() alone cannot
28
+ // reconstruct. Part of the public surface, so it is a named import too.
29
+ allowedService,
26
30
  getState,
27
31
  accept,
28
32
  rejectAll,
package/npm/index.cjs CHANGED
@@ -28,6 +28,9 @@ function createStub() {
28
28
  config: {},
29
29
  init: function () { return undecidedState(); },
30
30
  allowed: function (cat) { return cat === 'necessary'; },
31
+ // v0.5.8 (SPEC V1.12 §3). `true`: a stub means the engine never attached
32
+ // and withholds nothing, so it must not claim a refusal it cannot enforce.
33
+ allowedService: function () { return true; },
31
34
  getState: undecidedState,
32
35
  accept: function () { return undecidedState(); },
33
36
  rejectAll: function () { return undecidedState(); },
package/npm/index.d.ts CHANGED
@@ -34,11 +34,26 @@ export interface CkState {
34
34
  /** Policy version the decision was recorded against. */
35
35
  policyVersion: string;
36
36
  categories: CkCategories;
37
+ /**
38
+ * v0.5.8 (SPEC V1.12 §3). Per-service refusals, and ONLY refusals:
39
+ * `{ 'hotjar': false }`. An id absent from the map is allowed, subject to its
40
+ * category. A denial survives its category being switched off and back on.
41
+ */
42
+ services: Record<string, false>;
37
43
  method: CkMethod;
38
44
  }
39
45
 
40
46
  /** Argument to `accept()`: `'all'`, or an explicit per-category selection. */
41
- export type CkAcceptArg = 'all' | Partial<Record<CkOptInCategory, boolean>>;
47
+ export type CkAcceptArg =
48
+ | 'all'
49
+ | (Partial<Record<CkOptInCategory, boolean>> & {
50
+ /**
51
+ * v0.5.8 (SPEC V1.12 §3). The FULL per-service refusal map for this
52
+ * decision — it replaces the stored one wholesale. Omit it to leave
53
+ * the existing refusals untouched.
54
+ */
55
+ services?: Record<string, false>;
56
+ });
42
57
 
43
58
  /** Banner placement. `bar` uses bottom/top; `box` uses the corner positions. */
44
59
  export type CkLayoutType = 'bar' | 'modal' | 'box';
@@ -196,6 +211,31 @@ export interface CkCookieTableEntry {
196
211
  expiry?: string;
197
212
  }
198
213
 
214
+ /**
215
+ * v0.5.8 (SPEC V1.12 §2). One declared third-party service.
216
+ *
217
+ * `hosts` are suffix-matched (a bare domain also covers its subdomains) and
218
+ * `paths` are matched as case-insensitive substrings of the resolved URL —
219
+ * the same two rules the built-in tracker database uses.
220
+ */
221
+ export interface CkService {
222
+ /** Stable kebab-case id, `^[a-z0-9-]{1,64}$`. */
223
+ id: string;
224
+ name: string;
225
+ vendor: string;
226
+ category: CkCategory;
227
+ hosts: string[];
228
+ paths?: string[];
229
+ /** Cookie names, matched against `cookieTable` to list them under the service. */
230
+ cookies: string[];
231
+ /** `http(s)` only; anything else is dropped rather than rendered as a link. */
232
+ privacyUrl?: string;
233
+ /** One line for the visitor, per language. Falls back to `en`. */
234
+ purpose?: { ru?: string; ro?: string; en?: string };
235
+ /** Default `true`. `false` hides the row and stops blocking it separately. */
236
+ enabled?: boolean;
237
+ }
238
+
199
239
  /** Configuration accepted by `init()`. Every field is optional. */
200
240
  export interface CkConfig {
201
241
  /** Bump to invalidate stored decisions and re-show the banner. Default `'1'`. */
@@ -220,6 +260,17 @@ export interface CkConfig {
220
260
  */
221
261
  hostdb?: Record<string, CkCategory>;
222
262
  cookieTable?: CkCookieTableEntry[];
263
+ /**
264
+ * v0.5.8 (SPEC V1.12 §2/§3). The third-party services this site declares.
265
+ * Each row gets its own switch inside its category group in the preferences
266
+ * panel, and the engine can hold back that one service while the rest of the
267
+ * category runs. At most 50 rows; a row with `enabled: false` is neither
268
+ * shown nor blocked separately.
269
+ *
270
+ * `hosts` need not be in the built-in tracker database — `init()` folds them
271
+ * into the block map under the row's own category.
272
+ */
273
+ services?: CkService[];
223
274
  }
224
275
 
225
276
  /** Detail payload of `ck:init`. */
@@ -241,6 +292,13 @@ export interface ConsentKitApi {
241
292
  /** Idempotent. Restores stored state, then dispatches `ck:init`. */
242
293
  init(config?: CkConfig): CkState;
243
294
  allowed(category: CkCategory | string): boolean;
295
+ /**
296
+ * v0.5.8 (SPEC V1.12 §3). May this one declared service run? True when its
297
+ * category is granted AND the visitor has not switched it off individually —
298
+ * a combination `getState().categories` alone cannot reconstruct. An id the
299
+ * config does not declare answers `true`.
300
+ */
301
+ allowedService(id: string): boolean;
244
302
  getState(): CkState;
245
303
  /** `accept('all')` grants everything; an object records `method: 'custom'`. */
246
304
  accept(choice?: CkAcceptArg): CkState;
@@ -396,6 +454,8 @@ export { ConsentKit };
396
454
 
397
455
  export declare function init(config?: CkConfig): CkState;
398
456
  export declare function allowed(category: CkCategory | string): boolean;
457
+ /** v0.5.8 (SPEC V1.12 §3). */
458
+ export declare function allowedService(id: string): boolean;
399
459
  export declare function getState(): CkState;
400
460
  export declare function accept(choice?: CkAcceptArg): CkState;
401
461
  export declare function rejectAll(): CkState;
@@ -427,6 +487,8 @@ declare module '@ecomconsult/consentkit' {
427
487
  export { ConsentKit };
428
488
  export function init(config?: CkConfig): CkState;
429
489
  export function allowed(category: CkCategory | string): boolean;
490
+ /** v0.5.8 (SPEC V1.12 §3). */
491
+ export function allowedService(id: string): boolean;
430
492
  export function getState(): CkState;
431
493
  export function accept(choice?: CkAcceptArg): CkState;
432
494
  export function rejectAll(): CkState;
@@ -442,6 +504,8 @@ declare module '@ecomconsult/consentkit/core' {
442
504
  export { ConsentKit };
443
505
  export function init(config?: CkConfig): CkState;
444
506
  export function allowed(category: CkCategory | string): boolean;
507
+ /** v0.5.8 (SPEC V1.12 §3). */
508
+ export function allowedService(id: string): boolean;
445
509
  export function getState(): CkState;
446
510
  export function accept(choice?: CkAcceptArg): CkState;
447
511
  export function rejectAll(): CkState;
@@ -456,6 +520,8 @@ export interface UseConsentResult {
456
520
  /** Current state. On the server, an undecided snapshot. */
457
521
  state: CkState;
458
522
  allowed(category: CkCategory | string): boolean;
523
+ /** v0.5.8 (SPEC V1.12 §3). May this one declared service run? */
524
+ allowedService(id: string): boolean;
459
525
  /** Defaults to `'all'` when called with no argument. */
460
526
  accept(choice?: CkAcceptArg): CkState;
461
527
  rejectAll(): CkState;
package/npm/index.mjs CHANGED
@@ -46,6 +46,10 @@ export default ConsentKit;
46
46
  export const {
47
47
  init,
48
48
  allowed,
49
+ // v0.5.8 (SPEC V1.12 §3): «may this ONE declared service run?» — the category
50
+ // check plus the visitor's per-service refusal, which getState() alone cannot
51
+ // reconstruct. Part of the public surface, so it is a named import too.
52
+ allowedService,
49
53
  getState,
50
54
  accept,
51
55
  rejectAll,
@@ -28,10 +28,15 @@ export function undecidedState() {
28
28
  */
29
29
  export function createStub() {
30
30
  const stub = {
31
- version: '0.5.7',
31
+ version: '0.5.9',
32
32
  config: {},
33
33
  init: function () { return undecidedState(); },
34
34
  allowed: function (cat) { return cat === 'necessary'; },
35
+ // v0.5.8 (SPEC V1.12 §3): may this one declared service run? `true` here
36
+ // for the same reason the real core answers `true` for an unknown id — a
37
+ // stub means the engine never attached and is withholding nothing, so
38
+ // claiming a refusal it does not enforce would be a lie consumer code acts on.
39
+ allowedService: function () { return true; },
35
40
  getState: undecidedState,
36
41
  accept: function () { return undecidedState(); },
37
42
  rejectAll: function () { return undecidedState(); },
@@ -51,6 +56,11 @@ export function createStub() {
51
56
  _infra: function () { return []; },
52
57
  _isInfra: function () { return false; },
53
58
  _blocked: function () { return []; },
59
+ // v0.5.8 (SPEC V1.12 §3): the service registry. Empty for the same reason
60
+ // _baseAllow and _infra are — nothing was normalised, nothing is blocked.
61
+ _serviceForUrl: function () { return null; },
62
+ _services: function () { return []; },
63
+ _deniedServices: function () { return []; },
54
64
  _isStub: true
55
65
  };
56
66
  return stub;
package/npm/react.mjs CHANGED
@@ -113,6 +113,7 @@ function sameState(a, b) {
113
113
  * @returns {{
114
114
  * state: object,
115
115
  * allowed: (cat: string) => boolean,
116
+ * allowedService: (id: string) => boolean,
116
117
  * accept: (choice?: any) => object,
117
118
  * rejectAll: () => object,
118
119
  * withdraw: () => object,
@@ -127,6 +128,14 @@ export function useConsent() {
127
128
  try { return api().allowed(cat); } catch (e) { return false; }
128
129
  }, []);
129
130
 
131
+ /* v0.5.8 (SPEC V1.12 §3). On the server there is no stored decision and no
132
+ service registry, so nothing is being withheld — `true`, matching what the
133
+ core answers for an id it does not know. */
134
+ const allowedService = useCallback(function (id) {
135
+ if (!hasDom()) return true;
136
+ try { return api().allowedService(id); } catch (e) { return true; }
137
+ }, []);
138
+
130
139
  const accept = useCallback(function (choice) {
131
140
  if (!hasDom()) return SERVER_SNAPSHOT;
132
141
  return api().accept(choice === undefined ? 'all' : choice);
@@ -147,7 +156,7 @@ export function useConsent() {
147
156
  api().show();
148
157
  }, []);
149
158
 
150
- return { state, allowed, accept, rejectAll, withdraw, show };
159
+ return { state, allowed, allowedService, accept, rejectAll, withdraw, show };
151
160
  }
152
161
 
153
162
  export default useConsent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecomconsult/consentkit",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
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",
@@ -21,7 +21,8 @@
21
21
  "author": "E-COM CONSULT PLUS",
22
22
  "license": "MIT",
23
23
  "scripts": {
24
- "test": "node --test test/*.test.mjs"
24
+ "test": "node --test test/*.test.mjs",
25
+ "build": "node tools/build-inline.mjs --langs=ru,ro,en --language=auto --layout=bar --position=bottom --accent=#2B50D8 --mode=auto --policy=1 --out=ready/ru-bar.txt && node tools/build-inline.mjs --langs=ru,ro,en --language=auto --layout=box --position=bottom-left --accent=#2B50D8 --mode=auto --policy=1 --out=ready/ru-box.txt && node tools/build-inline.mjs --langs=ru,ro,en --language=auto --layout=box --position=bottom-right --accent=#2B50D8 --mode=auto --policy=1 --out=ready/ru-box-right.txt && node tools/build-inline.mjs --langs=ru,ro,en --language=auto --layout=modal --position=bottom --accent=#2B50D8 --mode=auto --policy=1 --out=ready/ru-modal.txt && node tools/build-inline.mjs --langs=en --language=en --layout=bar --position=bottom --accent=#2B50D8 --mode=auto --policy=1 --out=ready/en-bar.txt && node tools/build-inline.mjs --langs=bg,ca,cs,da,de,el,en,es,et,fi,fr,ga,hr,hu,is,it,lt,lv,mk,mt,nb,nl,no,pl,pt,ro,ru,sk,sl,sq,sr,sv,tr,uk --language=auto --layout=bar --position=bottom --accent=#2B50D8 --mode=auto --policy=1 --out=ready/eu-bar.txt && node tools/export-hostdb.mjs && node tools/sync-site.mjs && node tools/build-site.mjs && cp -f src/ck-core.js src/ck-locales.js src/ck-ui-branding.js src/ck-ui.js src/ck-debug-loader.js plugins/wordpress/consentkit/assets/"
25
26
  },
26
27
  "type": "module",
27
28
  "sideEffects": true,