@ecomconsult/consentkit 0.5.6 → 0.5.8

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
@@ -188,15 +188,18 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
188
188
  | `theme.mode` | `"auto" \| "light" \| "dark"` | `"auto"` | `auto` follows `prefers-color-scheme` |
189
189
  | `theme.dark` | `{ bg, ink, accent, onAccent }` | built-in | Overrides the dark palette |
190
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 |
191
+ | `texts.detailsAction` | `"policy" \| "settings" \| "hide" \| "declaration"` | see notes | v0.5.0, `declaration` in v0.5.7. What «Learn more» does. Defaults to `policy` when `policyUrl` is set, `settings` when it is not. `policy` or `declaration` without a usable URL falls back to `settings` rather than rendering a dead link |
192
+ | `texts.declarationUrl` | `string` | — | v0.5.7. Address of the cookie declaration page, used by `detailsAction: "declaration"`. `http(s)` only. Filled by the hosted service; the client only reads it |
192
193
  | `categories.*.enabled` | `boolean` | `true` | Per category: `functional`, `analytics`, `marketing`. Hides the toggle when `false` |
193
194
  | `consentTtlDays` | `number` | `365` | Lifetime of the stored decision |
194
195
  | `integrations.gcm` | `boolean` | `true` | Google Consent Mode v2 signals |
195
196
  | `integrations.gtmDataLayer` | `boolean` | `true` | Push consent events to `window.dataLayer` |
196
197
  | `blocking.mode` | `"known" \| "strict"` | `"known"` | `strict` also holds back unknown third-party scripts and iframes — see [Strict mode](#strict-mode) |
197
198
  | `blocking.allow` | `string[]` | `[]` | Hosts strict mode must never intercept. Matched by suffix, so `partner.com` also covers `cdn.partner.com` |
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 |
198
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 |
199
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 |
200
203
 
201
204
  `cookieTable` entries:
202
205
 
@@ -204,6 +207,45 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
204
207
  { name: '_ga', category: 'analytics', vendor: 'Google', purpose: 'Visit statistics', expiry: '2 years' }
205
208
  ```
206
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
+
207
249
  ### Button appearance
208
250
 
209
251
  Each of the three banner buttons can be styled independently:
@@ -267,14 +309,69 @@ resolved colours, its contrast ratio, and whether the value was adjusted.
267
309
  | `"policy"` | A link to `texts.policyUrl`, opened with `target="_blank" rel="noopener"` |
268
310
  | `"settings"` | A button that opens the preferences panel |
269
311
  | `"hide"` | Nothing at all |
312
+ | `"declaration"` | v0.5.7. A link to `texts.declarationUrl` — the cookie declaration page — opened the same way as `policy` |
270
313
 
271
314
  The default follows `policyUrl`: `policy` when one is set, `settings` when it
272
- is not — so supplying only a URL does the obvious thing.
315
+ is not — so supplying only a URL does the obvious thing. `declarationUrl`
316
+ deliberately does *not* affect that default: a site that gains a declaration
317
+ address keeps whatever «Learn more» already did until it asks for the change.
318
+
319
+ Both link forms accept `http(s)` addresses only. A `javascript:` or `data:` URL
320
+ in a control the visitor is invited to click is an XSS vector, so anything else
321
+ is refused and the link degrades to `settings`.
273
322
 
274
323
  > Before 0.5.0 this control was rendered as `<a href="#">` with no handler at
275
324
  > all: clicking it jumped to the top of the page and nothing else. Any site
276
325
  > running 0.4.x or earlier has a dead «Learn more» link.
277
326
 
327
+ ### Reopening the settings
328
+
329
+ `ConsentKit.openSettings()` opens the preferences panel from anywhere on the
330
+ page — a footer link, a menu item, a button in your own cookie policy. It is
331
+ safe to call before the banner has mounted: a call that arrives while the UI
332
+ file is still loading is remembered and honoured on mount, so a link clicked
333
+ during a slow page load still works.
334
+
335
+ The same panel has an address. Ссылка «Изменить выбор cookie» →
336
+ `https://site/#ck-settings`: любая ссылка на страницу сайта с этим хвостом
337
+ открывает окно настроек — и при загрузке страницы, и при переходе по ссылке на
338
+ уже открытой странице. Хвост убирается из адреса через `history.replaceState`,
339
+ поэтому перезагрузка или «назад» не открывают окно повторно. Это тот адрес, на
340
+ который ведёт кнопка «Изменить выбор cookie» на странице декларации cookie, и
341
+ его же удобно поставить в подвал сайта:
342
+
343
+ ```html
344
+ <a href="#ck-settings">Изменить выбор cookie</a>
345
+ ```
346
+
347
+ ### Placeholders for blocked embeds
348
+
349
+ When the engine holds back an `<iframe>` before consent — a known tracker, or
350
+ any third-party frame in strict mode — the visitor would otherwise see an empty
351
+ hole where a video or a map should be. Since 0.5.7 ConsentKit draws a card in
352
+ its place: the name of the service, the category the embed is waiting for, a
353
+ primary button «Разрешить и показать» that grants **that one category** and
354
+ loads the embed, and a link to the full settings panel.
355
+
356
+ The card is sized from the frame's own `width`/`height` (or its computed size),
357
+ never shorter than 120px, and never wider than its container. It is rendered in
358
+ its own Shadow DOM and takes the banner's theme — the page's font, your accent
359
+ colour and corner radius — so it looks like part of the site rather than part of
360
+ a third-party widget. Strings ship in ru, ro and en; every other language falls
361
+ back to en.
362
+
363
+ The button grants one category through the ordinary consent path: the decision
364
+ is stored and journalled as `method: 'custom'`, the usual `ck:consent` /
365
+ `ck:change` events fire, and consent the visitor had already given to *other*
366
+ categories is preserved rather than overwritten. The frame itself is restored by
367
+ the core's normal revival pass, which is the same code path the panel's switches
368
+ and «Accept all» already use.
369
+
370
+ Frames that are `display:none`, 1×1 tracking pixels, or outside `<body>` are
371
+ left alone, and a frame the site allowed never gets a card at all — an allowed
372
+ frame is never intercepted in the first place. Set `blocking.placeholders:
373
+ false` to restore the pre-0.5.7 behaviour.
374
+
278
375
  ### Infrastructure
279
376
 
280
377
  Some third-party hosts are not a consent decision at all: they are where a site
@@ -304,12 +401,14 @@ All methods are safe to call at any time and never throw.
304
401
  |---|---|---|
305
402
  | `init(config?)` | `CkState` | Idempotent. Restores stored consent, then dispatches `ck:init`. Calling again merges config only |
306
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` |
307
405
  | `getState()` | `CkState` | A fresh object on every call |
308
406
  | `accept('all')` | `CkState` | Grants everything. `method: 'accept_all'` |
309
- | `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 |
310
408
  | `rejectAll()` | `CkState` | Denies every opt-in category. `method: 'reject_all'` |
311
409
  | `withdraw()` | `CkState` | Clears storage and known cookies, sends GCM `denied`, resets to `decided: false` |
312
410
  | `show()` | `void` | Opens the preferences panel |
411
+ | `openSettings()` | `void` | v0.5.7. Opens the preferences panel. Safe before the UI has loaded — the request is remembered and honoured as soon as the banner mounts |
313
412
  | `hide()` | `void` | Closes the panel |
314
413
  | `config` | `CkConfig` | The merged, effective config |
315
414
  | `version` | `string` | Core version string |
@@ -323,6 +422,7 @@ All methods are safe to call at any time and never throw.
323
422
  ts: null, // ISO timestamp
324
423
  policyVersion: '1',
325
424
  categories: { necessary: true, functional: false, analytics: false, marketing: false },
425
+ services: {}, // v0.5.8. Per-service refusals ONLY: { hotjar: false }
326
426
  method: null // 'accept_all' | 'reject_all' | 'custom'
327
427
  }
328
428
  ```
package/npm/core.cjs CHANGED
@@ -25,11 +25,15 @@ 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(); },
31
34
  withdraw: function () { return undecidedState(); },
32
35
  show: function () {},
36
+ openSettings: function () {},
33
37
  hide: function () {},
34
38
  _categories: CATEGORIES.slice(),
35
39
  _isStub: true
package/npm/core.mjs CHANGED
@@ -23,11 +23,18 @@ 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,
29
33
  withdraw,
30
34
  show,
35
+ // v0.5.7: the public name for «open the cookie settings», safe to call
36
+ // before the UI has loaded.
37
+ openSettings,
31
38
  hide,
32
39
  // v0.4.0 (§1.3): merging service overrides into the tracker database is part
33
40
  // of the public surface, so it must be reachable as a named import too.
package/npm/index.cjs CHANGED
@@ -28,11 +28,15 @@ 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(); },
34
37
  withdraw: function () { return undecidedState(); },
35
38
  show: function () {},
39
+ openSettings: function () {},
36
40
  hide: function () {},
37
41
  _categories: CATEGORIES.slice(),
38
42
  _isStub: true
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';
@@ -136,8 +151,17 @@ export interface CkTextsConfig {
136
151
  * What «Learn more» does. Defaults to `'policy'` when `policyUrl` is set and
137
152
  * `'settings'` when it is not. `'policy'` without a usable URL falls back to
138
153
  * `'settings'` rather than rendering a dead link. `'hide'` renders nothing.
154
+ * v0.5.7 adds `'declaration'`: a link to {@link CkTextsConfig.declarationUrl},
155
+ * the cookie declaration page, which likewise degrades to `'settings'`
156
+ * without a usable URL.
157
+ */
158
+ detailsAction?: 'policy' | 'settings' | 'hide' | 'declaration';
159
+ /**
160
+ * v0.5.7. Address of the cookie declaration page, used by
161
+ * `detailsAction: 'declaration'`. `http(s)` only. Supplied by the hosted
162
+ * service; the client only reads it.
139
163
  */
140
- detailsAction?: 'policy' | 'settings' | 'hide';
164
+ declarationUrl?: string;
141
165
  }
142
166
 
143
167
  /** Whether a category is offered in the preferences panel at all. */
@@ -170,6 +194,12 @@ export interface CkBlockingConfig {
170
194
  mode?: 'known' | 'strict';
171
195
  /** Hosts strict mode must never intercept. Suffix match: `p.com` covers `cdn.p.com`. */
172
196
  allow?: string[];
197
+ /**
198
+ * v0.5.7. Draw a card in place of an `<iframe>` held back before consent,
199
+ * offering «Разрешить и показать» for that one category. Default `true`;
200
+ * `false` restores the pre-0.5.7 behaviour (still blocked, just invisible).
201
+ */
202
+ placeholders?: boolean;
173
203
  }
174
204
 
175
205
  /** One declared cookie, shown under its category in the preferences panel. */
@@ -181,6 +211,31 @@ export interface CkCookieTableEntry {
181
211
  expiry?: string;
182
212
  }
183
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
+
184
239
  /** Configuration accepted by `init()`. Every field is optional. */
185
240
  export interface CkConfig {
186
241
  /** Bump to invalidate stored decisions and re-show the banner. Default `'1'`. */
@@ -205,6 +260,17 @@ export interface CkConfig {
205
260
  */
206
261
  hostdb?: Record<string, CkCategory>;
207
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[];
208
274
  }
209
275
 
210
276
  /** Detail payload of `ck:init`. */
@@ -226,6 +292,13 @@ export interface ConsentKitApi {
226
292
  /** Idempotent. Restores stored state, then dispatches `ck:init`. */
227
293
  init(config?: CkConfig): CkState;
228
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;
229
302
  getState(): CkState;
230
303
  /** `accept('all')` grants everything; an object records `method: 'custom'`. */
231
304
  accept(choice?: CkAcceptArg): CkState;
@@ -234,6 +307,12 @@ export interface ConsentKitApi {
234
307
  withdraw(): CkState;
235
308
  /** Dispatches `ck:ui:open-preferences`. */
236
309
  show(): void;
310
+ /**
311
+ * v0.5.7. Opens the preferences panel. Unlike {@link show}, a call made
312
+ * before the UI has loaded is remembered and honoured once the banner
313
+ * mounts, so a footer link clicked during a slow load still works.
314
+ */
315
+ openSettings(): void;
237
316
  /** Dispatches `ck:ui:close`. */
238
317
  hide(): void;
239
318
 
@@ -375,11 +454,14 @@ export { ConsentKit };
375
454
 
376
455
  export declare function init(config?: CkConfig): CkState;
377
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;
378
459
  export declare function getState(): CkState;
379
460
  export declare function accept(choice?: CkAcceptArg): CkState;
380
461
  export declare function rejectAll(): CkState;
381
462
  export declare function withdraw(): CkState;
382
463
  export declare function show(): void;
464
+ export declare function openSettings(): void;
383
465
  export declare function hide(): void;
384
466
  export declare function _extendHostDb(map: Record<string, CkCategory>): number;
385
467
 
@@ -405,11 +487,14 @@ declare module '@ecomconsult/consentkit' {
405
487
  export { ConsentKit };
406
488
  export function init(config?: CkConfig): CkState;
407
489
  export function allowed(category: CkCategory | string): boolean;
490
+ /** v0.5.8 (SPEC V1.12 §3). */
491
+ export function allowedService(id: string): boolean;
408
492
  export function getState(): CkState;
409
493
  export function accept(choice?: CkAcceptArg): CkState;
410
494
  export function rejectAll(): CkState;
411
495
  export function withdraw(): CkState;
412
496
  export function show(): void;
497
+ export function openSettings(): void;
413
498
  export function hide(): void;
414
499
  }
415
500
 
@@ -419,11 +504,14 @@ declare module '@ecomconsult/consentkit/core' {
419
504
  export { ConsentKit };
420
505
  export function init(config?: CkConfig): CkState;
421
506
  export function allowed(category: CkCategory | string): boolean;
507
+ /** v0.5.8 (SPEC V1.12 §3). */
508
+ export function allowedService(id: string): boolean;
422
509
  export function getState(): CkState;
423
510
  export function accept(choice?: CkAcceptArg): CkState;
424
511
  export function rejectAll(): CkState;
425
512
  export function withdraw(): CkState;
426
513
  export function show(): void;
514
+ export function openSettings(): void;
427
515
  export function hide(): void;
428
516
  }
429
517
 
@@ -432,6 +520,8 @@ export interface UseConsentResult {
432
520
  /** Current state. On the server, an undecided snapshot. */
433
521
  state: CkState;
434
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;
435
525
  /** Defaults to `'all'` when called with no argument. */
436
526
  accept(choice?: CkAcceptArg): CkState;
437
527
  rejectAll(): CkState;
package/npm/index.mjs CHANGED
@@ -46,11 +46,18 @@ 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,
52
56
  withdraw,
53
57
  show,
58
+ // v0.5.7: the public name for «open the cookie settings», safe to call
59
+ // before the UI has loaded.
60
+ openSettings,
54
61
  hide,
55
62
  // v0.4.0 (§1.3): merging service overrides into the tracker database is part
56
63
  // of the public surface, so it must be reachable as a named import too.
@@ -28,15 +28,21 @@ export function undecidedState() {
28
28
  */
29
29
  export function createStub() {
30
30
  const stub = {
31
- version: '0.5.6',
31
+ version: '0.5.8',
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(); },
38
43
  withdraw: function () { return undecidedState(); },
39
44
  show: function () {},
45
+ openSettings: function () {},
40
46
  hide: function () {},
41
47
  _categories: CATEGORIES.slice(),
42
48
  // v0.4.0: the stub mirrors the real surface, so consumer code that calls
@@ -50,6 +56,11 @@ export function createStub() {
50
56
  _infra: function () { return []; },
51
57
  _isInfra: function () { return false; },
52
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 []; },
53
64
  _isStub: true
54
65
  };
55
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.6",
3
+ "version": "0.5.8",
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,