@modernconsent/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/chunk-UZ3JLA4T.js +23 -0
- package/dist/chunk-UZ3JLA4T.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +454 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver-CKoWIVlu.d.ts +243 -0
- package/dist/resolver.d.ts +1 -0
- package/dist/resolver.js +3 -0
- package/dist/resolver.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kévin Schnekenburger
|
|
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,23 @@
|
|
|
1
|
+
// src/resolver.ts
|
|
2
|
+
var _resolvers = [];
|
|
3
|
+
function addResolver(resolver) {
|
|
4
|
+
_resolvers.push(resolver);
|
|
5
|
+
return () => {
|
|
6
|
+
const idx = _resolvers.indexOf(resolver);
|
|
7
|
+
if (idx !== -1) _resolvers.splice(idx, 1);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function resolveVendor(name) {
|
|
11
|
+
for (let i = _resolvers.length - 1; i >= 0; i--) {
|
|
12
|
+
const result = _resolvers[i](name);
|
|
13
|
+
if (result) return result;
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
function __resetResolvers() {
|
|
18
|
+
_resolvers.length = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { __resetResolvers, addResolver, resolveVendor };
|
|
22
|
+
//# sourceMappingURL=chunk-UZ3JLA4T.js.map
|
|
23
|
+
//# sourceMappingURL=chunk-UZ3JLA4T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resolver.ts"],"names":[],"mappings":";AASA,IAAM,aAA+B,EAAC;AAS/B,SAAS,YAAY,QAAA,EAAsC;AAChE,EAAA,UAAA,CAAW,KAAK,QAAQ,CAAA;AACxB,EAAA,OAAO,MAAM;AACX,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAA;AACvC,IAAA,IAAI,GAAA,KAAQ,EAAA,EAAI,UAAA,CAAW,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EAC1C,CAAA;AACF;AAMO,SAAS,cAAc,IAAA,EAAwC;AACpE,EAAA,KAAA,IAAS,IAAI,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC/C,IAAA,MAAM,MAAA,GAAS,UAAA,CAAW,CAAC,CAAA,CAAE,IAAI,CAAA;AACjC,IAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,EACrB;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,gBAAA,GAAyB;AACvC,EAAA,UAAA,CAAW,MAAA,GAAS,CAAA;AACtB","file":"chunk-UZ3JLA4T.js","sourcesContent":["// core/src/resolver.ts\nimport type { VendorLoader } from './registry';\n\n/**\n * A resolver maps a vendor name to a loader function.\n * Return `undefined` to pass resolution to the next resolver in the chain.\n */\nexport type VendorResolver = (name: string) => VendorLoader | undefined;\n\nconst _resolvers: VendorResolver[] = [];\n\n/**\n * Register a vendor resolver. Resolvers are tried in LIFO order\n * (last registered = highest priority), following the convention\n * from Vite/Rollup where later plugins override earlier ones.\n *\n * @returns A cleanup function that removes this resolver from the chain.\n */\nexport function addResolver(resolver: VendorResolver): () => void {\n _resolvers.push(resolver);\n return () => {\n const idx = _resolvers.indexOf(resolver);\n if (idx !== -1) _resolvers.splice(idx, 1);\n };\n}\n\n/**\n * Resolve a vendor name to a loader by walking the resolver chain (LIFO).\n * Returns `undefined` if no resolver can handle the name.\n */\nexport function resolveVendor(name: string): VendorLoader | undefined {\n for (let i = _resolvers.length - 1; i >= 0; i--) {\n const result = _resolvers[i](name);\n if (result) return result;\n }\n return undefined;\n}\n\n/** @internal — test helper only */\nexport function __resetResolvers(): void {\n _resolvers.length = 0;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { m as ConfigCommand, q as ConsentEventMap, C as ConsentState, l as McCommand, M as McConfig, k as McVendor, S as ServiceMetadata, a as Store, V as Vendor, n as VendorCommand, j as VendorConfig, g as VendorLoader, e as activateService, b as addResolver, f as clearVendorArtifacts, c as consentState, p as emitter, h as hasAnswered, t as initMcLayer, i as isPanelOpen, o as openPanel, d as registerService, r as resolveVendor, s as servicesList } from './resolver-CKoWIVlu.js';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
consentLayer: any[];
|
|
6
|
+
dataLayer: any[];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare function setConsent(id: string, allowed: boolean): void;
|
|
10
|
+
declare function acceptAll(): void;
|
|
11
|
+
declare function denyAll(): void;
|
|
12
|
+
|
|
13
|
+
declare const generateUUID: () => string;
|
|
14
|
+
|
|
15
|
+
export { acceptAll, denyAll, generateUUID, setConsent };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { addResolver, resolveVendor } from './chunk-UZ3JLA4T.js';
|
|
2
|
+
export { addResolver, resolveVendor } from './chunk-UZ3JLA4T.js';
|
|
3
|
+
|
|
4
|
+
// src/utils/store.ts
|
|
5
|
+
var Store = class {
|
|
6
|
+
value;
|
|
7
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
8
|
+
constructor(initialValue) {
|
|
9
|
+
this.value = initialValue;
|
|
10
|
+
}
|
|
11
|
+
get() {
|
|
12
|
+
return this.value;
|
|
13
|
+
}
|
|
14
|
+
set(newValue) {
|
|
15
|
+
if (this.value !== newValue) {
|
|
16
|
+
this.value = newValue;
|
|
17
|
+
this.notify();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
update(updater) {
|
|
21
|
+
this.set(updater(this.value));
|
|
22
|
+
}
|
|
23
|
+
subscribe(callback) {
|
|
24
|
+
this.subscribers.add(callback);
|
|
25
|
+
callback(this.value);
|
|
26
|
+
return () => this.subscribers.delete(callback);
|
|
27
|
+
}
|
|
28
|
+
notify() {
|
|
29
|
+
this.subscribers.forEach((cb) => cb(this.value));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/utils/uuid.ts
|
|
34
|
+
function manualUUIDv4() {
|
|
35
|
+
const bytes = new Uint8Array(16);
|
|
36
|
+
crypto.getRandomValues(bytes);
|
|
37
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
38
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
39
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
40
|
+
return [
|
|
41
|
+
hex.slice(0, 8),
|
|
42
|
+
hex.slice(8, 12),
|
|
43
|
+
hex.slice(12, 16),
|
|
44
|
+
hex.slice(16, 20),
|
|
45
|
+
hex.slice(20)
|
|
46
|
+
].join("-");
|
|
47
|
+
}
|
|
48
|
+
var generateUUID = () => typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : manualUUIDv4();
|
|
49
|
+
|
|
50
|
+
// src/state.ts
|
|
51
|
+
var DEFAULT_STORAGE_KEY = "mc_consent_state";
|
|
52
|
+
function escapeCookieName(name) {
|
|
53
|
+
return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
54
|
+
}
|
|
55
|
+
var getCookie = (name) => {
|
|
56
|
+
if (typeof document === "undefined") return null;
|
|
57
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${escapeCookieName(name)}=([^;]*)`));
|
|
58
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
59
|
+
};
|
|
60
|
+
var setCookie = (name, value, options = {}) => {
|
|
61
|
+
if (typeof document === "undefined") return;
|
|
62
|
+
const { days = 365, path = "/", domain, sameSite = "Lax", secure = false } = options;
|
|
63
|
+
const expires = /* @__PURE__ */ new Date();
|
|
64
|
+
expires.setDate(expires.getDate() + days);
|
|
65
|
+
let cookieString = `${name}=${encodeURIComponent(value)};expires=${expires.toUTCString()};path=${path};SameSite=${sameSite}`;
|
|
66
|
+
if (domain) {
|
|
67
|
+
const hostname = typeof window !== "undefined" ? window.location.hostname : "";
|
|
68
|
+
if (hostname && !hostname.endsWith(domain.replace(/^\./, ""))) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`[modern-consent] cookieDomain "${domain}" does not match current hostname "${hostname}". The cookie will be silently rejected by the browser. Remove cookieDomain for local development.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
cookieString += `;domain=${domain}`;
|
|
74
|
+
}
|
|
75
|
+
if (secure || sameSite === "None") cookieString += `;Secure`;
|
|
76
|
+
document.cookie = cookieString;
|
|
77
|
+
};
|
|
78
|
+
var consentState = new Store({});
|
|
79
|
+
var hasAnswered = new Store(false);
|
|
80
|
+
var servicesList = new Store([]);
|
|
81
|
+
var isPanelOpen = new Store(false);
|
|
82
|
+
var openPanel = () => isPanelOpen.set(true);
|
|
83
|
+
var _cookieUnsubs = [];
|
|
84
|
+
var initState = (config) => {
|
|
85
|
+
const cookieName = config?.cookieName || DEFAULT_STORAGE_KEY;
|
|
86
|
+
const cookieDomain = config?.cookieDomain;
|
|
87
|
+
const consentVersion = config?.consentVersion;
|
|
88
|
+
let initialData = { consent: {}, answered: false };
|
|
89
|
+
if (typeof window !== "undefined") {
|
|
90
|
+
const stored = getCookie(cookieName);
|
|
91
|
+
if (stored) {
|
|
92
|
+
try {
|
|
93
|
+
initialData = JSON.parse(stored);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error("[modern-consent] Error parsing consent cookie", e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
consentState.set(initialData.consent || {});
|
|
100
|
+
hasAnswered.set(initialData.answered || false);
|
|
101
|
+
if (initialData.answered && consentVersion && initialData.version !== consentVersion) {
|
|
102
|
+
hasAnswered.set(false);
|
|
103
|
+
isPanelOpen.set(true);
|
|
104
|
+
}
|
|
105
|
+
if (typeof window !== "undefined") {
|
|
106
|
+
_cookieUnsubs.forEach((fn) => fn());
|
|
107
|
+
_cookieUnsubs = [];
|
|
108
|
+
const saveToCookie = () => {
|
|
109
|
+
const state = {
|
|
110
|
+
consent: consentState.get(),
|
|
111
|
+
answered: hasAnswered.get(),
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
version: consentVersion,
|
|
114
|
+
consentId: generateUUID()
|
|
115
|
+
};
|
|
116
|
+
setCookie(cookieName, JSON.stringify(state), {
|
|
117
|
+
domain: cookieDomain,
|
|
118
|
+
secure: window.location.protocol === "https:"
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
_cookieUnsubs.push(consentState.subscribe(saveToCookie));
|
|
122
|
+
_cookieUnsubs.push(hasAnswered.subscribe(saveToCookie));
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/registry.ts
|
|
127
|
+
var loaders = /* @__PURE__ */ new Map();
|
|
128
|
+
var loadedVendors = /* @__PURE__ */ new Map();
|
|
129
|
+
var vendorsConfig = /* @__PURE__ */ new Map();
|
|
130
|
+
function clearVendorArtifacts(id) {
|
|
131
|
+
const vendor = loadedVendors.get(id);
|
|
132
|
+
if (!vendor?.artifacts) return;
|
|
133
|
+
const config = vendorsConfig.get(id);
|
|
134
|
+
const cookieNames = typeof vendor.artifacts === "function" ? vendor.artifacts(config) : vendor.artifacts;
|
|
135
|
+
const domain = window._modernConsentConfig?.cookieDomain;
|
|
136
|
+
cookieNames.forEach((name) => {
|
|
137
|
+
document.cookie = `${name}=; max-age=0; path=/`;
|
|
138
|
+
if (domain) {
|
|
139
|
+
document.cookie = `${name}=; max-age=0; path=/; domain=${domain}`;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function registerService(args) {
|
|
144
|
+
const { id, category, loader, config = {} } = args;
|
|
145
|
+
if (loaders.has(id)) return;
|
|
146
|
+
loaders.set(id, loader);
|
|
147
|
+
loader().then((module) => {
|
|
148
|
+
const vendor = module.default || module;
|
|
149
|
+
loadedVendors.set(id, vendor);
|
|
150
|
+
if (config !== void 0) {
|
|
151
|
+
vendorsConfig.set(id, config);
|
|
152
|
+
}
|
|
153
|
+
if (vendor?.setup) {
|
|
154
|
+
vendor.setup(config);
|
|
155
|
+
}
|
|
156
|
+
if (vendor?.link && vendor.link.length > 0) {
|
|
157
|
+
vendor.link.forEach((link) => {
|
|
158
|
+
if (link.condition && !link.condition({ vendorConfig: config, consentConfig: window._modernConsentConfig })) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const linkedLoader = resolveVendor(link.vendor);
|
|
162
|
+
if (linkedLoader) {
|
|
163
|
+
registerService({
|
|
164
|
+
id: link.vendor,
|
|
165
|
+
category,
|
|
166
|
+
config: link.config ?? {},
|
|
167
|
+
loader: linkedLoader
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
console.warn(`[modern-consent] Dependency "${link.vendor}" not found for "${id}"`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
servicesList.update((list) => {
|
|
175
|
+
if (list.some((s) => s.id === id)) return list;
|
|
176
|
+
return [
|
|
177
|
+
...list,
|
|
178
|
+
{
|
|
179
|
+
id,
|
|
180
|
+
name: vendor.name,
|
|
181
|
+
description: vendor.description,
|
|
182
|
+
category: vendor.category,
|
|
183
|
+
purposeLabel: vendor.purposeLabel,
|
|
184
|
+
purposeDescription: vendor.purposeDescription,
|
|
185
|
+
loaded: false,
|
|
186
|
+
requireConsent: vendor.requireConsent
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
});
|
|
190
|
+
checkAutoActivation(id);
|
|
191
|
+
}).catch((err) => {
|
|
192
|
+
console.error(`[modern-consent] Failed to load vendor "${id}":`, err);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function checkAutoActivation(id) {
|
|
196
|
+
const vendor = loadedVendors.get(id);
|
|
197
|
+
if (vendor && !vendor.requireConsent) {
|
|
198
|
+
activateService(id);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const currentConsent = consentState.get();
|
|
202
|
+
const answered = hasAnswered.get();
|
|
203
|
+
if (answered && currentConsent[id] === void 0) {
|
|
204
|
+
isPanelOpen.set(true);
|
|
205
|
+
}
|
|
206
|
+
if (currentConsent[id]) {
|
|
207
|
+
activateService(id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function activateService(id) {
|
|
211
|
+
if (typeof window === "undefined") return;
|
|
212
|
+
let vendor = loadedVendors.get(id);
|
|
213
|
+
if (!vendor) {
|
|
214
|
+
const loader = loaders.get(id);
|
|
215
|
+
if (loader) {
|
|
216
|
+
const module = await loader();
|
|
217
|
+
vendor = module.default || module;
|
|
218
|
+
if (vendor) loadedVendors.set(id, vendor);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (!vendor) return;
|
|
222
|
+
const isSoft = window._modernConsentConfig?.consentOnly === true;
|
|
223
|
+
const list = servicesList.get();
|
|
224
|
+
const meta = list.find((s) => s.id === id);
|
|
225
|
+
if (!isSoft && vendor.init && meta && !meta.loaded) {
|
|
226
|
+
vendor.init(vendorsConfig.get(id));
|
|
227
|
+
servicesList.update((l) => l.map((s) => s.id === id ? { ...s, loaded: true } : s));
|
|
228
|
+
vendor.event?.forEach((ev) => {
|
|
229
|
+
if (ev.name === "onAccept") {
|
|
230
|
+
try {
|
|
231
|
+
ev.callback();
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (!isSoft && vendor.domSelector && vendor.render) {
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
document.querySelectorAll(vendor.domSelector).forEach((el) => {
|
|
240
|
+
const htmlElement = el;
|
|
241
|
+
if (htmlElement.dataset.loaded !== "true" && vendor?.render) {
|
|
242
|
+
htmlElement.innerHTML = vendor.render(htmlElement.dataset);
|
|
243
|
+
htmlElement.dataset.loaded = "true";
|
|
244
|
+
htmlElement.classList.remove("mc-placeholder-pending");
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}, 50);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/emitter.ts
|
|
252
|
+
var ConsentEmitter = class {
|
|
253
|
+
listeners = /* @__PURE__ */ new Map();
|
|
254
|
+
on(event, cb) {
|
|
255
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
256
|
+
this.listeners.get(event).add(cb);
|
|
257
|
+
return () => this.listeners.get(event)?.delete(cb);
|
|
258
|
+
}
|
|
259
|
+
off(event, cb) {
|
|
260
|
+
this.listeners.get(event)?.delete(cb);
|
|
261
|
+
}
|
|
262
|
+
emit(event, data) {
|
|
263
|
+
this.listeners.get(event)?.forEach((cb) => {
|
|
264
|
+
try {
|
|
265
|
+
cb(data);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
var emitter = new ConsentEmitter();
|
|
272
|
+
|
|
273
|
+
// src/consent.ts
|
|
274
|
+
function pushConsentEvent(consent) {
|
|
275
|
+
if (typeof window === "undefined") return;
|
|
276
|
+
const event = {
|
|
277
|
+
event: "consent_update",
|
|
278
|
+
consent_state: consent
|
|
279
|
+
};
|
|
280
|
+
window.consentLayer = window.consentLayer || [];
|
|
281
|
+
window.consentLayer.push(event);
|
|
282
|
+
if (window._modernConsentConfig?.pushDataLayer) {
|
|
283
|
+
window.dataLayer = window.dataLayer || [];
|
|
284
|
+
window.dataLayer.push(event);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function emitConsentSaved(consent) {
|
|
288
|
+
emitter.emit("consent:saved", {
|
|
289
|
+
consentId: generateUUID(),
|
|
290
|
+
timestamp: Date.now(),
|
|
291
|
+
version: typeof window !== "undefined" ? window._modernConsentConfig?.consentVersion : void 0,
|
|
292
|
+
consent
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function setConsent(id, allowed) {
|
|
296
|
+
const wasPreviouslyLoaded = servicesList.get().find((s) => s.id === id)?.loaded ?? false;
|
|
297
|
+
consentState.update((s) => ({ ...s, [id]: allowed }));
|
|
298
|
+
hasAnswered.set(true);
|
|
299
|
+
emitter.emit("consent:update", {
|
|
300
|
+
vendor: id,
|
|
301
|
+
status: allowed ? "granted" : "denied"
|
|
302
|
+
});
|
|
303
|
+
const updatedConsent = consentState.get();
|
|
304
|
+
pushConsentEvent(updatedConsent);
|
|
305
|
+
emitConsentSaved(updatedConsent);
|
|
306
|
+
if (allowed) {
|
|
307
|
+
activateService(id);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (wasPreviouslyLoaded) {
|
|
311
|
+
clearVendorArtifacts(id);
|
|
312
|
+
const isConsentOnly = typeof window !== "undefined" && window._modernConsentConfig?.consentOnly === true;
|
|
313
|
+
if (!isConsentOnly && typeof window !== "undefined") window.location.reload();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function acceptAll() {
|
|
317
|
+
const list = servicesList.get();
|
|
318
|
+
const updates = {};
|
|
319
|
+
list.forEach((s) => {
|
|
320
|
+
updates[s.id] = true;
|
|
321
|
+
activateService(s.id);
|
|
322
|
+
});
|
|
323
|
+
consentState.set(updates);
|
|
324
|
+
hasAnswered.set(true);
|
|
325
|
+
isPanelOpen.set(false);
|
|
326
|
+
list.forEach((s) => {
|
|
327
|
+
emitter.emit("consent:update", { vendor: s.id, status: "granted" });
|
|
328
|
+
});
|
|
329
|
+
pushConsentEvent(updates);
|
|
330
|
+
emitConsentSaved(updates);
|
|
331
|
+
}
|
|
332
|
+
function denyAll() {
|
|
333
|
+
const list = servicesList.get();
|
|
334
|
+
const updates = {};
|
|
335
|
+
let needsReload = false;
|
|
336
|
+
list.forEach((s) => {
|
|
337
|
+
updates[s.id] = false;
|
|
338
|
+
clearVendorArtifacts(s.id);
|
|
339
|
+
if (s.loaded) needsReload = true;
|
|
340
|
+
});
|
|
341
|
+
consentState.set(updates);
|
|
342
|
+
hasAnswered.set(true);
|
|
343
|
+
isPanelOpen.set(false);
|
|
344
|
+
list.forEach((s) => {
|
|
345
|
+
emitter.emit("consent:update", { vendor: s.id, status: "denied" });
|
|
346
|
+
});
|
|
347
|
+
pushConsentEvent(updates);
|
|
348
|
+
emitConsentSaved(updates);
|
|
349
|
+
const isConsentOnly = typeof window !== "undefined" && window._modernConsentConfig?.consentOnly === true;
|
|
350
|
+
if (needsReload && !isConsentOnly && typeof window !== "undefined") {
|
|
351
|
+
window.location.reload();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/layer.ts
|
|
356
|
+
var validCommands = ["config", "vendor"];
|
|
357
|
+
function handleVendor(event) {
|
|
358
|
+
const {
|
|
359
|
+
name,
|
|
360
|
+
label,
|
|
361
|
+
description = "",
|
|
362
|
+
category = "other",
|
|
363
|
+
config = {},
|
|
364
|
+
requireConsent = true,
|
|
365
|
+
setup,
|
|
366
|
+
init,
|
|
367
|
+
artifacts
|
|
368
|
+
} = event;
|
|
369
|
+
let loader;
|
|
370
|
+
if (init ?? setup) {
|
|
371
|
+
loader = async () => ({
|
|
372
|
+
name: label ?? name,
|
|
373
|
+
description,
|
|
374
|
+
category,
|
|
375
|
+
requireConsent,
|
|
376
|
+
setup,
|
|
377
|
+
init,
|
|
378
|
+
artifacts
|
|
379
|
+
});
|
|
380
|
+
} else {
|
|
381
|
+
const resolved = resolveVendor(name);
|
|
382
|
+
if (!resolved) {
|
|
383
|
+
console.warn(
|
|
384
|
+
`[modern-consent] Unknown vendor "${name}". Provide an init() function to define a custom vendor.`
|
|
385
|
+
);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
loader = resolved;
|
|
389
|
+
}
|
|
390
|
+
registerService({ id: name, category, config, loader });
|
|
391
|
+
}
|
|
392
|
+
function initMcLayer() {
|
|
393
|
+
if (typeof window === "undefined") return;
|
|
394
|
+
const w = window;
|
|
395
|
+
const existingQueue = Array.isArray(w.mcLayer) ? [...w.mcLayer] : [];
|
|
396
|
+
w._modernConsentConfig = {};
|
|
397
|
+
addResolver((_name) => {
|
|
398
|
+
return async () => {
|
|
399
|
+
const cdnBase = w._modernConsentConfig?.cdnBase;
|
|
400
|
+
if (!cdnBase) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
`[modern-consent] Vendor "${_name}" not found. Configure cdnBase to enable CDN loading, or provide an inline init().`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const url = `${cdnBase.replace(/\/$/, "")}/${_name}.js`;
|
|
406
|
+
const mod = await import(
|
|
407
|
+
/* @vite-ignore */
|
|
408
|
+
url
|
|
409
|
+
);
|
|
410
|
+
return mod.default ?? mod;
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
existingQueue.forEach((args) => {
|
|
414
|
+
const [command, payload] = args;
|
|
415
|
+
if (!validCommands.includes(command)) {
|
|
416
|
+
console.warn(`[modern-consent] "${command}" is not a valid command`);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (command === "config") {
|
|
420
|
+
w._modernConsentConfig = { ...w._modernConsentConfig, ...payload };
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
handleVendor(payload);
|
|
424
|
+
});
|
|
425
|
+
initState(w._modernConsentConfig);
|
|
426
|
+
const apiFn = (...args) => {
|
|
427
|
+
const [command, payload] = args;
|
|
428
|
+
if (!validCommands.includes(command)) {
|
|
429
|
+
console.warn(`[modern-consent] "${command}" is not a valid command`);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (command === "vendor") {
|
|
433
|
+
handleVendor(payload);
|
|
434
|
+
} else if (command === "config") {
|
|
435
|
+
w._modernConsentConfig = { ...w._modernConsentConfig, ...payload };
|
|
436
|
+
}
|
|
437
|
+
if (!Array.isArray(w.mcLayer)) w.mcLayer = [];
|
|
438
|
+
w.mcLayer.push(args);
|
|
439
|
+
};
|
|
440
|
+
w.modernConsent = Object.assign(apiFn, {
|
|
441
|
+
openPanel: () => isPanelOpen.set(true),
|
|
442
|
+
getConsent: () => consentState.get(),
|
|
443
|
+
on: emitter.on.bind(emitter)
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/index.ts
|
|
448
|
+
if (typeof window !== "undefined") {
|
|
449
|
+
initMcLayer();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export { Store, acceptAll, activateService, clearVendorArtifacts, consentState, denyAll, emitter, generateUUID, hasAnswered, initMcLayer, isPanelOpen, openPanel, registerService, servicesList, setConsent };
|
|
453
|
+
//# sourceMappingURL=index.js.map
|
|
454
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/store.ts","../src/utils/uuid.ts","../src/state.ts","../src/registry.ts","../src/emitter.ts","../src/consent.ts","../src/layer.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export type Subscriber<T> = (value: T) => void;\nexport type Unsubscriber = () => void;\n\nexport class Store<T> {\n private value: T;\n private subscribers: Set<Subscriber<T>> = new Set();\n\n constructor(initialValue: T) {\n this.value = initialValue;\n }\n\n get(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n if (this.value !== newValue) {\n this.value = newValue;\n this.notify();\n }\n }\n\n update(updater: (value: T) => T): void {\n this.set(updater(this.value));\n }\n\n subscribe(callback: Subscriber<T>): Unsubscriber {\n this.subscribers.add(callback);\n callback(this.value); // Appel immédiat\n return () => this.subscribers.delete(callback);\n }\n\n private notify(): void {\n this.subscribers.forEach(cb => cb(this.value));\n }\n}\n","function manualUUIDv4(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n // Set version 4 (0100) and variant 10xx bits per RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20),\n ].join('-');\n}\n\nexport const generateUUID = (): string =>\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : manualUUIDv4();\n","// core/src/state.ts\nimport { Store } from './utils/store';\nimport { generateUUID } from './utils/uuid';\n\nexport interface ConsentState {\n [serviceId: string]: boolean;\n}\n\nexport interface ServiceMetadata {\n id: string;\n name: string;\n description: string;\n category: string;\n /** Human-readable label for the purpose/category in 'purpose' displayMode. */\n purposeLabel?: string | Record<string, string>;\n /** Optional description for the purpose/category in 'purpose' displayMode. */\n purposeDescription?: string | Record<string, string>;\n loaded: boolean;\n requireConsent: boolean;\n}\n\ninterface StoredState {\n consent: ConsentState;\n answered: boolean;\n /** Unix timestamp (ms) of when consent was last given — for GDPR audit trail. */\n timestamp?: number;\n /** Version of the consent banner at the time of consent. */\n version?: string;\n /** Unique identifier per consent action — for GDPR audit trail. */\n consentId?: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'mc_consent_state';\n\ninterface CookieOptions {\n days?: number;\n path?: string;\n domain?: string;\n sameSite?: 'Lax' | 'Strict' | 'None';\n secure?: boolean;\n}\n\nfunction escapeCookieName(name: string): string {\n return name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nconst getCookie = (name: string): string | null => {\n if (typeof document === 'undefined') return null;\n const match = document.cookie.match(new RegExp(`(?:^|; )${escapeCookieName(name)}=([^;]*)`));\n return match ? decodeURIComponent(match[1]) : null;\n};\n\nconst setCookie = (name: string, value: string, options: CookieOptions = {}) => {\n if (typeof document === 'undefined') return;\n\n const { days = 365, path = '/', domain, sameSite = 'Lax', secure = false } = options;\n\n const expires = new Date();\n expires.setDate(expires.getDate() + days);\n\n let cookieString =\n `${name}=${encodeURIComponent(value)};` +\n `expires=${expires.toUTCString()};` +\n `path=${path};` +\n `SameSite=${sameSite}`;\n\n if (domain) {\n const hostname = typeof window !== 'undefined' ? window.location.hostname : '';\n if (hostname && !hostname.endsWith(domain.replace(/^\\./, ''))) {\n console.warn(\n `[modern-consent] cookieDomain \"${domain}\" does not match current hostname \"${hostname}\". ` +\n `The cookie will be silently rejected by the browser. ` +\n `Remove cookieDomain for local development.`,\n );\n }\n cookieString += `;domain=${domain}`;\n }\n if (secure || sameSite === 'None') cookieString += `;Secure`;\n\n document.cookie = cookieString;\n};\n\nexport const consentState = new Store<ConsentState>({});\nexport const hasAnswered = new Store<boolean>(false);\nexport const servicesList = new Store<ServiceMetadata[]>([]);\nexport const isPanelOpen = new Store<boolean>(false);\nexport const openPanel = () => isPanelOpen.set(true);\n\n// Tracked so that calling initState multiple times never stacks duplicate subscribers\nlet _cookieUnsubs: (() => void)[] = [];\n\nexport const initState = (config: any) => {\n const cookieName: string = config?.cookieName || DEFAULT_STORAGE_KEY;\n const cookieDomain: string | undefined = config?.cookieDomain;\n const consentVersion: string | undefined = config?.consentVersion;\n\n let initialData: StoredState = { consent: {}, answered: false };\n\n if (typeof window !== 'undefined') {\n const stored = getCookie(cookieName);\n if (stored) {\n try {\n initialData = JSON.parse(stored) as StoredState;\n } catch (e) {\n console.error('[modern-consent] Error parsing consent cookie', e);\n }\n }\n }\n\n consentState.set(initialData.consent || {});\n hasAnswered.set(initialData.answered || false);\n\n // Re-prompt when consentVersion changes (GDPR: policy update invalidates prior consent)\n if (initialData.answered && consentVersion && initialData.version !== consentVersion) {\n hasAnswered.set(false);\n isPanelOpen.set(true);\n }\n\n if (typeof window !== 'undefined') {\n // Clean up previous subscriptions before creating new ones\n _cookieUnsubs.forEach(fn => fn());\n _cookieUnsubs = [];\n\n const saveToCookie = () => {\n const state: StoredState = {\n consent: consentState.get(),\n answered: hasAnswered.get(),\n timestamp: Date.now(),\n version: consentVersion,\n consentId: generateUUID(),\n };\n setCookie(cookieName, JSON.stringify(state), {\n domain: cookieDomain,\n secure: window.location.protocol === 'https:',\n });\n };\n\n _cookieUnsubs.push(consentState.subscribe(saveToCookie));\n _cookieUnsubs.push(hasAnswered.subscribe(saveToCookie));\n }\n};\n","import { consentState, hasAnswered, isPanelOpen, servicesList } from './state';\nimport { resolveVendor } from './resolver';\nimport type { McConfig } from './layer';\n\ndeclare global {\n interface Window {\n _modernConsentConfig: McConfig;\n }\n}\n\nexport type VendorConfig = unknown;\n\nexport interface Vendor<TConfig = void> {\n name: string;\n description: string;\n category: string;\n /**\n * Human-readable label for the purpose/category in 'purpose' displayMode.\n * Can be a string or a Record<lang, string> for i18n support.\n * @example purposeLabel: { fr: \"Mesure d'audience\", en: \"Audience measurement\" }\n */\n purposeLabel?: string | Record<string, string>;\n /**\n * Description for the purpose/category in 'purpose' displayMode.\n * Can be a string or a Record<lang, string> for i18n support.\n */\n purposeDescription?: string | Record<string, string>;\n setup?: (config: TConfig) => void;\n domSelector?: string;\n init?: (config: TConfig) => void;\n /** Cookie names set by this vendor — cleared when consent is revoked. */\n artifacts?: string[] | ((config: TConfig) => string[]);\n render?: (dataset: DOMStringMap) => string;\n requireConsent: boolean;\n event?: {\n name: string;\n callback: () => void;\n }[];\n link?: {\n vendor: string;\n /** Config passed to the linked vendor. Defaults to {} if omitted. */\n config?: VendorConfig;\n condition: (params: { vendorConfig: any; consentConfig: any }) => boolean;\n }[];\n}\n\nexport type VendorLoader = () => Promise<Vendor<any>>;\n\nconst loaders = new Map<string, VendorLoader>();\nconst loadedVendors = new Map<string, Vendor<VendorConfig>>();\nconst vendorsConfig = new Map<string, VendorConfig>();\n\n// Re-export resolveVendor from resolver.ts for backward compatibility\nexport { resolveVendor } from './resolver';\n\n/** @internal — test helper only */\nexport function __resetRegistry() {\n loaders.clear();\n loadedVendors.clear();\n vendorsConfig.clear();\n}\n\n/**\n * Clears all cookies declared in vendor.artifacts.\n * Called before reload or on denyAll to clean up vendor-set cookies.\n */\nexport function clearVendorArtifacts(id: string): void {\n const vendor = loadedVendors.get(id);\n if (!vendor?.artifacts) return;\n\n const config = vendorsConfig.get(id);\n const cookieNames =\n typeof vendor.artifacts === 'function' ? vendor.artifacts(config) : vendor.artifacts;\n\n const domain = window._modernConsentConfig?.cookieDomain;\n\n cookieNames.forEach(name => {\n document.cookie = `${name}=; max-age=0; path=/`;\n if (domain) {\n document.cookie = `${name}=; max-age=0; path=/; domain=${domain}`;\n }\n });\n}\n\nexport function registerService(args: {\n id: string;\n category: string;\n loader: VendorLoader;\n config: VendorConfig;\n}) {\n const { id, category, loader, config = {} } = args;\n\n // Prevent double-registration for the same service id\n if (loaders.has(id)) return;\n loaders.set(id, loader);\n\n loader()\n .then((module: any) => {\n const vendor: Vendor<any> = module.default || module;\n loadedVendors.set(id, vendor);\n if (config !== undefined) {\n vendorsConfig.set(id, config);\n }\n\n if (vendor?.setup) {\n vendor.setup(config);\n }\n\n if (vendor?.link && vendor.link.length > 0) {\n vendor.link.forEach(link => {\n if (\n link.condition &&\n !link.condition({ vendorConfig: config, consentConfig: window._modernConsentConfig })\n ) {\n return;\n }\n const linkedLoader = resolveVendor(link.vendor);\n if (linkedLoader) {\n registerService({\n id: link.vendor,\n category,\n config: link.config ?? {},\n loader: linkedLoader,\n });\n } else {\n console.warn(`[modern-consent] Dependency \"${link.vendor}\" not found for \"${id}\"`);\n }\n });\n }\n\n servicesList.update(list => {\n if (list.some(s => s.id === id)) return list;\n return [\n ...list,\n {\n id,\n name: vendor.name,\n description: vendor.description,\n category: vendor.category,\n purposeLabel: vendor.purposeLabel,\n purposeDescription: vendor.purposeDescription,\n loaded: false,\n requireConsent: vendor.requireConsent,\n },\n ];\n });\n\n checkAutoActivation(id);\n })\n .catch(err => {\n console.error(`[modern-consent] Failed to load vendor \"${id}\":`, err);\n });\n}\n\n/**\n * Vérifie si on doit activer le service ou ouvrir le panel (nouveau service détecté)\n */\nfunction checkAutoActivation(id: string) {\n const vendor = loadedVendors.get(id);\n\n // Services that don't require consent activate immediately\n if (vendor && !vendor.requireConsent) {\n activateService(id);\n return;\n }\n\n const currentConsent = consentState.get();\n const answered = hasAnswered.get();\n\n if (answered && currentConsent[id] === undefined) {\n isPanelOpen.set(true);\n }\n\n if (currentConsent[id]) {\n activateService(id);\n }\n}\n\n/**\n * Active le service (Script + DOM)\n */\nexport async function activateService(id: string) {\n if (typeof window === 'undefined') return;\n\n let vendor = loadedVendors.get(id);\n\n if (!vendor) {\n const loader = loaders.get(id);\n if (loader) {\n const module: any = await loader();\n vendor = module.default || module;\n if (vendor) loadedVendors.set(id, vendor);\n }\n }\n\n if (!vendor) return;\n\n const isSoft = window._modernConsentConfig?.consentOnly === true;\n const list = servicesList.get();\n const meta = list.find(s => s.id === id);\n\n if (!isSoft && vendor.init && meta && !meta.loaded) {\n vendor.init(vendorsConfig.get(id));\n servicesList.update(l => l.map(s => (s.id === id ? { ...s, loaded: true } : s)));\n\n // Trigger onAccept events after successful activation\n vendor.event?.forEach(ev => {\n if (ev.name === 'onAccept') {\n try {\n ev.callback();\n } catch {\n /* don't let event errors break activation */\n }\n }\n });\n }\n\n if (!isSoft && vendor.domSelector && vendor.render) {\n setTimeout(() => {\n document.querySelectorAll(vendor!.domSelector!).forEach(el => {\n const htmlElement = el as HTMLElement;\n if (htmlElement.dataset.loaded !== 'true' && vendor?.render) {\n htmlElement.innerHTML = vendor.render(htmlElement.dataset);\n htmlElement.dataset.loaded = 'true';\n htmlElement.classList.remove('mc-placeholder-pending');\n }\n });\n }, 50);\n }\n}\n","import type { ConsentState } from './state';\n\nexport type ConsentEventMap = {\n 'consent:update': { vendor: string; status: 'granted' | 'denied'; gcm?: Record<string, boolean> };\n 'consent:saved': {\n consentId: string;\n timestamp: number;\n version?: string;\n consent: ConsentState;\n };\n};\n\ntype Listener<T> = (data: T) => void;\n\nclass ConsentEmitter {\n private listeners = new Map<string, Set<Listener<any>>>();\n\n on<K extends keyof ConsentEventMap>(event: K, cb: Listener<ConsentEventMap[K]>): () => void {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(cb);\n return () => this.listeners.get(event)?.delete(cb);\n }\n\n off<K extends keyof ConsentEventMap>(event: K, cb: Listener<ConsentEventMap[K]>): void {\n this.listeners.get(event)?.delete(cb);\n }\n\n emit<K extends keyof ConsentEventMap>(event: K, data: ConsentEventMap[K]): void {\n this.listeners.get(event)?.forEach(cb => {\n try {\n cb(data);\n } catch {\n /* don't let listener errors break the core */\n }\n });\n }\n}\n\nexport const emitter = new ConsentEmitter();\n","import { servicesList, consentState, hasAnswered, isPanelOpen } from './state';\nimport type { ConsentState } from './state';\nimport { activateService, clearVendorArtifacts } from './registry';\nimport { emitter } from './emitter';\nimport { generateUUID } from './utils/uuid';\n\ndeclare global {\n interface Window {\n consentLayer: any[];\n dataLayer: any[];\n }\n}\n\n/**\n * Always pushes consent state to `window.consentLayer` (TMS-agnostic).\n * Optionally pushes to `window.dataLayer` when `pushDataLayer` is enabled (GTM convenience).\n */\nfunction pushConsentEvent(consent: ConsentState) {\n if (typeof window === 'undefined') return;\n\n const event = {\n event: 'consent_update',\n consent_state: consent,\n };\n\n // consentLayer — always active, TMS-agnostic\n window.consentLayer = window.consentLayer || [];\n window.consentLayer.push(event);\n\n // dataLayer — opt-in for GTM native triggers\n if (window._modernConsentConfig?.pushDataLayer) {\n window.dataLayer = window.dataLayer || [];\n window.dataLayer.push(event);\n }\n}\n\nfunction emitConsentSaved(consent: ConsentState) {\n emitter.emit('consent:saved', {\n consentId: generateUUID(),\n timestamp: Date.now(),\n version:\n typeof window !== 'undefined' ? window._modernConsentConfig?.consentVersion : undefined,\n consent,\n });\n}\n\nexport function setConsent(id: string, allowed: boolean) {\n // Check before updating state whether this service was actively running\n const wasPreviouslyLoaded = servicesList.get().find(s => s.id === id)?.loaded ?? false;\n\n consentState.update(s => ({ ...s, [id]: allowed }));\n hasAnswered.set(true);\n\n emitter.emit('consent:update', {\n vendor: id,\n status: allowed ? 'granted' : 'denied',\n });\n\n const updatedConsent = consentState.get();\n pushConsentEvent(updatedConsent);\n emitConsentSaved(updatedConsent);\n\n if (allowed) {\n activateService(id);\n return;\n }\n\n // Only clean up and reload if the service was actively running\n if (wasPreviouslyLoaded) {\n clearVendorArtifacts(id);\n const isConsentOnly =\n typeof window !== 'undefined' && window._modernConsentConfig?.consentOnly === true;\n if (!isConsentOnly && typeof window !== 'undefined') window.location.reload();\n }\n}\n\nexport function acceptAll() {\n const list = servicesList.get();\n const updates: Record<string, boolean> = {};\n\n list.forEach(s => {\n updates[s.id] = true;\n activateService(s.id);\n });\n\n consentState.set(updates);\n hasAnswered.set(true);\n isPanelOpen.set(false);\n\n list.forEach(s => {\n emitter.emit('consent:update', { vendor: s.id, status: 'granted' });\n });\n\n pushConsentEvent(updates);\n emitConsentSaved(updates);\n}\n\nexport function denyAll() {\n const list = servicesList.get();\n const updates: Record<string, boolean> = {};\n let needsReload = false;\n\n list.forEach(s => {\n updates[s.id] = false;\n clearVendorArtifacts(s.id);\n if (s.loaded) needsReload = true;\n });\n\n consentState.set(updates);\n hasAnswered.set(true);\n isPanelOpen.set(false);\n\n list.forEach(s => {\n emitter.emit('consent:update', { vendor: s.id, status: 'denied' });\n });\n\n pushConsentEvent(updates);\n emitConsentSaved(updates);\n\n // Reload only if at least one vendor was actively running —\n // clears vendor scripts from memory after cookie cleanup.\n const isConsentOnly =\n typeof window !== 'undefined' && window._modernConsentConfig?.consentOnly === true;\n if (needsReload && !isConsentOnly && typeof window !== 'undefined') {\n window.location.reload();\n }\n}\n","import { registerService } from './registry';\nimport { addResolver, resolveVendor } from './resolver';\nimport { consentState, initState, isPanelOpen } from './state';\nimport type { ConsentState } from './state';\nimport type { Vendor } from './registry';\nimport { emitter } from './emitter';\n\n/**\n * Command can add in config\n */\nconst validCommands = ['config', 'vendor'] as const;\n\nexport interface McConfig {\n cookieDomain?: string;\n cookieName?: string;\n consentMode?: boolean;\n /** Version string embedded in the stored consent — used for GDPR audit trails. */\n consentVersion?: string;\n /**\n * Base URL where vendor JS files are hosted.\n * Used by the CDN bundle to lazy-load vendors on demand.\n *\n * @example 'https://cdn.monsite.com/mc-vendors'\n * → window.modernConsent('vendor', { name: 'google-analytics' })\n * → fetches https://cdn.monsite.com/mc-vendors/google-analytics.js\n */\n cdnBase?: string;\n /**\n * In Consent-Only mode, the CMP only manages consent decisions + cookie storage.\n * Vendors are never initialized (no call to init()).\n * Use this to manage vendor scripts externally via a Tag Manager (GTM, TagCommander, etc.).\n *\n * Consent changes are always pushed to `window.consentLayer`.\n * Enable `pushDataLayer` to also push to `window.dataLayer` for native GTM triggers.\n */\n consentOnly?: boolean;\n /**\n * When true, consent changes are also pushed to `window.dataLayer` for native\n * GTM trigger support. Disabled by default — GTM users should enable this.\n */\n pushDataLayer?: boolean;\n /**\n * How the details panel displays consent controls:\n * - 'vendor' (default): individual toggle per vendor, grouped by category\n * - 'purpose': toggle per category/purpose, vendors listed as info below\n */\n displayMode?: 'vendor' | 'purpose';\n /**\n * Custom labels for each purpose/category in 'purpose' displayMode.\n * Keys must match the vendor `category` values.\n *\n * @example\n * purposes: {\n * analytics: { label: \"Analyse de session et mesure d'audience\" },\n * Publicité: { label: \"Ciblage Marketing\" },\n * }\n */\n purposes?: Record<\n string,\n {\n /** Human-readable label displayed as the purpose title. */\n label: string;\n /** Optional description shown below the purpose title. */\n description?: string;\n }\n >;\n /**\n * When true, displays a \"Functional / Site operation\" mandatory purpose block\n * at the top of the details panel. This is a visual-only block with no toggle —\n * it informs the user that essential cookies are always active.\n */\n functionalPurpose?: boolean;\n}\n\nexport interface McVendor {\n /**\n * Built-in vendor name (e.g. 'google-analytics') when using npm with\n * built-in loaders, OR a unique ID when providing an inline init().\n */\n name: string;\n /**\n * Category for this vendor (e.g. 'analytics', 'marketing').\n * Optional for built-in vendors — they define their own category.\n * Required for inline vendors (defaults to 'other' if omitted).\n */\n category?: string;\n config?: Record<string, any>;\n\n // --- Inline vendor definition (CDN users / custom vendors) ---\n /** Human-readable label shown in the consent UI. Defaults to `name`. */\n label?: string;\n /** Short description shown in the consent UI. */\n description?: string;\n /** Whether the user must explicitly consent before this vendor activates. @default true */\n requireConsent?: boolean;\n /** Called immediately at page load (e.g. set consent defaults). */\n setup?: (config?: Record<string, any>) => void;\n /** Called when consent is granted. Inject scripts, pixels, etc. here. */\n init?: (config?: Record<string, any>) => void;\n /** Cookie names written by this vendor — cleared when consent is revoked. */\n artifacts?: string[];\n}\n\nexport type ConfigCommand = ['config', McConfig];\nexport type VendorCommand = ['vendor', McVendor];\nexport type McCommand = ConfigCommand | VendorCommand;\n\ntype McAPI = {\n (...args: McCommand): void;\n openPanel: () => void;\n getConsent: () => ConsentState;\n on: typeof emitter.on;\n};\n\ndeclare global {\n interface Window {\n _modernConsentConfig: McConfig;\n mcLayer: McCommand[];\n modernConsent: McAPI;\n }\n}\n\nfunction handleVendor(event: McVendor) {\n const {\n name,\n label,\n description = '',\n category = 'other',\n config = {},\n requireConsent = true,\n setup,\n init,\n artifacts,\n } = event;\n\n let loader: import('./registry').VendorLoader;\n\n if (init ?? setup) {\n // Inline vendor — CDN users provide their own implementation.\n loader = async () => ({\n name: label ?? name,\n description,\n category,\n requireConsent,\n setup,\n init,\n artifacts,\n });\n } else {\n // Look up via the resolver chain (builtin, CDN, or user-registered resolvers).\n const resolved = resolveVendor(name);\n if (!resolved) {\n console.warn(\n `[modern-consent] Unknown vendor \"${name}\". ` +\n `Provide an init() function to define a custom vendor.`,\n );\n return;\n }\n loader = resolved;\n }\n\n registerService({ id: name, category, config, loader });\n}\n\n/**\n * Initialise window.mcLayer et traite la queue éventuelle.\n */\nexport function initMcLayer() {\n if (typeof window === 'undefined') return;\n\n const w = window;\n const existingQueue = Array.isArray(w.mcLayer) ? [...w.mcLayer] : [];\n\n w._modernConsentConfig = {};\n\n // CDN resolver — lowest priority (registered first = LIFO lowest).\n // Returns a loader for any name; cdnBase is checked lazily at activation time.\n addResolver(_name => {\n return async () => {\n const cdnBase = w._modernConsentConfig?.cdnBase;\n if (!cdnBase) {\n throw new Error(\n `[modern-consent] Vendor \"${_name}\" not found. ` +\n `Configure cdnBase to enable CDN loading, or provide an inline init().`,\n );\n }\n const url = `${cdnBase.replace(/\\/$/, '')}/${_name}.js`;\n const mod = await import(/* @vite-ignore */ url);\n return (mod.default ?? mod) as Vendor<any>;\n };\n });\n\n // Process the existing queue — config commands accumulate before vendors are registered\n existingQueue.forEach((args: McCommand) => {\n const [command, payload] = args;\n\n if (!validCommands.includes(command as any)) {\n console.warn(`[modern-consent] \"${command}\" is not a valid command`);\n return;\n }\n\n if (command === 'config') {\n w._modernConsentConfig = { ...w._modernConsentConfig, ...(payload as McConfig) };\n return;\n }\n\n handleVendor(payload as McVendor);\n });\n\n // State is initialised once after config is fully resolved from the queue\n initState(w._modernConsentConfig);\n\n // Live API\n const apiFn = (...args: McCommand) => {\n const [command, payload] = args;\n\n if (!validCommands.includes(command as any)) {\n console.warn(`[modern-consent] \"${command}\" is not a valid command`);\n return;\n }\n\n if (command === 'vendor') {\n handleVendor(payload as McVendor);\n } else if (command === 'config') {\n w._modernConsentConfig = { ...w._modernConsentConfig, ...(payload as McConfig) };\n }\n\n if (!Array.isArray(w.mcLayer)) w.mcLayer = [];\n w.mcLayer.push(args);\n };\n\n w.modernConsent = Object.assign(apiFn, {\n openPanel: () => isPanelOpen.set(true),\n getConsent: () => consentState.get(),\n on: emitter.on.bind(emitter),\n });\n}\n","export type { ConsentState, ServiceMetadata } from './state.ts';\n\nexport { Store } from './utils/store';\n\nexport { consentState, hasAnswered, servicesList, isPanelOpen, openPanel } from './state';\n\n// Vendor Resolution\nexport { addResolver, resolveVendor } from './resolver';\n\nexport {\n registerService,\n activateService,\n clearVendorArtifacts,\n type Vendor,\n type VendorLoader,\n type VendorConfig,\n} from './registry';\n\nexport { setConsent, acceptAll, denyAll } from './consent';\n\nexport type { McConfig, McVendor, McCommand, ConfigCommand, VendorCommand } from './layer';\n\nexport { emitter, type ConsentEventMap } from './emitter';\nexport { generateUUID } from './utils/uuid';\nexport { initMcLayer } from './layer';\n\nimport { initMcLayer } from './layer';\n\nif (typeof window !== 'undefined') {\n initMcLayer();\n}\n"]}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
type Subscriber<T> = (value: T) => void;
|
|
2
|
+
type Unsubscriber = () => void;
|
|
3
|
+
declare class Store<T> {
|
|
4
|
+
private value;
|
|
5
|
+
private subscribers;
|
|
6
|
+
constructor(initialValue: T);
|
|
7
|
+
get(): T;
|
|
8
|
+
set(newValue: T): void;
|
|
9
|
+
update(updater: (value: T) => T): void;
|
|
10
|
+
subscribe(callback: Subscriber<T>): Unsubscriber;
|
|
11
|
+
private notify;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ConsentState {
|
|
15
|
+
[serviceId: string]: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface ServiceMetadata {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
category: string;
|
|
22
|
+
/** Human-readable label for the purpose/category in 'purpose' displayMode. */
|
|
23
|
+
purposeLabel?: string | Record<string, string>;
|
|
24
|
+
/** Optional description for the purpose/category in 'purpose' displayMode. */
|
|
25
|
+
purposeDescription?: string | Record<string, string>;
|
|
26
|
+
loaded: boolean;
|
|
27
|
+
requireConsent: boolean;
|
|
28
|
+
}
|
|
29
|
+
declare const consentState: Store<ConsentState>;
|
|
30
|
+
declare const hasAnswered: Store<boolean>;
|
|
31
|
+
declare const servicesList: Store<ServiceMetadata[]>;
|
|
32
|
+
declare const isPanelOpen: Store<boolean>;
|
|
33
|
+
declare const openPanel: () => void;
|
|
34
|
+
|
|
35
|
+
type ConsentEventMap = {
|
|
36
|
+
'consent:update': {
|
|
37
|
+
vendor: string;
|
|
38
|
+
status: 'granted' | 'denied';
|
|
39
|
+
gcm?: Record<string, boolean>;
|
|
40
|
+
};
|
|
41
|
+
'consent:saved': {
|
|
42
|
+
consentId: string;
|
|
43
|
+
timestamp: number;
|
|
44
|
+
version?: string;
|
|
45
|
+
consent: ConsentState;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
type Listener<T> = (data: T) => void;
|
|
49
|
+
declare class ConsentEmitter {
|
|
50
|
+
private listeners;
|
|
51
|
+
on<K extends keyof ConsentEventMap>(event: K, cb: Listener<ConsentEventMap[K]>): () => void;
|
|
52
|
+
off<K extends keyof ConsentEventMap>(event: K, cb: Listener<ConsentEventMap[K]>): void;
|
|
53
|
+
emit<K extends keyof ConsentEventMap>(event: K, data: ConsentEventMap[K]): void;
|
|
54
|
+
}
|
|
55
|
+
declare const emitter: ConsentEmitter;
|
|
56
|
+
|
|
57
|
+
interface McConfig {
|
|
58
|
+
cookieDomain?: string;
|
|
59
|
+
cookieName?: string;
|
|
60
|
+
consentMode?: boolean;
|
|
61
|
+
/** Version string embedded in the stored consent — used for GDPR audit trails. */
|
|
62
|
+
consentVersion?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Base URL where vendor JS files are hosted.
|
|
65
|
+
* Used by the CDN bundle to lazy-load vendors on demand.
|
|
66
|
+
*
|
|
67
|
+
* @example 'https://cdn.monsite.com/mc-vendors'
|
|
68
|
+
* → window.modernConsent('vendor', { name: 'google-analytics' })
|
|
69
|
+
* → fetches https://cdn.monsite.com/mc-vendors/google-analytics.js
|
|
70
|
+
*/
|
|
71
|
+
cdnBase?: string;
|
|
72
|
+
/**
|
|
73
|
+
* In Consent-Only mode, the CMP only manages consent decisions + cookie storage.
|
|
74
|
+
* Vendors are never initialized (no call to init()).
|
|
75
|
+
* Use this to manage vendor scripts externally via a Tag Manager (GTM, TagCommander, etc.).
|
|
76
|
+
*
|
|
77
|
+
* Consent changes are always pushed to `window.consentLayer`.
|
|
78
|
+
* Enable `pushDataLayer` to also push to `window.dataLayer` for native GTM triggers.
|
|
79
|
+
*/
|
|
80
|
+
consentOnly?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* When true, consent changes are also pushed to `window.dataLayer` for native
|
|
83
|
+
* GTM trigger support. Disabled by default — GTM users should enable this.
|
|
84
|
+
*/
|
|
85
|
+
pushDataLayer?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* How the details panel displays consent controls:
|
|
88
|
+
* - 'vendor' (default): individual toggle per vendor, grouped by category
|
|
89
|
+
* - 'purpose': toggle per category/purpose, vendors listed as info below
|
|
90
|
+
*/
|
|
91
|
+
displayMode?: 'vendor' | 'purpose';
|
|
92
|
+
/**
|
|
93
|
+
* Custom labels for each purpose/category in 'purpose' displayMode.
|
|
94
|
+
* Keys must match the vendor `category` values.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* purposes: {
|
|
98
|
+
* analytics: { label: "Analyse de session et mesure d'audience" },
|
|
99
|
+
* Publicité: { label: "Ciblage Marketing" },
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
purposes?: Record<string, {
|
|
103
|
+
/** Human-readable label displayed as the purpose title. */
|
|
104
|
+
label: string;
|
|
105
|
+
/** Optional description shown below the purpose title. */
|
|
106
|
+
description?: string;
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* When true, displays a "Functional / Site operation" mandatory purpose block
|
|
110
|
+
* at the top of the details panel. This is a visual-only block with no toggle —
|
|
111
|
+
* it informs the user that essential cookies are always active.
|
|
112
|
+
*/
|
|
113
|
+
functionalPurpose?: boolean;
|
|
114
|
+
}
|
|
115
|
+
interface McVendor {
|
|
116
|
+
/**
|
|
117
|
+
* Built-in vendor name (e.g. 'google-analytics') when using npm with
|
|
118
|
+
* built-in loaders, OR a unique ID when providing an inline init().
|
|
119
|
+
*/
|
|
120
|
+
name: string;
|
|
121
|
+
/**
|
|
122
|
+
* Category for this vendor (e.g. 'analytics', 'marketing').
|
|
123
|
+
* Optional for built-in vendors — they define their own category.
|
|
124
|
+
* Required for inline vendors (defaults to 'other' if omitted).
|
|
125
|
+
*/
|
|
126
|
+
category?: string;
|
|
127
|
+
config?: Record<string, any>;
|
|
128
|
+
/** Human-readable label shown in the consent UI. Defaults to `name`. */
|
|
129
|
+
label?: string;
|
|
130
|
+
/** Short description shown in the consent UI. */
|
|
131
|
+
description?: string;
|
|
132
|
+
/** Whether the user must explicitly consent before this vendor activates. @default true */
|
|
133
|
+
requireConsent?: boolean;
|
|
134
|
+
/** Called immediately at page load (e.g. set consent defaults). */
|
|
135
|
+
setup?: (config?: Record<string, any>) => void;
|
|
136
|
+
/** Called when consent is granted. Inject scripts, pixels, etc. here. */
|
|
137
|
+
init?: (config?: Record<string, any>) => void;
|
|
138
|
+
/** Cookie names written by this vendor — cleared when consent is revoked. */
|
|
139
|
+
artifacts?: string[];
|
|
140
|
+
}
|
|
141
|
+
type ConfigCommand = ['config', McConfig];
|
|
142
|
+
type VendorCommand = ['vendor', McVendor];
|
|
143
|
+
type McCommand = ConfigCommand | VendorCommand;
|
|
144
|
+
type McAPI = {
|
|
145
|
+
(...args: McCommand): void;
|
|
146
|
+
openPanel: () => void;
|
|
147
|
+
getConsent: () => ConsentState;
|
|
148
|
+
on: typeof emitter.on;
|
|
149
|
+
};
|
|
150
|
+
declare global {
|
|
151
|
+
interface Window {
|
|
152
|
+
_modernConsentConfig: McConfig;
|
|
153
|
+
mcLayer: McCommand[];
|
|
154
|
+
modernConsent: McAPI;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Initialise window.mcLayer et traite la queue éventuelle.
|
|
159
|
+
*/
|
|
160
|
+
declare function initMcLayer(): void;
|
|
161
|
+
|
|
162
|
+
declare global {
|
|
163
|
+
interface Window {
|
|
164
|
+
_modernConsentConfig: McConfig;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
type VendorConfig = unknown;
|
|
168
|
+
interface Vendor<TConfig = void> {
|
|
169
|
+
name: string;
|
|
170
|
+
description: string;
|
|
171
|
+
category: string;
|
|
172
|
+
/**
|
|
173
|
+
* Human-readable label for the purpose/category in 'purpose' displayMode.
|
|
174
|
+
* Can be a string or a Record<lang, string> for i18n support.
|
|
175
|
+
* @example purposeLabel: { fr: "Mesure d'audience", en: "Audience measurement" }
|
|
176
|
+
*/
|
|
177
|
+
purposeLabel?: string | Record<string, string>;
|
|
178
|
+
/**
|
|
179
|
+
* Description for the purpose/category in 'purpose' displayMode.
|
|
180
|
+
* Can be a string or a Record<lang, string> for i18n support.
|
|
181
|
+
*/
|
|
182
|
+
purposeDescription?: string | Record<string, string>;
|
|
183
|
+
setup?: (config: TConfig) => void;
|
|
184
|
+
domSelector?: string;
|
|
185
|
+
init?: (config: TConfig) => void;
|
|
186
|
+
/** Cookie names set by this vendor — cleared when consent is revoked. */
|
|
187
|
+
artifacts?: string[] | ((config: TConfig) => string[]);
|
|
188
|
+
render?: (dataset: DOMStringMap) => string;
|
|
189
|
+
requireConsent: boolean;
|
|
190
|
+
event?: {
|
|
191
|
+
name: string;
|
|
192
|
+
callback: () => void;
|
|
193
|
+
}[];
|
|
194
|
+
link?: {
|
|
195
|
+
vendor: string;
|
|
196
|
+
/** Config passed to the linked vendor. Defaults to {} if omitted. */
|
|
197
|
+
config?: VendorConfig;
|
|
198
|
+
condition: (params: {
|
|
199
|
+
vendorConfig: any;
|
|
200
|
+
consentConfig: any;
|
|
201
|
+
}) => boolean;
|
|
202
|
+
}[];
|
|
203
|
+
}
|
|
204
|
+
type VendorLoader = () => Promise<Vendor<any>>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Clears all cookies declared in vendor.artifacts.
|
|
208
|
+
* Called before reload or on denyAll to clean up vendor-set cookies.
|
|
209
|
+
*/
|
|
210
|
+
declare function clearVendorArtifacts(id: string): void;
|
|
211
|
+
declare function registerService(args: {
|
|
212
|
+
id: string;
|
|
213
|
+
category: string;
|
|
214
|
+
loader: VendorLoader;
|
|
215
|
+
config: VendorConfig;
|
|
216
|
+
}): void;
|
|
217
|
+
/**
|
|
218
|
+
* Active le service (Script + DOM)
|
|
219
|
+
*/
|
|
220
|
+
declare function activateService(id: string): Promise<void>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A resolver maps a vendor name to a loader function.
|
|
224
|
+
* Return `undefined` to pass resolution to the next resolver in the chain.
|
|
225
|
+
*/
|
|
226
|
+
type VendorResolver = (name: string) => VendorLoader | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* Register a vendor resolver. Resolvers are tried in LIFO order
|
|
229
|
+
* (last registered = highest priority), following the convention
|
|
230
|
+
* from Vite/Rollup where later plugins override earlier ones.
|
|
231
|
+
*
|
|
232
|
+
* @returns A cleanup function that removes this resolver from the chain.
|
|
233
|
+
*/
|
|
234
|
+
declare function addResolver(resolver: VendorResolver): () => void;
|
|
235
|
+
/**
|
|
236
|
+
* Resolve a vendor name to a loader by walking the resolver chain (LIFO).
|
|
237
|
+
* Returns `undefined` if no resolver can handle the name.
|
|
238
|
+
*/
|
|
239
|
+
declare function resolveVendor(name: string): VendorLoader | undefined;
|
|
240
|
+
/** @internal — test helper only */
|
|
241
|
+
declare function __resetResolvers(): void;
|
|
242
|
+
|
|
243
|
+
export { type ConsentState as C, type McConfig as M, type ServiceMetadata as S, type Vendor as V, __resetResolvers as _, Store as a, addResolver as b, consentState as c, registerService as d, activateService as e, clearVendorArtifacts as f, type VendorLoader as g, hasAnswered as h, isPanelOpen as i, type VendorConfig as j, type McVendor as k, type McCommand as l, type ConfigCommand as m, type VendorCommand as n, openPanel as o, emitter as p, type ConsentEventMap as q, resolveVendor as r, servicesList as s, initMcLayer as t, type VendorResolver as u };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { u as VendorResolver, _ as __resetResolvers, b as addResolver, r as resolveVendor } from './resolver-CKoWIVlu.js';
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"resolver.js"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@modernconsent/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A modern, lightweight consent management library",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./resolver": {
|
|
15
|
+
"types": "./dist/resolver.d.ts",
|
|
16
|
+
"import": "./dist/resolver.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@vitest/ui": "^4.0.16",
|
|
27
|
+
"jsdom": "^27.4.0",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"typescript": "^5.0.0",
|
|
30
|
+
"vitest": "^4.0.16"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"author": "ModernConsent Contributors",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"consent",
|
|
36
|
+
"gdpr",
|
|
37
|
+
"rgpd",
|
|
38
|
+
"cookie",
|
|
39
|
+
"cmp"
|
|
40
|
+
],
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/kschnekenburger/modern-consent",
|
|
44
|
+
"directory": "packages/core"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/kschnekenburger/modern-consent#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/kschnekenburger/modern-consent/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"typecheck": "tsc --noEmit"
|
|
59
|
+
}
|
|
60
|
+
}
|