@kywi-software/js 0.13.0 → 0.15.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/dist/audience/dom-patcher.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/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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kywi.esm.js +111 -9
- package/dist/kywi.esm.js.map +3 -3
- package/dist/kywi.js +111 -9
- package/dist/kywi.js.map +3 -3
- 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,22 @@ 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,
|
|
28
33
|
enhanceNav: () => enhanceNav,
|
|
34
|
+
getConsentState: () => getConsentState,
|
|
35
|
+
hasConsent: () => hasConsent,
|
|
29
36
|
initNavMenus: () => initNavMenus,
|
|
30
37
|
openTransparencyPanel: () => openTransparencyPanel,
|
|
31
|
-
renderTransparencyPanel: () => renderTransparencyPanel
|
|
38
|
+
renderTransparencyPanel: () => renderTransparencyPanel,
|
|
39
|
+
setConsent: () => setConsent
|
|
32
40
|
});
|
|
33
41
|
|
|
34
42
|
// src/context.ts
|
|
@@ -904,7 +912,9 @@ var Kywi = (() => {
|
|
|
904
912
|
el.removeAttribute("aria-busy");
|
|
905
913
|
const skeleton = el.querySelector(".kywi-variant-skeleton");
|
|
906
914
|
if (skeleton) skeleton.remove();
|
|
907
|
-
const
|
|
915
|
+
const requested = winningAudienceId ?? "default";
|
|
916
|
+
const hasMatch = arms.some((child) => child.getAttribute("data-kywi-variant") === requested);
|
|
917
|
+
const target = hasMatch ? requested : "default";
|
|
908
918
|
el.setAttribute("data-variant", target);
|
|
909
919
|
for (const child of arms) {
|
|
910
920
|
const h = child;
|
|
@@ -999,14 +1009,26 @@ var Kywi = (() => {
|
|
|
999
1009
|
async function pinAudience(audienceId, opts = {}) {
|
|
1000
1010
|
const apiBase = opts.apiBase ?? DEFAULT_API_BASE;
|
|
1001
1011
|
try {
|
|
1002
|
-
await fetch(`${apiBase}/kywi/audience`, {
|
|
1012
|
+
const res = await fetch(`${apiBase}/kywi/audience`, {
|
|
1003
1013
|
method: "POST",
|
|
1004
1014
|
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
1005
|
-
body: JSON.stringify({ audienceId })
|
|
1015
|
+
body: JSON.stringify({ audienceId, source: "switcher" })
|
|
1006
1016
|
});
|
|
1017
|
+
if (!res.ok) return false;
|
|
1018
|
+
const body = await res.json().catch(() => null);
|
|
1019
|
+
return body?.data?.persisted !== false;
|
|
1007
1020
|
} catch {
|
|
1021
|
+
return false;
|
|
1008
1022
|
}
|
|
1009
1023
|
}
|
|
1024
|
+
function showSwitcherError(group) {
|
|
1025
|
+
group.parentElement?.querySelector(".kywi-transparency-switcher-error")?.remove();
|
|
1026
|
+
const note = document.createElement("p");
|
|
1027
|
+
note.className = "kywi-transparency-switcher-error";
|
|
1028
|
+
note.setAttribute("role", "status");
|
|
1029
|
+
note.textContent = "Couldn't save that choice. It won't be remembered on the next page.";
|
|
1030
|
+
group.parentElement?.appendChild(note);
|
|
1031
|
+
}
|
|
1010
1032
|
function reload() {
|
|
1011
1033
|
try {
|
|
1012
1034
|
window.location.reload();
|
|
@@ -1111,7 +1133,12 @@ var Kywi = (() => {
|
|
|
1111
1133
|
const isCurrent = option.id === state.audienceId;
|
|
1112
1134
|
const optionBtn = button("kywi-transparency-option", option.name, () => {
|
|
1113
1135
|
if (isCurrent) return;
|
|
1114
|
-
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1136
|
+
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1137
|
+
(persisted) => {
|
|
1138
|
+
if (persisted) reload();
|
|
1139
|
+
else showSwitcherError(group);
|
|
1140
|
+
}
|
|
1141
|
+
);
|
|
1115
1142
|
});
|
|
1116
1143
|
optionBtn.dataset.kywiAudienceId = option.id;
|
|
1117
1144
|
optionBtn.setAttribute("aria-pressed", String(isCurrent));
|
|
@@ -1242,6 +1269,74 @@ var Kywi = (() => {
|
|
|
1242
1269
|
return { audienceId: m("audience-id"), experimentId: m("experiment-id"), variantId: m("variant-id"), visitorId: m("visitor-id") ?? "" };
|
|
1243
1270
|
}
|
|
1244
1271
|
|
|
1272
|
+
// src/consent.ts
|
|
1273
|
+
var CONSENT_COOKIE = "kywi_consent";
|
|
1274
|
+
var AUDIENCE_PIN_COOKIE = "kywi_audience";
|
|
1275
|
+
var SWITCHER_PIN_PREFIX = "switcher:";
|
|
1276
|
+
var COOKIE_CONSENT_CATEGORY = {
|
|
1277
|
+
kywi_consent: "essential",
|
|
1278
|
+
kywi_optout: "essential",
|
|
1279
|
+
kywi_preview_init: "essential",
|
|
1280
|
+
kywi_signals: "personalization",
|
|
1281
|
+
kywi_visitor: "personalization",
|
|
1282
|
+
kywi_utm: "personalization",
|
|
1283
|
+
kywi_audience: "personalization",
|
|
1284
|
+
kywi_known: "personalization"
|
|
1285
|
+
};
|
|
1286
|
+
function getConsentState() {
|
|
1287
|
+
const cookies = readCookies();
|
|
1288
|
+
const raw = cookies.get(CONSENT_COOKIE);
|
|
1289
|
+
if (!raw) return null;
|
|
1290
|
+
let parsed;
|
|
1291
|
+
try {
|
|
1292
|
+
parsed = JSON.parse(raw);
|
|
1293
|
+
} catch {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1297
|
+
const obj = parsed;
|
|
1298
|
+
return {
|
|
1299
|
+
essential: true,
|
|
1300
|
+
analytics: obj.analytics === true,
|
|
1301
|
+
marketing: obj.marketing === true,
|
|
1302
|
+
personalization: obj.personalization === true
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function setConsent(prefs) {
|
|
1306
|
+
const state = {
|
|
1307
|
+
essential: true,
|
|
1308
|
+
analytics: prefs.analytics ?? false,
|
|
1309
|
+
marketing: prefs.marketing ?? false,
|
|
1310
|
+
personalization: prefs.personalization ?? false
|
|
1311
|
+
};
|
|
1312
|
+
setCookie(CONSENT_COOKIE, JSON.stringify(state), 31536e3);
|
|
1313
|
+
clearDeniedCookies(state);
|
|
1314
|
+
}
|
|
1315
|
+
function clearDeniedCookies(state) {
|
|
1316
|
+
if (typeof document === "undefined") return [];
|
|
1317
|
+
const cookies = readCookies();
|
|
1318
|
+
const deleted = [];
|
|
1319
|
+
for (const [name, category] of Object.entries(COOKIE_CONSENT_CATEGORY)) {
|
|
1320
|
+
if (category === "essential") continue;
|
|
1321
|
+
if (state && state[category] === true) continue;
|
|
1322
|
+
if (isDeliberatePreference(name, cookies.get(name))) continue;
|
|
1323
|
+
deleteCookie(name);
|
|
1324
|
+
deleted.push(name);
|
|
1325
|
+
}
|
|
1326
|
+
return deleted;
|
|
1327
|
+
}
|
|
1328
|
+
function isDeliberatePreference(name, value) {
|
|
1329
|
+
if (name !== AUDIENCE_PIN_COOKIE || value === void 0) return false;
|
|
1330
|
+
return value.startsWith(SWITCHER_PIN_PREFIX) && value.length > SWITCHER_PIN_PREFIX.length;
|
|
1331
|
+
}
|
|
1332
|
+
function hasConsent(category, options = {}) {
|
|
1333
|
+
if (options.requireConsent === false) return true;
|
|
1334
|
+
if (category === "essential") return true;
|
|
1335
|
+
const state = getConsentState();
|
|
1336
|
+
if (!state) return false;
|
|
1337
|
+
return state[category] === true;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1245
1340
|
// src/audience/adapter-runner.ts
|
|
1246
1341
|
var CACHE_PREFIX = "kywi_adapter_";
|
|
1247
1342
|
function getCached(adapterId) {
|
|
@@ -1587,9 +1682,12 @@ var Kywi = (() => {
|
|
|
1587
1682
|
return;
|
|
1588
1683
|
}
|
|
1589
1684
|
const cookies = readCookies();
|
|
1685
|
+
const consentGate = config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent };
|
|
1686
|
+
const mayPersonalize = hasConsent("personalization", consentGate);
|
|
1687
|
+
const storedVisitorId = mayPersonalize ? cookies.get(COOKIE_NAMES.VISITOR) : void 0;
|
|
1590
1688
|
let merged = {
|
|
1591
1689
|
...serverSignals,
|
|
1592
|
-
identity: { visitorId: serverMeta.visitorId ||
|
|
1690
|
+
identity: { visitorId: serverMeta.visitorId || storedVisitorId || "", isKnown: mayPersonalize && cookies.has(COOKIE_NAMES.KNOWN), maLeadId: null },
|
|
1593
1691
|
meta: { isOptedOut: cookies.has(COOKIE_NAMES.OPTOUT), isPreview: false, previewAudienceId: null, resolvedAt: "server" }
|
|
1594
1692
|
};
|
|
1595
1693
|
if (config.adapters && config.adapters.length > 0) {
|
|
@@ -1597,7 +1695,8 @@ var Kywi = (() => {
|
|
|
1597
1695
|
merged.adapters = { ...merged.adapters, ...adapterResults };
|
|
1598
1696
|
}
|
|
1599
1697
|
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory);
|
|
1600
|
-
const
|
|
1698
|
+
const rawPin = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
|
|
1699
|
+
const pinnedId = rawPin?.startsWith("switcher:") ? rawPin.slice("switcher:".length) || null : rawPin;
|
|
1601
1700
|
const isPinned = !result.isOptedOut && pinnedId !== null && activeAudiences.some((a) => a.id === pinnedId);
|
|
1602
1701
|
const effectiveAudienceId = isPinned ? pinnedId : result.winningAudienceId;
|
|
1603
1702
|
patchVariantContainers(effectiveAudienceId);
|
|
@@ -1614,11 +1713,14 @@ var Kywi = (() => {
|
|
|
1614
1713
|
renderPersonalizationBadge({ audienceName: nameOf(effectiveAudienceId) });
|
|
1615
1714
|
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1616
1715
|
fireKywiReady();
|
|
1617
|
-
if (!isPinned && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1716
|
+
if (!isPinned && mayPersonalize && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1618
1717
|
fetch(`${apiBase}/kywi/audience`, {
|
|
1619
1718
|
method: "POST",
|
|
1620
1719
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
1621
|
-
|
|
1720
|
+
// Explicitly the AUTOMATIC write, so the server applies the
|
|
1721
|
+
// personalization gate to it (kywi-cms#91). The transparency panel's
|
|
1722
|
+
// hand-operated switcher sends source:'switcher' and is exempt.
|
|
1723
|
+
body: JSON.stringify({ audienceId: result.winningAudienceId, source: "boot" })
|
|
1622
1724
|
}).catch(() => {
|
|
1623
1725
|
});
|
|
1624
1726
|
}
|