@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.esm.js
CHANGED
|
@@ -536,6 +536,98 @@ function bootExperiments(config = {}) {
|
|
|
536
536
|
return runtime;
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
+
// src/audience/selfid-collector.ts
|
|
540
|
+
var SELFID_KEY = "kywi_selfid";
|
|
541
|
+
function collectSelfId() {
|
|
542
|
+
const raw = readStorage(SELFID_KEY);
|
|
543
|
+
if (!raw) return {};
|
|
544
|
+
try {
|
|
545
|
+
const parsed = JSON.parse(raw);
|
|
546
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
547
|
+
return parsed;
|
|
548
|
+
} catch {
|
|
549
|
+
return {};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function storeSelfId(responses) {
|
|
553
|
+
try {
|
|
554
|
+
localStorage.setItem(SELFID_KEY, JSON.stringify(responses));
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function clearStoredSelfId() {
|
|
559
|
+
try {
|
|
560
|
+
localStorage.removeItem(SELFID_KEY);
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function readStorage(key) {
|
|
565
|
+
try {
|
|
566
|
+
return localStorage.getItem(key);
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/audience/selfid-api.ts
|
|
573
|
+
var DEFAULT_API_BASE = "/api/v1";
|
|
574
|
+
function resolveApiBase(explicit) {
|
|
575
|
+
if (explicit) return explicit;
|
|
576
|
+
const basePath = globalThis.Kywi?.context?.basePath;
|
|
577
|
+
if (!basePath || basePath === "/") return DEFAULT_API_BASE;
|
|
578
|
+
return `${basePath}/api/v1`;
|
|
579
|
+
}
|
|
580
|
+
function resolveCsrfToken(explicit) {
|
|
581
|
+
if (explicit !== void 0) return explicit;
|
|
582
|
+
return globalThis.Kywi?.context?.csrfToken ?? null;
|
|
583
|
+
}
|
|
584
|
+
function reloadPage() {
|
|
585
|
+
try {
|
|
586
|
+
window.location.reload();
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async function setSelfId(fields, options = {}) {
|
|
591
|
+
const merged = { ...collectSelfId() };
|
|
592
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
593
|
+
if (value === "") delete merged[key];
|
|
594
|
+
else merged[key] = value;
|
|
595
|
+
}
|
|
596
|
+
if (Object.keys(merged).length > 0) storeSelfId(merged);
|
|
597
|
+
else clearStoredSelfId();
|
|
598
|
+
const result = await submit(merged, options);
|
|
599
|
+
if (options.reload !== false && result !== null) reloadPage();
|
|
600
|
+
return result ?? { audience: null, persisted: false, fields: merged };
|
|
601
|
+
}
|
|
602
|
+
async function clearSelfId(options = {}) {
|
|
603
|
+
clearStoredSelfId();
|
|
604
|
+
const result = await submit({}, options);
|
|
605
|
+
if (options.reload !== false && result !== null) reloadPage();
|
|
606
|
+
return result ?? { audience: null, persisted: false, fields: {} };
|
|
607
|
+
}
|
|
608
|
+
async function submit(fields, options) {
|
|
609
|
+
const csrfToken = resolveCsrfToken(options.csrfToken);
|
|
610
|
+
try {
|
|
611
|
+
const res = await fetch(`${resolveApiBase(options.apiBase)}/kywi/self-id`, {
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: { "Content-Type": "application/json", ...csrfToken ? { "X-CSRF-Token": csrfToken } : {} },
|
|
614
|
+
body: JSON.stringify({
|
|
615
|
+
fields,
|
|
616
|
+
...options.audienceId ? { audienceId: options.audienceId } : {}
|
|
617
|
+
})
|
|
618
|
+
});
|
|
619
|
+
if (!res.ok) return null;
|
|
620
|
+
const body = await res.json().catch(() => null);
|
|
621
|
+
return {
|
|
622
|
+
audience: body?.data?.audience ?? null,
|
|
623
|
+
persisted: body?.data?.persisted === true,
|
|
624
|
+
fields
|
|
625
|
+
};
|
|
626
|
+
} catch {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
539
631
|
// ../core/dist/audiences/types.js
|
|
540
632
|
function createEmptySignals() {
|
|
541
633
|
return {
|
|
@@ -543,6 +635,7 @@ function createEmptySignals() {
|
|
|
543
635
|
referrer: { raw: null, domain: null, sourceType: "direct" },
|
|
544
636
|
session: { pageViewCount: 0, totalPageViews: 0, sessionDuration: 0, entryPage: "/", pagesVisited: [] },
|
|
545
637
|
behavioral: { categoryScores: {}, totalScore: 0 },
|
|
638
|
+
query: {},
|
|
546
639
|
selfId: {},
|
|
547
640
|
identity: { visitorId: "", isKnown: false, maLeadId: null },
|
|
548
641
|
adapters: {},
|
|
@@ -603,6 +696,10 @@ function resolveSignalValue(signals, ref) {
|
|
|
603
696
|
return signals.session.entryPage;
|
|
604
697
|
if (ref === "session.pagesVisited")
|
|
605
698
|
return signals.session.pagesVisited.join(",");
|
|
699
|
+
if (ref.startsWith("query.")) {
|
|
700
|
+
const key = ref.slice(6);
|
|
701
|
+
return signals.query[key] ?? null;
|
|
702
|
+
}
|
|
606
703
|
if (ref.startsWith("selfId.")) {
|
|
607
704
|
const key = ref.slice(7);
|
|
608
705
|
return signals.selfId[key] ?? null;
|
|
@@ -770,6 +867,121 @@ function resolveAudiences(audiences, signals) {
|
|
|
770
867
|
return { winningAudienceId: allMatchedIds[0] ?? null, allMatchedIds, traces };
|
|
771
868
|
}
|
|
772
869
|
|
|
870
|
+
// src/audience/query-collector.ts
|
|
871
|
+
function collectQuery() {
|
|
872
|
+
const out = {};
|
|
873
|
+
if (typeof location === "undefined") return out;
|
|
874
|
+
new URLSearchParams(location.search).forEach((value, key) => {
|
|
875
|
+
if (key !== "" && !(key in out)) out[key] = value;
|
|
876
|
+
});
|
|
877
|
+
return out;
|
|
878
|
+
}
|
|
879
|
+
function entryPageFor(currentPath) {
|
|
880
|
+
if (currentPath.includes("?")) return currentPath;
|
|
881
|
+
const search = typeof location === "undefined" ? "" : location.search;
|
|
882
|
+
return search ? `${currentPath}${search}` : currentPath;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/cookies.ts
|
|
886
|
+
var COOKIE_NAMES = {
|
|
887
|
+
SIGNALS: "kywi_signals",
|
|
888
|
+
VISITOR: "kywi_visitor",
|
|
889
|
+
UTM: "kywi_utm",
|
|
890
|
+
AUDIENCE: "kywi_audience",
|
|
891
|
+
OPTOUT: "kywi_optout",
|
|
892
|
+
KNOWN: "kywi_known",
|
|
893
|
+
PREVIEW_INIT: "kywi_preview_init"
|
|
894
|
+
};
|
|
895
|
+
function readCookies() {
|
|
896
|
+
const map = /* @__PURE__ */ new Map();
|
|
897
|
+
if (typeof document === "undefined") return map;
|
|
898
|
+
for (const pair of document.cookie.split(";")) {
|
|
899
|
+
const trimmed = pair.trim();
|
|
900
|
+
const eqIdx = trimmed.indexOf("=");
|
|
901
|
+
if (eqIdx < 0) continue;
|
|
902
|
+
map.set(trimmed.slice(0, eqIdx), decodeURIComponent(trimmed.slice(eqIdx + 1)));
|
|
903
|
+
}
|
|
904
|
+
return map;
|
|
905
|
+
}
|
|
906
|
+
function setCookie(name, value, maxAge) {
|
|
907
|
+
let str = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
908
|
+
if (maxAge !== void 0 && maxAge >= 0) str += `; Max-Age=${maxAge}`;
|
|
909
|
+
document.cookie = str;
|
|
910
|
+
}
|
|
911
|
+
function deleteCookie(name) {
|
|
912
|
+
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/consent.ts
|
|
916
|
+
var CONSENT_COOKIE = "kywi_consent";
|
|
917
|
+
var AUDIENCE_PIN_COOKIE = "kywi_audience";
|
|
918
|
+
var SWITCHER_PIN_PREFIX = "switcher:";
|
|
919
|
+
var COOKIE_CONSENT_CATEGORY = {
|
|
920
|
+
kywi_consent: "essential",
|
|
921
|
+
kywi_optout: "essential",
|
|
922
|
+
kywi_preview_init: "essential",
|
|
923
|
+
kywi_signals: "personalization",
|
|
924
|
+
kywi_visitor: "personalization",
|
|
925
|
+
kywi_utm: "personalization",
|
|
926
|
+
kywi_audience: "personalization",
|
|
927
|
+
kywi_known: "personalization",
|
|
928
|
+
kywi_entry: "personalization",
|
|
929
|
+
kywi_selfid: "personalization"
|
|
930
|
+
};
|
|
931
|
+
function getConsentState() {
|
|
932
|
+
const cookies = readCookies();
|
|
933
|
+
const raw = cookies.get(CONSENT_COOKIE);
|
|
934
|
+
if (!raw) return null;
|
|
935
|
+
let parsed;
|
|
936
|
+
try {
|
|
937
|
+
parsed = JSON.parse(raw);
|
|
938
|
+
} catch {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
942
|
+
const obj = parsed;
|
|
943
|
+
return {
|
|
944
|
+
essential: true,
|
|
945
|
+
analytics: obj.analytics === true,
|
|
946
|
+
marketing: obj.marketing === true,
|
|
947
|
+
personalization: obj.personalization === true
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function setConsent(prefs) {
|
|
951
|
+
const state = {
|
|
952
|
+
essential: true,
|
|
953
|
+
analytics: prefs.analytics ?? false,
|
|
954
|
+
marketing: prefs.marketing ?? false,
|
|
955
|
+
personalization: prefs.personalization ?? false
|
|
956
|
+
};
|
|
957
|
+
setCookie(CONSENT_COOKIE, JSON.stringify(state), 31536e3);
|
|
958
|
+
clearDeniedCookies(state);
|
|
959
|
+
}
|
|
960
|
+
function clearDeniedCookies(state) {
|
|
961
|
+
if (typeof document === "undefined") return [];
|
|
962
|
+
const cookies = readCookies();
|
|
963
|
+
const deleted = [];
|
|
964
|
+
for (const [name, category] of Object.entries(COOKIE_CONSENT_CATEGORY)) {
|
|
965
|
+
if (category === "essential") continue;
|
|
966
|
+
if (state && state[category] === true) continue;
|
|
967
|
+
if (isDeliberatePreference(name, cookies.get(name))) continue;
|
|
968
|
+
deleteCookie(name);
|
|
969
|
+
deleted.push(name);
|
|
970
|
+
}
|
|
971
|
+
return deleted;
|
|
972
|
+
}
|
|
973
|
+
function isDeliberatePreference(name, value) {
|
|
974
|
+
if (name !== AUDIENCE_PIN_COOKIE || value === void 0) return false;
|
|
975
|
+
return value.startsWith(SWITCHER_PIN_PREFIX) && value.length > SWITCHER_PIN_PREFIX.length;
|
|
976
|
+
}
|
|
977
|
+
function hasConsent(category, options = {}) {
|
|
978
|
+
if (options.requireConsent === false) return true;
|
|
979
|
+
if (category === "essential") return true;
|
|
980
|
+
const state = getConsentState();
|
|
981
|
+
if (!state) return false;
|
|
982
|
+
return state[category] === true;
|
|
983
|
+
}
|
|
984
|
+
|
|
773
985
|
// src/audience/behavioral-collector.ts
|
|
774
986
|
var SESSION_KEY = "kywi_session";
|
|
775
987
|
var TOTAL_VIEWS_KEY = "kywi_total_views";
|
|
@@ -802,10 +1014,11 @@ function getCategoryScores() {
|
|
|
802
1014
|
}
|
|
803
1015
|
return {};
|
|
804
1016
|
}
|
|
805
|
-
function collectBehavioral(currentPath, category) {
|
|
1017
|
+
function collectBehavioral(currentPath, category, gate = {}) {
|
|
806
1018
|
const session = getSession();
|
|
807
1019
|
session.pageViewCount += 1;
|
|
808
|
-
|
|
1020
|
+
const entryPage = session.entryPage || entryPageFor(currentPath);
|
|
1021
|
+
if (!session.entryPage && hasConsent("personalization", gate)) session.entryPage = entryPage;
|
|
809
1022
|
session.pagesVisited.push(currentPath);
|
|
810
1023
|
sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
|
|
811
1024
|
const totalViews = (Number(localStorage.getItem(TOTAL_VIEWS_KEY)) || 0) + 1;
|
|
@@ -817,26 +1030,11 @@ function collectBehavioral(currentPath, category) {
|
|
|
817
1030
|
localStorage.setItem(CATEGORY_SCORES_KEY, JSON.stringify(categoryScores));
|
|
818
1031
|
}
|
|
819
1032
|
return {
|
|
820
|
-
session: { pageViewCount: session.pageViewCount, totalPageViews: totalViews, sessionDuration: Math.floor((Date.now() - sessionStart) / 1e3), entryPage
|
|
1033
|
+
session: { pageViewCount: session.pageViewCount, totalPageViews: totalViews, sessionDuration: Math.floor((Date.now() - sessionStart) / 1e3), entryPage, pagesVisited: session.pagesVisited },
|
|
821
1034
|
behavioral: { categoryScores, totalScore: 0 }
|
|
822
1035
|
};
|
|
823
1036
|
}
|
|
824
1037
|
|
|
825
|
-
// src/audience/selfid-collector.ts
|
|
826
|
-
var SELFID_KEY = "kywi_selfid";
|
|
827
|
-
function collectSelfId() {
|
|
828
|
-
const raw = localStorage.getItem(SELFID_KEY);
|
|
829
|
-
if (!raw) return {};
|
|
830
|
-
try {
|
|
831
|
-
return JSON.parse(raw);
|
|
832
|
-
} catch {
|
|
833
|
-
return {};
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
function storeSelfId(responses) {
|
|
837
|
-
localStorage.setItem(SELFID_KEY, JSON.stringify(responses));
|
|
838
|
-
}
|
|
839
|
-
|
|
840
1038
|
// src/audience/evaluate.ts
|
|
841
1039
|
function merge(base, partial) {
|
|
842
1040
|
return {
|
|
@@ -844,17 +1042,18 @@ function merge(base, partial) {
|
|
|
844
1042
|
referrer: partial.referrer ?? base.referrer,
|
|
845
1043
|
session: partial.session ?? base.session,
|
|
846
1044
|
behavioral: partial.behavioral ?? base.behavioral,
|
|
1045
|
+
query: { ...base.query, ...partial.query },
|
|
847
1046
|
selfId: { ...base.selfId, ...partial.selfId },
|
|
848
1047
|
identity: partial.identity ?? base.identity,
|
|
849
1048
|
adapters: { ...base.adapters, ...partial.adapters },
|
|
850
1049
|
meta: { ...base.meta, ...partial.meta }
|
|
851
1050
|
};
|
|
852
1051
|
}
|
|
853
|
-
function evaluateClientSide(audiences, serverSignals, currentPath, pageCategory) {
|
|
1052
|
+
function evaluateClientSide(audiences, serverSignals, currentPath, pageCategory, gate = {}) {
|
|
854
1053
|
let signals = merge(createEmptySignals(), serverSignals);
|
|
855
|
-
const client = collectBehavioral(currentPath, pageCategory);
|
|
1054
|
+
const client = collectBehavioral(currentPath, pageCategory, gate);
|
|
856
1055
|
const selfId = collectSelfId();
|
|
857
|
-
signals = merge(signals, { session: client.session, behavioral: client.behavioral, selfId, meta: { ...signals.meta, resolvedAt: "client" } });
|
|
1056
|
+
signals = merge(signals, { session: client.session, behavioral: client.behavioral, query: collectQuery(), selfId, meta: { ...signals.meta, resolvedAt: "client" } });
|
|
858
1057
|
const { winningAudienceId, allMatchedIds } = resolveAudiences(audiences, signals);
|
|
859
1058
|
const isOptedOut2 = signals.meta.isOptedOut;
|
|
860
1059
|
return { winningAudienceId: isOptedOut2 ? null : winningAudienceId, allMatchedIds, signals, isOptedOut: isOptedOut2 };
|
|
@@ -871,7 +1070,9 @@ function patchVariantContainers(winningAudienceId) {
|
|
|
871
1070
|
el.removeAttribute("aria-busy");
|
|
872
1071
|
const skeleton = el.querySelector(".kywi-variant-skeleton");
|
|
873
1072
|
if (skeleton) skeleton.remove();
|
|
874
|
-
const
|
|
1073
|
+
const requested = winningAudienceId ?? "default";
|
|
1074
|
+
const hasMatch = arms.some((child) => child.getAttribute("data-kywi-variant") === requested);
|
|
1075
|
+
const target = hasMatch ? requested : "default";
|
|
875
1076
|
el.setAttribute("data-variant", target);
|
|
876
1077
|
for (const child of arms) {
|
|
877
1078
|
const h = child;
|
|
@@ -880,36 +1081,6 @@ function patchVariantContainers(winningAudienceId) {
|
|
|
880
1081
|
}
|
|
881
1082
|
}
|
|
882
1083
|
|
|
883
|
-
// src/cookies.ts
|
|
884
|
-
var COOKIE_NAMES = {
|
|
885
|
-
SIGNALS: "kywi_signals",
|
|
886
|
-
VISITOR: "kywi_visitor",
|
|
887
|
-
UTM: "kywi_utm",
|
|
888
|
-
AUDIENCE: "kywi_audience",
|
|
889
|
-
OPTOUT: "kywi_optout",
|
|
890
|
-
KNOWN: "kywi_known",
|
|
891
|
-
PREVIEW_INIT: "kywi_preview_init"
|
|
892
|
-
};
|
|
893
|
-
function readCookies() {
|
|
894
|
-
const map = /* @__PURE__ */ new Map();
|
|
895
|
-
if (typeof document === "undefined") return map;
|
|
896
|
-
for (const pair of document.cookie.split(";")) {
|
|
897
|
-
const trimmed = pair.trim();
|
|
898
|
-
const eqIdx = trimmed.indexOf("=");
|
|
899
|
-
if (eqIdx < 0) continue;
|
|
900
|
-
map.set(trimmed.slice(0, eqIdx), decodeURIComponent(trimmed.slice(eqIdx + 1)));
|
|
901
|
-
}
|
|
902
|
-
return map;
|
|
903
|
-
}
|
|
904
|
-
function setCookie(name, value, maxAge) {
|
|
905
|
-
let str = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
906
|
-
if (maxAge !== void 0 && maxAge >= 0) str += `; Max-Age=${maxAge}`;
|
|
907
|
-
document.cookie = str;
|
|
908
|
-
}
|
|
909
|
-
function deleteCookie(name) {
|
|
910
|
-
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
1084
|
// src/audience/transparency.ts
|
|
914
1085
|
var COLLAPSED_KEY = "kywi_transparency_collapsed";
|
|
915
1086
|
var LEGACY_DISMISSED_KEY = "kywi_transparency_dismissed";
|
|
@@ -917,10 +1088,10 @@ var ROOT_ID = "kywi-transparency";
|
|
|
917
1088
|
var PANEL_ID = "kywi-transparency-panel";
|
|
918
1089
|
var TITLE_ID = "kywi-transparency-title";
|
|
919
1090
|
var SWITCHER_LABEL_ID = "kywi-transparency-switcher-label";
|
|
920
|
-
var
|
|
1091
|
+
var DEFAULT_API_BASE2 = "/api/v1";
|
|
921
1092
|
var lastState = null;
|
|
922
1093
|
var forcedOpen = false;
|
|
923
|
-
function
|
|
1094
|
+
function readStorage2(key) {
|
|
924
1095
|
try {
|
|
925
1096
|
return localStorage.getItem(key);
|
|
926
1097
|
} catch {
|
|
@@ -940,12 +1111,12 @@ function removeStorage(key) {
|
|
|
940
1111
|
}
|
|
941
1112
|
}
|
|
942
1113
|
function isCollapsed() {
|
|
943
|
-
if (
|
|
1114
|
+
if (readStorage2(LEGACY_DISMISSED_KEY) === "1") {
|
|
944
1115
|
removeStorage(LEGACY_DISMISSED_KEY);
|
|
945
1116
|
writeStorage(COLLAPSED_KEY, "1");
|
|
946
1117
|
return true;
|
|
947
1118
|
}
|
|
948
|
-
return
|
|
1119
|
+
return readStorage2(COLLAPSED_KEY) !== "0";
|
|
949
1120
|
}
|
|
950
1121
|
function setCollapsed(collapsed) {
|
|
951
1122
|
writeStorage(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
@@ -964,16 +1135,28 @@ function hasPin() {
|
|
|
964
1135
|
return readCookies().has(COOKIE_NAMES.AUDIENCE);
|
|
965
1136
|
}
|
|
966
1137
|
async function pinAudience(audienceId, opts = {}) {
|
|
967
|
-
const apiBase = opts.apiBase ??
|
|
1138
|
+
const apiBase = opts.apiBase ?? DEFAULT_API_BASE2;
|
|
968
1139
|
try {
|
|
969
|
-
await fetch(`${apiBase}/kywi/audience`, {
|
|
1140
|
+
const res = await fetch(`${apiBase}/kywi/audience`, {
|
|
970
1141
|
method: "POST",
|
|
971
1142
|
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
972
|
-
body: JSON.stringify({ audienceId })
|
|
1143
|
+
body: JSON.stringify({ audienceId, source: "switcher" })
|
|
973
1144
|
});
|
|
1145
|
+
if (!res.ok) return false;
|
|
1146
|
+
const body = await res.json().catch(() => null);
|
|
1147
|
+
return body?.data?.persisted !== false;
|
|
974
1148
|
} catch {
|
|
1149
|
+
return false;
|
|
975
1150
|
}
|
|
976
1151
|
}
|
|
1152
|
+
function showSwitcherError(group) {
|
|
1153
|
+
group.parentElement?.querySelector(".kywi-transparency-switcher-error")?.remove();
|
|
1154
|
+
const note = document.createElement("p");
|
|
1155
|
+
note.className = "kywi-transparency-switcher-error";
|
|
1156
|
+
note.setAttribute("role", "status");
|
|
1157
|
+
note.textContent = "Couldn't save that choice. It won't be remembered on the next page.";
|
|
1158
|
+
group.parentElement?.appendChild(note);
|
|
1159
|
+
}
|
|
977
1160
|
function reload() {
|
|
978
1161
|
try {
|
|
979
1162
|
window.location.reload();
|
|
@@ -1078,7 +1261,12 @@ function renderTransparencyPanel(state) {
|
|
|
1078
1261
|
const isCurrent = option.id === state.audienceId;
|
|
1079
1262
|
const optionBtn = button("kywi-transparency-option", option.name, () => {
|
|
1080
1263
|
if (isCurrent) return;
|
|
1081
|
-
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1264
|
+
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1265
|
+
(persisted) => {
|
|
1266
|
+
if (persisted) reload();
|
|
1267
|
+
else showSwitcherError(group);
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1082
1270
|
});
|
|
1083
1271
|
optionBtn.dataset.kywiAudienceId = option.id;
|
|
1084
1272
|
optionBtn.setAttribute("aria-pressed", String(isCurrent));
|
|
@@ -1531,7 +1719,7 @@ function renderPersonalizationBadge(info = {}) {
|
|
|
1531
1719
|
}
|
|
1532
1720
|
|
|
1533
1721
|
// src/audience/index.ts
|
|
1534
|
-
function
|
|
1722
|
+
function resolveApiBase2(explicit) {
|
|
1535
1723
|
if (explicit) return explicit;
|
|
1536
1724
|
const basePath = Kywi.context?.basePath;
|
|
1537
1725
|
if (!basePath || basePath === "/") return "/api/v1";
|
|
@@ -1539,7 +1727,7 @@ function resolveApiBase(explicit) {
|
|
|
1539
1727
|
}
|
|
1540
1728
|
async function bootAudienceEngine(config) {
|
|
1541
1729
|
const { audiences, currentPath, pageCategory, serverSignals } = config;
|
|
1542
|
-
const apiBase =
|
|
1730
|
+
const apiBase = resolveApiBase2(config.apiBase);
|
|
1543
1731
|
checkPreviewInit();
|
|
1544
1732
|
const serverMeta = readServerMeta();
|
|
1545
1733
|
const previewId = getPreviewAudienceId();
|
|
@@ -1554,17 +1742,21 @@ async function bootAudienceEngine(config) {
|
|
|
1554
1742
|
return;
|
|
1555
1743
|
}
|
|
1556
1744
|
const cookies = readCookies();
|
|
1745
|
+
const consentGate = config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent };
|
|
1746
|
+
const mayPersonalize = hasConsent("personalization", consentGate);
|
|
1747
|
+
const storedVisitorId = mayPersonalize ? cookies.get(COOKIE_NAMES.VISITOR) : void 0;
|
|
1557
1748
|
let merged = {
|
|
1558
1749
|
...serverSignals,
|
|
1559
|
-
identity: { visitorId: serverMeta.visitorId ||
|
|
1750
|
+
identity: { visitorId: serverMeta.visitorId || storedVisitorId || "", isKnown: mayPersonalize && cookies.has(COOKIE_NAMES.KNOWN), maLeadId: null },
|
|
1560
1751
|
meta: { isOptedOut: cookies.has(COOKIE_NAMES.OPTOUT), isPreview: false, previewAudienceId: null, resolvedAt: "server" }
|
|
1561
1752
|
};
|
|
1562
1753
|
if (config.adapters && config.adapters.length > 0) {
|
|
1563
1754
|
const adapterResults = await runClientAdapters(config.adapters, merged, apiBase);
|
|
1564
1755
|
merged.adapters = { ...merged.adapters, ...adapterResults };
|
|
1565
1756
|
}
|
|
1566
|
-
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory);
|
|
1567
|
-
const
|
|
1757
|
+
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory, consentGate);
|
|
1758
|
+
const rawPin = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
|
|
1759
|
+
const pinnedId = rawPin?.startsWith("switcher:") ? rawPin.slice("switcher:".length) || null : rawPin;
|
|
1568
1760
|
const isPinned = !result.isOptedOut && pinnedId !== null && activeAudiences.some((a) => a.id === pinnedId);
|
|
1569
1761
|
const effectiveAudienceId = isPinned ? pinnedId : result.winningAudienceId;
|
|
1570
1762
|
patchVariantContainers(effectiveAudienceId);
|
|
@@ -1581,11 +1773,14 @@ async function bootAudienceEngine(config) {
|
|
|
1581
1773
|
renderPersonalizationBadge({ audienceName: nameOf(effectiveAudienceId) });
|
|
1582
1774
|
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1583
1775
|
fireKywiReady();
|
|
1584
|
-
if (!isPinned && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1776
|
+
if (!isPinned && mayPersonalize && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1585
1777
|
fetch(`${apiBase}/kywi/audience`, {
|
|
1586
1778
|
method: "POST",
|
|
1587
1779
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
1588
|
-
|
|
1780
|
+
// Explicitly the AUTOMATIC write, so the server applies the
|
|
1781
|
+
// personalization gate to it (kywi-cms#91). The transparency panel's
|
|
1782
|
+
// hand-operated switcher sends source:'switcher' and is exempt.
|
|
1783
|
+
body: JSON.stringify({ audienceId: result.winningAudienceId, source: "boot" })
|
|
1589
1784
|
}).catch(() => {
|
|
1590
1785
|
});
|
|
1591
1786
|
}
|
|
@@ -1663,6 +1858,19 @@ var Kywi = {
|
|
|
1663
1858
|
}
|
|
1664
1859
|
}
|
|
1665
1860
|
},
|
|
1861
|
+
/**
|
|
1862
|
+
* Record who the visitor says they are, and resolve their audience
|
|
1863
|
+
* (kywi-cms#200). Writes the browser-side answers, posts them to the
|
|
1864
|
+
* consent-gated runtime endpoint, and reloads so the SERVER can re-render
|
|
1865
|
+
* with the resolved audience pinned — which is the only way to reach a
|
|
1866
|
+
* variant arm, since every losing arm is pruned server-side (#167).
|
|
1867
|
+
*
|
|
1868
|
+
* On the global because a site's own "What brings you here?" control needs
|
|
1869
|
+
* it: the built-in widget's write path is module-private.
|
|
1870
|
+
*/
|
|
1871
|
+
setSelfId,
|
|
1872
|
+
/** Forget the visitor's self-identification and un-pin their audience. */
|
|
1873
|
+
clearSelfId,
|
|
1666
1874
|
getEntity: null,
|
|
1667
1875
|
getFeed: null,
|
|
1668
1876
|
renderFeed: null,
|
|
@@ -1694,13 +1902,23 @@ if (typeof document !== "undefined") {
|
|
|
1694
1902
|
}
|
|
1695
1903
|
}
|
|
1696
1904
|
export {
|
|
1905
|
+
AUDIENCE_PIN_COOKIE,
|
|
1906
|
+
CONSENT_COOKIE,
|
|
1907
|
+
COOKIE_CONSENT_CATEGORY,
|
|
1697
1908
|
Kywi,
|
|
1698
1909
|
NAV_BREAKPOINT,
|
|
1910
|
+
SWITCHER_PIN_PREFIX,
|
|
1699
1911
|
bootAudienceEngine,
|
|
1700
1912
|
bootExperiments,
|
|
1913
|
+
clearDeniedCookies,
|
|
1914
|
+
clearSelfId,
|
|
1701
1915
|
enhanceNav,
|
|
1916
|
+
getConsentState,
|
|
1917
|
+
hasConsent,
|
|
1702
1918
|
initNavMenus,
|
|
1703
1919
|
openTransparencyPanel,
|
|
1704
|
-
renderTransparencyPanel
|
|
1920
|
+
renderTransparencyPanel,
|
|
1921
|
+
setConsent,
|
|
1922
|
+
setSelfId
|
|
1705
1923
|
};
|
|
1706
1924
|
//# sourceMappingURL=kywi.esm.js.map
|