@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-patcher.d.ts","sourceRoot":"","sources":["../../src/audience/dom-patcher.ts"],"names":[],"mappings":"AACA,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"dom-patcher.d.ts","sourceRoot":"","sources":["../../src/audience/dom-patcher.ts"],"names":[],"mappings":"AACA,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAwC7E"}
|
package/dist/audience/index.d.ts
CHANGED
|
@@ -26,6 +26,14 @@ export interface AudienceBootConfig {
|
|
|
26
26
|
* the host page passes the resolved config value through.
|
|
27
27
|
*/
|
|
28
28
|
transparencyNotice?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* `theme.personalization.requireConsent` (kywi-cms#91). Defaults to true —
|
|
31
|
+
* the resolved audience is only remembered for a visitor who consented to
|
|
32
|
+
* personalization. `false` turns that gate off for a site whose consent is
|
|
33
|
+
* collected by external tooling; the server applies the same flag, so the two
|
|
34
|
+
* must agree or the request is made and then refused persistence.
|
|
35
|
+
*/
|
|
36
|
+
requireConsent?: boolean;
|
|
29
37
|
}
|
|
30
38
|
export declare function bootAudienceEngine(config: AudienceBootConfig): Promise<void>;
|
|
31
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audience/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audience/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAUnF,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAGpG,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IACvC,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC,CAAA;IAC9J,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAUD,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiIlF"}
|
|
@@ -31,13 +31,22 @@ export declare function optIn(): void;
|
|
|
31
31
|
export declare function resetToAutomatic(): void;
|
|
32
32
|
/**
|
|
33
33
|
* Pins the visitor to `audienceId` via the public runtime route — the same
|
|
34
|
-
* endpoint
|
|
35
|
-
*
|
|
34
|
+
* endpoint the boot path uses, but marked `source: 'switcher'`. That flag is
|
|
35
|
+
* what tells the server this is the visitor operating a control by hand rather
|
|
36
|
+
* than the runtime remembering something automatically, which is why it is
|
|
37
|
+
* honoured as an `essential` preference and not gated on personalization consent
|
|
38
|
+
* (kywi-cms#91; the same argument as kywi_optout — see the block above).
|
|
39
|
+
*
|
|
40
|
+
* The server re-validates the id against the active audiences.
|
|
41
|
+
*
|
|
42
|
+
* Returns whether the pin will SURVIVE this page: false when the server declined
|
|
43
|
+
* to store it (an older server that predates `source`, or a refusal), so the
|
|
44
|
+
* caller can say so instead of reloading into an unchanged page.
|
|
36
45
|
*/
|
|
37
46
|
export declare function pinAudience(audienceId: string, opts?: {
|
|
38
47
|
apiBase?: string | undefined;
|
|
39
48
|
csrfToken?: string | null | undefined;
|
|
40
|
-
}): Promise<
|
|
49
|
+
}): Promise<boolean>;
|
|
41
50
|
/**
|
|
42
51
|
* Renders (or removes) the transparency surface for `state`. Safe to call on
|
|
43
52
|
* every boot; it replaces whatever is currently mounted.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transparency.d.ts","sourceRoot":"","sources":["../../src/audience/transparency.ts"],"names":[],"mappings":"AAiCA,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,cAAc,EAAE,OAAO,CAAA;IACvB,UAAU,EAAE,OAAO,CAAA;IACnB,8EAA8E;IAC9E,iBAAiB,EAAE,OAAO,CAAA;IAC1B,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAA;IAClC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAoBD,8EAA8E;AAC9E,wBAAgB,WAAW,IAAI,OAAO,CASrC;
|
|
1
|
+
{"version":3,"file":"transparency.d.ts","sourceRoot":"","sources":["../../src/audience/transparency.ts"],"names":[],"mappings":"AAiCA,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,cAAc,EAAE,OAAO,CAAA;IACvB,UAAU,EAAE,OAAO,CAAA;IACnB,8EAA8E;IAC9E,iBAAiB,EAAE,OAAO,CAAA;IAC1B,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAA;IAClC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAoBD,8EAA8E;AAC9E,wBAAgB,WAAW,IAAI,OAAO,CASrC;AAeD,wBAAgB,MAAM,IAAI,IAAI,CAG7B;AAED,wBAAgB,KAAK,IAAI,IAAI,CAE5B;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAO,GACjF,OAAO,CAAC,OAAO,CAAC,CAkBlB;AA+CD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAqItE;AAwBD,kDAAkD;AAClD,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,oFAAoF;AACpF,wBAAgB,aAAa,IAAI,IAAI,CAIpC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAU5C;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
|
package/dist/consent.d.ts
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
export declare const CONSENT_COOKIE: "kywi_consent";
|
|
2
|
+
/** The cookie an audience pin lives in. */
|
|
3
|
+
export declare const AUDIENCE_PIN_COOKIE: "kywi_audience";
|
|
4
|
+
/**
|
|
5
|
+
* The `kywi_audience` value prefix marking a pin the visitor chose BY HAND in
|
|
6
|
+
* the transparency panel, rather than one the runtime remembered on its own.
|
|
7
|
+
* Mirrors `SWITCHER_PIN_PREFIX` in core's `audiences/consent.ts`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SWITCHER_PIN_PREFIX: "switcher:";
|
|
2
10
|
export type ConsentCategory = 'essential' | 'analytics' | 'marketing' | 'personalization';
|
|
11
|
+
/**
|
|
12
|
+
* Every cookie the engine writes → the consent category it requires.
|
|
13
|
+
*
|
|
14
|
+
* A MIRROR of `COOKIE_CONSENT_CATEGORY` in `@kywi-software/core`'s
|
|
15
|
+
* `audiences/consent.ts`, which is the authority. This package cannot import
|
|
16
|
+
* core (neither depends on the other), so the copy exists to let a decision made
|
|
17
|
+
* here REVOKE the cookies it denies without a round trip.
|
|
18
|
+
* `__tests__/consent-parity.test.ts` asserts the two maps are identical.
|
|
19
|
+
*/
|
|
20
|
+
export declare const COOKIE_CONSENT_CATEGORY: Record<string, ConsentCategory>;
|
|
21
|
+
/** Mirrors `ConsentGateOptions` in core's `audiences/consent.ts`. */
|
|
22
|
+
export interface ConsentGateOptions {
|
|
23
|
+
/**
|
|
24
|
+
* `theme.personalization.requireConsent`. Defaults to true. When explicitly
|
|
25
|
+
* `false` the gate is off and every category reads as allowed — for a site
|
|
26
|
+
* whose consent is collected by external tooling (kywi-cms#91).
|
|
27
|
+
*/
|
|
28
|
+
requireConsent?: boolean;
|
|
29
|
+
}
|
|
3
30
|
export interface ConsentState {
|
|
4
31
|
essential: boolean;
|
|
5
32
|
analytics: boolean;
|
|
@@ -9,6 +36,19 @@ export interface ConsentState {
|
|
|
9
36
|
/**
|
|
10
37
|
* Read the current consent state from the `kywi_consent` cookie.
|
|
11
38
|
* Returns the parsed ConsentState, or null if the cookie is missing or malformed.
|
|
39
|
+
*
|
|
40
|
+
* The payload is NORMALIZED, not trusted: `essential` is forced true and every
|
|
41
|
+
* other category is `=== true` or false. The cookie is client-writable, so it
|
|
42
|
+
* can arrive partial (written by an older version), tampered with, or carrying
|
|
43
|
+
* `"yes"` where a boolean belongs. Casting it to `ConsentState` and hoping left
|
|
44
|
+
* callers reading `state.marketing` a truthy string. This mirrors
|
|
45
|
+
* `parseConsentValue` in core's `audiences/consent.ts` exactly, so the client
|
|
46
|
+
* and the server can never disagree about what a given cookie means
|
|
47
|
+
* (`__tests__/consent-parity.test.ts` pins that).
|
|
48
|
+
*
|
|
49
|
+
* Unlike core's version this does NOT decodeURIComponent: `readCookies` has
|
|
50
|
+
* already decoded the value, and decoding twice would corrupt a payload
|
|
51
|
+
* containing a literal `%`.
|
|
12
52
|
*/
|
|
13
53
|
export declare function getConsentState(): ConsentState | null;
|
|
14
54
|
/**
|
|
@@ -16,9 +56,36 @@ export declare function getConsentState(): ConsentState | null;
|
|
|
16
56
|
* The cookie is stored with a 1-year Max-Age.
|
|
17
57
|
*/
|
|
18
58
|
export declare function setConsent(prefs: Partial<Omit<ConsentState, 'essential'>>): void;
|
|
59
|
+
/**
|
|
60
|
+
* Delete every cookie whose category `state` does not grant, and return the
|
|
61
|
+
* names deleted.
|
|
62
|
+
*
|
|
63
|
+
* Refusing to WRITE new cookies is only half of "Reject All": a visitor who
|
|
64
|
+
* accepted last month and rejects today is still carrying a `kywi_visitor` id,
|
|
65
|
+
* and a gate that only blocked writes would keep reading it forever
|
|
66
|
+
* (kywi-cms#91).
|
|
67
|
+
*
|
|
68
|
+
* `kywi_signals` is HttpOnly and cannot be removed from script; the attempt is
|
|
69
|
+
* harmless, the server stops writing it, and it expires on its own 60-second
|
|
70
|
+
* horizon. Core's server-side read paths also ignore personalization cookies
|
|
71
|
+
* whenever the gate denies, so a cookie script cannot clear is inert anyway.
|
|
72
|
+
*/
|
|
73
|
+
export declare function clearDeniedCookies(state: ConsentState | null): string[];
|
|
19
74
|
/**
|
|
20
75
|
* Check whether a specific consent category has been granted.
|
|
21
|
-
*
|
|
76
|
+
*
|
|
77
|
+
* `essential` is ALWAYS granted, decision or not — necessary cookies (the
|
|
78
|
+
* consent record itself, the personalization opt-out flag) are never gated, and
|
|
79
|
+
* before kywi-cms#91 that was true only by accident because nothing called this
|
|
80
|
+
* function at all. Every other category needs an explicit `true`, so an
|
|
81
|
+
* undecided visitor grants nothing and an unknown category is denied.
|
|
82
|
+
*
|
|
83
|
+
* `requireConsent: false` (from `theme.personalization.requireConsent`) turns the
|
|
84
|
+
* gate off entirely for a site whose consent is collected by external tooling.
|
|
85
|
+
*
|
|
86
|
+
* The authoritative cookie→category map lives in `@kywi-software/core`'s
|
|
87
|
+
* `audiences/consent.ts` (`COOKIE_CONSENT_CATEGORY`) — this package cannot
|
|
88
|
+
* import core, so `__tests__/consent-parity.test.ts` asserts the two agree.
|
|
22
89
|
*/
|
|
23
|
-
export declare function hasConsent(category: ConsentCategory): boolean;
|
|
90
|
+
export declare function hasConsent(category: ConsentCategory, options?: ConsentGateOptions): boolean;
|
|
24
91
|
//# sourceMappingURL=consent.d.ts.map
|
package/dist/consent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,EAAG,cAAuB,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,iBAAiB,CAAA;AAEzF,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,eAAe,EAAE,OAAO,CAAA;CACzB;AAED
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,EAAG,cAAuB,CAAA;AAErD,2CAA2C;AAC3C,eAAO,MAAM,mBAAmB,EAAG,eAAwB,CAAA;AAE3D;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAG,WAAoB,CAAA;AAEvD,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,iBAAiB,CAAA;AAEzF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CASnE,CAAA;AAED,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,eAAe,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAmBrD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,GAC9C,IAAI,CAYN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,EAAE,CAYvE;AAyBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAM/F"}
|
package/dist/index.d.ts
CHANGED
|
@@ -23,4 +23,5 @@ export { bootAudienceEngine } from './audience/index.js';
|
|
|
23
23
|
export { openTransparencyPanel, renderTransparencyPanel, type TransparencyState, type TransparencyAudience, } from './audience/transparency.js';
|
|
24
24
|
export { bootExperiments, type ExperimentRuntime, type ConversionSignal } from './experiments/index.js';
|
|
25
25
|
export { initNavMenus, enhanceNav, NAV_BREAKPOINT } from './nav/menu.js';
|
|
26
|
+
export { getConsentState, setConsent, hasConsent, clearDeniedCookies, COOKIE_CONSENT_CATEGORY, CONSENT_COOKIE, AUDIENCE_PIN_COOKIE, SWITCHER_PIN_PREFIX, type ConsentCategory, type ConsentGateOptions, type ConsentState, } from './consent.js';
|
|
26
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAUnE,KAAK,eAAe,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,KAAK,IAAI,CAAA;AAIlD,eAAO,MAAM,IAAI;;aAEa,WAAW;YACzB,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC;gBACtC,eAAe,GAAG,IAAI;;2BAIb,oBAAoB,GAAG,oBAAoB;;;;oBAM9C,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;4BAC7C,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,IAAI;;;eAK5C,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG;aACvB,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG;gBAClB,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI;YAC7C,IAAI;CAkBb,CAAA;AAWD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACvG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAUnE,KAAK,eAAe,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,KAAK,IAAI,CAAA;AAIlD,eAAO,MAAM,IAAI;;aAEa,WAAW;YACzB,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC;gBACtC,eAAe,GAAG,IAAI;;2BAIb,oBAAoB,GAAG,oBAAoB;;;;oBAM9C,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;4BAC7C,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,IAAI;;;eAK5C,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG;aACvB,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG;gBAClB,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI;YAC7C,IAAI;CAkBb,CAAA;AAWD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACvG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAIxE,OAAO,EACL,eAAe,EACf,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAA"}
|
package/dist/kywi.esm.js
CHANGED
|
@@ -871,7 +871,9 @@ function patchVariantContainers(winningAudienceId) {
|
|
|
871
871
|
el.removeAttribute("aria-busy");
|
|
872
872
|
const skeleton = el.querySelector(".kywi-variant-skeleton");
|
|
873
873
|
if (skeleton) skeleton.remove();
|
|
874
|
-
const
|
|
874
|
+
const requested = winningAudienceId ?? "default";
|
|
875
|
+
const hasMatch = arms.some((child) => child.getAttribute("data-kywi-variant") === requested);
|
|
876
|
+
const target = hasMatch ? requested : "default";
|
|
875
877
|
el.setAttribute("data-variant", target);
|
|
876
878
|
for (const child of arms) {
|
|
877
879
|
const h = child;
|
|
@@ -966,14 +968,26 @@ function hasPin() {
|
|
|
966
968
|
async function pinAudience(audienceId, opts = {}) {
|
|
967
969
|
const apiBase = opts.apiBase ?? DEFAULT_API_BASE;
|
|
968
970
|
try {
|
|
969
|
-
await fetch(`${apiBase}/kywi/audience`, {
|
|
971
|
+
const res = await fetch(`${apiBase}/kywi/audience`, {
|
|
970
972
|
method: "POST",
|
|
971
973
|
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
972
|
-
body: JSON.stringify({ audienceId })
|
|
974
|
+
body: JSON.stringify({ audienceId, source: "switcher" })
|
|
973
975
|
});
|
|
976
|
+
if (!res.ok) return false;
|
|
977
|
+
const body = await res.json().catch(() => null);
|
|
978
|
+
return body?.data?.persisted !== false;
|
|
974
979
|
} catch {
|
|
980
|
+
return false;
|
|
975
981
|
}
|
|
976
982
|
}
|
|
983
|
+
function showSwitcherError(group) {
|
|
984
|
+
group.parentElement?.querySelector(".kywi-transparency-switcher-error")?.remove();
|
|
985
|
+
const note = document.createElement("p");
|
|
986
|
+
note.className = "kywi-transparency-switcher-error";
|
|
987
|
+
note.setAttribute("role", "status");
|
|
988
|
+
note.textContent = "Couldn't save that choice. It won't be remembered on the next page.";
|
|
989
|
+
group.parentElement?.appendChild(note);
|
|
990
|
+
}
|
|
977
991
|
function reload() {
|
|
978
992
|
try {
|
|
979
993
|
window.location.reload();
|
|
@@ -1078,7 +1092,12 @@ function renderTransparencyPanel(state) {
|
|
|
1078
1092
|
const isCurrent = option.id === state.audienceId;
|
|
1079
1093
|
const optionBtn = button("kywi-transparency-option", option.name, () => {
|
|
1080
1094
|
if (isCurrent) return;
|
|
1081
|
-
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1095
|
+
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(
|
|
1096
|
+
(persisted) => {
|
|
1097
|
+
if (persisted) reload();
|
|
1098
|
+
else showSwitcherError(group);
|
|
1099
|
+
}
|
|
1100
|
+
);
|
|
1082
1101
|
});
|
|
1083
1102
|
optionBtn.dataset.kywiAudienceId = option.id;
|
|
1084
1103
|
optionBtn.setAttribute("aria-pressed", String(isCurrent));
|
|
@@ -1209,6 +1228,74 @@ function readServerMeta() {
|
|
|
1209
1228
|
return { audienceId: m("audience-id"), experimentId: m("experiment-id"), variantId: m("variant-id"), visitorId: m("visitor-id") ?? "" };
|
|
1210
1229
|
}
|
|
1211
1230
|
|
|
1231
|
+
// src/consent.ts
|
|
1232
|
+
var CONSENT_COOKIE = "kywi_consent";
|
|
1233
|
+
var AUDIENCE_PIN_COOKIE = "kywi_audience";
|
|
1234
|
+
var SWITCHER_PIN_PREFIX = "switcher:";
|
|
1235
|
+
var COOKIE_CONSENT_CATEGORY = {
|
|
1236
|
+
kywi_consent: "essential",
|
|
1237
|
+
kywi_optout: "essential",
|
|
1238
|
+
kywi_preview_init: "essential",
|
|
1239
|
+
kywi_signals: "personalization",
|
|
1240
|
+
kywi_visitor: "personalization",
|
|
1241
|
+
kywi_utm: "personalization",
|
|
1242
|
+
kywi_audience: "personalization",
|
|
1243
|
+
kywi_known: "personalization"
|
|
1244
|
+
};
|
|
1245
|
+
function getConsentState() {
|
|
1246
|
+
const cookies = readCookies();
|
|
1247
|
+
const raw = cookies.get(CONSENT_COOKIE);
|
|
1248
|
+
if (!raw) return null;
|
|
1249
|
+
let parsed;
|
|
1250
|
+
try {
|
|
1251
|
+
parsed = JSON.parse(raw);
|
|
1252
|
+
} catch {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1256
|
+
const obj = parsed;
|
|
1257
|
+
return {
|
|
1258
|
+
essential: true,
|
|
1259
|
+
analytics: obj.analytics === true,
|
|
1260
|
+
marketing: obj.marketing === true,
|
|
1261
|
+
personalization: obj.personalization === true
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function setConsent(prefs) {
|
|
1265
|
+
const state = {
|
|
1266
|
+
essential: true,
|
|
1267
|
+
analytics: prefs.analytics ?? false,
|
|
1268
|
+
marketing: prefs.marketing ?? false,
|
|
1269
|
+
personalization: prefs.personalization ?? false
|
|
1270
|
+
};
|
|
1271
|
+
setCookie(CONSENT_COOKIE, JSON.stringify(state), 31536e3);
|
|
1272
|
+
clearDeniedCookies(state);
|
|
1273
|
+
}
|
|
1274
|
+
function clearDeniedCookies(state) {
|
|
1275
|
+
if (typeof document === "undefined") return [];
|
|
1276
|
+
const cookies = readCookies();
|
|
1277
|
+
const deleted = [];
|
|
1278
|
+
for (const [name, category] of Object.entries(COOKIE_CONSENT_CATEGORY)) {
|
|
1279
|
+
if (category === "essential") continue;
|
|
1280
|
+
if (state && state[category] === true) continue;
|
|
1281
|
+
if (isDeliberatePreference(name, cookies.get(name))) continue;
|
|
1282
|
+
deleteCookie(name);
|
|
1283
|
+
deleted.push(name);
|
|
1284
|
+
}
|
|
1285
|
+
return deleted;
|
|
1286
|
+
}
|
|
1287
|
+
function isDeliberatePreference(name, value) {
|
|
1288
|
+
if (name !== AUDIENCE_PIN_COOKIE || value === void 0) return false;
|
|
1289
|
+
return value.startsWith(SWITCHER_PIN_PREFIX) && value.length > SWITCHER_PIN_PREFIX.length;
|
|
1290
|
+
}
|
|
1291
|
+
function hasConsent(category, options = {}) {
|
|
1292
|
+
if (options.requireConsent === false) return true;
|
|
1293
|
+
if (category === "essential") return true;
|
|
1294
|
+
const state = getConsentState();
|
|
1295
|
+
if (!state) return false;
|
|
1296
|
+
return state[category] === true;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1212
1299
|
// src/audience/adapter-runner.ts
|
|
1213
1300
|
var CACHE_PREFIX = "kywi_adapter_";
|
|
1214
1301
|
function getCached(adapterId) {
|
|
@@ -1554,9 +1641,12 @@ async function bootAudienceEngine(config) {
|
|
|
1554
1641
|
return;
|
|
1555
1642
|
}
|
|
1556
1643
|
const cookies = readCookies();
|
|
1644
|
+
const consentGate = config.requireConsent === void 0 ? {} : { requireConsent: config.requireConsent };
|
|
1645
|
+
const mayPersonalize = hasConsent("personalization", consentGate);
|
|
1646
|
+
const storedVisitorId = mayPersonalize ? cookies.get(COOKIE_NAMES.VISITOR) : void 0;
|
|
1557
1647
|
let merged = {
|
|
1558
1648
|
...serverSignals,
|
|
1559
|
-
identity: { visitorId: serverMeta.visitorId ||
|
|
1649
|
+
identity: { visitorId: serverMeta.visitorId || storedVisitorId || "", isKnown: mayPersonalize && cookies.has(COOKIE_NAMES.KNOWN), maLeadId: null },
|
|
1560
1650
|
meta: { isOptedOut: cookies.has(COOKIE_NAMES.OPTOUT), isPreview: false, previewAudienceId: null, resolvedAt: "server" }
|
|
1561
1651
|
};
|
|
1562
1652
|
if (config.adapters && config.adapters.length > 0) {
|
|
@@ -1564,7 +1654,8 @@ async function bootAudienceEngine(config) {
|
|
|
1564
1654
|
merged.adapters = { ...merged.adapters, ...adapterResults };
|
|
1565
1655
|
}
|
|
1566
1656
|
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory);
|
|
1567
|
-
const
|
|
1657
|
+
const rawPin = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
|
|
1658
|
+
const pinnedId = rawPin?.startsWith("switcher:") ? rawPin.slice("switcher:".length) || null : rawPin;
|
|
1568
1659
|
const isPinned = !result.isOptedOut && pinnedId !== null && activeAudiences.some((a) => a.id === pinnedId);
|
|
1569
1660
|
const effectiveAudienceId = isPinned ? pinnedId : result.winningAudienceId;
|
|
1570
1661
|
patchVariantContainers(effectiveAudienceId);
|
|
@@ -1581,11 +1672,14 @@ async function bootAudienceEngine(config) {
|
|
|
1581
1672
|
renderPersonalizationBadge({ audienceName: nameOf(effectiveAudienceId) });
|
|
1582
1673
|
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1583
1674
|
fireKywiReady();
|
|
1584
|
-
if (!isPinned && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1675
|
+
if (!isPinned && mayPersonalize && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1585
1676
|
fetch(`${apiBase}/kywi/audience`, {
|
|
1586
1677
|
method: "POST",
|
|
1587
1678
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
1588
|
-
|
|
1679
|
+
// Explicitly the AUTOMATIC write, so the server applies the
|
|
1680
|
+
// personalization gate to it (kywi-cms#91). The transparency panel's
|
|
1681
|
+
// hand-operated switcher sends source:'switcher' and is exempt.
|
|
1682
|
+
body: JSON.stringify({ audienceId: result.winningAudienceId, source: "boot" })
|
|
1589
1683
|
}).catch(() => {
|
|
1590
1684
|
});
|
|
1591
1685
|
}
|
|
@@ -1694,13 +1788,21 @@ if (typeof document !== "undefined") {
|
|
|
1694
1788
|
}
|
|
1695
1789
|
}
|
|
1696
1790
|
export {
|
|
1791
|
+
AUDIENCE_PIN_COOKIE,
|
|
1792
|
+
CONSENT_COOKIE,
|
|
1793
|
+
COOKIE_CONSENT_CATEGORY,
|
|
1697
1794
|
Kywi,
|
|
1698
1795
|
NAV_BREAKPOINT,
|
|
1796
|
+
SWITCHER_PIN_PREFIX,
|
|
1699
1797
|
bootAudienceEngine,
|
|
1700
1798
|
bootExperiments,
|
|
1799
|
+
clearDeniedCookies,
|
|
1701
1800
|
enhanceNav,
|
|
1801
|
+
getConsentState,
|
|
1802
|
+
hasConsent,
|
|
1702
1803
|
initNavMenus,
|
|
1703
1804
|
openTransparencyPanel,
|
|
1704
|
-
renderTransparencyPanel
|
|
1805
|
+
renderTransparencyPanel,
|
|
1806
|
+
setConsent
|
|
1705
1807
|
};
|
|
1706
1808
|
//# sourceMappingURL=kywi.esm.js.map
|