@emplugins/emprivacy 3.0.0 → 3.1.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 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 (+ optional SRI / host allowlist) |
117
+ | Analytics presets | Cloudflare Web Analytics, Plausible, Fathom, Umami, Simple Analytics, GA4, **GTM (Marketing)**, **Microsoft Clarity (Analytics)**, **Microsoft UET (Marketing)**, None, Custom `https` URLs (+ optional SRI / host allowlist) |
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 | Hex CSS variables (`--emprivacy-bg`, `--emprivacy-text`, `--emprivacy-accent`, `--emprivacy-radius`) |
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;
@@ -141,10 +187,15 @@ interface EmprivacyConfig {
141
187
  gateEmbeds: boolean;
142
188
  /** Hide the first-visit banner on privacy/cookie policy paths */
143
189
  hideBannerOnPolicyPages: boolean;
190
+ /** Viewport edge for the fixed consent banner. Fragment placement stays `body:end`. */
191
+ bannerPosition: BannerPosition;
144
192
  defaultLocale: string;
145
193
  localeOverrides: LocaleOverrides;
194
+ /** `custom` uses `theme`. A profile id replaces `theme` with that profile’s fixed colors. */
195
+ themeProfile: ThemeChoice;
146
196
  theme: EmprivacyTheme;
147
197
  }
198
+ type BannerPosition = "bottom" | "top";
148
199
  export declare const DEFAULT_CONFIG: EmprivacyConfig;
149
200
  /** Reject characters that would break JSON or attributes; allow typical Cloudflare site token strings. */
150
201
  export declare function assertValidCloudflareToken(t: string): void;
@@ -167,6 +218,8 @@ interface ConsentRecordPayload {
167
218
  functional: boolean;
168
219
  analytics: boolean;
169
220
  marketing: boolean;
221
+ /** Present when the browser's Global Privacy Control signal forced marketing off. */
222
+ gpc?: boolean;
170
223
  }
171
224
  interface ConsentState {
172
225
  v: string;
@@ -195,6 +248,7 @@ interface EmprivacyPublicRuntimeConfig {
195
248
  embedCategory: EmbedCategory;
196
249
  gateEmbeds: boolean;
197
250
  hideBanner: boolean;
251
+ bannerPosition: BannerPosition;
198
252
  theme: EmprivacyTheme;
199
253
  ui: EmprivacyChromeStrings;
200
254
  loader: AnalyticsLoader;
@@ -259,4 +313,4 @@ export declare function resolveEmbed(node: {
259
313
  */
260
314
  export declare function emprivacyPlugin(): PluginDescriptor;
261
315
  //#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 };
316
+ 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 };