@owlmeans/consent 0.1.18-rc.4 → 0.1.18-rc.6

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/build/consts.d.ts CHANGED
@@ -17,10 +17,20 @@ export declare const CONSENT_COOKIE_DAYS = 365;
17
17
  export declare const CONSENT_SCHEMA_VERSION = 2;
18
18
  /** Idempotence flag for the Consent Mode defaults. Fixed: a page may carry two bundles. */
19
19
  export declare const CONSENT_SETUP_FLAG = "cookieConsentSetup";
20
+ /**
21
+ * The DOM event `applyConsent` dispatches on `window` after it finishes writing globals and
22
+ * pushing the Consent Mode update.
23
+ *
24
+ * A loader that already ran (a gate script that decided, on first paint, not to load yet) has no
25
+ * other way to hear a LATER grant — Consent Mode itself speaks only on `window.dataLayer`, which a
26
+ * tag that has not loaded is not listening to. `event.detail.record` carries the record that was
27
+ * just applied.
28
+ */
29
+ export declare const CONSENT_EVENT = "owlmeans:consent";
20
30
  export declare const CONSENT_ESSENTIAL = "essential";
21
31
  export declare const CONSENT_ANALYTICS = "analytics";
22
32
  export declare const CONSENT_MARKETING = "marketing";
23
- export declare const CONSENT_LOCALES: readonly ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de'];
33
+ export declare const CONSENT_LOCALES: readonly ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de', 'fr'];
24
34
  export type ConsentLocale = (typeof CONSENT_LOCALES)[number];
25
35
  /**
26
36
  * The categories every OwlMeans surface starts with.
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,wBAAwB,CAAA;AAEhD,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAC5C,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAC5C,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAE5C,eAAO,MAAM,eAAe,YAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAA;AAElF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,EAmBvD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAQxE,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,wBAAwB,CAAA;AAEhD,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,qBAAqB,CAAA;AAE/C,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAC5C,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAC5C,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAE5C,eAAO,MAAM,eAAe,YAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAA;AAExF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,EAmBvD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAQxE,CAAA"}
package/build/consts.js CHANGED
@@ -16,10 +16,20 @@ export const CONSENT_COOKIE_DAYS = 365;
16
16
  export const CONSENT_SCHEMA_VERSION = 2;
17
17
  /** Idempotence flag for the Consent Mode defaults. Fixed: a page may carry two bundles. */
18
18
  export const CONSENT_SETUP_FLAG = 'cookieConsentSetup';
19
+ /**
20
+ * The DOM event `applyConsent` dispatches on `window` after it finishes writing globals and
21
+ * pushing the Consent Mode update.
22
+ *
23
+ * A loader that already ran (a gate script that decided, on first paint, not to load yet) has no
24
+ * other way to hear a LATER grant — Consent Mode itself speaks only on `window.dataLayer`, which a
25
+ * tag that has not loaded is not listening to. `event.detail.record` carries the record that was
26
+ * just applied.
27
+ */
28
+ export const CONSENT_EVENT = 'owlmeans:consent';
19
29
  export const CONSENT_ESSENTIAL = 'essential';
20
30
  export const CONSENT_ANALYTICS = 'analytics';
21
31
  export const CONSENT_MARKETING = 'marketing';
22
- export const CONSENT_LOCALES = ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de'];
32
+ export const CONSENT_LOCALES = ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de', 'fr'];
23
33
  /**
24
34
  * The categories every OwlMeans surface starts with.
25
35
  *
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,qBAAqB,CAAA;AAEhD,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAEvC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAE5C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAA;AAIlF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D;QACE,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,IAAI;QACtC,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe;QACtD,SAAS,EAAE,qBAAqB;QAChC,OAAO,EAAE,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;KACvD;IACD;QACE,GAAG,EAAE,iBAAiB;QACtB,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe;QACtD,SAAS,EAAE,qBAAqB;QAChC,OAAO,EAAE,CAAC,mBAAmB,CAAC;KAC/B;IACD;QACE,GAAG,EAAE,iBAAiB;QACtB,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe;QACtD,SAAS,EAAE,qBAAqB;QAChC,OAAO,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,oBAAoB,CAAC;KAC9D;CACF,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAyC;IAC3E,UAAU,EAAE,QAAQ;IACpB,YAAY,EAAE,QAAQ;IACtB,kBAAkB,EAAE,QAAQ;IAC5B,iBAAiB,EAAE,QAAQ;IAC3B,qBAAqB,EAAE,QAAQ;IAC/B,uBAAuB,EAAE,QAAQ;IACjC,gBAAgB,EAAE,SAAS;CAC5B,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,qBAAqB,CAAA;AAEhD,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAEvC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAA;AAE/C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAA;AAE5C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAA;AAIxF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D;QACE,GAAG,EAAE,iBAAiB,EAAE,QAAQ,EAAE,IAAI;QACtC,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe;QACtD,SAAS,EAAE,qBAAqB;QAChC,OAAO,EAAE,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;KACvD;IACD;QACE,GAAG,EAAE,iBAAiB;QACtB,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe;QACtD,SAAS,EAAE,qBAAqB;QAChC,OAAO,EAAE,CAAC,mBAAmB,CAAC;KAC/B;IACD;QACE,GAAG,EAAE,iBAAiB;QACtB,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe;QACtD,SAAS,EAAE,qBAAqB;QAChC,OAAO,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,oBAAoB,CAAC;KAC9D;CACF,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAyC;IAC3E,UAAU,EAAE,QAAQ;IACpB,YAAY,EAAE,QAAQ;IACtB,kBAAkB,EAAE,QAAQ;IAC5B,iBAAiB,EAAE,QAAQ;IAC3B,qBAAqB,EAAE,QAAQ;IAC/B,uBAAuB,EAAE,QAAQ;IACjC,gBAAgB,EAAE,SAAS;CAC5B,CAAA"}
package/build/gtm.d.ts CHANGED
@@ -26,6 +26,16 @@ export declare const pushConsentDefaults: (opts?: ConsentOptions) => void;
26
26
  * turn, so writing them afterwards would let the first firing see the previous answer.
27
27
  */
28
28
  export declare const applyConsent: (record: ConsentRecord, opts?: ConsentOptions) => void;
29
+ /**
30
+ * Whether a stored decision grants tracking at all — a signal-bearing category, not merely the
31
+ * required essential one.
32
+ *
33
+ * "Signal-bearing" is what a required category can never be: it exists to gate whether a LOADER
34
+ * runs, and a required category is disclosure, not a question — everyone gets it. Only a category
35
+ * that both is optional and drives at least one Consent Mode signal counts, which is exactly what
36
+ * `googleTagHeadScript`'s `'basic'` mode gates a Google tag behind.
37
+ */
38
+ export declare const trackingGranted: (record: ConsentRecord | null, categories?: ConsentCategory[]) => boolean;
29
39
  /**
30
40
  * The inline script a document stamps ABOVE its tag-manager snippet.
31
41
  *
@@ -36,4 +46,27 @@ export declare const applyConsent: (record: ConsentRecord, opts?: ConsentOptions
36
46
  * denied for the first paint of every page.
37
47
  */
38
48
  export declare const consentBootstrapScript: (opts?: ConsentOptions) => string;
49
+ /**
50
+ * Withhold a loader until a stored or later decision grants tracking.
51
+ *
52
+ * The counterpart to `consentBootstrapScript` for the `'basic'` gated loading mode
53
+ * (`@owlmeans/web-gtm`'s `GOOGLE_TAG_DEFAULT_MODE`): where the bootstrap always declares Consent
54
+ * Mode's defaults so a tag may load cookieless, this withholds the tag itself — the loader string
55
+ * is only ever reached once `trackingGranted` is true, either because a returning visitor's stored
56
+ * record already says so, or because `applyConsent` later dispatches {@link CONSENT_EVENT} with
57
+ * one that does. The listener removes itself on the first passing event, so the loader — which
58
+ * marks its own element and refuses to run twice — is asked to run at most once from here.
59
+ *
60
+ * `loaderExpr` is a complete statement or expression that runs the loader when reached — the
61
+ * self-invoking IIFE string `googleTagHeadScript`'s `gtagScript`/`gtmContainerScript` already
62
+ * produce, embedded here verbatim rather than called. It is placed inline exactly as
63
+ * `consentBootstrapScript`'s own output is: dynamic values (the storage key, the category shape,
64
+ * the event name) are JSON-encoded so nothing configured can break out of a string literal, and
65
+ * escaping the RESULT for HTML (`</script`, `<!--`) is left to whoever composes the final inline
66
+ * script — the same discipline `consentBootstrapScript` follows, and `googleTagHeadScript` already
67
+ * applies to the whole script it stamps.
68
+ */
69
+ export declare const consentGateScript: (loaderExpr: string, opts?: ConsentOptions & {
70
+ categories?: ConsentCategory[];
71
+ }) => string;
39
72
  //# sourceMappingURL=gtm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gtm.d.ts","sourceRoot":"","sources":["../src/gtm.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAUhF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAUrD;AAKD,8EAA8E;AAC9E,eAAO,MAAM,eAAe,gBACb,eAAe,EAAE,KAC7B,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAMrC,CAAA;AAED,sDAAsD;AACtD,eAAO,MAAM,aAAa,WAChB,aAAa,eAAe,eAAe,EAAE,KACpD,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAWrC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAAW,cAAc,KAAG,IAO3D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,WAAY,aAAa,SAAS,cAAc,KAAG,IAqB3E,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,UAAW,cAAc,KAAG,MAwB9D,CAAA"}
1
+ {"version":3,"file":"gtm.d.ts","sourceRoot":"","sources":["../src/gtm.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAUhF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAUrD;AAKD,8EAA8E;AAC9E,eAAO,MAAM,eAAe,gBACb,eAAe,EAAE,KAC7B,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAMrC,CAAA;AAED,sDAAsD;AACtD,eAAO,MAAM,aAAa,WAChB,aAAa,eAAe,eAAe,EAAE,KACpD,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAWrC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAAW,cAAc,KAAG,IAO3D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,WAAY,aAAa,SAAS,cAAc,KAAG,IA6B3E,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,WAClB,aAAa,GAAG,IAAI,eAAc,eAAe,EAAE,KAC1D,OAEF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,UAAW,cAAc,KAAG,MAwB9D,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB,eAChB,MAAM,SAAS,cAAc,GAAG;IAAE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;CAAE,KAC7E,MAuBF,CAAA"}
package/build/gtm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CONSENT_KEY, CONSENT_SETUP_FLAG, CONSENT_SIGNAL_DEFAULTS, DEFAULT_CONSENT_CATEGORIES, } from './consts.js';
1
+ import { CONSENT_EVENT, CONSENT_KEY, CONSENT_SETUP_FLAG, CONSENT_SIGNAL_DEFAULTS, DEFAULT_CONSENT_CATEGORIES, } from './consts.js';
2
2
  const global = () => typeof window !== 'undefined' ? window : null;
3
3
  /**
4
4
  * Push onto the tag-manager queue in the shape Google's own snippet uses.
@@ -77,7 +77,24 @@ export const applyConsent = (record, opts) => {
77
77
  win.dataLayer.push({ event: category.event });
78
78
  }
79
79
  }
80
+ // A gate script (`consentGateScript`) that already decided NOT to load, because nothing was
81
+ // granted at the time it ran, has no other way to hear this. Consent Mode itself speaks only on
82
+ // `dataLayer`, which a tag that never loaded is not listening to.
83
+ if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function'
84
+ && typeof CustomEvent === 'function') {
85
+ window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: { record } }));
86
+ }
80
87
  };
88
+ /**
89
+ * Whether a stored decision grants tracking at all — a signal-bearing category, not merely the
90
+ * required essential one.
91
+ *
92
+ * "Signal-bearing" is what a required category can never be: it exists to gate whether a LOADER
93
+ * runs, and a required category is disclosure, not a question — everyone gets it. Only a category
94
+ * that both is optional and drives at least one Consent Mode signal counts, which is exactly what
95
+ * `googleTagHeadScript`'s `'basic'` mode gates a Google tag behind.
96
+ */
97
+ export const trackingGranted = (record, categories = DEFAULT_CONSENT_CATEGORIES) => record != null && categories.some(category => !category.required && (category.signals?.length ?? 0) > 0 && record[category.key] === true);
81
98
  /**
82
99
  * The inline script a document stamps ABOVE its tag-manager snippet.
83
100
  *
@@ -111,4 +128,47 @@ export const consentBootstrapScript = (opts) => {
111
128
  `u[s]=ok&&u[s]!=='denied'?'granted':'denied'}}` +
112
129
  `g('consent','update',u)})(window,document)`;
113
130
  };
131
+ /**
132
+ * Withhold a loader until a stored or later decision grants tracking.
133
+ *
134
+ * The counterpart to `consentBootstrapScript` for the `'basic'` gated loading mode
135
+ * (`@owlmeans/web-gtm`'s `GOOGLE_TAG_DEFAULT_MODE`): where the bootstrap always declares Consent
136
+ * Mode's defaults so a tag may load cookieless, this withholds the tag itself — the loader string
137
+ * is only ever reached once `trackingGranted` is true, either because a returning visitor's stored
138
+ * record already says so, or because `applyConsent` later dispatches {@link CONSENT_EVENT} with
139
+ * one that does. The listener removes itself on the first passing event, so the loader — which
140
+ * marks its own element and refuses to run twice — is asked to run at most once from here.
141
+ *
142
+ * `loaderExpr` is a complete statement or expression that runs the loader when reached — the
143
+ * self-invoking IIFE string `googleTagHeadScript`'s `gtagScript`/`gtmContainerScript` already
144
+ * produce, embedded here verbatim rather than called. It is placed inline exactly as
145
+ * `consentBootstrapScript`'s own output is: dynamic values (the storage key, the category shape,
146
+ * the event name) are JSON-encoded so nothing configured can break out of a string literal, and
147
+ * escaping the RESULT for HTML (`</script`, `<!--`) is left to whoever composes the final inline
148
+ * script — the same discipline `consentBootstrapScript` follows, and `googleTagHeadScript` already
149
+ * applies to the whole script it stamps.
150
+ */
151
+ export const consentGateScript = (loaderExpr, opts) => {
152
+ const gate = JSON.stringify(categoriesOf(opts).map(category => ({
153
+ key: category.key,
154
+ required: category.required === true,
155
+ hasSignal: (category.signals ?? []).length > 0,
156
+ })));
157
+ const storageKey = JSON.stringify(opts?.storageKey ?? CONSENT_KEY);
158
+ const event = JSON.stringify(CONSENT_EVENT);
159
+ return `(function(w,d){` +
160
+ `function ok(r){if(!r)return false;var cs=${gate};` +
161
+ `for(var i=0;i<cs.length;i++){var c=cs[i];` +
162
+ `if(!c.required&&c.hasSignal&&r[c.key]===true)return true}return false}` +
163
+ `function load(){${loaderExpr}}` +
164
+ `var raw=null;try{raw=w.localStorage.getItem(${storageKey})}catch(e){}` +
165
+ `if(!raw){var p=('; '+d.cookie).split('; '+${storageKey}+'=');` +
166
+ `if(p.length===2){raw=p.pop().split(';').shift()}}` +
167
+ `var r=null;if(raw){try{r=JSON.parse(raw)}catch(e){r=null}}` +
168
+ `if(ok(r)){load();return}` +
169
+ `function h(ev){if(ok(ev&&ev.detail&&ev.detail.record)){` +
170
+ `w.removeEventListener(${event},h);load()}}` +
171
+ `w.addEventListener(${event},h)` +
172
+ `})(window,document)`;
173
+ };
114
174
  //# sourceMappingURL=gtm.js.map
package/build/gtm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gtm.js","sourceRoot":"","sources":["../src/gtm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gtm.js","sourceRoot":"","sources":["../src/gtm.ts"],"names":[],"mappings":"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"}
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ваш выбар захоўваецца ў гэтым браўзеры пад ключом «{{key}}» — і ў лакальным сховішчы, і ў файле cookie.",
22
22
  "policyRetention": "Запіс захоўваецца {{days}} дзён, пасля чаго мы спытаем зноў.",
23
23
  "policyOperator": "Гэтая праграма належыць {{operator}}.",
24
- "policyContact": "Азнаёмцеся з іх палітыкай прыватнасці і ўмовамі карыстання, каб даведацца, як яны апрацоўваюць вашы даныя."
24
+ "policyContact": "Азнаёмцеся з іх палітыкай прыватнасці і ўмовамі карыстання, каб даведацца, як яны апрацоўваюць вашы даныя.",
25
+ "policyServicesOf": "Сэрвісы — {{category}}",
26
+ "policyOtherServices": "Іншыя сэрвісы",
27
+ "policyOtherServicesDesc": "Сэрвісы, якія гэтая праграма выкарыстоўвае па-за пералічанымі вышэй катэгорыямі.",
28
+ "policyProvider": "Пастаўшчык",
29
+ "policyPurpose": "Мэта",
30
+ "policyCookies": "Файлы cookie",
31
+ "policyServicePrivacy": "Палітыка прыватнасці: {{provider}}",
32
+ "privacy": "Палітыка прыватнасці",
33
+ "terms": "Умовы карыстання"
25
34
  }
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ihre Auswahl wird in diesem Browser unter „{{key}}\" gespeichert — sowohl im lokalen Speicher als auch als Cookie.",
22
22
  "policyRetention": "Der Eintrag wird {{days}} Tage aufbewahrt, danach werden Sie erneut gefragt.",
23
23
  "policyOperator": "Diese Anwendung wird von {{operator}} betrieben.",
24
- "policyContact": "Sehen Sie in deren Datenschutzerklärung und Bedingungen nach, wie Ihre Daten verarbeitet werden."
24
+ "policyContact": "Sehen Sie in deren Datenschutzerklärung und Bedingungen nach, wie Ihre Daten verarbeitet werden.",
25
+ "policyServicesOf": "Dienste — {{category}}",
26
+ "policyOtherServices": "Weitere Dienste",
27
+ "policyOtherServicesDesc": "Dienste, die diese Anwendung außerhalb der oben genannten Kategorien nutzt.",
28
+ "policyProvider": "Anbieter",
29
+ "policyPurpose": "Zweck",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "Datenschutzerklärung von {{provider}}",
32
+ "privacy": "Datenschutzerklärung",
33
+ "terms": "Nutzungsbedingungen"
25
34
  }
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Your choice is stored in this browser under \"{{key}}\", both in local storage and as a cookie.",
22
22
  "policyRetention": "The record is kept for {{days}} days, after which you will be asked again.",
23
23
  "policyOperator": "This application is operated by {{operator}}.",
24
- "policyContact": "See their privacy policy and terms for how they handle your data."
24
+ "policyContact": "See their privacy policy and terms for how they handle your data.",
25
+ "policyServicesOf": "Services — {{category}}",
26
+ "policyOtherServices": "Other services",
27
+ "policyOtherServicesDesc": "Services this application runs outside the categories above.",
28
+ "policyProvider": "Provider",
29
+ "policyPurpose": "Purpose",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "{{provider}} privacy policy",
32
+ "privacy": "Privacy Policy",
33
+ "terms": "Terms & Conditions"
25
34
  }
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Tu elección se guarda en este navegador bajo «{{key}}», tanto en el almacenamiento local como en una cookie.",
22
22
  "policyRetention": "El registro se conserva {{days}} días, tras los cuales se te preguntará de nuevo.",
23
23
  "policyOperator": "Esta aplicación está gestionada por {{operator}}.",
24
- "policyContact": "Consulta su política de privacidad y sus términos para saber cómo tratan tus datos."
24
+ "policyContact": "Consulta su política de privacidad y sus términos para saber cómo tratan tus datos.",
25
+ "policyServicesOf": "Servicios — {{category}}",
26
+ "policyOtherServices": "Otros servicios",
27
+ "policyOtherServicesDesc": "Servicios que esta aplicación utiliza fuera de las categorías anteriores.",
28
+ "policyProvider": "Proveedor",
29
+ "policyPurpose": "Finalidad",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "Política de privacidad de {{provider}}",
32
+ "privacy": "Política de privacidad",
33
+ "terms": "Términos y condiciones"
25
34
  }
@@ -0,0 +1,34 @@
1
+ {
2
+ "title": "Préférences de cookies",
3
+ "description": "Nous utilisons des cookies pour améliorer votre navigation, proposer des publicités ou contenus personnalisés et analyser notre trafic. En cliquant sur « Tout accepter », vous consentez à leur utilisation.",
4
+ "essential": "Cookies essentiels",
5
+ "essentialDesc": "Nécessaires au fonctionnement du site : connexion, sécurité et mémorisation de ces préférences. Ils ne peuvent pas être désactivés.",
6
+ "analytics": "Cookies analytiques",
7
+ "analyticsDesc": "Nous aident à améliorer notre site en recueillant et en analysant des informations sur son utilisation.",
8
+ "marketing": "Cookies marketing",
9
+ "marketingDesc": "Utilisés pour suivre les visiteurs entre les sites et afficher des publicités pertinentes.",
10
+ "required": "Requis",
11
+ "acceptAll": "Tout accepter",
12
+ "acceptAndContinue": "Accepter et continuer",
13
+ "savePreferences": "Enregistrer les préférences",
14
+ "policyLink": "Politique relative aux cookies",
15
+ "openPreferences": "Préférences de cookies",
16
+ "close": "Fermer",
17
+ "loginReason": "La connexion enregistre un cookie de session. Acceptez les cookies essentiels pour continuer.",
18
+ "manage": "Gérer les préférences",
19
+ "policyTitle": "Politique relative aux cookies",
20
+ "policyIntro": "Cette page décrit les cookies et stockages similaires utilisés par cette application, ainsi que la façon de les contrôler.",
21
+ "policyStorage": "Votre choix est enregistré dans ce navigateur sous « {{key}} », dans le stockage local et dans un cookie.",
22
+ "policyRetention": "Ce choix est conservé {{days}} jours, après quoi il vous sera demandé de nouveau.",
23
+ "policyOperator": "Cette application est exploitée par {{operator}}.",
24
+ "policyContact": "Consultez leur politique de confidentialité et leurs conditions pour savoir comment vos données sont traitées.",
25
+ "policyServicesOf": "Services — {{category}}",
26
+ "policyOtherServices": "Autres services",
27
+ "policyOtherServicesDesc": "Services utilisés par cette application en dehors des catégories ci-dessus.",
28
+ "policyProvider": "Fournisseur",
29
+ "policyPurpose": "Finalité",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "Politique de confidentialité de {{provider}}",
32
+ "privacy": "Politique de confidentialité",
33
+ "terms": "Conditions générales"
34
+ }
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Twój wybór jest zapisywany w tej przeglądarce pod kluczem „{{key}}\" — zarówno w pamięci lokalnej, jak i w pliku cookie.",
22
22
  "policyRetention": "Zapis jest przechowywany przez {{days}} dni, po czym zapytamy ponownie.",
23
23
  "policyOperator": "Tę aplikację prowadzi {{operator}}.",
24
- "policyContact": "Zapoznaj się z ich polityką prywatności i regulaminem, aby dowiedzieć się, jak przetwarzają Twoje dane."
24
+ "policyContact": "Zapoznaj się z ich polityką prywatności i regulaminem, aby dowiedzieć się, jak przetwarzają Twoje dane.",
25
+ "policyServicesOf": "Usługi — {{category}}",
26
+ "policyOtherServices": "Inne usługi",
27
+ "policyOtherServicesDesc": "Usługi, z których ta aplikacja korzysta poza powyższymi kategoriami.",
28
+ "policyProvider": "Dostawca",
29
+ "policyPurpose": "Cel",
30
+ "policyCookies": "Pliki cookie",
31
+ "policyServicePrivacy": "Polityka prywatności: {{provider}}",
32
+ "privacy": "Polityka prywatności",
33
+ "terms": "Regulamin"
25
34
  }
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ваш выбор сохраняется в этом браузере под ключом «{{key}}» — и в локальном хранилище, и в файле cookie.",
22
22
  "policyRetention": "Запись хранится {{days}} дней, после чего мы спросим снова.",
23
23
  "policyOperator": "Это приложение принадлежит {{operator}}.",
24
- "policyContact": "Ознакомьтесь с их политикой конфиденциальности и условиями использования, чтобы узнать, как они обрабатывают ваши данные."
24
+ "policyContact": "Ознакомьтесь с их политикой конфиденциальности и условиями использования, чтобы узнать, как они обрабатывают ваши данные.",
25
+ "policyServicesOf": "Сервисы — {{category}}",
26
+ "policyOtherServices": "Другие сервисы",
27
+ "policyOtherServicesDesc": "Сервисы, которые это приложение использует вне перечисленных выше категорий.",
28
+ "policyProvider": "Поставщик",
29
+ "policyPurpose": "Цель",
30
+ "policyCookies": "Файлы cookie",
31
+ "policyServicePrivacy": "Политика конфиденциальности: {{provider}}",
32
+ "privacy": "Политика конфиденциальности",
33
+ "terms": "Условия использования"
25
34
  }
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ваш вибір зберігається в цьому браузері під ключем «{{key}}» — і в локальному сховищі, і у файлі cookie.",
22
22
  "policyRetention": "Запис зберігається {{days}} днів, після чого ми запитаємо знову.",
23
23
  "policyOperator": "Цей застосунок належить {{operator}}.",
24
- "policyContact": "Ознайомтеся з їхньою політикою конфіденційності та умовами користування, щоб дізнатися, як вони обробляють ваші дані."
24
+ "policyContact": "Ознайомтеся з їхньою політикою конфіденційності та умовами користування, щоб дізнатися, як вони обробляють ваші дані.",
25
+ "policyServicesOf": "Сервіси — {{category}}",
26
+ "policyOtherServices": "Інші сервіси",
27
+ "policyOtherServicesDesc": "Сервіси, які цей застосунок використовує поза переліченими вище категоріями.",
28
+ "policyProvider": "Постачальник",
29
+ "policyPurpose": "Мета",
30
+ "policyCookies": "Файли cookie",
31
+ "policyServicePrivacy": "Політика конфіденційності: {{provider}}",
32
+ "privacy": "Політика конфіденційності",
33
+ "terms": "Умови користування"
25
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAUhD;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAElF,CAAA;AAED,eAAO,MAAM,eAAe,YAAa,MAAM,KAAG,aAIjD,CAAA;AAED,uFAAuF;AACvF,eAAO,MAAM,uBAAuB,YAAa,MAAM,WAC/C,MAAM,gBAAgB,MAAM,KAAG,MACqC,CAAA;AAE5E,oFAAoF;AACpF,eAAO,MAAM,WAAW,SAAU,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAG,MAE5B,CAAA"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAWhD;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAElF,CAAA;AAED,eAAO,MAAM,eAAe,YAAa,MAAM,KAAG,aAIjD,CAAA;AAED,uFAAuF;AACvF,eAAO,MAAM,uBAAuB,YAAa,MAAM,WAC/C,MAAM,gBAAgB,MAAM,KAAG,MACqC,CAAA;AAE5E,oFAAoF;AACpF,eAAO,MAAM,WAAW,SAAU,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAG,MAE5B,CAAA"}
package/build/i18n.js CHANGED
@@ -6,6 +6,7 @@ import be from './i18n/be.json' with { type: 'json' };
6
6
  import uk from './i18n/uk.json' with { type: 'json' };
7
7
  import es from './i18n/es.json' with { type: 'json' };
8
8
  import de from './i18n/de.json' with { type: 'json' };
9
+ import fr from './i18n/fr.json' with { type: 'json' };
9
10
  /**
10
11
  * The dialog's copy, in the box.
11
12
  *
@@ -15,7 +16,7 @@ import de from './i18n/de.json' with { type: 'json' };
15
16
  * overrides every one of these.
16
17
  */
17
18
  export const DEFAULT_CONSENT_MESSAGES = {
18
- en, pl, ru, be, uk, es, de,
19
+ en, pl, ru, be, uk, es, de, fr,
19
20
  };
20
21
  export const normalizeLocale = (locale) => {
21
22
  const base = (locale ?? 'en').toLowerCase().split('-')[0];
package/build/i18n.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAG7C,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAErD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAkD;IACrF,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;CAC3B,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAe,EAAiB,EAAE;IAChE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAEzD,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAqB,CAAC,CAAC,CAAC,CAAC,IAAqB,CAAC,CAAC,CAAC,IAAI,CAAA;AACvF,CAAC,CAAA;AAED,uFAAuF;AACvF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,MAAe,EAAE,EAAE,CACzD,CAAC,GAAW,EAAE,YAAoB,EAAU,EAAE,CAC5C,wBAAwB,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,YAAY,CAAA;AAE5E,oFAAoF;AACpF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,MAAuC,EAAU,EAAE,CAC3F,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CACzD,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA"}
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAG7C,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAErD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAkD;IACrF,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;CAC/B,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAe,EAAiB,EAAE;IAChE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAEzD,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAqB,CAAC,CAAC,CAAC,CAAC,IAAqB,CAAC,CAAC,CAAC,IAAI,CAAA;AACvF,CAAC,CAAA;AAED,uFAAuF;AACvF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,MAAe,EAAE,EAAE,CACzD,CAAC,GAAW,EAAE,YAAoB,EAAU,EAAE,CAC5C,wBAAwB,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,YAAY,CAAA;AAE5E,oFAAoF;AACpF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,MAAuC,EAAU,EAAE,CAC3F,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CACzD,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA"}
package/build/types.d.ts CHANGED
@@ -33,6 +33,35 @@ export interface ConsentCategory {
33
33
  /** dataLayer event pushed when this category transitions from denied to granted. */
34
34
  event?: string;
35
35
  }
36
+ /**
37
+ * One third-party service the site runs under a consent category, as the cookie policy discloses
38
+ * it.
39
+ *
40
+ * A category says WHY something is stored; a service says WHO receives it. A regulator reading
41
+ * the policy wants the second — "analytics cookies" discloses nothing about which company learns
42
+ * what a visitor did — so a site that runs a tag lists it here, beside the category that gates
43
+ * it. Plain data rather than keys: the name, the provider and the purpose are whatever the party
44
+ * adding the tag knows about it, and `@owlmeans/web-gtm`'s `googleTagServices(id)` is the
45
+ * disclosure for a Google tag.
46
+ */
47
+ export interface ConsentService {
48
+ /** Display name: "Google Analytics". */
49
+ name: string;
50
+ /** Who receives the data: "Google LLC". */
51
+ provider: string;
52
+ /**
53
+ * The `ConsentCategory.key` this service runs under: `'analytics'`, `'marketing'`,
54
+ * `'essential'`, or a custom key. A service whose category is not in force is still disclosed,
55
+ * in a trailing group of its own — hiding it would hide something that may still run.
56
+ */
57
+ category: string;
58
+ /** One sentence on what it does with the data. */
59
+ purpose?: string;
60
+ /** Cookie names it may set: `['_ga', '_ga_<ID>']`. */
61
+ cookies?: string[];
62
+ /** The provider's own privacy policy. */
63
+ privacyHref?: string;
64
+ }
36
65
  /** `v` is the schema version; every other key is a category key. */
37
66
  export interface ConsentRecord {
38
67
  v?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,YAAY,GAAG,cAAc,GAAG,oBAAoB,GACpD,mBAAmB,GAAG,uBAAuB,GAC7C,yBAAyB,GAAG,kBAAkB,CAAA;AAElD,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iGAAiG;IACjG,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oDAAoD;IACpD,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CACjD;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAEnE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;AAE3D,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,YAAY,CAAA;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IACpD,oGAAoG;IACpG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IACrC,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;IACtC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,6DAA6D;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IACjC,OAAO,EAAE,MAAM,cAAc,GAAG;QAAE,UAAU,EAAE,eAAe,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CACtF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,YAAY,GAAG,cAAc,GAAG,oBAAoB,GACpD,mBAAmB,GAAG,uBAAuB,GAC7C,yBAAyB,GAAG,kBAAkB,CAAA;AAElD,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iGAAiG;IACjG,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oDAAoD;IACpD,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CACjD;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAEnE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;AAE3D,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,YAAY,CAAA;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IACpD,oGAAoG;IACpG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IACrC,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;IACtC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,6DAA6D;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IACjC,OAAO,EAAE,MAAM,cAAc,GAAG;QAAE,UAAU,EAAE,eAAe,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CACtF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/consent",
3
- "version": "0.1.18-rc.4",
3
+ "version": "0.1.18-rc.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/consts.ts CHANGED
@@ -22,11 +22,22 @@ export const CONSENT_SCHEMA_VERSION = 2
22
22
  /** Idempotence flag for the Consent Mode defaults. Fixed: a page may carry two bundles. */
23
23
  export const CONSENT_SETUP_FLAG = 'cookieConsentSetup'
24
24
 
25
+ /**
26
+ * The DOM event `applyConsent` dispatches on `window` after it finishes writing globals and
27
+ * pushing the Consent Mode update.
28
+ *
29
+ * A loader that already ran (a gate script that decided, on first paint, not to load yet) has no
30
+ * other way to hear a LATER grant — Consent Mode itself speaks only on `window.dataLayer`, which a
31
+ * tag that has not loaded is not listening to. `event.detail.record` carries the record that was
32
+ * just applied.
33
+ */
34
+ export const CONSENT_EVENT = 'owlmeans:consent'
35
+
25
36
  export const CONSENT_ESSENTIAL = 'essential'
26
37
  export const CONSENT_ANALYTICS = 'analytics'
27
38
  export const CONSENT_MARKETING = 'marketing'
28
39
 
29
- export const CONSENT_LOCALES = ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de'] as const
40
+ export const CONSENT_LOCALES = ['en', 'pl', 'ru', 'be', 'uk', 'es', 'de', 'fr'] as const
30
41
 
31
42
  export type ConsentLocale = (typeof CONSENT_LOCALES)[number]
32
43
 
package/src/gtm.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
- CONSENT_KEY, CONSENT_SETUP_FLAG, CONSENT_SIGNAL_DEFAULTS, DEFAULT_CONSENT_CATEGORIES,
2
+ CONSENT_EVENT, CONSENT_KEY, CONSENT_SETUP_FLAG, CONSENT_SIGNAL_DEFAULTS,
3
+ DEFAULT_CONSENT_CATEGORIES,
3
4
  } from './consts.js'
4
5
  import type { ConsentCategory, ConsentOptions, ConsentRecord } from './types.js'
5
6
 
@@ -103,8 +104,31 @@ export const applyConsent = (record: ConsentRecord, opts?: ConsentOptions): void
103
104
  win.dataLayer.push({ event: category.event })
104
105
  }
105
106
  }
107
+
108
+ // A gate script (`consentGateScript`) that already decided NOT to load, because nothing was
109
+ // granted at the time it ran, has no other way to hear this. Consent Mode itself speaks only on
110
+ // `dataLayer`, which a tag that never loaded is not listening to.
111
+ if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function'
112
+ && typeof CustomEvent === 'function') {
113
+ window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: { record } }))
114
+ }
106
115
  }
107
116
 
117
+ /**
118
+ * Whether a stored decision grants tracking at all — a signal-bearing category, not merely the
119
+ * required essential one.
120
+ *
121
+ * "Signal-bearing" is what a required category can never be: it exists to gate whether a LOADER
122
+ * runs, and a required category is disclosure, not a question — everyone gets it. Only a category
123
+ * that both is optional and drives at least one Consent Mode signal counts, which is exactly what
124
+ * `googleTagHeadScript`'s `'basic'` mode gates a Google tag behind.
125
+ */
126
+ export const trackingGranted = (
127
+ record: ConsentRecord | null, categories: ConsentCategory[] = DEFAULT_CONSENT_CATEGORIES
128
+ ): boolean => record != null && categories.some(
129
+ category => !category.required && (category.signals?.length ?? 0) > 0 && record[category.key] === true
130
+ )
131
+
108
132
  /**
109
133
  * The inline script a document stamps ABOVE its tag-manager snippet.
110
134
  *
@@ -139,3 +163,50 @@ export const consentBootstrapScript = (opts?: ConsentOptions): string => {
139
163
  `u[s]=ok&&u[s]!=='denied'?'granted':'denied'}}` +
140
164
  `g('consent','update',u)})(window,document)`
141
165
  }
166
+
167
+ /**
168
+ * Withhold a loader until a stored or later decision grants tracking.
169
+ *
170
+ * The counterpart to `consentBootstrapScript` for the `'basic'` gated loading mode
171
+ * (`@owlmeans/web-gtm`'s `GOOGLE_TAG_DEFAULT_MODE`): where the bootstrap always declares Consent
172
+ * Mode's defaults so a tag may load cookieless, this withholds the tag itself — the loader string
173
+ * is only ever reached once `trackingGranted` is true, either because a returning visitor's stored
174
+ * record already says so, or because `applyConsent` later dispatches {@link CONSENT_EVENT} with
175
+ * one that does. The listener removes itself on the first passing event, so the loader — which
176
+ * marks its own element and refuses to run twice — is asked to run at most once from here.
177
+ *
178
+ * `loaderExpr` is a complete statement or expression that runs the loader when reached — the
179
+ * self-invoking IIFE string `googleTagHeadScript`'s `gtagScript`/`gtmContainerScript` already
180
+ * produce, embedded here verbatim rather than called. It is placed inline exactly as
181
+ * `consentBootstrapScript`'s own output is: dynamic values (the storage key, the category shape,
182
+ * the event name) are JSON-encoded so nothing configured can break out of a string literal, and
183
+ * escaping the RESULT for HTML (`</script`, `<!--`) is left to whoever composes the final inline
184
+ * script — the same discipline `consentBootstrapScript` follows, and `googleTagHeadScript` already
185
+ * applies to the whole script it stamps.
186
+ */
187
+ export const consentGateScript = (
188
+ loaderExpr: string, opts?: ConsentOptions & { categories?: ConsentCategory[] }
189
+ ): string => {
190
+ const gate = JSON.stringify(categoriesOf(opts).map(category => ({
191
+ key: category.key,
192
+ required: category.required === true,
193
+ hasSignal: (category.signals ?? []).length > 0,
194
+ })))
195
+ const storageKey = JSON.stringify(opts?.storageKey ?? CONSENT_KEY)
196
+ const event = JSON.stringify(CONSENT_EVENT)
197
+
198
+ return `(function(w,d){` +
199
+ `function ok(r){if(!r)return false;var cs=${gate};` +
200
+ `for(var i=0;i<cs.length;i++){var c=cs[i];` +
201
+ `if(!c.required&&c.hasSignal&&r[c.key]===true)return true}return false}` +
202
+ `function load(){${loaderExpr}}` +
203
+ `var raw=null;try{raw=w.localStorage.getItem(${storageKey})}catch(e){}` +
204
+ `if(!raw){var p=('; '+d.cookie).split('; '+${storageKey}+'=');` +
205
+ `if(p.length===2){raw=p.pop().split(';').shift()}}` +
206
+ `var r=null;if(raw){try{r=JSON.parse(raw)}catch(e){r=null}}` +
207
+ `if(ok(r)){load();return}` +
208
+ `function h(ev){if(ok(ev&&ev.detail&&ev.detail.record)){` +
209
+ `w.removeEventListener(${event},h);load()}}` +
210
+ `w.addEventListener(${event},h)` +
211
+ `})(window,document)`
212
+ }
package/src/i18n/be.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ваш выбар захоўваецца ў гэтым браўзеры пад ключом «{{key}}» — і ў лакальным сховішчы, і ў файле cookie.",
22
22
  "policyRetention": "Запіс захоўваецца {{days}} дзён, пасля чаго мы спытаем зноў.",
23
23
  "policyOperator": "Гэтая праграма належыць {{operator}}.",
24
- "policyContact": "Азнаёмцеся з іх палітыкай прыватнасці і ўмовамі карыстання, каб даведацца, як яны апрацоўваюць вашы даныя."
24
+ "policyContact": "Азнаёмцеся з іх палітыкай прыватнасці і ўмовамі карыстання, каб даведацца, як яны апрацоўваюць вашы даныя.",
25
+ "policyServicesOf": "Сэрвісы — {{category}}",
26
+ "policyOtherServices": "Іншыя сэрвісы",
27
+ "policyOtherServicesDesc": "Сэрвісы, якія гэтая праграма выкарыстоўвае па-за пералічанымі вышэй катэгорыямі.",
28
+ "policyProvider": "Пастаўшчык",
29
+ "policyPurpose": "Мэта",
30
+ "policyCookies": "Файлы cookie",
31
+ "policyServicePrivacy": "Палітыка прыватнасці: {{provider}}",
32
+ "privacy": "Палітыка прыватнасці",
33
+ "terms": "Умовы карыстання"
25
34
  }
package/src/i18n/de.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ihre Auswahl wird in diesem Browser unter „{{key}}\" gespeichert — sowohl im lokalen Speicher als auch als Cookie.",
22
22
  "policyRetention": "Der Eintrag wird {{days}} Tage aufbewahrt, danach werden Sie erneut gefragt.",
23
23
  "policyOperator": "Diese Anwendung wird von {{operator}} betrieben.",
24
- "policyContact": "Sehen Sie in deren Datenschutzerklärung und Bedingungen nach, wie Ihre Daten verarbeitet werden."
24
+ "policyContact": "Sehen Sie in deren Datenschutzerklärung und Bedingungen nach, wie Ihre Daten verarbeitet werden.",
25
+ "policyServicesOf": "Dienste — {{category}}",
26
+ "policyOtherServices": "Weitere Dienste",
27
+ "policyOtherServicesDesc": "Dienste, die diese Anwendung außerhalb der oben genannten Kategorien nutzt.",
28
+ "policyProvider": "Anbieter",
29
+ "policyPurpose": "Zweck",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "Datenschutzerklärung von {{provider}}",
32
+ "privacy": "Datenschutzerklärung",
33
+ "terms": "Nutzungsbedingungen"
25
34
  }
package/src/i18n/en.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Your choice is stored in this browser under \"{{key}}\", both in local storage and as a cookie.",
22
22
  "policyRetention": "The record is kept for {{days}} days, after which you will be asked again.",
23
23
  "policyOperator": "This application is operated by {{operator}}.",
24
- "policyContact": "See their privacy policy and terms for how they handle your data."
24
+ "policyContact": "See their privacy policy and terms for how they handle your data.",
25
+ "policyServicesOf": "Services — {{category}}",
26
+ "policyOtherServices": "Other services",
27
+ "policyOtherServicesDesc": "Services this application runs outside the categories above.",
28
+ "policyProvider": "Provider",
29
+ "policyPurpose": "Purpose",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "{{provider}} privacy policy",
32
+ "privacy": "Privacy Policy",
33
+ "terms": "Terms & Conditions"
25
34
  }
package/src/i18n/es.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Tu elección se guarda en este navegador bajo «{{key}}», tanto en el almacenamiento local como en una cookie.",
22
22
  "policyRetention": "El registro se conserva {{days}} días, tras los cuales se te preguntará de nuevo.",
23
23
  "policyOperator": "Esta aplicación está gestionada por {{operator}}.",
24
- "policyContact": "Consulta su política de privacidad y sus términos para saber cómo tratan tus datos."
24
+ "policyContact": "Consulta su política de privacidad y sus términos para saber cómo tratan tus datos.",
25
+ "policyServicesOf": "Servicios — {{category}}",
26
+ "policyOtherServices": "Otros servicios",
27
+ "policyOtherServicesDesc": "Servicios que esta aplicación utiliza fuera de las categorías anteriores.",
28
+ "policyProvider": "Proveedor",
29
+ "policyPurpose": "Finalidad",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "Política de privacidad de {{provider}}",
32
+ "privacy": "Política de privacidad",
33
+ "terms": "Términos y condiciones"
25
34
  }
@@ -0,0 +1,34 @@
1
+ {
2
+ "title": "Préférences de cookies",
3
+ "description": "Nous utilisons des cookies pour améliorer votre navigation, proposer des publicités ou contenus personnalisés et analyser notre trafic. En cliquant sur « Tout accepter », vous consentez à leur utilisation.",
4
+ "essential": "Cookies essentiels",
5
+ "essentialDesc": "Nécessaires au fonctionnement du site : connexion, sécurité et mémorisation de ces préférences. Ils ne peuvent pas être désactivés.",
6
+ "analytics": "Cookies analytiques",
7
+ "analyticsDesc": "Nous aident à améliorer notre site en recueillant et en analysant des informations sur son utilisation.",
8
+ "marketing": "Cookies marketing",
9
+ "marketingDesc": "Utilisés pour suivre les visiteurs entre les sites et afficher des publicités pertinentes.",
10
+ "required": "Requis",
11
+ "acceptAll": "Tout accepter",
12
+ "acceptAndContinue": "Accepter et continuer",
13
+ "savePreferences": "Enregistrer les préférences",
14
+ "policyLink": "Politique relative aux cookies",
15
+ "openPreferences": "Préférences de cookies",
16
+ "close": "Fermer",
17
+ "loginReason": "La connexion enregistre un cookie de session. Acceptez les cookies essentiels pour continuer.",
18
+ "manage": "Gérer les préférences",
19
+ "policyTitle": "Politique relative aux cookies",
20
+ "policyIntro": "Cette page décrit les cookies et stockages similaires utilisés par cette application, ainsi que la façon de les contrôler.",
21
+ "policyStorage": "Votre choix est enregistré dans ce navigateur sous « {{key}} », dans le stockage local et dans un cookie.",
22
+ "policyRetention": "Ce choix est conservé {{days}} jours, après quoi il vous sera demandé de nouveau.",
23
+ "policyOperator": "Cette application est exploitée par {{operator}}.",
24
+ "policyContact": "Consultez leur politique de confidentialité et leurs conditions pour savoir comment vos données sont traitées.",
25
+ "policyServicesOf": "Services — {{category}}",
26
+ "policyOtherServices": "Autres services",
27
+ "policyOtherServicesDesc": "Services utilisés par cette application en dehors des catégories ci-dessus.",
28
+ "policyProvider": "Fournisseur",
29
+ "policyPurpose": "Finalité",
30
+ "policyCookies": "Cookies",
31
+ "policyServicePrivacy": "Politique de confidentialité de {{provider}}",
32
+ "privacy": "Politique de confidentialité",
33
+ "terms": "Conditions générales"
34
+ }
package/src/i18n/pl.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Twój wybór jest zapisywany w tej przeglądarce pod kluczem „{{key}}\" — zarówno w pamięci lokalnej, jak i w pliku cookie.",
22
22
  "policyRetention": "Zapis jest przechowywany przez {{days}} dni, po czym zapytamy ponownie.",
23
23
  "policyOperator": "Tę aplikację prowadzi {{operator}}.",
24
- "policyContact": "Zapoznaj się z ich polityką prywatności i regulaminem, aby dowiedzieć się, jak przetwarzają Twoje dane."
24
+ "policyContact": "Zapoznaj się z ich polityką prywatności i regulaminem, aby dowiedzieć się, jak przetwarzają Twoje dane.",
25
+ "policyServicesOf": "Usługi — {{category}}",
26
+ "policyOtherServices": "Inne usługi",
27
+ "policyOtherServicesDesc": "Usługi, z których ta aplikacja korzysta poza powyższymi kategoriami.",
28
+ "policyProvider": "Dostawca",
29
+ "policyPurpose": "Cel",
30
+ "policyCookies": "Pliki cookie",
31
+ "policyServicePrivacy": "Polityka prywatności: {{provider}}",
32
+ "privacy": "Polityka prywatności",
33
+ "terms": "Regulamin"
25
34
  }
package/src/i18n/ru.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ваш выбор сохраняется в этом браузере под ключом «{{key}}» — и в локальном хранилище, и в файле cookie.",
22
22
  "policyRetention": "Запись хранится {{days}} дней, после чего мы спросим снова.",
23
23
  "policyOperator": "Это приложение принадлежит {{operator}}.",
24
- "policyContact": "Ознакомьтесь с их политикой конфиденциальности и условиями использования, чтобы узнать, как они обрабатывают ваши данные."
24
+ "policyContact": "Ознакомьтесь с их политикой конфиденциальности и условиями использования, чтобы узнать, как они обрабатывают ваши данные.",
25
+ "policyServicesOf": "Сервисы — {{category}}",
26
+ "policyOtherServices": "Другие сервисы",
27
+ "policyOtherServicesDesc": "Сервисы, которые это приложение использует вне перечисленных выше категорий.",
28
+ "policyProvider": "Поставщик",
29
+ "policyPurpose": "Цель",
30
+ "policyCookies": "Файлы cookie",
31
+ "policyServicePrivacy": "Политика конфиденциальности: {{provider}}",
32
+ "privacy": "Политика конфиденциальности",
33
+ "terms": "Условия использования"
25
34
  }
package/src/i18n/uk.json CHANGED
@@ -21,5 +21,14 @@
21
21
  "policyStorage": "Ваш вибір зберігається в цьому браузері під ключем «{{key}}» — і в локальному сховищі, і у файлі cookie.",
22
22
  "policyRetention": "Запис зберігається {{days}} днів, після чого ми запитаємо знову.",
23
23
  "policyOperator": "Цей застосунок належить {{operator}}.",
24
- "policyContact": "Ознайомтеся з їхньою політикою конфіденційності та умовами користування, щоб дізнатися, як вони обробляють ваші дані."
24
+ "policyContact": "Ознайомтеся з їхньою політикою конфіденційності та умовами користування, щоб дізнатися, як вони обробляють ваші дані.",
25
+ "policyServicesOf": "Сервіси — {{category}}",
26
+ "policyOtherServices": "Інші сервіси",
27
+ "policyOtherServicesDesc": "Сервіси, які цей застосунок використовує поза переліченими вище категоріями.",
28
+ "policyProvider": "Постачальник",
29
+ "policyPurpose": "Мета",
30
+ "policyCookies": "Файли cookie",
31
+ "policyServicePrivacy": "Політика конфіденційності: {{provider}}",
32
+ "privacy": "Політика конфіденційності",
33
+ "terms": "Умови користування"
25
34
  }
package/src/i18n.ts CHANGED
@@ -8,6 +8,7 @@ import be from './i18n/be.json' with { type: 'json' }
8
8
  import uk from './i18n/uk.json' with { type: 'json' }
9
9
  import es from './i18n/es.json' with { type: 'json' }
10
10
  import de from './i18n/de.json' with { type: 'json' }
11
+ import fr from './i18n/fr.json' with { type: 'json' }
11
12
 
12
13
  /**
13
14
  * The dialog's copy, in the box.
@@ -18,7 +19,7 @@ import de from './i18n/de.json' with { type: 'json' }
18
19
  * overrides every one of these.
19
20
  */
20
21
  export const DEFAULT_CONSENT_MESSAGES: Record<ConsentLocale, Record<string, string>> = {
21
- en, pl, ru, be, uk, es, de,
22
+ en, pl, ru, be, uk, es, de, fr,
22
23
  }
23
24
 
24
25
  export const normalizeLocale = (locale?: string): ConsentLocale => {
package/src/types.ts CHANGED
@@ -38,6 +38,36 @@ export interface ConsentCategory {
38
38
  event?: string
39
39
  }
40
40
 
41
+ /**
42
+ * One third-party service the site runs under a consent category, as the cookie policy discloses
43
+ * it.
44
+ *
45
+ * A category says WHY something is stored; a service says WHO receives it. A regulator reading
46
+ * the policy wants the second — "analytics cookies" discloses nothing about which company learns
47
+ * what a visitor did — so a site that runs a tag lists it here, beside the category that gates
48
+ * it. Plain data rather than keys: the name, the provider and the purpose are whatever the party
49
+ * adding the tag knows about it, and `@owlmeans/web-gtm`'s `googleTagServices(id)` is the
50
+ * disclosure for a Google tag.
51
+ */
52
+ export interface ConsentService {
53
+ /** Display name: "Google Analytics". */
54
+ name: string
55
+ /** Who receives the data: "Google LLC". */
56
+ provider: string
57
+ /**
58
+ * The `ConsentCategory.key` this service runs under: `'analytics'`, `'marketing'`,
59
+ * `'essential'`, or a custom key. A service whose category is not in force is still disclosed,
60
+ * in a trailing group of its own — hiding it would hide something that may still run.
61
+ */
62
+ category: string
63
+ /** One sentence on what it does with the data. */
64
+ purpose?: string
65
+ /** Cookie names it may set: `['_ga', '_ga_<ID>']`. */
66
+ cookies?: string[]
67
+ /** The provider's own privacy policy. */
68
+ privacyHref?: string
69
+ }
70
+
41
71
  /** `v` is the schema version; every other key is a category key. */
42
72
  export interface ConsentRecord {
43
73
  v?: number
@@ -1,12 +1,14 @@
1
1
  import { describe, test, expect, beforeEach } from 'bun:test'
2
2
  import {
3
- CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_KEY, CONSENT_MARKETING, CONSENT_SCHEMA_VERSION,
4
- DEFAULT_CONSENT_CATEGORIES,
3
+ CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_EVENT, CONSENT_KEY, CONSENT_LOCALES,
4
+ CONSENT_MARKETING, CONSENT_SCHEMA_VERSION, DEFAULT_CONSENT_CATEGORIES,
5
5
  } from '../src/consts.js'
6
6
  import { migrateConsent, readConsent, writeConsent, clearConsent } from '../src/storage.js'
7
- import { consentDefaults, consentUpdate, consentBootstrapScript } from '../src/gtm.js'
7
+ import { applyConsent, consentDefaults, consentUpdate, consentBootstrapScript } from '../src/gtm.js'
8
8
  import { makeConsentStore } from '../src/store.js'
9
- import { defaultConsentTranslate, interpolate, normalizeLocale } from '../src/i18n.js'
9
+ import {
10
+ DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, normalizeLocale,
11
+ } from '../src/i18n.js'
10
12
  import type { ConsentCategory } from '../src/types.js'
11
13
 
12
14
  /** A minimal browser: just the two stores this package writes to. */
@@ -115,6 +117,36 @@ describe('consent mode signals', () => {
115
117
  })
116
118
  })
117
119
 
120
+ describe('applyConsent dispatches a DOM event', () => {
121
+ test('fires CONSENT_EVENT with the record after writing globals and pushing the update', () => {
122
+ const seen: unknown[] = []
123
+ const listener = (event: Event) => { seen.push((event as CustomEvent).detail) }
124
+ ;(globalThis as any).addEventListener(CONSENT_EVENT, listener)
125
+
126
+ try {
127
+ applyConsent({ [CONSENT_ANALYTICS]: true })
128
+
129
+ expect(seen).toEqual([{ record: { [CONSENT_ANALYTICS]: true } }])
130
+ } finally {
131
+ ;(globalThis as any).removeEventListener(CONSENT_EVENT, listener)
132
+ }
133
+ })
134
+
135
+ test('silent mode dispatches nothing', () => {
136
+ const seen: unknown[] = []
137
+ const listener = (event: Event) => { seen.push((event as CustomEvent).detail) }
138
+ ;(globalThis as any).addEventListener(CONSENT_EVENT, listener)
139
+
140
+ try {
141
+ applyConsent({ [CONSENT_ANALYTICS]: true }, { silent: true })
142
+
143
+ expect(seen).toEqual([])
144
+ } finally {
145
+ ;(globalThis as any).removeEventListener(CONSENT_EVENT, listener)
146
+ }
147
+ })
148
+ })
149
+
118
150
  describe('the bootstrap script', () => {
119
151
  const script = consentBootstrapScript()
120
152
 
@@ -186,13 +218,29 @@ describe('the built-in translations', () => {
186
218
  // Without this, a language added to the bundle ships with English strings in it and nothing
187
219
  // reports it — the readers who cannot tell us are exactly the ones affected.
188
220
  const keys = Object.keys(require('../src/i18n/en.json') as Record<string, string>)
189
- for (const lng of ['pl', 'ru', 'be', 'uk', 'es', 'de']) {
221
+ // Driven by the shipped locale list, so a language added there is checked without anyone
222
+ // remembering to add it here — French was once in the list and missing from this loop.
223
+ for (const lng of CONSENT_LOCALES.filter(locale => locale !== 'en')) {
190
224
  const bundle = require(`../src/i18n/${lng}.json`) as Record<string, string>
191
225
  expect({ lng, missing: keys.filter(key => bundle[key] == null) })
192
226
  .toEqual({ lng, missing: [] })
193
227
  }
194
228
  })
195
229
 
230
+ test('every locale keeps every placeholder English has', () => {
231
+ // A translation that drops `{{provider}}` still renders — just without the name of the company
232
+ // the link belongs to, which is the one word of that line a reader needed.
233
+ const en = DEFAULT_CONSENT_MESSAGES.en
234
+ const placeholders = (text: string) => (text.match(/\{\{[a-z]+\}\}/gi) ?? []).sort()
235
+ for (const lng of CONSENT_LOCALES) {
236
+ const bundle = DEFAULT_CONSENT_MESSAGES[lng]
237
+ const drifted = Object.keys(en).filter(key =>
238
+ typeof bundle[key] === 'string'
239
+ && placeholders(bundle[key]).join() !== placeholders(en[key]).join())
240
+ expect({ lng, drifted }).toEqual({ lng, drifted: [] })
241
+ }
242
+ })
243
+
196
244
  test('an unknown locale falls back to English rather than to a raw key', () => {
197
245
  expect(normalizeLocale('kl')).toBe('en')
198
246
  expect(defaultConsentTranslate('kl')('title', 'fallback')).toBe('Cookie Preferences')
@@ -0,0 +1,191 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+ import { CONSENT_ANALYTICS, CONSENT_ESSENTIAL, CONSENT_EVENT, CONSENT_KEY, CONSENT_MARKETING } from '../src/consts.js'
3
+ import { consentGateScript, trackingGranted } from '../src/gtm.js'
4
+ import type { ConsentCategory, ConsentRecord } from '../src/types.js'
5
+
6
+ describe('trackingGranted', () => {
7
+ test('no record at all is not granted', () => {
8
+ expect(trackingGranted(null)).toBe(false)
9
+ })
10
+
11
+ test('an essential-only record is not granted — required categories never count', () => {
12
+ const record: ConsentRecord = {
13
+ [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: false, [CONSENT_MARKETING]: false,
14
+ }
15
+
16
+ expect(trackingGranted(record)).toBe(false)
17
+ })
18
+
19
+ test('analytics granted is tracking-granted', () => {
20
+ const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_ANALYTICS]: true }
21
+
22
+ expect(trackingGranted(record)).toBe(true)
23
+ })
24
+
25
+ test('marketing granted is tracking-granted too', () => {
26
+ const record: ConsentRecord = { [CONSENT_ESSENTIAL]: true, [CONSENT_MARKETING]: true }
27
+
28
+ expect(trackingGranted(record)).toBe(true)
29
+ })
30
+
31
+ test('a required category is ignored even when the record marks it granted', () => {
32
+ // A category can be BOTH required and signal-bearing (`essential` drives security_storage /
33
+ // functionality_storage by default) — required must still veto it, because everyone gets it
34
+ // regardless of what the record says.
35
+ const categories: ConsentCategory[] = [
36
+ {
37
+ key: 'essential', required: true, labelKey: 'a', descriptionKey: 'b',
38
+ signals: ['security_storage'],
39
+ },
40
+ ]
41
+
42
+ expect(trackingGranted({ essential: true }, categories)).toBe(false)
43
+ })
44
+
45
+ test('a category with no signals is ignored even when granted', () => {
46
+ const categories: ConsentCategory[] = [
47
+ { key: 'preferences', labelKey: 'a', descriptionKey: 'b' },
48
+ ]
49
+
50
+ expect(trackingGranted({ preferences: true }, categories)).toBe(false)
51
+ })
52
+ })
53
+
54
+ describe('consentGateScript — the two reachable paths', () => {
55
+ /** A minimal window/document the generated script can run against, with an event bus. */
56
+ const makeBrowser = (stored?: { key?: string, record: Record<string, unknown> }) => {
57
+ const storage = new Map<string, string>()
58
+ if (stored != null) {
59
+ storage.set(stored.key ?? CONSENT_KEY, JSON.stringify(stored.record))
60
+ }
61
+ const listeners = new Map<string, ((event: unknown) => void)[]>()
62
+ const win: Record<string, unknown> & {
63
+ addEventListener: (type: string, cb: (event: unknown) => void) => void
64
+ removeEventListener: (type: string, cb: (event: unknown) => void) => void
65
+ } = {
66
+ localStorage: { getItem: (k: string) => storage.get(k) ?? null },
67
+ addEventListener: (type, cb) => {
68
+ listeners.set(type, [...(listeners.get(type) ?? []), cb])
69
+ },
70
+ removeEventListener: (type, cb) => {
71
+ listeners.set(type, (listeners.get(type) ?? []).filter(listener => listener !== cb))
72
+ },
73
+ }
74
+ const doc = { cookie: '' }
75
+
76
+ return {
77
+ win,
78
+ run: (script: string) => {
79
+ // eslint-disable-next-line no-new-func
80
+ new Function('window', 'document', script)(win, doc)
81
+ },
82
+ dispatch: (detail: unknown) => {
83
+ for (const cb of listeners.get(CONSENT_EVENT) ?? []) {
84
+ cb({ detail })
85
+ }
86
+ },
87
+ listenerCount: () => listeners.get(CONSENT_EVENT)?.length ?? 0,
88
+ }
89
+ }
90
+
91
+ test('a returning visitor with a stored grant runs the loader immediately, synchronously', () => {
92
+ const browser = makeBrowser({ record: { essential: true, analytics: true, v: 2 } })
93
+ browser.win.__loaded = 0
94
+ browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
95
+
96
+ expect(browser.win.__loaded).toBe(1)
97
+ // Nothing left listening — the immediate branch never subscribes.
98
+ expect(browser.listenerCount()).toBe(0)
99
+ })
100
+
101
+ test('a stored record that grants nothing does not run the loader immediately', () => {
102
+ const browser = makeBrowser({ record: { essential: true, analytics: false, v: 2 } })
103
+ browser.win.__loaded = 0
104
+ browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
105
+
106
+ expect(browser.win.__loaded).toBe(0)
107
+ expect(browser.listenerCount()).toBe(1)
108
+ })
109
+
110
+ test('no stored record at all falls to the event-listener branch and waits', () => {
111
+ const browser = makeBrowser()
112
+ browser.win.__loaded = 0
113
+ browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1'))
114
+
115
+ expect(browser.win.__loaded).toBe(0)
116
+
117
+ // A denying event changes nothing.
118
+ browser.dispatch({ record: { essential: true, analytics: false } })
119
+ expect(browser.win.__loaded).toBe(0)
120
+
121
+ // The first granting event runs the loader and removes the listener.
122
+ browser.dispatch({ record: { essential: true, analytics: true } })
123
+ expect(browser.win.__loaded).toBe(1)
124
+ expect(browser.listenerCount()).toBe(0)
125
+
126
+ // A later event does nothing further — the listener already removed itself.
127
+ browser.dispatch({ record: { essential: true, analytics: true } })
128
+ expect(browser.win.__loaded).toBe(1)
129
+ })
130
+
131
+ test('the cookie answers when localStorage has nothing stored', () => {
132
+ // Same fallback order `consentBootstrapScript`/`readConsent` use: localStorage first, the
133
+ // cookie second. Simulated here by handing the script a `document` whose cookie carries the
134
+ // record and a `window` whose localStorage is empty.
135
+ const record = JSON.stringify({ essential: true, analytics: true, v: 2 })
136
+ const win: Record<string, unknown> & {
137
+ addEventListener: (type: string, cb: (event: unknown) => void) => void
138
+ removeEventListener: (type: string, cb: (event: unknown) => void) => void
139
+ } = {
140
+ localStorage: { getItem: () => null },
141
+ addEventListener: () => {},
142
+ removeEventListener: () => {},
143
+ __loaded: 0,
144
+ }
145
+ const doc = { cookie: `${CONSENT_KEY}=${record}` }
146
+ // eslint-disable-next-line no-new-func
147
+ new Function(
148
+ 'window', 'document', consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
149
+ )(win, doc)
150
+
151
+ expect(win.__loaded).toBe(1)
152
+ })
153
+
154
+ test('only a signal-bearing, non-required category can satisfy the gate', () => {
155
+ const categories: ConsentCategory[] = [
156
+ { key: 'essential', required: true, labelKey: 'a', descriptionKey: 'b', signals: ['security_storage'] },
157
+ { key: 'analytics', labelKey: 'c', descriptionKey: 'd', signals: ['analytics_storage'] },
158
+ ]
159
+ const browser = makeBrowser({ record: { essential: true, analytics: false, v: 2 } })
160
+ browser.win.__loaded = 0
161
+ browser.run(consentGateScript('window.__loaded = (window.__loaded || 0) + 1', { categories }))
162
+
163
+ expect(browser.win.__loaded).toBe(0)
164
+
165
+ browser.dispatch({ record: { essential: true, analytics: true } })
166
+ expect(browser.win.__loaded).toBe(1)
167
+ })
168
+
169
+ test('the generated script carries both reachable code paths in its text', () => {
170
+ const script = consentGateScript('window.__loaded = (window.__loaded || 0) + 1')
171
+
172
+ // The immediate branch: `load()` called right after the stored-record check, before any
173
+ // listener is attached.
174
+ expect(script.indexOf('load()')).toBeLessThan(script.indexOf('addEventListener'))
175
+ // The event-listener branch: the same loader, reachable a second time from inside the handler
176
+ // — plus the `function load(){...}` declaration itself, three occurrences of `load()` in all.
177
+ expect(script.match(/load\(\)/g)?.length).toBe(3)
178
+ expect(script).toContain(JSON.stringify(CONSENT_EVENT))
179
+ expect(script).toContain(JSON.stringify(CONSENT_KEY))
180
+ })
181
+
182
+ test('a custom storage key is honoured, matching the bootstrap script\'s own lookup', () => {
183
+ const browser = makeBrowser({ key: 'custom_key', record: { essential: true, analytics: true, v: 2 } })
184
+ browser.win.__loaded = 0
185
+ browser.run(consentGateScript(
186
+ 'window.__loaded = (window.__loaded || 0) + 1', { storageKey: 'custom_key' }
187
+ ))
188
+
189
+ expect(browser.win.__loaded).toBe(1)
190
+ })
191
+ })