keksi 0.1.4 → 0.1.6
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 +49 -4
- package/dist/config.d.ts +3 -0
- package/dist/index.js +38 -18
- package/dist/playwright.d.ts +1 -1
- package/dist/playwright.js +28 -12
- package/dist/providers.d.ts +4 -2
- package/dist/sveltekit.js +38 -18
- package/dist/types.d.ts +24 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,9 +102,54 @@ posthog.capture('signup');
|
|
|
102
102
|
<keksi-youtube video="aqz-KE-bpKQ" label="Esittelyvideo" poster="/img/poster.jpg"></keksi-youtube>
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
- Shows a placeholder
|
|
106
|
-
-
|
|
107
|
-
-
|
|
105
|
+
- Shows a YouTube-style placeholder. Clicking play is the consent: it's remembered, and all videos load from
|
|
106
|
+
`youtube-nocookie.com` from then on.
|
|
107
|
+
- Marketing consent in the banner covers YouTube too, and turning it off withdraws YouTube.
|
|
108
|
+
- With YouTube as the only provider, the banner doesn't show by itself. The `data-keksi-open` link still opens the
|
|
109
|
+
settings for withdrawing.
|
|
110
|
+
- `poster` is optional and should be self-hosted. YouTube's thumbnails come from `i.ytimg.com`, which would contact
|
|
111
|
+
Google before consent.
|
|
112
|
+
|
|
113
|
+
## Other providers
|
|
114
|
+
|
|
115
|
+
For a provider keksi doesn't have, describe it in `providers`. It's listed in the banner, the settings and the
|
|
116
|
+
policy page, and handled like the built-in ones.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
export default defineConfig({
|
|
120
|
+
ga: 'G-XXXXXXX',
|
|
121
|
+
providers: [
|
|
122
|
+
{
|
|
123
|
+
name: 'Leadfeeder',
|
|
124
|
+
company: 'Dealfront GmbH',
|
|
125
|
+
privacyPolicy: 'https://www.dealfront.com/privacy-policy/',
|
|
126
|
+
category: 'marketing',
|
|
127
|
+
purpose: { fi: 'Vierailevien yritysten tunnistus.', en: 'Identifying visiting companies.' },
|
|
128
|
+
cookies: [{ name: '_lfa', duration: '2y' }],
|
|
129
|
+
hosts: ['lfeeder.com'],
|
|
130
|
+
script: 'https://sc.lfeeder.com/lftracker_v1_XXXX.js',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `script` is loaded after consent in its category.
|
|
137
|
+
- First-party `cookies` are deleted on withdrawal; `*` works as a wildcard. Add `host` to ones set on the provider's
|
|
138
|
+
domain.
|
|
139
|
+
- `hosts` are what `watchTracking` checks for.
|
|
140
|
+
- Adding a provider asks returning visitors again.
|
|
141
|
+
|
|
142
|
+
If the provider needs setup code, leave out `script` and load it yourself:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const load = () => {
|
|
146
|
+
if (window.keksi?.consent?.marketing) {
|
|
147
|
+
// inject the script and run its setup
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
load();
|
|
151
|
+
document.addEventListener('keksi:change', load);
|
|
152
|
+
```
|
|
108
153
|
|
|
109
154
|
## API
|
|
110
155
|
|
|
@@ -166,5 +211,5 @@ pnpm test
|
|
|
166
211
|
pnpm test:e2e # Astro and SvelteKit examples
|
|
167
212
|
```
|
|
168
213
|
|
|
169
|
-
To
|
|
214
|
+
To build in a provider that's used across projects: add its data to `src/providers.ts`, a config key with the same id to `KeksiConfig`, and its
|
|
170
215
|
loading to `load()` in `src/runtime.ts`.
|
package/dist/config.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import type { Category, KeksiConfig, Locale, Provider, RuntimeData } from './typ
|
|
|
3
3
|
export declare function defineConfig(config: KeksiConfig): KeksiConfig;
|
|
4
4
|
export declare function activeProviders(config: KeksiConfig): Provider[];
|
|
5
5
|
export declare function categoriesOf(config: KeksiConfig): Category[];
|
|
6
|
+
/** False when YouTube is the only provider: a click on a video is the consent, so the banner doesn't show by itself. */
|
|
7
|
+
export declare function needsBanner(config: KeksiConfig): boolean;
|
|
8
|
+
export declare function policyOf(config: KeksiConfig, locale: Locale): string | undefined;
|
|
6
9
|
export declare function textsFor(config: KeksiConfig, locale: Locale): Texts;
|
|
7
10
|
/** Changes when a provider is added or removed, which asks everyone again. Ids and texts can change freely. */
|
|
8
11
|
export declare function version(config: KeksiConfig): string;
|
package/dist/index.js
CHANGED
|
@@ -33,9 +33,9 @@ var fi = {
|
|
|
33
33
|
mo: "kuukausi|kuukautta",
|
|
34
34
|
d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
|
|
35
35
|
min: "minuutti|minuuttia",
|
|
36
|
-
ytText: "
|
|
37
|
-
ytButton: "
|
|
38
|
-
ytOpen: "
|
|
36
|
+
ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
|
|
37
|
+
ytButton: "Toista video",
|
|
38
|
+
ytOpen: "Katso YouTubessa"
|
|
39
39
|
};
|
|
40
40
|
var sv = {
|
|
41
41
|
title: "Kakor",
|
|
@@ -71,9 +71,9 @@ var sv = {
|
|
|
71
71
|
mo: "m\xE5nad|m\xE5nader",
|
|
72
72
|
d: "dag|dagar",
|
|
73
73
|
min: "minut|minuter",
|
|
74
|
-
ytText: "
|
|
75
|
-
ytButton: "
|
|
76
|
-
ytOpen: "
|
|
74
|
+
ytText: "Genom att spela upp videon godk\xE4nner du kakor fr\xE5n YouTube.",
|
|
75
|
+
ytButton: "Spela upp video",
|
|
76
|
+
ytOpen: "Titta p\xE5 YouTube"
|
|
77
77
|
};
|
|
78
78
|
var en = {
|
|
79
79
|
title: "Cookies",
|
|
@@ -109,9 +109,9 @@ var en = {
|
|
|
109
109
|
mo: "month|months",
|
|
110
110
|
d: "day|days",
|
|
111
111
|
min: "minute|minutes",
|
|
112
|
-
ytText: "
|
|
113
|
-
ytButton: "
|
|
114
|
-
ytOpen: "
|
|
112
|
+
ytText: "By playing the video, you accept cookies from YouTube.",
|
|
113
|
+
ytButton: "Play video",
|
|
114
|
+
ytOpen: "Watch on YouTube"
|
|
115
115
|
};
|
|
116
116
|
var texts = { fi, sv, en };
|
|
117
117
|
|
|
@@ -230,11 +230,21 @@ var posthogHost = (config) => (config.posthog?.host ?? "https://eu.i.posthog.com
|
|
|
230
230
|
function activeProviders(config) {
|
|
231
231
|
return providers.filter(
|
|
232
232
|
(p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
|
|
233
|
-
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
|
|
233
|
+
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p).concat((config.providers ?? []).map(custom));
|
|
234
|
+
}
|
|
235
|
+
function custom({ purpose, hosts = [], script: _, ...p }) {
|
|
236
|
+
const text = (l) => typeof purpose === "string" ? purpose : purpose[l] ?? Object.values(purpose).find(Boolean) ?? "";
|
|
237
|
+
return { ...p, id: `custom:${p.name}`, purpose: { fi: text("fi"), sv: text("sv"), en: text("en") }, hosts };
|
|
234
238
|
}
|
|
235
239
|
function categoriesOf(config) {
|
|
236
240
|
return ["analytics", "marketing"].filter((c) => activeProviders(config).some((p) => p.category === c));
|
|
237
241
|
}
|
|
242
|
+
function needsBanner(config) {
|
|
243
|
+
return activeProviders(config).some((p) => p.id !== "youtube");
|
|
244
|
+
}
|
|
245
|
+
function policyOf(config, locale) {
|
|
246
|
+
return typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[locale];
|
|
247
|
+
}
|
|
238
248
|
function textsFor(config, locale) {
|
|
239
249
|
return { ...texts[locale], ...config.texts?.[locale] };
|
|
240
250
|
}
|
|
@@ -256,9 +266,15 @@ function runtimeData(config, locale) {
|
|
|
256
266
|
ads: list(config.googleAds),
|
|
257
267
|
meta: list(config.meta),
|
|
258
268
|
ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
|
|
269
|
+
scripts: {
|
|
270
|
+
analytics: (config.providers ?? []).filter((p) => p.script && p.category === "analytics").map((p) => p.script),
|
|
271
|
+
marketing: (config.providers ?? []).filter((p) => p.script && p.category === "marketing").map((p) => p.script)
|
|
272
|
+
},
|
|
259
273
|
yt: !!config.youtube,
|
|
274
|
+
banner: needsBanner(config),
|
|
275
|
+
policy: policyOf(config, locale),
|
|
260
276
|
del: { analytics: del("analytics"), marketing: del("marketing") },
|
|
261
|
-
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
|
|
277
|
+
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
|
|
262
278
|
};
|
|
263
279
|
}
|
|
264
280
|
|
|
@@ -304,12 +320,16 @@ var css = [
|
|
|
304
320
|
":where(.keksi-cat table){width:100%;border-collapse:collapse;font-size:.8125rem;margin-top:.5rem}",
|
|
305
321
|
":where(.keksi-cat) :where(th,td){padding:.3rem .75rem .3rem 0;border-bottom:1px solid var(--_line);text-align:left;vertical-align:top}",
|
|
306
322
|
":where(.keksi-cat) :where(th){font-weight:600}",
|
|
307
|
-
":where(keksi-youtube){display:block;aspect-ratio:16/9;background:#
|
|
323
|
+
":where(keksi-youtube){position:relative;display:block;aspect-ratio:16/9;overflow:hidden;border-radius:12px;background:#0f0f0f;color:#fff;font-family:Roboto,Arial,sans-serif}",
|
|
308
324
|
":where(keksi-youtube iframe){display:block;width:100%;height:100%;border:0}",
|
|
309
|
-
":where(.keksi-yt){
|
|
310
|
-
":where(.keksi-yt) :
|
|
311
|
-
":where(.keksi-yt) :where(
|
|
312
|
-
":where(.keksi-yt
|
|
325
|
+
":where(keksi-youtube[label]:has(.keksi-yt))::before{content:attr(label);position:absolute;inset:0 0 auto;z-index:1;padding:1rem 1.25rem 2.5rem;font-size:1.125rem;font-weight:500;background:linear-gradient(#000b,#0000);pointer-events:none}",
|
|
326
|
+
":where(.keksi-yt){position:relative;display:grid;grid-template-rows:1fr auto auto;gap:.25rem;height:100%;padding:0 1.25rem 1rem;box-sizing:border-box;font-size:.875rem;line-height:1.45;background:linear-gradient(#0000 55%,#000c),center/cover var(--keksi-poster,none)}",
|
|
327
|
+
":where(.keksi-yt) :where(p){margin:0}",
|
|
328
|
+
":where(.keksi-yt) :where(p:first-of-type){grid-row:2}",
|
|
329
|
+
":where(.keksi-yt) :where(p:last-of-type){grid-row:3;color:#fffc}",
|
|
330
|
+
":where(.keksi-yt) :where(a){color:inherit;text-decoration:underline;text-underline-offset:2px}",
|
|
331
|
+
`:where(.keksi-yt button){position:absolute;inset:0;width:68px;height:48px;margin:auto;padding:0;border:0;font-size:0;color:transparent;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 48'%3E%3Cpath d='M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z' fill='%23f00'/%3E%3Cpath d='M45 24 27 14v20' fill='%23fff'/%3E%3C/svg%3E") center/contain no-repeat;cursor:pointer;transition:transform .15s}`,
|
|
332
|
+
":where(.keksi-yt button:hover){transform:scale(1.08)}"
|
|
313
333
|
].join("");
|
|
314
334
|
|
|
315
335
|
// src/render.ts
|
|
@@ -319,12 +339,12 @@ function renderKeksi(config, { lang = "fi", nonce } = {}) {
|
|
|
319
339
|
if (!cats.length) return "";
|
|
320
340
|
const t = textsFor(config, lang);
|
|
321
341
|
const active = activeProviders(config);
|
|
322
|
-
const policy =
|
|
342
|
+
const policy = policyOf(config, lang);
|
|
323
343
|
const n = nonce ? ` nonce="${e(nonce)}"` : "";
|
|
324
344
|
const btn = (action, label, type = "button") => `<button type="${type}" class="keksi-btn" data-keksi="${action}">${e(label)}</button>`;
|
|
325
345
|
const uses = cats.map((c) => `<li><strong>${e(t[c])}:</strong> ${e(active.filter((p) => p.category === c).map((p) => p.name).join(", "))}</li>`).join("");
|
|
326
346
|
const json = JSON.stringify(runtimeData(config, lang)).replace(/</g, "\\u003c");
|
|
327
|
-
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><div class="keksi-main"><div class="keksi-head"><h2 id="keksi-title">${e(t.title)}</h2></div><div class="keksi-body"><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.change)}${policy ? ` <a href="${e(policy)}">${e(t.more)}</a>` : ""}</p></div><div class="keksi-actions"><button type="button" class="keksi-text" data-keksi-open>${e(t.settings)}</button>${btn("reject", t.reject)}${btn("accept", t.accept)}</div></div><form id="keksi-settings" class="keksi-settings" aria-labelledby="keksi-settings-title" hidden><div class="keksi-head"><h2 id="keksi-settings-title">${e(t.settingsTitle)}</h2><button type="button" class="keksi-close" data-keksi="close" aria-label="${e(t.close)}">\xD7</button></div><div class="keksi-body">${sections(config, lang, 3, true)}</div><div class="keksi-actions">${btn("save", t.save)}${btn("reject", t.reject)}${btn("accept", t.accept)}</div></form></section><script type="application/json" id="keksi-data">${json}</script><script${n}>${'"use strict";(()=>{var
|
|
347
|
+
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><div class="keksi-main"><div class="keksi-head"><h2 id="keksi-title">${e(t.title)}</h2></div><div class="keksi-body"><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.change)}${policy ? ` <a href="${e(policy)}">${e(t.more)}</a>` : ""}</p></div><div class="keksi-actions"><button type="button" class="keksi-text" data-keksi-open>${e(t.settings)}</button>${btn("reject", t.reject)}${btn("accept", t.accept)}</div></div><form id="keksi-settings" class="keksi-settings" aria-labelledby="keksi-settings-title" hidden><div class="keksi-head"><h2 id="keksi-settings-title">${e(t.settingsTitle)}</h2><button type="button" class="keksi-close" data-keksi="close" aria-label="${e(t.close)}">\xD7</button></div><div class="keksi-body">${sections(config, lang, 3, true)}</div><div class="keksi-actions">${btn("save", t.save)}${btn("reject", t.reject)}${btn("accept", t.accept)}</div></form></section><script type="application/json" id="keksi-data">${json}</script><script${n}>${'"use strict";(()=>{var H=(n,s)=>new RegExp(`^${n.split("*").map(i=>i.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&")).join(".*")}$`).test(s);var E=["analytics","marketing"],$={analytics:!1,marketing:!1};function f(...n){return arguments}function I(n,s=window){if(typeof s.keksi?.sync=="function")return s.keksi.sync(),s.keksi;let i=s.document,g=e=>i.getElementById(e),c=O(),A=!1,w=!1,L=!1,S=new Set,x=new Set,T=f("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied"});if(n.ga.length||n.ads.length){let e=s.dataLayer??(s.dataLayer=[]);s.gtag=function(){e.push(arguments)},e.push(T)}if(n.meta.length){let e=function(){e.callMethod?e.callMethod.apply(e,[...arguments]):e.queue.push(arguments)};Object.assign(e,{push:e,loaded:!0,version:"2.0",queue:[]}),s.fbq=s._fbq=e}if(n.ph){let e=Object.assign([],{_i:[],people:[]});for(let o of"capture identify alias reset register register_once unregister group setPersonProperties getFeatureFlag isFeatureEnabled onFeatureFlags reloadFeatureFlags get_distinct_id startSessionRecording stopSessionRecording captureException on".split(" "))e[o]=(...t)=>e.push([o,...t]);s.posthog=e}function O(){let e=i.cookie.split("; ").find(l=>l.startsWith(`${n.name}=`))?.slice(n.name.length+1),[o,,t,a]=e?.split(".")??[];if(o!==n.v||!t||!a)return null;let r={analytics:a.includes("a"),marketing:a.includes("m")};return{id:t,c:r,yt:r.marketing||a.includes("y"),done:!a.includes("u")}}function M(e,o=e.marketing,t=!0){let a=c?.id??Array.from(s.crypto.getRandomValues(new Uint8Array(8)),l=>l.toString(16).padStart(2,"0")).join(""),r=(t?"":"u")+(e.analytics?"a":"")+(e.marketing?"m":"")+(o&&!e.marketing?"y":"")||"-";return i.cookie=`${n.name}=${n.v}.${Date.now().toString(36)}.${a}.${r}; Path=/; Max-Age=${n.days*86400}; SameSite=Lax`+(n.domain?`; Domain=${n.domain}`:"")+(s.location.protocol==="https:"?"; Secure":""),{id:a,c:e,yt:o,done:t}}function h(e){let o=i.createElement("script");return o.async=!0,o.src=e,i.head.append(o),o}function C(e){let o=[...e.analytics?n.ga:[],...e.marketing?n.ads:[]].filter(t=>!S.has(t));if(o.length){let t=e.marketing?"granted":"denied",a=[f("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:t,ad_user_data:t,ad_personalization:t})];S.size||a.push(f("js",new Date));for(let r of o)a.push(f("config",r)),S.add(r);if(A)for(let r of a)s.dataLayer.push(r);else{let r=s.dataLayer;r.splice(r.indexOf(T)+1,0,...a),h(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(o[0])}`),A=!0}}e.marketing&&n.meta.length&&!w&&(w=!0,s.fbq.queue.unshift(...n.meta.map(t=>f("init",t)),f("track","PageView")),h("https://connect.facebook.net/en_US/fbevents.js")),e.analytics&&n.ph&&!L&&(L=!0,s.posthog._i.push([n.ph.key,{api_host:n.ph.host,defaults:"2026-08-30"},"posthog"]),h(`${n.ph.host.replace(".i.posthog.com","-assets.i.posthog.com")}/static/array.js`).crossOrigin="anonymous");for(let t of E.flatMap(a=>e[a]?n.scripts[a]:[]))x.has(t)||(x.add(t),h(t));if(c?.yt)for(let t of i.querySelectorAll("keksi-youtube"))t.show(!0)}function R(){c?.yt||(c=M(c?.c??$,!0,c?.done??!1)),C(c.c)}function D(e){let o=e.flatMap(l=>n.del[l]),t=l=>o.some(u=>H(u,l)),a=s.location.hostname.split("."),r=["",...a.map((l,u)=>`; Domain=${a.slice(u).join(".")}`)];for(let l of i.cookie.split("; ")){let u=l.split("=")[0];if(t(u))for(let v of r)i.cookie=`${u}=; Max-Age=0; Path=/${v}`}try{for(let l of[s.localStorage,s.sessionStorage])for(let u of Object.keys(l))t(u)&&l.removeItem(u)}catch{}}function p(e){let o=c;c=M(e),b(),i.dispatchEvent(new s.CustomEvent("keksi:change",{detail:{...e}})),D(E.filter(t=>!e[t])),E.some(t=>o?.c[t]&&!e[t])||o?.yt&&!c.yt?s.location.reload():C(e)}function y(e=!1,o=n.banner&&!c?.done||e){let t=g("keksi-banner"),a=g("keksi-settings");!t||!a||(t.hidden=!o,a.hidden=!e,t.firstElementChild.hidden=e)}function b(){y()}function j(){let e=g("keksi-settings");for(let o of E){let t=e?.querySelector(`[name=${o}]`);t&&(t.checked=!!c?.c[o])}y(!0),e?.querySelector(".keksi-close")?.focus()}i.addEventListener("click",e=>{let o=e.target.closest?.("[data-keksi],[data-keksi-open]");if(!o)return;if(o.hasAttribute("data-keksi-open"))return e.preventDefault(),j();let t=o.getAttribute("data-keksi");if(t==="close")y();else if(t==="accept")p({analytics:!0,marketing:!0});else if(t==="reject")p($);else if(t==="save"){let a=r=>!!g("keksi-settings")?.querySelector(`[name=${r}]`)?.checked;p({analytics:a("analytics"),marketing:a("marketing")})}}),i.addEventListener("keydown",e=>{e.key==="Escape"&&g("keksi-settings")?.hidden===!1&&y()}),i.addEventListener("astro:after-swap",b);class N extends s.HTMLElement{connectedCallback(){this.show(!!c?.yt)}show(o,t=!1){let a=encodeURIComponent(this.getAttribute("video")??""),r=this.getAttribute("label");if(o){if(this.querySelector("iframe"))return;let d=i.createElement("iframe");d.src=`https://www.youtube-nocookie.com/embed/${a}${t?"?autoplay=1":""}`,d.title=r??"YouTube",d.allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",d.allowFullscreen=!0,d.referrerPolicy="strict-origin-when-cross-origin",this.replaceChildren(d),t&&d.focus();return}let l=i.createElement("div");l.className="keksi-yt";let u=this.getAttribute("poster");u&&l.style.setProperty("--keksi-poster",`url(${JSON.stringify(u)})`);let v=i.createElement("p");v.textContent=n.t.ytText;let m=i.createElement("button");m.type="button",m.textContent=n.t.ytButton,r&&m.setAttribute("aria-label",`${n.t.ytButton}: ${r}`),m.onclick=()=>{this.show(!0,!0),R()};let _=i.createElement("p"),F=(d,P,B=!1)=>{let k=i.createElement("a");k.href=d,k.textContent=P,B&&(k.target="_blank",k.rel="noopener"),_.append(_.childNodes.length?" \\xB7 ":"",k)};F(`https://www.youtube.com/watch?v=${a}`,n.t.ytOpen,!0),n.policy&&F(n.policy,n.t.more),l.append(v,m,_),this.replaceChildren(l)}}n.yt&&!s.customElements.get("keksi-youtube")&&s.customElements.define("keksi-youtube",N);let q={get consent(){return c?.done?{...c.c}:null},open:j,save:e=>p({...c?.c??$,...e}),acceptAll:()=>p({analytics:!0,marketing:!0}),rejectAll:()=>p($),sync:b};return s.keksi=q,b(),c&&C(c.c),q}I(JSON.parse(document.getElementById("keksi-data").textContent));})();'}</script>`;
|
|
328
348
|
}
|
|
329
349
|
function cookieTable(config, { lang = "fi", headingLevel = 2 } = {}) {
|
|
330
350
|
return `<div class="keksi-policy">${sections(config, lang, headingLevel, false)}</div>`;
|
package/dist/playwright.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface TrackingOptions {
|
|
|
5
5
|
allow?: string[];
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Records requests to
|
|
8
|
+
* Records requests to the built-in and configured providers from now on. `check()` throws when any were made or when the page
|
|
9
9
|
* stored anything besides the consent cookie and `allow`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function watchTracking(page: Page, config: KeksiConfig, { allow }?: TrackingOptions): {
|
package/dist/playwright.js
CHANGED
|
@@ -33,9 +33,9 @@ var fi = {
|
|
|
33
33
|
mo: "kuukausi|kuukautta",
|
|
34
34
|
d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
|
|
35
35
|
min: "minuutti|minuuttia",
|
|
36
|
-
ytText: "
|
|
37
|
-
ytButton: "
|
|
38
|
-
ytOpen: "
|
|
36
|
+
ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
|
|
37
|
+
ytButton: "Toista video",
|
|
38
|
+
ytOpen: "Katso YouTubessa"
|
|
39
39
|
};
|
|
40
40
|
var sv = {
|
|
41
41
|
title: "Kakor",
|
|
@@ -71,9 +71,9 @@ var sv = {
|
|
|
71
71
|
mo: "m\xE5nad|m\xE5nader",
|
|
72
72
|
d: "dag|dagar",
|
|
73
73
|
min: "minut|minuter",
|
|
74
|
-
ytText: "
|
|
75
|
-
ytButton: "
|
|
76
|
-
ytOpen: "
|
|
74
|
+
ytText: "Genom att spela upp videon godk\xE4nner du kakor fr\xE5n YouTube.",
|
|
75
|
+
ytButton: "Spela upp video",
|
|
76
|
+
ytOpen: "Titta p\xE5 YouTube"
|
|
77
77
|
};
|
|
78
78
|
var en = {
|
|
79
79
|
title: "Cookies",
|
|
@@ -109,9 +109,9 @@ var en = {
|
|
|
109
109
|
mo: "month|months",
|
|
110
110
|
d: "day|days",
|
|
111
111
|
min: "minute|minutes",
|
|
112
|
-
ytText: "
|
|
113
|
-
ytButton: "
|
|
114
|
-
ytOpen: "
|
|
112
|
+
ytText: "By playing the video, you accept cookies from YouTube.",
|
|
113
|
+
ytButton: "Play video",
|
|
114
|
+
ytOpen: "Watch on YouTube"
|
|
115
115
|
};
|
|
116
116
|
var texts = { fi, sv, en };
|
|
117
117
|
|
|
@@ -227,7 +227,17 @@ var posthogHost = (config) => (config.posthog?.host ?? "https://eu.i.posthog.com
|
|
|
227
227
|
function activeProviders(config) {
|
|
228
228
|
return providers.filter(
|
|
229
229
|
(p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
|
|
230
|
-
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
|
|
230
|
+
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p).concat((config.providers ?? []).map(custom));
|
|
231
|
+
}
|
|
232
|
+
function custom({ purpose, hosts = [], script: _, ...p }) {
|
|
233
|
+
const text = (l) => typeof purpose === "string" ? purpose : purpose[l] ?? Object.values(purpose).find(Boolean) ?? "";
|
|
234
|
+
return { ...p, id: `custom:${p.name}`, purpose: { fi: text("fi"), sv: text("sv"), en: text("en") }, hosts };
|
|
235
|
+
}
|
|
236
|
+
function needsBanner(config) {
|
|
237
|
+
return activeProviders(config).some((p) => p.id !== "youtube");
|
|
238
|
+
}
|
|
239
|
+
function policyOf(config, locale) {
|
|
240
|
+
return typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[locale];
|
|
231
241
|
}
|
|
232
242
|
function textsFor(config, locale) {
|
|
233
243
|
return { ...texts[locale], ...config.texts?.[locale] };
|
|
@@ -250,15 +260,21 @@ function runtimeData(config, locale) {
|
|
|
250
260
|
ads: list(config.googleAds),
|
|
251
261
|
meta: list(config.meta),
|
|
252
262
|
ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
|
|
263
|
+
scripts: {
|
|
264
|
+
analytics: (config.providers ?? []).filter((p) => p.script && p.category === "analytics").map((p) => p.script),
|
|
265
|
+
marketing: (config.providers ?? []).filter((p) => p.script && p.category === "marketing").map((p) => p.script)
|
|
266
|
+
},
|
|
253
267
|
yt: !!config.youtube,
|
|
268
|
+
banner: needsBanner(config),
|
|
269
|
+
policy: policyOf(config, locale),
|
|
254
270
|
del: { analytics: del("analytics"), marketing: del("marketing") },
|
|
255
|
-
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
|
|
271
|
+
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
|
|
256
272
|
};
|
|
257
273
|
}
|
|
258
274
|
|
|
259
275
|
// src/playwright.ts
|
|
260
|
-
var trackerHosts = [...new Set(providers.flatMap((p) => p.hosts))];
|
|
261
276
|
function watchTracking(page, config, { allow = [] } = {}) {
|
|
277
|
+
const trackerHosts = [...providers, ...config.providers ?? []].flatMap((p) => p.hosts ?? []);
|
|
262
278
|
const requests = [];
|
|
263
279
|
page.on("request", (r) => {
|
|
264
280
|
const host = new URL(r.url()).hostname;
|
package/dist/providers.d.ts
CHANGED
package/dist/sveltekit.js
CHANGED
|
@@ -33,9 +33,9 @@ var fi = {
|
|
|
33
33
|
mo: "kuukausi|kuukautta",
|
|
34
34
|
d: "p\xE4iv\xE4|p\xE4iv\xE4\xE4",
|
|
35
35
|
min: "minuutti|minuuttia",
|
|
36
|
-
ytText: "
|
|
37
|
-
ytButton: "
|
|
38
|
-
ytOpen: "
|
|
36
|
+
ytText: "Toistamalla videon hyv\xE4ksyt YouTuben ev\xE4steet.",
|
|
37
|
+
ytButton: "Toista video",
|
|
38
|
+
ytOpen: "Katso YouTubessa"
|
|
39
39
|
};
|
|
40
40
|
var sv = {
|
|
41
41
|
title: "Kakor",
|
|
@@ -71,9 +71,9 @@ var sv = {
|
|
|
71
71
|
mo: "m\xE5nad|m\xE5nader",
|
|
72
72
|
d: "dag|dagar",
|
|
73
73
|
min: "minut|minuter",
|
|
74
|
-
ytText: "
|
|
75
|
-
ytButton: "
|
|
76
|
-
ytOpen: "
|
|
74
|
+
ytText: "Genom att spela upp videon godk\xE4nner du kakor fr\xE5n YouTube.",
|
|
75
|
+
ytButton: "Spela upp video",
|
|
76
|
+
ytOpen: "Titta p\xE5 YouTube"
|
|
77
77
|
};
|
|
78
78
|
var en = {
|
|
79
79
|
title: "Cookies",
|
|
@@ -109,9 +109,9 @@ var en = {
|
|
|
109
109
|
mo: "month|months",
|
|
110
110
|
d: "day|days",
|
|
111
111
|
min: "minute|minutes",
|
|
112
|
-
ytText: "
|
|
113
|
-
ytButton: "
|
|
114
|
-
ytOpen: "
|
|
112
|
+
ytText: "By playing the video, you accept cookies from YouTube.",
|
|
113
|
+
ytButton: "Play video",
|
|
114
|
+
ytOpen: "Watch on YouTube"
|
|
115
115
|
};
|
|
116
116
|
var texts = { fi, sv, en };
|
|
117
117
|
|
|
@@ -227,11 +227,21 @@ var posthogHost = (config) => (config.posthog?.host ?? "https://eu.i.posthog.com
|
|
|
227
227
|
function activeProviders(config) {
|
|
228
228
|
return providers.filter(
|
|
229
229
|
(p) => p.id === "youtube" ? config.youtube : p.id === "posthog" ? config.posthog?.key : list(config[p.id]).length
|
|
230
|
-
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p);
|
|
230
|
+
).map((p) => p.id === "posthog" && /\/\/us\./.test(posthogHost(config)) ? { ...p, transfer: "us" } : p).concat((config.providers ?? []).map(custom));
|
|
231
|
+
}
|
|
232
|
+
function custom({ purpose, hosts = [], script: _, ...p }) {
|
|
233
|
+
const text = (l) => typeof purpose === "string" ? purpose : purpose[l] ?? Object.values(purpose).find(Boolean) ?? "";
|
|
234
|
+
return { ...p, id: `custom:${p.name}`, purpose: { fi: text("fi"), sv: text("sv"), en: text("en") }, hosts };
|
|
231
235
|
}
|
|
232
236
|
function categoriesOf(config) {
|
|
233
237
|
return ["analytics", "marketing"].filter((c) => activeProviders(config).some((p) => p.category === c));
|
|
234
238
|
}
|
|
239
|
+
function needsBanner(config) {
|
|
240
|
+
return activeProviders(config).some((p) => p.id !== "youtube");
|
|
241
|
+
}
|
|
242
|
+
function policyOf(config, locale) {
|
|
243
|
+
return typeof config.policyUrl === "string" ? config.policyUrl : config.policyUrl?.[locale];
|
|
244
|
+
}
|
|
235
245
|
function textsFor(config, locale) {
|
|
236
246
|
return { ...texts[locale], ...config.texts?.[locale] };
|
|
237
247
|
}
|
|
@@ -253,9 +263,15 @@ function runtimeData(config, locale) {
|
|
|
253
263
|
ads: list(config.googleAds),
|
|
254
264
|
meta: list(config.meta),
|
|
255
265
|
ph: config.posthog?.key ? { key: config.posthog.key, host: posthogHost(config) } : void 0,
|
|
266
|
+
scripts: {
|
|
267
|
+
analytics: (config.providers ?? []).filter((p) => p.script && p.category === "analytics").map((p) => p.script),
|
|
268
|
+
marketing: (config.providers ?? []).filter((p) => p.script && p.category === "marketing").map((p) => p.script)
|
|
269
|
+
},
|
|
256
270
|
yt: !!config.youtube,
|
|
271
|
+
banner: needsBanner(config),
|
|
272
|
+
policy: policyOf(config, locale),
|
|
257
273
|
del: { analytics: del("analytics"), marketing: del("marketing") },
|
|
258
|
-
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen }
|
|
274
|
+
t: { ytText: t.ytText, ytButton: t.ytButton, ytOpen: t.ytOpen, more: t.more }
|
|
259
275
|
};
|
|
260
276
|
}
|
|
261
277
|
|
|
@@ -301,12 +317,16 @@ var css = [
|
|
|
301
317
|
":where(.keksi-cat table){width:100%;border-collapse:collapse;font-size:.8125rem;margin-top:.5rem}",
|
|
302
318
|
":where(.keksi-cat) :where(th,td){padding:.3rem .75rem .3rem 0;border-bottom:1px solid var(--_line);text-align:left;vertical-align:top}",
|
|
303
319
|
":where(.keksi-cat) :where(th){font-weight:600}",
|
|
304
|
-
":where(keksi-youtube){display:block;aspect-ratio:16/9;background:#
|
|
320
|
+
":where(keksi-youtube){position:relative;display:block;aspect-ratio:16/9;overflow:hidden;border-radius:12px;background:#0f0f0f;color:#fff;font-family:Roboto,Arial,sans-serif}",
|
|
305
321
|
":where(keksi-youtube iframe){display:block;width:100%;height:100%;border:0}",
|
|
306
|
-
":where(.keksi-yt){
|
|
307
|
-
":where(.keksi-yt) :
|
|
308
|
-
":where(.keksi-yt) :where(
|
|
309
|
-
":where(.keksi-yt
|
|
322
|
+
":where(keksi-youtube[label]:has(.keksi-yt))::before{content:attr(label);position:absolute;inset:0 0 auto;z-index:1;padding:1rem 1.25rem 2.5rem;font-size:1.125rem;font-weight:500;background:linear-gradient(#000b,#0000);pointer-events:none}",
|
|
323
|
+
":where(.keksi-yt){position:relative;display:grid;grid-template-rows:1fr auto auto;gap:.25rem;height:100%;padding:0 1.25rem 1rem;box-sizing:border-box;font-size:.875rem;line-height:1.45;background:linear-gradient(#0000 55%,#000c),center/cover var(--keksi-poster,none)}",
|
|
324
|
+
":where(.keksi-yt) :where(p){margin:0}",
|
|
325
|
+
":where(.keksi-yt) :where(p:first-of-type){grid-row:2}",
|
|
326
|
+
":where(.keksi-yt) :where(p:last-of-type){grid-row:3;color:#fffc}",
|
|
327
|
+
":where(.keksi-yt) :where(a){color:inherit;text-decoration:underline;text-underline-offset:2px}",
|
|
328
|
+
`:where(.keksi-yt button){position:absolute;inset:0;width:68px;height:48px;margin:auto;padding:0;border:0;font-size:0;color:transparent;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 48'%3E%3Cpath d='M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z' fill='%23f00'/%3E%3Cpath d='M45 24 27 14v20' fill='%23fff'/%3E%3C/svg%3E") center/contain no-repeat;cursor:pointer;transition:transform .15s}`,
|
|
329
|
+
":where(.keksi-yt button:hover){transform:scale(1.08)}"
|
|
310
330
|
].join("");
|
|
311
331
|
|
|
312
332
|
// src/render.ts
|
|
@@ -316,12 +336,12 @@ function renderKeksi(config, { lang = "fi", nonce } = {}) {
|
|
|
316
336
|
if (!cats.length) return "";
|
|
317
337
|
const t = textsFor(config, lang);
|
|
318
338
|
const active = activeProviders(config);
|
|
319
|
-
const policy =
|
|
339
|
+
const policy = policyOf(config, lang);
|
|
320
340
|
const n = nonce ? ` nonce="${e(nonce)}"` : "";
|
|
321
341
|
const btn = (action, label, type = "button") => `<button type="${type}" class="keksi-btn" data-keksi="${action}">${e(label)}</button>`;
|
|
322
342
|
const uses = cats.map((c) => `<li><strong>${e(t[c])}:</strong> ${e(active.filter((p) => p.category === c).map((p) => p.name).join(", "))}</li>`).join("");
|
|
323
343
|
const json = JSON.stringify(runtimeData(config, lang)).replace(/</g, "\\u003c");
|
|
324
|
-
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><div class="keksi-main"><div class="keksi-head"><h2 id="keksi-title">${e(t.title)}</h2></div><div class="keksi-body"><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.change)}${policy ? ` <a href="${e(policy)}">${e(t.more)}</a>` : ""}</p></div><div class="keksi-actions"><button type="button" class="keksi-text" data-keksi-open>${e(t.settings)}</button>${btn("reject", t.reject)}${btn("accept", t.accept)}</div></div><form id="keksi-settings" class="keksi-settings" aria-labelledby="keksi-settings-title" hidden><div class="keksi-head"><h2 id="keksi-settings-title">${e(t.settingsTitle)}</h2><button type="button" class="keksi-close" data-keksi="close" aria-label="${e(t.close)}">\xD7</button></div><div class="keksi-body">${sections(config, lang, 3, true)}</div><div class="keksi-actions">${btn("save", t.save)}${btn("reject", t.reject)}${btn("accept", t.accept)}</div></form></section><script type="application/json" id="keksi-data">${json}</script><script${n}>${'"use strict";(()=>{var
|
|
344
|
+
return `<style${n}>${css}</style><section id="keksi-banner" class="keksi" aria-labelledby="keksi-title" hidden><div class="keksi-main"><div class="keksi-head"><h2 id="keksi-title">${e(t.title)}</h2></div><div class="keksi-body"><p>${e(t.intro)}</p><ul>${uses}</ul><p>${e(t.change)}${policy ? ` <a href="${e(policy)}">${e(t.more)}</a>` : ""}</p></div><div class="keksi-actions"><button type="button" class="keksi-text" data-keksi-open>${e(t.settings)}</button>${btn("reject", t.reject)}${btn("accept", t.accept)}</div></div><form id="keksi-settings" class="keksi-settings" aria-labelledby="keksi-settings-title" hidden><div class="keksi-head"><h2 id="keksi-settings-title">${e(t.settingsTitle)}</h2><button type="button" class="keksi-close" data-keksi="close" aria-label="${e(t.close)}">\xD7</button></div><div class="keksi-body">${sections(config, lang, 3, true)}</div><div class="keksi-actions">${btn("save", t.save)}${btn("reject", t.reject)}${btn("accept", t.accept)}</div></form></section><script type="application/json" id="keksi-data">${json}</script><script${n}>${'"use strict";(()=>{var H=(n,s)=>new RegExp(`^${n.split("*").map(i=>i.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&")).join(".*")}$`).test(s);var E=["analytics","marketing"],$={analytics:!1,marketing:!1};function f(...n){return arguments}function I(n,s=window){if(typeof s.keksi?.sync=="function")return s.keksi.sync(),s.keksi;let i=s.document,g=e=>i.getElementById(e),c=O(),A=!1,w=!1,L=!1,S=new Set,x=new Set,T=f("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied"});if(n.ga.length||n.ads.length){let e=s.dataLayer??(s.dataLayer=[]);s.gtag=function(){e.push(arguments)},e.push(T)}if(n.meta.length){let e=function(){e.callMethod?e.callMethod.apply(e,[...arguments]):e.queue.push(arguments)};Object.assign(e,{push:e,loaded:!0,version:"2.0",queue:[]}),s.fbq=s._fbq=e}if(n.ph){let e=Object.assign([],{_i:[],people:[]});for(let o of"capture identify alias reset register register_once unregister group setPersonProperties getFeatureFlag isFeatureEnabled onFeatureFlags reloadFeatureFlags get_distinct_id startSessionRecording stopSessionRecording captureException on".split(" "))e[o]=(...t)=>e.push([o,...t]);s.posthog=e}function O(){let e=i.cookie.split("; ").find(l=>l.startsWith(`${n.name}=`))?.slice(n.name.length+1),[o,,t,a]=e?.split(".")??[];if(o!==n.v||!t||!a)return null;let r={analytics:a.includes("a"),marketing:a.includes("m")};return{id:t,c:r,yt:r.marketing||a.includes("y"),done:!a.includes("u")}}function M(e,o=e.marketing,t=!0){let a=c?.id??Array.from(s.crypto.getRandomValues(new Uint8Array(8)),l=>l.toString(16).padStart(2,"0")).join(""),r=(t?"":"u")+(e.analytics?"a":"")+(e.marketing?"m":"")+(o&&!e.marketing?"y":"")||"-";return i.cookie=`${n.name}=${n.v}.${Date.now().toString(36)}.${a}.${r}; Path=/; Max-Age=${n.days*86400}; SameSite=Lax`+(n.domain?`; Domain=${n.domain}`:"")+(s.location.protocol==="https:"?"; Secure":""),{id:a,c:e,yt:o,done:t}}function h(e){let o=i.createElement("script");return o.async=!0,o.src=e,i.head.append(o),o}function C(e){let o=[...e.analytics?n.ga:[],...e.marketing?n.ads:[]].filter(t=>!S.has(t));if(o.length){let t=e.marketing?"granted":"denied",a=[f("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:t,ad_user_data:t,ad_personalization:t})];S.size||a.push(f("js",new Date));for(let r of o)a.push(f("config",r)),S.add(r);if(A)for(let r of a)s.dataLayer.push(r);else{let r=s.dataLayer;r.splice(r.indexOf(T)+1,0,...a),h(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(o[0])}`),A=!0}}e.marketing&&n.meta.length&&!w&&(w=!0,s.fbq.queue.unshift(...n.meta.map(t=>f("init",t)),f("track","PageView")),h("https://connect.facebook.net/en_US/fbevents.js")),e.analytics&&n.ph&&!L&&(L=!0,s.posthog._i.push([n.ph.key,{api_host:n.ph.host,defaults:"2026-08-30"},"posthog"]),h(`${n.ph.host.replace(".i.posthog.com","-assets.i.posthog.com")}/static/array.js`).crossOrigin="anonymous");for(let t of E.flatMap(a=>e[a]?n.scripts[a]:[]))x.has(t)||(x.add(t),h(t));if(c?.yt)for(let t of i.querySelectorAll("keksi-youtube"))t.show(!0)}function R(){c?.yt||(c=M(c?.c??$,!0,c?.done??!1)),C(c.c)}function D(e){let o=e.flatMap(l=>n.del[l]),t=l=>o.some(u=>H(u,l)),a=s.location.hostname.split("."),r=["",...a.map((l,u)=>`; Domain=${a.slice(u).join(".")}`)];for(let l of i.cookie.split("; ")){let u=l.split("=")[0];if(t(u))for(let v of r)i.cookie=`${u}=; Max-Age=0; Path=/${v}`}try{for(let l of[s.localStorage,s.sessionStorage])for(let u of Object.keys(l))t(u)&&l.removeItem(u)}catch{}}function p(e){let o=c;c=M(e),b(),i.dispatchEvent(new s.CustomEvent("keksi:change",{detail:{...e}})),D(E.filter(t=>!e[t])),E.some(t=>o?.c[t]&&!e[t])||o?.yt&&!c.yt?s.location.reload():C(e)}function y(e=!1,o=n.banner&&!c?.done||e){let t=g("keksi-banner"),a=g("keksi-settings");!t||!a||(t.hidden=!o,a.hidden=!e,t.firstElementChild.hidden=e)}function b(){y()}function j(){let e=g("keksi-settings");for(let o of E){let t=e?.querySelector(`[name=${o}]`);t&&(t.checked=!!c?.c[o])}y(!0),e?.querySelector(".keksi-close")?.focus()}i.addEventListener("click",e=>{let o=e.target.closest?.("[data-keksi],[data-keksi-open]");if(!o)return;if(o.hasAttribute("data-keksi-open"))return e.preventDefault(),j();let t=o.getAttribute("data-keksi");if(t==="close")y();else if(t==="accept")p({analytics:!0,marketing:!0});else if(t==="reject")p($);else if(t==="save"){let a=r=>!!g("keksi-settings")?.querySelector(`[name=${r}]`)?.checked;p({analytics:a("analytics"),marketing:a("marketing")})}}),i.addEventListener("keydown",e=>{e.key==="Escape"&&g("keksi-settings")?.hidden===!1&&y()}),i.addEventListener("astro:after-swap",b);class N extends s.HTMLElement{connectedCallback(){this.show(!!c?.yt)}show(o,t=!1){let a=encodeURIComponent(this.getAttribute("video")??""),r=this.getAttribute("label");if(o){if(this.querySelector("iframe"))return;let d=i.createElement("iframe");d.src=`https://www.youtube-nocookie.com/embed/${a}${t?"?autoplay=1":""}`,d.title=r??"YouTube",d.allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",d.allowFullscreen=!0,d.referrerPolicy="strict-origin-when-cross-origin",this.replaceChildren(d),t&&d.focus();return}let l=i.createElement("div");l.className="keksi-yt";let u=this.getAttribute("poster");u&&l.style.setProperty("--keksi-poster",`url(${JSON.stringify(u)})`);let v=i.createElement("p");v.textContent=n.t.ytText;let m=i.createElement("button");m.type="button",m.textContent=n.t.ytButton,r&&m.setAttribute("aria-label",`${n.t.ytButton}: ${r}`),m.onclick=()=>{this.show(!0,!0),R()};let _=i.createElement("p"),F=(d,P,B=!1)=>{let k=i.createElement("a");k.href=d,k.textContent=P,B&&(k.target="_blank",k.rel="noopener"),_.append(_.childNodes.length?" \\xB7 ":"",k)};F(`https://www.youtube.com/watch?v=${a}`,n.t.ytOpen,!0),n.policy&&F(n.policy,n.t.more),l.append(v,m,_),this.replaceChildren(l)}}n.yt&&!s.customElements.get("keksi-youtube")&&s.customElements.define("keksi-youtube",N);let q={get consent(){return c?.done?{...c.c}:null},open:j,save:e=>p({...c?.c??$,...e}),acceptAll:()=>p({analytics:!0,marketing:!0}),rejectAll:()=>p($),sync:b};return s.keksi=q,b(),c&&C(c.c),q}I(JSON.parse(document.getElementById("keksi-data").textContent));})();'}</script>`;
|
|
325
345
|
}
|
|
326
346
|
function sections(config, lang, h, dialog) {
|
|
327
347
|
const t = textsFor(config, lang);
|
package/dist/types.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface KeksiConfig {
|
|
|
25
25
|
domain?: string;
|
|
26
26
|
days?: number;
|
|
27
27
|
};
|
|
28
|
+
/** Providers keksi doesn't have built in, e.g. one client's chat widget. */
|
|
29
|
+
providers?: CustomProvider[];
|
|
28
30
|
/** Cookies your site or framework sets without consent, usually from `keksi scan`. Listed under "Necessary". */
|
|
29
31
|
necessary?: NecessaryCookie[];
|
|
30
32
|
texts?: Partial<Record<Locale, Partial<Texts>>>;
|
|
@@ -38,6 +40,20 @@ export interface NecessaryCookie {
|
|
|
38
40
|
/** `localStorage` or `sessionStorage`; omitted for cookies. */
|
|
39
41
|
storage?: string;
|
|
40
42
|
}
|
|
43
|
+
export interface CustomProvider {
|
|
44
|
+
name: string;
|
|
45
|
+
company: string;
|
|
46
|
+
privacyPolicy: string;
|
|
47
|
+
category: Category;
|
|
48
|
+
/** One text for all languages, or per language. */
|
|
49
|
+
purpose: string | Partial<Record<Locale, string>>;
|
|
50
|
+
cookies: CookieInfo[];
|
|
51
|
+
/** Hosts it contacts, for the Playwright check. */
|
|
52
|
+
hosts?: string[];
|
|
53
|
+
transfer?: 'dpf' | 'us';
|
|
54
|
+
/** Loaded after consent in `category`. Leave out to load it yourself on `keksi:change`. */
|
|
55
|
+
script?: string;
|
|
56
|
+
}
|
|
41
57
|
/** `session`, `persistent`, or a number with unit: `2y`, `13mo`, `90d`, `15min`. */
|
|
42
58
|
export type Duration = 'session' | 'persistent' | `${number}${'y' | 'mo' | 'd' | 'min'}`;
|
|
43
59
|
export interface CookieInfo {
|
|
@@ -48,7 +64,8 @@ export interface CookieInfo {
|
|
|
48
64
|
storage?: 'localStorage' | 'sessionStorage';
|
|
49
65
|
}
|
|
50
66
|
export interface Provider {
|
|
51
|
-
|
|
67
|
+
/** Custom providers get `custom:` and their name. */
|
|
68
|
+
id: ProviderId | `custom:${string}`;
|
|
52
69
|
name: string;
|
|
53
70
|
category: Category;
|
|
54
71
|
company: string;
|
|
@@ -73,8 +90,13 @@ export interface RuntimeData {
|
|
|
73
90
|
key: string;
|
|
74
91
|
host: string;
|
|
75
92
|
};
|
|
93
|
+
/** Custom provider scripts per category. */
|
|
94
|
+
scripts: Record<Category, string[]>;
|
|
76
95
|
yt: boolean;
|
|
96
|
+
/** False with YouTube only: the settings exist but the banner doesn't show by itself. */
|
|
97
|
+
banner: boolean;
|
|
98
|
+
policy?: string;
|
|
77
99
|
/** First-party cookie and storage names per category, `*` = wildcard. */
|
|
78
100
|
del: Record<Category, string[]>;
|
|
79
|
-
t: Pick<Texts, 'ytText' | 'ytButton' | 'ytOpen'>;
|
|
101
|
+
t: Pick<Texts, 'ytText' | 'ytButton' | 'ytOpen' | 'more'>;
|
|
80
102
|
}
|