@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/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
- if (!session.entryPage) session.entryPage = currentPath;
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: session.entryPage, pagesVisited: session.pagesVisited },
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 target = winningAudienceId ?? "default";
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 DEFAULT_API_BASE = "/api/v1";
1091
+ var DEFAULT_API_BASE2 = "/api/v1";
921
1092
  var lastState = null;
922
1093
  var forcedOpen = false;
923
- function readStorage(key) {
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 (readStorage(LEGACY_DISMISSED_KEY) === "1") {
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 readStorage(COLLAPSED_KEY) !== "0";
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 ?? DEFAULT_API_BASE;
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(reload);
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 resolveApiBase(explicit) {
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 = resolveApiBase(config.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 || cookies.get(COOKIE_NAMES.VISITOR) || "", isKnown: cookies.has(COOKIE_NAMES.KNOWN), maLeadId: null },
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 pinnedId = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
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
- body: JSON.stringify({ audienceId: result.winningAudienceId })
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