@cookiemunch/consent-shim 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog: @cookiemunch/consent-shim
2
+
3
+ Every published release, newest first. Dates are the day the version was cut.
4
+
5
+ ## 0.2.0 (2026-10-25)
6
+
7
+ - Added: the shim also sets `window.__consent`, the shared API's name from now on; `window.__fdConsent` stays, the same object. Calls queued on either are replayed, and a `__consent` another tool owns is left alone.
8
+
9
+ ## 0.1.0 (2026-10-24)
10
+
11
+ - Added: `import '@cookiemunch/consent-shim'` sets `window.__fdConsent` over Cookiebot, OneTrust or no consent tool; `dist/fd-consent-shim.js` is the same script as the CDN's.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cookie Munch
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.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # @cookiemunch/consent-shim
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `window.__consent` (also `window.__fdConsent`, the same object) on any page: one small consent API that a chat widget, a referral snippet or an
4
+ analytics tag can code against, whatever consent tool the page runs. Cookie Munch sets it natively;
5
+ on a page running Cookiebot, OneTrust or no consent tool, this shim sets the same object.
6
+
7
+ ```ts
8
+ import '@cookiemunch/consent-shim';
9
+
10
+ const fd = window.__consent;
11
+ fd.has('referrals'); // true, false, or null before the visitor decides
12
+ fd.on('change', (state) => { /* … */ });
13
+ await fd.request('support_chat'); // the mapped category's state (the shim cannot prompt)
14
+ ```
15
+
16
+ Or as a script tag, from the CDN or from this package's `dist/fd-consent-shim.js`:
17
+
18
+ ```html
19
+ <script src="https://cdn.cookiemunch.net/fd-consent-shim.js" async></script>
20
+ ```
21
+
22
+ It maps the tool's categories onto `preferences`, `statistics` and `marketing`, then
23
+ `support_chat` → `preferences`, `referrals` → `marketing` and `product_analytics` → `statistics`.
24
+ Change the mapping before it loads with `window.fdConsentShim = { map: { referrals: 'statistics' } }`.
25
+ With no consent tool every category is allowed, except what a Global Privacy Control signal refuses.
26
+ Do not load it on a Cookie Munch site.
27
+
28
+ Docs: [cookiemunch.net/docs/consent-api](https://cookiemunch.net/docs/consent-api).
@@ -0,0 +1,2 @@
1
+ "use strict";(()=>{(s=>{let _=n=>!!n&&typeof n=="object"&&("q"in n||"version"in n),a=s.__fdConsent,r=_(s.__consent)?s.__consent:void 0;if(a&&a.version||r&&r.version)return;let h=n=>n&&Array.isArray(n.q)?n.q:[],q=[...h(a),...r&&r!==a?h(r):[]],f={support_chat:"preferences",referrals:"marketing",product_analytics:"statistics",...s.fdConsentShim&&s.fdConsentShim.map},O=["preferences","statistics","marketing"],m=()=>!!s.navigator.globalPrivacyControl,d=()=>{let n=s.Cookiebot;if(n&&n.consent&&typeof n.hasResponse=="boolean"){let t=n.consent;return["cookiebot",n.hasResponse,!0,{preferences:!!t.preferences,statistics:!!t.statistics,marketing:!!t.marketing}]}let e=s.OnetrustActiveGroups;if(typeof e=="string"){let t=i=>e.indexOf(`,${i},`)>=0;return["onetrust",/(^|; )OptanonAlertBoxClosed=/.test(document.cookie),!0,{preferences:t("C0003"),statistics:t("C0002"),marketing:t("C0004")}]}return["none",!1,!1,{preferences:!0,statistics:!0,marketing:!m()}]},l=()=>{let[,n,e,t]=d(),o={necessary:!0,...t};for(let i in f)o[i]=!!t[f[i]];return{decided:n,required:e,categories:o,stamp:null,region:"",gpc:m(),decidedAt:null}},p=()=>{let n=d();return n[1]||!n[2]},v={},b=n=>{if(n=="necessary")return!0;let e=O.indexOf(n)>=0?n:f[n];return e?p()?!!d()[3][e]:null:(v[n]||(v[n]=1,console.warn(`[fd-consent-shim] unknown category "${n}"; answering false`)),!1)},c=[],g=n=>{let e=l();c.slice().forEach(t=>{try{t(e,n)}catch(o){console.error(o)}})};s.addEventListener("CookiebotOnAccept",()=>g("accept")),s.addEventListener("CookiebotOnDecline",()=>g("decline")),s.addEventListener("OneTrustGroupsUpdated",()=>g("selection"));let y=(n,e)=>n!="change"||typeof e!="function"?()=>{}:(c.push(e),p()&&Promise.resolve().then(()=>c.indexOf(e)>=0&&e(l(),"replay")),()=>{let t=c.indexOf(e);t>=0&&c.splice(t,1)}),C=()=>new Promise(n=>{if(p())return n(l());let e=y("change",t=>(e(),n(t)))}),u={version:1,get provider(){return`shim:${d()[0]}`},has:b,on:y,ready:C,request:n=>Promise.resolve(b(n)===!0),identify:()=>{},layout:{bottomInset:()=>0,on:()=>()=>{}}},k=n=>{let[e,t,o,i]=n;e=="on"?y(t,o):e=="ready"?C().then(t):e=="request"&&u.request(t).then(i)};u.q={push:(...n)=>(n.forEach(k),0)},s.__fdConsent=u,(s.__consent===void 0||r)&&(s.__consent=u),q.forEach(k)})(window);})();
2
+ //# sourceMappingURL=fd-consent-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shim.ts"],"sourcesContent":["/**\n * fd-consent-shim.js (CMU-X18): `window.__fdConsent` on a site that does NOT run Cookie Munch —\n * Cookiebot, OneTrust, or no CMP at all (GPC only) — so a chat widget or a referral snippet\n * codes against one API wherever it is embedded.\n *\n * <script src=\"https://cdn.cookiemunch.net/fd-consent-shim.js\" async></script>\n *\n * It maps the CMP's categories onto the standard ids, and the reserved ids onto those:\n * `support_chat` → preferences, `referrals` → marketing, `product_analytics` → statistics (change with\n * `window.fdConsentShim = { map: { referrals: 'statistics' } }` before it loads). It cannot\n * prompt, so `request()` answers with the mapped category's state; `layout` answers 0;\n * `identify` does nothing. With no CMP there is nothing to decide: every category is allowed\n * except where Global Privacy Control says no (marketing, and what maps to it).\n *\n * Do not load it on a Cookie Munch site: Cookie Munch sets the native object itself.\n * Standalone and tiny (size-gated): it imports nothing.\n */\n\ntype Cats = Record<string, boolean>;\ntype Cb = (s: unknown, cause: string) => void;\n\n((w: Window & Record<string, any>) => {\n // window.__consent is the name; __fdConsent is an alias of the same object. An embed may have queued on either.\n const ours = (o: any): boolean => !!o && typeof o == 'object' && ('q' in o || 'version' in o);\n const fd = w.__fdConsent;\n const alt = ours(w.__consent) ? w.__consent : undefined;\n if ((fd && fd.version) || (alt && alt.version)) return; // a CMP already set the real one\n const qOf = (o: any): unknown[][] => (o && Array.isArray(o.q) ? o.q : []);\n const queued: unknown[][] = [...qOf(fd), ...(alt && alt !== fd ? qOf(alt) : [])];\n const map: Record<string, string> = { support_chat: 'preferences', referrals: 'marketing', product_analytics: 'statistics', ...(w.fdConsentShim && w.fdConsentShim.map) };\n const std = ['preferences', 'statistics', 'marketing'];\n const gpc = (): boolean => !!(w.navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl;\n\n /** What the CMP on the page says now: [provider, decided, required, categories]. */\n const read = (): [string, boolean, boolean, Cats] => {\n const cb = w.Cookiebot;\n if (cb && cb.consent && typeof cb.hasResponse == 'boolean') {\n const c = cb.consent;\n return ['cookiebot', cb.hasResponse, true, { preferences: !!c.preferences, statistics: !!c.statistics, marketing: !!c.marketing }];\n }\n const groups: unknown = w.OnetrustActiveGroups;\n if (typeof groups == 'string') {\n const on = (g: string): boolean => groups.indexOf(`,${g},`) >= 0;\n const closed = /(^|; )OptanonAlertBoxClosed=/.test(document.cookie);\n return ['onetrust', closed, true, { preferences: on('C0003'), statistics: on('C0002'), marketing: on('C0004') }];\n }\n return ['none', false, false, { preferences: true, statistics: true, marketing: !gpc() }];\n };\n\n const snapshot = () => {\n const [, decided, required, c] = read();\n const categories: Cats = { necessary: true, ...c };\n for (const id in map) categories[id] = !!c[map[id]!];\n return { decided, required, categories, stamp: null, region: '', gpc: gpc(), decidedAt: null };\n };\n const settled = (): boolean => {\n const r = read();\n return r[1] || !r[2];\n };\n const warned: Record<string, 1> = {};\n const has = (cat: string): boolean | null => {\n if (cat == 'necessary') return true;\n const id = std.indexOf(cat) >= 0 ? cat : map[cat];\n if (!id) {\n if (!warned[cat]) (warned[cat] = 1), console.warn(`[fd-consent-shim] unknown category \"${cat}\"; answering false`);\n return false;\n }\n return settled() ? !!read()[3][id] : null;\n };\n\n const listeners: Cb[] = [];\n const emit = (cause: string): void => {\n const s = snapshot();\n listeners.slice().forEach((cb) => {\n try {\n cb(s, cause);\n } catch (e) {\n console.error(e);\n }\n });\n };\n // Cookiebot's events, and OneTrust's; whichever CMP the page runs fires its own.\n w.addEventListener('CookiebotOnAccept', () => emit('accept'));\n w.addEventListener('CookiebotOnDecline', () => emit('decline'));\n w.addEventListener('OneTrustGroupsUpdated', () => emit('selection'));\n\n const on = (ev: string, cb: Cb): (() => void) => {\n if (ev != 'change' || typeof cb != 'function') return () => undefined;\n listeners.push(cb);\n if (settled()) Promise.resolve().then(() => listeners.indexOf(cb) >= 0 && cb(snapshot(), 'replay'));\n return () => {\n const i = listeners.indexOf(cb);\n if (i >= 0) listeners.splice(i, 1);\n };\n };\n const ready = () =>\n new Promise((done) => {\n if (settled()) return done(snapshot());\n const off = on('change', (s) => (off(), done(s)));\n });\n\n const api: Record<string, any> = {\n version: 1,\n get provider() {\n return `shim:${read()[0]}`;\n },\n has,\n on,\n ready,\n // It cannot ask: the answer is the mapped category's state (false while undecided).\n request: (cat: string) => Promise.resolve(has(cat) === true),\n identify: () => undefined,\n layout: { bottomInset: () => 0, on: () => () => undefined },\n };\n const run = (c: unknown[]): void => {\n const [m, a, b, d] = c as [string, any, any, any];\n if (m == 'on') on(a, b);\n else if (m == 'ready') ready().then(a);\n else if (m == 'request') api.request(a).then(d);\n };\n api.q = { push: (...calls: unknown[][]) => (calls.forEach(run), 0) };\n w.__fdConsent = api;\n if (w.__consent === undefined || alt) w.__consent = api;\n queued.forEach(run);\n})(window as never);\n"],"mappings":"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","names":["w","ours","o","fd","alt","qOf","queued","map","std","gpc","read","cb","c","groups","on","g","snapshot","decided","required","categories","id","settled","r","warned","has","cat","listeners","emit","cause","s","e","ev","i","ready","done","off","api","run","m","a","b","d","calls"]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,97 @@
1
+ // src/shim.ts
2
+ ((w) => {
3
+ const ours = (o) => !!o && typeof o == "object" && ("q" in o || "version" in o);
4
+ const fd = w.__fdConsent;
5
+ const alt = ours(w.__consent) ? w.__consent : void 0;
6
+ if (fd && fd.version || alt && alt.version) return;
7
+ const qOf = (o) => o && Array.isArray(o.q) ? o.q : [];
8
+ const queued = [...qOf(fd), ...alt && alt !== fd ? qOf(alt) : []];
9
+ const map = { support_chat: "preferences", referrals: "marketing", product_analytics: "statistics", ...w.fdConsentShim && w.fdConsentShim.map };
10
+ const std = ["preferences", "statistics", "marketing"];
11
+ const gpc = () => !!w.navigator.globalPrivacyControl;
12
+ const read = () => {
13
+ const cb = w.Cookiebot;
14
+ if (cb && cb.consent && typeof cb.hasResponse == "boolean") {
15
+ const c = cb.consent;
16
+ return ["cookiebot", cb.hasResponse, true, { preferences: !!c.preferences, statistics: !!c.statistics, marketing: !!c.marketing }];
17
+ }
18
+ const groups = w.OnetrustActiveGroups;
19
+ if (typeof groups == "string") {
20
+ const on2 = (g) => groups.indexOf(`,${g},`) >= 0;
21
+ const closed = /(^|; )OptanonAlertBoxClosed=/.test(document.cookie);
22
+ return ["onetrust", closed, true, { preferences: on2("C0003"), statistics: on2("C0002"), marketing: on2("C0004") }];
23
+ }
24
+ return ["none", false, false, { preferences: true, statistics: true, marketing: !gpc() }];
25
+ };
26
+ const snapshot = () => {
27
+ const [, decided, required, c] = read();
28
+ const categories = { necessary: true, ...c };
29
+ for (const id in map) categories[id] = !!c[map[id]];
30
+ return { decided, required, categories, stamp: null, region: "", gpc: gpc(), decidedAt: null };
31
+ };
32
+ const settled = () => {
33
+ const r = read();
34
+ return r[1] || !r[2];
35
+ };
36
+ const warned = {};
37
+ const has = (cat) => {
38
+ if (cat == "necessary") return true;
39
+ const id = std.indexOf(cat) >= 0 ? cat : map[cat];
40
+ if (!id) {
41
+ if (!warned[cat]) warned[cat] = 1, console.warn(`[fd-consent-shim] unknown category "${cat}"; answering false`);
42
+ return false;
43
+ }
44
+ return settled() ? !!read()[3][id] : null;
45
+ };
46
+ const listeners = [];
47
+ const emit = (cause) => {
48
+ const s = snapshot();
49
+ listeners.slice().forEach((cb) => {
50
+ try {
51
+ cb(s, cause);
52
+ } catch (e) {
53
+ console.error(e);
54
+ }
55
+ });
56
+ };
57
+ w.addEventListener("CookiebotOnAccept", () => emit("accept"));
58
+ w.addEventListener("CookiebotOnDecline", () => emit("decline"));
59
+ w.addEventListener("OneTrustGroupsUpdated", () => emit("selection"));
60
+ const on = (ev, cb) => {
61
+ if (ev != "change" || typeof cb != "function") return () => void 0;
62
+ listeners.push(cb);
63
+ if (settled()) Promise.resolve().then(() => listeners.indexOf(cb) >= 0 && cb(snapshot(), "replay"));
64
+ return () => {
65
+ const i = listeners.indexOf(cb);
66
+ if (i >= 0) listeners.splice(i, 1);
67
+ };
68
+ };
69
+ const ready = () => new Promise((done) => {
70
+ if (settled()) return done(snapshot());
71
+ const off = on("change", (s) => (off(), done(s)));
72
+ });
73
+ const api = {
74
+ version: 1,
75
+ get provider() {
76
+ return `shim:${read()[0]}`;
77
+ },
78
+ has,
79
+ on,
80
+ ready,
81
+ // It cannot ask: the answer is the mapped category's state (false while undecided).
82
+ request: (cat) => Promise.resolve(has(cat) === true),
83
+ identify: () => void 0,
84
+ layout: { bottomInset: () => 0, on: () => () => void 0 }
85
+ };
86
+ const run = (c) => {
87
+ const [m, a, b, d] = c;
88
+ if (m == "on") on(a, b);
89
+ else if (m == "ready") ready().then(a);
90
+ else if (m == "request") api.request(a).then(d);
91
+ };
92
+ api.q = { push: (...calls) => (calls.forEach(run), 0) };
93
+ w.__fdConsent = api;
94
+ if (w.__consent === void 0 || alt) w.__consent = api;
95
+ queued.forEach(run);
96
+ })(window);
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shim.ts"],"sourcesContent":["/**\n * fd-consent-shim.js (CMU-X18): `window.__fdConsent` on a site that does NOT run Cookie Munch —\n * Cookiebot, OneTrust, or no CMP at all (GPC only) — so a chat widget or a referral snippet\n * codes against one API wherever it is embedded.\n *\n * <script src=\"https://cdn.cookiemunch.net/fd-consent-shim.js\" async></script>\n *\n * It maps the CMP's categories onto the standard ids, and the reserved ids onto those:\n * `support_chat` → preferences, `referrals` → marketing, `product_analytics` → statistics (change with\n * `window.fdConsentShim = { map: { referrals: 'statistics' } }` before it loads). It cannot\n * prompt, so `request()` answers with the mapped category's state; `layout` answers 0;\n * `identify` does nothing. With no CMP there is nothing to decide: every category is allowed\n * except where Global Privacy Control says no (marketing, and what maps to it).\n *\n * Do not load it on a Cookie Munch site: Cookie Munch sets the native object itself.\n * Standalone and tiny (size-gated): it imports nothing.\n */\n\ntype Cats = Record<string, boolean>;\ntype Cb = (s: unknown, cause: string) => void;\n\n((w: Window & Record<string, any>) => {\n // window.__consent is the name; __fdConsent is an alias of the same object. An embed may have queued on either.\n const ours = (o: any): boolean => !!o && typeof o == 'object' && ('q' in o || 'version' in o);\n const fd = w.__fdConsent;\n const alt = ours(w.__consent) ? w.__consent : undefined;\n if ((fd && fd.version) || (alt && alt.version)) return; // a CMP already set the real one\n const qOf = (o: any): unknown[][] => (o && Array.isArray(o.q) ? o.q : []);\n const queued: unknown[][] = [...qOf(fd), ...(alt && alt !== fd ? qOf(alt) : [])];\n const map: Record<string, string> = { support_chat: 'preferences', referrals: 'marketing', product_analytics: 'statistics', ...(w.fdConsentShim && w.fdConsentShim.map) };\n const std = ['preferences', 'statistics', 'marketing'];\n const gpc = (): boolean => !!(w.navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl;\n\n /** What the CMP on the page says now: [provider, decided, required, categories]. */\n const read = (): [string, boolean, boolean, Cats] => {\n const cb = w.Cookiebot;\n if (cb && cb.consent && typeof cb.hasResponse == 'boolean') {\n const c = cb.consent;\n return ['cookiebot', cb.hasResponse, true, { preferences: !!c.preferences, statistics: !!c.statistics, marketing: !!c.marketing }];\n }\n const groups: unknown = w.OnetrustActiveGroups;\n if (typeof groups == 'string') {\n const on = (g: string): boolean => groups.indexOf(`,${g},`) >= 0;\n const closed = /(^|; )OptanonAlertBoxClosed=/.test(document.cookie);\n return ['onetrust', closed, true, { preferences: on('C0003'), statistics: on('C0002'), marketing: on('C0004') }];\n }\n return ['none', false, false, { preferences: true, statistics: true, marketing: !gpc() }];\n };\n\n const snapshot = () => {\n const [, decided, required, c] = read();\n const categories: Cats = { necessary: true, ...c };\n for (const id in map) categories[id] = !!c[map[id]!];\n return { decided, required, categories, stamp: null, region: '', gpc: gpc(), decidedAt: null };\n };\n const settled = (): boolean => {\n const r = read();\n return r[1] || !r[2];\n };\n const warned: Record<string, 1> = {};\n const has = (cat: string): boolean | null => {\n if (cat == 'necessary') return true;\n const id = std.indexOf(cat) >= 0 ? cat : map[cat];\n if (!id) {\n if (!warned[cat]) (warned[cat] = 1), console.warn(`[fd-consent-shim] unknown category \"${cat}\"; answering false`);\n return false;\n }\n return settled() ? !!read()[3][id] : null;\n };\n\n const listeners: Cb[] = [];\n const emit = (cause: string): void => {\n const s = snapshot();\n listeners.slice().forEach((cb) => {\n try {\n cb(s, cause);\n } catch (e) {\n console.error(e);\n }\n });\n };\n // Cookiebot's events, and OneTrust's; whichever CMP the page runs fires its own.\n w.addEventListener('CookiebotOnAccept', () => emit('accept'));\n w.addEventListener('CookiebotOnDecline', () => emit('decline'));\n w.addEventListener('OneTrustGroupsUpdated', () => emit('selection'));\n\n const on = (ev: string, cb: Cb): (() => void) => {\n if (ev != 'change' || typeof cb != 'function') return () => undefined;\n listeners.push(cb);\n if (settled()) Promise.resolve().then(() => listeners.indexOf(cb) >= 0 && cb(snapshot(), 'replay'));\n return () => {\n const i = listeners.indexOf(cb);\n if (i >= 0) listeners.splice(i, 1);\n };\n };\n const ready = () =>\n new Promise((done) => {\n if (settled()) return done(snapshot());\n const off = on('change', (s) => (off(), done(s)));\n });\n\n const api: Record<string, any> = {\n version: 1,\n get provider() {\n return `shim:${read()[0]}`;\n },\n has,\n on,\n ready,\n // It cannot ask: the answer is the mapped category's state (false while undecided).\n request: (cat: string) => Promise.resolve(has(cat) === true),\n identify: () => undefined,\n layout: { bottomInset: () => 0, on: () => () => undefined },\n };\n const run = (c: unknown[]): void => {\n const [m, a, b, d] = c as [string, any, any, any];\n if (m == 'on') on(a, b);\n else if (m == 'ready') ready().then(a);\n else if (m == 'request') api.request(a).then(d);\n };\n api.q = { push: (...calls: unknown[][]) => (calls.forEach(run), 0) };\n w.__fdConsent = api;\n if (w.__consent === undefined || alt) w.__consent = api;\n queued.forEach(run);\n})(window as never);\n"],"mappings":"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","names":["on"]}
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@cookiemunch/consent-shim",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "window.__fdConsent on any page: one consent API for embeds over Cookiebot, OneTrust or no consent tool",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": true,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./fd-consent-shim.js": "./dist/fd-consent-shim.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "test": "vitest run"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/Cookie-Munch/consent-shim.git"
29
+ },
30
+ "homepage": "https://cookiemunch.net/docs/consent-api",
31
+ "bugs": {
32
+ "url": "https://github.com/Cookie-Munch/consent-shim/issues"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "22.20.0",
36
+ "happy-dom": "15.11.7",
37
+ "tsup": "8.5.1",
38
+ "typescript": "5.9.3",
39
+ "vitest": "2.1.9"
40
+ }
41
+ }