@tickboxhq/core 0.0.1
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/LICENSE +21 -0
- package/dist/index-D_Xt5NFB.d.ts +155 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.js +425 -0
- package/dist/index.js.map +1 -0
- package/dist/jurisdictions/index.d.ts +1 -0
- package/dist/jurisdictions/index.js +124 -0
- package/dist/jurisdictions/index.js.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tiny Systems Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a category should be treated for a given jurisdiction.
|
|
3
|
+
*
|
|
4
|
+
* - `consent` — must opt-in. Block tags until granted. (e.g. marketing under GDPR/PECR)
|
|
5
|
+
* - `notice` — show info + easy opt-out, but don't block tags. (e.g. UK DUAA "statistical" analytics)
|
|
6
|
+
* - `always` — no consent or notice required. (e.g. necessary cookies)
|
|
7
|
+
*/
|
|
8
|
+
type ConsentMode = 'consent' | 'notice' | 'always';
|
|
9
|
+
type CategoryId = string;
|
|
10
|
+
type CategoryDefinition = {
|
|
11
|
+
/** True for strictly necessary categories that the user cannot deny. */
|
|
12
|
+
required?: boolean;
|
|
13
|
+
/** Pre-toggled state when the user hasn't chosen yet. Ignored when `required` is true. */
|
|
14
|
+
default?: boolean;
|
|
15
|
+
/** Vendor identifiers (e.g. 'plausible', 'google-ads') used for jurisdiction classification. */
|
|
16
|
+
vendors?: string[];
|
|
17
|
+
/** Human-readable description shown in the banner / preference centre. */
|
|
18
|
+
description?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Override the inferred mode for this category. Normally not needed —
|
|
21
|
+
* the jurisdiction + vendor list determines the mode.
|
|
22
|
+
*/
|
|
23
|
+
mode?: ConsentMode;
|
|
24
|
+
};
|
|
25
|
+
type JurisdictionId = 'UK_DUAA' | 'EU_GDPR' | 'US_CA' | 'CCPA' | (string & {});
|
|
26
|
+
type Jurisdiction = {
|
|
27
|
+
id: JurisdictionId;
|
|
28
|
+
name: string;
|
|
29
|
+
/**
|
|
30
|
+
* Per-vendor classification: which vendors require full consent vs. notice-only
|
|
31
|
+
* vs. always-allowed under this jurisdiction.
|
|
32
|
+
*/
|
|
33
|
+
vendorRules: Record<string, ConsentMode>;
|
|
34
|
+
/** Default mode applied to vendors not in `vendorRules`. */
|
|
35
|
+
defaultMode: ConsentMode;
|
|
36
|
+
/** UI requirements imposed by the jurisdiction. */
|
|
37
|
+
ui: {
|
|
38
|
+
/** Whether a "Reject All" button is required on the first banner layer. */
|
|
39
|
+
rejectButtonOnFirstLayer: boolean;
|
|
40
|
+
/** Whether Accept/Reject must have equal visual prominence. */
|
|
41
|
+
equalProminence: boolean;
|
|
42
|
+
/** Whether Global Privacy Control (Sec-GPC) signals must be honoured as opt-out. */
|
|
43
|
+
honorGPC: boolean;
|
|
44
|
+
};
|
|
45
|
+
/** Optional ISO 3166-1 alpha-2 country codes that map to this jurisdiction (used by 'auto' mode). */
|
|
46
|
+
countries?: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
type ConsentConfig = {
|
|
49
|
+
/**
|
|
50
|
+
* The jurisdiction governing this site, or `'auto'` to detect by visitor country.
|
|
51
|
+
* 'auto' falls back to the strictest available match.
|
|
52
|
+
*/
|
|
53
|
+
jurisdiction: Jurisdiction | 'auto';
|
|
54
|
+
/** Map of category ID to definition. The keys are arbitrary (e.g. 'analytics', 'marketing'). */
|
|
55
|
+
categories: Record<CategoryId, CategoryDefinition>;
|
|
56
|
+
/** Versioned policy reference. Audit log entries are tied to a policy version. */
|
|
57
|
+
policy?: {
|
|
58
|
+
version: string;
|
|
59
|
+
url?: string;
|
|
60
|
+
};
|
|
61
|
+
/** Cloud configuration. If omitted, the SDK runs entirely client-side with no telemetry. */
|
|
62
|
+
cloud?: {
|
|
63
|
+
apiKey?: string;
|
|
64
|
+
/** Override the default ingest endpoint. Useful for self-hosting. */
|
|
65
|
+
endpoint?: string;
|
|
66
|
+
};
|
|
67
|
+
/** Cookie storage options. */
|
|
68
|
+
storage?: StorageOptions;
|
|
69
|
+
};
|
|
70
|
+
type StorageOptions = {
|
|
71
|
+
/** Cookie name. Defaults to `__tb_consent`. */
|
|
72
|
+
cookieName?: string;
|
|
73
|
+
/** Optional Domain attribute (e.g. '.example.com' to share across subdomains). */
|
|
74
|
+
domain?: string;
|
|
75
|
+
/** Cookie max-age in days. Defaults to 365. */
|
|
76
|
+
maxAgeDays?: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The serialised consent record stored in the browser cookie.
|
|
80
|
+
* Schema is versioned via `v` so future migrations don't break readers.
|
|
81
|
+
*/
|
|
82
|
+
type StoredConsent = {
|
|
83
|
+
/** Schema version. */
|
|
84
|
+
v: 1;
|
|
85
|
+
/** Map of category ID to granted/denied. */
|
|
86
|
+
c: Record<CategoryId, boolean>;
|
|
87
|
+
/** Policy version at the time of choice. */
|
|
88
|
+
pv: string;
|
|
89
|
+
/** Unix epoch milliseconds when the choice was made. */
|
|
90
|
+
ts: number;
|
|
91
|
+
/** Jurisdiction ID at the time of choice. */
|
|
92
|
+
j: JurisdictionId;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* A category resolved against the active jurisdiction — what the SDK actually
|
|
96
|
+
* needs to know to render UI and gate scripts.
|
|
97
|
+
*/
|
|
98
|
+
type ResolvedCategory = {
|
|
99
|
+
id: CategoryId;
|
|
100
|
+
required: boolean;
|
|
101
|
+
mode: ConsentMode;
|
|
102
|
+
default: boolean;
|
|
103
|
+
vendors: string[];
|
|
104
|
+
description?: string;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* United Kingdom — Data (Use and Access) Act 2025 (DUAA).
|
|
109
|
+
*
|
|
110
|
+
* In force from 5 February 2026. Amends PECR to introduce new exemptions for
|
|
111
|
+
* cookies/storage used solely for statistical, security, authentication, and
|
|
112
|
+
* appearance purposes. Advertising and individual-level tracking still require
|
|
113
|
+
* full opt-in consent.
|
|
114
|
+
*
|
|
115
|
+
* UI requirements (ICO):
|
|
116
|
+
* - "Reject All" must be on the first banner layer
|
|
117
|
+
* - "Accept All" and "Reject All" must have equal visual prominence
|
|
118
|
+
* - GPC is not (yet) mandatory in UK guidance; default off.
|
|
119
|
+
*
|
|
120
|
+
* @see https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guide-to-pecr/cookies-and-similar-technologies/
|
|
121
|
+
*/
|
|
122
|
+
declare const UK_DUAA: Jurisdiction;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* European Union — GDPR + ePrivacy Directive ("Cookie Law").
|
|
126
|
+
*
|
|
127
|
+
* EU rules don't have the DUAA "statistical exemption" — analytics that
|
|
128
|
+
* involve client-side storage on a user's device generally require opt-in
|
|
129
|
+
* consent, even for first-party privacy-friendly tools (positions vary by
|
|
130
|
+
* DPA; CNIL has been more permissive than others). This preset takes the
|
|
131
|
+
* conservative position: treat all tracking categories as consent-required.
|
|
132
|
+
*
|
|
133
|
+
* UI requirements (EDPB / national DPAs):
|
|
134
|
+
* - "Reject All" on first banner layer with equal prominence
|
|
135
|
+
* - GPC: not yet mandatory but increasingly recognised
|
|
136
|
+
*/
|
|
137
|
+
declare const EU_GDPR: Jurisdiction;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Map of all built-in jurisdiction presets, keyed by their ID for ergonomic
|
|
141
|
+
* lookup: `jurisdictions.UK_DUAA`.
|
|
142
|
+
*/
|
|
143
|
+
declare const jurisdictions: {
|
|
144
|
+
readonly UK_DUAA: Jurisdiction;
|
|
145
|
+
readonly EU_GDPR: Jurisdiction;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Resolve a jurisdiction from an ISO 3166-1 alpha-2 country code (e.g. 'GB').
|
|
149
|
+
* Falls back to `EU_GDPR` for any country not explicitly mapped — the safer
|
|
150
|
+
* default for an unknown EEA visitor. Returns `null` when the code is unknown
|
|
151
|
+
* and no fallback is requested.
|
|
152
|
+
*/
|
|
153
|
+
declare function resolveJurisdictionByCountry(country: string | undefined, fallback?: Jurisdiction | null): Jurisdiction | null;
|
|
154
|
+
|
|
155
|
+
export { type ConsentConfig as C, EU_GDPR as E, type Jurisdiction as J, type ResolvedCategory as R, type StorageOptions as S, UK_DUAA as U, type StoredConsent as a, type CategoryDefinition as b, type CategoryId as c, type ConsentMode as d, type JurisdictionId as e, jurisdictions as j, resolveJurisdictionByCountry as r };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { R as ResolvedCategory, C as ConsentConfig, S as StorageOptions, J as Jurisdiction, a as StoredConsent } from './index-D_Xt5NFB.js';
|
|
2
|
+
export { b as CategoryDefinition, c as CategoryId, d as ConsentMode, e as JurisdictionId, j as jurisdictions, r as resolveJurisdictionByCountry } from './index-D_Xt5NFB.js';
|
|
3
|
+
|
|
4
|
+
type ConsentState = {
|
|
5
|
+
/** True after the store has hydrated from the cookie (client-only). */
|
|
6
|
+
ready: boolean;
|
|
7
|
+
/** True when the banner / preference centre should be visible. */
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
/** Map of category ID → granted (true) / denied (false). */
|
|
10
|
+
decisions: Record<string, boolean>;
|
|
11
|
+
/** Resolved categories for the active jurisdiction. */
|
|
12
|
+
resolved: ResolvedCategory[];
|
|
13
|
+
/** Timestamp of the most-recent stored decision, if any. */
|
|
14
|
+
storedAt: number | null;
|
|
15
|
+
};
|
|
16
|
+
type Listener = (state: ConsentState) => void;
|
|
17
|
+
type StoreOptions = {
|
|
18
|
+
/** Storage options forwarded to the cookie reader/writer. */
|
|
19
|
+
storage?: StorageOptions;
|
|
20
|
+
/** When `true`, side-effects like script rewriting fire on state changes. Defaults to true. */
|
|
21
|
+
applyEffects?: boolean;
|
|
22
|
+
/** Custom side-effect handler. Receives `(state, resolved)` whenever decisions change. */
|
|
23
|
+
onApply?: (state: ConsentState) => void;
|
|
24
|
+
/** Active jurisdiction. Required — pass `config.jurisdiction` after resolving 'auto'. */
|
|
25
|
+
jurisdiction: Jurisdiction;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Framework-agnostic consent state machine.
|
|
29
|
+
*
|
|
30
|
+
* The store hydrates from the cookie on first read, keeps decisions in memory,
|
|
31
|
+
* and broadcasts changes to subscribers. Adapters (`@tickboxhq/react`,
|
|
32
|
+
* `@tickboxhq/vue`) bind to it via their idiomatic reactivity primitive.
|
|
33
|
+
*/
|
|
34
|
+
declare class ConsentStore {
|
|
35
|
+
private state;
|
|
36
|
+
private readonly listeners;
|
|
37
|
+
private readonly config;
|
|
38
|
+
private readonly options;
|
|
39
|
+
constructor(config: ConsentConfig, options: StoreOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Read the stored cookie and update state. Safe to call on the server
|
|
42
|
+
* (no-op when `document` is unavailable). Call from a mount effect.
|
|
43
|
+
*/
|
|
44
|
+
hydrate(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Hydrate from a raw Cookie header — for server-side rendering.
|
|
47
|
+
* Pass the value of the request's `cookie` header (or `undefined` if absent).
|
|
48
|
+
*/
|
|
49
|
+
hydrateFromHeader(cookieHeader: string | undefined): void;
|
|
50
|
+
private applyHydration;
|
|
51
|
+
getState(): ConsentState;
|
|
52
|
+
subscribe(fn: Listener): () => void;
|
|
53
|
+
grant(id: string): void;
|
|
54
|
+
deny(id: string): void;
|
|
55
|
+
grantAll(): void;
|
|
56
|
+
denyAll(): void;
|
|
57
|
+
/** Persist the current decisions and close the banner. */
|
|
58
|
+
save(): void;
|
|
59
|
+
open(): void;
|
|
60
|
+
close(): void;
|
|
61
|
+
/** Wipe stored consent and reopen the banner. */
|
|
62
|
+
reset(): void;
|
|
63
|
+
isRequired(id: string): boolean;
|
|
64
|
+
private update;
|
|
65
|
+
private persist;
|
|
66
|
+
private emit;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Element attribute used by Tickbox-aware scripts to declare their category.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```html
|
|
74
|
+
* <script type="text/plain" data-tb-category="analytics" src="plausible.js"></script>
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* On grant, the SDK rewrites `type` to `text/javascript` so the browser executes the script.
|
|
78
|
+
* On deny, the script is left as `text/plain` (browser ignores it).
|
|
79
|
+
*/
|
|
80
|
+
declare const TAG_ATTRIBUTE = "data-tb-category";
|
|
81
|
+
/**
|
|
82
|
+
* Apply a consent state to the document by:
|
|
83
|
+
* 1. Activating any `<script type="text/plain" data-tb-category="X">` whose category was granted
|
|
84
|
+
* 2. Calling `gtag('consent', 'update', ...)` if `gtag` is on the global scope
|
|
85
|
+
* 3. Dispatching a `tickbox:consent-changed` CustomEvent for any custom integrations
|
|
86
|
+
*
|
|
87
|
+
* No-op on the server.
|
|
88
|
+
*/
|
|
89
|
+
declare function applyConsent(state: ConsentState): void;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Identity helper that narrows the type of a consent config so users get
|
|
93
|
+
* full autocomplete + type-checking in their `consent.config.ts`.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* import { defineConsent, jurisdictions } from '@tickboxhq/core'
|
|
98
|
+
*
|
|
99
|
+
* export default defineConsent({
|
|
100
|
+
* jurisdiction: jurisdictions.UK_DUAA,
|
|
101
|
+
* categories: {
|
|
102
|
+
* necessary: { required: true },
|
|
103
|
+
* analytics: { vendors: ['plausible'] },
|
|
104
|
+
* },
|
|
105
|
+
* })
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function defineConsent<const T extends ConsentConfig>(config: T): T;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Detect a Global Privacy Control signal from the visitor's browser.
|
|
112
|
+
*
|
|
113
|
+
* GPC is exposed as `navigator.globalPrivacyControl` (boolean) and the
|
|
114
|
+
* `Sec-GPC: 1` HTTP header. The header is server-side only; this helper
|
|
115
|
+
* covers the client-side property.
|
|
116
|
+
*
|
|
117
|
+
* @see https://globalprivacycontrol.org/
|
|
118
|
+
*/
|
|
119
|
+
declare function isGPCSignaled(): boolean;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolve each declared category against the active jurisdiction's vendor rules.
|
|
123
|
+
*
|
|
124
|
+
* For each category, the most-restrictive vendor mode wins:
|
|
125
|
+
* `consent` > `notice` > `always`
|
|
126
|
+
*
|
|
127
|
+
* Categories with no vendors fall back to the jurisdiction's default mode,
|
|
128
|
+
* unless `required: true` (always allowed) or an explicit `mode` override is set.
|
|
129
|
+
*/
|
|
130
|
+
declare function resolveCategories(config: ConsentConfig, jurisdiction: Jurisdiction): ResolvedCategory[];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Read the stored consent record from `document.cookie`.
|
|
134
|
+
* Returns `null` on the server, when no cookie is set, or when the cookie is malformed.
|
|
135
|
+
*/
|
|
136
|
+
declare function readConsent(options?: StorageOptions): StoredConsent | null;
|
|
137
|
+
/**
|
|
138
|
+
* Parse a stored consent record from a raw `Cookie` header string.
|
|
139
|
+
* Useful on the server: pass the request's `cookie` header.
|
|
140
|
+
* Returns `null` when the cookie isn't present or is malformed.
|
|
141
|
+
*/
|
|
142
|
+
declare function parseConsentFromHeader(cookieHeader: string | undefined, options?: StorageOptions): StoredConsent | null;
|
|
143
|
+
/**
|
|
144
|
+
* Persist a consent record to `document.cookie`.
|
|
145
|
+
* No-op on the server.
|
|
146
|
+
*/
|
|
147
|
+
declare function writeConsent(value: StoredConsent, options?: StorageOptions): void;
|
|
148
|
+
/** Clear the stored consent cookie. */
|
|
149
|
+
declare function clearConsent(options?: StorageOptions): void;
|
|
150
|
+
|
|
151
|
+
export { ConsentConfig, type ConsentState, ConsentStore, Jurisdiction, ResolvedCategory, StorageOptions, type StoreOptions, StoredConsent, TAG_ATTRIBUTE, applyConsent, clearConsent, defineConsent, isGPCSignaled, parseConsentFromHeader, readConsent, resolveCategories, writeConsent };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// src/apply.ts
|
|
2
|
+
var TAG_ATTRIBUTE = "data-tb-category";
|
|
3
|
+
function applyConsent(state) {
|
|
4
|
+
if (typeof document === "undefined") return;
|
|
5
|
+
activateScripts(state.decisions);
|
|
6
|
+
updateConsentMode(state.decisions);
|
|
7
|
+
dispatchEvent(state);
|
|
8
|
+
}
|
|
9
|
+
function activateScripts(decisions) {
|
|
10
|
+
const blocked = document.querySelectorAll(`script[type="text/plain"][${TAG_ATTRIBUTE}]`);
|
|
11
|
+
for (const node of Array.from(blocked)) {
|
|
12
|
+
const category = node.getAttribute(TAG_ATTRIBUTE);
|
|
13
|
+
if (!category || !decisions[category]) continue;
|
|
14
|
+
const replacement = document.createElement("script");
|
|
15
|
+
for (const attr of Array.from(node.attributes)) {
|
|
16
|
+
if (attr.name === "type") continue;
|
|
17
|
+
replacement.setAttribute(attr.name, attr.value);
|
|
18
|
+
}
|
|
19
|
+
replacement.text = node.textContent ?? "";
|
|
20
|
+
node.parentNode?.replaceChild(replacement, node);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function updateConsentMode(decisions) {
|
|
24
|
+
const win = globalThis;
|
|
25
|
+
if (typeof win.gtag !== "function") return;
|
|
26
|
+
win.gtag("consent", "update", {
|
|
27
|
+
ad_storage: gv(decisions, "marketing"),
|
|
28
|
+
ad_user_data: gv(decisions, "marketing"),
|
|
29
|
+
ad_personalization: gv(decisions, "marketing"),
|
|
30
|
+
analytics_storage: gv(decisions, "analytics"),
|
|
31
|
+
functionality_storage: gv(decisions, "functional", true),
|
|
32
|
+
personalization_storage: gv(decisions, "preferences", true),
|
|
33
|
+
security_storage: "granted"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function gv(decisions, id, defaultGranted = false) {
|
|
37
|
+
const value = decisions[id];
|
|
38
|
+
if (value === void 0) return defaultGranted ? "granted" : "denied";
|
|
39
|
+
return value ? "granted" : "denied";
|
|
40
|
+
}
|
|
41
|
+
function dispatchEvent(state) {
|
|
42
|
+
document.dispatchEvent(
|
|
43
|
+
new CustomEvent("tickbox:consent-changed", {
|
|
44
|
+
detail: { decisions: state.decisions, ts: state.storedAt }
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/define-consent.ts
|
|
50
|
+
function defineConsent(config) {
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/gpc.ts
|
|
55
|
+
function isGPCSignaled() {
|
|
56
|
+
if (typeof navigator === "undefined") return false;
|
|
57
|
+
return navigator.globalPrivacyControl === true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/jurisdictions/eu-gdpr.ts
|
|
61
|
+
var EU_GDPR = {
|
|
62
|
+
id: "EU_GDPR",
|
|
63
|
+
name: "European Union (GDPR / ePrivacy)",
|
|
64
|
+
vendorRules: {},
|
|
65
|
+
defaultMode: "consent",
|
|
66
|
+
ui: {
|
|
67
|
+
rejectButtonOnFirstLayer: true,
|
|
68
|
+
equalProminence: true,
|
|
69
|
+
honorGPC: false
|
|
70
|
+
},
|
|
71
|
+
countries: [
|
|
72
|
+
"AT",
|
|
73
|
+
"BE",
|
|
74
|
+
"BG",
|
|
75
|
+
"HR",
|
|
76
|
+
"CY",
|
|
77
|
+
"CZ",
|
|
78
|
+
"DK",
|
|
79
|
+
"EE",
|
|
80
|
+
"FI",
|
|
81
|
+
"FR",
|
|
82
|
+
"DE",
|
|
83
|
+
"GR",
|
|
84
|
+
"HU",
|
|
85
|
+
"IE",
|
|
86
|
+
"IT",
|
|
87
|
+
"LV",
|
|
88
|
+
"LT",
|
|
89
|
+
"LU",
|
|
90
|
+
"MT",
|
|
91
|
+
"NL",
|
|
92
|
+
"PL",
|
|
93
|
+
"PT",
|
|
94
|
+
"RO",
|
|
95
|
+
"SK",
|
|
96
|
+
"SI",
|
|
97
|
+
"ES",
|
|
98
|
+
"SE",
|
|
99
|
+
// EEA additions
|
|
100
|
+
"IS",
|
|
101
|
+
"LI",
|
|
102
|
+
"NO"
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/jurisdictions/uk-duaa.ts
|
|
107
|
+
var DUAA_STATISTICAL_VENDORS = [
|
|
108
|
+
"plausible",
|
|
109
|
+
"fathom",
|
|
110
|
+
"simpleanalytics",
|
|
111
|
+
"pirsch",
|
|
112
|
+
"goatcounter",
|
|
113
|
+
"umami",
|
|
114
|
+
"tinybird-analytics",
|
|
115
|
+
"cloudflare-web-analytics"
|
|
116
|
+
];
|
|
117
|
+
var DUAA_CONSENT_REQUIRED_VENDORS = [
|
|
118
|
+
// Advertising
|
|
119
|
+
"google-ads",
|
|
120
|
+
"google-analytics",
|
|
121
|
+
"ga4",
|
|
122
|
+
"meta-pixel",
|
|
123
|
+
"facebook-pixel",
|
|
124
|
+
"tiktok-pixel",
|
|
125
|
+
"linkedin-insight",
|
|
126
|
+
"twitter-pixel",
|
|
127
|
+
"pinterest-tag",
|
|
128
|
+
"reddit-pixel",
|
|
129
|
+
// Session replay / individual tracking
|
|
130
|
+
"hotjar",
|
|
131
|
+
"fullstory",
|
|
132
|
+
"microsoft-clarity",
|
|
133
|
+
"mouseflow",
|
|
134
|
+
"logrocket",
|
|
135
|
+
// CDPs / marketing automation
|
|
136
|
+
"segment",
|
|
137
|
+
"rudderstack",
|
|
138
|
+
"hubspot",
|
|
139
|
+
"mixpanel",
|
|
140
|
+
"amplitude",
|
|
141
|
+
// AI training crawlers (always require explicit opt-in/out)
|
|
142
|
+
"gptbot",
|
|
143
|
+
"claudebot",
|
|
144
|
+
"anthropic-ai",
|
|
145
|
+
"google-extended",
|
|
146
|
+
"perplexitybot",
|
|
147
|
+
"ccbot",
|
|
148
|
+
"bytespider",
|
|
149
|
+
"applebot-extended"
|
|
150
|
+
];
|
|
151
|
+
var UK_DUAA = {
|
|
152
|
+
id: "UK_DUAA",
|
|
153
|
+
name: "United Kingdom (Data (Use and Access) Act 2025)",
|
|
154
|
+
vendorRules: {
|
|
155
|
+
...Object.fromEntries(DUAA_STATISTICAL_VENDORS.map((v) => [v, "notice"])),
|
|
156
|
+
...Object.fromEntries(DUAA_CONSENT_REQUIRED_VENDORS.map((v) => [v, "consent"]))
|
|
157
|
+
},
|
|
158
|
+
defaultMode: "consent",
|
|
159
|
+
ui: {
|
|
160
|
+
rejectButtonOnFirstLayer: true,
|
|
161
|
+
equalProminence: true,
|
|
162
|
+
honorGPC: false
|
|
163
|
+
},
|
|
164
|
+
countries: ["GB"]
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/jurisdictions/index.ts
|
|
168
|
+
var jurisdictions = {
|
|
169
|
+
UK_DUAA,
|
|
170
|
+
EU_GDPR
|
|
171
|
+
};
|
|
172
|
+
function resolveJurisdictionByCountry(country, fallback = EU_GDPR) {
|
|
173
|
+
if (!country) return fallback;
|
|
174
|
+
const upper = country.toUpperCase();
|
|
175
|
+
for (const j of Object.values(jurisdictions)) {
|
|
176
|
+
if (j.countries?.includes(upper)) return j;
|
|
177
|
+
}
|
|
178
|
+
return fallback;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/resolve.ts
|
|
182
|
+
function resolveCategories(config, jurisdiction) {
|
|
183
|
+
return Object.entries(config.categories).map(([id, def]) => {
|
|
184
|
+
const required = def.required === true;
|
|
185
|
+
const explicit = def.mode;
|
|
186
|
+
const vendors = def.vendors ?? [];
|
|
187
|
+
let mode;
|
|
188
|
+
if (required) {
|
|
189
|
+
mode = "always";
|
|
190
|
+
} else if (explicit) {
|
|
191
|
+
mode = explicit;
|
|
192
|
+
} else if (vendors.length === 0) {
|
|
193
|
+
mode = jurisdiction.defaultMode;
|
|
194
|
+
} else {
|
|
195
|
+
mode = mostRestrictive(
|
|
196
|
+
vendors.map((v) => jurisdiction.vendorRules[v] ?? jurisdiction.defaultMode)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
id,
|
|
201
|
+
required,
|
|
202
|
+
mode,
|
|
203
|
+
default: required ? true : def.default ?? false,
|
|
204
|
+
vendors,
|
|
205
|
+
description: def.description
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
var ORDER = { always: 0, notice: 1, consent: 2 };
|
|
210
|
+
function mostRestrictive(modes) {
|
|
211
|
+
let winner = "always";
|
|
212
|
+
for (const m of modes) {
|
|
213
|
+
if (ORDER[m] > ORDER[winner]) winner = m;
|
|
214
|
+
}
|
|
215
|
+
return winner;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/storage.ts
|
|
219
|
+
var DEFAULT_COOKIE_NAME = "__tb_consent";
|
|
220
|
+
var DEFAULT_MAX_AGE_DAYS = 365;
|
|
221
|
+
function readConsent(options = {}) {
|
|
222
|
+
if (typeof document === "undefined") return null;
|
|
223
|
+
return parseConsentFromHeader(document.cookie, options);
|
|
224
|
+
}
|
|
225
|
+
function parseConsentFromHeader(cookieHeader, options = {}) {
|
|
226
|
+
if (!cookieHeader) return null;
|
|
227
|
+
const name = options.cookieName ?? DEFAULT_COOKIE_NAME;
|
|
228
|
+
const match = cookieHeader.match(new RegExp(`(?:^|; )${name}=([^;]+)`));
|
|
229
|
+
if (!match) return null;
|
|
230
|
+
try {
|
|
231
|
+
const parsed = JSON.parse(decodeURIComponent(match[1]));
|
|
232
|
+
if (isStoredConsent(parsed)) return parsed;
|
|
233
|
+
return null;
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function writeConsent(value, options = {}) {
|
|
239
|
+
if (typeof document === "undefined") return;
|
|
240
|
+
const name = options.cookieName ?? DEFAULT_COOKIE_NAME;
|
|
241
|
+
const maxAge = (options.maxAgeDays ?? DEFAULT_MAX_AGE_DAYS) * 86400;
|
|
242
|
+
const domain = options.domain ? `; Domain=${options.domain}` : "";
|
|
243
|
+
const secure = typeof location !== "undefined" && location.protocol === "https:" ? "; Secure" : "";
|
|
244
|
+
const encoded = encodeURIComponent(JSON.stringify(value));
|
|
245
|
+
document.cookie = `${name}=${encoded}; Path=/; Max-Age=${maxAge}; SameSite=Lax${secure}${domain}`;
|
|
246
|
+
}
|
|
247
|
+
function clearConsent(options = {}) {
|
|
248
|
+
if (typeof document === "undefined") return;
|
|
249
|
+
const name = options.cookieName ?? DEFAULT_COOKIE_NAME;
|
|
250
|
+
const domain = options.domain ? `; Domain=${options.domain}` : "";
|
|
251
|
+
document.cookie = `${name}=; Path=/; Max-Age=0${domain}`;
|
|
252
|
+
}
|
|
253
|
+
function isStoredConsent(value) {
|
|
254
|
+
if (typeof value !== "object" || value === null) return false;
|
|
255
|
+
const v = value;
|
|
256
|
+
return v.v === 1 && typeof v.c === "object" && v.c !== null && typeof v.pv === "string" && typeof v.ts === "number" && typeof v.j === "string";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/store.ts
|
|
260
|
+
var ConsentStore = class {
|
|
261
|
+
state;
|
|
262
|
+
listeners = /* @__PURE__ */ new Set();
|
|
263
|
+
config;
|
|
264
|
+
options;
|
|
265
|
+
constructor(config, options) {
|
|
266
|
+
this.config = config;
|
|
267
|
+
this.options = options;
|
|
268
|
+
const resolved = resolveCategories(config, options.jurisdiction);
|
|
269
|
+
this.state = {
|
|
270
|
+
ready: false,
|
|
271
|
+
isOpen: false,
|
|
272
|
+
decisions: defaultDecisions(resolved),
|
|
273
|
+
resolved,
|
|
274
|
+
storedAt: null
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Read the stored cookie and update state. Safe to call on the server
|
|
279
|
+
* (no-op when `document` is unavailable). Call from a mount effect.
|
|
280
|
+
*/
|
|
281
|
+
hydrate() {
|
|
282
|
+
const stored = readConsent(this.options.storage);
|
|
283
|
+
this.applyHydration(stored);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Hydrate from a raw Cookie header — for server-side rendering.
|
|
287
|
+
* Pass the value of the request's `cookie` header (or `undefined` if absent).
|
|
288
|
+
*/
|
|
289
|
+
hydrateFromHeader(cookieHeader) {
|
|
290
|
+
const stored = parseConsentFromHeader(cookieHeader, this.options.storage);
|
|
291
|
+
this.applyHydration(stored);
|
|
292
|
+
}
|
|
293
|
+
applyHydration(stored) {
|
|
294
|
+
if (stored) {
|
|
295
|
+
this.state = {
|
|
296
|
+
...this.state,
|
|
297
|
+
ready: true,
|
|
298
|
+
isOpen: needsRefresh(stored, this.config),
|
|
299
|
+
decisions: mergeDecisions(this.state.resolved, stored.c),
|
|
300
|
+
storedAt: stored.ts
|
|
301
|
+
};
|
|
302
|
+
} else {
|
|
303
|
+
this.state = {
|
|
304
|
+
...this.state,
|
|
305
|
+
ready: true,
|
|
306
|
+
isOpen: shouldShowBanner(this.state.resolved)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
this.emit();
|
|
310
|
+
}
|
|
311
|
+
getState() {
|
|
312
|
+
return this.state;
|
|
313
|
+
}
|
|
314
|
+
subscribe(fn) {
|
|
315
|
+
this.listeners.add(fn);
|
|
316
|
+
return () => {
|
|
317
|
+
this.listeners.delete(fn);
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
grant(id) {
|
|
321
|
+
this.update({ [id]: true });
|
|
322
|
+
}
|
|
323
|
+
deny(id) {
|
|
324
|
+
if (this.isRequired(id)) return;
|
|
325
|
+
this.update({ [id]: false });
|
|
326
|
+
}
|
|
327
|
+
grantAll() {
|
|
328
|
+
const next = {};
|
|
329
|
+
for (const r of this.state.resolved) next[r.id] = true;
|
|
330
|
+
this.update(next, { close: true });
|
|
331
|
+
}
|
|
332
|
+
denyAll() {
|
|
333
|
+
const next = {};
|
|
334
|
+
for (const r of this.state.resolved) next[r.id] = r.required;
|
|
335
|
+
this.update(next, { close: true });
|
|
336
|
+
}
|
|
337
|
+
/** Persist the current decisions and close the banner. */
|
|
338
|
+
save() {
|
|
339
|
+
const ts = this.persist(this.state.decisions);
|
|
340
|
+
this.state = { ...this.state, isOpen: false, storedAt: ts };
|
|
341
|
+
this.emit();
|
|
342
|
+
}
|
|
343
|
+
open() {
|
|
344
|
+
if (this.state.isOpen) return;
|
|
345
|
+
this.state = { ...this.state, isOpen: true };
|
|
346
|
+
this.emit();
|
|
347
|
+
}
|
|
348
|
+
close() {
|
|
349
|
+
if (!this.state.isOpen) return;
|
|
350
|
+
this.state = { ...this.state, isOpen: false };
|
|
351
|
+
this.emit();
|
|
352
|
+
}
|
|
353
|
+
/** Wipe stored consent and reopen the banner. */
|
|
354
|
+
reset() {
|
|
355
|
+
this.state = {
|
|
356
|
+
...this.state,
|
|
357
|
+
decisions: defaultDecisions(this.state.resolved),
|
|
358
|
+
isOpen: true,
|
|
359
|
+
storedAt: null
|
|
360
|
+
};
|
|
361
|
+
this.emit();
|
|
362
|
+
}
|
|
363
|
+
isRequired(id) {
|
|
364
|
+
return this.state.resolved.find((r) => r.id === id)?.required === true;
|
|
365
|
+
}
|
|
366
|
+
update(partial, opts = {}) {
|
|
367
|
+
const next = { ...this.state.decisions, ...partial };
|
|
368
|
+
const ts = this.persist(next);
|
|
369
|
+
this.state = {
|
|
370
|
+
...this.state,
|
|
371
|
+
decisions: next,
|
|
372
|
+
isOpen: opts.close ? false : this.state.isOpen,
|
|
373
|
+
storedAt: ts
|
|
374
|
+
};
|
|
375
|
+
this.emit();
|
|
376
|
+
}
|
|
377
|
+
persist(decisions) {
|
|
378
|
+
const ts = Date.now();
|
|
379
|
+
const stored = {
|
|
380
|
+
v: 1,
|
|
381
|
+
c: decisions,
|
|
382
|
+
pv: this.config.policy?.version ?? "0",
|
|
383
|
+
ts,
|
|
384
|
+
j: this.options.jurisdiction.id
|
|
385
|
+
};
|
|
386
|
+
writeConsent(stored, this.options.storage);
|
|
387
|
+
return ts;
|
|
388
|
+
}
|
|
389
|
+
emit() {
|
|
390
|
+
if (this.options.applyEffects !== false) {
|
|
391
|
+
this.options.onApply?.(this.state);
|
|
392
|
+
}
|
|
393
|
+
for (const fn of this.listeners) fn(this.state);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
function defaultDecisions(resolved) {
|
|
397
|
+
const out = {};
|
|
398
|
+
for (const r of resolved) out[r.id] = r.required ? true : r.default;
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function mergeDecisions(resolved, stored) {
|
|
402
|
+
const out = {};
|
|
403
|
+
for (const r of resolved) {
|
|
404
|
+
if (r.required) {
|
|
405
|
+
out[r.id] = true;
|
|
406
|
+
} else if (stored[r.id] !== void 0) {
|
|
407
|
+
out[r.id] = stored[r.id];
|
|
408
|
+
} else {
|
|
409
|
+
out[r.id] = r.default;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
function needsRefresh(stored, config) {
|
|
415
|
+
const declared = config.policy?.version;
|
|
416
|
+
if (!declared) return false;
|
|
417
|
+
return stored.pv !== declared;
|
|
418
|
+
}
|
|
419
|
+
function shouldShowBanner(resolved) {
|
|
420
|
+
return resolved.some((r) => r.mode === "consent" && !r.required);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export { ConsentStore, TAG_ATTRIBUTE, applyConsent, clearConsent, defineConsent, isGPCSignaled, jurisdictions, parseConsentFromHeader, readConsent, resolveCategories, resolveJurisdictionByCountry, writeConsent };
|
|
424
|
+
//# sourceMappingURL=index.js.map
|
|
425
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apply.ts","../src/define-consent.ts","../src/gpc.ts","../src/jurisdictions/eu-gdpr.ts","../src/jurisdictions/uk-duaa.ts","../src/jurisdictions/index.ts","../src/resolve.ts","../src/storage.ts","../src/store.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ConsentState } from './store.js'\n\n/**\n * Element attribute used by Tickbox-aware scripts to declare their category.\n *\n * @example\n * ```html\n * <script type=\"text/plain\" data-tb-category=\"analytics\" src=\"plausible.js\"></script>\n * ```\n *\n * On grant, the SDK rewrites `type` to `text/javascript` so the browser executes the script.\n * On deny, the script is left as `text/plain` (browser ignores it).\n */\nexport const TAG_ATTRIBUTE = 'data-tb-category'\n\n/**\n * Apply a consent state to the document by:\n * 1. Activating any `<script type=\"text/plain\" data-tb-category=\"X\">` whose category was granted\n * 2. Calling `gtag('consent', 'update', ...)` if `gtag` is on the global scope\n * 3. Dispatching a `tickbox:consent-changed` CustomEvent for any custom integrations\n *\n * No-op on the server.\n */\nexport function applyConsent(state: ConsentState): void {\n if (typeof document === 'undefined') return\n activateScripts(state.decisions)\n updateConsentMode(state.decisions)\n dispatchEvent(state)\n}\n\nfunction activateScripts(decisions: Record<string, boolean>): void {\n const blocked = document.querySelectorAll(`script[type=\"text/plain\"][${TAG_ATTRIBUTE}]`)\n for (const node of Array.from(blocked)) {\n const category = node.getAttribute(TAG_ATTRIBUTE)\n if (!category || !decisions[category]) continue\n const replacement = document.createElement('script')\n for (const attr of Array.from(node.attributes)) {\n if (attr.name === 'type') continue\n replacement.setAttribute(attr.name, attr.value)\n }\n replacement.text = node.textContent ?? ''\n node.parentNode?.replaceChild(replacement, node)\n }\n}\n\ntype Gtag = (cmd: 'consent', action: 'update', params: Record<string, 'granted' | 'denied'>) => void\n\nfunction updateConsentMode(decisions: Record<string, boolean>): void {\n const win = globalThis as unknown as { gtag?: Gtag }\n if (typeof win.gtag !== 'function') return\n win.gtag('consent', 'update', {\n ad_storage: gv(decisions, 'marketing'),\n ad_user_data: gv(decisions, 'marketing'),\n ad_personalization: gv(decisions, 'marketing'),\n analytics_storage: gv(decisions, 'analytics'),\n functionality_storage: gv(decisions, 'functional', true),\n personalization_storage: gv(decisions, 'preferences', true),\n security_storage: 'granted',\n })\n}\n\nfunction gv(\n decisions: Record<string, boolean>,\n id: string,\n defaultGranted = false,\n): 'granted' | 'denied' {\n const value = decisions[id]\n if (value === undefined) return defaultGranted ? 'granted' : 'denied'\n return value ? 'granted' : 'denied'\n}\n\nfunction dispatchEvent(state: ConsentState): void {\n document.dispatchEvent(\n new CustomEvent('tickbox:consent-changed', {\n detail: { decisions: state.decisions, ts: state.storedAt },\n }),\n )\n}\n","import type { ConsentConfig } from './types.js'\n\n/**\n * Identity helper that narrows the type of a consent config so users get\n * full autocomplete + type-checking in their `consent.config.ts`.\n *\n * @example\n * ```ts\n * import { defineConsent, jurisdictions } from '@tickboxhq/core'\n *\n * export default defineConsent({\n * jurisdiction: jurisdictions.UK_DUAA,\n * categories: {\n * necessary: { required: true },\n * analytics: { vendors: ['plausible'] },\n * },\n * })\n * ```\n */\nexport function defineConsent<const T extends ConsentConfig>(config: T): T {\n return config\n}\n","/**\n * Detect a Global Privacy Control signal from the visitor's browser.\n *\n * GPC is exposed as `navigator.globalPrivacyControl` (boolean) and the\n * `Sec-GPC: 1` HTTP header. The header is server-side only; this helper\n * covers the client-side property.\n *\n * @see https://globalprivacycontrol.org/\n */\nexport function isGPCSignaled(): boolean {\n if (typeof navigator === 'undefined') return false\n return (navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl === true\n}\n","import type { Jurisdiction } from '../types.js'\n\n/**\n * European Union — GDPR + ePrivacy Directive (\"Cookie Law\").\n *\n * EU rules don't have the DUAA \"statistical exemption\" — analytics that\n * involve client-side storage on a user's device generally require opt-in\n * consent, even for first-party privacy-friendly tools (positions vary by\n * DPA; CNIL has been more permissive than others). This preset takes the\n * conservative position: treat all tracking categories as consent-required.\n *\n * UI requirements (EDPB / national DPAs):\n * - \"Reject All\" on first banner layer with equal prominence\n * - GPC: not yet mandatory but increasingly recognised\n */\nexport const EU_GDPR: Jurisdiction = {\n id: 'EU_GDPR',\n name: 'European Union (GDPR / ePrivacy)',\n vendorRules: {},\n defaultMode: 'consent',\n ui: {\n rejectButtonOnFirstLayer: true,\n equalProminence: true,\n honorGPC: false,\n },\n countries: [\n 'AT',\n 'BE',\n 'BG',\n 'HR',\n 'CY',\n 'CZ',\n 'DK',\n 'EE',\n 'FI',\n 'FR',\n 'DE',\n 'GR',\n 'HU',\n 'IE',\n 'IT',\n 'LV',\n 'LT',\n 'LU',\n 'MT',\n 'NL',\n 'PL',\n 'PT',\n 'RO',\n 'SK',\n 'SI',\n 'ES',\n 'SE',\n // EEA additions\n 'IS',\n 'LI',\n 'NO',\n ],\n}\n","import type { Jurisdiction } from '../types.js'\n\n/**\n * Vendors classified as eligible for the DUAA \"statistical purposes\" exemption:\n * privacy-first analytics with aggregated, non-identifying data and no ad features.\n *\n * These do **not** require consent under the UK Data (Use and Access) Act 2025\n * but still require clear notice and an easy way to object.\n *\n * Treat the list as a starting position; specific deployments may still need\n * consent depending on configuration (e.g. session replay, cross-domain tracking).\n */\nconst DUAA_STATISTICAL_VENDORS = [\n 'plausible',\n 'fathom',\n 'simpleanalytics',\n 'pirsch',\n 'goatcounter',\n 'umami',\n 'tinybird-analytics',\n 'cloudflare-web-analytics',\n] as const\n\n/**\n * Vendors that require full opt-in consent under DUAA — anything that touches\n * advertising, individual-level tracking, session replay, or cross-site profiles.\n */\nconst DUAA_CONSENT_REQUIRED_VENDORS = [\n // Advertising\n 'google-ads',\n 'google-analytics',\n 'ga4',\n 'meta-pixel',\n 'facebook-pixel',\n 'tiktok-pixel',\n 'linkedin-insight',\n 'twitter-pixel',\n 'pinterest-tag',\n 'reddit-pixel',\n // Session replay / individual tracking\n 'hotjar',\n 'fullstory',\n 'microsoft-clarity',\n 'mouseflow',\n 'logrocket',\n // CDPs / marketing automation\n 'segment',\n 'rudderstack',\n 'hubspot',\n 'mixpanel',\n 'amplitude',\n // AI training crawlers (always require explicit opt-in/out)\n 'gptbot',\n 'claudebot',\n 'anthropic-ai',\n 'google-extended',\n 'perplexitybot',\n 'ccbot',\n 'bytespider',\n 'applebot-extended',\n] as const\n\n/**\n * United Kingdom — Data (Use and Access) Act 2025 (DUAA).\n *\n * In force from 5 February 2026. Amends PECR to introduce new exemptions for\n * cookies/storage used solely for statistical, security, authentication, and\n * appearance purposes. Advertising and individual-level tracking still require\n * full opt-in consent.\n *\n * UI requirements (ICO):\n * - \"Reject All\" must be on the first banner layer\n * - \"Accept All\" and \"Reject All\" must have equal visual prominence\n * - GPC is not (yet) mandatory in UK guidance; default off.\n *\n * @see https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guide-to-pecr/cookies-and-similar-technologies/\n */\nexport const UK_DUAA: Jurisdiction = {\n id: 'UK_DUAA',\n name: 'United Kingdom (Data (Use and Access) Act 2025)',\n vendorRules: {\n ...Object.fromEntries(DUAA_STATISTICAL_VENDORS.map((v) => [v, 'notice' as const])),\n ...Object.fromEntries(DUAA_CONSENT_REQUIRED_VENDORS.map((v) => [v, 'consent' as const])),\n },\n defaultMode: 'consent',\n ui: {\n rejectButtonOnFirstLayer: true,\n equalProminence: true,\n honorGPC: false,\n },\n countries: ['GB'],\n}\n","import type { Jurisdiction } from '../types.js'\nimport { EU_GDPR } from './eu-gdpr.js'\nimport { UK_DUAA } from './uk-duaa.js'\n\nexport { UK_DUAA } from './uk-duaa.js'\nexport { EU_GDPR } from './eu-gdpr.js'\n\n/**\n * Map of all built-in jurisdiction presets, keyed by their ID for ergonomic\n * lookup: `jurisdictions.UK_DUAA`.\n */\nexport const jurisdictions = {\n UK_DUAA,\n EU_GDPR,\n} as const\n\n/**\n * Resolve a jurisdiction from an ISO 3166-1 alpha-2 country code (e.g. 'GB').\n * Falls back to `EU_GDPR` for any country not explicitly mapped — the safer\n * default for an unknown EEA visitor. Returns `null` when the code is unknown\n * and no fallback is requested.\n */\nexport function resolveJurisdictionByCountry(\n country: string | undefined,\n fallback: Jurisdiction | null = EU_GDPR,\n): Jurisdiction | null {\n if (!country) return fallback\n const upper = country.toUpperCase()\n for (const j of Object.values(jurisdictions)) {\n if (j.countries?.includes(upper)) return j\n }\n return fallback\n}\n","import type { ConsentConfig, ConsentMode, Jurisdiction, ResolvedCategory } from './types.js'\n\n/**\n * Resolve each declared category against the active jurisdiction's vendor rules.\n *\n * For each category, the most-restrictive vendor mode wins:\n * `consent` > `notice` > `always`\n *\n * Categories with no vendors fall back to the jurisdiction's default mode,\n * unless `required: true` (always allowed) or an explicit `mode` override is set.\n */\nexport function resolveCategories(\n config: ConsentConfig,\n jurisdiction: Jurisdiction,\n): ResolvedCategory[] {\n return Object.entries(config.categories).map(([id, def]) => {\n const required = def.required === true\n const explicit = def.mode\n const vendors = def.vendors ?? []\n\n let mode: ConsentMode\n if (required) {\n mode = 'always'\n } else if (explicit) {\n mode = explicit\n } else if (vendors.length === 0) {\n mode = jurisdiction.defaultMode\n } else {\n mode = mostRestrictive(\n vendors.map((v) => jurisdiction.vendorRules[v] ?? jurisdiction.defaultMode),\n )\n }\n\n return {\n id,\n required,\n mode,\n default: required ? true : (def.default ?? false),\n vendors,\n description: def.description,\n }\n })\n}\n\nconst ORDER: Record<ConsentMode, number> = { always: 0, notice: 1, consent: 2 }\n\nfunction mostRestrictive(modes: ConsentMode[]): ConsentMode {\n let winner: ConsentMode = 'always'\n for (const m of modes) {\n if (ORDER[m] > ORDER[winner]) winner = m\n }\n return winner\n}\n","import type { StorageOptions, StoredConsent } from './types.js'\n\nconst DEFAULT_COOKIE_NAME = '__tb_consent'\nconst DEFAULT_MAX_AGE_DAYS = 365\n\n/**\n * Read the stored consent record from `document.cookie`.\n * Returns `null` on the server, when no cookie is set, or when the cookie is malformed.\n */\nexport function readConsent(options: StorageOptions = {}): StoredConsent | null {\n if (typeof document === 'undefined') return null\n return parseConsentFromHeader(document.cookie, options)\n}\n\n/**\n * Parse a stored consent record from a raw `Cookie` header string.\n * Useful on the server: pass the request's `cookie` header.\n * Returns `null` when the cookie isn't present or is malformed.\n */\nexport function parseConsentFromHeader(\n cookieHeader: string | undefined,\n options: StorageOptions = {},\n): StoredConsent | null {\n if (!cookieHeader) return null\n const name = options.cookieName ?? DEFAULT_COOKIE_NAME\n const match = cookieHeader.match(new RegExp(`(?:^|; )${name}=([^;]+)`))\n if (!match) return null\n try {\n const parsed = JSON.parse(decodeURIComponent(match[1]!)) as unknown\n if (isStoredConsent(parsed)) return parsed\n return null\n } catch {\n return null\n }\n}\n\n/**\n * Persist a consent record to `document.cookie`.\n * No-op on the server.\n */\nexport function writeConsent(value: StoredConsent, options: StorageOptions = {}): void {\n if (typeof document === 'undefined') return\n const name = options.cookieName ?? DEFAULT_COOKIE_NAME\n const maxAge = (options.maxAgeDays ?? DEFAULT_MAX_AGE_DAYS) * 86_400\n const domain = options.domain ? `; Domain=${options.domain}` : ''\n const secure = typeof location !== 'undefined' && location.protocol === 'https:' ? '; Secure' : ''\n const encoded = encodeURIComponent(JSON.stringify(value))\n document.cookie = `${name}=${encoded}; Path=/; Max-Age=${maxAge}; SameSite=Lax${secure}${domain}`\n}\n\n/** Clear the stored consent cookie. */\nexport function clearConsent(options: StorageOptions = {}): void {\n if (typeof document === 'undefined') return\n const name = options.cookieName ?? DEFAULT_COOKIE_NAME\n const domain = options.domain ? `; Domain=${options.domain}` : ''\n document.cookie = `${name}=; Path=/; Max-Age=0${domain}`\n}\n\nfunction isStoredConsent(value: unknown): value is StoredConsent {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return (\n v.v === 1 &&\n typeof v.c === 'object' &&\n v.c !== null &&\n typeof v.pv === 'string' &&\n typeof v.ts === 'number' &&\n typeof v.j === 'string'\n )\n}\n","import { resolveCategories } from './resolve.js'\nimport { parseConsentFromHeader, readConsent, writeConsent } from './storage.js'\nimport type {\n ConsentConfig,\n Jurisdiction,\n ResolvedCategory,\n StorageOptions,\n StoredConsent,\n} from './types.js'\n\nexport type ConsentState = {\n /** True after the store has hydrated from the cookie (client-only). */\n ready: boolean\n /** True when the banner / preference centre should be visible. */\n isOpen: boolean\n /** Map of category ID → granted (true) / denied (false). */\n decisions: Record<string, boolean>\n /** Resolved categories for the active jurisdiction. */\n resolved: ResolvedCategory[]\n /** Timestamp of the most-recent stored decision, if any. */\n storedAt: number | null\n}\n\ntype Listener = (state: ConsentState) => void\n\nexport type StoreOptions = {\n /** Storage options forwarded to the cookie reader/writer. */\n storage?: StorageOptions\n /** When `true`, side-effects like script rewriting fire on state changes. Defaults to true. */\n applyEffects?: boolean\n /** Custom side-effect handler. Receives `(state, resolved)` whenever decisions change. */\n onApply?: (state: ConsentState) => void\n /** Active jurisdiction. Required — pass `config.jurisdiction` after resolving 'auto'. */\n jurisdiction: Jurisdiction\n}\n\n/**\n * Framework-agnostic consent state machine.\n *\n * The store hydrates from the cookie on first read, keeps decisions in memory,\n * and broadcasts changes to subscribers. Adapters (`@tickboxhq/react`,\n * `@tickboxhq/vue`) bind to it via their idiomatic reactivity primitive.\n */\nexport class ConsentStore {\n private state: ConsentState\n private readonly listeners = new Set<Listener>()\n private readonly config: ConsentConfig\n private readonly options: StoreOptions\n\n constructor(config: ConsentConfig, options: StoreOptions) {\n this.config = config\n this.options = options\n const resolved = resolveCategories(config, options.jurisdiction)\n this.state = {\n ready: false,\n isOpen: false,\n decisions: defaultDecisions(resolved),\n resolved,\n storedAt: null,\n }\n }\n\n /**\n * Read the stored cookie and update state. Safe to call on the server\n * (no-op when `document` is unavailable). Call from a mount effect.\n */\n hydrate(): void {\n const stored = readConsent(this.options.storage)\n this.applyHydration(stored)\n }\n\n /**\n * Hydrate from a raw Cookie header — for server-side rendering.\n * Pass the value of the request's `cookie` header (or `undefined` if absent).\n */\n hydrateFromHeader(cookieHeader: string | undefined): void {\n const stored = parseConsentFromHeader(cookieHeader, this.options.storage)\n this.applyHydration(stored)\n }\n\n private applyHydration(stored: StoredConsent | null): void {\n if (stored) {\n this.state = {\n ...this.state,\n ready: true,\n isOpen: needsRefresh(stored, this.config),\n decisions: mergeDecisions(this.state.resolved, stored.c),\n storedAt: stored.ts,\n }\n } else {\n this.state = {\n ...this.state,\n ready: true,\n isOpen: shouldShowBanner(this.state.resolved),\n }\n }\n this.emit()\n }\n\n getState(): ConsentState {\n return this.state\n }\n\n subscribe(fn: Listener): () => void {\n this.listeners.add(fn)\n return () => {\n this.listeners.delete(fn)\n }\n }\n\n grant(id: string): void {\n this.update({ [id]: true })\n }\n\n deny(id: string): void {\n if (this.isRequired(id)) return\n this.update({ [id]: false })\n }\n\n grantAll(): void {\n const next: Record<string, boolean> = {}\n for (const r of this.state.resolved) next[r.id] = true\n this.update(next, { close: true })\n }\n\n denyAll(): void {\n const next: Record<string, boolean> = {}\n for (const r of this.state.resolved) next[r.id] = r.required\n this.update(next, { close: true })\n }\n\n /** Persist the current decisions and close the banner. */\n save(): void {\n const ts = this.persist(this.state.decisions)\n this.state = { ...this.state, isOpen: false, storedAt: ts }\n this.emit()\n }\n\n open(): void {\n if (this.state.isOpen) return\n this.state = { ...this.state, isOpen: true }\n this.emit()\n }\n\n close(): void {\n if (!this.state.isOpen) return\n this.state = { ...this.state, isOpen: false }\n this.emit()\n }\n\n /** Wipe stored consent and reopen the banner. */\n reset(): void {\n this.state = {\n ...this.state,\n decisions: defaultDecisions(this.state.resolved),\n isOpen: true,\n storedAt: null,\n }\n this.emit()\n }\n\n isRequired(id: string): boolean {\n return this.state.resolved.find((r) => r.id === id)?.required === true\n }\n\n private update(partial: Record<string, boolean>, opts: { close?: boolean } = {}): void {\n const next = { ...this.state.decisions, ...partial }\n const ts = this.persist(next)\n this.state = {\n ...this.state,\n decisions: next,\n isOpen: opts.close ? false : this.state.isOpen,\n storedAt: ts,\n }\n this.emit()\n }\n\n private persist(decisions: Record<string, boolean>): number {\n const ts = Date.now()\n const stored: StoredConsent = {\n v: 1,\n c: decisions,\n pv: this.config.policy?.version ?? '0',\n ts,\n j: this.options.jurisdiction.id,\n }\n writeConsent(stored, this.options.storage)\n return ts\n }\n\n private emit(): void {\n if (this.options.applyEffects !== false) {\n this.options.onApply?.(this.state)\n }\n for (const fn of this.listeners) fn(this.state)\n }\n}\n\nfunction defaultDecisions(resolved: ResolvedCategory[]): Record<string, boolean> {\n const out: Record<string, boolean> = {}\n for (const r of resolved) out[r.id] = r.required ? true : r.default\n return out\n}\n\nfunction mergeDecisions(\n resolved: ResolvedCategory[],\n stored: Record<string, boolean>,\n): Record<string, boolean> {\n const out: Record<string, boolean> = {}\n for (const r of resolved) {\n if (r.required) {\n out[r.id] = true\n } else if (stored[r.id] !== undefined) {\n out[r.id] = stored[r.id]!\n } else {\n out[r.id] = r.default\n }\n }\n return out\n}\n\nfunction needsRefresh(stored: StoredConsent, config: ConsentConfig): boolean {\n const declared = config.policy?.version\n if (!declared) return false\n return stored.pv !== declared\n}\n\nfunction shouldShowBanner(resolved: ResolvedCategory[]): boolean {\n return resolved.some((r) => r.mode === 'consent' && !r.required)\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { E as EU_GDPR, U as UK_DUAA, j as jurisdictions, r as resolveJurisdictionByCountry } from '../index-D_Xt5NFB.js';
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/jurisdictions/eu-gdpr.ts
|
|
2
|
+
var EU_GDPR = {
|
|
3
|
+
id: "EU_GDPR",
|
|
4
|
+
name: "European Union (GDPR / ePrivacy)",
|
|
5
|
+
vendorRules: {},
|
|
6
|
+
defaultMode: "consent",
|
|
7
|
+
ui: {
|
|
8
|
+
rejectButtonOnFirstLayer: true,
|
|
9
|
+
equalProminence: true,
|
|
10
|
+
honorGPC: false
|
|
11
|
+
},
|
|
12
|
+
countries: [
|
|
13
|
+
"AT",
|
|
14
|
+
"BE",
|
|
15
|
+
"BG",
|
|
16
|
+
"HR",
|
|
17
|
+
"CY",
|
|
18
|
+
"CZ",
|
|
19
|
+
"DK",
|
|
20
|
+
"EE",
|
|
21
|
+
"FI",
|
|
22
|
+
"FR",
|
|
23
|
+
"DE",
|
|
24
|
+
"GR",
|
|
25
|
+
"HU",
|
|
26
|
+
"IE",
|
|
27
|
+
"IT",
|
|
28
|
+
"LV",
|
|
29
|
+
"LT",
|
|
30
|
+
"LU",
|
|
31
|
+
"MT",
|
|
32
|
+
"NL",
|
|
33
|
+
"PL",
|
|
34
|
+
"PT",
|
|
35
|
+
"RO",
|
|
36
|
+
"SK",
|
|
37
|
+
"SI",
|
|
38
|
+
"ES",
|
|
39
|
+
"SE",
|
|
40
|
+
// EEA additions
|
|
41
|
+
"IS",
|
|
42
|
+
"LI",
|
|
43
|
+
"NO"
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/jurisdictions/uk-duaa.ts
|
|
48
|
+
var DUAA_STATISTICAL_VENDORS = [
|
|
49
|
+
"plausible",
|
|
50
|
+
"fathom",
|
|
51
|
+
"simpleanalytics",
|
|
52
|
+
"pirsch",
|
|
53
|
+
"goatcounter",
|
|
54
|
+
"umami",
|
|
55
|
+
"tinybird-analytics",
|
|
56
|
+
"cloudflare-web-analytics"
|
|
57
|
+
];
|
|
58
|
+
var DUAA_CONSENT_REQUIRED_VENDORS = [
|
|
59
|
+
// Advertising
|
|
60
|
+
"google-ads",
|
|
61
|
+
"google-analytics",
|
|
62
|
+
"ga4",
|
|
63
|
+
"meta-pixel",
|
|
64
|
+
"facebook-pixel",
|
|
65
|
+
"tiktok-pixel",
|
|
66
|
+
"linkedin-insight",
|
|
67
|
+
"twitter-pixel",
|
|
68
|
+
"pinterest-tag",
|
|
69
|
+
"reddit-pixel",
|
|
70
|
+
// Session replay / individual tracking
|
|
71
|
+
"hotjar",
|
|
72
|
+
"fullstory",
|
|
73
|
+
"microsoft-clarity",
|
|
74
|
+
"mouseflow",
|
|
75
|
+
"logrocket",
|
|
76
|
+
// CDPs / marketing automation
|
|
77
|
+
"segment",
|
|
78
|
+
"rudderstack",
|
|
79
|
+
"hubspot",
|
|
80
|
+
"mixpanel",
|
|
81
|
+
"amplitude",
|
|
82
|
+
// AI training crawlers (always require explicit opt-in/out)
|
|
83
|
+
"gptbot",
|
|
84
|
+
"claudebot",
|
|
85
|
+
"anthropic-ai",
|
|
86
|
+
"google-extended",
|
|
87
|
+
"perplexitybot",
|
|
88
|
+
"ccbot",
|
|
89
|
+
"bytespider",
|
|
90
|
+
"applebot-extended"
|
|
91
|
+
];
|
|
92
|
+
var UK_DUAA = {
|
|
93
|
+
id: "UK_DUAA",
|
|
94
|
+
name: "United Kingdom (Data (Use and Access) Act 2025)",
|
|
95
|
+
vendorRules: {
|
|
96
|
+
...Object.fromEntries(DUAA_STATISTICAL_VENDORS.map((v) => [v, "notice"])),
|
|
97
|
+
...Object.fromEntries(DUAA_CONSENT_REQUIRED_VENDORS.map((v) => [v, "consent"]))
|
|
98
|
+
},
|
|
99
|
+
defaultMode: "consent",
|
|
100
|
+
ui: {
|
|
101
|
+
rejectButtonOnFirstLayer: true,
|
|
102
|
+
equalProminence: true,
|
|
103
|
+
honorGPC: false
|
|
104
|
+
},
|
|
105
|
+
countries: ["GB"]
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/jurisdictions/index.ts
|
|
109
|
+
var jurisdictions = {
|
|
110
|
+
UK_DUAA,
|
|
111
|
+
EU_GDPR
|
|
112
|
+
};
|
|
113
|
+
function resolveJurisdictionByCountry(country, fallback = EU_GDPR) {
|
|
114
|
+
if (!country) return fallback;
|
|
115
|
+
const upper = country.toUpperCase();
|
|
116
|
+
for (const j of Object.values(jurisdictions)) {
|
|
117
|
+
if (j.countries?.includes(upper)) return j;
|
|
118
|
+
}
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { EU_GDPR, UK_DUAA, jurisdictions, resolveJurisdictionByCountry };
|
|
123
|
+
//# sourceMappingURL=index.js.map
|
|
124
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/jurisdictions/eu-gdpr.ts","../../src/jurisdictions/uk-duaa.ts","../../src/jurisdictions/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Jurisdiction } from '../types.js'\n\n/**\n * European Union — GDPR + ePrivacy Directive (\"Cookie Law\").\n *\n * EU rules don't have the DUAA \"statistical exemption\" — analytics that\n * involve client-side storage on a user's device generally require opt-in\n * consent, even for first-party privacy-friendly tools (positions vary by\n * DPA; CNIL has been more permissive than others). This preset takes the\n * conservative position: treat all tracking categories as consent-required.\n *\n * UI requirements (EDPB / national DPAs):\n * - \"Reject All\" on first banner layer with equal prominence\n * - GPC: not yet mandatory but increasingly recognised\n */\nexport const EU_GDPR: Jurisdiction = {\n id: 'EU_GDPR',\n name: 'European Union (GDPR / ePrivacy)',\n vendorRules: {},\n defaultMode: 'consent',\n ui: {\n rejectButtonOnFirstLayer: true,\n equalProminence: true,\n honorGPC: false,\n },\n countries: [\n 'AT',\n 'BE',\n 'BG',\n 'HR',\n 'CY',\n 'CZ',\n 'DK',\n 'EE',\n 'FI',\n 'FR',\n 'DE',\n 'GR',\n 'HU',\n 'IE',\n 'IT',\n 'LV',\n 'LT',\n 'LU',\n 'MT',\n 'NL',\n 'PL',\n 'PT',\n 'RO',\n 'SK',\n 'SI',\n 'ES',\n 'SE',\n // EEA additions\n 'IS',\n 'LI',\n 'NO',\n ],\n}\n","import type { Jurisdiction } from '../types.js'\n\n/**\n * Vendors classified as eligible for the DUAA \"statistical purposes\" exemption:\n * privacy-first analytics with aggregated, non-identifying data and no ad features.\n *\n * These do **not** require consent under the UK Data (Use and Access) Act 2025\n * but still require clear notice and an easy way to object.\n *\n * Treat the list as a starting position; specific deployments may still need\n * consent depending on configuration (e.g. session replay, cross-domain tracking).\n */\nconst DUAA_STATISTICAL_VENDORS = [\n 'plausible',\n 'fathom',\n 'simpleanalytics',\n 'pirsch',\n 'goatcounter',\n 'umami',\n 'tinybird-analytics',\n 'cloudflare-web-analytics',\n] as const\n\n/**\n * Vendors that require full opt-in consent under DUAA — anything that touches\n * advertising, individual-level tracking, session replay, or cross-site profiles.\n */\nconst DUAA_CONSENT_REQUIRED_VENDORS = [\n // Advertising\n 'google-ads',\n 'google-analytics',\n 'ga4',\n 'meta-pixel',\n 'facebook-pixel',\n 'tiktok-pixel',\n 'linkedin-insight',\n 'twitter-pixel',\n 'pinterest-tag',\n 'reddit-pixel',\n // Session replay / individual tracking\n 'hotjar',\n 'fullstory',\n 'microsoft-clarity',\n 'mouseflow',\n 'logrocket',\n // CDPs / marketing automation\n 'segment',\n 'rudderstack',\n 'hubspot',\n 'mixpanel',\n 'amplitude',\n // AI training crawlers (always require explicit opt-in/out)\n 'gptbot',\n 'claudebot',\n 'anthropic-ai',\n 'google-extended',\n 'perplexitybot',\n 'ccbot',\n 'bytespider',\n 'applebot-extended',\n] as const\n\n/**\n * United Kingdom — Data (Use and Access) Act 2025 (DUAA).\n *\n * In force from 5 February 2026. Amends PECR to introduce new exemptions for\n * cookies/storage used solely for statistical, security, authentication, and\n * appearance purposes. Advertising and individual-level tracking still require\n * full opt-in consent.\n *\n * UI requirements (ICO):\n * - \"Reject All\" must be on the first banner layer\n * - \"Accept All\" and \"Reject All\" must have equal visual prominence\n * - GPC is not (yet) mandatory in UK guidance; default off.\n *\n * @see https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guide-to-pecr/cookies-and-similar-technologies/\n */\nexport const UK_DUAA: Jurisdiction = {\n id: 'UK_DUAA',\n name: 'United Kingdom (Data (Use and Access) Act 2025)',\n vendorRules: {\n ...Object.fromEntries(DUAA_STATISTICAL_VENDORS.map((v) => [v, 'notice' as const])),\n ...Object.fromEntries(DUAA_CONSENT_REQUIRED_VENDORS.map((v) => [v, 'consent' as const])),\n },\n defaultMode: 'consent',\n ui: {\n rejectButtonOnFirstLayer: true,\n equalProminence: true,\n honorGPC: false,\n },\n countries: ['GB'],\n}\n","import type { Jurisdiction } from '../types.js'\nimport { EU_GDPR } from './eu-gdpr.js'\nimport { UK_DUAA } from './uk-duaa.js'\n\nexport { UK_DUAA } from './uk-duaa.js'\nexport { EU_GDPR } from './eu-gdpr.js'\n\n/**\n * Map of all built-in jurisdiction presets, keyed by their ID for ergonomic\n * lookup: `jurisdictions.UK_DUAA`.\n */\nexport const jurisdictions = {\n UK_DUAA,\n EU_GDPR,\n} as const\n\n/**\n * Resolve a jurisdiction from an ISO 3166-1 alpha-2 country code (e.g. 'GB').\n * Falls back to `EU_GDPR` for any country not explicitly mapped — the safer\n * default for an unknown EEA visitor. Returns `null` when the code is unknown\n * and no fallback is requested.\n */\nexport function resolveJurisdictionByCountry(\n country: string | undefined,\n fallback: Jurisdiction | null = EU_GDPR,\n): Jurisdiction | null {\n if (!country) return fallback\n const upper = country.toUpperCase()\n for (const j of Object.values(jurisdictions)) {\n if (j.countries?.includes(upper)) return j\n }\n return fallback\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tickboxhq/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Core types, jurisdictions, and storage primitives for Tickbox consent management",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://tickbox.dev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/tickboxhq/tickbox.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./jurisdictions": {
|
|
21
|
+
"types": "./dist/jurisdictions/index.d.ts",
|
|
22
|
+
"import": "./dist/jurisdictions/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.3.5",
|
|
35
|
+
"typescript": "^5.7.2",
|
|
36
|
+
"vitest": "^2.1.8"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:watch": "vitest",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
43
|
+
}
|
|
44
|
+
}
|