@cookieyes/core 0.1.1 → 0.3.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 +421 -47
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +507 -49
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,8 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cookieyes/cookieyes/main/.github/assets/banner-dark.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/cookieyes/cookieyes/main/.github/assets/banner-light.svg" alt="CookieYes consent banner — powered by @cookieyes/core" width="820">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
<h1 align="center">@cookieyes/core</h1>
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
<p align="center"><strong>The headless consent engine powering the CookieYes SDK.</strong></p>
|
|
11
|
+
|
|
12
|
+
<p align="center">Zero UI, zero runtime dependencies — the single source of truth for all consent logic. Every framework adapter imports from this package.</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/@cookieyes/core"><img src="https://img.shields.io/npm/v/@cookieyes/core" alt="npm version"></a>
|
|
16
|
+
<a href="https://www.npmjs.com/package/@cookieyes/core"><img src="https://img.shields.io/npm/dw/@cookieyes/core" alt="npm downloads"></a>
|
|
17
|
+
<a href="https://github.com/cookieyes/cookieyes/actions/workflows/ci.yml"><img src="https://github.com/cookieyes/cookieyes/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
18
|
+
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/@cookieyes/core" alt="license"></a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="#quick-start">Quick start</a> ·
|
|
23
|
+
<a href="#api">API</a> ·
|
|
24
|
+
<a href="#troubleshooting">Troubleshooting</a> ·
|
|
25
|
+
<a href="https://github.com/cookieyes/cookieyes/blob/main/docs/configuration.md">Docs</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Building a UI? Use an adapter
|
|
31
|
+
|
|
32
|
+
`@cookieyes/core` is the headless engine — it has **no components**. Most developers want a
|
|
33
|
+
framework adapter, which bundles core plus ready-made banner/dialog UI:
|
|
34
|
+
|
|
35
|
+
- **[`@cookieyes/react`](https://github.com/cookieyes/cookieyes/tree/main/sdk/react)** — React components + hooks.
|
|
36
|
+
- **[`@cookieyes/nextjs`](https://github.com/cookieyes/cookieyes/tree/main/sdk/nextjs)** — Next.js App Router / Pages Router.
|
|
37
|
+
|
|
38
|
+
Use `@cookieyes/core` directly only for vanilla JS, a custom framework, or your own UI.
|
|
39
|
+
|
|
40
|
+
## Key features
|
|
41
|
+
|
|
42
|
+
- **Headless engine** — all consent logic, no UI, no framework assumptions.
|
|
43
|
+
- **Zero dependencies** — nothing pulled into your bundle but the engine itself.
|
|
44
|
+
- **Offline or self-hosted** — cookie-only, or POST every decision to your own backend.
|
|
45
|
+
- **GDPR & CCPA** — regulation-aware consent state and payloads.
|
|
46
|
+
- **Cookie utilities** — read/write/parse the consent cookie directly if you need to.
|
|
47
|
+
|
|
48
|
+
## Prerequisites
|
|
49
|
+
|
|
50
|
+
- **Node.js** ≥ 20
|
|
51
|
+
- A JavaScript environment with `document`/`window` (browser or SSR with a DOM). No framework required.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
**1. Install**
|
|
6
56
|
|
|
7
57
|
```bash
|
|
8
58
|
npm install @cookieyes/core
|
|
@@ -11,57 +61,66 @@ yarn add @cookieyes/core
|
|
|
11
61
|
bun add @cookieyes/core
|
|
12
62
|
```
|
|
13
63
|
|
|
14
|
-
|
|
64
|
+
**2. Initialise the runtime**
|
|
65
|
+
|
|
66
|
+
> **Which API should I use?** `consentStore.subscribe` is the recommended way to
|
|
67
|
+
> read consent outside React. See the [shared decision tree](../../docs/which-api-should-i-use.md)
|
|
68
|
+
> if you're not sure which API applies to your situation — core also exposes a
|
|
69
|
+
> handful of lower-level options (see [Low-level / advanced API](#low-level--advanced-api))
|
|
70
|
+
> for specific edge cases.
|
|
15
71
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`consentManager` (imperative API).
|
|
72
|
+
`initCookieYes()` (an alias of `getOrCreateConsentRuntime()`) returns a process-wide singleton
|
|
73
|
+
with a `consentStore` (reactive state) and a `consentManager` (imperative API).
|
|
19
74
|
|
|
20
75
|
```ts
|
|
21
|
-
import {
|
|
76
|
+
import { initCookieYes } from "@cookieyes/core";
|
|
22
77
|
|
|
23
|
-
const { consentManager, consentStore } =
|
|
24
|
-
mode: "
|
|
25
|
-
|
|
26
|
-
colorScheme: "system",
|
|
78
|
+
const { consentManager, consentStore } = initCookieYes({
|
|
79
|
+
mode: "cookie-only", // "cookie-only" | "self-hosted"
|
|
80
|
+
regulation: "GDPR", // "GDPR" | "CCPA" | "DEFAULT"
|
|
81
|
+
colorScheme: "system", // "light" | "dark" | "system"
|
|
27
82
|
});
|
|
83
|
+
```
|
|
28
84
|
|
|
29
|
-
|
|
85
|
+
**3. React to consent changes**
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// consentStore.subscribe fires on every state change — category saves,
|
|
89
|
+
// transient preference-dialog toggles, and the dialog opening/closing.
|
|
90
|
+
// That's the right level for "should this script run right now?" checks:
|
|
30
91
|
const unsubscribe = consentStore.subscribe((state) => {
|
|
31
92
|
if (state.has("analytics")) {
|
|
32
93
|
// load analytics scripts (gtag, Mixpanel, …)
|
|
33
94
|
}
|
|
34
|
-
if (state.has("advertisement")) {
|
|
35
|
-
// load ad scripts (Meta Pixel, Google Ads, …)
|
|
36
|
-
}
|
|
37
95
|
});
|
|
38
96
|
|
|
39
|
-
//
|
|
97
|
+
// Only saved preference changes (not transient UI toggles)
|
|
40
98
|
consentStore
|
|
41
99
|
.getState()
|
|
42
100
|
.subscribeToConsentChanges(({ allowedCategories, deniedCategories }) => {
|
|
43
|
-
console.log("Allowed:", allowedCategories);
|
|
44
|
-
console.log("Denied:", deniedCategories);
|
|
101
|
+
console.log("Allowed:", allowedCategories, "Denied:", deniedCategories);
|
|
45
102
|
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**4. Drive it imperatively**
|
|
46
106
|
|
|
47
|
-
|
|
48
|
-
consentStore.getState().has("analytics");
|
|
49
|
-
consentStore.getState().saveConsents("all");
|
|
50
|
-
consentStore.getState().saveConsents("necessary");
|
|
107
|
+
```ts
|
|
108
|
+
consentStore.getState().has("analytics"); // → boolean
|
|
109
|
+
consentStore.getState().saveConsents("all"); // accept all
|
|
110
|
+
consentStore.getState().saveConsents("necessary"); // reject all (necessary only)
|
|
51
111
|
consentStore.getState().setConsent("analytics", true);
|
|
52
112
|
consentManager.showPreferences(); // open the preferences dialog
|
|
53
113
|
consentManager.resetConsent(); // clear + re-prompt
|
|
54
|
-
|
|
55
114
|
unsubscribe();
|
|
56
115
|
```
|
|
57
116
|
|
|
58
117
|
### Self-hosted mode
|
|
59
118
|
|
|
60
|
-
|
|
61
|
-
`
|
|
119
|
+
`mode: "self-hosted"` POSTs a `ConsentPayload` to your endpoint on every decision. Provide
|
|
120
|
+
either an `apiUrl` or a custom `backend` adapter for full control:
|
|
62
121
|
|
|
63
122
|
```ts
|
|
64
|
-
|
|
123
|
+
initCookieYes({
|
|
65
124
|
mode: "self-hosted",
|
|
66
125
|
backend: {
|
|
67
126
|
async persist(payload) {
|
|
@@ -75,58 +134,373 @@ getOrCreateConsentRuntime({
|
|
|
75
134
|
});
|
|
76
135
|
```
|
|
77
136
|
|
|
137
|
+
### Deprecated: `mode: "offline"`
|
|
138
|
+
|
|
139
|
+
`"offline"` was renamed to `"cookie-only"` — same behavior, clearer name. It
|
|
140
|
+
still works today and logs a one-time console warning, and will be removed
|
|
141
|
+
3 releases from now.
|
|
142
|
+
|
|
143
|
+
```diff
|
|
144
|
+
getOrCreateConsentRuntime({
|
|
145
|
+
- mode: "offline",
|
|
146
|
+
+ mode: "cookie-only",
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
78
150
|
## API
|
|
79
151
|
|
|
80
|
-
### `getOrCreateConsentRuntime(
|
|
152
|
+
### `initCookieYes(config)` / `getOrCreateConsentRuntime(config)`
|
|
81
153
|
|
|
82
|
-
|
|
83
|
-
`resetConsentRuntime()` to clear it,
|
|
154
|
+
Both accept the canonical `CookieYesConfig` and return `{ consentManager, consentStore }` (a
|
|
155
|
+
singleton — call `resetConsentRuntime()` to clear it, mainly for tests). `initCookieYes` is an
|
|
156
|
+
alias provided so one setup name reads across every package. Every option is documented once in
|
|
157
|
+
**[Configuration](https://github.com/cookieyes/cookieyes/blob/main/docs/configuration.md)**.
|
|
158
|
+
Migrating off the deprecated `overrides.regulation` / `backendURL` keys? See the
|
|
159
|
+
**[migration guide](https://github.com/cookieyes/cookieyes/blob/main/docs/migration/builder-to-config.md)**.
|
|
84
160
|
|
|
85
|
-
**`
|
|
161
|
+
**`config`** (`CookieYesConfig`):
|
|
86
162
|
|
|
87
163
|
| Option | Type | Notes |
|
|
88
164
|
|--------|------|-------|
|
|
89
|
-
| `mode` | `"
|
|
90
|
-
| `
|
|
165
|
+
| `mode` | `"cookie-only" \| "self-hosted"` | **Required.** See [Deprecated](#deprecated-mode-offline) for the retired `"offline"` name. |
|
|
166
|
+
| `regulation` | `"GDPR" \| "CCPA" \| "DEFAULT"` | Force the applicable regulation. (The deprecated `overrides.regulation` alias still works.) |
|
|
167
|
+
| `apiUrl` | `string` | Self-hosted: endpoint the payload is POSTed to. (The deprecated `backendURL` alias still works.) |
|
|
91
168
|
| `backend` | `ConsentBackend` | Self-hosted: custom `persist(payload)` adapter. |
|
|
92
169
|
| `apiKey` | `string` | Optional auth key. |
|
|
93
|
-
| `overrides.regulation` | `"GDPR" \| "CCPA" \| "DEFAULT"` | Force the applicable regulation. |
|
|
94
170
|
| `colorScheme` | `"light" \| "dark" \| "system"` | |
|
|
95
171
|
| `theme` | `ThemeConfig` | Color / spacing tokens. |
|
|
96
172
|
| `i18n` | `I18nConfig` | Translation messages / locale. |
|
|
97
173
|
| `networkBlocker` | `NetworkBlockerConfig` | Block network requests by category. |
|
|
98
174
|
| `reloadOnRevoke` | `boolean` | Reload the page when consent is revoked. |
|
|
99
|
-
| `onConsentReady` / `onConsentUpdate` | `(state) => void` |
|
|
175
|
+
| `onConsentReady` / `onConsentUpdate` | `(state) => void` | Low-level lifecycle callbacks — see below. |
|
|
100
176
|
|
|
101
177
|
**`consentStore`** — `subscribe(listener)` and `getState()`. State
|
|
102
178
|
(`ConsentStoreState`) includes `consentId`, `hasActed`, `categories`,
|
|
103
179
|
`regulation`, `lastRenewed`, `activeUI`, plus the methods `has()`,
|
|
104
|
-
`saveConsents()`, `setConsent()`, and `subscribeToConsentChanges()
|
|
180
|
+
`saveConsents()`, `setConsent()`, and the low-level `subscribeToConsentChanges()`
|
|
181
|
+
(below).
|
|
105
182
|
|
|
106
183
|
### `createConsentManager(config)` (low-level)
|
|
107
184
|
|
|
108
|
-
The underlying manager, if you want to bypass the store. Returns a
|
|
109
|
-
`
|
|
185
|
+
The underlying manager, if you want to bypass the store. Returns a `ConsentManager` with state
|
|
186
|
+
(`consentId`, `hasActed`, `categories`, `regulation`, `lastRenewed`, `isPreferencesOpen`) and
|
|
187
|
+
methods (`acceptAll()`, `rejectAll()`, `acceptSelected(cats)`, `updateCategory(cat, val)`,
|
|
188
|
+
`savePreferences()`, `resetConsent()`, `showPreferences()`, `hidePreferences()`, `subscribe(fn)`,
|
|
189
|
+
`registerScript(entry)`).
|
|
190
|
+
|
|
191
|
+
> The applicable regulation comes from your top-level `regulation` config (the deprecated
|
|
192
|
+
> `overrides.regulation` alias still works) and defaults to `"DEFAULT"`. The core engine does not
|
|
193
|
+
> perform IP-based geo-detection.
|
|
194
|
+
|
|
195
|
+
## Reacting to consent changes
|
|
196
|
+
|
|
197
|
+
To run your own code when a visitor grants or withdraws consent, use
|
|
198
|
+
`consentStore.on(type, listener, options?)`:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const { consentStore } = initCookieYes({ mode: "cookie-only" });
|
|
202
|
+
|
|
203
|
+
// "change" fires only when a category actually differs — the right place to
|
|
204
|
+
// (re)load a script, so a re-confirm of the same choices doesn't run it again.
|
|
205
|
+
const off = consentStore.on("change", ({ changedCategories }) => {
|
|
206
|
+
if (changedCategories.includes("analytics")) loadAnalytics();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// "save" fires on every save, even an unchanged re-confirm.
|
|
210
|
+
consentStore.on("save", () => toast("Preferences saved"));
|
|
211
|
+
|
|
212
|
+
off(); // stop listening when you're done
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- The listener fires **once immediately** with the current state
|
|
216
|
+
(`isInitial: true`), so a late listener isn't blind to earlier choices.
|
|
217
|
+
- Pass `{ category: "analytics" }` to only hear about one category.
|
|
218
|
+
- Payload: `{ categories, changedCategories, isInitial }`.
|
|
219
|
+
|
|
220
|
+
This is the recommended way to react to consent. The paths below still work but
|
|
221
|
+
aren't the primary one.
|
|
222
|
+
|
|
223
|
+
## Translations & language
|
|
224
|
+
|
|
225
|
+
For a framework-less custom UI, `consentStore` carries the active language and
|
|
226
|
+
lets you switch it live (no reload):
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const { consentStore } = initCookieYes({
|
|
230
|
+
mode: "cookie-only",
|
|
231
|
+
i18n: { messages: { fr } }, // languages you support; each may be partial
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
consentStore.translations.acceptAll; // text for the active language
|
|
235
|
+
consentStore.getLanguageInfo(); // { language, direction, languages }
|
|
236
|
+
|
|
237
|
+
consentStore.subscribe(() => render()); // re-render on consent OR language change
|
|
238
|
+
await consentStore.setLanguage("fr"); // switch live; loads via i18n.loadLanguage if needed
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Missing text falls back to English; custom categories translate by id (see the
|
|
242
|
+
[configuration guide](https://github.com/cookieyes/cookieyes/blob/main/docs/configuration.md)).
|
|
243
|
+
In React, use the `useTranslations()` / `useLanguage()` hooks instead.
|
|
244
|
+
|
|
245
|
+
## Low-level / advanced API
|
|
110
246
|
|
|
111
|
-
|
|
112
|
-
|
|
247
|
+
You shouldn't need these for a typical integration — each exists for a
|
|
248
|
+
specific narrower situation than `consentStore.on` / `consentStore.subscribe`:
|
|
113
249
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
`
|
|
250
|
+
| API | Use when |
|
|
251
|
+
|---|---|
|
|
252
|
+
| `subscribeToConsentChanges(listener)` (on `consentStore.getState()`) | Predates `consentStore.on`; fires on saved changes but without the `change`/`save` split, the `isInitial` flag, or per-category filtering. Prefer `on`. |
|
|
253
|
+
| `onConsentReady` (config option) | You need a one-time callback right after the initial state is known — e.g. conditionally loading analytics on first load — rather than an ongoing subscription. |
|
|
254
|
+
| `onConsentUpdate` (config option) | A saved-changes callback registered once at config time instead of dynamically after mount. Prefer `consentStore.on` unless you specifically need a config-time callback. |
|
|
255
|
+
| `createConsentManager(config)` | Bypasses `consentStore` entirely for direct access to the manager: `acceptAll()`, `rejectAll()`, `acceptSelected(cats)`, `updateCategory(cat, val)`, `savePreferences()`, `resetConsent()`, `showPreferences()`, `hidePreferences()`, `subscribe(fn)`, `registerScript(entry)`. `config` (`ConsentConfig`) accepts `regulation`, `colorScheme`, `theme`, `apiUrl`, `apiKey`, `backend`, `reloadOnRevoke`, `onConsentReady`, `onConsentUpdate`. |
|
|
256
|
+
| `parseCookie` / `serializeCookie` | Reading or writing the raw `cookieyes-consent` cookie directly — e.g. in a Next.js Server Component or route handler, where no live runtime or React hooks are available. |
|
|
117
257
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
258
|
+
## Stopping tracking when consent is withdrawn
|
|
259
|
+
|
|
260
|
+
When a visitor revokes consent, the SDK stops tracking **without reloading the
|
|
261
|
+
page** — nothing they were doing (form input, scroll position, an open dialog)
|
|
262
|
+
is lost. There are three layers:
|
|
263
|
+
|
|
264
|
+
1. **Network blocking** (`networkBlocker` / `blockNetwork`) — intercepts
|
|
265
|
+
`fetch`, `XMLHttpRequest`, **and `navigator.sendBeacon`** to blocked domains,
|
|
266
|
+
in real time, for as long as the page is open. `sendBeacon` matters because
|
|
267
|
+
GA4/Meta use it for exit/unload tracking that fetch/XHR interception misses.
|
|
268
|
+
2. **Integration stop-handlers** (`integrations`) — call a vendor's own
|
|
269
|
+
documented "stop" API on revoke, and resume it on re-accept:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
getOrCreateConsentRuntime({
|
|
273
|
+
mode: "cookie-only",
|
|
274
|
+
integrations: [
|
|
275
|
+
{ vendor: "meta" }, // fbq('consent','revoke'|'grant')
|
|
276
|
+
],
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
> **Google Analytics & Tag Manager are handled automatically** — you don't
|
|
281
|
+
> list them here. The SDK broadcasts Google Consent Mode v2 whenever a
|
|
282
|
+
> `dataLayer` is present (see [Google Consent Mode](#google-consent-mode-v2)
|
|
283
|
+
> below). You still set the **deny-by-default** state in your gtag snippet.
|
|
284
|
+
3. **Your own scripts** (`customStopHandlers`) — for anything without a built-in
|
|
285
|
+
integration. Provide a clean `stop()`/`resume()`, or register it as
|
|
286
|
+
reload-only so revoking it shows the reload notice rather than silently
|
|
287
|
+
continuing to track:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
customStopHandlers: [
|
|
291
|
+
{ id: "my-tool", category: "analytics", stop: () => window.myTool?.disable() },
|
|
292
|
+
{ id: "legacy-widget", category: "advertisement", needsReload: true },
|
|
293
|
+
]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Vendor audit — which stop cleanly, which need a reload
|
|
297
|
+
|
|
298
|
+
| Vendor | Runtime stop | How |
|
|
299
|
+
|--------|-------------|-----|
|
|
300
|
+
| **Google Analytics 4 / Tag Manager** | ✅ automatic | Consent Mode v2 broadcast — no `integrations` entry needed (see [below](#google-consent-mode-v2)). |
|
|
301
|
+
| **Meta Pixel** | ✅ clean | `fbq('consent', 'revoke')` / `'grant'` |
|
|
302
|
+
| TikTok Pixel | ⚠️ reload | No runtime stop we could confidently verify; modelled as reload-only. |
|
|
303
|
+
| LinkedIn Insight Tag | ⚠️ reload | No documented runtime opt-out after load. |
|
|
304
|
+
| Hotjar | ⚠️ reload | No documented "stop after load"; gate before load instead. |
|
|
305
|
+
| Segment (analytics.js) | ⚠️ reload | No documented runtime "stop all"; gate `analytics.load()`. |
|
|
306
|
+
|
|
307
|
+
"Reload" vendors surface the reload notice (below) on a genuine revoke — the SDK
|
|
308
|
+
never continues tracking them silently. Any of them can be upgraded to a clean
|
|
309
|
+
stop later (in `resolveBuiltInIntegration`) once a real runtime API is confirmed.
|
|
310
|
+
|
|
311
|
+
### Reload notice
|
|
312
|
+
|
|
313
|
+
If a revoked tool has no clean runtime stop, the manager computes
|
|
314
|
+
`manager.reloadNotice` (`{ required, reasons }`) automatically on revoke, with
|
|
315
|
+
`manager.dismissReloadNotice()` to clear it. The *state* is automatic; showing
|
|
316
|
+
it is up to you.
|
|
317
|
+
|
|
318
|
+
**If you configure any reload-only tool, surface this state to the visitor** —
|
|
319
|
+
otherwise a revoke that needs a reload is silent and that tool keeps running.
|
|
320
|
+
In React that means rendering the built-in `<ReloadNotice />` (dismissible,
|
|
321
|
+
`role="alert"`, wording via translations); it never reloads on its own. Outside
|
|
322
|
+
React, read `manager.reloadNotice.required` and render your own prompt.
|
|
323
|
+
|
|
324
|
+
### `reloadOnRevoke` (legacy, off by default)
|
|
325
|
+
|
|
326
|
+
`reloadOnRevoke` performs a full page reload on revoke. It is **off by default**
|
|
327
|
+
— the clean stop-handlers above are the safe path. Turn it on only if you
|
|
328
|
+
explicitly want the old behavior; note it erases whatever the visitor was doing.
|
|
121
329
|
|
|
122
330
|
## Consent categories
|
|
123
331
|
|
|
332
|
+
By default the SDK ships the familiar five:
|
|
333
|
+
|
|
124
334
|
`necessary` (always on), `functional`, `analytics`, `performance`, `advertisement`.
|
|
125
335
|
|
|
336
|
+
Configure nothing and you get exactly these, unchanged.
|
|
337
|
+
|
|
338
|
+
### Defining your own categories
|
|
339
|
+
|
|
340
|
+
Pass a `categories` array to use your own taxonomy — rename, add, remove, or
|
|
341
|
+
restructure. Each entry is a [`CategoryDef`](./src/categories.ts):
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
getOrCreateConsentRuntime({
|
|
345
|
+
mode: "cookie-only",
|
|
346
|
+
categories: [
|
|
347
|
+
{ id: "essential", required: true, label: "Strictly Necessary" },
|
|
348
|
+
{ id: "marketing", label: "Marketing & Ads",
|
|
349
|
+
gcm: ["ad_storage", "ad_user_data", "ad_personalization"] },
|
|
350
|
+
{ id: "insights", label: "Product Insights",
|
|
351
|
+
gcm: ["analytics_storage"] },
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- **`id`** — the stable key stored in the cookie and used everywhere (banner,
|
|
357
|
+
preferences UI, read APIs, `gate`/integration category names, events). Pick it
|
|
358
|
+
once and keep it stable; renaming an `id` is a taxonomy change (see below).
|
|
359
|
+
- **`required`** — the always-on, non-optional category. **Mark it explicitly** —
|
|
360
|
+
it is *never* inferred from the name `necessary`, so you can rename it freely.
|
|
361
|
+
At least one category must be `required: true`.
|
|
362
|
+
- **`label` / `description`** — shown in the preferences UI. For the five
|
|
363
|
+
built-in ids these fall back to the translation strings if omitted; for a
|
|
364
|
+
custom id with no `label`, the UI falls back to the `id` itself.
|
|
365
|
+
- **`gcm`** — which Google Consent Mode signals this category governs (see
|
|
366
|
+
[below](#google-consent-mode-v2)).
|
|
367
|
+
|
|
368
|
+
**Id rules.** An `id` must be a non-empty string, unique within the list, and
|
|
369
|
+
must not contain `,` or `:` or be one of the cookie's reserved keys (`consentid`,
|
|
370
|
+
`consent`, `action`, `tax`, `lastRenewedDate`) — those would corrupt the stored
|
|
371
|
+
cookie. Otherwise any string is fine (spaces and unicode are OK).
|
|
372
|
+
|
|
373
|
+
**Invalid config is safe.** If the array is empty, has duplicate/reserved/invalid
|
|
374
|
+
ids, or has no `required` category, the SDK logs a `console.warn` and falls back
|
|
375
|
+
to the built-in five rather than leaving you a broken or unprotected banner.
|
|
376
|
+
|
|
377
|
+
### Changing your taxonomy later (upgrade behaviour)
|
|
378
|
+
|
|
379
|
+
Every stored consent record is stamped with a **taxonomy signature** (a hash of
|
|
380
|
+
the ids, `required` flags, and `gcm` mappings — visible as `taxonomyHash` on the
|
|
381
|
+
snapshot and `tax:` in the cookie). This lets the SDK tell what a returning
|
|
382
|
+
visitor actually agreed to.
|
|
383
|
+
|
|
384
|
+
- **Signature unchanged** → the returning visitor's stored consent is reused
|
|
385
|
+
silently. No re-prompt.
|
|
386
|
+
- **Signature changed** (you renamed/added/removed a category or changed a `gcm`
|
|
387
|
+
mapping) → the SDK **re-requests consent**: it discards the stale record and
|
|
388
|
+
shows the banner again, so the visitor consents against the taxonomy that's
|
|
389
|
+
actually in effect. This is the one documented outcome for a taxonomy change.
|
|
390
|
+
- **Legacy cookies** written before this feature (no `tax:` stamp) are treated
|
|
391
|
+
as the built-in five: if you're still on the default taxonomy they're honoured
|
|
392
|
+
as-is (returning visitors are **never** silently reset by upgrading the SDK);
|
|
393
|
+
if you've since moved to a custom taxonomy they re-request like any other
|
|
394
|
+
change.
|
|
395
|
+
|
|
396
|
+
## Google Consent Mode v2
|
|
397
|
+
|
|
398
|
+
If a Google `dataLayer` is present on the page, the SDK **broadcasts** all seven
|
|
399
|
+
Consent Mode v2 signals — on load and on every consent change — for every
|
|
400
|
+
visitor. This is what governs Google Analytics 4 and Tag Manager; you do **not**
|
|
401
|
+
register them under `integrations`.
|
|
402
|
+
|
|
403
|
+
Each signal is `granted` when any granted category maps to it (via its `gcm`
|
|
404
|
+
field), otherwise `denied`. `security_storage` is always `granted`. The built-in
|
|
405
|
+
five map like this:
|
|
406
|
+
|
|
407
|
+
| Category | GCM signals |
|
|
408
|
+
|----------|-------------|
|
|
409
|
+
| `necessary` | *(none — `security_storage` is always granted)* |
|
|
410
|
+
| `functional` | `functionality_storage`, `personalization_storage` |
|
|
411
|
+
| `analytics` | `analytics_storage` |
|
|
412
|
+
| `performance` | *(none)* |
|
|
413
|
+
| `advertisement` | `ad_storage`, `ad_user_data`, `ad_personalization` |
|
|
414
|
+
|
|
415
|
+
Under the hood the broadcast does the equivalent of:
|
|
416
|
+
|
|
417
|
+
```js
|
|
418
|
+
dataLayer.push(["consent", "update", {
|
|
419
|
+
ad_storage: "denied",
|
|
420
|
+
ad_user_data: "denied",
|
|
421
|
+
ad_personalization: "denied",
|
|
422
|
+
analytics_storage: "granted",
|
|
423
|
+
functionality_storage: "granted",
|
|
424
|
+
personalization_storage: "granted",
|
|
425
|
+
security_storage: "granted",
|
|
426
|
+
}]);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
> **You still own the default.** Consent Mode requires a **deny-by-default**
|
|
430
|
+
> state set *before* your Google tags load — the SDK can't set it because it
|
|
431
|
+
> doesn't control that load order. Put it in your gtag bootstrap snippet:
|
|
432
|
+
>
|
|
433
|
+
> ```js
|
|
434
|
+
> gtag('consent', 'default', {
|
|
435
|
+
> ad_storage: 'denied',
|
|
436
|
+
> ad_user_data: 'denied',
|
|
437
|
+
> ad_personalization: 'denied',
|
|
438
|
+
> analytics_storage: 'denied',
|
|
439
|
+
> functionality_storage: 'denied',
|
|
440
|
+
> personalization_storage: 'denied',
|
|
441
|
+
> security_storage: 'granted',
|
|
442
|
+
> wait_for_update: 500,
|
|
443
|
+
> });
|
|
444
|
+
> ```
|
|
445
|
+
>
|
|
446
|
+
> Set all seven signals explicitly: deny the six consent-gated ones and grant
|
|
447
|
+
> `security_storage` (it's strictly necessary). Leaving any signal unspecified
|
|
448
|
+
> makes Google treat it as granted until the SDK's `update` fires, leaking it for
|
|
449
|
+
> that first moment. The SDK owns the `update`; you own the `default`.
|
|
450
|
+
|
|
451
|
+
To wire Consent Mode to a **custom** taxonomy, put the `gcm` field on whichever
|
|
452
|
+
of your categories should drive each signal — see the example under
|
|
453
|
+
[Defining your own categories](#defining-your-own-categories) (the `marketing`
|
|
454
|
+
and `insights` entries carry `gcm` mappings). A signal no category maps to
|
|
455
|
+
simply stays `denied`.
|
|
456
|
+
|
|
126
457
|
## Cookie
|
|
127
458
|
|
|
128
459
|
Consent is persisted in the `cookieyes-consent` cookie (`SameSite=Lax`, `path=/`).
|
|
129
|
-
|
|
460
|
+
It stores each category id as `id:yes|no`, plus a `tax:` stamp recording the
|
|
461
|
+
[taxonomy signature](#changing-your-taxonomy-later-upgrade-behaviour) that was in
|
|
462
|
+
effect when the consent was recorded. Use `parseCookie` / `serializeCookie` from
|
|
463
|
+
this package to read or write it directly.
|
|
464
|
+
|
|
465
|
+
## Troubleshooting
|
|
466
|
+
|
|
467
|
+
**The runtime isn't initialising (or hooks/consumers see no state).**
|
|
468
|
+
`initCookieYes()` returns a **singleton** — the first call wins, later calls return the same
|
|
469
|
+
instance. Call it once at startup before anything reads consent. In tests, call
|
|
470
|
+
`resetConsentRuntime()` between cases or state leaks across them.
|
|
471
|
+
|
|
472
|
+
**Mode / config type errors.**
|
|
473
|
+
`CookieYesConfig` is a discriminated union on `mode`. Backend keys (`apiUrl`, `apiKey`,
|
|
474
|
+
`backend`) are only valid with `mode: "self-hosted"` — supplying them under `mode: "cookie-only"`
|
|
475
|
+
is a compile error. `mode: "self-hosted"` needs either `apiUrl` or a `backend` adapter.
|
|
476
|
+
|
|
477
|
+
**Consent doesn't persist between reloads.**
|
|
478
|
+
State lives in the `cookieyes-consent` cookie. Confirm it isn't blocked by a browser privacy
|
|
479
|
+
setting or extension, that you're on a `document`-bearing environment (not a bare Node worker),
|
|
480
|
+
and that you aren't calling `resetConsentRuntime()` on every load.
|
|
481
|
+
|
|
482
|
+
Still stuck? [Open an issue](https://github.com/cookieyes/cookieyes/issues).
|
|
483
|
+
|
|
484
|
+
## Community & support
|
|
485
|
+
|
|
486
|
+
- [Open an issue](https://github.com/cookieyes/cookieyes/issues) — bug reports and feature requests.
|
|
487
|
+
- Email — [support@cookieyes.com](mailto:support@cookieyes.com).
|
|
488
|
+
- [Full documentation](https://github.com/cookieyes/cookieyes/blob/main/docs/configuration.md).
|
|
489
|
+
|
|
490
|
+
_(A community chat channel is on the roadmap.)_
|
|
491
|
+
|
|
492
|
+
## Contributing
|
|
493
|
+
|
|
494
|
+
Contributions are welcome. Read our
|
|
495
|
+
[Contributing Guidelines](https://github.com/cookieyes/cookieyes/blob/main/CONTRIBUTING.md) and
|
|
496
|
+
[Code of Conduct](https://github.com/cookieyes/cookieyes/blob/main/CODE_OF_CONDUCT.md), then open
|
|
497
|
+
a pull request.
|
|
498
|
+
|
|
499
|
+
### Security
|
|
500
|
+
|
|
501
|
+
Found a vulnerability? **Do not open a public issue** — follow our
|
|
502
|
+
[Security Policy](https://github.com/cookieyes/cookieyes/blob/main/SECURITY.md) and use GitHub's
|
|
503
|
+
private vulnerability reporting.
|
|
130
504
|
|
|
131
505
|
## License
|
|
132
506
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e="cookieyes-consent",t=["necessary","functional","analytics","performance","advertisement"];function n(e){const t={},n=e.split(",");for(const e of n){const n=e.indexOf(":");if(-1===n)continue;const r=e.slice(0,n).trim(),s=e.slice(n+1).trim();(r in t||o.has(r))&&(t[r]=s)}return t}const o=new Set(["consentid","consent","action","necessary","functional","analytics","performance","advertisement","lastRenewedDate"]);function r(e){const n=[`consentid:${e.consentId}`,"consent:"+(e.hasActed?"yes":"no"),"action:"+(e.hasActed?"yes":"no")];for(const o of t)n.push(`${o}:${e.categories[o]?"yes":"no"}`);return n.push(`lastRenewedDate:${e.lastRenewed??Date.now()}`),n.join(",")}function s(t){if("undefined"==typeof document)return;const n=encodeURIComponent(r(t));document.cookie=`${e}=${n}; max-age=31536000; path=/; SameSite=Lax`}function a(){const e=new Uint8Array(32);if("undefined"!=typeof crypto&&crypto.getRandomValues)crypto.getRandomValues(e);else for(let t=0;t<e.length;t++)e[t]=Math.floor(256*Math.random());return btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"").slice(0,44)}function c(e,t){const n="CCPA"===t;return{consentId:e,hasActed:!1,categories:{necessary:!0,functional:n,analytics:n,performance:n,advertisement:n},regulation:t}}const i={bannerTitle:"We value your privacy",bannerDescription:"We use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking “Accept All”, you consent to our use of cookies.",acceptAll:"Accept All",rejectAll:"Reject All",managePreferences:"Customise",savePreferences:"Save My Preferences",doNotSell:"Do Not Sell or Share My Personal Information",ccpaDescription:"This website or its third-party tools process personal data. You can opt out of the sale of your personal information by clicking on the “Do Not Sell or Share My Personal Information” link.",accept:"Accept",poweredBy:"Powered by CookieYes",preferencesTitle:"Customise Consent Preferences",preferencesIntro:"We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.",categories:{necessary:{label:"Necessary",description:"Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data."},functional:{label:"Functional",description:"Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features."},analytics:{label:"Analytics",description:"Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc."},performance:{label:"Performance",description:"Performance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors."},advertisement:{label:"Advertisement",description:"Advertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns."}},optOut:{title:"Opt-out Preferences",description:'We use third-party cookies that help us analyse how you use this website, store your preferences, and provide the content and advertisements that are relevant to you. However, you can opt out of these cookies by checking "Do Not Sell or Share My Personal Information" and clicking the "Save My Preferences" button. Once you opt out, you can opt in again at any time by unchecking "Do Not Sell or Share My Personal Information" and clicking the "Save My Preferences" button.',cancel:"Cancel",successText:"Your opt-out preference has been honored.",successCountdown:"Banner closes automatically in {seconds} s..."}};const l=new Map,d=new Set,u=new Map;function f(e){if("undefined"!=typeof document)for(const[t,n]of l){const o=!0===e[n.category],r=n.strategy??"afterConsent";if(o){if("lazyOnce"===r&&d.has(t))continue;u.has(t)||p(t,n)}else h(t)}}function p(e,t){if(document.getElementById(e))return;const n=document.createElement("script");n.id=e,n.src=t.src,n.async=!0,t.onLoad&&n.addEventListener("load",t.onLoad,{once:!0}),document.head.appendChild(n),u.set(e,n),d.add(e)}function h(e){const t=u.get(e);t&&(t.remove(),u.delete(e))}function y(e){return{consentId:e.consentId,categories:e.categories,regulation:e.regulation,domain:"undefined"!=typeof window?window.location.hostname:"unknown"}}const g=["necessary","functional","analytics","performance","advertisement"];function m(t){const o=new Set;let r,i,d=!1;const u=function(){if("undefined"==typeof document)return null;const t=document.cookie.split(";");for(const o of t){const t=o.trim(),r=t.indexOf("=");if(-1!==r&&t.slice(0,r).trim()===e){const e=t.slice(r+1).trim();return n(decodeURIComponent(e))}}return null}(),p=t.regulation??"DEFAULT";if(u)r=function(e,t){const n={necessary:!0,functional:"yes"===e.functional,analytics:"yes"===e.analytics,performance:"yes"===e.performance,advertisement:"yes"===e.advertisement};return{consentId:e.consentid??a(),hasActed:"yes"===e.action,categories:n,regulation:t,lastRenewed:e.lastRenewedDate?Number(e.lastRenewedDate):void 0}}(u,p);else{const e=a();r=c(e,p),"CCPA"===r.regulation&&s(r)}function h(){const e={consentId:r.consentId,hasActed:r.hasActed,categories:{...r.categories},regulation:r.regulation,lastRenewed:r.lastRenewed};for(const t of o)t(e);f(r.categories)}function m(){if(r={...r,hasActed:!0,lastRenewed:Date.now()},s(r),t.backend)try{Promise.resolve(t.backend.persist(y(r))).catch(()=>{})}catch{}else t.apiUrl&&async function(e,t,n){const o=y(n),r={"Content-Type":"application/json"};t&&(r.Authorization=`Bearer ${t}`);try{await fetch(e,{method:"POST",headers:r,body:JSON.stringify(o),keepalive:!0})}catch{}}(t.apiUrl,t.apiKey,r);let e=!1;for(const t of g)if(i[t]&&!r.categories[t]){e=!0;break}i={...r.categories},h(),t.onConsentUpdate?.(r),e&&t.reloadOnRevoke&&"undefined"!=typeof window&&window.location.reload()}i={...r.categories},Promise.resolve().then(()=>t.onConsentReady?.(r));const w={get consentId(){return r.consentId},get hasActed(){return r.hasActed},get categories(){return{...r.categories}},get regulation(){return r.regulation},get lastRenewed(){return r.lastRenewed},get isPreferencesOpen(){return d},acceptAll(){r={...r,categories:{necessary:!0,functional:!0,analytics:!0,performance:!0,advertisement:!0}},d=!1,m()},rejectAll(){r={...r,categories:{necessary:!0,functional:!1,analytics:!1,performance:!1,advertisement:!1}},d=!1,m()},acceptSelected(e){const t={...r.categories};for(const n of g)"necessary"!==n&&(t[n]=e.includes(n));r={...r,categories:t},d=!1,m()},updateCategory(e,t){"necessary"!==e&&(r={...r,categories:{...r.categories,[e]:t}},h())},savePreferences(){d=!1,m()},resetConsent(){"undefined"!=typeof document&&(document.cookie=`${e}=; max-age=0; path=/`);const t=a();r=c(t,r.regulation),d=!1,h()},showPreferences(){d=!0,h()},hidePreferences(){d=!1,h()},subscribe:e=>(o.add(e),()=>o.delete(e)),registerScript(e){!function(e){l.set(e.id,e)}(e),f(r.categories)}};return f(r.categories),w}function w(e,t,n,o){let r;try{const e="undefined"!=typeof window?window.location.href:"http://localhost";r=new URL(t,e)}catch{return null}const s=r.hostname.toLowerCase(),a=r.pathname+r.search,c=n.toUpperCase();for(const t of e){const e=t.domain.toLowerCase();if((s===e||s.endsWith("."+e))&&((!t.pathIncludes||a.includes(t.pathIncludes))&&(!t.methods||t.methods.map(e=>e.toUpperCase()).includes(c))&&!o(t.category)))return t}return null}let k=null;function v(e,t){if("undefined"==typeof window)return()=>{};if(k)return()=>{};if(!e.rules.length)return()=>{};const n={originalFetch:window.fetch,originalXhrOpen:XMLHttpRequest.prototype.open,originalXhrSend:XMLHttpRequest.prototype.send};k=n;const o=!1!==e.logBlockedRequests;function r(t){o&&console.warn(`[cookieyes] blocked ${t.method} ${t.url} (rule "${t.rule.id}", category: ${t.rule.category})`),e.onRequestBlocked?.(t)}return window.fetch=function(o,s){let a="",c=s?.method??"GET";"string"==typeof o?a=o:o instanceof URL?a=o.toString():(a=o.url,c=s?.method??o.method);const i=w(e.rules,a,c,t);return i?(r({rule:i,url:a,method:c}),Promise.reject(new TypeError(`Blocked by consent (rule: ${i.id}, category: ${i.category})`))):n.originalFetch.call(window,o,s)},XMLHttpRequest.prototype.open=function(e,t,...o){return this._cyUrl=t.toString(),this._cyMethod=e,n.originalXhrOpen.apply(this,[e,t,...o])},XMLHttpRequest.prototype.send=function(o){const s=this._cyUrl??"",a=this._cyMethod??"GET",c=w(e.rules,s,a,t);return c?(r({rule:c,url:s,method:a}),void this.abort()):n.originalXhrSend.call(this,o)},b}function b(){k&&("undefined"!=typeof window&&(window.fetch=k.originalFetch,XMLHttpRequest.prototype.open=k.originalXhrOpen,XMLHttpRequest.prototype.send=k.originalXhrSend),k=null)}let C=null;exports.createConsentManager=m,exports.defaultTranslations=i,exports.generateConsentId=a,exports.getOrCreateConsentRuntime=function(e){if(C)return C;const t=new Set,n=e.onConsentUpdate,o={};"self-hosted"===e.mode&&(e.backend?o.backend=e.backend:e.backendURL&&(o.apiUrl=e.backendURL)),e.apiKey&&(o.apiKey=e.apiKey),e.overrides?.regulation&&(o.regulation=e.overrides.regulation),e.colorScheme&&(o.colorScheme=e.colorScheme),e.theme&&(o.theme=e.theme),e.reloadOnRevoke&&(o.reloadOnRevoke=e.reloadOnRevoke),e.onConsentReady&&(o.onConsentReady=e.onConsentReady),o.onConsentUpdate=e=>{n?.(e);const o=function(e){const t=[],n=[];for(const o of Object.keys(e))e[o]?t.push(o):n.push(o);return{allowedCategories:t,deniedCategories:n}}(e.categories);for(const e of t)e(o)};const r=m(o);function s(){const e=r.categories;return{consentId:r.consentId,hasActed:r.hasActed,categories:e,consents:e,regulation:r.regulation,lastRenewed:r.lastRenewed,activeUI:r.isPreferencesOpen?"dialog":r.hasActed?null:"banner",has:e=>r.categories[e]??!1,saveConsents:async e=>{"all"===e?r.acceptAll():"necessary"===e?r.rejectAll():r.acceptSelected(e)},setConsent:(e,t)=>r.updateCategory(e,t),subscribeToConsentChanges:e=>(t.add(e),()=>{t.delete(e)})}}const a={subscribe:e=>r.subscribe(()=>e(s())),getState:s};return e.networkBlocker&&e.networkBlocker.rules.length>0&&v(e.networkBlocker,e=>!0===r.categories[e]),C={consentManager:r,consentStore:a},C},exports.installNetworkBlocker=v,exports.parseCookie=n,exports.resetConsentRuntime=function(){C=null},exports.resolveTranslations=function(e){const t=e?.messages??{},n=e?.detectBrowserLanguage??!0,o=[];e?.locale&&o.push(e.locale),n&&"undefined"!=typeof navigator&&navigator.language&&o.push(navigator.language);for(const e of o){const n=e.split("-")[0]?.toLowerCase()??"",o=t[e]??(n?t[n]:void 0);if(o)return o}return t.en??i},exports.serializeCookie=r,exports.uninstallNetworkBlocker=b;
|
|
1
|
+
"use strict";const e="cookieyes-consent",t=new Set(["consentid","consent","action","tax","lastRenewedDate"]);function o(e){const o={categories:{}};for(const n of e.split(",")){const e=n.indexOf(":");if(-1===e)continue;const r=n.slice(0,e).trim(),s=n.slice(e+1).trim();t.has(r)?o[r]=s:r.length>0&&(o.categories[r]=s)}return o}function n(e){const t=[`consentid:${e.consentId}`,"consent:"+(e.hasActed?"yes":"no"),"action:"+(e.hasActed?"yes":"no")];e.taxonomyHash&&t.push(`tax:${e.taxonomyHash}`);for(const[o,n]of Object.entries(e.categories))t.push(`${o}:${n?"yes":"no"}`);return t.push(`lastRenewedDate:${e.lastRenewed??Date.now()}`),t.join(",")}function r(t){if("undefined"==typeof document)return;const o=encodeURIComponent(n(t));document.cookie=`${e}=${o}; max-age=31536000; path=/; SameSite=Lax`}function s(){"undefined"!=typeof document&&(document.cookie=`${e}=; max-age=0; path=/`)}function a(){const e=new Uint8Array(32);if("undefined"!=typeof crypto&&crypto.getRandomValues)crypto.getRandomValues(e);else for(let t=0;t<e.length;t++)e[t]=Math.floor(256*Math.random());return btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"").slice(0,44)}function i(e,t,o){const n="CCPA"===t,r={};for(const e of o.ids)r[e]=!!o.requiredIds.has(e)||n;return{consentId:e,hasActed:!1,categories:r,regulation:t,taxonomyHash:o.taxonomyHash}}const c=[{id:"necessary",required:!0},{id:"functional",gcm:["functionality_storage","personalization_storage"]},{id:"analytics",gcm:["analytics_storage"]},{id:"performance"},{id:"advertisement",gcm:["ad_storage","ad_user_data","ad_personalization"]}];function d(e){let t=2166136261;for(let o=0;o<e.length;o++)t^=e.charCodeAt(o),t=Math.imul(t,16777619);return(t>>>0).toString(36)}function l(e,t){const o=e.map(e=>e.id),n=new Set(e.filter(e=>e.required).map(e=>e.id)),r=e.map(e=>`${e.id}:${e.required?1:0}:${(e.gcm??[]).join("+")}`).join("|");return{list:e,ids:o,requiredIds:n,taxonomyHash:d(r),isDefault:t}}function u(e){if(!e||0===e.length)return l(c,!0);const o=function(e){const o=e.map(e=>e.id);return o.some(e=>"string"!=typeof e||0===e.length)?"every category needs a non-empty string id":o.some(e=>e.includes(",")||e.includes(":"))?"category ids must not contain ',' or ':'":new Set(o).size!==o.length?"category ids must be unique":o.some(e=>t.has(e))?`category ids must not be one of the reserved keys: ${[...t].join(", ")}`:e.some(e=>!0===e.required)?null:"at least one category must be marked { required: true }"}(e);return o?("undefined"!=typeof console&&console.warn(`[cookieyes] Invalid categories config (${o}). Falling back to the default five (necessary, functional, analytics, performance, advertisement).`),l(c,!0)):l(e,!1)}function g(e){"undefined"!=typeof console&&console.warn(e)}function f(e){const t={mode:e.mode},o=e.overrides?.regulation;return void 0!==e.regulation?(t.regulation=e.regulation,void 0!==o&&g("[CookieYes] Received both `regulation` and the deprecated `overrides.regulation`. Using the top-level `regulation` and ignoring `overrides`. Drop the `overrides` object — it is deprecated and will be removed after three release cycles.")):void 0!==o&&(t.regulation=o),void 0!==e.colorScheme&&(t.colorScheme=e.colorScheme),void 0!==e.theme&&(t.theme=e.theme),void 0!==e.i18n&&(t.i18n=e.i18n),void 0!==e.consentCategories&&(t.consentCategories=e.consentCategories),void 0!==e.categories&&(t.categories=e.categories),void 0!==e.networkBlocker&&(t.networkBlocker=e.networkBlocker),void 0!==e.reloadOnRevoke&&(t.reloadOnRevoke=e.reloadOnRevoke),void 0!==e.integrations&&(t.integrations=e.integrations),void 0!==e.customStopHandlers&&(t.customStopHandlers=e.customStopHandlers),void 0!==e.onConsentReady&&(t.onConsentReady=e.onConsentReady),void 0!==e.onConsentUpdate&&(t.onConsentUpdate=e.onConsentUpdate),"self-hosted"===e.mode&&(void 0!==e.apiKey&&(t.apiKey=e.apiKey),void 0!==e.backend&&(t.backend=e.backend),void 0!==e.apiUrl?(t.apiUrl=e.apiUrl,void 0!==e.backendURL&&g("[CookieYes] Received both `apiUrl` and the deprecated `backendURL`. Using `apiUrl` and ignoring `backendURL`. Rename `backendURL` to `apiUrl` — the alias is deprecated and will be removed after three release cycles.")):void 0!==e.backendURL&&(t.apiUrl=e.backendURL)),t}let p=!1;function h(){p||(p=!0,"undefined"!=typeof console&&console.warn('[cookieyes] mode: "offline" has been renamed to "cookie-only". Both do exactly the same thing, but "offline" is deprecated and will be removed in 3 releases. Update to .mode("cookie-only") (or { mode: "cookie-only" }).'))}function y(e){const t={save:new Set,change:new Set};let o={...e()};function n(e,t,o){try{e.listener(o)}catch(e){"undefined"!=typeof console&&console.error(`[cookieyes] a consent "${t}" listener threw; others are unaffected:`,e)}}function r(e,o){for(const r of[...t[e]])r.category&&!o.changedCategories.includes(r.category)||n(r,e,o)}return{on(o,r,s){const a=s?.category?{listener:r,category:s.category}:{listener:r};return t[o].add(a),n(a,o,{categories:{...e()},changedCategories:[],isInitial:!0}),()=>{t[o].delete(a)}},push(e){const t={...e},n=[];for(const e of Object.keys(t))o[e]!==t[e]&&n.push(e);o=t,r("save",{categories:t,changedCategories:n,isInitial:!1}),n.length>0&&r("change",{categories:t,changedCategories:n,isInitial:!1})}}}const m=["ad_storage","ad_user_data","ad_personalization","analytics_storage","functionality_storage","personalization_storage","security_storage"];function w(e,t){const o={};for(const e of m)o[e]="denied";o.security_storage="granted";for(const n of e.list)if(n.gcm&&0!==n.gcm.length&&t[n.id])for(const e of n.gcm)o[e]="granted";return o}function v(e,t){if("undefined"==typeof window||!Array.isArray(window.dataLayer))return;const o=w(e,t),n=window.dataLayer;if(!n)return;!function(){n.push(arguments)}("consent","update",o)}const k={bannerTitle:"We value your privacy",bannerDescription:"We use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking “Accept All”, you consent to our use of cookies.",acceptAll:"Accept All",rejectAll:"Reject All",managePreferences:"Customise",savePreferences:"Save My Preferences",doNotSell:"Do Not Sell or Share My Personal Information",ccpaDescription:"This website or its third-party tools process personal data. You can opt out of the sale of your personal information by clicking on the “Do Not Sell or Share My Personal Information” link.",accept:"Accept",poweredBy:"Powered by CookieYes",preferencesTitle:"Customise Consent Preferences",preferencesIntro:"We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.",categories:{necessary:{label:"Necessary",description:"Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data."},functional:{label:"Functional",description:"Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features."},analytics:{label:"Analytics",description:"Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc."},performance:{label:"Performance",description:"Performance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors."},advertisement:{label:"Advertisement",description:"Advertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns."}},optOut:{title:"Opt-out Preferences",description:'We use third-party cookies that help us analyse how you use this website, store your preferences, and provide the content and advertisements that are relevant to you. However, you can opt out of these cookies by checking "Do Not Sell or Share My Personal Information" and clicking the "Save My Preferences" button. Once you opt out, you can opt in again at any time by unchecking "Do Not Sell or Share My Personal Information" and clicking the "Save My Preferences" button.',cancel:"Cancel",successText:"Your opt-out preference has been honored.",successCountdown:"Banner closes automatically in {seconds} s..."},reloadNotice:{message:"Some tracking on this page can only be fully stopped by reloading. Reload to apply your change, or dismiss to keep browsing.",reloadButton:"Reload page",dismissButton:"Dismiss"}},b=new Set(["ar","he","fa","ur","ps","sd","yi","dv"]);function C(e){return e.split("-")[0]?.toLowerCase()??""}function S(e){return b.has(C(e))?"rtl":"ltr"}function R(e,t){if(!t)return e;const o={...e};for(const[n,r]of Object.entries(t)){if(null==r)continue;const t=e[n],s="object"==typeof r&&!Array.isArray(r)&&"object"==typeof t&&null!=t;o[n]=s?R(t,r):r}return o}function x(e){const t=e?.messages??{},o=[];e?.locale&&o.push(e.locale),(e?.detectBrowserLanguage??1)&&"undefined"!=typeof navigator&&navigator.language&&o.push(navigator.language);for(const e of o){if(t[e])return e;const o=C(e);if(o&&t[o])return o}return"en"}function A(e,t){const o={...e?.messages},n=e?.loadLanguage,r=new Set;let s=x(e),a=l(s),i=u();function c(e){return o[e]??o[C(e)]}function d(e){return"en"===C(e)||void 0!==c(e)}function l(e){return R(k,c(e))}function u(){return{language:s,direction:S(s),languages:Array.from(new Set(["en",...Object.keys(o)]))}}function g(e){s=e,a=l(e),i=u(),t()}function f(e,t){r.has(e)||"undefined"==typeof console||(r.add(e),console.warn(`[cookieyes] no translations for language "${e}"; staying on "${s}". Add it to i18n.messages or provide i18n.loadLanguage.`,t??""))}function p(e){return d(e)?(g(e),Promise.resolve()):n?Promise.resolve().then(()=>n(e)).then(t=>{o[e]=t,g(e)}).catch(t=>f(e,t)):(f(e),Promise.resolve())}return n&&e?.locale&&!d(e.locale)&&"undefined"!=typeof window&&p(e.locale),{getTranslations:()=>a,getLanguageInfo:()=>i,setLanguage:p,getCategoryText:function(e){return c(s)?.categories?.[e]}}}const U=new Map,I=new Map;function L(e,t){if(document.getElementById(e))return;const o=document.createElement("script");o.id=e,o.src=t.src,o.async=!0,t.onLoad&&o.addEventListener("load",t.onLoad,{once:!0}),document.head.appendChild(o),I.set(e,o)}function B(e){return"needsReload"in e&&!0===e.needsReload}function H(e){switch(e.vendor){case"meta":return{id:"meta",category:e.category??"advertisement",stop:()=>window.fbq?.("consent","revoke"),resume:()=>window.fbq?.("consent","grant")};case"tiktok":return{id:"tiktok",category:e.category??"advertisement",needsReload:!0};case"linkedin":return{id:"linkedin",category:e.category??"advertisement",needsReload:!0};case"hotjar":return{id:"hotjar",category:e.category??"analytics",needsReload:!0};case"segment":return{id:"segment",category:e.category??"analytics",needsReload:!0}}}const O=new Map,P=new Set,T=new Set;function _(e){O.set(e.id,e)}function q(e){const t=[];for(const o of O.values()){const n=!0!==e[o.category];if(B(o))n?T.has(o.id)&&(t.push(o.id),T.delete(o.id)):T.add(o.id);else if(n){if(!P.has(o.id))try{o.stop(),P.add(o.id)}catch{t.push(o.id)}}else if(P.has(o.id)){P.delete(o.id);try{o.resume?.()}catch{}}}return{reloadRequiredBy:t}}function M(e){return{consentId:e.consentId,categories:e.categories,regulation:e.regulation,domain:"undefined"!=typeof window?window.location.hostname:"unknown"}}function $(t){const n=new Set,c=u(t.categories);let d,l,g,f=!1;function p(e){const t={};for(const o of c.ids)t[o]=!!c.requiredIds.has(o)||e(o);return t}let h=[],y=!1;for(const e of t.integrations??[])_(H(e));for(const e of t.customStopHandlers??[])_(e);const m=function(){if("undefined"==typeof document)return null;const t=document.cookie.split(";");for(const n of t){const t=n.trim(),r=t.indexOf("=");if(-1!==r&&t.slice(0,r).trim()===e){const e=t.slice(r+1).trim();return o(decodeURIComponent(e))}}return null}(),w=t.regulation??"DEFAULT",k=m?.tax,b=k===c.taxonomyHash,C=null!=m&&(b||void 0===k&&c.isDefault);if(null!=m&&C)d=function(e,t,o){const n={};for(const t of o.ids)n[t]=!!o.requiredIds.has(t)||"yes"===e.categories[t];return{consentId:e.consentid??a(),hasActed:"yes"===e.action,categories:n,regulation:t,lastRenewed:e.lastRenewedDate?Number(e.lastRenewedDate):void 0,taxonomyHash:e.tax}}(m,w,c);else{const e=m?.consentid??a();d=i(e,w,c),null!=m&&s(),"CCPA"===d.regulation&&r(d)}function S(){const e={consentId:d.consentId,hasActed:d.hasActed,categories:{...d.categories},regulation:d.regulation,lastRenewed:d.lastRenewed,taxonomyHash:d.taxonomyHash};for(const t of n)t(e)}function R(){!function(e){if("undefined"!=typeof document)for(const[t,o]of U)!0===e[o.category]&&(I.has(t)||L(t,o))}(g)}function x(){if(d={...d,hasActed:!0,lastRenewed:Date.now()},r(d),t.backend)try{Promise.resolve(t.backend.persist(M(d))).catch(()=>{})}catch{}else t.apiUrl&&async function(e,t,o){const n=M(o),r={"Content-Type":"application/json"};t&&(r.Authorization=`Bearer ${t}`);try{await fetch(e,{method:"POST",headers:r,body:JSON.stringify(n),keepalive:!0})}catch{}}(t.apiUrl,t.apiKey,d);let e=!1;for(const t of c.ids)if(l[t]&&!d.categories[t]){e=!0;break}l={...d.categories},g={...d.categories},R();const{reloadRequiredBy:o}=q(g);var n;((n=o).length!==h.length||n.some((e,t)=>e!==h[t]))&&(h=n,y=!1),v(c,g),S(),t.onConsentUpdate?.(d),e&&t.reloadOnRevoke&&"undefined"!=typeof window&&window.location.reload()}d={...d,taxonomyHash:c.taxonomyHash},l={...d.categories},g={...d.categories},Promise.resolve().then(()=>t.onConsentReady?.(d));const A={get consentId(){return d.consentId},get hasActed(){return d.hasActed},get categories(){return{...d.categories}},get committedCategories(){return{...g}},get regulation(){return d.regulation},get lastRenewed(){return d.lastRenewed},get taxonomyHash(){return d.taxonomyHash},get isPreferencesOpen(){return f},acceptAll(){d={...d,categories:p(()=>!0)},f=!1,x()},rejectAll(){d={...d,categories:p(()=>!1)},f=!1,x()},acceptSelected(e){d={...d,categories:p(t=>e.includes(t))},f=!1,x()},updateCategory(e,t){c.requiredIds.has(e)||c.ids.includes(e)&&(d={...d,categories:{...d.categories,[e]:t}},S())},savePreferences(){f=!1,x()},resetConsent(){s();const e=a();d=i(e,d.regulation,c),g={...d.categories},l={...d.categories},f=!1,q(g),v(c,g),h=[],y=!1,S()},showPreferences(){f=!0,S()},hidePreferences(){f=!1,S()},subscribe:e=>(n.add(e),()=>n.delete(e)),registerScript(e){!function(e){U.set(e.id,e)}(e),R()},get reloadNotice(){return{required:h.length>0&&!y,reasons:[...h]}},dismissReloadNotice(){y||(y=!0,S())}};return R(),function(e){for(const t of O.values()){const o=!0!==e[t.category];if(B(t))o?T.delete(t.id):T.add(t.id);else try{o?(t.stop(),P.add(t.id)):(P.delete(t.id),t.resume?.())}catch{}}}(d.categories),v(c,d.categories),A}function j(e,t,o,n){let r;try{const e="undefined"!=typeof window?window.location.href:"http://localhost";r=new URL(t,e)}catch{return null}const s=r.hostname.toLowerCase(),a=r.pathname+r.search,i=o.toUpperCase();for(const t of e){const e=t.domain.toLowerCase();if((s===e||s.endsWith("."+e))&&((!t.pathIncludes||a.includes(t.pathIncludes))&&(!t.methods||t.methods.map(e=>e.toUpperCase()).includes(i))&&!n(t.category)))return t}return null}let D=null;function N(e,t){if("undefined"==typeof window)return()=>{};if(D)return()=>{};if(!e.rules.length)return()=>{};const o={originalFetch:window.fetch,originalXhrOpen:XMLHttpRequest.prototype.open,originalXhrSend:XMLHttpRequest.prototype.send,originalSendBeacon:"undefined"!=typeof navigator&&"function"==typeof navigator.sendBeacon?navigator.sendBeacon:void 0};D=o;const n=!1!==e.logBlockedRequests;function r(t){n&&console.warn(`[cookieyes] blocked ${t.method} ${t.url} (rule "${t.rule.id}", category: ${t.rule.category})`),e.onRequestBlocked?.(t)}if(window.fetch=function(n,s){let a="",i=s?.method??"GET";"string"==typeof n?a=n:n instanceof URL?a=n.toString():(a=n.url,i=s?.method??n.method);const c=j(e.rules,a,i,t);return c?(r({rule:c,url:a,method:i}),Promise.reject(new TypeError(`Blocked by consent (rule: ${c.id}, category: ${c.category})`))):o.originalFetch.call(window,n,s)},XMLHttpRequest.prototype.open=function(e,t,...n){return this._cyUrl=t.toString(),this._cyMethod=e,o.originalXhrOpen.apply(this,[e,t,...n])},XMLHttpRequest.prototype.send=function(n){const s=this._cyUrl??"",a=this._cyMethod??"GET",i=j(e.rules,s,a,t);return i?(r({rule:i,url:s,method:a}),void this.abort()):o.originalXhrSend.call(this,n)},o.originalSendBeacon){const n=o.originalSendBeacon;navigator.sendBeacon=function(o,s){const a=j(e.rules,o.toString(),"POST",t);return a?(r({rule:a,url:o.toString(),method:"POST"}),!0):n.call(navigator,o,s)}}return X}function X(){D&&("undefined"!=typeof window&&(window.fetch=D.originalFetch,XMLHttpRequest.prototype.open=D.originalXhrOpen,XMLHttpRequest.prototype.send=D.originalXhrSend,D.originalSendBeacon&&(navigator.sendBeacon=D.originalSendBeacon)),D=null)}let E=null;function z(e){if(E)return E;"offline"===e.mode&&h();const t=f(e),o=new Set,n=t.onConsentUpdate;let r;const s={};"self-hosted"===t.mode&&(t.backend?s.backend=t.backend:t.apiUrl&&(s.apiUrl=t.apiUrl)),t.apiKey&&(s.apiKey=t.apiKey),t.regulation&&(s.regulation=t.regulation),t.colorScheme&&(s.colorScheme=t.colorScheme),t.theme&&(s.theme=t.theme),t.reloadOnRevoke&&(s.reloadOnRevoke=t.reloadOnRevoke),t.integrations&&(s.integrations=t.integrations),t.customStopHandlers&&(s.customStopHandlers=t.customStopHandlers),t.categories&&(s.categories=t.categories),t.onConsentReady&&(s.onConsentReady=t.onConsentReady),s.onConsentUpdate=e=>{n?.(e),r.push(e.categories);const t=function(e){const t=[],o=[];for(const n of Object.keys(e))e[n]?t.push(n):o.push(n);return{allowedCategories:t,deniedCategories:o}}(e.categories);for(const e of o)e(t)};const a=$(s);r=y(()=>a.committedCategories);const i=u(t.categories),c=new Set;function d(){const e=g();for(const t of c)t(e)}a.subscribe(d);const l=A(t.i18n,d);function g(){const e=a.categories;return{consentId:a.consentId,hasActed:a.hasActed,categories:e,consents:e,committedConsents:a.committedCategories,regulation:a.regulation,lastRenewed:a.lastRenewed,taxonomyHash:a.taxonomyHash,activeUI:a.isPreferencesOpen?"dialog":a.hasActed?null:"banner",has:e=>!0===a.committedCategories[e],saveConsents:async e=>{"all"===e?a.acceptAll():"necessary"===e?a.rejectAll():a.acceptSelected(e)},setConsent:(e,t)=>a.updateCategory(e,t),subscribeToConsentChanges:e=>(o.add(e),()=>{o.delete(e)})}}const p={subscribe:e=>(c.add(e),()=>{c.delete(e)}),getState:g,on:(e,t,o)=>r.on(e,t,o),get translations(){return l.getTranslations()},getLanguageInfo:l.getLanguageInfo,setLanguage:l.setLanguage,getCategoryText:l.getCategoryText,categories:i};return t.networkBlocker&&t.networkBlocker.rules.length>0&&N(t.networkBlocker,e=>!0===a.committedCategories[e]),E={consentManager:a,consentStore:p},E}exports.DEFAULT_CATEGORIES=c,exports._clearStopHandlers=function(){O.clear(),P.clear(),T.clear()},exports._normalizeConfig=f,exports._resetOfflineModeWarning=function(){p=!1},exports._warnOfflineModeDeprecated=h,exports.broadcastGoogleConsent=v,exports.computeGoogleConsent=w,exports.createConsentEmitter=y,exports.createConsentManager=$,exports.createLanguageController=A,exports.defaultTranslations=k,exports.generateConsentId=a,exports.getOrCreateConsentRuntime=z,exports.getTextDirection=S,exports.initCookieYes=function(e){return z(e)},exports.installNetworkBlocker=N,exports.mergeTranslations=R,exports.parseCookie=o,exports.pickLanguage=x,exports.primaryOf=C,exports.registerStopHandler=_,exports.resetConsentRuntime=function(){E=null},exports.resolveBuiltInIntegration=H,exports.resolveCategories=u,exports.resolveTranslations=function(e){const t=e?.messages??{},o=x(e);return R(k,t[o]??t[C(o)])},exports.serializeCookie=n,exports.uninstallNetworkBlocker=X;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|