@structured-world/vue-privacy 0.0.0-development
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 +190 -0
- package/README.md +228 -0
- package/dist/__tests__/types.test.d.ts +1 -0
- package/dist/core/consent-manager.d.ts +64 -0
- package/dist/core/gtag.d.ts +41 -0
- package/dist/core/storage.d.ts +31 -0
- package/dist/core/types.d.ts +147 -0
- package/dist/geo/index.d.ts +45 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +496 -0
- package/dist/index.js.map +1 -0
- package/dist/quasar/index.d.ts +39 -0
- package/dist/quasar/index.js +19 -0
- package/dist/quasar/index.js.map +1 -0
- package/dist/vitepress/index.d.ts +44 -0
- package/dist/vitepress/index.js +24 -0
- package/dist/vitepress/index.js.map +1 -0
- package/dist/vue/ConsentBanner.vue.d.ts +17 -0
- package/dist/vue/index.d.ts +63 -0
- package/dist/vue/index.js +175 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/vue-privacy.css +127 -0
- package/package.json +109 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent categories that can be managed
|
|
3
|
+
*/
|
|
4
|
+
export interface ConsentCategories {
|
|
5
|
+
/** Analytics cookies (e.g., Google Analytics) */
|
|
6
|
+
analytics: boolean;
|
|
7
|
+
/** Marketing/advertising cookies */
|
|
8
|
+
marketing: boolean;
|
|
9
|
+
/** Functional cookies (preferences, etc.) */
|
|
10
|
+
functional: boolean;
|
|
11
|
+
/** Strictly necessary cookies (always true, cannot be disabled) */
|
|
12
|
+
necessary: true;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Google Consent Mode v2 signals
|
|
16
|
+
* @see https://developers.google.com/tag-platform/security/guides/consent
|
|
17
|
+
*/
|
|
18
|
+
export interface GoogleConsentSignals {
|
|
19
|
+
/** Controls Google Analytics cookies */
|
|
20
|
+
analytics_storage: "granted" | "denied";
|
|
21
|
+
/** Controls advertising cookies */
|
|
22
|
+
ad_storage: "granted" | "denied";
|
|
23
|
+
/** Controls whether user data can be sent to Google for ads */
|
|
24
|
+
ad_user_data: "granted" | "denied";
|
|
25
|
+
/** Controls personalized advertising */
|
|
26
|
+
ad_personalization: "granted" | "denied";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Stored consent state
|
|
30
|
+
*/
|
|
31
|
+
export interface StoredConsent {
|
|
32
|
+
/** Consent categories */
|
|
33
|
+
categories: Omit<ConsentCategories, "necessary">;
|
|
34
|
+
/** Timestamp when consent was given */
|
|
35
|
+
timestamp: number;
|
|
36
|
+
/** Version of the consent configuration */
|
|
37
|
+
version: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Geo-detection result
|
|
41
|
+
*/
|
|
42
|
+
export interface GeoDetectionResult {
|
|
43
|
+
/** Whether the user is in the EU */
|
|
44
|
+
isEU: boolean;
|
|
45
|
+
/** Country code (ISO 3166-1 alpha-2) */
|
|
46
|
+
countryCode?: string;
|
|
47
|
+
/** Detection method used */
|
|
48
|
+
method: "cloudflare" | "api" | "fallback" | "manual";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Geo-detection provider interface
|
|
52
|
+
*/
|
|
53
|
+
export interface GeoDetector {
|
|
54
|
+
/** Detect if user is in the EU */
|
|
55
|
+
detect(): Promise<GeoDetectionResult>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Banner UI configuration
|
|
59
|
+
*/
|
|
60
|
+
export interface BannerConfig {
|
|
61
|
+
/** Banner title */
|
|
62
|
+
title: string;
|
|
63
|
+
/** Main message text */
|
|
64
|
+
message: string;
|
|
65
|
+
/** Accept all button text */
|
|
66
|
+
acceptAll: string;
|
|
67
|
+
/** Reject all button text */
|
|
68
|
+
rejectAll: string;
|
|
69
|
+
/** Customize preferences button text */
|
|
70
|
+
customize?: string;
|
|
71
|
+
/** Privacy policy link */
|
|
72
|
+
privacyLink?: string;
|
|
73
|
+
/** Privacy policy link text */
|
|
74
|
+
privacyLinkText?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Main plugin configuration
|
|
78
|
+
*/
|
|
79
|
+
export interface ConsentConfig {
|
|
80
|
+
/** Google Analytics measurement ID (G-XXXXXXXXXX) */
|
|
81
|
+
gaId?: string;
|
|
82
|
+
/** Consent categories to manage */
|
|
83
|
+
categories?: Partial<Omit<ConsentCategories, "necessary">>;
|
|
84
|
+
/** Banner UI configuration */
|
|
85
|
+
banner?: Partial<BannerConfig>;
|
|
86
|
+
/** Cookie configuration */
|
|
87
|
+
cookie?: {
|
|
88
|
+
/** Cookie name for storing consent */
|
|
89
|
+
name?: string;
|
|
90
|
+
/** Cookie expiry in days */
|
|
91
|
+
expiry?: number;
|
|
92
|
+
/** Cookie domain */
|
|
93
|
+
domain?: string;
|
|
94
|
+
/** Cookie path */
|
|
95
|
+
path?: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* EU detection mode:
|
|
99
|
+
* - 'auto': Try Cloudflare header, fallback to IP API
|
|
100
|
+
* - 'cloudflare': Only use Cloudflare header
|
|
101
|
+
* - 'api': Only use IP API
|
|
102
|
+
* - 'always': Always show banner (treat all as EU)
|
|
103
|
+
* - 'never': Never show banner (treat all as non-EU)
|
|
104
|
+
*/
|
|
105
|
+
euDetection?: "auto" | "cloudflare" | "api" | "always" | "never";
|
|
106
|
+
/** Custom geo-detection provider */
|
|
107
|
+
geoDetector?: GeoDetector;
|
|
108
|
+
/** Consent version (changing this resets consent for all users) */
|
|
109
|
+
version?: string;
|
|
110
|
+
/** Callback when consent changes */
|
|
111
|
+
onConsentChange?: (consent: StoredConsent) => void;
|
|
112
|
+
/** Callback when banner is shown */
|
|
113
|
+
onBannerShow?: () => void;
|
|
114
|
+
/** Callback when banner is hidden */
|
|
115
|
+
onBannerHide?: () => void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Required cookie configuration (with defaults)
|
|
119
|
+
*/
|
|
120
|
+
export interface CookieConfigDefaults {
|
|
121
|
+
name: string;
|
|
122
|
+
expiry: number;
|
|
123
|
+
path: string;
|
|
124
|
+
domain?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Required banner configuration (with defaults)
|
|
128
|
+
*/
|
|
129
|
+
export interface BannerConfigDefaults {
|
|
130
|
+
title: string;
|
|
131
|
+
message: string;
|
|
132
|
+
acceptAll: string;
|
|
133
|
+
rejectAll: string;
|
|
134
|
+
customize: string;
|
|
135
|
+
privacyLink: string;
|
|
136
|
+
privacyLinkText: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Default configuration values
|
|
140
|
+
*/
|
|
141
|
+
export declare const DEFAULT_CONFIG: {
|
|
142
|
+
categories: Omit<ConsentCategories, "necessary">;
|
|
143
|
+
banner: BannerConfigDefaults;
|
|
144
|
+
cookie: CookieConfigDefaults;
|
|
145
|
+
euDetection: "auto" | "cloudflare" | "api" | "always" | "never";
|
|
146
|
+
version: string;
|
|
147
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { GeoDetector, GeoDetectionResult } from "../core/types";
|
|
2
|
+
/**
|
|
3
|
+
* Cloudflare geo-detection using headers
|
|
4
|
+
*
|
|
5
|
+
* Requires Cloudflare Worker or Transform Rule to set X-Is-EU-Country header
|
|
6
|
+
*/
|
|
7
|
+
export declare class CloudflareGeoDetector implements GeoDetector {
|
|
8
|
+
private headerName;
|
|
9
|
+
constructor(headerName?: string);
|
|
10
|
+
detect(): Promise<GeoDetectionResult>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* IP API geo-detection using ipapi.co
|
|
14
|
+
*
|
|
15
|
+
* Free tier: 1000 requests/day
|
|
16
|
+
* No API key required for basic usage
|
|
17
|
+
*/
|
|
18
|
+
export declare class IPAPIGeoDetector implements GeoDetector {
|
|
19
|
+
private apiUrl;
|
|
20
|
+
constructor(apiUrl?: string);
|
|
21
|
+
detect(): Promise<GeoDetectionResult>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fallback detector that uses browser timezone heuristics
|
|
25
|
+
*
|
|
26
|
+
* Not 100% accurate but works without external requests
|
|
27
|
+
*/
|
|
28
|
+
export declare class TimezoneGeoDetector implements GeoDetector {
|
|
29
|
+
private euTimezones;
|
|
30
|
+
detect(): Promise<GeoDetectionResult>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Auto-detection: tries Cloudflare first, then IP API, then timezone fallback
|
|
34
|
+
*/
|
|
35
|
+
export declare class AutoGeoDetector implements GeoDetector {
|
|
36
|
+
private cloudflare;
|
|
37
|
+
private ipapi;
|
|
38
|
+
private timezone;
|
|
39
|
+
constructor();
|
|
40
|
+
detect(): Promise<GeoDetectionResult>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a geo-detector based on mode
|
|
44
|
+
*/
|
|
45
|
+
export declare function createGeoDetector(mode: "auto" | "cloudflare" | "api" | "always" | "never"): GeoDetector;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @structured-world/vue-privacy
|
|
3
|
+
*
|
|
4
|
+
* GDPR-compliant cookie consent with Google Consent Mode v2 support
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createConsentManager } from '@structured-world/vue-privacy';
|
|
9
|
+
*
|
|
10
|
+
* const manager = createConsentManager({
|
|
11
|
+
* gaId: 'G-XXXXXXXXXX',
|
|
12
|
+
* euDetection: 'auto',
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* await manager.init();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export { ConsentManager, createConsentManager } from "./core/consent-manager";
|
|
19
|
+
export { getStoredConsent, storeConsent, clearConsent } from "./core/storage";
|
|
20
|
+
export { initGtag, setConsentDefaults, updateConsent, loadGtagScript, initGoogleAnalytics, categoriesToGoogleSignals, } from "./core/gtag";
|
|
21
|
+
export { DEFAULT_CONFIG } from "./core/types";
|
|
22
|
+
export { CloudflareGeoDetector, IPAPIGeoDetector, TimezoneGeoDetector, AutoGeoDetector, createGeoDetector, } from "./geo/index";
|
|
23
|
+
export type { ConsentConfig, ConsentCategories, StoredConsent, GoogleConsentSignals, GeoDetector, GeoDetectionResult, BannerConfig, } from "./core/types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
categories: {
|
|
6
|
+
analytics: false,
|
|
7
|
+
marketing: false,
|
|
8
|
+
functional: true
|
|
9
|
+
},
|
|
10
|
+
banner: {
|
|
11
|
+
title: "Cookie Consent",
|
|
12
|
+
message: "We use cookies to improve your experience. You can accept all cookies or customize your preferences.",
|
|
13
|
+
acceptAll: "Accept All",
|
|
14
|
+
rejectAll: "Reject All",
|
|
15
|
+
customize: "Customize",
|
|
16
|
+
privacyLink: "/privacy",
|
|
17
|
+
privacyLinkText: "Privacy Policy"
|
|
18
|
+
},
|
|
19
|
+
cookie: {
|
|
20
|
+
name: "consent_preferences",
|
|
21
|
+
expiry: 365,
|
|
22
|
+
path: "/"
|
|
23
|
+
},
|
|
24
|
+
euDetection: "auto",
|
|
25
|
+
version: "1.0"
|
|
26
|
+
};
|
|
27
|
+
function getCookie(name) {
|
|
28
|
+
if (typeof document === "undefined") return null;
|
|
29
|
+
const cookies = document.cookie.split(";");
|
|
30
|
+
for (const cookie of cookies) {
|
|
31
|
+
const [key, value] = cookie.trim().split("=");
|
|
32
|
+
if (key === name) {
|
|
33
|
+
return decodeURIComponent(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function setCookie(name, value, options = {}) {
|
|
39
|
+
if (typeof document === "undefined") return;
|
|
40
|
+
const { expiry = 365, domain, path = "/", sameSite = "Lax", secure = false } = options;
|
|
41
|
+
let cookieString = `${name}=${encodeURIComponent(value)}`;
|
|
42
|
+
if (expiry) {
|
|
43
|
+
const date = /* @__PURE__ */ new Date();
|
|
44
|
+
date.setTime(date.getTime() + expiry * 24 * 60 * 60 * 1e3);
|
|
45
|
+
cookieString += `; expires=${date.toUTCString()}`;
|
|
46
|
+
}
|
|
47
|
+
if (domain) {
|
|
48
|
+
cookieString += `; domain=${domain}`;
|
|
49
|
+
}
|
|
50
|
+
cookieString += `; path=${path}`;
|
|
51
|
+
cookieString += `; SameSite=${sameSite}`;
|
|
52
|
+
if (secure || sameSite === "None") {
|
|
53
|
+
cookieString += "; Secure";
|
|
54
|
+
}
|
|
55
|
+
document.cookie = cookieString;
|
|
56
|
+
}
|
|
57
|
+
function deleteCookie(name, path = "/") {
|
|
58
|
+
if (typeof document === "undefined") return;
|
|
59
|
+
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}`;
|
|
60
|
+
}
|
|
61
|
+
function getStoredConsent(config = {}) {
|
|
62
|
+
var _a;
|
|
63
|
+
const cookieName = ((_a = config.cookie) == null ? void 0 : _a.name) ?? DEFAULT_CONFIG.cookie.name;
|
|
64
|
+
const version = config.version ?? DEFAULT_CONFIG.version;
|
|
65
|
+
const raw = getCookie(cookieName);
|
|
66
|
+
if (!raw) return null;
|
|
67
|
+
try {
|
|
68
|
+
const stored = JSON.parse(raw);
|
|
69
|
+
if (stored.version !== version) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return stored;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function storeConsent(consent, config = {}) {
|
|
78
|
+
const cookieConfig = {
|
|
79
|
+
...DEFAULT_CONFIG.cookie,
|
|
80
|
+
...config.cookie
|
|
81
|
+
};
|
|
82
|
+
const version = config.version ?? DEFAULT_CONFIG.version;
|
|
83
|
+
const stored = {
|
|
84
|
+
categories: consent.categories,
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
version
|
|
87
|
+
};
|
|
88
|
+
setCookie(cookieConfig.name, JSON.stringify(stored), {
|
|
89
|
+
expiry: cookieConfig.expiry,
|
|
90
|
+
domain: cookieConfig.domain,
|
|
91
|
+
path: cookieConfig.path
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function clearConsent(config = {}) {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
const cookieName = ((_a = config.cookie) == null ? void 0 : _a.name) ?? DEFAULT_CONFIG.cookie.name;
|
|
97
|
+
const path = ((_b = config.cookie) == null ? void 0 : _b.path) ?? DEFAULT_CONFIG.cookie.path;
|
|
98
|
+
deleteCookie(cookieName, path);
|
|
99
|
+
}
|
|
100
|
+
function initGtag() {
|
|
101
|
+
if (typeof window === "undefined") return;
|
|
102
|
+
window.dataLayer = window.dataLayer || [];
|
|
103
|
+
if (typeof window.gtag !== "function") {
|
|
104
|
+
window.gtag = function gtag(...args) {
|
|
105
|
+
window.dataLayer.push(args);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function categoriesToGoogleSignals(categories) {
|
|
110
|
+
return {
|
|
111
|
+
analytics_storage: categories.analytics ? "granted" : "denied",
|
|
112
|
+
ad_storage: categories.marketing ? "granted" : "denied",
|
|
113
|
+
ad_user_data: categories.marketing ? "granted" : "denied",
|
|
114
|
+
ad_personalization: categories.marketing ? "granted" : "denied"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function setConsentDefaults(signals, waitForUpdate = 500) {
|
|
118
|
+
initGtag();
|
|
119
|
+
if (typeof window === "undefined") return;
|
|
120
|
+
window.gtag("consent", "default", {
|
|
121
|
+
...signals,
|
|
122
|
+
wait_for_update: waitForUpdate
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function updateConsent(signals) {
|
|
126
|
+
initGtag();
|
|
127
|
+
if (typeof window === "undefined") return;
|
|
128
|
+
window.gtag("consent", "update", signals);
|
|
129
|
+
}
|
|
130
|
+
function loadGtagScript(gaId) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
if (typeof document === "undefined") {
|
|
133
|
+
resolve();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (document.querySelector(
|
|
137
|
+
`script[src*="googletagmanager.com/gtag/js?id=${gaId}"]`
|
|
138
|
+
)) {
|
|
139
|
+
resolve();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const script = document.createElement("script");
|
|
143
|
+
script.async = true;
|
|
144
|
+
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaId}`;
|
|
145
|
+
script.onload = () => resolve();
|
|
146
|
+
script.onerror = () => reject(new Error(`Failed to load gtag.js for ${gaId}`));
|
|
147
|
+
document.head.appendChild(script);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function initGoogleAnalytics(gaId, defaultDenied = true) {
|
|
151
|
+
initGtag();
|
|
152
|
+
if (defaultDenied) {
|
|
153
|
+
setConsentDefaults({
|
|
154
|
+
analytics_storage: "denied",
|
|
155
|
+
ad_storage: "denied",
|
|
156
|
+
ad_user_data: "denied",
|
|
157
|
+
ad_personalization: "denied"
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
setConsentDefaults({
|
|
161
|
+
analytics_storage: "granted",
|
|
162
|
+
ad_storage: "granted",
|
|
163
|
+
ad_user_data: "granted",
|
|
164
|
+
ad_personalization: "granted"
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
await loadGtagScript(gaId);
|
|
168
|
+
if (typeof window !== "undefined") {
|
|
169
|
+
window.gtag("js", /* @__PURE__ */ new Date());
|
|
170
|
+
window.gtag("config", gaId);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
class CloudflareGeoDetector {
|
|
174
|
+
constructor(headerName = "X-Is-EU-Country") {
|
|
175
|
+
__publicField(this, "headerName");
|
|
176
|
+
this.headerName = headerName;
|
|
177
|
+
}
|
|
178
|
+
async detect() {
|
|
179
|
+
if (typeof document === "undefined") {
|
|
180
|
+
return { isEU: false, method: "cloudflare" };
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const response = await fetch(window.location.href, {
|
|
184
|
+
method: "HEAD",
|
|
185
|
+
cache: "no-store"
|
|
186
|
+
});
|
|
187
|
+
const isEUHeader = response.headers.get(this.headerName);
|
|
188
|
+
const countryCode = response.headers.get("CF-IPCountry") ?? void 0;
|
|
189
|
+
if (isEUHeader !== null) {
|
|
190
|
+
return {
|
|
191
|
+
isEU: isEUHeader.toLowerCase() === "true",
|
|
192
|
+
countryCode,
|
|
193
|
+
method: "cloudflare"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
throw new Error("Cloudflare header not present");
|
|
197
|
+
} catch {
|
|
198
|
+
throw new Error("Cloudflare geo-detection failed");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
class IPAPIGeoDetector {
|
|
203
|
+
constructor(apiUrl = "https://ipapi.co/json/") {
|
|
204
|
+
__publicField(this, "apiUrl");
|
|
205
|
+
this.apiUrl = apiUrl;
|
|
206
|
+
}
|
|
207
|
+
async detect() {
|
|
208
|
+
try {
|
|
209
|
+
const response = await fetch(this.apiUrl);
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
return {
|
|
212
|
+
isEU: data.in_eu === true,
|
|
213
|
+
countryCode: data.country_code,
|
|
214
|
+
method: "api"
|
|
215
|
+
};
|
|
216
|
+
} catch {
|
|
217
|
+
throw new Error("IP API geo-detection failed");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
class TimezoneGeoDetector {
|
|
222
|
+
constructor() {
|
|
223
|
+
// EU timezones (not exhaustive but covers most)
|
|
224
|
+
__publicField(this, "euTimezones", /* @__PURE__ */ new Set([
|
|
225
|
+
"Europe/Amsterdam",
|
|
226
|
+
"Europe/Andorra",
|
|
227
|
+
"Europe/Athens",
|
|
228
|
+
"Europe/Berlin",
|
|
229
|
+
"Europe/Bratislava",
|
|
230
|
+
"Europe/Brussels",
|
|
231
|
+
"Europe/Bucharest",
|
|
232
|
+
"Europe/Budapest",
|
|
233
|
+
"Europe/Copenhagen",
|
|
234
|
+
"Europe/Dublin",
|
|
235
|
+
"Europe/Helsinki",
|
|
236
|
+
"Europe/Lisbon",
|
|
237
|
+
"Europe/Ljubljana",
|
|
238
|
+
"Europe/Luxembourg",
|
|
239
|
+
"Europe/Madrid",
|
|
240
|
+
"Europe/Malta",
|
|
241
|
+
"Europe/Monaco",
|
|
242
|
+
"Europe/Oslo",
|
|
243
|
+
"Europe/Paris",
|
|
244
|
+
"Europe/Prague",
|
|
245
|
+
"Europe/Riga",
|
|
246
|
+
"Europe/Rome",
|
|
247
|
+
"Europe/San_Marino",
|
|
248
|
+
"Europe/Sarajevo",
|
|
249
|
+
"Europe/Skopje",
|
|
250
|
+
"Europe/Sofia",
|
|
251
|
+
"Europe/Stockholm",
|
|
252
|
+
"Europe/Tallinn",
|
|
253
|
+
"Europe/Tirane",
|
|
254
|
+
"Europe/Vaduz",
|
|
255
|
+
"Europe/Vatican",
|
|
256
|
+
"Europe/Vienna",
|
|
257
|
+
"Europe/Vilnius",
|
|
258
|
+
"Europe/Warsaw",
|
|
259
|
+
"Europe/Zagreb",
|
|
260
|
+
"Atlantic/Canary",
|
|
261
|
+
"Atlantic/Faroe",
|
|
262
|
+
"Atlantic/Madeira"
|
|
263
|
+
]));
|
|
264
|
+
}
|
|
265
|
+
async detect() {
|
|
266
|
+
try {
|
|
267
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
268
|
+
const isEU = this.euTimezones.has(timezone);
|
|
269
|
+
return {
|
|
270
|
+
isEU,
|
|
271
|
+
method: "fallback"
|
|
272
|
+
};
|
|
273
|
+
} catch {
|
|
274
|
+
return {
|
|
275
|
+
isEU: true,
|
|
276
|
+
method: "fallback"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
class AutoGeoDetector {
|
|
282
|
+
constructor() {
|
|
283
|
+
__publicField(this, "cloudflare");
|
|
284
|
+
__publicField(this, "ipapi");
|
|
285
|
+
__publicField(this, "timezone");
|
|
286
|
+
this.cloudflare = new CloudflareGeoDetector();
|
|
287
|
+
this.ipapi = new IPAPIGeoDetector();
|
|
288
|
+
this.timezone = new TimezoneGeoDetector();
|
|
289
|
+
}
|
|
290
|
+
async detect() {
|
|
291
|
+
try {
|
|
292
|
+
return await this.cloudflare.detect();
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
return await this.ipapi.detect();
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
return await this.timezone.detect();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function createGeoDetector(mode) {
|
|
303
|
+
switch (mode) {
|
|
304
|
+
case "cloudflare":
|
|
305
|
+
return new CloudflareGeoDetector();
|
|
306
|
+
case "api":
|
|
307
|
+
return new IPAPIGeoDetector();
|
|
308
|
+
case "always":
|
|
309
|
+
return {
|
|
310
|
+
detect: async () => ({ isEU: true, method: "manual" })
|
|
311
|
+
};
|
|
312
|
+
case "never":
|
|
313
|
+
return {
|
|
314
|
+
detect: async () => ({ isEU: false, method: "manual" })
|
|
315
|
+
};
|
|
316
|
+
case "auto":
|
|
317
|
+
default:
|
|
318
|
+
return new AutoGeoDetector();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
class ConsentManager {
|
|
322
|
+
constructor(config = {}) {
|
|
323
|
+
__publicField(this, "config");
|
|
324
|
+
__publicField(this, "initialized", false);
|
|
325
|
+
__publicField(this, "isEU", null);
|
|
326
|
+
__publicField(this, "showBannerCallback", null);
|
|
327
|
+
__publicField(this, "hideBannerCallback", null);
|
|
328
|
+
this.config = {
|
|
329
|
+
...config,
|
|
330
|
+
categories: { ...DEFAULT_CONFIG.categories, ...config.categories },
|
|
331
|
+
banner: { ...DEFAULT_CONFIG.banner, ...config.banner },
|
|
332
|
+
cookie: { ...DEFAULT_CONFIG.cookie, ...config.cookie }
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Register callback to show banner
|
|
337
|
+
*/
|
|
338
|
+
onShowBanner(callback) {
|
|
339
|
+
this.showBannerCallback = callback;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Register callback to hide banner
|
|
343
|
+
*/
|
|
344
|
+
onHideBanner(callback) {
|
|
345
|
+
this.hideBannerCallback = callback;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Initialize consent manager
|
|
349
|
+
*/
|
|
350
|
+
async init() {
|
|
351
|
+
var _a, _b, _c, _d;
|
|
352
|
+
if (this.initialized) return;
|
|
353
|
+
this.initialized = true;
|
|
354
|
+
const stored = getStoredConsent(this.config);
|
|
355
|
+
if (stored) {
|
|
356
|
+
await this.applyConsent(stored.categories);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const detector = this.config.geoDetector ?? createGeoDetector(this.config.euDetection ?? "auto");
|
|
360
|
+
const geoResult = await detector.detect();
|
|
361
|
+
this.isEU = geoResult.isEU;
|
|
362
|
+
if (this.isEU) {
|
|
363
|
+
if (this.config.gaId) {
|
|
364
|
+
await initGoogleAnalytics(this.config.gaId, true);
|
|
365
|
+
}
|
|
366
|
+
(_a = this.showBannerCallback) == null ? void 0 : _a.call(this);
|
|
367
|
+
(_c = (_b = this.config).onBannerShow) == null ? void 0 : _c.call(_b);
|
|
368
|
+
} else {
|
|
369
|
+
const grantedCategories = {
|
|
370
|
+
analytics: true,
|
|
371
|
+
marketing: ((_d = this.config.categories) == null ? void 0 : _d.marketing) ?? false,
|
|
372
|
+
functional: true
|
|
373
|
+
};
|
|
374
|
+
await this.applyConsent(grantedCategories);
|
|
375
|
+
storeConsent({ categories: grantedCategories }, this.config);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Apply consent settings
|
|
380
|
+
*/
|
|
381
|
+
async applyConsent(categories) {
|
|
382
|
+
var _a, _b;
|
|
383
|
+
if (this.config.gaId) {
|
|
384
|
+
await initGoogleAnalytics(this.config.gaId, !categories.analytics);
|
|
385
|
+
}
|
|
386
|
+
const signals = categoriesToGoogleSignals(categories);
|
|
387
|
+
updateConsent(signals);
|
|
388
|
+
(_b = (_a = this.config).onConsentChange) == null ? void 0 : _b.call(_a, {
|
|
389
|
+
categories,
|
|
390
|
+
timestamp: Date.now(),
|
|
391
|
+
version: this.config.version ?? DEFAULT_CONFIG.version
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Accept all cookies
|
|
396
|
+
*/
|
|
397
|
+
async acceptAll() {
|
|
398
|
+
var _a, _b, _c;
|
|
399
|
+
const categories = {
|
|
400
|
+
analytics: true,
|
|
401
|
+
marketing: true,
|
|
402
|
+
functional: true
|
|
403
|
+
};
|
|
404
|
+
await this.applyConsent(categories);
|
|
405
|
+
storeConsent({ categories }, this.config);
|
|
406
|
+
(_a = this.hideBannerCallback) == null ? void 0 : _a.call(this);
|
|
407
|
+
(_c = (_b = this.config).onBannerHide) == null ? void 0 : _c.call(_b);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Reject all non-essential cookies
|
|
411
|
+
*/
|
|
412
|
+
async rejectAll() {
|
|
413
|
+
var _a, _b, _c;
|
|
414
|
+
const categories = {
|
|
415
|
+
analytics: false,
|
|
416
|
+
marketing: false,
|
|
417
|
+
functional: true
|
|
418
|
+
// Functional is always allowed
|
|
419
|
+
};
|
|
420
|
+
await this.applyConsent(categories);
|
|
421
|
+
storeConsent({ categories }, this.config);
|
|
422
|
+
(_a = this.hideBannerCallback) == null ? void 0 : _a.call(this);
|
|
423
|
+
(_c = (_b = this.config).onBannerHide) == null ? void 0 : _c.call(_b);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Save custom preferences
|
|
427
|
+
*/
|
|
428
|
+
async savePreferences(categories) {
|
|
429
|
+
var _a, _b, _c;
|
|
430
|
+
const finalCategories = {
|
|
431
|
+
analytics: categories.analytics ?? false,
|
|
432
|
+
marketing: categories.marketing ?? false,
|
|
433
|
+
functional: categories.functional ?? true
|
|
434
|
+
};
|
|
435
|
+
await this.applyConsent(finalCategories);
|
|
436
|
+
storeConsent({ categories: finalCategories }, this.config);
|
|
437
|
+
(_a = this.hideBannerCallback) == null ? void 0 : _a.call(this);
|
|
438
|
+
(_c = (_b = this.config).onBannerHide) == null ? void 0 : _c.call(_b);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get current consent state
|
|
442
|
+
*/
|
|
443
|
+
getConsent() {
|
|
444
|
+
return getStoredConsent(this.config);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Check if user has made a consent choice
|
|
448
|
+
*/
|
|
449
|
+
hasConsent() {
|
|
450
|
+
return getStoredConsent(this.config) !== null;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Reset consent (show banner again)
|
|
454
|
+
*/
|
|
455
|
+
resetConsent() {
|
|
456
|
+
var _a, _b, _c;
|
|
457
|
+
clearConsent(this.config);
|
|
458
|
+
(_a = this.showBannerCallback) == null ? void 0 : _a.call(this);
|
|
459
|
+
(_c = (_b = this.config).onBannerShow) == null ? void 0 : _c.call(_b);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Check if user is detected as EU
|
|
463
|
+
*/
|
|
464
|
+
isEUUser() {
|
|
465
|
+
return this.isEU;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get configuration
|
|
469
|
+
*/
|
|
470
|
+
getConfig() {
|
|
471
|
+
return this.config;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function createConsentManager(config = {}) {
|
|
475
|
+
return new ConsentManager(config);
|
|
476
|
+
}
|
|
477
|
+
export {
|
|
478
|
+
AutoGeoDetector,
|
|
479
|
+
CloudflareGeoDetector,
|
|
480
|
+
ConsentManager,
|
|
481
|
+
DEFAULT_CONFIG,
|
|
482
|
+
IPAPIGeoDetector,
|
|
483
|
+
TimezoneGeoDetector,
|
|
484
|
+
categoriesToGoogleSignals,
|
|
485
|
+
clearConsent,
|
|
486
|
+
createConsentManager,
|
|
487
|
+
createGeoDetector,
|
|
488
|
+
getStoredConsent,
|
|
489
|
+
initGoogleAnalytics,
|
|
490
|
+
initGtag,
|
|
491
|
+
loadGtagScript,
|
|
492
|
+
setConsentDefaults,
|
|
493
|
+
storeConsent,
|
|
494
|
+
updateConsent
|
|
495
|
+
};
|
|
496
|
+
//# sourceMappingURL=index.js.map
|