@owlmeans/consent 0.1.18-rc.10
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 +64 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +90 -0
- package/build/consts.js.map +1 -0
- package/build/consts.local.d.ts +4 -0
- package/build/consts.local.d.ts.map +1 -0
- package/build/consts.local.js +4 -0
- package/build/consts.local.js.map +1 -0
- package/build/gtm.d.ts +72 -0
- package/build/gtm.d.ts.map +1 -0
- package/build/gtm.js +182 -0
- package/build/gtm.js.map +1 -0
- package/build/i18n/be.json +36 -0
- package/build/i18n/de.json +36 -0
- package/build/i18n/en.json +36 -0
- package/build/i18n/es.json +36 -0
- package/build/i18n/fr.json +36 -0
- package/build/i18n/pl.json +36 -0
- package/build/i18n/ru.json +36 -0
- package/build/i18n/uk.json +36 -0
- package/build/i18n.d.ts +16 -0
- package/build/i18n.d.ts.map +1 -0
- package/build/i18n.js +29 -0
- package/build/i18n.js.map +1 -0
- package/build/index.d.ts +12 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +9 -0
- package/build/index.js.map +1 -0
- package/build/linker.d.ts +55 -0
- package/build/linker.d.ts.map +1 -0
- package/build/linker.js +334 -0
- package/build/linker.js.map +1 -0
- package/build/plugins.d.ts +30 -0
- package/build/plugins.d.ts.map +1 -0
- package/build/plugins.js +73 -0
- package/build/plugins.js.map +1 -0
- package/build/storage.d.ts +27 -0
- package/build/storage.d.ts.map +1 -0
- package/build/storage.js +86 -0
- package/build/storage.js.map +1 -0
- package/build/store.d.ts +16 -0
- package/build/store.d.ts.map +1 -0
- package/build/store.js +100 -0
- package/build/store.js.map +1 -0
- package/build/types.d.ts +205 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/types.local.d.ts +5 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/package.json +33 -0
- package/src/consts.local.ts +4 -0
- package/src/consts.ts +103 -0
- package/src/gtm.ts +216 -0
- package/src/i18n/be.json +36 -0
- package/src/i18n/de.json +36 -0
- package/src/i18n/en.json +36 -0
- package/src/i18n/es.json +36 -0
- package/src/i18n/fr.json +36 -0
- package/src/i18n/pl.json +36 -0
- package/src/i18n/ru.json +36 -0
- package/src/i18n/uk.json +36 -0
- package/src/i18n.ts +39 -0
- package/src/index.ts +14 -0
- package/src/linker.ts +369 -0
- package/src/plugins.ts +88 -0
- package/src/storage.ts +96 -0
- package/src/store.ts +126 -0
- package/src/types.local.ts +4 -0
- package/src/types.ts +218 -0
- package/tests/consent.spec.ts +256 -0
- package/tests/linker.spec.ts +774 -0
- package/tests/tracking.spec.ts +191 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,36 @@
|
|
|
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 vos choix en matière de cookies et de la langue de l'interface. 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
|
+
"domains": "Ce choix s'applique à {{domains}}.",
|
|
35
|
+
"policyDomains": "Ce choix s'applique aux domaines suivants :"
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Preferencje dotyczące plików cookie",
|
|
3
|
+
"description": "Używamy plików cookie w celu ulepszenia Twojego doświadczenia podczas przeglądania, wyświetlania spersonalizowanych reklam lub treści oraz analizowania naszego ruchu. Klikając „Akceptuj wszystkie\", wyrażasz zgodę na używanie przez nas plików cookie.",
|
|
4
|
+
"essential": "Niezbędne pliki cookie",
|
|
5
|
+
"essentialDesc": "Wymagane do działania witryny — logowanie, bezpieczeństwo oraz zapamiętanie Twoich wyborów dotyczących plików cookie i języka interfejsu. Nie można ich wyłączyć.",
|
|
6
|
+
"analytics": "Analityczne pliki cookie",
|
|
7
|
+
"analyticsDesc": "Pomagają nam ulepszać naszą witrynę internetową, zbierając i raportując informacje o jej użytkowaniu.",
|
|
8
|
+
"marketing": "Marketingowe pliki cookie",
|
|
9
|
+
"marketingDesc": "Służą do śledzenia odwiedzających na różnych stronach internetowych w celu wyświetlania trafnych reklam, dostosowanych do konkretnego użytkownika.",
|
|
10
|
+
"required": "Wymagane",
|
|
11
|
+
"acceptAll": "Akceptuj wszystkie",
|
|
12
|
+
"acceptAndContinue": "Akceptuj i kontynuuj",
|
|
13
|
+
"savePreferences": "Zapisz preferencje",
|
|
14
|
+
"policyLink": "Polityka plików cookie",
|
|
15
|
+
"openPreferences": "Preferencje plików cookie",
|
|
16
|
+
"close": "Zamknij",
|
|
17
|
+
"loginReason": "Logowanie zapisuje plik cookie sesji. Zaakceptuj niezbędne pliki cookie, aby kontynuować.",
|
|
18
|
+
"manage": "Zarządzaj preferencjami",
|
|
19
|
+
"policyTitle": "Polityka plików cookie",
|
|
20
|
+
"policyIntro": "Ta strona opisuje pliki cookie i podobne mechanizmy przechowywania używane przez tę aplikację oraz sposób ich kontrolowania.",
|
|
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
|
+
"policyRetention": "Zapis jest przechowywany przez {{days}} dni, po czym zapytamy ponownie.",
|
|
23
|
+
"policyOperator": "Tę aplikację prowadzi {{operator}}.",
|
|
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",
|
|
34
|
+
"domains": "Ten wybór dotyczy: {{domains}}.",
|
|
35
|
+
"policyDomains": "Ten wybór dotyczy następujących domen:"
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Настройки файлов cookie",
|
|
3
|
+
"description": "Мы используем файлы cookie, чтобы улучшить ваш опыт просмотра, показывать персонализированную рекламу или контент и анализировать наш трафик. Нажимая «Принять все», вы соглашаетесь на использование файлов cookie.",
|
|
4
|
+
"essential": "Необходимые файлы cookie",
|
|
5
|
+
"essentialDesc": "Нужны для работы сайта — вход, безопасность и сохранение вашего выбора по файлам cookie и языка интерфейса. Их нельзя отключить.",
|
|
6
|
+
"analytics": "Аналитические файлы cookie",
|
|
7
|
+
"analyticsDesc": "Помогают нам улучшать наш сайт, собирая и предоставляя информацию о его использовании.",
|
|
8
|
+
"marketing": "Маркетинговые файлы cookie",
|
|
9
|
+
"marketingDesc": "Используются для отслеживания посетителей на разных сайтах для показа релевантной и интересной рекламы.",
|
|
10
|
+
"required": "Обязательно",
|
|
11
|
+
"acceptAll": "Принять все",
|
|
12
|
+
"acceptAndContinue": "Принять и продолжить",
|
|
13
|
+
"savePreferences": "Сохранить настройки",
|
|
14
|
+
"policyLink": "Политика использования файлов cookie",
|
|
15
|
+
"openPreferences": "Настройки файлов cookie",
|
|
16
|
+
"close": "Закрыть",
|
|
17
|
+
"loginReason": "Вход сохраняет сессионный файл cookie. Примите необходимые файлы cookie, чтобы продолжить.",
|
|
18
|
+
"manage": "Изменить настройки",
|
|
19
|
+
"policyTitle": "Политика использования файлов cookie",
|
|
20
|
+
"policyIntro": "На этой странице описаны файлы cookie и подобные хранилища, которые использует это приложение, и как ими управлять.",
|
|
21
|
+
"policyStorage": "Ваш выбор сохраняется в этом браузере под ключом «{{key}}» — и в локальном хранилище, и в файле cookie.",
|
|
22
|
+
"policyRetention": "Запись хранится {{days}} дней, после чего мы спросим снова.",
|
|
23
|
+
"policyOperator": "Это приложение принадлежит {{operator}}.",
|
|
24
|
+
"policyContact": "Ознакомьтесь с их политикой конфиденциальности и условиями использования, чтобы узнать, как они обрабатывают ваши данные.",
|
|
25
|
+
"policyServicesOf": "Сервисы — {{category}}",
|
|
26
|
+
"policyOtherServices": "Другие сервисы",
|
|
27
|
+
"policyOtherServicesDesc": "Сервисы, которые это приложение использует вне перечисленных выше категорий.",
|
|
28
|
+
"policyProvider": "Поставщик",
|
|
29
|
+
"policyPurpose": "Цель",
|
|
30
|
+
"policyCookies": "Файлы cookie",
|
|
31
|
+
"policyServicePrivacy": "Политика конфиденциальности: {{provider}}",
|
|
32
|
+
"privacy": "Политика конфиденциальности",
|
|
33
|
+
"terms": "Условия использования",
|
|
34
|
+
"domains": "Этот выбор применяется к {{domains}}.",
|
|
35
|
+
"policyDomains": "Этот выбор применяется к следующим доменам:"
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Налаштування файлів cookie",
|
|
3
|
+
"description": "Ми використовуємо файли cookie, щоб покращити ваш досвід перегляду, показувати персоналізовану рекламу чи вміст і аналізувати наш трафік. Натискаючи «Прийняти всі», ви погоджуєтеся на використання файлів cookie.",
|
|
4
|
+
"essential": "Необхідні файли cookie",
|
|
5
|
+
"essentialDesc": "Потрібні для роботи сайту — вхід, безпека та збереження вашого вибору щодо файлів cookie і мови інтерфейсу. Їх не можна вимкнути.",
|
|
6
|
+
"analytics": "Аналітичні файли cookie",
|
|
7
|
+
"analyticsDesc": "Допомагають нам покращувати наш вебсайт, збираючи та надаючи інформацію про його використання.",
|
|
8
|
+
"marketing": "Маркетингові файли cookie",
|
|
9
|
+
"marketingDesc": "Використовуються для відстеження відвідувачів на різних вебсайтах для показу релевантної та цікавої реклами.",
|
|
10
|
+
"required": "Обов'язково",
|
|
11
|
+
"acceptAll": "Прийняти всі",
|
|
12
|
+
"acceptAndContinue": "Прийняти й продовжити",
|
|
13
|
+
"savePreferences": "Зберегти налаштування",
|
|
14
|
+
"policyLink": "Політика щодо файлів cookie",
|
|
15
|
+
"openPreferences": "Налаштування файлів cookie",
|
|
16
|
+
"close": "Закрити",
|
|
17
|
+
"loginReason": "Вхід зберігає сеансовий файл cookie. Прийміть необхідні файли cookie, щоб продовжити.",
|
|
18
|
+
"manage": "Змінити налаштування",
|
|
19
|
+
"policyTitle": "Політика щодо файлів cookie",
|
|
20
|
+
"policyIntro": "Ця сторінка описує файли cookie та подібні сховища, які використовує цей застосунок, і як ними керувати.",
|
|
21
|
+
"policyStorage": "Ваш вибір зберігається в цьому браузері під ключем «{{key}}» — і в локальному сховищі, і у файлі cookie.",
|
|
22
|
+
"policyRetention": "Запис зберігається {{days}} днів, після чого ми запитаємо знову.",
|
|
23
|
+
"policyOperator": "Цей застосунок належить {{operator}}.",
|
|
24
|
+
"policyContact": "Ознайомтеся з їхньою політикою конфіденційності та умовами користування, щоб дізнатися, як вони обробляють ваші дані.",
|
|
25
|
+
"policyServicesOf": "Сервіси — {{category}}",
|
|
26
|
+
"policyOtherServices": "Інші сервіси",
|
|
27
|
+
"policyOtherServicesDesc": "Сервіси, які цей застосунок використовує поза переліченими вище категоріями.",
|
|
28
|
+
"policyProvider": "Постачальник",
|
|
29
|
+
"policyPurpose": "Мета",
|
|
30
|
+
"policyCookies": "Файли cookie",
|
|
31
|
+
"policyServicePrivacy": "Політика конфіденційності: {{provider}}",
|
|
32
|
+
"privacy": "Політика конфіденційності",
|
|
33
|
+
"terms": "Умови користування",
|
|
34
|
+
"domains": "Цей вибір застосовується до {{domains}}.",
|
|
35
|
+
"policyDomains": "Цей вибір застосовується до таких доменів:"
|
|
36
|
+
}
|
package/build/i18n.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ConsentLocale } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The dialog's copy, in the box.
|
|
4
|
+
*
|
|
5
|
+
* Carried here rather than registered into an i18n framework because one of the three consumers is
|
|
6
|
+
* an Astro site with React islands and no OwlMeans i18n at all — and a consent dialog that renders
|
|
7
|
+
* raw keys is worse than no dialog. An application that HAS translations passes `translate` and
|
|
8
|
+
* overrides every one of these.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_CONSENT_MESSAGES: Record<ConsentLocale, Record<string, string>>;
|
|
11
|
+
export declare const normalizeLocale: (locale?: string) => ConsentLocale;
|
|
12
|
+
/** `(key, defaultValue) => string`, the house shape, backed by the built-in bundle. */
|
|
13
|
+
export declare const defaultConsentTranslate: (locale?: string) => (key: string, defaultValue: string) => string;
|
|
14
|
+
/** Substitute `{{name}}` placeholders. Enough for the two the policy page needs. */
|
|
15
|
+
export declare const interpolate: (text: string, values: Record<string, string | number>) => string;
|
|
16
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAW/C;;;;;;;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
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CONSENT_LOCALES } from './consts.js';
|
|
2
|
+
import en from './i18n/en.json' with { type: 'json' };
|
|
3
|
+
import pl from './i18n/pl.json' with { type: 'json' };
|
|
4
|
+
import ru from './i18n/ru.json' with { type: 'json' };
|
|
5
|
+
import be from './i18n/be.json' with { type: 'json' };
|
|
6
|
+
import uk from './i18n/uk.json' with { type: 'json' };
|
|
7
|
+
import es from './i18n/es.json' with { type: 'json' };
|
|
8
|
+
import de from './i18n/de.json' with { type: 'json' };
|
|
9
|
+
import fr from './i18n/fr.json' with { type: 'json' };
|
|
10
|
+
/**
|
|
11
|
+
* The dialog's copy, in the box.
|
|
12
|
+
*
|
|
13
|
+
* Carried here rather than registered into an i18n framework because one of the three consumers is
|
|
14
|
+
* an Astro site with React islands and no OwlMeans i18n at all — and a consent dialog that renders
|
|
15
|
+
* raw keys is worse than no dialog. An application that HAS translations passes `translate` and
|
|
16
|
+
* overrides every one of these.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_CONSENT_MESSAGES = {
|
|
19
|
+
en, pl, ru, be, uk, es, de, fr,
|
|
20
|
+
};
|
|
21
|
+
export const normalizeLocale = (locale) => {
|
|
22
|
+
const base = (locale ?? 'en').toLowerCase().split('-')[0];
|
|
23
|
+
return CONSENT_LOCALES.includes(base) ? base : 'en';
|
|
24
|
+
};
|
|
25
|
+
/** `(key, defaultValue) => string`, the house shape, backed by the built-in bundle. */
|
|
26
|
+
export const defaultConsentTranslate = (locale) => (key, defaultValue) => DEFAULT_CONSENT_MESSAGES[normalizeLocale(locale)]?.[key] ?? defaultValue;
|
|
27
|
+
/** Substitute `{{name}}` placeholders. Enough for the two the policy page needs. */
|
|
28
|
+
export const interpolate = (text, values) => text.replace(/\{\{([a-z]+)\}\}/gi, (whole, name) => values[name] != null ? String(values[name]) : whole);
|
|
29
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +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;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/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type * from './types.js';
|
|
2
|
+
export * from './consts.js';
|
|
3
|
+
export * from './storage.js';
|
|
4
|
+
export * from './gtm.js';
|
|
5
|
+
export * from './store.js';
|
|
6
|
+
export * from './i18n.js';
|
|
7
|
+
export type { ConsentPlugin } from './types.js';
|
|
8
|
+
export { registerConsentPlugin, consentPlugins, decorateConsentUrl, consentDomains, adoptConsent, startConsentPlugins, adoptConsentLanguage, } from './plugins.js';
|
|
9
|
+
export type { ConsentLinkPayload } from './types.js';
|
|
10
|
+
export { consentLinker, encodeConsentLink, decodeConsentLink, stripConsentLinkParam, consentLinkerScript, writeConsentLanguage } from './linker.js';
|
|
11
|
+
export { CONSENT_LINK_PARAM, CONSENT_LINK_MAX_AGE, CONSENT_LINK_SKEW } from './consts.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,qBAAqB,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EACvF,mBAAmB,EAAE,oBAAoB,GAC1C,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACnJ,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './consts.js';
|
|
2
|
+
export * from './storage.js';
|
|
3
|
+
export * from './gtm.js';
|
|
4
|
+
export * from './store.js';
|
|
5
|
+
export * from './i18n.js';
|
|
6
|
+
export { registerConsentPlugin, consentPlugins, decorateConsentUrl, consentDomains, adoptConsent, startConsentPlugins, adoptConsentLanguage, } from './plugins.js';
|
|
7
|
+
export { consentLinker, encodeConsentLink, decodeConsentLink, stripConsentLinkParam, consentLinkerScript, writeConsentLanguage } from './linker.js';
|
|
8
|
+
export { CONSENT_LINK_PARAM, CONSENT_LINK_MAX_AGE, CONSENT_LINK_SKEW } from './consts.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AAEzB,OAAO,EACL,qBAAqB,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EACvF,mBAAmB,EAAE,oBAAoB,GAC1C,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACnJ,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ConsentPlugin, ConsentOptions, ConsentRecord, ConsentLinkPayload } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The link parameter's value: the current document's decision as `c` (one `0`/`1` per optional
|
|
4
|
+
* category — empty while `record` is `null`, i.e. before the visitor has decided) and, when
|
|
5
|
+
* `opts.linker.language` is set, the page's language as `l`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const encodeConsentLink: (record: ConsentRecord | null, opts?: ConsentOptions) => string;
|
|
8
|
+
/** Parse-only — no trust decision here, that is `consentLinker(opts).adopt`'s job. Never throws. */
|
|
9
|
+
export declare const decodeConsentLink: (value: string) => ConsentLinkPayload | null;
|
|
10
|
+
/**
|
|
11
|
+
* Persist an adopted language where the application's i18n layer reads its explicit choice from —
|
|
12
|
+
* `linker.language.storageKey`, `owlmeans-lng` by default. Unconditional: the interface language is
|
|
13
|
+
* strictly necessary storage (the visitor picked it, and the site cannot speak to them without it),
|
|
14
|
+
* so no cookie decision — none yet, a refusal, an old record — stands between it and the device.
|
|
15
|
+
*
|
|
16
|
+
* It overwrites what is there on purpose: the carried language is the one the visitor was reading a
|
|
17
|
+
* moment ago, which outranks a choice they made on this domain some other day. Storage that refuses
|
|
18
|
+
* the write is not an error. Returns whether the language was written.
|
|
19
|
+
*/
|
|
20
|
+
export declare const writeConsentLanguage: (language: string, opts?: ConsentOptions) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Cross-domain consent: share this decision, and adopt one, between the domains named in
|
|
23
|
+
* `opts.linker.domains`.
|
|
24
|
+
*
|
|
25
|
+
* `start` decorates outgoing first-party links; `adopt` reads and validates an incoming one;
|
|
26
|
+
* `domains` discloses the list a dialog or policy page names. `decorate` is generic (a bare `URL`,
|
|
27
|
+
* no DOM) so the same trust rule serves a real anchor click and a programmatic navigation
|
|
28
|
+
* (`decorateConsentUrl`) alike — the `rel="noreferrer"` exemption is a DOM-only concept and is
|
|
29
|
+
* therefore checked by `start`'s click handler, never here.
|
|
30
|
+
*/
|
|
31
|
+
export declare const consentLinker: () => ConsentPlugin;
|
|
32
|
+
/**
|
|
33
|
+
* Remove the link parameter from the visible URL, keeping every other parameter and the hash — the
|
|
34
|
+
* real-TS equivalent of what {@link consentLinkerScript}'s embedded fragment already did inline.
|
|
35
|
+
* Idempotent: a page with no head script calls this from `consentStore.init` and it strips once; a
|
|
36
|
+
* page that ALSO carried the head script finds the parameter already gone and does nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare const stripConsentLinkParam: (opts: ConsentOptions) => void;
|
|
39
|
+
/**
|
|
40
|
+
* The inline `<head>` fragment that adopts a decorated link's decision and strips the parameter —
|
|
41
|
+
* standalone, so a page can stamp it even where nothing else here runs (a legal page with no tag
|
|
42
|
+
* script). `@owlmeans/web-gtm`'s `consentBootstrapScript` embeds this SAME fragment right after
|
|
43
|
+
* pushing Consent Mode's defaults, so a page that has both never runs it twice with different
|
|
44
|
+
* effect — the second attempt finds the parameter already gone and does nothing.
|
|
45
|
+
*
|
|
46
|
+
* Mirrors `consentLinker().adopt` exactly (v2, referrer + freshness/skew + full optional coverage +
|
|
47
|
+
* no existing record), but as hand-rolled JS text rather than a call into this module — the same
|
|
48
|
+
* discipline `consentBootstrapScript`/`consentGateScript` already follow, because this has to run
|
|
49
|
+
* before any bundle (this module included) has loaded. The parameter is ALWAYS stripped via
|
|
50
|
+
* `history.replaceState`, whether or not the trust rule accepts it: a stale or foreign `owlcc` is
|
|
51
|
+
* exactly as much noise as an adopted one, and neither belongs in the visible URL or in what a page
|
|
52
|
+
* reload, a shared link or a browser history entry carries forward.
|
|
53
|
+
*/
|
|
54
|
+
export declare const consentLinkerScript: (opts: ConsentOptions) => string;
|
|
55
|
+
//# sourceMappingURL=linker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linker.d.ts","sourceRoot":"","sources":["../src/linker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAmB,cAAc,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAsCnH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,WAAY,aAAa,GAAG,IAAI,SAAS,cAAc,KAAG,MAavF,CAAA;AAED,oGAAoG;AACpG,eAAO,MAAM,iBAAiB,UAAW,MAAM,KAAG,kBAAkB,GAAG,IAuBtE,CAAA;AAyDD;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,aAAc,MAAM,SAAS,cAAc,KAAG,OAS9E,CAAA;AAQD;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAO,aA2FhC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,SAAU,cAAc,KAAG,IAgB5D,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,SAAU,cAAc,KAAG,MA2D1D,CAAA"}
|
package/build/linker.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { CONSENT_COOKIE_DAYS, CONSENT_KEY, CONSENT_LANGUAGE_KEY, CONSENT_SCHEMA_VERSION, DEFAULT_CONSENT_CATEGORIES, CONSENT_LINK_MAX_AGE, CONSENT_LINK_PARAM, CONSENT_LINK_SKEW } from './consts.js';
|
|
2
|
+
import { decorateConsentUrl } from './plugins.js';
|
|
3
|
+
import { readConsent } from './storage.js';
|
|
4
|
+
import { CONSENT_LINK_VERSION, LANGUAGE_TAG } from './consts.local.js';
|
|
5
|
+
const categoriesOf = (opts) => opts?.categories ?? DEFAULT_CONSENT_CATEGORIES;
|
|
6
|
+
const optionalOf = (opts) => categoriesOf(opts).filter(category => category.required !== true);
|
|
7
|
+
const toBase64Url = (json) => btoa(json).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
8
|
+
const fromBase64Url = (value) => {
|
|
9
|
+
try {
|
|
10
|
+
let padded = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
11
|
+
while (padded.length % 4 !== 0) {
|
|
12
|
+
padded += '=';
|
|
13
|
+
}
|
|
14
|
+
return atob(padded);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** The language this document is written in — `<html lang>`, the one signal every page has. */
|
|
21
|
+
const documentLanguage = () => {
|
|
22
|
+
if (typeof document === 'undefined') {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const lang = document.documentElement?.lang?.trim() ?? '';
|
|
26
|
+
return LANGUAGE_TAG.test(lang) ? lang.toLowerCase() : null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The link parameter's value: the current document's decision as `c` (one `0`/`1` per optional
|
|
30
|
+
* category — empty while `record` is `null`, i.e. before the visitor has decided) and, when
|
|
31
|
+
* `opts.linker.language` is set, the page's language as `l`.
|
|
32
|
+
*/
|
|
33
|
+
export const encodeConsentLink = (record, opts) => {
|
|
34
|
+
const c = {};
|
|
35
|
+
if (record != null) {
|
|
36
|
+
for (const category of optionalOf(opts)) {
|
|
37
|
+
c[category.key] = record[category.key] === true ? 1 : 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const language = opts?.linker?.language != null ? documentLanguage() : null;
|
|
41
|
+
const payload = {
|
|
42
|
+
v: CONSENT_LINK_VERSION, c, t: Math.floor(Date.now() / 1000), ...(language != null ? { l: language } : {}),
|
|
43
|
+
};
|
|
44
|
+
return toBase64Url(JSON.stringify(payload));
|
|
45
|
+
};
|
|
46
|
+
/** Parse-only — no trust decision here, that is `consentLinker(opts).adopt`'s job. Never throws. */
|
|
47
|
+
export const decodeConsentLink = (value) => {
|
|
48
|
+
const json = fromBase64Url(value);
|
|
49
|
+
if (json == null) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(json);
|
|
54
|
+
if (parsed == null || typeof parsed !== 'object') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const { v, c, t, l } = parsed;
|
|
58
|
+
if (v !== CONSENT_LINK_VERSION || typeof t !== 'number' || c == null || typeof c !== 'object') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
v: CONSENT_LINK_VERSION, c: c, t,
|
|
63
|
+
// A malformed language is dropped, never a reason to refuse the decision beside it.
|
|
64
|
+
...(typeof l === 'string' && LANGUAGE_TAG.test(l) ? { l: l.toLowerCase() } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const referrerHostname = () => {
|
|
72
|
+
if (typeof document === 'undefined' || document.referrer === '') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return new URL(document.referrer).hostname;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The payload of the link this document was opened from, once it has passed every check that does
|
|
84
|
+
* not depend on WHAT it carries: the parameter decodes and its version matches, its timestamp is
|
|
85
|
+
* fresh (and not from the future beyond the allowed skew), and the request's referrer host is a
|
|
86
|
+
* LISTED domain — read from `document.referrer`, never from the parameter itself. `null` otherwise.
|
|
87
|
+
*/
|
|
88
|
+
const trustedPayload = (opts) => {
|
|
89
|
+
const linker = opts.linker;
|
|
90
|
+
if (linker == null || typeof location === 'undefined') {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const raw = new URLSearchParams(location.search).get(linker.param ?? CONSENT_LINK_PARAM);
|
|
94
|
+
if (raw == null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const decoded = decodeConsentLink(raw);
|
|
98
|
+
if (decoded == null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const now = Math.floor(Date.now() / 1000);
|
|
102
|
+
const maxAge = linker.maxAge ?? CONSENT_LINK_MAX_AGE;
|
|
103
|
+
if (now - decoded.t > maxAge || decoded.t - now > CONSENT_LINK_SKEW) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const referrer = referrerHostname();
|
|
107
|
+
if (referrer == null || !linker.domains.includes(referrer)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return decoded;
|
|
111
|
+
};
|
|
112
|
+
/** The entry of `supported` that `language` names — exactly, else by its base tag — or `null`. */
|
|
113
|
+
const supportedLanguage = (language, supported) => {
|
|
114
|
+
const wanted = language.toLowerCase();
|
|
115
|
+
const exact = supported.find(candidate => candidate.toLowerCase() === wanted);
|
|
116
|
+
if (exact != null) {
|
|
117
|
+
return exact;
|
|
118
|
+
}
|
|
119
|
+
const base = wanted.split(/[-_]/)[0];
|
|
120
|
+
return supported.find(candidate => candidate.toLowerCase() === base) ?? null;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Persist an adopted language where the application's i18n layer reads its explicit choice from —
|
|
124
|
+
* `linker.language.storageKey`, `owlmeans-lng` by default. Unconditional: the interface language is
|
|
125
|
+
* strictly necessary storage (the visitor picked it, and the site cannot speak to them without it),
|
|
126
|
+
* so no cookie decision — none yet, a refusal, an old record — stands between it and the device.
|
|
127
|
+
*
|
|
128
|
+
* It overwrites what is there on purpose: the carried language is the one the visitor was reading a
|
|
129
|
+
* moment ago, which outranks a choice they made on this domain some other day. Storage that refuses
|
|
130
|
+
* the write is not an error. Returns whether the language was written.
|
|
131
|
+
*/
|
|
132
|
+
export const writeConsentLanguage = (language, opts) => {
|
|
133
|
+
try {
|
|
134
|
+
localStorage.setItem(opts?.linker?.language?.storageKey ?? CONSENT_LANGUAGE_KEY, language);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Private modes and blocked storage both throw; the app simply keeps choosing its own language.
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
/** Escape `<` inside a JSON literal meant for an inline `<script>` — the same discipline every
|
|
143
|
+
* caller of `consentBootstrapScript`/`consentGateScript` already owes the HTML it lands in, applied
|
|
144
|
+
* here at the source since this fragment's dynamic values (a configured domain, a storage key) are
|
|
145
|
+
* least likely to have been reviewed for it by whoever composes the final page. */
|
|
146
|
+
const jsonForScript = (value) => JSON.stringify(value).replace(/</g, '\\u003c');
|
|
147
|
+
/**
|
|
148
|
+
* Cross-domain consent: share this decision, and adopt one, between the domains named in
|
|
149
|
+
* `opts.linker.domains`.
|
|
150
|
+
*
|
|
151
|
+
* `start` decorates outgoing first-party links; `adopt` reads and validates an incoming one;
|
|
152
|
+
* `domains` discloses the list a dialog or policy page names. `decorate` is generic (a bare `URL`,
|
|
153
|
+
* no DOM) so the same trust rule serves a real anchor click and a programmatic navigation
|
|
154
|
+
* (`decorateConsentUrl`) alike — the `rel="noreferrer"` exemption is a DOM-only concept and is
|
|
155
|
+
* therefore checked by `start`'s click handler, never here.
|
|
156
|
+
*/
|
|
157
|
+
export const consentLinker = () => {
|
|
158
|
+
let installed = false;
|
|
159
|
+
return {
|
|
160
|
+
alias: 'linker',
|
|
161
|
+
start: opts => {
|
|
162
|
+
if (typeof document === 'undefined' || installed || opts.linker == null) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
installed = true;
|
|
166
|
+
const onNavigate = (event) => {
|
|
167
|
+
const target = event.target;
|
|
168
|
+
const anchor = target instanceof Element ? target.closest('a[href]') : null;
|
|
169
|
+
if (!(anchor instanceof HTMLAnchorElement)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// The trust rule keeps `noreferrer` links untouched: a link that refuses to disclose the
|
|
173
|
+
// referrer is refusing exactly the signal the receiving side's `adopt` needs to trust it.
|
|
174
|
+
if (/\bnoreferrer\b/.test(anchor.getAttribute('rel') ?? '')) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// No decision yet is still worth a decoration when a language rides along with it.
|
|
178
|
+
const record = readConsent(opts);
|
|
179
|
+
if (record == null && opts.linker?.language == null) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let url;
|
|
183
|
+
try {
|
|
184
|
+
url = new URL(anchor.href, location.href);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const decorated = decorateConsentUrl(url, record, opts);
|
|
190
|
+
if (decorated.toString() !== url.toString()) {
|
|
191
|
+
anchor.href = decorated.toString();
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
// Capture phase: this must decide before the click's own navigation (or a framework router
|
|
195
|
+
// intercepting it) reads `href`. `auxclick`/`contextmenu` cover a middle-click and "copy link
|
|
196
|
+
// address" — an address copied undecorated is a link that silently fails to carry the choice.
|
|
197
|
+
document.addEventListener('click', onNavigate, true);
|
|
198
|
+
document.addEventListener('auxclick', onNavigate, true);
|
|
199
|
+
document.addEventListener('contextmenu', onNavigate, true);
|
|
200
|
+
},
|
|
201
|
+
decorate: (url, record, opts) => {
|
|
202
|
+
const linker = opts.linker;
|
|
203
|
+
const host = typeof location !== 'undefined' ? location.hostname : '';
|
|
204
|
+
if (linker == null || url.hostname === host || !linker.domains.includes(url.hostname)) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
// A link carries a decision, a language, or both; with neither there is nothing to say.
|
|
208
|
+
if (record == null && (linker.language == null || documentLanguage() == null)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const next = new URL(url.toString());
|
|
212
|
+
next.searchParams.set(linker.param ?? CONSENT_LINK_PARAM, encodeConsentLink(record, opts));
|
|
213
|
+
return next;
|
|
214
|
+
},
|
|
215
|
+
adopt: opts => {
|
|
216
|
+
const decoded = trustedPayload(opts);
|
|
217
|
+
if (decoded == null) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const optional = optionalOf(opts);
|
|
221
|
+
if (!optional.every(category => decoded.c[category.key] === 0 || decoded.c[category.key] === 1)) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const record = { v: CONSENT_SCHEMA_VERSION };
|
|
225
|
+
for (const category of categoriesOf(opts)) {
|
|
226
|
+
record[category.key] = category.required === true || decoded.c[category.key] === 1;
|
|
227
|
+
}
|
|
228
|
+
return record;
|
|
229
|
+
},
|
|
230
|
+
adoptLanguage: opts => {
|
|
231
|
+
const supported = opts.linker?.language?.supported;
|
|
232
|
+
const decoded = supported != null ? trustedPayload(opts) : null;
|
|
233
|
+
return decoded?.l != null && supported != null ? supportedLanguage(decoded.l, supported) : null;
|
|
234
|
+
},
|
|
235
|
+
domains: opts => opts.linker?.domains ?? [],
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Remove the link parameter from the visible URL, keeping every other parameter and the hash — the
|
|
240
|
+
* real-TS equivalent of what {@link consentLinkerScript}'s embedded fragment already did inline.
|
|
241
|
+
* Idempotent: a page with no head script calls this from `consentStore.init` and it strips once; a
|
|
242
|
+
* page that ALSO carried the head script finds the parameter already gone and does nothing.
|
|
243
|
+
*/
|
|
244
|
+
export const stripConsentLinkParam = (opts) => {
|
|
245
|
+
const linker = opts.linker;
|
|
246
|
+
if (linker == null || typeof location === 'undefined' || typeof history === 'undefined') {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const param = linker.param ?? CONSENT_LINK_PARAM;
|
|
250
|
+
const url = new URL(location.href);
|
|
251
|
+
if (!url.searchParams.has(param)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
url.searchParams.delete(param);
|
|
255
|
+
try {
|
|
256
|
+
history.replaceState(history.state, '', `${url.pathname}${url.search}${url.hash}`);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// A sandboxed/embedded context may refuse history writes; the parameter just stays visible.
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* The inline `<head>` fragment that adopts a decorated link's decision and strips the parameter —
|
|
264
|
+
* standalone, so a page can stamp it even where nothing else here runs (a legal page with no tag
|
|
265
|
+
* script). `@owlmeans/web-gtm`'s `consentBootstrapScript` embeds this SAME fragment right after
|
|
266
|
+
* pushing Consent Mode's defaults, so a page that has both never runs it twice with different
|
|
267
|
+
* effect — the second attempt finds the parameter already gone and does nothing.
|
|
268
|
+
*
|
|
269
|
+
* Mirrors `consentLinker().adopt` exactly (v2, referrer + freshness/skew + full optional coverage +
|
|
270
|
+
* no existing record), but as hand-rolled JS text rather than a call into this module — the same
|
|
271
|
+
* discipline `consentBootstrapScript`/`consentGateScript` already follow, because this has to run
|
|
272
|
+
* before any bundle (this module included) has loaded. The parameter is ALWAYS stripped via
|
|
273
|
+
* `history.replaceState`, whether or not the trust rule accepts it: a stale or foreign `owlcc` is
|
|
274
|
+
* exactly as much noise as an adopted one, and neither belongs in the visible URL or in what a page
|
|
275
|
+
* reload, a shared link or a browser history entry carries forward.
|
|
276
|
+
*/
|
|
277
|
+
export const consentLinkerScript = (opts) => {
|
|
278
|
+
const linker = opts.linker;
|
|
279
|
+
if (linker == null) {
|
|
280
|
+
return '';
|
|
281
|
+
}
|
|
282
|
+
const param = jsonForScript(linker.param ?? CONSENT_LINK_PARAM);
|
|
283
|
+
const domains = jsonForScript(linker.domains);
|
|
284
|
+
const storageKey = jsonForScript(opts.storageKey ?? CONSENT_KEY);
|
|
285
|
+
const optionalKeys = jsonForScript(optionalOf(opts).map(category => category.key));
|
|
286
|
+
const allCategories = jsonForScript(categoriesOf(opts).map(category => ({ key: category.key, required: category.required === true })));
|
|
287
|
+
const maxAge = linker.maxAge ?? CONSENT_LINK_MAX_AGE;
|
|
288
|
+
const skew = CONSENT_LINK_SKEW;
|
|
289
|
+
const cookieDays = opts.cookieDays ?? CONSENT_COOKIE_DAYS;
|
|
290
|
+
const version = CONSENT_LINK_VERSION;
|
|
291
|
+
// The language rides the same trust decision and is written unconditionally — it is strictly
|
|
292
|
+
// necessary storage, whatever the cookie decision is. Mirrors `supportedLanguage` +
|
|
293
|
+
// `writeConsentLanguage`; empty for a page that does not receive a language.
|
|
294
|
+
const language = linker.language?.supported != null
|
|
295
|
+
? `var L=${jsonForScript(linker.language.supported)};` +
|
|
296
|
+
`if(typeof j.l==='string'){var lc=j.l.toLowerCase(),lm=null,q;` +
|
|
297
|
+
`for(q=0;q<L.length;q++){if(String(L[q]).toLowerCase()===lc){lm=L[q];break}}` +
|
|
298
|
+
`if(!lm){lc=lc.split(/[-_]/)[0];for(q=0;q<L.length;q++){if(String(L[q]).toLowerCase()===lc){lm=L[q];break}}}` +
|
|
299
|
+
`if(lm){try{w.localStorage.setItem(${jsonForScript(linker.language.storageKey ?? CONSENT_LANGUAGE_KEY)},lm)}catch(e){}}}`
|
|
300
|
+
: '';
|
|
301
|
+
return `(function(w,d){` +
|
|
302
|
+
`var p=${param};var qs;try{qs=new URLSearchParams(location.search)}catch(e){return}` +
|
|
303
|
+
`var raw=qs.get(p);if(raw==null)return;` +
|
|
304
|
+
`qs.delete(p);var rest=qs.toString();` +
|
|
305
|
+
`var url=location.pathname+(rest?('?'+rest):'')+location.hash;` +
|
|
306
|
+
`try{history.replaceState(history.state,'',url)}catch(e){}` +
|
|
307
|
+
`try{` +
|
|
308
|
+
`var b=raw.replace(/-/g,'+').replace(/_/g,'/');while(b.length%4)b+='=';` +
|
|
309
|
+
`var j=JSON.parse(atob(b));` +
|
|
310
|
+
`if(!j||j.v!==${version}||typeof j.t!=='number'||!j.c||typeof j.c!=='object')return;` +
|
|
311
|
+
`var now=Math.floor(Date.now()/1000);` +
|
|
312
|
+
`if(now-j.t>${maxAge}||j.t-now>${skew})return;` +
|
|
313
|
+
`var ref=null;try{ref=new URL(d.referrer).hostname}catch(e){}` +
|
|
314
|
+
`if(!ref||${domains}.indexOf(ref)<0)return;` +
|
|
315
|
+
`var existing=null;try{existing=w.localStorage.getItem(${storageKey})}catch(e){}` +
|
|
316
|
+
`if(!existing){var cp=('; '+d.cookie).split('; '+${storageKey}+'=');` +
|
|
317
|
+
`if(cp.length===2)existing=cp.pop().split(';').shift()}` +
|
|
318
|
+
`if(!existing){` +
|
|
319
|
+
`var keys=${optionalKeys},ok=true;` +
|
|
320
|
+
`for(var i=0;i<keys.length;i++){if(j.c[keys[i]]!==0&&j.c[keys[i]]!==1){ok=false;break}}` +
|
|
321
|
+
`if(ok){` +
|
|
322
|
+
`var cats=${allCategories},rec={v:${version}};` +
|
|
323
|
+
`for(var k=0;k<cats.length;k++){var c=cats[k];rec[c.key]=c.required||j.c[c.key]===1}` +
|
|
324
|
+
`var val=JSON.stringify(rec);` +
|
|
325
|
+
`try{w.localStorage.setItem(${storageKey},val)}catch(e){}` +
|
|
326
|
+
`var exp=new Date(Date.now()+${cookieDays}*86400000).toUTCString();` +
|
|
327
|
+
`d.cookie=${storageKey}+'='+val+';expires='+exp+';path=/;SameSite=Lax'` +
|
|
328
|
+
`}` +
|
|
329
|
+
`}` +
|
|
330
|
+
language +
|
|
331
|
+
`}catch(e){}` +
|
|
332
|
+
`})(window,document)`;
|
|
333
|
+
};
|
|
334
|
+
//# sourceMappingURL=linker.js.map
|