@kywi-software/js 0.24.0 → 0.26.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 +10 -5
- package/dist/audience/adapter-runner.d.ts.map +1 -1
- package/dist/audience/index.d.ts.map +1 -1
- package/dist/audience/preview.d.ts +20 -0
- package/dist/audience/preview.d.ts.map +1 -1
- package/dist/audience/selfid-api.d.ts.map +1 -1
- package/dist/audience/selfid-collector.d.ts +9 -1
- package/dist/audience/selfid-collector.d.ts.map +1 -1
- package/dist/audience/selfid-submit.d.ts +13 -1
- package/dist/audience/selfid-submit.d.ts.map +1 -1
- package/dist/audience/selfid-widget.d.ts +20 -0
- package/dist/audience/selfid-widget.d.ts.map +1 -1
- package/dist/audience/transparency.d.ts.map +1 -1
- package/dist/consent.d.ts +24 -0
- package/dist/consent.d.ts.map +1 -1
- package/dist/cookies.d.ts +1 -0
- package/dist/cookies.d.ts.map +1 -1
- package/dist/entity.d.ts.map +1 -1
- package/dist/experiments/index.d.ts.map +1 -1
- package/dist/experiments/pending.d.ts +54 -0
- package/dist/experiments/pending.d.ts.map +1 -0
- package/dist/feed.d.ts.map +1 -1
- package/dist/form-hooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/kywi.esm.js +340 -135
- package/dist/kywi.esm.js.map +4 -4
- package/dist/kywi.js +340 -135
- package/dist/kywi.js.map +4 -4
- package/dist/site-scope.d.ts +18 -0
- package/dist/site-scope.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/kywi.js
CHANGED
|
@@ -23,6 +23,8 @@ var Kywi = (() => {
|
|
|
23
23
|
__export(src_exports, {
|
|
24
24
|
AUDIENCE_PIN_COOKIE: () => AUDIENCE_PIN_COOKIE,
|
|
25
25
|
CONSENT_COOKIE: () => CONSENT_COOKIE,
|
|
26
|
+
CONSENT_EVENT: () => CONSENT_EVENT,
|
|
27
|
+
CONSENT_REOPEN_EVENT: () => CONSENT_REOPEN_EVENT,
|
|
26
28
|
COOKIE_CONSENT_CATEGORY: () => COOKIE_CONSENT_CATEGORY,
|
|
27
29
|
Kywi: () => Kywi,
|
|
28
30
|
NAV_BREAKPOINT: () => NAV_BREAKPOINT,
|
|
@@ -37,6 +39,7 @@ var Kywi = (() => {
|
|
|
37
39
|
initNavMenus: () => initNavMenus,
|
|
38
40
|
openTransparencyPanel: () => openTransparencyPanel,
|
|
39
41
|
renderTransparencyPanel: () => renderTransparencyPanel,
|
|
42
|
+
reopenConsent: () => reopenConsent,
|
|
40
43
|
setConsent: () => setConsent,
|
|
41
44
|
setSelfId: () => setSelfId
|
|
42
45
|
});
|
|
@@ -58,6 +61,26 @@ var Kywi = (() => {
|
|
|
58
61
|
};
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
// src/site-scope.ts
|
|
65
|
+
function currentSiteId() {
|
|
66
|
+
if (typeof document === "undefined") return "";
|
|
67
|
+
return document.querySelector('meta[name="kywi:site-id"]')?.getAttribute("content") || "";
|
|
68
|
+
}
|
|
69
|
+
function withSiteId(url) {
|
|
70
|
+
if (!isSameOrigin(url)) return url;
|
|
71
|
+
const siteId = currentSiteId();
|
|
72
|
+
if (!siteId || /[?&]siteId=/.test(url)) return url;
|
|
73
|
+
const [path, hash] = url.split("#", 2);
|
|
74
|
+
const joined = `${path}${path.includes("?") ? "&" : "?"}siteId=${encodeURIComponent(siteId)}`;
|
|
75
|
+
return hash === void 0 ? joined : `${joined}#${hash}`;
|
|
76
|
+
}
|
|
77
|
+
function isSameOrigin(url) {
|
|
78
|
+
if (url.startsWith("/")) return !url.startsWith("//");
|
|
79
|
+
const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
|
|
80
|
+
if (!origin || origin === "null") return false;
|
|
81
|
+
return url === origin || url.startsWith(`${origin}/`);
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
// src/entity.ts
|
|
62
85
|
function createEntity(type, basePath, csrfToken, data) {
|
|
63
86
|
const fields = { ...data };
|
|
@@ -77,7 +100,7 @@ var Kywi = (() => {
|
|
|
77
100
|
async save() {
|
|
78
101
|
const id = fields["id"];
|
|
79
102
|
const url = id ? `${basePath}/content/${type}/${id}` : `${basePath}/content/${type}`;
|
|
80
|
-
const res = await fetch(url, { method: id ? "PUT" : "POST", headers, body: JSON.stringify(Object.fromEntries(dirty)) });
|
|
103
|
+
const res = await fetch(withSiteId(url), { method: id ? "PUT" : "POST", headers, body: JSON.stringify(Object.fromEntries(dirty)) });
|
|
81
104
|
const json = await res.json();
|
|
82
105
|
if (!res.ok || json.error) {
|
|
83
106
|
errors.push(json.error?.message ?? "Save failed");
|
|
@@ -89,14 +112,14 @@ var Kywi = (() => {
|
|
|
89
112
|
},
|
|
90
113
|
async delete() {
|
|
91
114
|
const id = fields["id"];
|
|
92
|
-
await fetch(`${basePath}/content/${type}/${id}
|
|
115
|
+
await fetch(withSiteId(`${basePath}/content/${type}/${id}`), { method: "DELETE", headers });
|
|
93
116
|
},
|
|
94
117
|
getErrors() {
|
|
95
118
|
return [...errors];
|
|
96
119
|
},
|
|
97
120
|
async loadBy(field, value) {
|
|
98
121
|
const url = field === "id" ? `${basePath}/content/${type}/${value}` : `${basePath}/content/${type}?${field}=${encodeURIComponent(value)}`;
|
|
99
|
-
const res = await fetch(url, { headers });
|
|
122
|
+
const res = await fetch(withSiteId(url), { headers });
|
|
100
123
|
const json = await res.json();
|
|
101
124
|
if (!res.ok || json.error) {
|
|
102
125
|
errors.push(json.error?.message ?? "Load failed");
|
|
@@ -215,7 +238,7 @@ var Kywi = (() => {
|
|
|
215
238
|
const agg = { function: fn };
|
|
216
239
|
if (field) agg["field"] = field;
|
|
217
240
|
body["aggregate"] = agg;
|
|
218
|
-
const res = await fetch(`${this.basePath}/feeds/aggregate
|
|
241
|
+
const res = await fetch(withSiteId(`${this.basePath}/feeds/aggregate`), {
|
|
219
242
|
method: "POST",
|
|
220
243
|
headers: this.buildHeaders(),
|
|
221
244
|
body: JSON.stringify(body)
|
|
@@ -225,7 +248,7 @@ var Kywi = (() => {
|
|
|
225
248
|
}
|
|
226
249
|
async getQuery() {
|
|
227
250
|
const body = this.buildBody();
|
|
228
|
-
const res = await fetch(`${this.basePath}/feeds/query
|
|
251
|
+
const res = await fetch(withSiteId(`${this.basePath}/feeds/query`), {
|
|
229
252
|
method: "POST",
|
|
230
253
|
headers: this.buildHeaders(),
|
|
231
254
|
body: JSON.stringify(body)
|
|
@@ -514,6 +537,248 @@ var Kywi = (() => {
|
|
|
514
537
|
};
|
|
515
538
|
}
|
|
516
539
|
|
|
540
|
+
// src/cookies.ts
|
|
541
|
+
var COOKIE_NAMES = {
|
|
542
|
+
SIGNALS: "kywi_signals",
|
|
543
|
+
VISITOR: "kywi_visitor",
|
|
544
|
+
UTM: "kywi_utm",
|
|
545
|
+
AUDIENCE: "kywi_audience",
|
|
546
|
+
OPTOUT: "kywi_optout",
|
|
547
|
+
KNOWN: "kywi_known",
|
|
548
|
+
PREVIEW_INIT: "kywi_preview_init",
|
|
549
|
+
BUCKET: "kywi_bucket"
|
|
550
|
+
};
|
|
551
|
+
function readCookies() {
|
|
552
|
+
const map = /* @__PURE__ */ new Map();
|
|
553
|
+
if (typeof document === "undefined") return map;
|
|
554
|
+
for (const pair of document.cookie.split(";")) {
|
|
555
|
+
const trimmed = pair.trim();
|
|
556
|
+
const eqIdx = trimmed.indexOf("=");
|
|
557
|
+
if (eqIdx < 0) continue;
|
|
558
|
+
map.set(trimmed.slice(0, eqIdx), decodeURIComponent(trimmed.slice(eqIdx + 1)));
|
|
559
|
+
}
|
|
560
|
+
return map;
|
|
561
|
+
}
|
|
562
|
+
function setCookie(name, value, maxAge) {
|
|
563
|
+
let str = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
564
|
+
if (maxAge !== void 0 && maxAge >= 0) str += `; Max-Age=${maxAge}`;
|
|
565
|
+
document.cookie = str;
|
|
566
|
+
}
|
|
567
|
+
function deleteCookie(name) {
|
|
568
|
+
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/consent.ts
|
|
572
|
+
var CONSENT_COOKIE = "kywi_consent";
|
|
573
|
+
var CONSENT_EVENT = "kywi:consent";
|
|
574
|
+
var CONSENT_REOPEN_EVENT = "kywi:consent-reopen";
|
|
575
|
+
function reopenConsent() {
|
|
576
|
+
if (typeof window === "undefined" || typeof CustomEvent === "undefined") return;
|
|
577
|
+
window.dispatchEvent(new CustomEvent(CONSENT_REOPEN_EVENT));
|
|
578
|
+
}
|
|
579
|
+
var AUDIENCE_PIN_COOKIE = "kywi_audience";
|
|
580
|
+
var SWITCHER_PIN_PREFIX = "switcher:";
|
|
581
|
+
var COOKIE_CONSENT_CATEGORY = {
|
|
582
|
+
kywi_consent: "essential",
|
|
583
|
+
kywi_optout: "essential",
|
|
584
|
+
kywi_preview_init: "essential",
|
|
585
|
+
kywi_bucket: "essential",
|
|
586
|
+
kywi_entry: "essential",
|
|
587
|
+
kywi_utm_visit: "essential",
|
|
588
|
+
kywi_selfid: "essential",
|
|
589
|
+
kywi_signals: "personalization",
|
|
590
|
+
kywi_visitor: "personalization",
|
|
591
|
+
kywi_utm: "personalization",
|
|
592
|
+
kywi_audience: "personalization",
|
|
593
|
+
kywi_known: "personalization"
|
|
594
|
+
};
|
|
595
|
+
function getConsentState() {
|
|
596
|
+
const cookies = readCookies();
|
|
597
|
+
const raw = cookies.get(CONSENT_COOKIE);
|
|
598
|
+
if (!raw) return null;
|
|
599
|
+
let parsed;
|
|
600
|
+
try {
|
|
601
|
+
parsed = JSON.parse(raw);
|
|
602
|
+
} catch {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
606
|
+
const obj = parsed;
|
|
607
|
+
return {
|
|
608
|
+
essential: true,
|
|
609
|
+
analytics: obj.analytics === true,
|
|
610
|
+
marketing: obj.marketing === true,
|
|
611
|
+
personalization: obj.personalization === true
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function setConsent(prefs) {
|
|
615
|
+
const state = {
|
|
616
|
+
essential: true,
|
|
617
|
+
analytics: prefs.analytics ?? false,
|
|
618
|
+
marketing: prefs.marketing ?? false,
|
|
619
|
+
personalization: prefs.personalization ?? false
|
|
620
|
+
};
|
|
621
|
+
setCookie(CONSENT_COOKIE, JSON.stringify(state), 31536e3);
|
|
622
|
+
clearDeniedCookies(state);
|
|
623
|
+
announceConsent(state);
|
|
624
|
+
}
|
|
625
|
+
function announceConsent(state) {
|
|
626
|
+
if (typeof window === "undefined" || typeof CustomEvent === "undefined") return;
|
|
627
|
+
try {
|
|
628
|
+
window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: state }));
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function clearDeniedCookies(state) {
|
|
633
|
+
if (typeof document === "undefined") return [];
|
|
634
|
+
const cookies = readCookies();
|
|
635
|
+
const deleted = [];
|
|
636
|
+
for (const [name, category] of Object.entries(COOKIE_CONSENT_CATEGORY)) {
|
|
637
|
+
if (category === "essential") continue;
|
|
638
|
+
if (state && state[category] === true) continue;
|
|
639
|
+
if (isDeliberatePreference(name, cookies.get(name))) continue;
|
|
640
|
+
deleteCookie(name);
|
|
641
|
+
deleted.push(name);
|
|
642
|
+
}
|
|
643
|
+
return deleted;
|
|
644
|
+
}
|
|
645
|
+
function isDeliberatePreference(name, value) {
|
|
646
|
+
if (name !== AUDIENCE_PIN_COOKIE || value === void 0) return false;
|
|
647
|
+
return value.startsWith(SWITCHER_PIN_PREFIX) && value.length > SWITCHER_PIN_PREFIX.length;
|
|
648
|
+
}
|
|
649
|
+
function hasConsent(category, options = {}) {
|
|
650
|
+
if (options.requireConsent === false) return true;
|
|
651
|
+
if (category === "essential") return true;
|
|
652
|
+
const state = getConsentState();
|
|
653
|
+
if (!state) return false;
|
|
654
|
+
return state[category] === true;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/experiments/pending.ts
|
|
658
|
+
var PENDING_EVENTS_META = "kywi:pending-events";
|
|
659
|
+
var PENDING_STORAGE_KEY = "kywi:pending-events";
|
|
660
|
+
var EMPTY = () => ({ exposures: [], matched: [] });
|
|
661
|
+
var memory = EMPTY();
|
|
662
|
+
var consentListener = null;
|
|
663
|
+
function hasValidSiteId(o) {
|
|
664
|
+
return o.siteId === void 0 || typeof o.siteId === "string" && o.siteId.length > 0;
|
|
665
|
+
}
|
|
666
|
+
function isExposure(v) {
|
|
667
|
+
if (typeof v !== "object" || v === null) return false;
|
|
668
|
+
const o = v;
|
|
669
|
+
return typeof o.experimentId === "string" && o.experimentId.length > 0 && (typeof o.variantKey === "string" || o.variantKey === null) && typeof o.at === "number" && hasValidSiteId(o);
|
|
670
|
+
}
|
|
671
|
+
function isMatch(v) {
|
|
672
|
+
if (typeof v !== "object" || v === null) return false;
|
|
673
|
+
const o = v;
|
|
674
|
+
return typeof o.audienceId === "string" && o.audienceId.length > 0 && typeof o.at === "number" && typeof o.sig === "string" && o.sig.length > 0 && hasValidSiteId(o);
|
|
675
|
+
}
|
|
676
|
+
function parsePending(raw) {
|
|
677
|
+
if (!raw) return EMPTY();
|
|
678
|
+
try {
|
|
679
|
+
const parsed = JSON.parse(raw);
|
|
680
|
+
const exposures = Array.isArray(parsed?.exposures) ? parsed.exposures.filter(isExposure) : [];
|
|
681
|
+
const matched = Array.isArray(parsed?.matched) ? parsed.matched.filter(isMatch) : [];
|
|
682
|
+
return { exposures, matched };
|
|
683
|
+
} catch {
|
|
684
|
+
return EMPTY();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function readStorage() {
|
|
688
|
+
try {
|
|
689
|
+
return parsePending(sessionStorage.getItem(PENDING_STORAGE_KEY));
|
|
690
|
+
} catch {
|
|
691
|
+
return EMPTY();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function writeStorage(events) {
|
|
695
|
+
try {
|
|
696
|
+
if (isEmpty(events)) sessionStorage.removeItem(PENDING_STORAGE_KEY);
|
|
697
|
+
else sessionStorage.setItem(PENDING_STORAGE_KEY, JSON.stringify(events));
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function isEmpty(events) {
|
|
702
|
+
return events.exposures.length === 0 && events.matched.length === 0;
|
|
703
|
+
}
|
|
704
|
+
function merge(into, from) {
|
|
705
|
+
const expKey = (e) => `${e.siteId ?? ""}|${e.experimentId}`;
|
|
706
|
+
const audKey = (m) => `${m.siteId ?? ""}|${m.audienceId}`;
|
|
707
|
+
const byExperiment = new Map(into.exposures.map((e) => [expKey(e), e]));
|
|
708
|
+
for (const e of from.exposures) if (!byExperiment.has(expKey(e))) byExperiment.set(expKey(e), e);
|
|
709
|
+
const byAudience = new Map(into.matched.map((m) => [audKey(m), m]));
|
|
710
|
+
for (const m of from.matched) if (!byAudience.has(audKey(m))) byAudience.set(audKey(m), m);
|
|
711
|
+
return { exposures: [...byExperiment.values()], matched: [...byAudience.values()] };
|
|
712
|
+
}
|
|
713
|
+
function stampSite(events) {
|
|
714
|
+
const siteId = currentSiteId();
|
|
715
|
+
if (!siteId) return events;
|
|
716
|
+
return {
|
|
717
|
+
exposures: events.exposures.map((e) => e.siteId ? e : { ...e, siteId }),
|
|
718
|
+
matched: events.matched.map((m) => m.siteId ? m : { ...m, siteId })
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function pendingEvents() {
|
|
722
|
+
return merge(memory, readStorage());
|
|
723
|
+
}
|
|
724
|
+
function queuePendingEvents(events) {
|
|
725
|
+
memory = merge(pendingEvents(), stampSite(events));
|
|
726
|
+
writeStorage(memory);
|
|
727
|
+
}
|
|
728
|
+
function clearQueue() {
|
|
729
|
+
memory = EMPTY();
|
|
730
|
+
writeStorage(memory);
|
|
731
|
+
}
|
|
732
|
+
function urlForSite(url, siteId) {
|
|
733
|
+
if (!siteId) return withSiteId(url);
|
|
734
|
+
const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
|
|
735
|
+
const sameOrigin = url.startsWith("/") && !url.startsWith("//") || !!origin && origin !== "null" && (url === origin || url.startsWith(`${origin}/`));
|
|
736
|
+
if (!sameOrigin || /[?&]siteId=/.test(url)) return url;
|
|
737
|
+
return `${url}${url.includes("?") ? "&" : "?"}siteId=${encodeURIComponent(siteId)}`;
|
|
738
|
+
}
|
|
739
|
+
function post(url, siteId, body, csrfToken) {
|
|
740
|
+
try {
|
|
741
|
+
void fetch(urlForSite(url, siteId), {
|
|
742
|
+
method: "POST",
|
|
743
|
+
headers: { "Content-Type": "application/json", ...csrfToken ? { "X-CSRF-Token": csrfToken } : {} },
|
|
744
|
+
body: JSON.stringify(body),
|
|
745
|
+
keepalive: true
|
|
746
|
+
}).catch(() => {
|
|
747
|
+
});
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function flushPendingEvents(config) {
|
|
752
|
+
const gate = config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent };
|
|
753
|
+
const queue = pendingEvents();
|
|
754
|
+
if (isEmpty(queue)) return 0;
|
|
755
|
+
if (!hasConsent("personalization", gate) || !hasConsent("analytics", gate)) {
|
|
756
|
+
if (getConsentState() !== null) clearQueue();
|
|
757
|
+
return 0;
|
|
758
|
+
}
|
|
759
|
+
for (const e of queue.exposures) {
|
|
760
|
+
post(`${config.apiBase}/kywi/experiments/exposure`, e.siteId, { experimentId: e.experimentId, at: e.at }, config.csrfToken);
|
|
761
|
+
}
|
|
762
|
+
for (const m of queue.matched) {
|
|
763
|
+
post(`${config.apiBase}/kywi/analytics/matched`, m.siteId, { audienceId: m.audienceId, at: m.at, sig: m.sig }, config.csrfToken);
|
|
764
|
+
}
|
|
765
|
+
clearQueue();
|
|
766
|
+
return queue.exposures.length + queue.matched.length;
|
|
767
|
+
}
|
|
768
|
+
function bootPendingEvents(config) {
|
|
769
|
+
if (typeof document !== "undefined") {
|
|
770
|
+
const raw = document.querySelector(`meta[name="${PENDING_EVENTS_META}"]`)?.getAttribute("content");
|
|
771
|
+
queuePendingEvents(parsePending(raw));
|
|
772
|
+
}
|
|
773
|
+
flushPendingEvents(config);
|
|
774
|
+
if (typeof window === "undefined") return;
|
|
775
|
+
if (consentListener) window.removeEventListener(CONSENT_EVENT, consentListener);
|
|
776
|
+
consentListener = () => {
|
|
777
|
+
flushPendingEvents(config);
|
|
778
|
+
};
|
|
779
|
+
window.addEventListener(CONSENT_EVENT, consentListener);
|
|
780
|
+
}
|
|
781
|
+
|
|
517
782
|
// src/experiments/index.ts
|
|
518
783
|
function apiBaseFrom(ctx) {
|
|
519
784
|
return ctx.basePath && ctx.basePath !== "/" ? `${ctx.basePath}/api/v1` : "/api/v1";
|
|
@@ -540,7 +805,7 @@ var Kywi = (() => {
|
|
|
540
805
|
}
|
|
541
806
|
function beacon(url, body, csrfToken) {
|
|
542
807
|
try {
|
|
543
|
-
void fetch(url, {
|
|
808
|
+
void fetch(withSiteId(url), {
|
|
544
809
|
method: "POST",
|
|
545
810
|
headers: { "Content-Type": "application/json", ...csrfToken ? { "X-CSRF-Token": csrfToken } : {} },
|
|
546
811
|
body: JSON.stringify(body),
|
|
@@ -566,6 +831,7 @@ var Kywi = (() => {
|
|
|
566
831
|
if (experimentId) {
|
|
567
832
|
beacon(`${apiBase}/kywi/experiments/exposure`, { experimentId, visitorId }, csrf);
|
|
568
833
|
}
|
|
834
|
+
bootPendingEvents({ apiBase, ...csrf ? { csrfToken: csrf } : {} });
|
|
569
835
|
if (config.autoPageview !== false) {
|
|
570
836
|
trackConversion({ type: "pageview", url: currentPath });
|
|
571
837
|
}
|
|
@@ -582,7 +848,7 @@ var Kywi = (() => {
|
|
|
582
848
|
// src/audience/selfid-collector.ts
|
|
583
849
|
var SELFID_KEY = "kywi_selfid";
|
|
584
850
|
function collectSelfId() {
|
|
585
|
-
const raw =
|
|
851
|
+
const raw = readStorage2(SELFID_KEY);
|
|
586
852
|
if (!raw) return {};
|
|
587
853
|
try {
|
|
588
854
|
const parsed = JSON.parse(raw);
|
|
@@ -592,7 +858,9 @@ var Kywi = (() => {
|
|
|
592
858
|
return {};
|
|
593
859
|
}
|
|
594
860
|
}
|
|
595
|
-
function storeSelfId(responses) {
|
|
861
|
+
function storeSelfId(responses, gate = {}) {
|
|
862
|
+
const category = COOKIE_CONSENT_CATEGORY[SELFID_KEY] ?? "personalization";
|
|
863
|
+
if (!hasConsent(category, gate)) return;
|
|
596
864
|
try {
|
|
597
865
|
localStorage.setItem(SELFID_KEY, JSON.stringify(responses));
|
|
598
866
|
} catch {
|
|
@@ -604,7 +872,7 @@ var Kywi = (() => {
|
|
|
604
872
|
} catch {
|
|
605
873
|
}
|
|
606
874
|
}
|
|
607
|
-
function
|
|
875
|
+
function readStorage2(key) {
|
|
608
876
|
try {
|
|
609
877
|
return localStorage.getItem(key);
|
|
610
878
|
} catch {
|
|
@@ -651,7 +919,7 @@ var Kywi = (() => {
|
|
|
651
919
|
async function submit(fields, options) {
|
|
652
920
|
const csrfToken = resolveCsrfToken(options.csrfToken);
|
|
653
921
|
try {
|
|
654
|
-
const res = await fetch(`${resolveApiBase(options.apiBase)}/kywi/self-id
|
|
922
|
+
const res = await fetch(withSiteId(`${resolveApiBase(options.apiBase)}/kywi/self-id`), {
|
|
655
923
|
method: "POST",
|
|
656
924
|
headers: { "Content-Type": "application/json", ...csrfToken ? { "X-CSRF-Token": csrfToken } : {} },
|
|
657
925
|
body: JSON.stringify({
|
|
@@ -925,106 +1193,6 @@ var Kywi = (() => {
|
|
|
925
1193
|
return search ? `${currentPath}${search}` : currentPath;
|
|
926
1194
|
}
|
|
927
1195
|
|
|
928
|
-
// src/cookies.ts
|
|
929
|
-
var COOKIE_NAMES = {
|
|
930
|
-
SIGNALS: "kywi_signals",
|
|
931
|
-
VISITOR: "kywi_visitor",
|
|
932
|
-
UTM: "kywi_utm",
|
|
933
|
-
AUDIENCE: "kywi_audience",
|
|
934
|
-
OPTOUT: "kywi_optout",
|
|
935
|
-
KNOWN: "kywi_known",
|
|
936
|
-
PREVIEW_INIT: "kywi_preview_init"
|
|
937
|
-
};
|
|
938
|
-
function readCookies() {
|
|
939
|
-
const map = /* @__PURE__ */ new Map();
|
|
940
|
-
if (typeof document === "undefined") return map;
|
|
941
|
-
for (const pair of document.cookie.split(";")) {
|
|
942
|
-
const trimmed = pair.trim();
|
|
943
|
-
const eqIdx = trimmed.indexOf("=");
|
|
944
|
-
if (eqIdx < 0) continue;
|
|
945
|
-
map.set(trimmed.slice(0, eqIdx), decodeURIComponent(trimmed.slice(eqIdx + 1)));
|
|
946
|
-
}
|
|
947
|
-
return map;
|
|
948
|
-
}
|
|
949
|
-
function setCookie(name, value, maxAge) {
|
|
950
|
-
let str = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
951
|
-
if (maxAge !== void 0 && maxAge >= 0) str += `; Max-Age=${maxAge}`;
|
|
952
|
-
document.cookie = str;
|
|
953
|
-
}
|
|
954
|
-
function deleteCookie(name) {
|
|
955
|
-
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// src/consent.ts
|
|
959
|
-
var CONSENT_COOKIE = "kywi_consent";
|
|
960
|
-
var AUDIENCE_PIN_COOKIE = "kywi_audience";
|
|
961
|
-
var SWITCHER_PIN_PREFIX = "switcher:";
|
|
962
|
-
var COOKIE_CONSENT_CATEGORY = {
|
|
963
|
-
kywi_consent: "essential",
|
|
964
|
-
kywi_optout: "essential",
|
|
965
|
-
kywi_preview_init: "essential",
|
|
966
|
-
kywi_signals: "personalization",
|
|
967
|
-
kywi_visitor: "personalization",
|
|
968
|
-
kywi_utm: "personalization",
|
|
969
|
-
kywi_audience: "personalization",
|
|
970
|
-
kywi_known: "personalization",
|
|
971
|
-
kywi_entry: "personalization",
|
|
972
|
-
kywi_selfid: "personalization"
|
|
973
|
-
};
|
|
974
|
-
function getConsentState() {
|
|
975
|
-
const cookies = readCookies();
|
|
976
|
-
const raw = cookies.get(CONSENT_COOKIE);
|
|
977
|
-
if (!raw) return null;
|
|
978
|
-
let parsed;
|
|
979
|
-
try {
|
|
980
|
-
parsed = JSON.parse(raw);
|
|
981
|
-
} catch {
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
if (typeof parsed !== "object" || parsed === null) return null;
|
|
985
|
-
const obj = parsed;
|
|
986
|
-
return {
|
|
987
|
-
essential: true,
|
|
988
|
-
analytics: obj.analytics === true,
|
|
989
|
-
marketing: obj.marketing === true,
|
|
990
|
-
personalization: obj.personalization === true
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
function setConsent(prefs) {
|
|
994
|
-
const state = {
|
|
995
|
-
essential: true,
|
|
996
|
-
analytics: prefs.analytics ?? false,
|
|
997
|
-
marketing: prefs.marketing ?? false,
|
|
998
|
-
personalization: prefs.personalization ?? false
|
|
999
|
-
};
|
|
1000
|
-
setCookie(CONSENT_COOKIE, JSON.stringify(state), 31536e3);
|
|
1001
|
-
clearDeniedCookies(state);
|
|
1002
|
-
}
|
|
1003
|
-
function clearDeniedCookies(state) {
|
|
1004
|
-
if (typeof document === "undefined") return [];
|
|
1005
|
-
const cookies = readCookies();
|
|
1006
|
-
const deleted = [];
|
|
1007
|
-
for (const [name, category] of Object.entries(COOKIE_CONSENT_CATEGORY)) {
|
|
1008
|
-
if (category === "essential") continue;
|
|
1009
|
-
if (state && state[category] === true) continue;
|
|
1010
|
-
if (isDeliberatePreference(name, cookies.get(name))) continue;
|
|
1011
|
-
deleteCookie(name);
|
|
1012
|
-
deleted.push(name);
|
|
1013
|
-
}
|
|
1014
|
-
return deleted;
|
|
1015
|
-
}
|
|
1016
|
-
function isDeliberatePreference(name, value) {
|
|
1017
|
-
if (name !== AUDIENCE_PIN_COOKIE || value === void 0) return false;
|
|
1018
|
-
return value.startsWith(SWITCHER_PIN_PREFIX) && value.length > SWITCHER_PIN_PREFIX.length;
|
|
1019
|
-
}
|
|
1020
|
-
function hasConsent(category, options = {}) {
|
|
1021
|
-
if (options.requireConsent === false) return true;
|
|
1022
|
-
if (category === "essential") return true;
|
|
1023
|
-
const state = getConsentState();
|
|
1024
|
-
if (!state) return false;
|
|
1025
|
-
return state[category] === true;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
1196
|
// src/audience/behavioral-collector.ts
|
|
1029
1197
|
var SESSION_KEY = "kywi_session";
|
|
1030
1198
|
var TOTAL_VIEWS_KEY = "kywi_total_views";
|
|
@@ -1079,7 +1247,7 @@ var Kywi = (() => {
|
|
|
1079
1247
|
}
|
|
1080
1248
|
|
|
1081
1249
|
// src/audience/evaluate.ts
|
|
1082
|
-
function
|
|
1250
|
+
function merge2(base, partial) {
|
|
1083
1251
|
return {
|
|
1084
1252
|
utm: partial.utm ?? base.utm,
|
|
1085
1253
|
referrer: partial.referrer ?? base.referrer,
|
|
@@ -1093,10 +1261,10 @@ var Kywi = (() => {
|
|
|
1093
1261
|
};
|
|
1094
1262
|
}
|
|
1095
1263
|
function evaluateClientSide(audiences, serverSignals, currentPath, pageCategory, gate = {}) {
|
|
1096
|
-
let signals =
|
|
1264
|
+
let signals = merge2(createEmptySignals(), serverSignals);
|
|
1097
1265
|
const client = collectBehavioral(currentPath, pageCategory, gate);
|
|
1098
1266
|
const selfId = collectSelfId();
|
|
1099
|
-
signals =
|
|
1267
|
+
signals = merge2(signals, { session: client.session, behavioral: client.behavioral, query: collectQuery(), selfId, meta: { ...signals.meta, resolvedAt: "client" } });
|
|
1100
1268
|
const { winningAudienceId, allMatchedIds } = resolveAudiences(audiences, signals);
|
|
1101
1269
|
const isOptedOut2 = signals.meta.isOptedOut;
|
|
1102
1270
|
return { winningAudienceId: isOptedOut2 ? null : winningAudienceId, allMatchedIds, signals, isOptedOut: isOptedOut2 };
|
|
@@ -1134,14 +1302,14 @@ var Kywi = (() => {
|
|
|
1134
1302
|
var DEFAULT_API_BASE2 = "/api/v1";
|
|
1135
1303
|
var lastState = null;
|
|
1136
1304
|
var forcedOpen = false;
|
|
1137
|
-
function
|
|
1305
|
+
function readStorage3(key) {
|
|
1138
1306
|
try {
|
|
1139
1307
|
return localStorage.getItem(key);
|
|
1140
1308
|
} catch {
|
|
1141
1309
|
return null;
|
|
1142
1310
|
}
|
|
1143
1311
|
}
|
|
1144
|
-
function
|
|
1312
|
+
function writeStorage2(key, value) {
|
|
1145
1313
|
try {
|
|
1146
1314
|
localStorage.setItem(key, value);
|
|
1147
1315
|
} catch {
|
|
@@ -1154,15 +1322,15 @@ var Kywi = (() => {
|
|
|
1154
1322
|
}
|
|
1155
1323
|
}
|
|
1156
1324
|
function isCollapsed() {
|
|
1157
|
-
if (
|
|
1325
|
+
if (readStorage3(LEGACY_DISMISSED_KEY) === "1") {
|
|
1158
1326
|
removeStorage(LEGACY_DISMISSED_KEY);
|
|
1159
|
-
|
|
1327
|
+
writeStorage2(COLLAPSED_KEY, "1");
|
|
1160
1328
|
return true;
|
|
1161
1329
|
}
|
|
1162
|
-
return
|
|
1330
|
+
return readStorage3(COLLAPSED_KEY) !== "0";
|
|
1163
1331
|
}
|
|
1164
1332
|
function setCollapsed(collapsed) {
|
|
1165
|
-
|
|
1333
|
+
writeStorage2(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
1166
1334
|
}
|
|
1167
1335
|
function optOut() {
|
|
1168
1336
|
setCookie(COOKIE_NAMES.OPTOUT, "1", 365 * 24 * 3600);
|
|
@@ -1180,7 +1348,7 @@ var Kywi = (() => {
|
|
|
1180
1348
|
async function pinAudience(audienceId, opts = {}) {
|
|
1181
1349
|
const apiBase = opts.apiBase ?? DEFAULT_API_BASE2;
|
|
1182
1350
|
try {
|
|
1183
|
-
const res = await fetch(`${apiBase}/kywi/audience
|
|
1351
|
+
const res = await fetch(withSiteId(`${apiBase}/kywi/audience`), {
|
|
1184
1352
|
method: "POST",
|
|
1185
1353
|
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
1186
1354
|
body: JSON.stringify({ audienceId, source: "switcher" })
|
|
@@ -1393,14 +1561,18 @@ var Kywi = (() => {
|
|
|
1393
1561
|
}
|
|
1394
1562
|
function clearPreview() {
|
|
1395
1563
|
sessionStorage.removeItem(PREVIEW_KEY);
|
|
1564
|
+
deleteCookie(COOKIE_NAMES.PREVIEW_INIT);
|
|
1565
|
+
}
|
|
1566
|
+
function previewAudienceFromCookie(value) {
|
|
1567
|
+
if (!value) return null;
|
|
1568
|
+
const parts = value.split(".");
|
|
1569
|
+
if (parts.length < 3) return null;
|
|
1570
|
+
const id = parts[0];
|
|
1571
|
+
return id.length > 0 ? id : null;
|
|
1396
1572
|
}
|
|
1397
1573
|
function checkPreviewInit() {
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
if (initId) {
|
|
1401
|
-
setPreviewAudienceId(initId);
|
|
1402
|
-
deleteCookie(COOKIE_NAMES.PREVIEW_INIT);
|
|
1403
|
-
}
|
|
1574
|
+
const initId = previewAudienceFromCookie(readCookies().get(COOKIE_NAMES.PREVIEW_INIT));
|
|
1575
|
+
if (initId) setPreviewAudienceId(initId);
|
|
1404
1576
|
}
|
|
1405
1577
|
function renderPreviewBanner(audienceName2) {
|
|
1406
1578
|
document.querySelector(".kywi-preview-banner")?.remove();
|
|
@@ -1461,7 +1633,7 @@ var Kywi = (() => {
|
|
|
1461
1633
|
if (cached) return cached;
|
|
1462
1634
|
let result;
|
|
1463
1635
|
if (adapter.side === "server-proxy") {
|
|
1464
|
-
const res = await fetch(`${apiBase}/kywi/adapters/${adapter.id}
|
|
1636
|
+
const res = await fetch(withSiteId(`${apiBase}/kywi/adapters/${adapter.id}`), {
|
|
1465
1637
|
method: "POST",
|
|
1466
1638
|
headers: { "Content-Type": "application/json" },
|
|
1467
1639
|
body: JSON.stringify({ signals: currentSignals })
|
|
@@ -1480,16 +1652,18 @@ var Kywi = (() => {
|
|
|
1480
1652
|
}
|
|
1481
1653
|
|
|
1482
1654
|
// src/audience/selfid-submit.ts
|
|
1483
|
-
function handleSelfIdSubmit(responses, audiences, currentPath) {
|
|
1484
|
-
storeSelfId(responses);
|
|
1655
|
+
function handleSelfIdSubmit(responses, audiences, currentPath, gate = {}) {
|
|
1656
|
+
storeSelfId(responses, gate);
|
|
1485
1657
|
if (audiences && currentPath) {
|
|
1486
1658
|
const cookies = readCookies();
|
|
1487
1659
|
const serverMeta = readServerMeta();
|
|
1660
|
+
const mayPersonalize = hasConsent("personalization", gate);
|
|
1661
|
+
const storedVisitorId = mayPersonalize ? cookies.get(COOKIE_NAMES.VISITOR) : void 0;
|
|
1488
1662
|
const serverSignals = {
|
|
1489
|
-
identity: { visitorId: serverMeta.visitorId ||
|
|
1663
|
+
identity: { visitorId: serverMeta.visitorId || storedVisitorId || "", isKnown: mayPersonalize && cookies.has(COOKIE_NAMES.KNOWN), maLeadId: null },
|
|
1490
1664
|
meta: { isOptedOut: cookies.has(COOKIE_NAMES.OPTOUT), isPreview: false, previewAudienceId: null, resolvedAt: "server" }
|
|
1491
1665
|
};
|
|
1492
|
-
const result = evaluateClientSide(audiences, serverSignals, currentPath);
|
|
1666
|
+
const result = evaluateClientSide(audiences, serverSignals, currentPath, void 0, gate);
|
|
1493
1667
|
patchVariantContainers(result.winningAudienceId);
|
|
1494
1668
|
populateDataLayer({
|
|
1495
1669
|
visitorId: result.signals.identity.visitorId,
|
|
@@ -1561,10 +1735,28 @@ var Kywi = (() => {
|
|
|
1561
1735
|
function markShown() {
|
|
1562
1736
|
localStorage.setItem(SHOWN_KEY, String(Date.now()));
|
|
1563
1737
|
}
|
|
1738
|
+
function consentGate() {
|
|
1739
|
+
return config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent };
|
|
1740
|
+
}
|
|
1741
|
+
function patchClientSide(responses) {
|
|
1742
|
+
handleSelfIdSubmit(responses, config.audiences ?? [], config.currentPath ?? "/", consentGate());
|
|
1743
|
+
}
|
|
1564
1744
|
function handleSubmit(responses) {
|
|
1565
|
-
|
|
1745
|
+
if (config.serverSubmit !== false) {
|
|
1746
|
+
markShown();
|
|
1747
|
+
const options = {
|
|
1748
|
+
...config.apiBase !== void 0 ? { apiBase: config.apiBase } : {},
|
|
1749
|
+
...config.csrfToken !== void 0 ? { csrfToken: config.csrfToken } : {}
|
|
1750
|
+
};
|
|
1751
|
+
void setSelfId(responses, options).then((result) => {
|
|
1752
|
+
if (!result || !result.persisted) patchClientSide(responses);
|
|
1753
|
+
}).catch(() => {
|
|
1754
|
+
});
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
storeSelfId(responses, consentGate());
|
|
1566
1758
|
try {
|
|
1567
|
-
|
|
1759
|
+
patchClientSide(responses);
|
|
1568
1760
|
} finally {
|
|
1569
1761
|
markShown();
|
|
1570
1762
|
}
|
|
@@ -1817,7 +2009,7 @@ var Kywi = (() => {
|
|
|
1817
2009
|
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1818
2010
|
fireKywiReady();
|
|
1819
2011
|
if (!isPinned && mayPersonalize && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1820
|
-
fetch(`${apiBase}/kywi/audience
|
|
2012
|
+
fetch(withSiteId(`${apiBase}/kywi/audience`), {
|
|
1821
2013
|
method: "POST",
|
|
1822
2014
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
1823
2015
|
// Explicitly the AUTOMATIC write, so the server applies the
|
|
@@ -1828,7 +2020,15 @@ var Kywi = (() => {
|
|
|
1828
2020
|
});
|
|
1829
2021
|
}
|
|
1830
2022
|
if (config.selfIdWidget) {
|
|
1831
|
-
const widget = createSelfIdWidget(
|
|
2023
|
+
const widget = createSelfIdWidget({
|
|
2024
|
+
// The API base and CSRF token this boot resolved, so the widget's server
|
|
2025
|
+
// submit posts where the rest of the runtime does (review fix 4); a
|
|
2026
|
+
// value the widget config sets itself still wins.
|
|
2027
|
+
apiBase,
|
|
2028
|
+
csrfToken: Kywi.context?.csrfToken ?? null,
|
|
2029
|
+
...config.selfIdWidget,
|
|
2030
|
+
...config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent }
|
|
2031
|
+
});
|
|
1832
2032
|
if (widget.shouldShow()) {
|
|
1833
2033
|
const doRender = () => {
|
|
1834
2034
|
const mount = resolveSelfIdMount(config.selfIdWidget.displayMode);
|
|
@@ -1914,6 +2114,11 @@ var Kywi = (() => {
|
|
|
1914
2114
|
setSelfId,
|
|
1915
2115
|
/** Forget the visitor's self-identification and un-pin their audience. */
|
|
1916
2116
|
clearSelfId,
|
|
2117
|
+
/**
|
|
2118
|
+
* Re-open the cookie-consent banner so the visitor can change an earlier
|
|
2119
|
+
* decision (kywi-cms#191) — for a site's own "Cookie settings" link.
|
|
2120
|
+
*/
|
|
2121
|
+
reopenConsent,
|
|
1917
2122
|
getEntity: null,
|
|
1918
2123
|
getFeed: null,
|
|
1919
2124
|
renderFeed: null,
|