@kywi-software/js 0.14.0 → 0.15.1
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/dist/audience/behavioral-collector.d.ts +2 -1
- package/dist/audience/behavioral-collector.d.ts.map +1 -1
- package/dist/audience/dom-patcher.d.ts.map +1 -1
- package/dist/audience/evaluate.d.ts +9 -1
- package/dist/audience/evaluate.d.ts.map +1 -1
- package/dist/audience/index.d.ts +8 -0
- package/dist/audience/index.d.ts.map +1 -1
- package/dist/audience/query-collector.d.ts +15 -0
- package/dist/audience/query-collector.d.ts.map +1 -0
- package/dist/audience/selfid-api.d.ts +57 -0
- package/dist/audience/selfid-api.d.ts.map +1 -0
- package/dist/audience/selfid-collector.d.ts +2 -0
- package/dist/audience/selfid-collector.d.ts.map +1 -1
- package/dist/audience/transparency.d.ts +12 -3
- package/dist/audience/transparency.d.ts.map +1 -1
- package/dist/consent.d.ts +69 -2
- package/dist/consent.d.ts.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kywi.esm.js +286 -68
- package/dist/kywi.esm.js.map +4 -4
- package/dist/kywi.js +286 -68
- package/dist/kywi.js.map +4 -4
- package/dist/personalization/badge.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/audience/transparency-styles.d.ts +0 -3
- package/dist/audience/transparency-styles.d.ts.map +0 -1
package/dist/kywi.js
CHANGED
|
@@ -21,14 +21,24 @@ var Kywi = (() => {
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
|
+
AUDIENCE_PIN_COOKIE: () => AUDIENCE_PIN_COOKIE,
|
|
25
|
+
CONSENT_COOKIE: () => CONSENT_COOKIE,
|
|
26
|
+
COOKIE_CONSENT_CATEGORY: () => COOKIE_CONSENT_CATEGORY,
|
|
24
27
|
Kywi: () => Kywi,
|
|
25
28
|
NAV_BREAKPOINT: () => NAV_BREAKPOINT,
|
|
29
|
+
SWITCHER_PIN_PREFIX: () => SWITCHER_PIN_PREFIX,
|
|
26
30
|
bootAudienceEngine: () => bootAudienceEngine,
|
|
27
31
|
bootExperiments: () => bootExperiments,
|
|
32
|
+
clearDeniedCookies: () => clearDeniedCookies,
|
|
33
|
+
clearSelfId: () => clearSelfId,
|
|
28
34
|
enhanceNav: () => enhanceNav,
|
|
35
|
+
getConsentState: () => getConsentState,
|
|
36
|
+
hasConsent: () => hasConsent,
|
|
29
37
|
initNavMenus: () => initNavMenus,
|
|
30
38
|
openTransparencyPanel: () => openTransparencyPanel,
|
|
31
|
-
renderTransparencyPanel: () => renderTransparencyPanel
|
|
39
|
+
renderTransparencyPanel: () => renderTransparencyPanel,
|
|
40
|
+
setConsent: () => setConsent,
|
|
41
|
+
setSelfId: () => setSelfId
|
|
32
42
|
});
|
|
33
43
|
|
|
34
44
|
// src/context.ts
|
|
@@ -569,6 +579,98 @@ var Kywi = (() => {
|
|
|
569
579
|
return runtime;
|
|
570
580
|
}
|
|
571
581
|
|
|
582
|
+
// src/audience/selfid-collector.ts
|
|
583
|
+
var SELFID_KEY = "kywi_selfid";
|
|
584
|
+
function collectSelfId() {
|
|
585
|
+
const raw = readStorage(SELFID_KEY);
|
|
586
|
+
if (!raw) return {};
|
|
587
|
+
try {
|
|
588
|
+
const parsed = JSON.parse(raw);
|
|
589
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
590
|
+
return parsed;
|
|
591
|
+
} catch {
|
|
592
|
+
return {};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function storeSelfId(responses) {
|
|
596
|
+
try {
|
|
597
|
+
localStorage.setItem(SELFID_KEY, JSON.stringify(responses));
|
|
598
|
+
} catch {
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function clearStoredSelfId() {
|
|
602
|
+
try {
|
|
603
|
+
localStorage.removeItem(SELFID_KEY);
|
|
604
|
+
} catch {
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function readStorage(key) {
|
|
608
|
+
try {
|
|
609
|
+
return localStorage.getItem(key);
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/audience/selfid-api.ts
|
|
616
|
+
var DEFAULT_API_BASE = "/api/v1";
|
|
617
|
+
function resolveApiBase(explicit) {
|
|
618
|
+
if (explicit) return explicit;
|
|
619
|
+
const basePath = globalThis.Kywi?.context?.basePath;
|
|
620
|
+
if (!basePath || basePath === "/") return DEFAULT_API_BASE;
|
|
621
|
+
return `${basePath}/api/v1`;
|
|
622
|
+
}
|
|
623
|
+
function resolveCsrfToken(explicit) {
|
|
624
|
+
if (explicit !== void 0) return explicit;
|
|
625
|
+
return globalThis.Kywi?.context?.csrfToken ?? null;
|
|
626
|
+
}
|
|
627
|
+
function reloadPage() {
|
|
628
|
+
try {
|
|
629
|
+
window.location.reload();
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async function setSelfId(fields, options = {}) {
|
|
634
|
+
const merged = { ...collectSelfId() };
|
|
635
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
636
|
+
if (value === "") delete merged[key];
|
|
637
|
+
else merged[key] = value;
|
|
638
|
+
}
|
|
639
|
+
if (Object.keys(merged).length > 0) storeSelfId(merged);
|
|
640
|
+
else clearStoredSelfId();
|
|
641
|
+
const result = await submit(merged, options);
|
|
642
|
+
if (options.reload !== false && result !== null) reloadPage();
|
|
643
|
+
return result ?? { audience: null, persisted: false, fields: merged };
|
|
644
|
+
}
|
|
645
|
+
async function clearSelfId(options = {}) {
|
|
646
|
+
clearStoredSelfId();
|
|
647
|
+
const result = await submit({}, options);
|
|
648
|
+
if (options.reload !== false && result !== null) reloadPage();
|
|
649
|
+
return result ?? { audience: null, persisted: false, fields: {} };
|
|
650
|
+
}
|
|
651
|
+
async function submit(fields, options) {
|
|
652
|
+
const csrfToken = resolveCsrfToken(options.csrfToken);
|
|
653
|
+
try {
|
|
654
|
+
const res = await fetch(`${resolveApiBase(options.apiBase)}/kywi/self-id`, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers: { "Content-Type": "application/json", ...csrfToken ? { "X-CSRF-Token": csrfToken } : {} },
|
|
657
|
+
body: JSON.stringify({
|
|
658
|
+
fields,
|
|
659
|
+
...options.audienceId ? { audienceId: options.audienceId } : {}
|
|
660
|
+
})
|
|
661
|
+
});
|
|
662
|
+
if (!res.ok) return null;
|
|
663
|
+
const body = await res.json().catch(() => null);
|
|
664
|
+
return {
|
|
665
|
+
audience: body?.data?.audience ?? null,
|
|
666
|
+
persisted: body?.data?.persisted === true,
|
|
667
|
+
fields
|
|
668
|
+
};
|
|
669
|
+
} catch {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
572
674
|
// ../core/dist/audiences/types.js
|
|
573
675
|
function createEmptySignals() {
|
|
574
676
|
return {
|
|
@@ -576,6 +678,7 @@ var Kywi = (() => {
|
|
|
576
678
|
referrer: { raw: null, domain: null, sourceType: "direct" },
|
|
577
679
|
session: { pageViewCount: 0, totalPageViews: 0, sessionDuration: 0, entryPage: "/", pagesVisited: [] },
|
|
578
680
|
behavioral: { categoryScores: {}, totalScore: 0 },
|
|
681
|
+
query: {},
|
|
579
682
|
selfId: {},
|
|
580
683
|
identity: { visitorId: "", isKnown: false, maLeadId: null },
|
|
581
684
|
adapters: {},
|
|
@@ -636,6 +739,10 @@ var Kywi = (() => {
|
|
|
636
739
|
return signals.session.entryPage;
|
|
637
740
|
if (ref === "session.pagesVisited")
|
|
638
741
|
return signals.session.pagesVisited.join(",");
|
|
742
|
+
if (ref.startsWith("query.")) {
|
|
743
|
+
const key = ref.slice(6);
|
|
744
|
+
return signals.query[key] ?? null;
|
|
745
|
+
}
|
|
639
746
|
if (ref.startsWith("selfId.")) {
|
|
640
747
|
const key = ref.slice(7);
|
|
641
748
|
return signals.selfId[key] ?? null;
|
|
@@ -803,6 +910,121 @@ var Kywi = (() => {
|
|
|
803
910
|
return { winningAudienceId: allMatchedIds[0] ?? null, allMatchedIds, traces };
|
|
804
911
|
}
|
|
805
912
|
|
|
913
|
+
// src/audience/query-collector.ts
|
|
914
|
+
function collectQuery() {
|
|
915
|
+
const out = {};
|
|
916
|
+
if (typeof location === "undefined") return out;
|
|
917
|
+
new URLSearchParams(location.search).forEach((value, key) => {
|
|
918
|
+
if (key !== "" && !(key in out)) out[key] = value;
|
|
919
|
+
});
|
|
920
|
+
return out;
|
|
921
|
+
}
|
|
922
|
+
function entryPageFor(currentPath) {
|
|
923
|
+
if (currentPath.includes("?")) return currentPath;
|
|
924
|
+
const search = typeof location === "undefined" ? "" : location.search;
|
|
925
|
+
return search ? `${currentPath}${search}` : currentPath;
|
|
926
|
+
}
|
|
927
|
+
|
|
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
|
+
|
|
806
1028
|
// src/audience/behavioral-collector.ts
|
|
807
1029
|
var SESSION_KEY = "kywi_session";
|
|
808
1030
|
var TOTAL_VIEWS_KEY = "kywi_total_views";
|
|
@@ -835,10 +1057,11 @@ var Kywi = (() => {
|
|
|
835
1057
|
}
|
|
836
1058
|
return {};
|
|
837
1059
|
}
|
|
838
|
-
function collectBehavioral(currentPath, category) {
|
|
1060
|
+
function collectBehavioral(currentPath, category, gate = {}) {
|
|
839
1061
|
const session = getSession();
|
|
840
1062
|
session.pageViewCount += 1;
|
|
841
|
-
|
|
1063
|
+
const entryPage = session.entryPage || entryPageFor(currentPath);
|
|
1064
|
+
if (!session.entryPage && hasConsent("personalization", gate)) session.entryPage = entryPage;
|
|
842
1065
|
session.pagesVisited.push(currentPath);
|
|
843
1066
|
sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
|
|
844
1067
|
const totalViews = (Number(localStorage.getItem(TOTAL_VIEWS_KEY)) || 0) + 1;
|
|
@@ -850,26 +1073,11 @@ var Kywi = (() => {
|
|
|
850
1073
|
localStorage.setItem(CATEGORY_SCORES_KEY, JSON.stringify(categoryScores));
|
|
851
1074
|
}
|
|
852
1075
|
return {
|
|
853
|
-
session: { pageViewCount: session.pageViewCount, totalPageViews: totalViews, sessionDuration: Math.floor((Date.now() - sessionStart) / 1e3), entryPage
|
|
1076
|
+
session: { pageViewCount: session.pageViewCount, totalPageViews: totalViews, sessionDuration: Math.floor((Date.now() - sessionStart) / 1e3), entryPage, pagesVisited: session.pagesVisited },
|
|
854
1077
|
behavioral: { categoryScores, totalScore: 0 }
|
|
855
1078
|
};
|
|
856
1079
|
}
|
|
857
1080
|
|
|
858
|
-
// src/audience/selfid-collector.ts
|
|
859
|
-
var SELFID_KEY = "kywi_selfid";
|
|
860
|
-
function collectSelfId() {
|
|
861
|
-
const raw = localStorage.getItem(SELFID_KEY);
|
|
862
|
-
if (!raw) return {};
|
|
863
|
-
try {
|
|
864
|
-
return JSON.parse(raw);
|
|
865
|
-
} catch {
|
|
866
|
-
return {};
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
function storeSelfId(responses) {
|
|
870
|
-
localStorage.setItem(SELFID_KEY, JSON.stringify(responses));
|
|
871
|
-
}
|
|
872
|
-
|
|
873
1081
|
// src/audience/evaluate.ts
|
|
874
1082
|
function merge(base, partial) {
|
|
875
1083
|
return {
|
|
@@ -877,17 +1085,18 @@ var Kywi = (() => {
|
|
|
877
1085
|
referrer: partial.referrer ?? base.referrer,
|
|
878
1086
|
session: partial.session ?? base.session,
|
|
879
1087
|
behavioral: partial.behavioral ?? base.behavioral,
|
|
1088
|
+
query: { ...base.query, ...partial.query },
|
|
880
1089
|
selfId: { ...base.selfId, ...partial.selfId },
|
|
881
1090
|
identity: partial.identity ?? base.identity,
|
|
882
1091
|
adapters: { ...base.adapters, ...partial.adapters },
|
|
883
1092
|
meta: { ...base.meta, ...partial.meta }
|
|
884
1093
|
};
|
|
885
1094
|
}
|
|
886
|
-
function evaluateClientSide(audiences, serverSignals, currentPath, pageCategory) {
|
|
1095
|
+
function evaluateClientSide(audiences, serverSignals, currentPath, pageCategory, gate = {}) {
|
|
887
1096
|
let signals = merge(createEmptySignals(), serverSignals);
|
|
888
|
-
const client = collectBehavioral(currentPath, pageCategory);
|
|
1097
|
+
const client = collectBehavioral(currentPath, pageCategory, gate);
|
|
889
1098
|
const selfId = collectSelfId();
|
|
890
|
-
signals = merge(signals, { session: client.session, behavioral: client.behavioral, selfId, meta: { ...signals.meta, resolvedAt: "client" } });
|
|
1099
|
+
signals = merge(signals, { session: client.session, behavioral: client.behavioral, query: collectQuery(), selfId, meta: { ...signals.meta, resolvedAt: "client" } });
|
|
891
1100
|
const { winningAudienceId, allMatchedIds } = resolveAudiences(audiences, signals);
|
|
892
1101
|
const isOptedOut2 = signals.meta.isOptedOut;
|
|
893
1102
|
return { winningAudienceId: isOptedOut2 ? null : winningAudienceId, allMatchedIds, signals, isOptedOut: isOptedOut2 };
|
|
@@ -904,7 +1113,9 @@ var Kywi = (() => {
|
|
|
904
1113
|
el.removeAttribute("aria-busy");
|
|
905
1114
|
const skeleton = el.querySelector(".kywi-variant-skeleton");
|
|
906
1115
|
if (skeleton) skeleton.remove();
|
|
907
|
-
const
|
|
1116
|
+
const requested = winningAudienceId ?? "default";
|
|
1117
|
+
const hasMatch = arms.some((child) => child.getAttribute("data-kywi-variant") === requested);
|
|
1118
|
+
const target = hasMatch ? requested : "default";
|
|
908
1119
|
el.setAttribute("data-variant", target);
|
|
909
1120
|
for (const child of arms) {
|
|
910
1121
|
const h = child;
|
|
@@ -913,36 +1124,6 @@ var Kywi = (() => {
|
|
|
913
1124
|
}
|
|
914
1125
|
}
|
|
915
1126
|
|
|
916
|
-
// src/cookies.ts
|
|
917
|
-
var COOKIE_NAMES = {
|
|
918
|
-
SIGNALS: "kywi_signals",
|
|
919
|
-
VISITOR: "kywi_visitor",
|
|
920
|
-
UTM: "kywi_utm",
|
|
921
|
-
AUDIENCE: "kywi_audience",
|
|
922
|
-
OPTOUT: "kywi_optout",
|
|
923
|
-
KNOWN: "kywi_known",
|
|
924
|
-
PREVIEW_INIT: "kywi_preview_init"
|
|
925
|
-
};
|
|
926
|
-
function readCookies() {
|
|
927
|
-
const map = /* @__PURE__ */ new Map();
|
|
928
|
-
if (typeof document === "undefined") return map;
|
|
929
|
-
for (const pair of document.cookie.split(";")) {
|
|
930
|
-
const trimmed = pair.trim();
|
|
931
|
-
const eqIdx = trimmed.indexOf("=");
|
|
932
|
-
if (eqIdx < 0) continue;
|
|
933
|
-
map.set(trimmed.slice(0, eqIdx), decodeURIComponent(trimmed.slice(eqIdx + 1)));
|
|
934
|
-
}
|
|
935
|
-
return map;
|
|
936
|
-
}
|
|
937
|
-
function setCookie(name, value, maxAge) {
|
|
938
|
-
let str = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
939
|
-
if (maxAge !== void 0 && maxAge >= 0) str += `; Max-Age=${maxAge}`;
|
|
940
|
-
document.cookie = str;
|
|
941
|
-
}
|
|
942
|
-
function deleteCookie(name) {
|
|
943
|
-
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
1127
|
// src/audience/transparency.ts
|
|
947
1128
|
var COLLAPSED_KEY = "kywi_transparency_collapsed";
|
|
948
1129
|
var LEGACY_DISMISSED_KEY = "kywi_transparency_dismissed";
|
|
@@ -950,10 +1131,10 @@ var Kywi = (() => {
|
|
|
950
1131
|
var PANEL_ID = "kywi-transparency-panel";
|
|
951
1132
|
var TITLE_ID = "kywi-transparency-title";
|
|
952
1133
|
var SWITCHER_LABEL_ID = "kywi-transparency-switcher-label";
|
|
953
|
-
var
|
|
1134
|
+
var DEFAULT_API_BASE2 = "/api/v1";
|
|
954
1135
|
var lastState = null;
|
|
955
1136
|
var forcedOpen = false;
|
|
956
|
-
function
|
|
1137
|
+
function readStorage2(key) {
|
|
957
1138
|
try {
|
|
958
1139
|
return localStorage.getItem(key);
|
|
959
1140
|
} catch {
|
|
@@ -973,12 +1154,12 @@ var Kywi = (() => {
|
|
|
973
1154
|
}
|
|
974
1155
|
}
|
|
975
1156
|
function isCollapsed() {
|
|
976
|
-
if (
|
|
1157
|
+
if (readStorage2(LEGACY_DISMISSED_KEY) === "1") {
|
|
977
1158
|
removeStorage(LEGACY_DISMISSED_KEY);
|
|
978
1159
|
writeStorage(COLLAPSED_KEY, "1");
|
|
979
1160
|
return true;
|
|
980
1161
|
}
|
|
981
|
-
return
|
|
1162
|
+
return readStorage2(COLLAPSED_KEY) !== "0";
|
|
982
1163
|
}
|
|
983
1164
|
function setCollapsed(collapsed) {
|
|
984
1165
|
writeStorage(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
@@ -997,16 +1178,28 @@ var Kywi = (() => {
|
|
|
997
1178
|
return readCookies().has(COOKIE_NAMES.AUDIENCE);
|
|
998
1179
|
}
|
|
999
1180
|
async function pinAudience(audienceId, opts = {}) {
|
|
1000
|
-
const apiBase = opts.apiBase ??
|
|
1181
|
+
const apiBase = opts.apiBase ?? DEFAULT_API_BASE2;
|
|
1001
1182
|
try {
|
|
1002
|
-
await fetch(`${apiBase}/kywi/audience`, {
|
|
1183
|
+
const res = await fetch(`${apiBase}/kywi/audience`, {
|
|
1003
1184
|
method: "POST",
|
|
1004
1185
|
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
1005
|
-
body: JSON.stringify({ audienceId })
|
|
1186
|
+
body: JSON.stringify({ audienceId, source: "switcher" })
|
|
1006
1187
|
});
|
|
1188
|
+
if (!res.ok) return false;
|
|
1189
|
+
const body = await res.json().catch(() => null);
|
|
1190
|
+
return body?.data?.persisted !== false;
|
|
1007
1191
|
} catch {
|
|
1192
|
+
return false;
|
|
1008
1193
|
}
|
|
1009
1194
|
}
|
|
1195
|
+
function showSwitcherError(group) {
|
|
1196
|
+
group.parentElement?.querySelector(".kywi-transparency-switcher-error")?.remove();
|
|
1197
|
+
const note = document.createElement("p");
|
|
1198
|
+
note.className = "kywi-transparency-switcher-error";
|
|
1199
|
+
note.setAttribute("role", "status");
|
|
1200
|
+
note.textContent = "Couldn't save that choice. It won't be remembered on the next page.";
|
|
1201
|
+
group.parentElement?.appendChild(note);
|
|
1202
|
+
}
|
|
1010
1203
|
function reload() {
|
|
1011
1204
|
try {
|
|
1012
1205
|
window.location.reload();
|
|
@@ -1111,7 +1304,12 @@ var Kywi = (() => {
|
|
|
1111
1304
|
const isCurrent = option.id === state.audienceId;
|
|
1112
1305
|
const optionBtn = button("kywi-transparency-option", option.name, () => {
|
|
1113
1306
|
if (isCurrent) return;
|
|
1114
|
-
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1307
|
+
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1308
|
+
(persisted) => {
|
|
1309
|
+
if (persisted) reload();
|
|
1310
|
+
else showSwitcherError(group);
|
|
1311
|
+
}
|
|
1312
|
+
);
|
|
1115
1313
|
});
|
|
1116
1314
|
optionBtn.dataset.kywiAudienceId = option.id;
|
|
1117
1315
|
optionBtn.setAttribute("aria-pressed", String(isCurrent));
|
|
@@ -1564,7 +1762,7 @@ var Kywi = (() => {
|
|
|
1564
1762
|
}
|
|
1565
1763
|
|
|
1566
1764
|
// src/audience/index.ts
|
|
1567
|
-
function
|
|
1765
|
+
function resolveApiBase2(explicit) {
|
|
1568
1766
|
if (explicit) return explicit;
|
|
1569
1767
|
const basePath = Kywi.context?.basePath;
|
|
1570
1768
|
if (!basePath || basePath === "/") return "/api/v1";
|
|
@@ -1572,7 +1770,7 @@ var Kywi = (() => {
|
|
|
1572
1770
|
}
|
|
1573
1771
|
async function bootAudienceEngine(config) {
|
|
1574
1772
|
const { audiences, currentPath, pageCategory, serverSignals } = config;
|
|
1575
|
-
const apiBase =
|
|
1773
|
+
const apiBase = resolveApiBase2(config.apiBase);
|
|
1576
1774
|
checkPreviewInit();
|
|
1577
1775
|
const serverMeta = readServerMeta();
|
|
1578
1776
|
const previewId = getPreviewAudienceId();
|
|
@@ -1587,17 +1785,21 @@ var Kywi = (() => {
|
|
|
1587
1785
|
return;
|
|
1588
1786
|
}
|
|
1589
1787
|
const cookies = readCookies();
|
|
1788
|
+
const consentGate = config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent };
|
|
1789
|
+
const mayPersonalize = hasConsent("personalization", consentGate);
|
|
1790
|
+
const storedVisitorId = mayPersonalize ? cookies.get(COOKIE_NAMES.VISITOR) : void 0;
|
|
1590
1791
|
let merged = {
|
|
1591
1792
|
...serverSignals,
|
|
1592
|
-
identity: { visitorId: serverMeta.visitorId ||
|
|
1793
|
+
identity: { visitorId: serverMeta.visitorId || storedVisitorId || "", isKnown: mayPersonalize && cookies.has(COOKIE_NAMES.KNOWN), maLeadId: null },
|
|
1593
1794
|
meta: { isOptedOut: cookies.has(COOKIE_NAMES.OPTOUT), isPreview: false, previewAudienceId: null, resolvedAt: "server" }
|
|
1594
1795
|
};
|
|
1595
1796
|
if (config.adapters && config.adapters.length > 0) {
|
|
1596
1797
|
const adapterResults = await runClientAdapters(config.adapters, merged, apiBase);
|
|
1597
1798
|
merged.adapters = { ...merged.adapters, ...adapterResults };
|
|
1598
1799
|
}
|
|
1599
|
-
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory);
|
|
1600
|
-
const
|
|
1800
|
+
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory, consentGate);
|
|
1801
|
+
const rawPin = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
|
|
1802
|
+
const pinnedId = rawPin?.startsWith("switcher:") ? rawPin.slice("switcher:".length) || null : rawPin;
|
|
1601
1803
|
const isPinned = !result.isOptedOut && pinnedId !== null && activeAudiences.some((a) => a.id === pinnedId);
|
|
1602
1804
|
const effectiveAudienceId = isPinned ? pinnedId : result.winningAudienceId;
|
|
1603
1805
|
patchVariantContainers(effectiveAudienceId);
|
|
@@ -1614,11 +1816,14 @@ var Kywi = (() => {
|
|
|
1614
1816
|
renderPersonalizationBadge({ audienceName: nameOf(effectiveAudienceId) });
|
|
1615
1817
|
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1616
1818
|
fireKywiReady();
|
|
1617
|
-
if (!isPinned && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1819
|
+
if (!isPinned && mayPersonalize && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1618
1820
|
fetch(`${apiBase}/kywi/audience`, {
|
|
1619
1821
|
method: "POST",
|
|
1620
1822
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
1621
|
-
|
|
1823
|
+
// Explicitly the AUTOMATIC write, so the server applies the
|
|
1824
|
+
// personalization gate to it (kywi-cms#91). The transparency panel's
|
|
1825
|
+
// hand-operated switcher sends source:'switcher' and is exempt.
|
|
1826
|
+
body: JSON.stringify({ audienceId: result.winningAudienceId, source: "boot" })
|
|
1622
1827
|
}).catch(() => {
|
|
1623
1828
|
});
|
|
1624
1829
|
}
|
|
@@ -1696,6 +1901,19 @@ var Kywi = (() => {
|
|
|
1696
1901
|
}
|
|
1697
1902
|
}
|
|
1698
1903
|
},
|
|
1904
|
+
/**
|
|
1905
|
+
* Record who the visitor says they are, and resolve their audience
|
|
1906
|
+
* (kywi-cms#200). Writes the browser-side answers, posts them to the
|
|
1907
|
+
* consent-gated runtime endpoint, and reloads so the SERVER can re-render
|
|
1908
|
+
* with the resolved audience pinned — which is the only way to reach a
|
|
1909
|
+
* variant arm, since every losing arm is pruned server-side (#167).
|
|
1910
|
+
*
|
|
1911
|
+
* On the global because a site's own "What brings you here?" control needs
|
|
1912
|
+
* it: the built-in widget's write path is module-private.
|
|
1913
|
+
*/
|
|
1914
|
+
setSelfId,
|
|
1915
|
+
/** Forget the visitor's self-identification and un-pin their audience. */
|
|
1916
|
+
clearSelfId,
|
|
1699
1917
|
getEntity: null,
|
|
1700
1918
|
getFeed: null,
|
|
1701
1919
|
renderFeed: null,
|