@xenia-martech/sdk-js 1.2.0-dev.2 → 1.2.0-dev.4

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.
Files changed (82) hide show
  1. package/README.md +112 -3
  2. package/dist/{actions-BcdlokGO.d.cts → actions-C18Kv-4w.d.cts} +50 -2
  3. package/dist/{actions-BcdlokGO.d.ts → actions-C18Kv-4w.d.ts} +50 -2
  4. package/dist/{api-CktVQ6Gl.d.ts → api-0IycTYJR.d.cts} +4 -2
  5. package/dist/{api-BEPW0Uan.d.cts → api-COXWLL5L.d.ts} +4 -2
  6. package/dist/{chunk-3PYPPIUM.cjs → chunk-2DR5AVPX.cjs} +11 -46
  7. package/dist/chunk-2DR5AVPX.cjs.map +1 -0
  8. package/dist/chunk-BQ5PJCZ7.js +179 -0
  9. package/dist/chunk-BQ5PJCZ7.js.map +1 -0
  10. package/dist/{chunk-DWFVDNU5.js → chunk-GWRDZIVA.js} +5 -37
  11. package/dist/chunk-GWRDZIVA.js.map +1 -0
  12. package/dist/{chunk-7NGJC7CF.js → chunk-LW5AEJ6K.js} +20 -183
  13. package/dist/chunk-LW5AEJ6K.js.map +1 -0
  14. package/dist/chunk-N5M4PI7C.cjs +192 -0
  15. package/dist/chunk-N5M4PI7C.cjs.map +1 -0
  16. package/dist/chunk-OMMIRSPK.js +336 -0
  17. package/dist/chunk-OMMIRSPK.js.map +1 -0
  18. package/dist/chunk-QEYONKWY.js +42 -0
  19. package/dist/chunk-QEYONKWY.js.map +1 -0
  20. package/dist/{chunk-IBNIRT7J.cjs → chunk-RWE2OCQL.cjs} +20 -194
  21. package/dist/chunk-RWE2OCQL.cjs.map +1 -0
  22. package/dist/chunk-WHGK55JP.cjs +46 -0
  23. package/dist/chunk-WHGK55JP.cjs.map +1 -0
  24. package/dist/chunk-YLMMTKQL.cjs +358 -0
  25. package/dist/chunk-YLMMTKQL.cjs.map +1 -0
  26. package/dist/client/index.cjs +320 -31
  27. package/dist/client/index.cjs.map +1 -1
  28. package/dist/client/index.d.cts +15 -4
  29. package/dist/client/index.d.ts +15 -4
  30. package/dist/client/index.js +320 -31
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/client/react/index.cjs +731 -34
  33. package/dist/client/react/index.cjs.map +1 -1
  34. package/dist/client/react/index.d.cts +32 -3
  35. package/dist/client/react/index.d.ts +32 -3
  36. package/dist/client/react/index.js +732 -37
  37. package/dist/client/react/index.js.map +1 -1
  38. package/dist/cms/contentful.cjs +2 -1
  39. package/dist/cms/contentful.cjs.map +1 -1
  40. package/dist/cms/contentful.d.cts +2 -1
  41. package/dist/cms/contentful.d.ts +2 -1
  42. package/dist/cms/contentful.js +2 -1
  43. package/dist/cms/contentful.js.map +1 -1
  44. package/dist/consent-Cemn91j3.d.ts +248 -0
  45. package/dist/consent-DgWdjsSy.d.cts +248 -0
  46. package/dist/{cookie-names-CLqtmGBM.d.ts → cookie-names-BvUaW3uo.d.ts} +1 -1
  47. package/dist/{cookie-names-BACctagz.d.cts → cookie-names-CnL65Oly.d.cts} +1 -1
  48. package/dist/edge/index.cjs +280 -0
  49. package/dist/edge/index.cjs.map +1 -0
  50. package/dist/edge/index.d.cts +61 -0
  51. package/dist/edge/index.d.ts +61 -0
  52. package/dist/edge/index.js +191 -0
  53. package/dist/edge/index.js.map +1 -0
  54. package/dist/index.cjs +41 -40
  55. package/dist/index.d.cts +5 -4
  56. package/dist/index.d.ts +5 -4
  57. package/dist/index.js +2 -1
  58. package/dist/next/index.cjs +320 -15
  59. package/dist/next/index.cjs.map +1 -1
  60. package/dist/next/index.d.cts +180 -6
  61. package/dist/next/index.d.ts +180 -6
  62. package/dist/next/index.js +309 -9
  63. package/dist/next/index.js.map +1 -1
  64. package/dist/personalize-content-c2DDAV6g.d.ts +140 -0
  65. package/dist/personalize-content-ghZPKlf1.d.cts +140 -0
  66. package/dist/server/index.cjs +34 -32
  67. package/dist/server/index.cjs.map +1 -1
  68. package/dist/server/index.d.cts +6 -5
  69. package/dist/server/index.d.ts +6 -5
  70. package/dist/server/index.js +5 -3
  71. package/dist/server/index.js.map +1 -1
  72. package/dist/{personalize-content-Clc1oMYY.d.ts → types-DgVHS4Qw.d.cts} +7 -127
  73. package/dist/{personalize-content-Clc1oMYY.d.cts → types-DgVHS4Qw.d.ts} +7 -127
  74. package/dist/xenia-sdk.iife.js +3 -3
  75. package/dist/xenia-sdk.iife.js.map +1 -1
  76. package/dist/{xenia-server-o_wDOcei.d.cts → xenia-server-C3O-cWQT.d.cts} +3 -2
  77. package/dist/{xenia-server-BWhWD0La.d.ts → xenia-server-DV8Sjmoo.d.ts} +3 -2
  78. package/package.json +14 -1
  79. package/dist/chunk-3PYPPIUM.cjs.map +0 -1
  80. package/dist/chunk-7NGJC7CF.js.map +0 -1
  81. package/dist/chunk-DWFVDNU5.js.map +0 -1
  82. package/dist/chunk-IBNIRT7J.cjs.map +0 -1
@@ -0,0 +1,179 @@
1
+ // src/core/http-client.ts
2
+ var DEFAULT_BASE_URL = "https://engine.xeniamartech.com/xenia";
3
+ function isBrowser() {
4
+ return typeof window !== "undefined" && typeof document !== "undefined";
5
+ }
6
+ function buildUrl(url, queryParams) {
7
+ if (!queryParams || Object.keys(queryParams).length === 0) return url;
8
+ const params = new URLSearchParams(queryParams);
9
+ const sep = url.includes("?") ? "&" : "?";
10
+ return `${url}${sep}${params.toString()}`;
11
+ }
12
+ function delay(ms) {
13
+ return new Promise((resolve) => setTimeout(resolve, ms));
14
+ }
15
+ var HttpError = class extends Error {
16
+ constructor(status, data, message) {
17
+ super(message ?? `Request failed with status ${status}`);
18
+ this.status = status;
19
+ this.data = data;
20
+ this.name = "HttpError";
21
+ }
22
+ status;
23
+ data;
24
+ };
25
+ var CircuitOpenError = class extends Error {
26
+ constructor(endpoint) {
27
+ super(`Circuit open for ${endpoint}: failing fast until the cooldown passes`);
28
+ this.endpoint = endpoint;
29
+ this.name = "CircuitOpenError";
30
+ }
31
+ endpoint;
32
+ };
33
+ var breakers = /* @__PURE__ */ new Map();
34
+ var HttpClient = class {
35
+ baseUrl;
36
+ defaultHeaders;
37
+ timeout;
38
+ retry;
39
+ debug;
40
+ breaker;
41
+ constructor(opts) {
42
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
43
+ this.defaultHeaders = opts.defaultHeaders ?? {};
44
+ this.timeout = opts.timeout ?? 8e3;
45
+ this.retry = {
46
+ attempts: opts.retry?.attempts ?? 2,
47
+ baseDelayMs: opts.retry?.baseDelayMs ?? 300
48
+ };
49
+ this.debug = opts.debug ?? false;
50
+ const cb = opts.circuitBreaker === true ? {} : opts.circuitBreaker || null;
51
+ this.breaker = cb && { failureThreshold: cb.failureThreshold ?? 3, cooldownMs: cb.cooldownMs ?? 3e4 };
52
+ }
53
+ async request(opts) {
54
+ if (!this.breaker) return this.send(opts);
55
+ const key = buildUrl(this.baseUrl + opts.path, opts.query);
56
+ const b = breakers.get(key) ?? { failures: 0, openedAt: 0 };
57
+ if (b.openedAt) {
58
+ if (Date.now() - b.openedAt < this.breaker.cooldownMs) throw new CircuitOpenError(key);
59
+ b.openedAt = 0;
60
+ b.failures = this.breaker.failureThreshold - 1;
61
+ }
62
+ try {
63
+ const result = await this.send(opts);
64
+ breakers.delete(key);
65
+ return result;
66
+ } catch (err) {
67
+ if (err instanceof HttpError && !this.isRetryableStatus(err.status)) {
68
+ breakers.delete(key);
69
+ throw err;
70
+ }
71
+ b.failures += 1;
72
+ if (b.failures >= this.breaker.failureThreshold) {
73
+ b.openedAt = Date.now();
74
+ this.log(`circuit opened for ${opts.path} after ${b.failures} failures`);
75
+ }
76
+ breakers.set(key, b);
77
+ throw err;
78
+ }
79
+ }
80
+ async send(opts) {
81
+ const url = buildUrl(this.baseUrl + opts.path, opts.query);
82
+ const method = opts.method ?? "GET";
83
+ const headers = {
84
+ "Content-Type": "application/json",
85
+ ...this.defaultHeaders,
86
+ ...opts.headers
87
+ };
88
+ const body = opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
89
+ const timeout = opts.timeout ?? this.timeout;
90
+ const maxAttempts = opts.noRetry ? 1 : (opts.retry?.attempts ?? this.retry.attempts) + 1;
91
+ const baseDelay = opts.retry?.baseDelayMs ?? this.retry.baseDelayMs;
92
+ let lastError;
93
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
94
+ try {
95
+ const response = await this.fetchWithTimeout(
96
+ url,
97
+ { method, headers, body, ...opts.keepalive ? { keepalive: true } : {} },
98
+ timeout
99
+ );
100
+ const data = await this.parseBody(response);
101
+ if (response.ok) {
102
+ return { response, data };
103
+ }
104
+ if (this.isRetryableStatus(response.status) && attempt < maxAttempts - 1) {
105
+ const wait = this.retryAfterMs(response, baseDelay, attempt);
106
+ this.log(`retrying ${opts.path} after ${wait}ms (status ${response.status})`);
107
+ await delay(wait);
108
+ continue;
109
+ }
110
+ throw new HttpError(response.status, data);
111
+ } catch (err) {
112
+ lastError = err;
113
+ if (err instanceof HttpError) throw err;
114
+ if (attempt < maxAttempts - 1) {
115
+ const wait = baseDelay * 2 ** attempt;
116
+ this.log(`retrying ${opts.path} after ${wait}ms (network error)`);
117
+ await delay(wait);
118
+ continue;
119
+ }
120
+ }
121
+ }
122
+ throw lastError instanceof Error ? lastError : new Error("Request failed");
123
+ }
124
+ async fetchWithTimeout(url, init, timeout) {
125
+ const controller = new AbortController();
126
+ const timer = setTimeout(() => controller.abort(), timeout);
127
+ try {
128
+ return await fetch(url, { ...init, signal: controller.signal });
129
+ } catch (err) {
130
+ if (err instanceof Error && err.name === "AbortError") {
131
+ throw new Error(`Request timeout after ${timeout}ms`, { cause: err });
132
+ }
133
+ throw err;
134
+ } finally {
135
+ clearTimeout(timer);
136
+ }
137
+ }
138
+ async parseBody(response) {
139
+ const text = await response.text().catch(() => "");
140
+ if (!text) return null;
141
+ try {
142
+ return JSON.parse(text);
143
+ } catch {
144
+ return text;
145
+ }
146
+ }
147
+ isRetryableStatus(status) {
148
+ return status === 429 || status >= 500 && status <= 599;
149
+ }
150
+ retryAfterMs(response, baseDelay, attempt) {
151
+ const header = response.headers.get("Retry-After");
152
+ if (header) {
153
+ const seconds = Number(header);
154
+ if (!Number.isNaN(seconds)) return seconds * 1e3;
155
+ }
156
+ return baseDelay * 2 ** attempt;
157
+ }
158
+ log(msg) {
159
+ if (this.debug) console.warn(`[xenia] ${msg}`);
160
+ }
161
+ };
162
+
163
+ // src/core/cookie-names.ts
164
+ var DEFAULT_PREFIX = "xenia";
165
+ function visitorCookieName(prefix = DEFAULT_PREFIX) {
166
+ return `${prefix || DEFAULT_PREFIX}_vid`;
167
+ }
168
+ function consentCookieName(prefix = DEFAULT_PREFIX) {
169
+ return `${prefix || DEFAULT_PREFIX}_consent`;
170
+ }
171
+ var VISITOR_COOKIE = visitorCookieName();
172
+ var CONSENT_COOKIE = consentCookieName();
173
+ function consentCookie(state, prefix, maxAgeDays = 365) {
174
+ return `${consentCookieName(prefix)}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${Math.floor(maxAgeDays * 86400)}; SameSite=Lax`;
175
+ }
176
+
177
+ export { CONSENT_COOKIE, CircuitOpenError, DEFAULT_BASE_URL, DEFAULT_PREFIX, HttpClient, HttpError, VISITOR_COOKIE, buildUrl, consentCookie, consentCookieName, isBrowser, visitorCookieName };
178
+ //# sourceMappingURL=chunk-BQ5PJCZ7.js.map
179
+ //# sourceMappingURL=chunk-BQ5PJCZ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/http-client.ts","../src/core/cookie-names.ts"],"names":[],"mappings":"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","file":"chunk-BQ5PJCZ7.js","sourcesContent":["import type { CircuitBreakerConfig, HttpMethod, JsonValue, RetryConfig } from \"./types\";\n\n/**\n * Default Xenia backend base URL, used whenever `baseUrl` is omitted from\n * config. Points at the hosted engine — override only for a self-hosted or\n * non-default environment.\n */\nexport const DEFAULT_BASE_URL = \"https://engine.xeniamartech.com/xenia\";\n\n/** Returns true when running in a browser environment (SSR-safe). */\nexport function isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\n/** Appends query parameters to a URL. Returns the URL unchanged when empty. */\nexport function buildUrl(url: string, queryParams?: Record<string, string>): string {\n if (!queryParams || Object.keys(queryParams).length === 0) return url;\n const params = new URLSearchParams(queryParams);\n const sep = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${sep}${params.toString()}`;\n}\n\n/** Sleep helper used by the retry backoff. */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Options for a single request. */\nexport interface RequestOptions {\n method?: HttpMethod;\n /** Path appended to `baseUrl` (should start with `/`). */\n path: string;\n headers?: Record<string, string>;\n query?: Record<string, string>;\n body?: JsonValue;\n /** Per-request timeout override in ms. */\n timeout?: number;\n /** Disable retry for this request (e.g. long-polling webhook). */\n noRetry?: boolean;\n /** Per-request retry override. */\n retry?: RetryConfig;\n /**\n * Keep the request alive past page unload (`fetch`'s `keepalive` flag).\n * Used for best-effort flushes on `pagehide`, where `sendBeacon` can't be\n * used instead because it doesn't support custom headers (tenant scope).\n */\n keepalive?: boolean;\n}\n\n/** Result of a request: the raw Response plus the parsed JSON (or null). */\nexport interface HttpResult {\n response: Response;\n data: JsonValue;\n}\n\n/** Error thrown for non-2xx responses (after retries are exhausted). */\nexport class HttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly data: JsonValue,\n message?: string,\n ) {\n super(message ?? `Request failed with status ${status}`);\n this.name = \"HttpError\";\n }\n}\n\n/** Thrown without a network call while an endpoint's circuit is open (see {@link CircuitBreakerConfig}). */\nexport class CircuitOpenError extends Error {\n constructor(public readonly endpoint: string) {\n super(`Circuit open for ${endpoint}: failing fast until the cooldown passes`);\n this.name = \"CircuitOpenError\";\n }\n}\n\n/**\n * Per-endpoint breaker state, module-wide so it outlives a single client.\n * ponytail: after the cooldown every concurrent caller probes, not just one; fine for a fail-fast\n * guard, add a probe lock if a thundering herd on recovery ever matters.\n */\nconst breakers = new Map<string, { failures: number; openedAt: number }>();\n\nexport interface HttpClientOptions {\n /** Defaults to {@link DEFAULT_BASE_URL} when omitted. */\n baseUrl?: string;\n defaultHeaders?: Record<string, string>;\n timeout?: number;\n retry?: RetryConfig;\n circuitBreaker?: boolean | CircuitBreakerConfig;\n debug?: boolean;\n}\n\n/**\n * Minimal fetch wrapper shared by all runtimes.\n *\n * - Injects default headers (tenant scope, content-type).\n * - Applies an `AbortController` timeout.\n * - Retries transient failures (network error, `429`, `5xx`) with exponential\n * backoff, honoring a `Retry-After` header when present.\n * - Parses JSON, falling back to `null` on empty/invalid bodies.\n */\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly defaultHeaders: Record<string, string>;\n private readonly timeout: number;\n private readonly retry: Required<RetryConfig>;\n private readonly debug: boolean;\n private readonly breaker: Required<CircuitBreakerConfig> | null;\n\n constructor(opts: HttpClientOptions) {\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.defaultHeaders = opts.defaultHeaders ?? {};\n this.timeout = opts.timeout ?? 8000;\n this.retry = {\n attempts: opts.retry?.attempts ?? 2,\n baseDelayMs: opts.retry?.baseDelayMs ?? 300,\n };\n this.debug = opts.debug ?? false;\n const cb = opts.circuitBreaker === true ? {} : opts.circuitBreaker || null;\n this.breaker = cb && { failureThreshold: cb.failureThreshold ?? 3, cooldownMs: cb.cooldownMs ?? 30_000 };\n }\n\n async request(opts: RequestOptions): Promise<HttpResult> {\n if (!this.breaker) return this.send(opts);\n // Keyed by full URL, query included: every webhook shares `/flow`, and one slow journey\n // (`?id=`) must not short-circuit the others.\n const key = buildUrl(this.baseUrl + opts.path, opts.query);\n const b = breakers.get(key) ?? { failures: 0, openedAt: 0 };\n if (b.openedAt) {\n if (Date.now() - b.openedAt < this.breaker.cooldownMs) throw new CircuitOpenError(key);\n // Half-open: let this call probe; one more failure re-opens.\n b.openedAt = 0;\n b.failures = this.breaker.failureThreshold - 1;\n }\n try {\n const result = await this.send(opts);\n breakers.delete(key);\n return result;\n } catch (err) {\n if (err instanceof HttpError && !this.isRetryableStatus(err.status)) {\n breakers.delete(key); // the backend answered: it's up\n throw err;\n }\n b.failures += 1;\n if (b.failures >= this.breaker.failureThreshold) {\n b.openedAt = Date.now();\n this.log(`circuit opened for ${opts.path} after ${b.failures} failures`);\n }\n breakers.set(key, b);\n throw err;\n }\n }\n\n private async send(opts: RequestOptions): Promise<HttpResult> {\n const url = buildUrl(this.baseUrl + opts.path, opts.query);\n const method = opts.method ?? \"GET\";\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...this.defaultHeaders,\n ...opts.headers,\n };\n const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;\n const timeout = opts.timeout ?? this.timeout;\n const maxAttempts = opts.noRetry ? 1 : (opts.retry?.attempts ?? this.retry.attempts) + 1;\n const baseDelay = opts.retry?.baseDelayMs ?? this.retry.baseDelayMs;\n\n let lastError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n const response = await this.fetchWithTimeout(\n url,\n { method, headers, body, ...(opts.keepalive ? { keepalive: true } : {}) },\n timeout,\n );\n const data = await this.parseBody(response);\n\n if (response.ok) {\n return { response, data };\n }\n\n // Retry on 429/5xx if attempts remain; otherwise throw.\n if (this.isRetryableStatus(response.status) && attempt < maxAttempts - 1) {\n const wait = this.retryAfterMs(response, baseDelay, attempt);\n this.log(`retrying ${opts.path} after ${wait}ms (status ${response.status})`);\n await delay(wait);\n continue;\n }\n throw new HttpError(response.status, data);\n } catch (err) {\n lastError = err;\n // Non-retryable HttpError → rethrow immediately.\n if (err instanceof HttpError) throw err;\n // Network/timeout error → retry if attempts remain.\n if (attempt < maxAttempts - 1) {\n const wait = baseDelay * 2 ** attempt;\n this.log(`retrying ${opts.path} after ${wait}ms (network error)`);\n await delay(wait);\n continue;\n }\n }\n }\n throw lastError instanceof Error ? lastError : new Error(\"Request failed\");\n }\n\n private async fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeout: number,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Request timeout after ${timeout}ms`, { cause: err });\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async parseBody(response: Response): Promise<JsonValue> {\n const text = await response.text().catch(() => \"\");\n if (!text) return null;\n try {\n return JSON.parse(text) as JsonValue;\n } catch {\n return text;\n }\n }\n\n private isRetryableStatus(status: number): boolean {\n return status === 429 || (status >= 500 && status <= 599);\n }\n\n private retryAfterMs(response: Response, baseDelay: number, attempt: number): number {\n const header = response.headers.get(\"Retry-After\");\n if (header) {\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return seconds * 1000;\n }\n return baseDelay * 2 ** attempt;\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[xenia] ${msg}`);\n }\n}\n","/**\n * Cookie names shared by the browser and server halves of the SDK.\n *\n * The visitor cookie is the one channel where two runtimes must agree on a literal string: the\n * browser writes it, a same-origin server reads it back, and that match is what lets a server-side\n * conversion attribute to a browser-entered journey. `config.prefix` namespaces it — so two Xenia\n * apps on one domain keep separate visitors — which means **both halves must derive the name the\n * same way**. Deriving it on one side and hardcoding it on the other silently breaks the link for\n * anyone using a custom prefix: the server just finds no cookie and falls back to no visitor.\n *\n * Always build the name with these helpers rather than interpolating it by hand.\n */\n\nimport type { ConsentState } from \"./types\";\n\n/** Default storage/cookie prefix, matching `XeniaConfig.prefix`. */\nexport const DEFAULT_PREFIX = \"xenia\";\n\n/** Name of the first-party cookie carrying the SDK-owned visitor id, for a given prefix. */\nexport function visitorCookieName(prefix: string = DEFAULT_PREFIX): string {\n return `${prefix || DEFAULT_PREFIX}_vid`;\n}\n\n/** Name of the first-party cookie carrying consent scopes, for a given prefix. */\nexport function consentCookieName(prefix: string = DEFAULT_PREFIX): string {\n return `${prefix || DEFAULT_PREFIX}_consent`;\n}\n\n/**\n * The visitor cookie name for the DEFAULT prefix (`\"xenia_vid\"`).\n *\n * Correct for the common case, and kept so existing code (e.g. reading the cookie straight out of a\n * framework's cookie jar) keeps working. If you set `config.prefix`, use {@link visitorCookieName}\n * instead — this constant will not match what the browser wrote.\n */\nexport const VISITOR_COOKIE = visitorCookieName();\n\n/**\n * The consent cookie name for the DEFAULT prefix (`\"xenia_consent\"`). Same custom-prefix caveat as\n * {@link VISITOR_COOKIE}.\n */\nexport const CONSENT_COOKIE = consentCookieName();\n\n/**\n * A `document.cookie` string carrying consent, in the JSON form the server SDK's `getConsent()`\n * reads back. The browser client's `consent()` writes it; a page with its own consent banner and no\n * browser client can write it directly, so server-side calls see the visitor's choice.\n */\nexport function consentCookie(state: ConsentState, prefix?: string, maxAgeDays = 365): string {\n return `${consentCookieName(prefix)}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${Math.floor(maxAgeDays * 86400)}; SameSite=Lax`;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { XeniaApi, toEventId, generateUlid, personalizeContent, personalizePage, compact, visitorCookieName, consentCookieName } from './chunk-7NGJC7CF.js';
1
+ import { XeniaApi, toEventId, generateUlid, personalizeContent, personalizePage, compact } from './chunk-LW5AEJ6K.js';
2
2
 
3
3
  // src/core/event-payload.ts
4
4
  var RESERVED = /* @__PURE__ */ new Set([
@@ -179,7 +179,7 @@ var XeniaServer = class {
179
179
  identity: opts.identity,
180
180
  context: opts.context
181
181
  });
182
- return async (ids) => (await this.api.resolveEntries(ids, resolveOpts)).decisions ?? [];
182
+ return async (ids) => (await this.api.resolveEntries(ids, { ...resolveOpts, contentKey: opts.contentKey })).decisions ?? [];
183
183
  }
184
184
  /**
185
185
  * Merge the ambient `config.context` and `opts.identity`/`opts.context` into
@@ -215,38 +215,6 @@ function createXeniaServer(config) {
215
215
  return new XeniaServer(config);
216
216
  }
217
217
 
218
- // src/server/cookies.ts
219
- function parseCookies(header) {
220
- const out = {};
221
- if (!header) return out;
222
- for (const part of header.split(";")) {
223
- const idx = part.indexOf("=");
224
- if (idx === -1) continue;
225
- const key = part.slice(0, idx).trim();
226
- const value = part.slice(idx + 1).trim();
227
- if (key) out[key] = decodeURIComponent(value);
228
- }
229
- return out;
230
- }
231
- function getVisitorId(cookieHeader, prefix) {
232
- return parseCookies(cookieHeader)[visitorCookieName(prefix)] ?? null;
233
- }
234
- function getConsent(cookieHeader, prefix) {
235
- const raw = parseCookies(cookieHeader)[consentCookieName(prefix)];
236
- if (!raw) return { analytics: false, marketing: false, personalization: false };
237
- try {
238
- const parsed = JSON.parse(raw);
239
- return parsed;
240
- } catch {
241
- const scopes = new Set(raw.split(",").map((s) => s.trim()));
242
- return {
243
- analytics: scopes.has("analytics"),
244
- marketing: scopes.has("marketing"),
245
- personalization: scopes.has("personalization")
246
- };
247
- }
248
- }
249
-
250
- export { XeniaServer, createXeniaServer, getConsent, getVisitorId, parseCookies };
251
- //# sourceMappingURL=chunk-DWFVDNU5.js.map
252
- //# sourceMappingURL=chunk-DWFVDNU5.js.map
218
+ export { XeniaServer, createXeniaServer };
219
+ //# sourceMappingURL=chunk-GWRDZIVA.js.map
220
+ //# sourceMappingURL=chunk-GWRDZIVA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/event-payload.ts","../src/server/xenia-server.ts"],"names":["compact"],"mappings":"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","file":"chunk-GWRDZIVA.js","sourcesContent":["import type { EventContext, EventIdentity, EventPayload, JsonValue } from \"./types\";\n\n/** Reserved keys that map to first-class fields (not passthrough data). */\nconst RESERVED = new Set([\n \"eventId\",\n \"eventType\",\n \"journeyId\",\n \"deviceId\",\n \"visitorId\",\n \"customerId\",\n \"email\",\n \"utmSource\",\n \"utmMedium\",\n \"utmCampaign\",\n \"utmContent\",\n \"utmId\",\n \"url\",\n \"referrer\",\n \"path\",\n \"query\",\n]);\n\nexport interface BuildEventInput {\n eventId: string;\n eventType?: string;\n journeyId?: string;\n identity?: EventIdentity;\n context?: EventContext;\n /** Arbitrary extra properties merged as passthrough data. */\n props?: Record<string, JsonValue | undefined>;\n}\n\n/** Drop `undefined` values so the JSON body stays clean. */\nfunction compact(obj: Record<string, JsonValue | undefined>): Record<string, JsonValue> {\n const out: Record<string, JsonValue> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) out[k] = v;\n }\n return out;\n}\n\n/**\n * Build the request body for the webhook `/flow` call (`XeniaApi.webhook`).\n *\n * Merge order (later wins): passthrough props < context < identity < core.\n * Field names match the backend `EventTriggerRequest` DTO exactly.\n *\n * @internal Used only by the webhook request/response path — the unified event\n * surface (`track()` → `POST /api/events/v1`) builds {@link XeniaEvent} instead.\n */\nexport function buildEventPayload(input: BuildEventInput): EventPayload {\n const passthrough: Record<string, JsonValue> = {};\n const reserved: Record<string, JsonValue> = {};\n if (input.props) {\n for (const [k, v] of Object.entries(input.props)) {\n if (v === undefined) continue;\n // A prop whose key matches a reserved field (e.g. `email`, `customerId`,\n // `utmSource`) is promoted to that first-class field; everything else is\n // custom passthrough data. Previously reserved-named props were dropped\n // outright — so `track(\"sign_up\", { email })` never sent `email` at all.\n if (RESERVED.has(k)) reserved[k] = v;\n else passthrough[k] = v;\n }\n }\n\n const payload = {\n ...passthrough,\n // Reserved-named props fill first-class fields (e.g. `email`, `customerId`)\n // the SDK didn't auto-collect. Auto-collected context/identity still win for\n // what they reliably know (real page url, visitor/device id), so a stray prop\n // can't override the true page context — see merge order below.\n ...reserved,\n ...compact((input.context ?? {}) as Record<string, JsonValue | undefined>),\n ...compact((input.identity ?? {}) as Record<string, JsonValue | undefined>),\n eventId: input.eventId,\n ...(input.eventType ? { eventType: input.eventType } : {}),\n ...(input.journeyId ? { journeyId: input.journeyId } : {}),\n };\n\n return payload as EventPayload;\n}\n","import { compact, XeniaApi } from \"../core/api\";\nimport { buildEventPayload } from \"../core/event-payload\";\nimport { generateUlid, toEventId } from \"../core/id\";\nimport { personalizeContent, personalizePage } from \"../core/personalize-content\";\nimport type { EntryResolver, PageRef, PagePersonalizeOptions, PersonalizeContentResult } from \"../core/personalize-content\";\nimport type {\n AnalyticsContext,\n AnalyticsEventName,\n CallerIdentity,\n AnalyticsIngestResponse,\n ConsentState,\n EventContext,\n EventIdentity,\n IdentifyRequest,\n IdentifyResponse,\n JsonValue,\n ResolveBatchItem,\n ResolveBatchResult,\n ResolveOptions,\n ResolveResult,\n WebhookResponse,\n XeniaConfig,\n XeniaEvent,\n} from \"../core/types\";\n\n/**\n * Extra per-call identity/context for server-side webhook calls — an override on top of whatever the\n * client already carries.\n *\n * There's no ambient `window` on the server to auto-collect `path`/`query`/`url`/UTM params the way\n * the browser client does, so bind them ONCE per request via {@link XeniaConfig.context} (see\n * `contextFromUrl`) and every call attaches them automatically. These per-call fields are for the\n * remainder: values known only at this call site, or a deliberate override of the ambient ones.\n */\nexport interface ServerWebhookOptions<F = never> extends WithFallback<F> {\n method?: \"GET\" | \"POST\";\n journeyId?: string;\n identity?: EventIdentity;\n context?: EventContext;\n}\n\n/**\n * Opt-in \"never throw\" for calls a page renders from. When `fallback` is set (even to `null`), any\n * failure (timeout, network error, non-2xx, open circuit) resolves to it instead of throwing, so a\n * caller can render its default content without a `try/catch`. Errors are swallowed silently: leave\n * it unset where you need to see them.\n */\nexport interface WithFallback<F> {\n fallback?: F;\n}\n\n/** {@link ResolveOptions} plus {@link WithFallback}. */\nexport type ServerResolveOptions<F = never> = ResolveOptions & WithFallback<F>;\n\n/** Runs `call`, or resolves to `opts.fallback` on any error when one was given. */\nasync function orFallback<T, F>(call: () => Promise<T>, opts?: WithFallback<F>): Promise<T | F> {\n if (!opts || !(\"fallback\" in opts)) return call();\n try {\n return await call();\n } catch {\n return opts.fallback as F;\n }\n}\n\n/**\n * Options for a server-side unified event (`POST /api/events/v1`). There's no\n * ambient request context on the server — pass whatever's known at the call\n * site (e.g. pathname/user-agent from the current request, session id from a\n * cookie) via `identity`/`context`.\n */\nexport interface ServerTrackOptions {\n /** When true, also match/advance a journey (never sampled/shed). Default false. */\n trigger?: boolean;\n /** Stable dedup key (e.g. `\"ord-1001\"`) for idempotent conversions. */\n eventId?: string;\n eventVersion?: number;\n occurredAtMs?: number;\n /** Origin tag. Default `\"xenia-server-sdk\"`. */\n source?: string;\n /** Identity fields for this event (except the SDK-owned `xeniaVisitorId`,\n * which comes from `config.visitorId` and can't be set per call). */\n identity?: CallerIdentity;\n context?: AnalyticsContext;\n properties?: Record<string, JsonValue | undefined>;\n consent?: ConsentState;\n}\n\n/**\n * Server-native Xenia client. Same method names as the browser client, but\n * every call is stateless: identity and context are passed explicitly rather\n * than read from the DOM/localStorage. Uses global `fetch` (Node ≥18).\n */\nexport class XeniaServer {\n readonly api: XeniaApi;\n /** SDK-owned visitor anchor for this (per-request) client — see {@link XeniaConfig.visitorId}. */\n readonly visitorId?: string;\n /** Ambient request context attached to every call — see {@link XeniaConfig.context}. */\n private readonly context?: EventContext;\n\n constructor(config: XeniaConfig) {\n this.api = new XeniaApi(config);\n this.visitorId = config.visitorId;\n this.context = config.context;\n }\n\n /**\n * Send a unified event server-side (`POST /api/events/v1`) and return the\n * ingest response (including per-result `triggered`). `opts.trigger: true`\n * also matches/advances a journey; pass `opts.eventId` (e.g. `\"ord-1001\"`)\n * as a stable dedup key for idempotent conversions.\n */\n async track(\n eventName: AnalyticsEventName,\n opts: ServerTrackOptions = {},\n ): Promise<AnalyticsIngestResponse> {\n const event: XeniaEvent = {\n eventName,\n eventId: opts.eventId ? toEventId(opts.eventId) : generateUlid(),\n eventVersion: opts.eventVersion ?? 1,\n occurredAtMs: opts.occurredAtMs ?? Date.now(),\n source: opts.source ?? \"xenia-server-sdk\",\n identity: {\n ...opts.identity,\n // SDK-owned, set LAST so it can't be overridden per call. Comes from\n // `config.visitorId` (bind it per request from the `xenia_vid` cookie).\n xeniaVisitorId: this.visitorId,\n },\n context: opts.context,\n properties: opts.properties,\n consent: opts.consent,\n trigger: opts.trigger === true ? true : undefined,\n };\n return this.api.sendEvents([event]);\n }\n\n /**\n * Send a webhook request and await the synchronous response. Merges\n * `opts.identity`/`opts.context` into the payload the same way the browser\n * client's auto-attached fields do (`path`, `query`, `url`, `referrer`,\n * `deviceId`, `visitorId`, etc.) — see `ServerWebhookOptions`. Pass `opts.fallback` to get it\n * back instead of an error (see {@link WithFallback}).\n */\n async webhook<F = never>(\n triggerId: string,\n data?: Record<string, JsonValue | undefined>,\n opts: ServerWebhookOptions<F> = {},\n ): Promise<WebhookResponse | F> {\n const payload = buildEventPayload({\n eventId: triggerId,\n journeyId: opts.journeyId,\n // SDK-owned visitor anchor wins: config.visitorId (bind the `xenia_vid`\n // cookie per request) is attached automatically. Falls back to a\n // per-call visitorId only when none is bound.\n identity: { ...opts.identity, visitorId: this.visitorId ?? opts.identity?.visitorId },\n // Ambient request context (config.context, bound once per request) under a per-call\n // override — same precedence resolve() uses, so both surfaces carry the same fields.\n context: { ...this.context, ...opts.context },\n props: data,\n });\n return orFallback(() => this.api.webhook(triggerId, payload as JsonValue, opts.method ?? \"POST\"), opts);\n }\n\n /**\n * Bind visitor→user server-side — `POST /api/events/v1/identify`. `xeniaVisitorId` defaults to\n * the bound `config.visitorId`, so a per-request client only passes what it learned (e.g. an email\n * from a submitted form).\n */\n async identify(\n body: Omit<IdentifyRequest, \"xeniaVisitorId\"> & { xeniaVisitorId?: string },\n ): Promise<IdentifyResponse> {\n const xeniaVisitorId = body.xeniaVisitorId ?? this.visitorId;\n if (!xeniaVisitorId) {\n throw new Error(\"identify() needs a visitor: bind config.visitorId or pass xeniaVisitorId\");\n }\n return this.api.identify({ ...body, xeniaVisitorId });\n }\n\n /**\n * Resolve in-entry content personalization for one content key\n * (`POST /api/personalization/resolve`). Requires a configured SDK token\n * (`token`/`getToken`) — resolve is auth-required. With `merge: true` the\n * returned `content` is server-spliced.\n *\n * Pass `opts.identity`/`opts.context` to attach the same SDK-provided\n * fields (`deviceId`, `customerId`, `email`, `path`, `url`, `referrer`,\n * `utm*`, …) the browser client collects ambiently — there's no ambient\n * `window`/`document` server-side, so a server caller must supply whatever\n * it knows (e.g. from the current request) explicitly. See\n * {@link ResolveOptions}. Pass `opts.fallback` to get it back instead of an\n * error (see {@link WithFallback}).\n */\n async resolve<F = never>(contentKey: string, opts?: ServerResolveOptions<F>): Promise<ResolveResult | F> {\n return orFallback(() => this.api.resolve(contentKey, this.withVisitor(opts)), opts);\n }\n\n /**\n * Resolve several content keys in one round-trip (`POST /api/personalization/resolve-batch`).\n * `opts.fallback` as in {@link resolve}.\n */\n async resolveBatch<F = never>(\n items: ResolveBatchItem[],\n opts?: ServerResolveOptions<F>,\n ): Promise<ResolveBatchResult[] | F> {\n return orFallback(() => this.api.resolveBatch(items, this.withVisitor(opts)), opts);\n }\n\n /**\n * Personalize a fetched CMS page server-side — see `XeniaClient.personalizeContent`. Never\n * throws: on error or timeout the page renders with whatever was decided (base content otherwise).\n * Pass `opts.identity`/`opts.context` like `resolve()`.\n */\n async personalizeContent<T>(page: T, opts: PagePersonalizeOptions): Promise<PersonalizeContentResult<T>> {\n return personalizeContent(page, { ...opts, resolve: this.entryResolver(opts) });\n }\n\n /**\n * Fetch a page through `opts.cms` and personalize it — the app passes only the page's identity\n * (entry id, or `{ contentType, fields: { slug } }`). Never throws: a failed fetch is `content:\n * null` with `error`, a missing page `content: null`.\n */\n async personalizePage<T = unknown>(ref: PageRef, opts: PagePersonalizeOptions): Promise<PersonalizeContentResult<T | null>> {\n return personalizePage<T>(ref, { ...opts, resolve: this.entryResolver(opts) });\n }\n\n private entryResolver(opts: PagePersonalizeOptions): EntryResolver {\n const resolveOpts = this.withVisitor({\n data: opts.data, locale: opts.locale, identity: opts.identity, context: opts.context,\n });\n return async (ids) =>\n (await this.api.resolveEntries(ids, { ...resolveOpts, contentKey: opts.contentKey })).decisions ?? [];\n }\n\n /**\n * Merge the ambient `config.context` and `opts.identity`/`opts.context` into\n * the resolve `data`, then stamp the SDK-owned visitor id\n * (`config.visitorId`) under both the reserved `_xenia_vid` key (read by the\n * backend for holdout/exposure bucketing) and the flat `visitorId` field —\n * mirrors `webhook()`'s merge order/precedence exactly: caller `data` <\n * ambient `config.context` < caller `context`/`identity` < SDK-owned\n * `visitorId`/`_xenia_vid`, set LAST so neither can be overridden per call.\n *\n * `config.context` is what makes the SDK-provided fields automatic on the\n * server: bound once per request, every `resolve()` carries `path`/`query`/\n * `url`/`referrer`/UTMs with no per-call wiring, matching what the browser\n * client collects from `window` on its own. A no-op when nothing is bound and\n * nothing was passed — preserving today's behavior for callers using neither.\n */\n private withVisitor(opts?: ResolveOptions): ResolveOptions | undefined {\n const identity = compact({\n ...opts?.identity,\n visitorId: this.visitorId ?? opts?.identity?.visitorId,\n });\n const merged = {\n ...compact((this.context ?? {}) as Record<string, JsonValue | undefined>),\n ...compact((opts?.context ?? {}) as Record<string, JsonValue | undefined>),\n ...identity,\n };\n if (Object.keys(merged).length === 0 && !this.visitorId) return opts;\n if (this.visitorId) merged._xenia_vid = this.visitorId;\n return { ...opts, data: { ...(opts?.data ?? {}), ...merged } };\n }\n}\n\n/** Create a server-native client. */\nexport function createXeniaServer(config: XeniaConfig): XeniaServer {\n return new XeniaServer(config);\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { HttpClient } from './chunk-BQ5PJCZ7.js';
2
+
1
3
  // src/core/actions.ts
2
4
  var MESSAGE_TYPES = /* @__PURE__ */ new Set([
3
5
  "banner",
@@ -137,168 +139,6 @@ function parseActions(body) {
137
139
  return actions;
138
140
  }
139
141
 
140
- // src/core/http-client.ts
141
- var DEFAULT_BASE_URL = "https://engine.xeniamartech.com/xenia";
142
- function isBrowser() {
143
- return typeof window !== "undefined" && typeof document !== "undefined";
144
- }
145
- function buildUrl(url, queryParams) {
146
- if (!queryParams || Object.keys(queryParams).length === 0) return url;
147
- const params = new URLSearchParams(queryParams);
148
- const sep = url.includes("?") ? "&" : "?";
149
- return `${url}${sep}${params.toString()}`;
150
- }
151
- function delay(ms) {
152
- return new Promise((resolve) => setTimeout(resolve, ms));
153
- }
154
- var HttpError = class extends Error {
155
- constructor(status, data, message) {
156
- super(message ?? `Request failed with status ${status}`);
157
- this.status = status;
158
- this.data = data;
159
- this.name = "HttpError";
160
- }
161
- status;
162
- data;
163
- };
164
- var CircuitOpenError = class extends Error {
165
- constructor(endpoint) {
166
- super(`Circuit open for ${endpoint}: failing fast until the cooldown passes`);
167
- this.endpoint = endpoint;
168
- this.name = "CircuitOpenError";
169
- }
170
- endpoint;
171
- };
172
- var breakers = /* @__PURE__ */ new Map();
173
- var HttpClient = class {
174
- baseUrl;
175
- defaultHeaders;
176
- timeout;
177
- retry;
178
- debug;
179
- breaker;
180
- constructor(opts) {
181
- this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
182
- this.defaultHeaders = opts.defaultHeaders ?? {};
183
- this.timeout = opts.timeout ?? 8e3;
184
- this.retry = {
185
- attempts: opts.retry?.attempts ?? 2,
186
- baseDelayMs: opts.retry?.baseDelayMs ?? 300
187
- };
188
- this.debug = opts.debug ?? false;
189
- const cb = opts.circuitBreaker === true ? {} : opts.circuitBreaker || null;
190
- this.breaker = cb && { failureThreshold: cb.failureThreshold ?? 3, cooldownMs: cb.cooldownMs ?? 3e4 };
191
- }
192
- async request(opts) {
193
- if (!this.breaker) return this.send(opts);
194
- const key = buildUrl(this.baseUrl + opts.path, opts.query);
195
- const b = breakers.get(key) ?? { failures: 0, openedAt: 0 };
196
- if (b.openedAt) {
197
- if (Date.now() - b.openedAt < this.breaker.cooldownMs) throw new CircuitOpenError(key);
198
- b.openedAt = 0;
199
- b.failures = this.breaker.failureThreshold - 1;
200
- }
201
- try {
202
- const result = await this.send(opts);
203
- breakers.delete(key);
204
- return result;
205
- } catch (err) {
206
- if (err instanceof HttpError && !this.isRetryableStatus(err.status)) {
207
- breakers.delete(key);
208
- throw err;
209
- }
210
- b.failures += 1;
211
- if (b.failures >= this.breaker.failureThreshold) {
212
- b.openedAt = Date.now();
213
- this.log(`circuit opened for ${opts.path} after ${b.failures} failures`);
214
- }
215
- breakers.set(key, b);
216
- throw err;
217
- }
218
- }
219
- async send(opts) {
220
- const url = buildUrl(this.baseUrl + opts.path, opts.query);
221
- const method = opts.method ?? "GET";
222
- const headers = {
223
- "Content-Type": "application/json",
224
- ...this.defaultHeaders,
225
- ...opts.headers
226
- };
227
- const body = opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
228
- const timeout = opts.timeout ?? this.timeout;
229
- const maxAttempts = opts.noRetry ? 1 : (opts.retry?.attempts ?? this.retry.attempts) + 1;
230
- const baseDelay = opts.retry?.baseDelayMs ?? this.retry.baseDelayMs;
231
- let lastError;
232
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
233
- try {
234
- const response = await this.fetchWithTimeout(
235
- url,
236
- { method, headers, body, ...opts.keepalive ? { keepalive: true } : {} },
237
- timeout
238
- );
239
- const data = await this.parseBody(response);
240
- if (response.ok) {
241
- return { response, data };
242
- }
243
- if (this.isRetryableStatus(response.status) && attempt < maxAttempts - 1) {
244
- const wait = this.retryAfterMs(response, baseDelay, attempt);
245
- this.log(`retrying ${opts.path} after ${wait}ms (status ${response.status})`);
246
- await delay(wait);
247
- continue;
248
- }
249
- throw new HttpError(response.status, data);
250
- } catch (err) {
251
- lastError = err;
252
- if (err instanceof HttpError) throw err;
253
- if (attempt < maxAttempts - 1) {
254
- const wait = baseDelay * 2 ** attempt;
255
- this.log(`retrying ${opts.path} after ${wait}ms (network error)`);
256
- await delay(wait);
257
- continue;
258
- }
259
- }
260
- }
261
- throw lastError instanceof Error ? lastError : new Error("Request failed");
262
- }
263
- async fetchWithTimeout(url, init, timeout) {
264
- const controller = new AbortController();
265
- const timer = setTimeout(() => controller.abort(), timeout);
266
- try {
267
- return await fetch(url, { ...init, signal: controller.signal });
268
- } catch (err) {
269
- if (err instanceof Error && err.name === "AbortError") {
270
- throw new Error(`Request timeout after ${timeout}ms`, { cause: err });
271
- }
272
- throw err;
273
- } finally {
274
- clearTimeout(timer);
275
- }
276
- }
277
- async parseBody(response) {
278
- const text = await response.text().catch(() => "");
279
- if (!text) return null;
280
- try {
281
- return JSON.parse(text);
282
- } catch {
283
- return text;
284
- }
285
- }
286
- isRetryableStatus(status) {
287
- return status === 429 || status >= 500 && status <= 599;
288
- }
289
- retryAfterMs(response, baseDelay, attempt) {
290
- const header = response.headers.get("Retry-After");
291
- if (header) {
292
- const seconds = Number(header);
293
- if (!Number.isNaN(seconds)) return seconds * 1e3;
294
- }
295
- return baseDelay * 2 ** attempt;
296
- }
297
- log(msg) {
298
- if (this.debug) console.warn(`[xenia] ${msg}`);
299
- }
300
- };
301
-
302
142
  // src/core/token-provider.ts
303
143
  function decodeExp(token) {
304
144
  try {
@@ -546,7 +386,7 @@ var XeniaApi = class {
546
386
  * ({@link personalizeContent}) calls this once per round with the runtime
547
387
  * targets it found; callers rarely need it directly.
548
388
  */
549
- async resolveEntries(entryIds, opts = {}) {
389
+ async resolveEntries(entryIds, opts) {
550
390
  const { data } = await this.http.request({
551
391
  method: "POST",
552
392
  path: "/api/personalization/resolve-entries",
@@ -554,7 +394,8 @@ var XeniaApi = class {
554
394
  body: compact({
555
395
  entryIds,
556
396
  data: opts.data,
557
- locale: opts.locale
397
+ locale: opts.locale,
398
+ contentKey: opts.contentKey
558
399
  })
559
400
  });
560
401
  return data;
@@ -609,20 +450,6 @@ function toEventId(raw) {
609
450
  return digest;
610
451
  }
611
452
 
612
- // src/core/cookie-names.ts
613
- var DEFAULT_PREFIX = "xenia";
614
- function visitorCookieName(prefix = DEFAULT_PREFIX) {
615
- return `${prefix || DEFAULT_PREFIX}_vid`;
616
- }
617
- function consentCookieName(prefix = DEFAULT_PREFIX) {
618
- return `${prefix || DEFAULT_PREFIX}_consent`;
619
- }
620
- var VISITOR_COOKIE = visitorCookieName();
621
- var CONSENT_COOKIE = consentCookieName();
622
- function consentCookie(state, prefix, maxAgeDays = 365) {
623
- return `${consentCookieName(prefix)}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${Math.floor(maxAgeDays * 86400)}; SameSite=Lax`;
624
- }
625
-
626
453
  // src/core/request-context.ts
627
454
  function contextFromUrl(url, referrer) {
628
455
  const ctx = {};
@@ -656,7 +483,7 @@ var DEFAULT_TIMEOUT_MS = 300;
656
483
  var DEFAULT_MAX_ROUNDS = 3;
657
484
  var copyOf = (v) => v && typeof v === "object" && typeof structuredClone === "function" ? structuredClone(v) : v;
658
485
  async function personalizeContent(payload, options) {
659
- const { cms } = options;
486
+ const cms = scopedAdapter(options.cms, options.contentKey);
660
487
  const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
661
488
  const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
662
489
  const applied = [];
@@ -855,7 +682,8 @@ function withDeadline(promise, deadline) {
855
682
  })
856
683
  ]).finally(() => clearTimeout(timer));
857
684
  }
858
- function pendingTargets(payload, cms) {
685
+ function pendingTargets(payload, adapter, contentKey) {
686
+ const cms = scopedAdapter(adapter, contentKey);
859
687
  try {
860
688
  let content = typeof structuredClone === "function" ? structuredClone(payload) : payload;
861
689
  content = cms.prepare ? cms.prepare(content) : content;
@@ -866,6 +694,15 @@ function pendingTargets(payload, cms) {
866
694
  return [];
867
695
  }
868
696
  }
697
+ function scopedAdapter(cms, contentKey) {
698
+ return {
699
+ ...cms,
700
+ marker(entry) {
701
+ const m = cms.marker(entry);
702
+ return m && contentKey && m.keys?.includes(contentKey) ? m : null;
703
+ }
704
+ };
705
+ }
869
706
  async function personalizePage(ref, options) {
870
707
  let page;
871
708
  try {
@@ -878,6 +715,6 @@ async function personalizePage(ref, options) {
878
715
  return personalizeContent(page, options);
879
716
  }
880
717
 
881
- export { CONSENT_COOKIE, CircuitOpenError, DEFAULT_PREFIX, HttpClient, HttpError, MAX_EVENT_ID_LENGTH, VISITOR_COOKIE, XeniaApi, buildUrl, compact, consentCookie, consentCookieName, contextFromUrl, generateUlid, isBrowser, parseActions, pendingTargets, personalizeContent, personalizePage, toEventId, visitorCookieName };
882
- //# sourceMappingURL=chunk-7NGJC7CF.js.map
883
- //# sourceMappingURL=chunk-7NGJC7CF.js.map
718
+ export { MAX_EVENT_ID_LENGTH, XeniaApi, compact, contextFromUrl, generateUlid, parseActions, pendingTargets, personalizeContent, personalizePage, toEventId };
719
+ //# sourceMappingURL=chunk-LW5AEJ6K.js.map
720
+ //# sourceMappingURL=chunk-LW5AEJ6K.js.map