@emplugins/emprivacy 1.0.0 → 2.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.
@@ -1,693 +0,0 @@
1
- import { i as PLUGIN_ID, l as normalizeConfig, o as assertValidSavedConfig, r as KV_KEY, t as COOKIE_NAME, u as resolvePolicyHref } from "./config-BJ1LHuri.mjs";
2
- import { z } from "zod";
3
-
4
- //#region src/sandbox-entry.ts
5
- const ADMIN_SETTINGS_PATH = "/settings";
6
- const SAVE_ACTION_ID = "emprivacy-save";
7
- const recordInput = z.object({
8
- policyVersion: z.string().trim().min(1).max(64),
9
- analytics: z.boolean(),
10
- marketing: z.boolean()
11
- });
12
- async function loadConfig(ctx) {
13
- const raw = await ctx.kv.get(KV_KEY);
14
- if (!raw) return normalizeConfig({});
15
- try {
16
- return normalizeConfig(JSON.parse(raw));
17
- } catch {
18
- return normalizeConfig({});
19
- }
20
- }
21
- async function saveConfigString(ctx, json) {
22
- await ctx.kv.set(KV_KEY, json);
23
- }
24
- function requestHeader(request, name) {
25
- const target = name.toLowerCase();
26
- for (const [key, value] of Object.entries(request.headers)) if (key.toLowerCase() === target) return value;
27
- return null;
28
- }
29
- /** Path-only record URL for browser fetch (same origin). */
30
- function recordPathForSite() {
31
- return `/_emdash/api/plugins/${PLUGIN_ID}/record`;
32
- }
33
- /** Absolute URL for metadata / banner; paths resolved via EmDash `ctx.url()`. */
34
- function absolutePolicyHref(stored, ctx) {
35
- const r = resolvePolicyHref(stored, ctx).trim();
36
- if (!r) return null;
37
- try {
38
- if (new URL(r).protocol !== "https:") return null;
39
- return r;
40
- } catch {
41
- return null;
42
- }
43
- }
44
- function buildGoogleConsentHeadScript() {
45
- return `(function(){window.dataLayer=window.dataLayer||[];function g(){window.dataLayer.push(arguments);}window.gtag=g;g("consent","default",{"analytics_storage":"denied","ad_storage":"denied","ad_user_data":"denied","ad_personalization":"denied","functionality_storage":"granted","security_storage":"granted","personalization_storage":"denied"});})();`;
46
- }
47
- /**
48
- * Public-site bootstrap: no HTML from admin strings in DOM APIs beyond JSON.parse —
49
- * banner copy is applied with textContent / createTextNode.
50
- */
51
- function buildBodyBootstrap(pr) {
52
- return `(function(){
53
- var C=${JSON.stringify(pr).replace(/[<>&\u2028\u2029]/g, (c) => {
54
- switch (c) {
55
- case "<": return "\\u003c";
56
- case ">": return "\\u003e";
57
- case "&": return "\\u0026";
58
- case "\u2028": return "\\u2028";
59
- case "\u2029": return "\\u2029";
60
- default: return c;
61
- }
62
- })};
63
- var CN="${COOKIE_NAME}";
64
- function readCookie(){
65
- try{
66
- var m=document.cookie.match(new RegExp("(?:^|;\\\\s*)"+CN+"=([^;]*)"));
67
- var v=m?decodeURIComponent(m[1]):"";
68
- if(!v||v.length>1024)return"";
69
- return v;
70
- }catch(e){return"";}
71
- }
72
- function writeCookie(val){
73
- var secure=document.location.protocol==="https:"?"; Secure":"";
74
- document.cookie=CN+"="+encodeURIComponent(val)+"; Path=/; SameSite=Lax"+secure+"; Max-Age=31536000";
75
- }
76
- function parseState(s){
77
- try{
78
- var o=JSON.parse(s);
79
- if(!o||typeof o!=="object")return null;
80
- if(typeof o.v!=="string")return null;
81
- if(o.v!==C.policyVersion)return null;
82
- var a=o.a,m=o.m;
83
- var aOk=(a===0||a===1||a===true||a===false);
84
- var mOk=(m===0||m===1||m===true||m===false);
85
- if(!aOk||!mOk)return null;
86
- return {v:o.v,a:!!a,m:!!m};
87
- }catch(e){return null;}
88
- }
89
- function needBanner(){
90
- var c=parseState(readCookie());
91
- if(!c)return true;
92
- return false;
93
- }
94
- function alreadyScriptSrc(u){
95
- try{
96
- return Array.prototype.some.call(document.getElementsByTagName("script"),function(s){
97
- return s.src===u;
98
- });
99
- }catch(e){
100
- return false;
101
- }
102
- }
103
- function loadScript(src){
104
- if(alreadyScriptSrc(src))return;
105
- var e=document.createElement("script");
106
- e.src=src;e.async=true;e.referrerPolicy="no-referrer-when-downgrade";
107
- document.head.appendChild(e);
108
- }
109
- function loadCloudflareBeacon(token){
110
- var u="https://static.cloudflareinsights.com/beacon.min.js";
111
- if(!token)return;
112
- if(alreadyScriptSrc(u))return;
113
- var e=document.createElement("script");
114
- e.src=u;
115
- e.defer=true;
116
- e.setAttribute("data-cf-beacon",JSON.stringify({token:token}));
117
- e.referrerPolicy="no-referrer-when-downgrade";
118
- document.head.appendChild(e);
119
- }
120
- function applyScripts(a,m){
121
- if(a){
122
- if(C.analyticsProvider==="cloudflare"){
123
- loadCloudflareBeacon(C.cloudflareToken);
124
- }else if(C.analyticsProvider==="custom"){
125
- C.analyticsScriptUrls.forEach(loadScript);
126
- }
127
- }
128
- if(m)C.marketingScriptUrls.forEach(loadScript);
129
- }
130
- function gtagUpdate(a,m){
131
- if(!C.googleConsentMode||!window.gtag)return;
132
- window.gtag("consent","update",{
133
- analytics_storage:a?"granted":"denied",
134
- ad_storage:m?"granted":"denied",
135
- ad_user_data:m?"granted":"denied",
136
- ad_personalization:m?"granted":"denied",
137
- personalization_storage:m?"granted":"denied"
138
- });
139
- }
140
- function logServer(a,m){
141
- if(!C.logConsent)return;
142
- var p=C.recordPath;
143
- fetch(p,{
144
- method:"POST",
145
- credentials:"same-origin",
146
- headers:{"Content-Type":"application/json"},
147
- body:JSON.stringify({policyVersion:C.policyVersion,analytics:!!a,marketing:!!m})
148
- }).catch(function(){});
149
- }
150
- function hide(el){if(el)el.setAttribute("hidden","");el&&(el.style.display="none");}
151
- function show(el){if(el)el.removeAttribute("hidden");el&&(el.style.display="");}
152
- function mount(){
153
- var root=document.getElementById("emprivacy-root");
154
- if(!root)return;
155
- var trigger=document.createElement("button");
156
- trigger.type="button";
157
- trigger.className="emprivacy-cookie-trigger";
158
- trigger.setAttribute("aria-label","Cookie and privacy settings");
159
- var ic=document.createElement("span");
160
- ic.setAttribute("aria-hidden","true");
161
- ic.className="emprivacy-cookie-trigger-icon";
162
- ic.appendChild(document.createTextNode("🍪"));
163
- trigger.appendChild(ic);
164
- function showTrigger(){
165
- trigger.removeAttribute("hidden");
166
- }
167
- function hideTrigger(){
168
- trigger.setAttribute("hidden","");
169
- }
170
- function mkRow(label,id,on){
171
- var w=document.createElement("label");
172
- w.className="emprivacy-switch";
173
- var cb=document.createElement("input");
174
- cb.type="checkbox";
175
- cb.id=id;
176
- cb.checked=on;
177
- w.appendChild(cb);
178
- w.appendChild(document.createTextNode(" "+label));
179
- return {wrap:w,box:cb};
180
- }
181
- function addPolicyLinks(links){
182
- function addOne(href,text){
183
- if(!href)return;
184
- var ok=href.indexOf("https://")===0;
185
- if(!ok)return;
186
- var a=document.createElement("a");
187
- a.href=href;
188
- a.rel="nofollow noopener";
189
- a.target="_blank";
190
- a.appendChild(document.createTextNode(text));
191
- links.appendChild(a);
192
- }
193
- addOne(C.privacyPolicyUrl,"Privacy policy");
194
- if(C.cookiePolicyUrl)addOne(C.cookiePolicyUrl,"Cookie policy");
195
- }
196
- function openReopenPanel(){
197
- if(root.querySelector(".emprivacy-bar--reopen"))return;
198
- var st=parseState(readCookie());
199
- if(!st)return;
200
- hideTrigger();
201
- var aOn=!!st.a;
202
- var mOn=!!st.m;
203
- var bar=document.createElement("div");
204
- bar.className="emprivacy-bar emprivacy-bar--reopen";
205
- bar.setAttribute("role","dialog");
206
- bar.setAttribute("aria-modal","true");
207
- var title=document.createElement("h2");
208
- title.className="emprivacy-title";
209
- title.appendChild(document.createTextNode(C.bannerTitle));
210
- var msg=document.createElement("p");
211
- msg.className="emprivacy-msg";
212
- msg.appendChild(document.createTextNode(C.bannerMessage));
213
- var links=document.createElement("div");
214
- links.className="emprivacy-links";
215
- addPolicyLinks(links);
216
- var opts=document.createElement("div");
217
- opts.className="emprivacy-opts";
218
- var er=mkRow("Analytics","emprivacy-ra",aOn);
219
- var mr=mkRow("Marketing","emprivacy-rm",mOn);
220
- opts.appendChild(er.wrap);
221
- opts.appendChild(mr.wrap);
222
- var actions=document.createElement("div");
223
- actions.className="emprivacy-actions";
224
- function doReopenPersist(a,m){
225
- writeCookie(JSON.stringify({v:C.policyVersion,a:a?1:0,m:m?1:0}));
226
- logServer(a,m);
227
- location.reload();
228
- }
229
- var btnAll=document.createElement("button");
230
- btnAll.type="button";
231
- btnAll.className="emprivacy-btn emprivacy-btn-primary";
232
- btnAll.appendChild(document.createTextNode("Accept all"));
233
- btnAll.addEventListener("click",function(){doReopenPersist(true,true);});
234
- var btnRej=document.createElement("button");
235
- btnRej.type="button";
236
- btnRej.className="emprivacy-btn";
237
- btnRej.appendChild(document.createTextNode("Reject non-essential"));
238
- btnRej.addEventListener("click",function(){doReopenPersist(false,false);});
239
- var btnSave=document.createElement("button");
240
- btnSave.type="button";
241
- btnSave.className="emprivacy-btn emprivacy-btn-primary";
242
- btnSave.appendChild(document.createTextNode("Save choices"));
243
- btnSave.addEventListener("click",function(){doReopenPersist(!!er.box.checked,!!mr.box.checked);});
244
- var btnClose=document.createElement("button");
245
- btnClose.type="button";
246
- btnClose.className="emprivacy-btn";
247
- btnClose.appendChild(document.createTextNode("Close"));
248
- btnClose.addEventListener("click",function(){
249
- if(bar.parentNode)bar.parentNode.removeChild(bar);
250
- showTrigger();
251
- });
252
- actions.appendChild(btnAll);
253
- actions.appendChild(btnRej);
254
- actions.appendChild(btnSave);
255
- actions.appendChild(btnClose);
256
- bar.appendChild(title);
257
- bar.appendChild(msg);
258
- bar.appendChild(links);
259
- bar.appendChild(opts);
260
- bar.appendChild(actions);
261
- root.appendChild(bar);
262
- }
263
- root.appendChild(trigger);
264
- hideTrigger();
265
- if(!needBanner()){
266
- var st=parseState(readCookie());
267
- applyScripts(!!(st&&st.a),!!(st&&st.m));
268
- gtagUpdate(!!(st&&st.a),!!(st&&st.m));
269
- showTrigger();
270
- trigger.addEventListener("click",function(){openReopenPanel();});
271
- return;
272
- }
273
- var aOn=C.strictDefaults?false:true;
274
- var mOn=C.strictDefaults?false:true;
275
- var bar=document.createElement("div");
276
- bar.className="emprivacy-bar";
277
- bar.setAttribute("role","dialog");
278
- bar.setAttribute("aria-modal","false");
279
- var title=document.createElement("h2");
280
- title.className="emprivacy-title";
281
- title.appendChild(document.createTextNode(C.bannerTitle));
282
- var msg=document.createElement("p");
283
- msg.className="emprivacy-msg";
284
- msg.appendChild(document.createTextNode(C.bannerMessage));
285
- var links=document.createElement("div");
286
- links.className="emprivacy-links";
287
- addPolicyLinks(links);
288
- var opts=document.createElement("div");
289
- opts.className="emprivacy-opts";
290
- opts.setAttribute("hidden","");
291
- var er=mkRow("Analytics","emprivacy-a",aOn);
292
- var mr=mkRow("Marketing","emprivacy-m",mOn);
293
- opts.appendChild(er.wrap);
294
- opts.appendChild(mr.wrap);
295
- var actions=document.createElement("div");
296
- actions.className="emprivacy-actions";
297
- function persist(a,m){
298
- writeCookie(JSON.stringify({v:C.policyVersion,a:a?1:0,m:m?1:0}));
299
- logServer(a,m);
300
- // For Cloudflare Web Analytics (and most tag-based analytics), injecting after load can miss the initial pageview.
301
- // Reload ensures the script runs in the normal page lifecycle immediately after consent is persisted.
302
- location.reload();
303
- }
304
- var btnAll=document.createElement("button");
305
- btnAll.type="button";
306
- btnAll.className="emprivacy-btn emprivacy-btn-primary";
307
- btnAll.appendChild(document.createTextNode("Accept all"));
308
- btnAll.addEventListener("click",function(){persist(true,true);});
309
- var btnRej=document.createElement("button");
310
- btnRej.type="button";
311
- btnRej.className="emprivacy-btn";
312
- btnRej.appendChild(document.createTextNode("Reject non-essential"));
313
- btnRej.addEventListener("click",function(){persist(false,false);});
314
- var btnCust=document.createElement("button");
315
- btnCust.type="button";
316
- btnCust.className="emprivacy-btn";
317
- btnCust.appendChild(document.createTextNode("Customize"));
318
- btnCust.addEventListener("click",function(){show(opts);btnSave.removeAttribute("hidden");});
319
- var btnSave=document.createElement("button");
320
- btnSave.type="button";
321
- btnSave.className="emprivacy-btn emprivacy-btn-primary";
322
- btnSave.setAttribute("hidden","");
323
- btnSave.appendChild(document.createTextNode("Save choices"));
324
- btnSave.addEventListener("click",function(){persist(!!er.box.checked,!!mr.box.checked);});
325
- actions.appendChild(btnAll);
326
- actions.appendChild(btnRej);
327
- actions.appendChild(btnCust);
328
- actions.appendChild(btnSave);
329
- bar.appendChild(title);
330
- bar.appendChild(msg);
331
- bar.appendChild(links);
332
- bar.appendChild(opts);
333
- bar.appendChild(actions);
334
- root.appendChild(bar);
335
- }
336
- if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",mount);
337
- else mount();
338
- })();`;
339
- }
340
- var sandbox_entry_default = {
341
- hooks: {
342
- "plugin:install": async (_e, ctx) => {
343
- if (!await ctx.kv.get(KV_KEY)) {
344
- await saveConfigString(ctx, JSON.stringify(normalizeConfig({})));
345
- ctx.log.info("EmPrivacy: seeded default configuration");
346
- }
347
- },
348
- "page:metadata": { handler: async (_e, ctx) => {
349
- const cfg = await loadConfig(ctx);
350
- const out = [];
351
- const privacyHref = absolutePolicyHref(cfg.privacyPolicyUrl, ctx);
352
- if (privacyHref) out.push({
353
- kind: "link",
354
- rel: "site.standard.document",
355
- href: privacyHref,
356
- key: "emprivacy:privacy"
357
- });
358
- const cookieHref = cfg.cookiePolicyUrl ? absolutePolicyHref(cfg.cookiePolicyUrl, ctx) : null;
359
- if (cookieHref) out.push({
360
- kind: "meta",
361
- name: "cookie-policy",
362
- content: cookieHref,
363
- key: "emprivacy:cookie-meta"
364
- });
365
- return out.length ? out : null;
366
- } },
367
- "page:fragments": { handler: async (_e, ctx) => {
368
- const cfg = await loadConfig(ctx);
369
- const privacyResolved = absolutePolicyHref(cfg.privacyPolicyUrl, ctx) ?? "";
370
- const cookieResolved = cfg.cookiePolicyUrl ? absolutePolicyHref(cfg.cookiePolicyUrl, ctx) ?? "" : "";
371
- const pr = {
372
- bannerTitle: cfg.bannerTitle,
373
- bannerMessage: cfg.bannerMessage,
374
- privacyPolicyUrl: privacyResolved,
375
- cookiePolicyUrl: cookieResolved,
376
- strictDefaults: cfg.strictDefaults,
377
- policyVersion: cfg.policyVersion,
378
- analyticsProvider: cfg.analyticsProvider,
379
- cloudflareToken: cfg.cloudflareWebAnalyticsToken,
380
- analyticsScriptUrls: cfg.analyticsScriptUrls,
381
- marketingScriptUrls: cfg.marketingScriptUrls,
382
- googleConsentMode: cfg.googleConsentMode,
383
- logConsent: cfg.logConsentToServer,
384
- recordPath: recordPathForSite()
385
- };
386
- const style = {
387
- kind: "html",
388
- placement: "body:end",
389
- key: "emprivacy:style",
390
- html: `<style id="emprivacy-style">
391
- #emprivacy-root{font-family:system-ui,sans-serif;font-size:14px}
392
- .emprivacy-bar{position:fixed;z-index:99999;left:0;right:0;bottom:0;background:#111;color:#eee;padding:16px 20px 20px;box-shadow:0 -4px 24px rgba(0,0,0,.25);max-height:45vh;overflow:auto}
393
- .emprivacy-title{margin:0 0 8px;font-size:1.1rem}
394
- .emprivacy-msg{margin:0 0 10px;line-height:1.4;opacity:.95}
395
- .emprivacy-links a{color:#8ecbff;margin-right:12px}
396
- .emprivacy-opts{margin:10px 0;border-top:1px solid #333;padding-top:10px}
397
- .emprivacy-switch{display:block;margin:6px 0}
398
- .emprivacy-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
399
- .emprivacy-btn{border-radius:6px;border:1px solid #555;background:#222;color:#eee;padding:8px 12px;cursor:pointer}
400
- .emprivacy-btn-primary{background:#3b82f6;border-color:#3b82f6;color:#fff}
401
- .emprivacy-cookie-trigger{position:fixed;z-index:99998;left:16px;bottom:16px;width:48px;height:48px;border-radius:50%;border:1px solid #555;background:#222;color:#e8b86d;box-shadow:0 2px 12px rgba(0,0,0,.2);cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center}
402
- .emprivacy-cookie-trigger:hover{background:#2a2a2a}
403
- .emprivacy-cookie-trigger:focus{outline:2px solid #3b82f6;outline-offset:2px}
404
- .emprivacy-cookie-trigger[hidden]{display:none!important}
405
- .emprivacy-cookie-trigger-icon{font-size:1.35rem;line-height:1}
406
- .emprivacy-bar--reopen{z-index:100000}
407
- </style>`
408
- };
409
- const shell = {
410
- kind: "html",
411
- placement: "body:end",
412
- key: "emprivacy:shell",
413
- html: `<div id="emprivacy-root" data-emprivacy="1"></div>`
414
- };
415
- const headScripts = [];
416
- if (cfg.googleConsentMode) headScripts.push({
417
- kind: "inline-script",
418
- placement: "head",
419
- key: "emprivacy:gcm-default",
420
- code: buildGoogleConsentHeadScript()
421
- });
422
- const boot = {
423
- kind: "inline-script",
424
- placement: "body:end",
425
- key: "emprivacy:boot",
426
- code: buildBodyBootstrap(pr)
427
- };
428
- return [
429
- ...headScripts,
430
- style,
431
- shell,
432
- boot
433
- ];
434
- } }
435
- },
436
- routes: {
437
- admin: { handler: async (routeCtx, ctx) => {
438
- const interaction = routeCtx.input;
439
- if (interaction?.type === "page_load" && interaction.page === ADMIN_SETTINGS_PATH) return buildSettingsPage(ctx);
440
- if (interaction?.type === "form_submit" && interaction.action_id === SAVE_ACTION_ID) {
441
- const v = interaction.values ?? {};
442
- const asBool = (x) => {
443
- if (x === true) return true;
444
- if (x === false) return false;
445
- if (typeof x === "string") return x === "true" || x === "on" || x === "1";
446
- return Boolean(x);
447
- };
448
- try {
449
- const next = assertValidSavedConfig({
450
- bannerTitle: String(v.banner_title ?? ""),
451
- bannerMessage: String(v.banner_message ?? ""),
452
- privacyPolicyUrl: String(v.privacy_url ?? ""),
453
- cookiePolicyUrl: String(v.cookie_url ?? ""),
454
- strictDefaults: asBool(v.strict_defaults),
455
- policyVersion: String(v.policy_version ?? ""),
456
- analyticsPlatform: String(v.analytics_platform ?? "cloudflare"),
457
- cloudflareToken: String(v.cloudflare_token ?? ""),
458
- analyticsUrlsText: String(v.analytics_urls ?? ""),
459
- marketingUrlsText: String(v.marketing_urls ?? ""),
460
- googleConsentMode: asBool(v.google_cm),
461
- logConsentToServer: asBool(v.log_server)
462
- });
463
- await saveConfigString(ctx, JSON.stringify(next));
464
- return {
465
- ...await buildSettingsPage(ctx),
466
- toast: {
467
- message: "EmPrivacy settings saved.",
468
- type: "success"
469
- }
470
- };
471
- } catch (e) {
472
- const msg = e instanceof Error ? e.message : "Save failed.";
473
- return {
474
- ...await buildSettingsPage(ctx),
475
- toast: {
476
- message: msg,
477
- type: "error"
478
- }
479
- };
480
- }
481
- }
482
- return { blocks: [] };
483
- } },
484
- record: {
485
- public: true,
486
- input: recordInput,
487
- handler: async (routeCtx, ctx) => {
488
- if (routeCtx.request.method !== "POST") return { ok: false };
489
- try {
490
- const origin = requestHeader(routeCtx.request, "origin");
491
- if (origin) {
492
- if (origin !== new URL(ctx.url("/")).origin) return new Response("forbidden", { status: 403 });
493
- }
494
- } catch {
495
- return new Response("bad_request", { status: 400 });
496
- }
497
- if (!(requestHeader(routeCtx.request, "content-type") ?? "").toLowerCase().includes("application/json")) return new Response("unsupported_media_type", { status: 415 });
498
- const cfg = await loadConfig(ctx);
499
- if (!cfg.logConsentToServer) return {
500
- ok: false,
501
- reason: "logging_disabled"
502
- };
503
- const input = routeCtx.input;
504
- if (input.policyVersion !== cfg.policyVersion) return new Response("policy_version_mismatch", { status: 400 });
505
- const row = {
506
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
507
- policyVersion: input.policyVersion,
508
- analytics: input.analytics,
509
- marketing: input.marketing
510
- };
511
- try {
512
- if ((await ctx.storage.consentEvents.query({
513
- orderBy: { createdAt: "desc" },
514
- limit: 500
515
- })).items.length >= 500) return {
516
- ok: false,
517
- reason: "log_full"
518
- };
519
- } catch {}
520
- await ctx.storage.consentEvents.put(`${Date.now()}-${Math.random().toString(36).slice(2)}`, row);
521
- return { ok: true };
522
- }
523
- }
524
- }
525
- };
526
- async function buildSettingsPage(ctx) {
527
- const cfg = await loadConfig(ctx);
528
- const analyticsText = cfg.analyticsScriptUrls.join("\n");
529
- const marketingText = cfg.marketingScriptUrls.join("\n");
530
- const platform = cfg.analyticsProvider;
531
- const docsUrl = "https://github.com/EmPlugins/EmPrivacy/blob/main/docs/PLUGIN_SETTINGS.md";
532
- const consentFields = await (async () => {
533
- try {
534
- return (await ctx.storage.consentEvents.query({
535
- orderBy: { createdAt: "desc" },
536
- limit: 15
537
- })).items.map((item) => {
538
- const d = item.data;
539
- return {
540
- label: d.createdAt,
541
- value: `policy ${d.policyVersion} · analytics ${d.analytics ? "on" : "off"} · marketing ${d.marketing ? "on" : "off"}`
542
- };
543
- });
544
- } catch {
545
- return [];
546
- }
547
- })();
548
- return { blocks: [
549
- {
550
- type: "header",
551
- text: "EmPrivacy — Cookie & consent"
552
- },
553
- {
554
- type: "context",
555
- text: `Documentation: ${docsUrl}`
556
- },
557
- {
558
- type: "context",
559
- text: "Configure the public banner. **Analytics:** pick a platform (Cloudflare is built in — enter your **site token** from Cloudflare → Web Analytics; EmPrivacy injects the standard beacon with `data-cf-beacon` after consent). **None** = no third-party analytics scripts. **Custom** = one `https://` script URL per line, `src` only. **Marketing:** `https` script URLs, one per line, after marketing consent. No arbitrary admin HTML in the public site."
560
- },
561
- {
562
- type: "context",
563
- text: "Privacy / cookie links: use your EmDash **Page** public path (e.g. `/privacy` — same as in the browser address bar when you view that Page) or a full `https://…` URL if the policy is hosted elsewhere."
564
- },
565
- { type: "divider" },
566
- {
567
- type: "form",
568
- blockId: "emprivacy-form",
569
- fields: [
570
- {
571
- type: "text_input",
572
- action_id: "banner_title",
573
- label: "Banner title",
574
- initial_value: cfg.bannerTitle
575
- },
576
- {
577
- type: "text_input",
578
- action_id: "banner_message",
579
- label: "Short notice",
580
- multiline: true,
581
- initial_value: cfg.bannerMessage
582
- },
583
- {
584
- type: "text_input",
585
- action_id: "privacy_url",
586
- label: "Privacy policy — EmDash Page path or https URL",
587
- placeholder: "/privacy or https://…",
588
- initial_value: cfg.privacyPolicyUrl
589
- },
590
- {
591
- type: "text_input",
592
- action_id: "cookie_url",
593
- label: "Cookie policy (optional) — path or https URL",
594
- placeholder: "/cookies or https://…",
595
- initial_value: cfg.cookiePolicyUrl || ""
596
- },
597
- {
598
- type: "text_input",
599
- action_id: "policy_version",
600
- label: "Policy / consent version",
601
- initial_value: cfg.policyVersion
602
- },
603
- {
604
- type: "toggle",
605
- action_id: "strict_defaults",
606
- label: "Strict defaults (require opt-in for analytics & marketing)",
607
- initial_value: cfg.strictDefaults
608
- },
609
- {
610
- type: "radio",
611
- action_id: "analytics_platform",
612
- label: "Analytics platform",
613
- options: [
614
- {
615
- value: "cloudflare",
616
- label: "Cloudflare Web Analytics"
617
- },
618
- {
619
- value: "none",
620
- label: "None"
621
- },
622
- {
623
- value: "custom",
624
- label: "Custom (https script URLs, one per line)"
625
- }
626
- ],
627
- initial_value: platform
628
- },
629
- {
630
- type: "text_input",
631
- action_id: "cloudflare_token",
632
- label: "Cloudflare Web Analytics site token",
633
- placeholder: "Token from Web Analytics in the Cloudflare dashboard",
634
- initial_value: cfg.cloudflareWebAnalyticsToken,
635
- condition: {
636
- field: "analytics_platform",
637
- eq: "cloudflare"
638
- }
639
- },
640
- {
641
- type: "text_input",
642
- action_id: "analytics_urls",
643
- label: "Custom analytics script URLs (one https URL per line, not a <script> tag)",
644
- placeholder: "https://… (one per line)",
645
- multiline: true,
646
- initial_value: analyticsText,
647
- condition: {
648
- field: "analytics_platform",
649
- eq: "custom"
650
- }
651
- },
652
- {
653
- type: "text_input",
654
- action_id: "marketing_urls",
655
- label: "Marketing script URLs (one https URL per line, not a <script> tag)",
656
- placeholder: "https://… (one per line)",
657
- multiline: true,
658
- initial_value: marketingText
659
- },
660
- {
661
- type: "toggle",
662
- action_id: "google_cm",
663
- label: "Google Consent Mode v2 (denied defaults in head; updates after choice)",
664
- initial_value: cfg.googleConsentMode
665
- },
666
- {
667
- type: "toggle",
668
- action_id: "log_server",
669
- label: "Log consent choices to the server (minimal record; no IP stored)",
670
- initial_value: cfg.logConsentToServer
671
- }
672
- ],
673
- submit: {
674
- label: "Save settings",
675
- action_id: SAVE_ACTION_ID
676
- }
677
- },
678
- ...cfg.logConsentToServer && consentFields.length > 0 ? [
679
- { type: "divider" },
680
- {
681
- type: "header",
682
- text: "Recent consent records"
683
- },
684
- {
685
- type: "fields",
686
- fields: consentFields
687
- }
688
- ] : []
689
- ] };
690
- }
691
-
692
- //#endregion
693
- export { sandbox_entry_default as default };