@owlmeans/web-consent 0.1.18-rc.30 → 0.1.18-rc.32
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/README.md +1 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-consent/SKILL.md +14 -2
- package/build/consent/component.d.ts.map +1 -1
- package/build/consent/component.js +8 -5
- package/build/consent/component.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/lib/utils.d.ts +13 -0
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +15 -0
- package/build/lib/utils.js.map +1 -1
- package/build/policy/component.d.ts.map +1 -1
- package/build/policy/component.js +3 -2
- package/build/policy/component.js.map +1 -1
- package/build/types.d.ts +5 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/consent/component.tsx +16 -5
- package/src/index.ts +5 -1
- package/src/lib/utils.ts +18 -0
- package/src/policy/component.tsx +10 -1
- package/src/types.ts +5 -1
- package/tests/harness/mount.tsx +17 -0
- package/tests/linker.spec.ts +226 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
6
6
|
your project's skill store (`.agents/skills/`):
|
|
7
7
|
|
|
8
8
|
```sh
|
|
9
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
9
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.39
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/web-consent",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.32",
|
|
5
|
+
"generatedAt": "2026-09-24T14:00:35.046Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/web-consent
|
|
9
9
|
|
|
10
10
|
**Layer:** Web (React)
|
|
11
|
-
**Install:** `"@owlmeans/web-consent": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/web-consent": "^0.1.18-rc.32"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The browser components of the consent set. The model — categories, storage, migration, the store,
|
|
14
14
|
Consent Mode signalling — is `@owlmeans/consent`, and this package re-exports a **named selection**
|
|
@@ -41,7 +41,7 @@ components — see below.
|
|
|
41
41
|
| `useConsent(opts?)` | This document's consent state and the actions over it (`UseConsentModel`) — **it also initialises the store on mount**, see below |
|
|
42
42
|
| `useConsentCategory(key)` | Whether one category is granted, for a component gating a single thing |
|
|
43
43
|
| `CookieConsentProps` / `CookiePolicyProps` / `ConsentLink` | The component props |
|
|
44
|
-
| Re-exports from `@owlmeans/consent` | The complete list: `consentStore`, `openConsent`, `isConsented`, `readConsent`, `writeConsent`, `clearConsent`, `migrateConsent`, `applyConsent`, `pushConsentDefaults`, `consentBootstrapScript`, `consentDefaults`, `consentUpdate`, `gtagConsent`, `DEFAULT_CONSENT_CATEGORIES`, `DEFAULT_CONSENT_MESSAGES`, `defaultConsentTranslate`, `interpolate`, `CONSENT_KEY`, `CONSENT_COOKIE_DAYS`, `CONSENT_SCHEMA_VERSION`, `CONSENT_LOCALES`, `CONSENT_ESSENTIAL` / `CONSENT_ANALYTICS` / `CONSENT_MARKETING`, and the types `ConsentCategory`, `ConsentOptions`, `ConsentReason`, `ConsentRecord`, `ConsentService`, `ConsentSignal`, `ConsentState`, `ConsentStore`, `ConsentLocale` |
|
|
44
|
+
| Re-exports from `@owlmeans/consent` | The complete list: `consentStore`, `openConsent`, `isConsented`, `readConsent`, `writeConsent`, `clearConsent`, `migrateConsent`, `applyConsent`, `pushConsentDefaults`, `consentBootstrapScript`, `consentDefaults`, `consentUpdate`, `gtagConsent`, `DEFAULT_CONSENT_CATEGORIES`, `DEFAULT_CONSENT_MESSAGES`, `defaultConsentTranslate`, `interpolate`, `CONSENT_KEY`, `CONSENT_COOKIE_DAYS`, `CONSENT_SCHEMA_VERSION`, `CONSENT_LOCALES`, `CONSENT_ESSENTIAL` / `CONSENT_ANALYTICS` / `CONSENT_MARKETING`, the plugin seam (`registerConsentPlugin`, `consentPlugins`, `decorateConsentUrl`, `consentDomains`, `adoptConsent`, `startConsentPlugins`), the linker (`consentLinker`, `encodeConsentLink`, `decodeConsentLink`, `stripConsentLinkParam`, `consentLinkerScript`, `CONSENT_LINK_PARAM`, `CONSENT_LINK_MAX_AGE`, `CONSENT_LINK_SKEW`), and the types `ConsentCategory`, `ConsentOptions`, `ConsentReason`, `ConsentRecord`, `ConsentService`, `ConsentSignal`, `ConsentState`, `ConsentStore`, `ConsentLocale`, `ConsentLinkerOptions`, `ConsentPlugin`, `ConsentLinkPayload` |
|
|
45
45
|
|
|
46
46
|
## Mounting the dialog
|
|
47
47
|
|
|
@@ -74,6 +74,13 @@ import { CookieConsent } from '@owlmeans/web-consent'
|
|
|
74
74
|
for as long as a visitor stays and must read as a small fixture rather than compete with the
|
|
75
75
|
page's own controls. The pictogram is 20px (`h-5 w-5`) inside an invisible 44px hit area
|
|
76
76
|
(`h-11 w-11`). `[data-consent-reopen]` is what any test or CSS override keys on.
|
|
77
|
+
- **`linker` (`ConsentLinkerOptions`)** turns on cross-domain consent (see the `consent` skill's
|
|
78
|
+
plugin-seam section): `<CookieConsent linker={{ domains: ['owlmeans.com', 'owlmeans.pl'] }} />`
|
|
79
|
+
passes it straight to `useConsent`, which registers `consentLinker` on mount. With it set and
|
|
80
|
+
more than just the current host to disclose, a domain line renders right after the description
|
|
81
|
+
(`[data-consent-domains]`, added to the dialog's `aria-describedby`) — computed DIRECTLY from
|
|
82
|
+
`linker.domains` plus the current host, not through the plugin registry, so it never depends on
|
|
83
|
+
registration having finished yet. Omit `linker` and nothing here changes at all.
|
|
77
84
|
|
|
78
85
|
## The look — flat, from the host's tokens
|
|
79
86
|
|
|
@@ -203,6 +210,11 @@ Never add headings for categories or services — they would skip a level under
|
|
|
203
210
|
CSS hooks: `[data-cookie-policy-category="<key>"]`, `[data-cookie-policy-services]`,
|
|
204
211
|
`[data-cookie-policy-service]`, `[data-cookie-policy-other]`.
|
|
205
212
|
|
|
213
|
+
**`linker`**, same shape as `CookieConsent`'s, adds a plain `[data-cookie-policy-domains]` LIST
|
|
214
|
+
(never a heading) right after the storage-key paragraph — the current host plus every domain
|
|
215
|
+
`linker.domains` names, computed the same direct way the dialog computes it. Omitted or with
|
|
216
|
+
nothing beyond the current host to disclose, nothing here renders.
|
|
217
|
+
|
|
206
218
|
The service labels (`policyProvider`, `policyPurpose`, `policyCookies`, `policyServicesOf`,
|
|
207
219
|
`policyServicePrivacy`, `policyOtherServices`, `policyOtherServicesDesc`) go through `translate`
|
|
208
220
|
with English defaults and are in the packaged bundle for every locale; the service's own `name`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAS/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAkCrD,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAS/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAkCrD,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA0IhD,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { Cookie } from 'lucide-react';
|
|
4
|
-
import { DEFAULT_CONSENT_CATEGORIES, defaultConsentTranslate, readConsent, } from '@owlmeans/consent';
|
|
5
|
-
import { cn } from '../lib/utils.js';
|
|
4
|
+
import { DEFAULT_CONSENT_CATEGORIES, defaultConsentTranslate, interpolate, readConsent, } from '@owlmeans/consent';
|
|
5
|
+
import { cn, disclosedDomains } from '../lib/utils.js';
|
|
6
6
|
import { useConsent } from '../hooks.js';
|
|
7
7
|
import { ConsentToggle } from './toggle.js';
|
|
8
8
|
/**
|
|
@@ -31,13 +31,16 @@ const LINK = cn('inline-flex min-h-11 items-center font-semibold underline decor
|
|
|
31
31
|
export const CookieConsent = props => {
|
|
32
32
|
const categories = props.categories ?? DEFAULT_CONSENT_CATEGORIES;
|
|
33
33
|
const t = props.translate ?? defaultConsentTranslate(props.locale);
|
|
34
|
-
const
|
|
34
|
+
const consentOpts = {
|
|
35
35
|
categories,
|
|
36
36
|
...(props.storageKey != null ? { storageKey: props.storageKey } : {}),
|
|
37
37
|
...(props.cookieDays != null ? { cookieDays: props.cookieDays } : {}),
|
|
38
38
|
...(props.cookieDomain != null ? { cookieDomain: props.cookieDomain } : {}),
|
|
39
39
|
...(props.silent != null ? { silent: props.silent } : {}),
|
|
40
|
-
|
|
40
|
+
...(props.linker != null ? { linker: props.linker } : {}),
|
|
41
|
+
};
|
|
42
|
+
const consent = useConsent(consentOpts);
|
|
43
|
+
const domains = disclosedDomains(props.linker);
|
|
41
44
|
const optional = categories.filter(category => category.required !== true);
|
|
42
45
|
const [draft, setDraft] = useState({});
|
|
43
46
|
// Re-seed the draft whenever the dialog opens, from what is actually stored: a visitor who opens
|
|
@@ -64,7 +67,7 @@ export const CookieConsent = props => {
|
|
|
64
67
|
// today. Saying so, and relabelling the primary action, is what makes the interruption make
|
|
65
68
|
// sense rather than look like the page asking twice.
|
|
66
69
|
const gated = consent.reason === 'login';
|
|
67
|
-
return _jsxs(_Fragment, { children: [consent.open && _jsx("div", { className: "fixed inset-0 z-[999998] flex items-center justify-center overflow-y-auto bg-black/70 px-4 py-6", "aria-modal": "true", role: "dialog", "aria-labelledby": "cc-title", "aria-describedby":
|
|
70
|
+
return _jsxs(_Fragment, { children: [consent.open && _jsx("div", { className: "fixed inset-0 z-[999998] flex items-center justify-center overflow-y-auto bg-black/70 px-4 py-6", "aria-modal": "true", role: "dialog", "aria-labelledby": "cc-title", "aria-describedby": domains.length > 1 ? 'cc-desc cc-domains' : 'cc-desc', "data-consent-dialog": true, children: _jsxs("div", { className: cn('relative z-[999999] w-full max-w-lg rounded-3xl border border-border bg-background p-6 text-foreground sm:p-8', props.className), children: [_jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-muted text-primary", children: _jsx(Cookie, { className: "h-5 w-5", "aria-hidden": "true" }) }), _jsx("h2", { id: "cc-title", className: "text-balance text-xl font-extrabold tracking-tight text-foreground sm:text-2xl", children: t('title', 'Cookie Preferences') })] }), _jsx("p", { id: "cc-desc", className: "mb-6 text-pretty text-sm leading-relaxed text-muted-foreground", children: t('description', 'We use cookies to enhance your browsing experience, serve personalized ads or content, and analyze our traffic.') }), domains.length > 1 && _jsx("p", { id: "cc-domains", "data-consent-domains": true, className: "mb-6 -mt-3 text-pretty text-xs text-muted-foreground", children: interpolate(t('domains', 'This choice applies to {{domains}}.'), { domains: domains.join(', ') }) }), _jsx("div", { className: "space-y-3", children: categories.map(category => _jsx(ConsentToggle, { id: `cc-${category.key}`, label: t(category.labelKey, category.key), description: t(category.descriptionKey, ''), checked: draft[category.key] === true, ...(category.required === true ? { required: true } : {}), requiredLabel: t('required', 'Required'), onChange: value => setDraft(current => ({ ...current, [category.key]: value })) }, category.key)) }), gated && _jsx("p", { role: "status", "data-consent-reason": true, className: "mt-4 text-sm font-semibold text-foreground", children: t('loginReason', 'Signing in stores a session cookie. Accept essential cookies to continue.') }), _jsxs("div", { className: "mt-8 flex flex-col gap-3 sm:flex-row sm:gap-4", children: [_jsx("button", { type: "button", onClick: onSave, "data-consent-save": true, className: cn(PILL, 'border-[1.5px] border-foreground bg-transparent font-semibold text-foreground hover:bg-muted'), children: t('savePreferences', 'Save Preferences') }), _jsx("button", { type: "button", onClick: onAcceptAll, "data-consent-accept-all": true, className: cn(PILL, 'bg-primary font-bold text-primary-foreground hover:bg-primary/90'), children: gated
|
|
68
71
|
? t('acceptAndContinue', 'Accept & continue')
|
|
69
72
|
: t('acceptAll', 'Accept All') })] }), (props.policyHref != null || props.links != null) && _jsxs("div", { className: "mt-3 flex flex-wrap justify-center gap-x-4 text-[13px] text-muted-foreground", children: [props.policyHref != null && _jsx("a", { href: props.policyHref, target: "_blank", rel: "noopener noreferrer", className: LINK, children: t('policyLink', 'Cookie Policy') }), props.links?.map(link => _jsx("a", { href: link.href, target: "_blank", rel: "noopener noreferrer", className: LINK, children: t(link.labelKey, link.defaultLabel) }, link.href))] })] }) }), props.noReopenButton !== true && consent.record != null && !consent.open && _jsx("button", { type: "button", onClick: () => consent.openDialog('reopen'), "aria-label": t('openPreferences', 'Cookie preferences'), "data-consent-reopen": true, className: cn('fixed bottom-1 left-1 z-[999997] inline-flex h-11 w-11 items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:text-primary focus-visible:opacity-100', FOCUS), children: _jsx(Cookie, { className: "h-5 w-5", "aria-hidden": "true" }) })] });
|
|
70
73
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EACL,0BAA0B,EAAE,uBAAuB,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ export * from './consent/component.js';
|
|
|
4
4
|
export * from './consent/widget.js';
|
|
5
5
|
export * from './consent/toggle.js';
|
|
6
6
|
export * from './policy/component.js';
|
|
7
|
-
export { consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, migrateConsent, applyConsent, pushConsentDefaults, consentBootstrapScript, consentDefaults, consentUpdate, gtagConsent, DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, } from '@owlmeans/consent';
|
|
8
|
-
export type { ConsentCategory, ConsentOptions, ConsentReason, ConsentRecord, ConsentService, ConsentSignal, ConsentState, ConsentStore, ConsentLocale, } from '@owlmeans/consent';
|
|
7
|
+
export { consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, migrateConsent, applyConsent, pushConsentDefaults, consentBootstrapScript, consentDefaults, consentUpdate, gtagConsent, DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, registerConsentPlugin, consentPlugins, decorateConsentUrl, consentDomains, adoptConsent, startConsentPlugins, consentLinker, encodeConsentLink, decodeConsentLink, stripConsentLinkParam, consentLinkerScript, CONSENT_LINK_PARAM, CONSENT_LINK_MAX_AGE, CONSENT_LINK_SKEW, } from '@owlmeans/consent';
|
|
8
|
+
export type { ConsentCategory, ConsentOptions, ConsentReason, ConsentRecord, ConsentService, ConsentSignal, ConsentState, ConsentStore, ConsentLocale, ConsentLinkerOptions, ConsentPlugin, ConsentLinkPayload, } from '@owlmeans/consent';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACvD,qBAAqB,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EACvF,mBAAmB,EACnB,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAC/F,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,GAC5D,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAC5F,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,kBAAkB,GACnG,MAAM,mBAAmB,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -3,5 +3,5 @@ export * from './consent/component.js';
|
|
|
3
3
|
export * from './consent/widget.js';
|
|
4
4
|
export * from './consent/toggle.js';
|
|
5
5
|
export * from './policy/component.js';
|
|
6
|
-
export { consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, migrateConsent, applyConsent, pushConsentDefaults, consentBootstrapScript, consentDefaults, consentUpdate, gtagConsent, DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, } from '@owlmeans/consent';
|
|
6
|
+
export { consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, migrateConsent, applyConsent, pushConsentDefaults, consentBootstrapScript, consentDefaults, consentUpdate, gtagConsent, DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, registerConsentPlugin, consentPlugins, decorateConsentUrl, consentDomains, adoptConsent, startConsentPlugins, consentLinker, encodeConsentLink, decodeConsentLink, stripConsentLinkParam, consentLinkerScript, CONSENT_LINK_PARAM, CONSENT_LINK_MAX_AGE, CONSENT_LINK_SKEW, } from '@owlmeans/consent';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACvD,qBAAqB,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EACvF,mBAAmB,EACnB,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAC/F,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,GAC5D,MAAM,mBAAmB,CAAA"}
|
package/build/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ClassValue } from 'clsx';
|
|
2
|
+
import type { ConsentLinkerOptions } from '@owlmeans/consent';
|
|
2
3
|
/**
|
|
3
4
|
* Class-name merge, owned by this package rather than reached through the `@` alias contract.
|
|
4
5
|
*
|
|
@@ -9,4 +10,16 @@ import { type ClassValue } from 'clsx';
|
|
|
9
10
|
* such alias, so the dialog would simply fail to build there.
|
|
10
11
|
*/
|
|
11
12
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* The domains one document's choice is disclosed as applying to: this host plus every domain
|
|
15
|
+
* `linker` names, deduplicated, current host first.
|
|
16
|
+
*
|
|
17
|
+
* Computed directly from `linker.domains` rather than through `@owlmeans/consent`'s
|
|
18
|
+
* `consentDomains()` registry helper: `CookieConsent` and `CookiePolicy` already have the
|
|
19
|
+
* configuration in hand as a prop, and a component that reads it straight has no reason to depend
|
|
20
|
+
* on whether `consentLinker` has finished registering itself yet (`consentStore.init` runs that
|
|
21
|
+
* registration from an effect, a render tick after mount). `consentDomains()` stays the right call
|
|
22
|
+
* for a caller that does NOT have `linker` in hand.
|
|
23
|
+
*/
|
|
24
|
+
export declare const disclosedDomains: (linker: ConsentLinkerOptions | undefined) => string[];
|
|
12
25
|
//# sourceMappingURL=utils.d.ts.map
|
package/build/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAE5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE7D;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,WAAY,oBAAoB,GAAG,SAAS,KAAG,MAAM,EAIjF,CAAA"}
|
package/build/lib/utils.js
CHANGED
|
@@ -12,4 +12,19 @@ import { twMerge } from 'tailwind-merge';
|
|
|
12
12
|
export function cn(...inputs) {
|
|
13
13
|
return twMerge(clsx(inputs));
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* The domains one document's choice is disclosed as applying to: this host plus every domain
|
|
17
|
+
* `linker` names, deduplicated, current host first.
|
|
18
|
+
*
|
|
19
|
+
* Computed directly from `linker.domains` rather than through `@owlmeans/consent`'s
|
|
20
|
+
* `consentDomains()` registry helper: `CookieConsent` and `CookiePolicy` already have the
|
|
21
|
+
* configuration in hand as a prop, and a component that reads it straight has no reason to depend
|
|
22
|
+
* on whether `consentLinker` has finished registering itself yet (`consentStore.init` runs that
|
|
23
|
+
* registration from an effect, a render tick after mount). `consentDomains()` stays the right call
|
|
24
|
+
* for a caller that does NOT have `linker` in hand.
|
|
25
|
+
*/
|
|
26
|
+
export const disclosedDomains = (linker) => {
|
|
27
|
+
const host = typeof location !== 'undefined' ? location.hostname : '';
|
|
28
|
+
return [...new Set([host, ...(linker?.domains ?? [])].filter(domain => domain !== ''))];
|
|
29
|
+
};
|
|
15
30
|
//# sourceMappingURL=utils.js.map
|
package/build/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAGxC;;;;;;;;GAQG;AACH,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAwC,EAAY,EAAE;IACrF,MAAM,IAAI,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IAErE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;AACzF,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/policy/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAO/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAkDpD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/policy/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAO/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAkDpD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CA+E9C,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { CONSENT_COOKIE_DAYS, CONSENT_KEY, DEFAULT_CONSENT_CATEGORIES, defaultConsentTranslate, interpolate, openConsent, } from '@owlmeans/consent';
|
|
3
|
-
import { cn } from '../lib/utils.js';
|
|
3
|
+
import { cn, disclosedDomains } from '../lib/utils.js';
|
|
4
4
|
/**
|
|
5
5
|
* The services one category gates, as a nested list inside that category's item.
|
|
6
6
|
*
|
|
@@ -36,10 +36,11 @@ export const CookiePolicy = props => {
|
|
|
36
36
|
const keys = new Set(categories.map(category => category.key));
|
|
37
37
|
const other = services.filter(service => !keys.has(service.category));
|
|
38
38
|
const otherLabel = t('policyOtherServices', 'Other services');
|
|
39
|
+
const domains = disclosedDomains(props.linker);
|
|
39
40
|
return _jsxs("article", { className: cn('prose prose-sm max-w-2xl', props.className), "data-cookie-policy": true, children: [_jsx("h1", { children: t('policyTitle', 'Cookie Policy') }), _jsx("p", { children: t('policyIntro', 'This page describes the cookies and similar storage this application uses, and how you can control them.') }), _jsxs("ul", { children: [categories.map(category => {
|
|
40
41
|
const label = t(category.labelKey, category.key);
|
|
41
42
|
const gated = services.filter(service => service.category === category.key);
|
|
42
43
|
return _jsxs("li", { "data-cookie-policy-category": category.key, children: [_jsx("strong", { children: label }), category.required === true && _jsxs(_Fragment, { children: [" \u2014 ", t('required', 'Required')] }), _jsx("br", {}), t(category.descriptionKey, ''), gated.length > 0 && _jsx(ServiceList, { services: gated, label: label, category: category.key, t: t })] }, category.key);
|
|
43
|
-
}), other.length > 0 && _jsxs("li", { "data-cookie-policy-other": true, children: [_jsx("strong", { children: otherLabel }), _jsx("br", {}), t('policyOtherServicesDesc', 'Services this application runs outside the categories above.'), _jsx(ServiceList, { services: other, label: otherLabel, t: t })] }, " other")] }), _jsx("p", { children: interpolate(t('policyStorage', 'Your choice is stored in this browser under "{{key}}", both in local storage and as a cookie.'), { key: storageKey }) }), _jsx("p", { children: interpolate(t('policyRetention', 'The record is kept for {{days}} days, after which you will be asked again.'), { days }) }), props.operator != null && props.operator !== '' && _jsxs("p", { children: [interpolate(t('policyOperator', 'This application is operated by {{operator}}.'), { operator: props.operator }), ' ', t('policyContact', 'See their privacy policy and terms for how they handle your data.')] }), _jsxs("p", { children: [props.privacyHref != null && props.privacyHref !== '' && _jsx("a", { href: props.privacyHref, target: "_blank", rel: "noopener noreferrer", children: t('privacy', 'Privacy Policy') }), props.privacyHref != null && props.termsHref != null && ' · ', props.termsHref != null && props.termsHref !== '' && _jsx("a", { href: props.termsHref, target: "_blank", rel: "noopener noreferrer", children: t('terms', 'Terms & Conditions') })] }), _jsx("p", { children: _jsx("button", { type: "button", onClick: () => openConsent('reopen'), "data-cookie-policy-manage": true, className: "not-prose inline-flex min-h-11 items-center justify-center rounded-full border-[1.5px] border-foreground bg-transparent px-6 py-2.5 text-[15px] font-semibold text-foreground transition-colors hover:bg-muted focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-ring", children: t('manage', 'Manage preferences') }) })] });
|
|
44
|
+
}), other.length > 0 && _jsxs("li", { "data-cookie-policy-other": true, children: [_jsx("strong", { children: otherLabel }), _jsx("br", {}), t('policyOtherServicesDesc', 'Services this application runs outside the categories above.'), _jsx(ServiceList, { services: other, label: otherLabel, t: t })] }, " other")] }), _jsx("p", { children: interpolate(t('policyStorage', 'Your choice is stored in this browser under "{{key}}", both in local storage and as a cookie.'), { key: storageKey }) }), domains.length > 1 && _jsxs(_Fragment, { children: [_jsx("p", { children: t('policyDomains', 'This choice applies across these domains:') }), _jsx("ul", { "data-cookie-policy-domains": true, children: domains.map(domain => _jsx("li", { children: domain }, domain)) })] }), _jsx("p", { children: interpolate(t('policyRetention', 'The record is kept for {{days}} days, after which you will be asked again.'), { days }) }), props.operator != null && props.operator !== '' && _jsxs("p", { children: [interpolate(t('policyOperator', 'This application is operated by {{operator}}.'), { operator: props.operator }), ' ', t('policyContact', 'See their privacy policy and terms for how they handle your data.')] }), _jsxs("p", { children: [props.privacyHref != null && props.privacyHref !== '' && _jsx("a", { href: props.privacyHref, target: "_blank", rel: "noopener noreferrer", children: t('privacy', 'Privacy Policy') }), props.privacyHref != null && props.termsHref != null && ' · ', props.termsHref != null && props.termsHref !== '' && _jsx("a", { href: props.termsHref, target: "_blank", rel: "noopener noreferrer", children: t('terms', 'Terms & Conditions') })] }), _jsx("p", { children: _jsx("button", { type: "button", onClick: () => openConsent('reopen'), "data-cookie-policy-manage": true, className: "not-prose inline-flex min-h-11 items-center justify-center rounded-full border-[1.5px] border-foreground bg-transparent px-6 py-2.5 text-[15px] font-semibold text-foreground transition-colors hover:bg-muted focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-ring", children: t('manage', 'Manage preferences') }) })] });
|
|
44
45
|
};
|
|
45
46
|
//# sourceMappingURL=component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/policy/component.tsx"],"names":[],"mappings":";AACA,OAAO,EACL,mBAAmB,EAAE,WAAW,EAAE,0BAA0B,EAC5D,uBAAuB,EAAE,WAAW,EAAE,WAAW,GAClD,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/policy/component.tsx"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConsentCategory, ConsentReason, ConsentRecord, ConsentService } from '@owlmeans/consent';
|
|
1
|
+
import type { ConsentCategory, ConsentLinkerOptions, ConsentReason, ConsentRecord, ConsentService } from '@owlmeans/consent';
|
|
2
2
|
export interface ConsentLink {
|
|
3
3
|
href: string;
|
|
4
4
|
labelKey: string;
|
|
@@ -23,6 +23,8 @@ export interface CookieConsentProps {
|
|
|
23
23
|
/** Hide the persistent re-open button, for an app that offers a footer link instead. */
|
|
24
24
|
noReopenButton?: boolean;
|
|
25
25
|
className?: string;
|
|
26
|
+
/** Cross-domain consent — see `ConsentLinkerOptions`. Passed through to `consentStore.init`. */
|
|
27
|
+
linker?: ConsentLinkerOptions;
|
|
26
28
|
}
|
|
27
29
|
export interface CookiePolicyProps {
|
|
28
30
|
locale?: string;
|
|
@@ -42,6 +44,8 @@ export interface CookiePolicyProps {
|
|
|
42
44
|
*/
|
|
43
45
|
services?: ConsentService[];
|
|
44
46
|
className?: string;
|
|
47
|
+
/** Cross-domain consent — see `ConsentLinkerOptions`. Names the domains the policy discloses. */
|
|
48
|
+
linker?: ConsentLinkerOptions;
|
|
45
49
|
}
|
|
46
50
|
export interface ConsentMenuWidgetProps {
|
|
47
51
|
locale?: string;
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EACpF,MAAM,mBAAmB,CAAA;AAE1B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,uFAAuF;IACvF,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACzD;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wFAAwF;IACxF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,MAAM,CAAC,EAAE,oBAAoB,CAAA;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACzD,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iGAAiG;IACjG,MAAM,CAAC,EAAE,oBAAoB,CAAA;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uFAAuF;IACvF,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IACjC,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-consent",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.32",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@owlmeans/consent": "^0.1.18-rc.
|
|
25
|
+
"@owlmeans/consent": "^0.1.18-rc.7"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"clsx": "*",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@owlmeans/dep-config": "workspace:*",
|
|
36
|
-
"@owlmeans/test-ui": "^0.1.18-rc.
|
|
36
|
+
"@owlmeans/test-ui": "^0.1.18-rc.39",
|
|
37
37
|
"@tailwindcss/vite": "^4.0.0",
|
|
38
38
|
"@types/bun": "^1.4.0",
|
|
39
39
|
"@types/react": "^19.2.17",
|
|
@@ -2,10 +2,10 @@ import { useCallback, useEffect, useState } from 'react'
|
|
|
2
2
|
import type { FC } from 'react'
|
|
3
3
|
import { Cookie } from 'lucide-react'
|
|
4
4
|
import {
|
|
5
|
-
DEFAULT_CONSENT_CATEGORIES, defaultConsentTranslate, readConsent,
|
|
5
|
+
DEFAULT_CONSENT_CATEGORIES, defaultConsentTranslate, interpolate, readConsent,
|
|
6
6
|
} from '@owlmeans/consent'
|
|
7
7
|
import type { ConsentRecord } from '@owlmeans/consent'
|
|
8
|
-
import { cn } from '../lib/utils.js'
|
|
8
|
+
import { cn, disclosedDomains } from '../lib/utils.js'
|
|
9
9
|
import { useConsent } from '../hooks.js'
|
|
10
10
|
import { ConsentToggle } from './toggle.js'
|
|
11
11
|
import type { CookieConsentProps } from '../types.js'
|
|
@@ -46,13 +46,16 @@ export const CookieConsent: FC<CookieConsentProps> = props => {
|
|
|
46
46
|
const categories = props.categories ?? DEFAULT_CONSENT_CATEGORIES
|
|
47
47
|
const t = props.translate ?? defaultConsentTranslate(props.locale)
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const consentOpts = {
|
|
50
50
|
categories,
|
|
51
51
|
...(props.storageKey != null ? { storageKey: props.storageKey } : {}),
|
|
52
52
|
...(props.cookieDays != null ? { cookieDays: props.cookieDays } : {}),
|
|
53
53
|
...(props.cookieDomain != null ? { cookieDomain: props.cookieDomain } : {}),
|
|
54
54
|
...(props.silent != null ? { silent: props.silent } : {}),
|
|
55
|
-
|
|
55
|
+
...(props.linker != null ? { linker: props.linker } : {}),
|
|
56
|
+
}
|
|
57
|
+
const consent = useConsent(consentOpts)
|
|
58
|
+
const domains = disclosedDomains(props.linker)
|
|
56
59
|
|
|
57
60
|
const optional = categories.filter(category => category.required !== true)
|
|
58
61
|
const [draft, setDraft] = useState<Record<string, boolean>>({})
|
|
@@ -91,7 +94,8 @@ export const CookieConsent: FC<CookieConsentProps> = props => {
|
|
|
91
94
|
return <>
|
|
92
95
|
{consent.open && <div
|
|
93
96
|
className="fixed inset-0 z-[999998] flex items-center justify-center overflow-y-auto bg-black/70 px-4 py-6"
|
|
94
|
-
aria-modal="true" role="dialog" aria-labelledby="cc-title"
|
|
97
|
+
aria-modal="true" role="dialog" aria-labelledby="cc-title"
|
|
98
|
+
aria-describedby={domains.length > 1 ? 'cc-desc cc-domains' : 'cc-desc'}
|
|
95
99
|
data-consent-dialog
|
|
96
100
|
>
|
|
97
101
|
<div className={cn(
|
|
@@ -111,6 +115,13 @@ export const CookieConsent: FC<CookieConsentProps> = props => {
|
|
|
111
115
|
{t('description', 'We use cookies to enhance your browsing experience, serve personalized ads or content, and analyze our traffic.')}
|
|
112
116
|
</p>
|
|
113
117
|
|
|
118
|
+
{domains.length > 1 && <p
|
|
119
|
+
id="cc-domains" data-consent-domains
|
|
120
|
+
className="mb-6 -mt-3 text-pretty text-xs text-muted-foreground"
|
|
121
|
+
>
|
|
122
|
+
{interpolate(t('domains', 'This choice applies to {{domains}}.'), { domains: domains.join(', ') })}
|
|
123
|
+
</p>}
|
|
124
|
+
|
|
114
125
|
<div className="space-y-3">
|
|
115
126
|
{categories.map(category => <ConsentToggle
|
|
116
127
|
key={category.key}
|
package/src/index.ts
CHANGED
|
@@ -12,8 +12,12 @@ export {
|
|
|
12
12
|
DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate,
|
|
13
13
|
CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES,
|
|
14
14
|
CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING,
|
|
15
|
+
registerConsentPlugin, consentPlugins, decorateConsentUrl, consentDomains, adoptConsent,
|
|
16
|
+
startConsentPlugins,
|
|
17
|
+
consentLinker, encodeConsentLink, decodeConsentLink, stripConsentLinkParam, consentLinkerScript,
|
|
18
|
+
CONSENT_LINK_PARAM, CONSENT_LINK_MAX_AGE, CONSENT_LINK_SKEW,
|
|
15
19
|
} from '@owlmeans/consent'
|
|
16
20
|
export type {
|
|
17
21
|
ConsentCategory, ConsentOptions, ConsentReason, ConsentRecord, ConsentService, ConsentSignal,
|
|
18
|
-
ConsentState, ConsentStore, ConsentLocale,
|
|
22
|
+
ConsentState, ConsentStore, ConsentLocale, ConsentLinkerOptions, ConsentPlugin, ConsentLinkPayload,
|
|
19
23
|
} from '@owlmeans/consent'
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from 'clsx'
|
|
2
2
|
import { twMerge } from 'tailwind-merge'
|
|
3
|
+
import type { ConsentLinkerOptions } from '@owlmeans/consent'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Class-name merge, owned by this package rather than reached through the `@` alias contract.
|
|
@@ -13,3 +14,20 @@ import { twMerge } from 'tailwind-merge'
|
|
|
13
14
|
export function cn(...inputs: ClassValue[]) {
|
|
14
15
|
return twMerge(clsx(inputs))
|
|
15
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The domains one document's choice is disclosed as applying to: this host plus every domain
|
|
20
|
+
* `linker` names, deduplicated, current host first.
|
|
21
|
+
*
|
|
22
|
+
* Computed directly from `linker.domains` rather than through `@owlmeans/consent`'s
|
|
23
|
+
* `consentDomains()` registry helper: `CookieConsent` and `CookiePolicy` already have the
|
|
24
|
+
* configuration in hand as a prop, and a component that reads it straight has no reason to depend
|
|
25
|
+
* on whether `consentLinker` has finished registering itself yet (`consentStore.init` runs that
|
|
26
|
+
* registration from an effect, a render tick after mount). `consentDomains()` stays the right call
|
|
27
|
+
* for a caller that does NOT have `linker` in hand.
|
|
28
|
+
*/
|
|
29
|
+
export const disclosedDomains = (linker: ConsentLinkerOptions | undefined): string[] => {
|
|
30
|
+
const host = typeof location !== 'undefined' ? location.hostname : ''
|
|
31
|
+
|
|
32
|
+
return [...new Set([host, ...(linker?.domains ?? [])].filter(domain => domain !== ''))]
|
|
33
|
+
}
|
package/src/policy/component.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
defaultConsentTranslate, interpolate, openConsent,
|
|
5
5
|
} from '@owlmeans/consent'
|
|
6
6
|
import type { ConsentService } from '@owlmeans/consent'
|
|
7
|
-
import { cn } from '../lib/utils.js'
|
|
7
|
+
import { cn, disclosedDomains } from '../lib/utils.js'
|
|
8
8
|
import type { CookiePolicyProps } from '../types.js'
|
|
9
9
|
|
|
10
10
|
type Translate = (key: string, defaultValue: string) => string
|
|
@@ -82,6 +82,7 @@ export const CookiePolicy: FC<CookiePolicyProps> = props => {
|
|
|
82
82
|
const keys = new Set(categories.map(category => category.key))
|
|
83
83
|
const other = services.filter(service => !keys.has(service.category))
|
|
84
84
|
const otherLabel = t('policyOtherServices', 'Other services')
|
|
85
|
+
const domains = disclosedDomains(props.linker)
|
|
85
86
|
|
|
86
87
|
return <article className={cn('prose prose-sm max-w-2xl', props.className)} data-cookie-policy>
|
|
87
88
|
<h1>{t('policyTitle', 'Cookie Policy')}</h1>
|
|
@@ -112,6 +113,14 @@ export const CookiePolicy: FC<CookiePolicyProps> = props => {
|
|
|
112
113
|
t('policyStorage', 'Your choice is stored in this browser under "{{key}}", both in local storage and as a cookie.'),
|
|
113
114
|
{ key: storageKey }
|
|
114
115
|
)}</p>
|
|
116
|
+
|
|
117
|
+
{domains.length > 1 && <>
|
|
118
|
+
<p>{t('policyDomains', 'This choice applies across these domains:')}</p>
|
|
119
|
+
<ul data-cookie-policy-domains>
|
|
120
|
+
{domains.map(domain => <li key={domain}>{domain}</li>)}
|
|
121
|
+
</ul>
|
|
122
|
+
</>}
|
|
123
|
+
|
|
115
124
|
<p>{interpolate(
|
|
116
125
|
t('policyRetention', 'The record is kept for {{days}} days, after which you will be asked again.'),
|
|
117
126
|
{ days }
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ConsentCategory, ConsentReason, ConsentRecord, ConsentService,
|
|
2
|
+
ConsentCategory, ConsentLinkerOptions, ConsentReason, ConsentRecord, ConsentService,
|
|
3
3
|
} from '@owlmeans/consent'
|
|
4
4
|
|
|
5
5
|
export interface ConsentLink {
|
|
@@ -27,6 +27,8 @@ export interface CookieConsentProps {
|
|
|
27
27
|
/** Hide the persistent re-open button, for an app that offers a footer link instead. */
|
|
28
28
|
noReopenButton?: boolean
|
|
29
29
|
className?: string
|
|
30
|
+
/** Cross-domain consent — see `ConsentLinkerOptions`. Passed through to `consentStore.init`. */
|
|
31
|
+
linker?: ConsentLinkerOptions
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export interface CookiePolicyProps {
|
|
@@ -47,6 +49,8 @@ export interface CookiePolicyProps {
|
|
|
47
49
|
*/
|
|
48
50
|
services?: ConsentService[]
|
|
49
51
|
className?: string
|
|
52
|
+
/** Cross-domain consent — see `ConsentLinkerOptions`. Names the domains the policy discloses. */
|
|
53
|
+
linker?: ConsentLinkerOptions
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
export interface ConsentMenuWidgetProps {
|
package/tests/harness/mount.tsx
CHANGED
|
@@ -14,9 +14,16 @@ import type { ConsentCategory, ConsentService } from '../../src/index.js'
|
|
|
14
14
|
* - `?services=1` — with `view=policy`, disclose {@link SERVICES} on it.
|
|
15
15
|
* - `?styled=1` — compile a real theme (`styled.css`), for the accessibility spec only.
|
|
16
16
|
* - `?theme=dark` — with `styled=1`, the dark scheme.
|
|
17
|
+
* - `?linker=1` — cross-domain consent, `linker={{ domains: LINKER_DOMAINS }}` on both the dialog
|
|
18
|
+
* and the policy page.
|
|
19
|
+
* - `?anchors=1` — renders a handful of `<a>` elements (a listed partner, a foreign host, and one
|
|
20
|
+
* with `rel="noreferrer"`) for the decoration specs to click/inspect.
|
|
17
21
|
*/
|
|
18
22
|
const params = new URLSearchParams(window.location.search)
|
|
19
23
|
|
|
24
|
+
/** The one partner domain every `?linker=1` case shares — never the harness's own origin. */
|
|
25
|
+
export const LINKER_DOMAINS = ['partner.test']
|
|
26
|
+
|
|
20
27
|
if (params.get('styled') != null) {
|
|
21
28
|
await import('./styled.css')
|
|
22
29
|
if (params.get('theme') === 'dark') {
|
|
@@ -65,6 +72,9 @@ const translate = params.get('categories') === 'custom'
|
|
|
65
72
|
: key === 'consent.telemetry.description' ? 'Product telemetry.' : defaultValue
|
|
66
73
|
: undefined
|
|
67
74
|
|
|
75
|
+
const withLinker = params.get('linker') != null
|
|
76
|
+
const withAnchors = params.get('anchors') != null
|
|
77
|
+
|
|
68
78
|
const App: FC = () => {
|
|
69
79
|
// Re-mounting on demand proves a decision SURVIVES a mount rather than merely a render — the
|
|
70
80
|
// migration case is meaningless otherwise.
|
|
@@ -73,6 +83,11 @@ const App: FC = () => {
|
|
|
73
83
|
return <div>
|
|
74
84
|
<button id="remount" onClick={() => setGeneration(g => g + 1)}>remount</button>
|
|
75
85
|
<span id="generation">{generation}</span>
|
|
86
|
+
{withAnchors && <nav>
|
|
87
|
+
<a id="partner-link" href="https://partner.test/legal/cookies">A listed partner</a>
|
|
88
|
+
<a id="foreign-link" href="https://elsewhere.test/">An unlisted host</a>
|
|
89
|
+
<a id="noreferrer-link" href="https://partner.test/legal/terms" rel="noreferrer">A listed partner, noreferrer</a>
|
|
90
|
+
</nav>}
|
|
76
91
|
{params.get('view') === 'policy' && <CookiePolicy
|
|
77
92
|
key={`policy-${generation}`}
|
|
78
93
|
locale={params.get('locale') ?? undefined}
|
|
@@ -81,6 +96,7 @@ const App: FC = () => {
|
|
|
81
96
|
privacyHref="https://example.test/privacy"
|
|
82
97
|
termsHref="https://example.test/terms"
|
|
83
98
|
services={params.get('services') != null ? SERVICES : undefined}
|
|
99
|
+
{...(withLinker ? { linker: { domains: LINKER_DOMAINS } } : {})}
|
|
84
100
|
/>}
|
|
85
101
|
{/*
|
|
86
102
|
Mounted in EVERY view, including alongside the policy page — that is how an application
|
|
@@ -94,6 +110,7 @@ const App: FC = () => {
|
|
|
94
110
|
translate={translate}
|
|
95
111
|
policyHref="/cookies"
|
|
96
112
|
links={[{ href: 'https://example.test/privacy', labelKey: 'consent.privacy', defaultLabel: 'Privacy Policy' }]}
|
|
113
|
+
{...(withLinker ? { linker: { domains: LINKER_DOMAINS } } : {})}
|
|
97
114
|
/>
|
|
98
115
|
</div>
|
|
99
116
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { afterAll, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
|
|
3
|
+
import type { Page } from '@owlmeans/test-ui'
|
|
4
|
+
import { CONSENT_KEY, CONSENT_LINK_MAX_AGE, CONSENT_LINK_PARAM, encodeConsentLink } from '@owlmeans/consent'
|
|
5
|
+
import { HARNESS_URL } from './context.js'
|
|
6
|
+
|
|
7
|
+
// Browser work does not fit the 5s default: a cold harness compiles the app on first request.
|
|
8
|
+
const TIMEOUT = 60_000
|
|
9
|
+
|
|
10
|
+
afterAll(async () => {
|
|
11
|
+
await closeBrowser()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const base = HARNESS_URL.replace(/\/$/, '')
|
|
15
|
+
const PARTNER_ORIGIN = 'https://partner.test'
|
|
16
|
+
|
|
17
|
+
const nowSeconds = (): number => Math.floor(Date.now() / 1000)
|
|
18
|
+
|
|
19
|
+
describe('@owlmeans/web-consent — the linker: domain disclosure', () => {
|
|
20
|
+
test('the dialog lists the current host plus every configured partner domain', async () => {
|
|
21
|
+
const { page, close } = await mountComponent({ url: `${base}/?linker=1` })
|
|
22
|
+
try {
|
|
23
|
+
await page.locator('[data-consent-dialog]').waitFor()
|
|
24
|
+
const domains = page.locator('[data-consent-domains]')
|
|
25
|
+
await domains.waitFor()
|
|
26
|
+
const text = await domains.textContent()
|
|
27
|
+
expect(text).toContain('partner.test')
|
|
28
|
+
// The current host — whatever the harness's own origin resolves to (127.0.0.1 or localhost).
|
|
29
|
+
expect(text).toMatch(/localhost|127\.0\.0\.1/)
|
|
30
|
+
} finally {
|
|
31
|
+
await close()
|
|
32
|
+
}
|
|
33
|
+
}, TIMEOUT)
|
|
34
|
+
|
|
35
|
+
test('without `linker`, no domain line renders at all', async () => {
|
|
36
|
+
const { page, close } = await mountComponent({ url: `${base}/` })
|
|
37
|
+
try {
|
|
38
|
+
await page.locator('[data-consent-dialog]').waitFor()
|
|
39
|
+
expect(await page.locator('[data-consent-domains]').count()).toBe(0)
|
|
40
|
+
} finally {
|
|
41
|
+
await close()
|
|
42
|
+
}
|
|
43
|
+
}, TIMEOUT)
|
|
44
|
+
|
|
45
|
+
test('the policy page lists the same domains, as a plain list (not a heading)', async () => {
|
|
46
|
+
const { page, close } = await mountComponent({ url: `${base}/?view=policy&linker=1` })
|
|
47
|
+
try {
|
|
48
|
+
await page.locator('[data-cookie-policy]').waitFor()
|
|
49
|
+
const list = page.locator('[data-cookie-policy-domains]')
|
|
50
|
+
await list.waitFor()
|
|
51
|
+
expect(await page.evaluate(el => el.tagName, await list.elementHandle())).toBe('UL')
|
|
52
|
+
expect(await list.textContent()).toContain('partner.test')
|
|
53
|
+
} finally {
|
|
54
|
+
await close()
|
|
55
|
+
}
|
|
56
|
+
}, TIMEOUT)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('@owlmeans/web-consent — the linker: adoption', () => {
|
|
60
|
+
test('a fresh, correctly-referred link is adopted: no dialog, storage written, URL clean', async () => {
|
|
61
|
+
const owlcc = encodeConsentLink({ essential: true, analytics: true, marketing: false })
|
|
62
|
+
const { page, close } = await mountComponent({ url: `${base}/`, waitUntil: 'commit' })
|
|
63
|
+
try {
|
|
64
|
+
await page.goto(`${base}/?linker=1&owlcc=${owlcc}`, {
|
|
65
|
+
waitUntil: 'domcontentloaded', referer: `${PARTNER_ORIGIN}/`,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Never asked — a stored record exists before the dialog could ever open.
|
|
69
|
+
await page.locator('[data-consent-reopen]').waitFor()
|
|
70
|
+
expect(await page.locator('[data-consent-dialog]').count()).toBe(0)
|
|
71
|
+
|
|
72
|
+
const record = await page.evaluate(
|
|
73
|
+
key => JSON.parse(window.localStorage.getItem(key) ?? '{}') as Record<string, unknown>,
|
|
74
|
+
CONSENT_KEY
|
|
75
|
+
)
|
|
76
|
+
expect(record.analytics).toBe(true)
|
|
77
|
+
expect(record.marketing).toBe(false)
|
|
78
|
+
|
|
79
|
+
expect(new URL(page.url()).searchParams.has(CONSENT_LINK_PARAM)).toBe(false)
|
|
80
|
+
} finally {
|
|
81
|
+
await close()
|
|
82
|
+
}
|
|
83
|
+
}, TIMEOUT)
|
|
84
|
+
|
|
85
|
+
test('no referrer at all: the dialog still shows, and the parameter is still stripped', async () => {
|
|
86
|
+
const owlcc = encodeConsentLink({ essential: true, analytics: true, marketing: true })
|
|
87
|
+
const { page, close } = await mountComponent({
|
|
88
|
+
url: `${base}/?linker=1&owlcc=${owlcc}`, waitUntil: 'domcontentloaded',
|
|
89
|
+
})
|
|
90
|
+
try {
|
|
91
|
+
await page.locator('[data-consent-dialog]').waitFor()
|
|
92
|
+
expect(new URL(page.url()).searchParams.has(CONSENT_LINK_PARAM)).toBe(false)
|
|
93
|
+
} finally {
|
|
94
|
+
await close()
|
|
95
|
+
}
|
|
96
|
+
}, TIMEOUT)
|
|
97
|
+
|
|
98
|
+
test('a foreign (unlisted) referrer: refused, dialog shows', async () => {
|
|
99
|
+
const owlcc = encodeConsentLink({ essential: true, analytics: true, marketing: true })
|
|
100
|
+
const { page, close } = await mountComponent({ url: `${base}/`, waitUntil: 'commit' })
|
|
101
|
+
try {
|
|
102
|
+
await page.goto(`${base}/?linker=1&owlcc=${owlcc}`, {
|
|
103
|
+
waitUntil: 'domcontentloaded', referer: 'https://evil.test/',
|
|
104
|
+
})
|
|
105
|
+
await page.locator('[data-consent-dialog]').waitFor()
|
|
106
|
+
} finally {
|
|
107
|
+
await close()
|
|
108
|
+
}
|
|
109
|
+
}, TIMEOUT)
|
|
110
|
+
|
|
111
|
+
test('a stale value past maxAge: refused, dialog shows', async () => {
|
|
112
|
+
const stale = Buffer.from(JSON.stringify({
|
|
113
|
+
v: 2, c: { analytics: 1, marketing: 1 }, t: nowSeconds() - CONSENT_LINK_MAX_AGE - 60,
|
|
114
|
+
})).toString('base64url')
|
|
115
|
+
const { page, close } = await mountComponent({ url: `${base}/`, waitUntil: 'commit' })
|
|
116
|
+
try {
|
|
117
|
+
await page.goto(`${base}/?linker=1&owlcc=${stale}`, {
|
|
118
|
+
waitUntil: 'domcontentloaded', referer: `${PARTNER_ORIGIN}/`,
|
|
119
|
+
})
|
|
120
|
+
await page.locator('[data-consent-dialog]').waitFor()
|
|
121
|
+
} finally {
|
|
122
|
+
await close()
|
|
123
|
+
}
|
|
124
|
+
}, TIMEOUT)
|
|
125
|
+
|
|
126
|
+
test('a stored decision is never overwritten by an incoming link', async () => {
|
|
127
|
+
const { page, close } = await mountComponent({ url: `${base}/`, waitUntil: 'commit' })
|
|
128
|
+
try {
|
|
129
|
+
await page.evaluate(
|
|
130
|
+
([key, value]) => window.localStorage.setItem(key as string, value as string),
|
|
131
|
+
[CONSENT_KEY, JSON.stringify({ essential: true, analytics: false, marketing: false, v: 2 })]
|
|
132
|
+
)
|
|
133
|
+
const owlcc = encodeConsentLink({ essential: true, analytics: true, marketing: true })
|
|
134
|
+
await page.goto(`${base}/?linker=1&owlcc=${owlcc}`, {
|
|
135
|
+
waitUntil: 'domcontentloaded', referer: `${PARTNER_ORIGIN}/`,
|
|
136
|
+
})
|
|
137
|
+
await page.locator('[data-consent-reopen]').waitFor()
|
|
138
|
+
|
|
139
|
+
const record = await page.evaluate(
|
|
140
|
+
key => JSON.parse(window.localStorage.getItem(key) ?? '{}') as Record<string, unknown>,
|
|
141
|
+
CONSENT_KEY
|
|
142
|
+
)
|
|
143
|
+
expect(record.analytics).toBe(false)
|
|
144
|
+
expect(record.marketing).toBe(false)
|
|
145
|
+
} finally {
|
|
146
|
+
await close()
|
|
147
|
+
}
|
|
148
|
+
}, TIMEOUT)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Arm every anchor to swallow its own default navigation, harmlessly — `partner.test`/
|
|
153
|
+
* `elsewhere.test` are not reachable from this sandbox, and a real follow would hang the test.
|
|
154
|
+
* `consentLinker`'s own listener runs in the CAPTURE phase (document, before the target is
|
|
155
|
+
* reached); a plain (bubble-phase) listener added directly on each anchor fires at the "target"
|
|
156
|
+
* stage, strictly AFTER capture has already finished rewriting `href` — so decoration is never
|
|
157
|
+
* raced by this, only the browser's own default action is ever stopped.
|
|
158
|
+
*/
|
|
159
|
+
const armAnchors = (page: Page) => page.evaluate(() => {
|
|
160
|
+
document.querySelectorAll('a').forEach(a => a.addEventListener('click', e => e.preventDefault()))
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('@owlmeans/web-consent — the linker: decoration', () => {
|
|
164
|
+
test('a listed partner anchor gets a fresh owlcc parameter once a decision exists', async () => {
|
|
165
|
+
const { page, close } = await mountComponent({ url: `${base}/?linker=1&anchors=1` })
|
|
166
|
+
try {
|
|
167
|
+
const dialog = page.locator('[data-consent-dialog]')
|
|
168
|
+
await dialog.waitFor()
|
|
169
|
+
await page.locator('[data-consent-accept-all]').click()
|
|
170
|
+
await dialog.waitFor({ state: 'detached' })
|
|
171
|
+
await armAnchors(page)
|
|
172
|
+
|
|
173
|
+
await page.locator('#partner-link').click()
|
|
174
|
+
const href = await page.locator('#partner-link').getAttribute('href')
|
|
175
|
+
expect(href).toContain(`${CONSENT_LINK_PARAM}=`)
|
|
176
|
+
expect(new URL(href!).hostname).toBe('partner.test')
|
|
177
|
+
} finally {
|
|
178
|
+
await close()
|
|
179
|
+
}
|
|
180
|
+
}, TIMEOUT)
|
|
181
|
+
|
|
182
|
+
test('an unlisted host is never decorated', async () => {
|
|
183
|
+
const { page, close } = await mountComponent({ url: `${base}/?linker=1&anchors=1` })
|
|
184
|
+
try {
|
|
185
|
+
const dialog = page.locator('[data-consent-dialog]')
|
|
186
|
+
await dialog.waitFor()
|
|
187
|
+
await page.locator('[data-consent-accept-all]').click()
|
|
188
|
+
await dialog.waitFor({ state: 'detached' })
|
|
189
|
+
await armAnchors(page)
|
|
190
|
+
|
|
191
|
+
await page.locator('#foreign-link').click()
|
|
192
|
+
expect(await page.locator('#foreign-link').getAttribute('href')).toBe('https://elsewhere.test/')
|
|
193
|
+
} finally {
|
|
194
|
+
await close()
|
|
195
|
+
}
|
|
196
|
+
}, TIMEOUT)
|
|
197
|
+
|
|
198
|
+
test('a noreferrer anchor is never decorated, even to a listed partner', async () => {
|
|
199
|
+
const { page, close } = await mountComponent({ url: `${base}/?linker=1&anchors=1` })
|
|
200
|
+
try {
|
|
201
|
+
const dialog = page.locator('[data-consent-dialog]')
|
|
202
|
+
await dialog.waitFor()
|
|
203
|
+
await page.locator('[data-consent-accept-all]').click()
|
|
204
|
+
await dialog.waitFor({ state: 'detached' })
|
|
205
|
+
await armAnchors(page)
|
|
206
|
+
|
|
207
|
+
await page.locator('#noreferrer-link').click()
|
|
208
|
+
expect(await page.locator('#noreferrer-link').getAttribute('href')).toBe('https://partner.test/legal/terms')
|
|
209
|
+
} finally {
|
|
210
|
+
await close()
|
|
211
|
+
}
|
|
212
|
+
}, TIMEOUT)
|
|
213
|
+
|
|
214
|
+
test('nothing is decorated before any decision exists', async () => {
|
|
215
|
+
const { page, close } = await mountComponent({ url: `${base}/?linker=1&anchors=1` })
|
|
216
|
+
try {
|
|
217
|
+
await page.locator('[data-consent-dialog]').waitFor()
|
|
218
|
+
await armAnchors(page)
|
|
219
|
+
// The dialog is still open (no decision yet) — click straight through to the anchor underneath.
|
|
220
|
+
await page.locator('#partner-link').click({ force: true })
|
|
221
|
+
expect(await page.locator('#partner-link').getAttribute('href')).toBe('https://partner.test/legal/cookies')
|
|
222
|
+
} finally {
|
|
223
|
+
await close()
|
|
224
|
+
}
|
|
225
|
+
}, TIMEOUT)
|
|
226
|
+
})
|