@emplugins/emprivacy 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/dist/index.d.mts +64 -4
- package/dist/index.mjs +692 -126
- package/docs/GETTING_STARTED.md +5 -3
- package/docs/PLUGIN_SETTINGS.md +52 -13
- package/docs/RELEASES.md +8 -5
- package/docs/RELEASE_NOTES.md +26 -1
- package/docs/TESTING.md +8 -6
- package/package.json +1 -1
- package/src/astro/embed-resolve.ts +4 -16
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ The legacy unscoped package `emprivacy` is deprecated; migrate imports to `@empl
|
|
|
87
87
|
|
|
88
88
|
5. **Create a Privacy Policy Page** (and optional Cookie Policy Page) in EmDash, then in admin (shield → **EmPrivacy**) set the public path (`/privacy`) or an `https://` URL.
|
|
89
89
|
|
|
90
|
-
6. Pick an analytics preset (Cloudflare, Plausible, Fathom, Umami, Simple Analytics, GA4, GTM, None, or Custom), optional marketing script URLs, and **Save**.
|
|
90
|
+
6. Pick an analytics preset (Cloudflare, Plausible, Fathom, Umami, Simple Analytics, GA4, GTM, Microsoft Clarity, Microsoft UET, None, or Custom), optional marketing script URLs, and **Save**.
|
|
91
91
|
|
|
92
92
|
Visitors see a banner on first visit, when **Policy / consent version** changes, or when an older cookie is missing the functional flag (`f`). Existing `{ v, a, m }` cookies are treated as expired so visitors choose the new category.
|
|
93
93
|
|
|
@@ -97,7 +97,8 @@ After the banner script runs:
|
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
99
|
window.emprivacy.get();
|
|
100
|
-
// { v, essential: true, functional, analytics, marketing } | null
|
|
100
|
+
// { v, essential: true, functional, analytics, marketing, gpc } | null
|
|
101
|
+
// gpc is true when Global Privacy Control forced marketing off
|
|
101
102
|
|
|
102
103
|
window.emprivacy.has("analytics"); // boolean
|
|
103
104
|
window.emprivacy.open(); // reopen the panel
|
|
@@ -113,16 +114,21 @@ stop();
|
|
|
113
114
|
|--------|-------------|
|
|
114
115
|
| Categories | Essential (always on), **Functional**, **Analytics**, **Marketing** |
|
|
115
116
|
| Privacy / cookie links | EmDash **Page** path or `https://` URL; resolved with `ctx.url()` |
|
|
116
|
-
| Analytics presets | Cloudflare Web Analytics, Plausible, Fathom, Umami, Simple Analytics, GA4, **GTM (Marketing)**, None, Custom `https` URLs (
|
|
117
|
+
| Analytics presets | Cloudflare Web Analytics, Plausible, Fathom, Umami, Simple Analytics, GA4, **GTM (Marketing)**, **Microsoft Clarity (Analytics)**, **Microsoft UET (Marketing)**, None, Custom `https` URLs (SRI and script host allowlist required) |
|
|
117
118
|
| Marketing scripts | Only **https** URLs you list, injected as `<script src>` after marketing consent |
|
|
118
119
|
| Official embeds | Optional placeholders for `@emdash-cms/plugin-embeds` block types; YouTube/Vimeo use allowlisted player URLs (`youtube-nocookie`, `player.vimeo.com`); client re-checks URLs before mount |
|
|
119
120
|
| Re-open preferences | Cookie button (bottom-left); saving reloads so script loading matches the latest choice |
|
|
120
121
|
| Consent cookie | `emprivacy_cc` (`path=/`, `SameSite=Lax`, `Secure` on HTTPS, configurable `Max-Age`, default 180 days), JSON `{ v, f, a, m }` |
|
|
121
122
|
| Vendor list | Derived from settings; shown under Customize; JSON at `/_emdash/api/plugins/emprivacy/vendors` (no tokens) |
|
|
122
|
-
| Optional server log | POST `{ policyVersion, functional, analytics, marketing }` to `/_emdash/api/plugins/emprivacy/record` (Origin / Sec-Fetch-Site, rate-limited, no IP) |
|
|
123
|
+
| Optional server log | POST `{ policyVersion, functional, analytics, marketing, gpc }` to `/_emdash/api/plugins/emprivacy/record` (Origin / Sec-Fetch-Site, rate-limited, no IP). Signed-in admins can download `/_emdash/api/plugins/emprivacy/consent-export` as CSV |
|
|
124
|
+
| Global Privacy Control | When `navigator.globalPrivacyControl` is true, Marketing stays off (including Accept all) until the signal is gone. Analytics is unchanged. Visitors can still open preferences |
|
|
125
|
+
| Cookie cleanup | Turning Analytics or Marketing off expires a fixed list of first-party cookie names for the presets EmPrivacy loads. The consent cookie is kept. HttpOnly and third-party cookies cannot be cleared from the page |
|
|
126
|
+
| Keyboard | Focus moves into the banner. Tab stays inside it. Escape closes the reopen panel and returns focus to the cookie button. The first visit stays open until a choice |
|
|
123
127
|
| Google Consent Mode v2 | Optional denied defaults in `<head>` + `gtag('consent','update',…)` including `functionality_storage` |
|
|
128
|
+
| Microsoft consent | Clarity and UET presets queue denied signals in `<head>`. The Clarity tag loads only after Analytics consent; the UET tag loads only after Marketing consent |
|
|
124
129
|
| i18n | Built-in chrome for common locales; optional JSON title/message overrides |
|
|
125
|
-
| Theme |
|
|
130
|
+
| Theme | Starts on the Slate profile. Also Paper, Ink, Indigo, Primer, or custom hex. Custom opens with the colors already in effect. Profiles use fixed colors only |
|
|
131
|
+
| Banner position | Fixed to the bottom (default) or top of the viewport. The fragment still renders at `body:end` |
|
|
126
132
|
|
|
127
133
|
## Capabilities
|
|
128
134
|
|
package/dist/index.d.mts
CHANGED
|
@@ -50,6 +50,16 @@ type AnalyticsLoader = {
|
|
|
50
50
|
{
|
|
51
51
|
type: "gtm";
|
|
52
52
|
containerId: string;
|
|
53
|
+
} |
|
|
54
|
+
/** Loaded only after Analytics consent. Advertising storage follows Marketing. */
|
|
55
|
+
{
|
|
56
|
+
type: "clarity";
|
|
57
|
+
projectId: string;
|
|
58
|
+
} |
|
|
59
|
+
/** Loaded only after Marketing consent. */
|
|
60
|
+
{
|
|
61
|
+
type: "uet";
|
|
62
|
+
tagId: string;
|
|
53
63
|
};
|
|
54
64
|
export declare function buildAnalyticsLoader(cfg: EmprivacyConfig): AnalyticsLoader;
|
|
55
65
|
export declare function buildVendorList(cfg: EmprivacyConfig): VendorRow[];
|
|
@@ -74,6 +84,7 @@ interface EmprivacyChromeStrings {
|
|
|
74
84
|
embedNeedConsent: string;
|
|
75
85
|
vendorsHeading: string;
|
|
76
86
|
whatWeUse: string;
|
|
87
|
+
gpcNote: string;
|
|
77
88
|
}
|
|
78
89
|
interface EmprivacyLocaleCopy {
|
|
79
90
|
bannerTitle: string;
|
|
@@ -88,11 +99,46 @@ interface EmprivacyTheme {
|
|
|
88
99
|
accent: string;
|
|
89
100
|
radiusPx: number;
|
|
90
101
|
}
|
|
102
|
+
/** Fixed banner palettes. Colors are constants, never taken from the admin form. */
|
|
103
|
+
declare const THEME_PROFILES: {
|
|
104
|
+
readonly slate: {
|
|
105
|
+
readonly bg: "#0f172a";
|
|
106
|
+
readonly text: "#f8fafc";
|
|
107
|
+
readonly accent: "#3b82f6";
|
|
108
|
+
readonly radiusPx: 8;
|
|
109
|
+
};
|
|
110
|
+
readonly paper: {
|
|
111
|
+
readonly bg: "#f7f6f3";
|
|
112
|
+
readonly text: "#37352f";
|
|
113
|
+
readonly accent: "#1d4ed8";
|
|
114
|
+
readonly radiusPx: 6;
|
|
115
|
+
};
|
|
116
|
+
readonly ink: {
|
|
117
|
+
readonly bg: "#000000";
|
|
118
|
+
readonly text: "#ededed";
|
|
119
|
+
readonly accent: "#0070f3";
|
|
120
|
+
readonly radiusPx: 8;
|
|
121
|
+
};
|
|
122
|
+
readonly indigo: {
|
|
123
|
+
readonly bg: "#ffffff";
|
|
124
|
+
readonly text: "#0f172a";
|
|
125
|
+
readonly accent: "#4f46e5";
|
|
126
|
+
readonly radiusPx: 12;
|
|
127
|
+
};
|
|
128
|
+
readonly primer: {
|
|
129
|
+
readonly bg: "#f6f8fa";
|
|
130
|
+
readonly text: "#1f2328";
|
|
131
|
+
readonly accent: "#0969da";
|
|
132
|
+
readonly radiusPx: 6;
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
type ThemeProfileId = keyof typeof THEME_PROFILES;
|
|
136
|
+
type ThemeChoice = ThemeProfileId | "custom";
|
|
91
137
|
//#endregion
|
|
92
138
|
//#region src/config.d.ts
|
|
93
139
|
export declare const KV_KEY: "consent:config";
|
|
94
140
|
export declare const COOKIE_NAME: "emprivacy_cc";
|
|
95
|
-
type AnalyticsProvider = "cloudflare" | "none" | "custom" | "plausible" | "fathom" | "umami" | "simpleanalytics" | "ga4" | "gtm";
|
|
141
|
+
type AnalyticsProvider = "cloudflare" | "none" | "custom" | "plausible" | "fathom" | "umami" | "simpleanalytics" | "ga4" | "gtm" | "clarity" | "uet";
|
|
96
142
|
type EmbedCategory = "functional" | "marketing";
|
|
97
143
|
interface EmprivacyConfig {
|
|
98
144
|
bannerTitle: string;
|
|
@@ -125,10 +171,15 @@ interface EmprivacyConfig {
|
|
|
125
171
|
/** Optional SRI hashes keyed by script src (sha256/384/512-…) */
|
|
126
172
|
scriptIntegrity: Record<string, string>;
|
|
127
173
|
/**
|
|
128
|
-
*
|
|
129
|
-
* Preset CDN hosts
|
|
174
|
+
* Hostnames allowed for Custom analytics, Umami, and marketing scripts.
|
|
175
|
+
* An empty list denies those URLs. Preset CDN hosts stay allowed for built-in providers.
|
|
130
176
|
*/
|
|
131
177
|
scriptHostAllowlist: string[];
|
|
178
|
+
/**
|
|
179
|
+
* Hosts that may become https links for Mastodon and link previews.
|
|
180
|
+
* X, Bluesky, Gist, YouTube, and Vimeo do not use this list.
|
|
181
|
+
*/
|
|
182
|
+
embedHostAllowlist: string[];
|
|
132
183
|
/** Consent cookie Max-Age in days (1–365, default 180) */
|
|
133
184
|
cookieMaxAgeDays: number;
|
|
134
185
|
/** Emit Google Consent Mode v2 defaults (denied) in head; updates after choice */
|
|
@@ -141,10 +192,15 @@ interface EmprivacyConfig {
|
|
|
141
192
|
gateEmbeds: boolean;
|
|
142
193
|
/** Hide the first-visit banner on privacy/cookie policy paths */
|
|
143
194
|
hideBannerOnPolicyPages: boolean;
|
|
195
|
+
/** Viewport edge for the fixed consent banner. Fragment placement stays `body:end`. */
|
|
196
|
+
bannerPosition: BannerPosition;
|
|
144
197
|
defaultLocale: string;
|
|
145
198
|
localeOverrides: LocaleOverrides;
|
|
199
|
+
/** `custom` uses `theme`. A profile id replaces `theme` with that profile’s fixed colors. */
|
|
200
|
+
themeProfile: ThemeChoice;
|
|
146
201
|
theme: EmprivacyTheme;
|
|
147
202
|
}
|
|
203
|
+
type BannerPosition = "bottom" | "top";
|
|
148
204
|
export declare const DEFAULT_CONFIG: EmprivacyConfig;
|
|
149
205
|
/** Reject characters that would break JSON or attributes; allow typical Cloudflare site token strings. */
|
|
150
206
|
export declare function assertValidCloudflareToken(t: string): void;
|
|
@@ -167,6 +223,8 @@ interface ConsentRecordPayload {
|
|
|
167
223
|
functional: boolean;
|
|
168
224
|
analytics: boolean;
|
|
169
225
|
marketing: boolean;
|
|
226
|
+
/** Present when the browser's Global Privacy Control signal forced marketing off. */
|
|
227
|
+
gpc?: boolean;
|
|
170
228
|
}
|
|
171
229
|
interface ConsentState {
|
|
172
230
|
v: string;
|
|
@@ -195,6 +253,7 @@ interface EmprivacyPublicRuntimeConfig {
|
|
|
195
253
|
embedCategory: EmbedCategory;
|
|
196
254
|
gateEmbeds: boolean;
|
|
197
255
|
hideBanner: boolean;
|
|
256
|
+
bannerPosition: BannerPosition;
|
|
198
257
|
theme: EmprivacyTheme;
|
|
199
258
|
ui: EmprivacyChromeStrings;
|
|
200
259
|
loader: AnalyticsLoader;
|
|
@@ -203,6 +262,7 @@ interface EmprivacyPublicRuntimeConfig {
|
|
|
203
262
|
integrity: string | null;
|
|
204
263
|
}[];
|
|
205
264
|
scriptHostAllowlist: string[];
|
|
265
|
+
embedHostAllowlist: string[];
|
|
206
266
|
scriptIntegrity: Record<string, string>;
|
|
207
267
|
cookieMaxAge: number;
|
|
208
268
|
vendors: VendorPublicRow[];
|
|
@@ -259,4 +319,4 @@ export declare function resolveEmbed(node: {
|
|
|
259
319
|
*/
|
|
260
320
|
export declare function emprivacyPlugin(): PluginDescriptor;
|
|
261
321
|
//#endregion
|
|
262
|
-
export { type AnalyticsLoader, type AnalyticsProvider, type ConsentRecordPayload, type ConsentState, type EmbedCategory, type EmprivacyBrowserApi, type EmprivacyCategory, type EmprivacyConfig, type EmprivacyPublicRuntimeConfig, type ResolvedEmbed, type VendorPublicRow, type VendorRow, createPlugin as default };
|
|
322
|
+
export { type AnalyticsLoader, type AnalyticsProvider, type BannerPosition, type ConsentRecordPayload, type ConsentState, type EmbedCategory, type EmprivacyBrowserApi, type EmprivacyCategory, type EmprivacyConfig, type EmprivacyPublicRuntimeConfig, type ResolvedEmbed, type VendorPublicRow, type VendorRow, createPlugin as default };
|