@xenia-martech/sdk-js 1.0.1-dev.1 → 1.1.0-dev.2
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/README.md +54 -4
- package/dist/{actions-CcBYV--3.d.cts → actions-CKeiofxJ.d.cts} +19 -1
- package/dist/{actions-CcBYV--3.d.ts → actions-CKeiofxJ.d.ts} +19 -1
- package/dist/{cookie-names-B3b0XNc2.d.cts → api-URhxxi4x.d.cts} +19 -42
- package/dist/{cookie-names-B3b0XNc2.d.ts → api-URhxxi4x.d.ts} +19 -42
- package/dist/chunk-5D7SJIO4.cjs +109 -0
- package/dist/chunk-5D7SJIO4.cjs.map +1 -0
- package/dist/{chunk-XXNDHYAS.js → chunk-FYSEKFIY.js} +45 -103
- package/dist/chunk-FYSEKFIY.js.map +1 -0
- package/dist/{chunk-WYM4CQVG.cjs → chunk-JEKVN7HK.cjs} +46 -106
- package/dist/chunk-JEKVN7HK.cjs.map +1 -0
- package/dist/chunk-TLTG3BTC.js +104 -0
- package/dist/chunk-TLTG3BTC.js.map +1 -0
- package/dist/chunk-U2WBBOYT.cjs +233 -0
- package/dist/chunk-U2WBBOYT.cjs.map +1 -0
- package/dist/chunk-WYAMPEVV.js +227 -0
- package/dist/chunk-WYAMPEVV.js.map +1 -0
- package/dist/client/index.cjs +60 -6
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +25 -3
- package/dist/client/index.d.ts +25 -3
- package/dist/client/index.js +59 -7
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +58 -6
- package/dist/client/react/index.cjs.map +1 -1
- package/dist/client/react/index.d.cts +2 -2
- package/dist/client/react/index.d.ts +2 -2
- package/dist/client/react/index.js +58 -6
- package/dist/client/react/index.js.map +1 -1
- package/dist/cookie-names-8FxbGKGS.d.ts +127 -0
- package/dist/cookie-names-QYMiEMAq.d.cts +127 -0
- package/dist/index.cjs +41 -32
- package/dist/index.d.cts +4 -71
- package/dist/index.d.ts +4 -71
- package/dist/index.js +2 -1
- package/dist/next/index.cjs +59 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +45 -0
- package/dist/next/index.d.ts +45 -0
- package/dist/next/index.js +54 -0
- package/dist/next/index.js.map +1 -0
- package/dist/server/index.cjs +38 -214
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +5 -104
- package/dist/server/index.d.ts +5 -104
- package/dist/server/index.js +5 -204
- package/dist/server/index.js.map +1 -1
- package/dist/xenia-sdk.iife.js +3 -3
- package/dist/xenia-sdk.iife.js.map +1 -1
- package/dist/xenia-server-D-ozqSgv.d.ts +126 -0
- package/dist/xenia-server-rs3Gpbw7.d.cts +126 -0
- package/package.json +26 -7
- package/dist/chunk-WYM4CQVG.cjs.map +0 -1
- package/dist/chunk-XXNDHYAS.js.map +0 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { V as VisitorCookieOptions, d as ConsentState, t as XeniaConfig } from '../api-URhxxi4x.cjs';
|
|
3
|
+
import { X as XeniaServer } from '../xenia-server-rs3Gpbw7.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `@xenia-martech/sdk-js/next` — Next.js (App Router, 15+) glue for the server SDK.
|
|
7
|
+
*
|
|
8
|
+
* A Server Component can't see its own URL or set cookies during render, so the two halves split
|
|
9
|
+
* the work: `xeniaProxy()` runs in `proxy.ts`/`middleware.ts` (mints the visitor cookie, stamps the
|
|
10
|
+
* URL onto a request header), and `xeniaServerFromNext()` reads both back to bind a per-request
|
|
11
|
+
* `XeniaServer`. Use them together; each alone degrades gracefully (no URL context / a throwaway
|
|
12
|
+
* visitor id).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Request header `xeniaProxy()` stamps with the URL being served. */
|
|
16
|
+
declare const URL_HEADER = "x-xenia-url";
|
|
17
|
+
interface XeniaProxyOptions {
|
|
18
|
+
/** Request headers to forward (e.g. your own additions); defaults to the incoming ones. */
|
|
19
|
+
headers?: Headers;
|
|
20
|
+
/** Must match the browser client's `config.prefix`, if you set one. */
|
|
21
|
+
prefix?: string;
|
|
22
|
+
/** Visitor cookie attributes. Default: `path=/`, `SameSite=Lax`, one year. */
|
|
23
|
+
cookie?: VisitorCookieOptions;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Next.js proxy/middleware step: ensure the visitor cookie exists and stamp the URL onto
|
|
27
|
+
* {@link URL_HEADER}, then continue. A new visitor id is also put on the forwarded `Cookie` header,
|
|
28
|
+
* so this very request's render already sees it.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* // proxy.ts
|
|
32
|
+
* export const proxy = (request: NextRequest) => xeniaProxy(request);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function xeniaProxy(request: NextRequest, opts?: XeniaProxyOptions): NextResponse;
|
|
36
|
+
/**
|
|
37
|
+
* A `XeniaServer` bound to this request's visitor (the cookie `xeniaProxy()` set) and URL context
|
|
38
|
+
* (path, query, UTMs, referrer). For Server Components, Server Actions and Route Handlers.
|
|
39
|
+
* `config.visitorId`/`config.context` still win when passed.
|
|
40
|
+
*/
|
|
41
|
+
declare function xeniaServerFromNext(config: XeniaConfig): Promise<XeniaServer>;
|
|
42
|
+
/** This request's consent, from the cookie the browser client's `consent()` (or `consentCookie()`) wrote. */
|
|
43
|
+
declare function consentFromNext(prefix?: string): Promise<ConsentState>;
|
|
44
|
+
|
|
45
|
+
export { URL_HEADER, type XeniaProxyOptions, consentFromNext, xeniaProxy, xeniaServerFromNext };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { V as VisitorCookieOptions, d as ConsentState, t as XeniaConfig } from '../api-URhxxi4x.js';
|
|
3
|
+
import { X as XeniaServer } from '../xenia-server-D-ozqSgv.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `@xenia-martech/sdk-js/next` — Next.js (App Router, 15+) glue for the server SDK.
|
|
7
|
+
*
|
|
8
|
+
* A Server Component can't see its own URL or set cookies during render, so the two halves split
|
|
9
|
+
* the work: `xeniaProxy()` runs in `proxy.ts`/`middleware.ts` (mints the visitor cookie, stamps the
|
|
10
|
+
* URL onto a request header), and `xeniaServerFromNext()` reads both back to bind a per-request
|
|
11
|
+
* `XeniaServer`. Use them together; each alone degrades gracefully (no URL context / a throwaway
|
|
12
|
+
* visitor id).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Request header `xeniaProxy()` stamps with the URL being served. */
|
|
16
|
+
declare const URL_HEADER = "x-xenia-url";
|
|
17
|
+
interface XeniaProxyOptions {
|
|
18
|
+
/** Request headers to forward (e.g. your own additions); defaults to the incoming ones. */
|
|
19
|
+
headers?: Headers;
|
|
20
|
+
/** Must match the browser client's `config.prefix`, if you set one. */
|
|
21
|
+
prefix?: string;
|
|
22
|
+
/** Visitor cookie attributes. Default: `path=/`, `SameSite=Lax`, one year. */
|
|
23
|
+
cookie?: VisitorCookieOptions;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Next.js proxy/middleware step: ensure the visitor cookie exists and stamp the URL onto
|
|
27
|
+
* {@link URL_HEADER}, then continue. A new visitor id is also put on the forwarded `Cookie` header,
|
|
28
|
+
* so this very request's render already sees it.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* // proxy.ts
|
|
32
|
+
* export const proxy = (request: NextRequest) => xeniaProxy(request);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function xeniaProxy(request: NextRequest, opts?: XeniaProxyOptions): NextResponse;
|
|
36
|
+
/**
|
|
37
|
+
* A `XeniaServer` bound to this request's visitor (the cookie `xeniaProxy()` set) and URL context
|
|
38
|
+
* (path, query, UTMs, referrer). For Server Components, Server Actions and Route Handlers.
|
|
39
|
+
* `config.visitorId`/`config.context` still win when passed.
|
|
40
|
+
*/
|
|
41
|
+
declare function xeniaServerFromNext(config: XeniaConfig): Promise<XeniaServer>;
|
|
42
|
+
/** This request's consent, from the cookie the browser client's `consent()` (or `consentCookie()`) wrote. */
|
|
43
|
+
declare function consentFromNext(prefix?: string): Promise<ConsentState>;
|
|
44
|
+
|
|
45
|
+
export { URL_HEADER, type XeniaProxyOptions, consentFromNext, xeniaProxy, xeniaServerFromNext };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createXeniaServer, getConsent } from '../chunk-WYAMPEVV.js';
|
|
2
|
+
import { visitorCookieName, generateUlid, contextFromUrl } from '../chunk-FYSEKFIY.js';
|
|
3
|
+
import { cookies, headers } from 'next/headers';
|
|
4
|
+
import { NextResponse } from 'next/server';
|
|
5
|
+
|
|
6
|
+
var URL_HEADER = "x-xenia-url";
|
|
7
|
+
function cookieAttributes(c = {}) {
|
|
8
|
+
const sameSite = c.sameSite ?? "Lax";
|
|
9
|
+
return {
|
|
10
|
+
path: "/",
|
|
11
|
+
maxAge: Math.floor((c.maxAgeDays ?? 365) * 86400),
|
|
12
|
+
sameSite: sameSite.toLowerCase(),
|
|
13
|
+
secure: c.secure || sameSite === "None",
|
|
14
|
+
...c.domain && { domain: c.domain }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function xeniaProxy(request, opts = {}) {
|
|
18
|
+
const name = visitorCookieName(opts.prefix);
|
|
19
|
+
const existing = request.cookies.get(name)?.value;
|
|
20
|
+
const visitorId = existing ?? generateUlid();
|
|
21
|
+
const forwarded = new Headers(opts.headers ?? request.headers);
|
|
22
|
+
forwarded.set(URL_HEADER, request.url);
|
|
23
|
+
if (!existing) {
|
|
24
|
+
const header = forwarded.get("cookie");
|
|
25
|
+
forwarded.set("cookie", header ? `${header}; ${name}=${visitorId}` : `${name}=${visitorId}`);
|
|
26
|
+
}
|
|
27
|
+
const response = NextResponse.next({ request: { headers: forwarded } });
|
|
28
|
+
if (!existing) response.cookies.set(name, visitorId, cookieAttributes(opts.cookie));
|
|
29
|
+
return response;
|
|
30
|
+
}
|
|
31
|
+
async function xeniaServerFromNext(config) {
|
|
32
|
+
const [jar, h] = await Promise.all([cookies(), headers()]);
|
|
33
|
+
const name = visitorCookieName(config.prefix);
|
|
34
|
+
let visitorId = config.visitorId ?? jar.get(name)?.value;
|
|
35
|
+
if (!visitorId) {
|
|
36
|
+
visitorId = generateUlid();
|
|
37
|
+
try {
|
|
38
|
+
jar.set(name, visitorId, cookieAttributes());
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return createXeniaServer({
|
|
43
|
+
...config,
|
|
44
|
+
visitorId,
|
|
45
|
+
context: { ...contextFromUrl(h.get(URL_HEADER), h.get("referer")), ...config.context }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function consentFromNext(prefix) {
|
|
49
|
+
return getConsent((await headers()).get("cookie"), prefix);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { URL_HEADER, consentFromNext, xeniaProxy, xeniaServerFromNext };
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/next/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `@xenia-martech/sdk-js/next` — Next.js (App Router, 15+) glue for the server SDK.\n *\n * A Server Component can't see its own URL or set cookies during render, so the two halves split\n * the work: `xeniaProxy()` runs in `proxy.ts`/`middleware.ts` (mints the visitor cookie, stamps the\n * URL onto a request header), and `xeniaServerFromNext()` reads both back to bind a per-request\n * `XeniaServer`. Use them together; each alone degrades gracefully (no URL context / a throwaway\n * visitor id).\n */\n\nimport { cookies, headers } from \"next/headers\";\nimport { NextResponse, type NextRequest } from \"next/server\";\nimport { visitorCookieName } from \"../core/cookie-names\";\nimport { generateUlid } from \"../core/id\";\nimport { contextFromUrl } from \"../core/request-context\";\nimport type { ConsentState, VisitorCookieOptions, XeniaConfig } from \"../core/types\";\nimport { getConsent } from \"../server/cookies\";\nimport { createXeniaServer, type XeniaServer } from \"../server/xenia-server\";\n\n/** Request header `xeniaProxy()` stamps with the URL being served. */\nexport const URL_HEADER = \"x-xenia-url\";\n\nexport interface XeniaProxyOptions {\n /** Request headers to forward (e.g. your own additions); defaults to the incoming ones. */\n headers?: Headers;\n /** Must match the browser client's `config.prefix`, if you set one. */\n prefix?: string;\n /** Visitor cookie attributes. Default: `path=/`, `SameSite=Lax`, one year. */\n cookie?: VisitorCookieOptions;\n}\n\nfunction cookieAttributes(c: VisitorCookieOptions = {}) {\n const sameSite = c.sameSite ?? \"Lax\";\n return {\n path: \"/\",\n maxAge: Math.floor((c.maxAgeDays ?? 365) * 86400),\n sameSite: sameSite.toLowerCase() as \"lax\" | \"strict\" | \"none\",\n secure: c.secure || sameSite === \"None\",\n ...(c.domain && { domain: c.domain }),\n };\n}\n\n/**\n * Next.js proxy/middleware step: ensure the visitor cookie exists and stamp the URL onto\n * {@link URL_HEADER}, then continue. A new visitor id is also put on the forwarded `Cookie` header,\n * so this very request's render already sees it.\n *\n * ```ts\n * // proxy.ts\n * export const proxy = (request: NextRequest) => xeniaProxy(request);\n * ```\n */\nexport function xeniaProxy(request: NextRequest, opts: XeniaProxyOptions = {}): NextResponse {\n const name = visitorCookieName(opts.prefix);\n const existing = request.cookies.get(name)?.value;\n const visitorId = existing ?? generateUlid();\n\n const forwarded = new Headers(opts.headers ?? request.headers);\n forwarded.set(URL_HEADER, request.url);\n if (!existing) {\n // NextRequest.cookies.set doesn't rewrite the forwarded Cookie header, and cookies() reads that.\n const header = forwarded.get(\"cookie\");\n forwarded.set(\"cookie\", header ? `${header}; ${name}=${visitorId}` : `${name}=${visitorId}`);\n }\n\n const response = NextResponse.next({ request: { headers: forwarded } });\n if (!existing) response.cookies.set(name, visitorId, cookieAttributes(opts.cookie));\n return response;\n}\n\n/**\n * A `XeniaServer` bound to this request's visitor (the cookie `xeniaProxy()` set) and URL context\n * (path, query, UTMs, referrer). For Server Components, Server Actions and Route Handlers.\n * `config.visitorId`/`config.context` still win when passed.\n */\nexport async function xeniaServerFromNext(config: XeniaConfig): Promise<XeniaServer> {\n const [jar, h] = await Promise.all([cookies(), headers()]);\n const name = visitorCookieName(config.prefix);\n let visitorId = config.visitorId ?? jar.get(name)?.value;\n if (!visitorId) {\n visitorId = generateUlid();\n try {\n jar.set(name, visitorId, cookieAttributes());\n } catch {\n // Expected during a Server Component render; xeniaProxy() is the place that persists it.\n }\n }\n return createXeniaServer({\n ...config,\n visitorId,\n context: { ...contextFromUrl(h.get(URL_HEADER), h.get(\"referer\")), ...config.context },\n });\n}\n\n/** This request's consent, from the cookie the browser client's `consent()` (or `consentCookie()`) wrote. */\nexport async function consentFromNext(prefix?: string): Promise<ConsentState> {\n return getConsent((await headers()).get(\"cookie\"), prefix);\n}\n"]}
|
package/dist/server/index.cjs
CHANGED
|
@@ -1,179 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var RESERVED = /* @__PURE__ */ new Set([
|
|
7
|
-
"eventId",
|
|
8
|
-
"eventType",
|
|
9
|
-
"journeyId",
|
|
10
|
-
"deviceId",
|
|
11
|
-
"visitorId",
|
|
12
|
-
"customerId",
|
|
13
|
-
"email",
|
|
14
|
-
"utmSource",
|
|
15
|
-
"utmMedium",
|
|
16
|
-
"utmCampaign",
|
|
17
|
-
"utmContent",
|
|
18
|
-
"utmId",
|
|
19
|
-
"url",
|
|
20
|
-
"referrer",
|
|
21
|
-
"path",
|
|
22
|
-
"query"
|
|
23
|
-
]);
|
|
24
|
-
function compact2(obj) {
|
|
25
|
-
const out = {};
|
|
26
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
27
|
-
if (v !== void 0) out[k] = v;
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
function buildEventPayload(input) {
|
|
32
|
-
const passthrough = {};
|
|
33
|
-
const reserved = {};
|
|
34
|
-
if (input.props) {
|
|
35
|
-
for (const [k, v] of Object.entries(input.props)) {
|
|
36
|
-
if (v === void 0) continue;
|
|
37
|
-
if (RESERVED.has(k)) reserved[k] = v;
|
|
38
|
-
else passthrough[k] = v;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const payload = {
|
|
42
|
-
...passthrough,
|
|
43
|
-
// Reserved-named props fill first-class fields (e.g. `email`, `customerId`)
|
|
44
|
-
// the SDK didn't auto-collect. Auto-collected context/identity still win for
|
|
45
|
-
// what they reliably know (real page url, visitor/device id), so a stray prop
|
|
46
|
-
// can't override the true page context — see merge order below.
|
|
47
|
-
...reserved,
|
|
48
|
-
...compact2(input.context ?? {}),
|
|
49
|
-
...compact2(input.identity ?? {}),
|
|
50
|
-
eventId: input.eventId,
|
|
51
|
-
...input.eventType ? { eventType: input.eventType } : {},
|
|
52
|
-
...input.journeyId ? { journeyId: input.journeyId } : {}
|
|
53
|
-
};
|
|
54
|
-
return payload;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// src/server/xenia-server.ts
|
|
58
|
-
var XeniaServer = class {
|
|
59
|
-
api;
|
|
60
|
-
/** SDK-owned visitor anchor for this (per-request) client — see {@link XeniaConfig.visitorId}. */
|
|
61
|
-
visitorId;
|
|
62
|
-
/** Ambient request context attached to every call — see {@link XeniaConfig.context}. */
|
|
63
|
-
context;
|
|
64
|
-
constructor(config) {
|
|
65
|
-
this.api = new chunkWYM4CQVG_cjs.XeniaApi(config);
|
|
66
|
-
this.visitorId = config.visitorId;
|
|
67
|
-
this.context = config.context;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Send a unified event server-side (`POST /api/events/v1`) and return the
|
|
71
|
-
* ingest response (including per-result `triggered`). `opts.trigger: true`
|
|
72
|
-
* also matches/advances a journey; pass `opts.eventId` (e.g. `"ord-1001"`)
|
|
73
|
-
* as a stable dedup key for idempotent conversions.
|
|
74
|
-
*/
|
|
75
|
-
async track(eventName, opts = {}) {
|
|
76
|
-
const event = {
|
|
77
|
-
eventName,
|
|
78
|
-
eventId: opts.eventId ? chunkWYM4CQVG_cjs.toEventId(opts.eventId) : chunkWYM4CQVG_cjs.generateUlid(),
|
|
79
|
-
eventVersion: opts.eventVersion ?? 1,
|
|
80
|
-
occurredAtMs: opts.occurredAtMs ?? Date.now(),
|
|
81
|
-
source: opts.source ?? "xenia-server-sdk",
|
|
82
|
-
identity: {
|
|
83
|
-
...opts.identity,
|
|
84
|
-
// SDK-owned, set LAST so it can't be overridden per call. Comes from
|
|
85
|
-
// `config.visitorId` (bind it per request from the `xenia_vid` cookie).
|
|
86
|
-
xeniaVisitorId: this.visitorId
|
|
87
|
-
},
|
|
88
|
-
context: opts.context,
|
|
89
|
-
properties: opts.properties,
|
|
90
|
-
consent: opts.consent,
|
|
91
|
-
trigger: opts.trigger === true ? true : void 0
|
|
92
|
-
};
|
|
93
|
-
return this.api.sendEvents([event]);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Send a webhook request and await the synchronous response. Merges
|
|
97
|
-
* `opts.identity`/`opts.context` into the payload the same way the browser
|
|
98
|
-
* client's auto-attached fields do (`path`, `query`, `url`, `referrer`,
|
|
99
|
-
* `deviceId`, `visitorId`, etc.) — see `ServerWebhookOptions`.
|
|
100
|
-
*/
|
|
101
|
-
async webhook(triggerId, data, opts = {}) {
|
|
102
|
-
const payload = buildEventPayload({
|
|
103
|
-
eventId: triggerId,
|
|
104
|
-
journeyId: opts.journeyId,
|
|
105
|
-
// SDK-owned visitor anchor wins: config.visitorId (bind the `xenia_vid`
|
|
106
|
-
// cookie per request) is attached automatically. Falls back to a
|
|
107
|
-
// per-call visitorId only when none is bound.
|
|
108
|
-
identity: { ...opts.identity, visitorId: this.visitorId ?? opts.identity?.visitorId },
|
|
109
|
-
// Ambient request context (config.context, bound once per request) under a per-call
|
|
110
|
-
// override — same precedence resolve() uses, so both surfaces carry the same fields.
|
|
111
|
-
context: { ...this.context, ...opts.context },
|
|
112
|
-
props: data
|
|
113
|
-
});
|
|
114
|
-
return this.api.webhook(triggerId, payload, opts.method ?? "POST");
|
|
115
|
-
}
|
|
116
|
-
/** Bind visitor→user server-side — `POST /api/events/v1/identify`. */
|
|
117
|
-
async identify(body) {
|
|
118
|
-
return this.api.identify(body);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Resolve in-entry content personalization for one content key
|
|
122
|
-
* (`POST /api/personalization/resolve`). Requires a configured SDK token
|
|
123
|
-
* (`token`/`getToken`) — resolve is auth-required. With `merge: true` the
|
|
124
|
-
* returned `content` is server-spliced.
|
|
125
|
-
*
|
|
126
|
-
* Pass `opts.identity`/`opts.context` to attach the same SDK-provided
|
|
127
|
-
* fields (`deviceId`, `customerId`, `email`, `path`, `url`, `referrer`,
|
|
128
|
-
* `utm*`, …) the browser client collects ambiently — there's no ambient
|
|
129
|
-
* `window`/`document` server-side, so a server caller must supply whatever
|
|
130
|
-
* it knows (e.g. from the current request) explicitly. See
|
|
131
|
-
* {@link ResolveOptions}.
|
|
132
|
-
*/
|
|
133
|
-
async resolve(contentKey, opts) {
|
|
134
|
-
return this.api.resolve(contentKey, this.withVisitor(opts));
|
|
135
|
-
}
|
|
136
|
-
/** Resolve several content keys in one round-trip (`POST /api/personalization/resolve-batch`). */
|
|
137
|
-
async resolveBatch(items, opts) {
|
|
138
|
-
return this.api.resolveBatch(items, this.withVisitor(opts));
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Merge the ambient `config.context` and `opts.identity`/`opts.context` into
|
|
142
|
-
* the resolve `data`, then stamp the SDK-owned visitor id
|
|
143
|
-
* (`config.visitorId`) under both the reserved `_xenia_vid` key (read by the
|
|
144
|
-
* backend for holdout/exposure bucketing) and the flat `visitorId` field —
|
|
145
|
-
* mirrors `webhook()`'s merge order/precedence exactly: caller `data` <
|
|
146
|
-
* ambient `config.context` < caller `context`/`identity` < SDK-owned
|
|
147
|
-
* `visitorId`/`_xenia_vid`, set LAST so neither can be overridden per call.
|
|
148
|
-
*
|
|
149
|
-
* `config.context` is what makes the SDK-provided fields automatic on the
|
|
150
|
-
* server: bound once per request, every `resolve()` carries `path`/`query`/
|
|
151
|
-
* `url`/`referrer`/UTMs with no per-call wiring, matching what the browser
|
|
152
|
-
* client collects from `window` on its own. A no-op when nothing is bound and
|
|
153
|
-
* nothing was passed — preserving today's behavior for callers using neither.
|
|
154
|
-
*/
|
|
155
|
-
withVisitor(opts) {
|
|
156
|
-
const identity = chunkWYM4CQVG_cjs.compact({
|
|
157
|
-
...opts?.identity,
|
|
158
|
-
visitorId: this.visitorId ?? opts?.identity?.visitorId
|
|
159
|
-
});
|
|
160
|
-
const merged = {
|
|
161
|
-
...chunkWYM4CQVG_cjs.compact(this.context ?? {}),
|
|
162
|
-
...chunkWYM4CQVG_cjs.compact(opts?.context ?? {}),
|
|
163
|
-
...identity
|
|
164
|
-
};
|
|
165
|
-
if (Object.keys(merged).length === 0 && !this.visitorId) return opts;
|
|
166
|
-
if (this.visitorId) merged._xenia_vid = this.visitorId;
|
|
167
|
-
return { ...opts, data: { ...opts?.data ?? {}, ...merged } };
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
function createXeniaServer(config) {
|
|
171
|
-
return new XeniaServer(config);
|
|
172
|
-
}
|
|
3
|
+
require('../chunk-5D7SJIO4.cjs');
|
|
4
|
+
var chunkU2WBBOYT_cjs = require('../chunk-U2WBBOYT.cjs');
|
|
5
|
+
var chunkJEKVN7HK_cjs = require('../chunk-JEKVN7HK.cjs');
|
|
173
6
|
|
|
174
7
|
// src/server/token.ts
|
|
175
8
|
async function refreshSdkToken(opts) {
|
|
176
|
-
const http = new
|
|
9
|
+
const http = new chunkJEKVN7HK_cjs.HttpClient({ baseUrl: opts.baseUrl, timeout: opts.timeout });
|
|
177
10
|
const { data } = await http.request({
|
|
178
11
|
method: "POST",
|
|
179
12
|
path: "/api/personalization/token/refresh",
|
|
@@ -204,68 +37,59 @@ function createTokenRoute(config) {
|
|
|
204
37
|
};
|
|
205
38
|
}
|
|
206
39
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const parsed = JSON.parse(raw);
|
|
228
|
-
return parsed;
|
|
229
|
-
} catch {
|
|
230
|
-
const scopes = new Set(raw.split(",").map((s) => s.trim()));
|
|
231
|
-
return {
|
|
232
|
-
analytics: scopes.has("analytics"),
|
|
233
|
-
marketing: scopes.has("marketing"),
|
|
234
|
-
personalization: scopes.has("personalization")
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
40
|
+
Object.defineProperty(exports, "XeniaServer", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
get: function () { return chunkU2WBBOYT_cjs.XeniaServer; }
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(exports, "createXeniaServer", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function () { return chunkU2WBBOYT_cjs.createXeniaServer; }
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "getConsent", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function () { return chunkU2WBBOYT_cjs.getConsent; }
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(exports, "getVisitorId", {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
get: function () { return chunkU2WBBOYT_cjs.getVisitorId; }
|
|
55
|
+
});
|
|
56
|
+
Object.defineProperty(exports, "parseCookies", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
get: function () { return chunkU2WBBOYT_cjs.parseCookies; }
|
|
59
|
+
});
|
|
239
60
|
Object.defineProperty(exports, "CONSENT_COOKIE", {
|
|
240
61
|
enumerable: true,
|
|
241
|
-
get: function () { return
|
|
62
|
+
get: function () { return chunkJEKVN7HK_cjs.CONSENT_COOKIE; }
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(exports, "CircuitOpenError", {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
get: function () { return chunkJEKVN7HK_cjs.CircuitOpenError; }
|
|
242
67
|
});
|
|
243
68
|
Object.defineProperty(exports, "VISITOR_COOKIE", {
|
|
244
69
|
enumerable: true,
|
|
245
|
-
get: function () { return
|
|
70
|
+
get: function () { return chunkJEKVN7HK_cjs.VISITOR_COOKIE; }
|
|
246
71
|
});
|
|
247
72
|
Object.defineProperty(exports, "XeniaApi", {
|
|
248
73
|
enumerable: true,
|
|
249
|
-
get: function () { return
|
|
74
|
+
get: function () { return chunkJEKVN7HK_cjs.XeniaApi; }
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(exports, "consentCookie", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
get: function () { return chunkJEKVN7HK_cjs.consentCookie; }
|
|
250
79
|
});
|
|
251
80
|
Object.defineProperty(exports, "consentCookieName", {
|
|
252
81
|
enumerable: true,
|
|
253
|
-
get: function () { return
|
|
82
|
+
get: function () { return chunkJEKVN7HK_cjs.consentCookieName; }
|
|
254
83
|
});
|
|
255
84
|
Object.defineProperty(exports, "parseActions", {
|
|
256
85
|
enumerable: true,
|
|
257
|
-
get: function () { return
|
|
86
|
+
get: function () { return chunkJEKVN7HK_cjs.parseActions; }
|
|
258
87
|
});
|
|
259
88
|
Object.defineProperty(exports, "visitorCookieName", {
|
|
260
89
|
enumerable: true,
|
|
261
|
-
get: function () { return
|
|
90
|
+
get: function () { return chunkJEKVN7HK_cjs.visitorCookieName; }
|
|
262
91
|
});
|
|
263
|
-
exports.XeniaServer = XeniaServer;
|
|
264
92
|
exports.createTokenRoute = createTokenRoute;
|
|
265
|
-
exports.createXeniaServer = createXeniaServer;
|
|
266
|
-
exports.getConsent = getConsent;
|
|
267
|
-
exports.getVisitorId = getVisitorId;
|
|
268
|
-
exports.parseCookies = parseCookies;
|
|
269
93
|
exports.refreshSdkToken = refreshSdkToken;
|
|
270
94
|
//# sourceMappingURL=index.cjs.map
|
|
271
95
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/event-payload.ts","../../src/server/xenia-server.ts","../../src/server/token.ts","../../src/server/cookies.ts"],"names":["compact","XeniaApi","toEventId","generateUlid","HttpClient","visitorCookieName","consentCookieName"],"mappings":"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","file":"index.cjs","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 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 {\n method?: \"GET\" | \"POST\";\n journeyId?: string;\n identity?: EventIdentity;\n context?: EventContext;\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 private 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`.\n */\n async webhook(\n triggerId: string,\n data?: Record<string, JsonValue | undefined>,\n opts: ServerWebhookOptions = {},\n ): Promise<WebhookResponse> {\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 this.api.webhook(triggerId, payload as JsonValue, opts.method ?? \"POST\");\n }\n\n /** Bind visitor→user server-side — `POST /api/events/v1/identify`. */\n async identify(body: IdentifyRequest): Promise<IdentifyResponse> {\n return this.api.identify(body);\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}.\n */\n async resolve(contentKey: string, opts?: ResolveOptions): Promise<ResolveResult> {\n return this.api.resolve(contentKey, this.withVisitor(opts));\n }\n\n /** Resolve several content keys in one round-trip (`POST /api/personalization/resolve-batch`). */\n async resolveBatch(\n items: ResolveBatchItem[],\n opts?: ResolveOptions,\n ): Promise<ResolveBatchResult[]> {\n return this.api.resolveBatch(items, this.withVisitor(opts));\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","import { HttpClient } from \"../core/http-client\";\nimport type { JsonObject } from \"../core/types\";\n\nexport interface RefreshTokenOptions {\n /**\n * Xenia base URL. Defaults to the hosted engine,\n * `https://engine.xeniamartech.com/xenia` — omit unless self-hosting.\n */\n baseUrl?: string;\n /**\n * The personalization SDK token created once in the dashboard (API Tokens →\n * Create token). Stays server-side; the browser never sees it — only the\n * short-lived token this exchanges it for does.\n */\n sdkToken: string;\n /** Lifetime of the returned token, in minutes. Default 15, capped server-side at 60. */\n ttlMinutes?: number;\n timeout?: number;\n}\n\nexport interface RefreshedToken {\n token: string;\n expiresAt?: string;\n}\n\n/**\n * Exchange the long-lived personalization SDK token for a short-lived one with\n * the same tenant/env scope, via `POST /api/personalization/token/refresh`.\n * Self-service — no dashboard credential needed, just the SDK token you\n * already have. Call this from your backend and hand the result to the\n * browser (e.g. from a token route the client SDK calls via `getToken`).\n */\nexport async function refreshSdkToken(opts: RefreshTokenOptions): Promise<RefreshedToken> {\n const http = new HttpClient({ baseUrl: opts.baseUrl, timeout: opts.timeout });\n const { data } = await http.request({\n method: \"POST\",\n path: \"/api/personalization/token/refresh\",\n headers: { Authorization: `Bearer ${opts.sdkToken}` },\n body: { ttlMinutes: opts.ttlMinutes ?? null },\n });\n const obj = (data ?? {}) as JsonObject;\n return {\n token: String(obj.token ?? \"\"),\n expiresAt: typeof obj.expiresAt === \"string\" ? obj.expiresAt : undefined,\n };\n}\n\n/**\n * Build a framework-agnostic request handler that hands the browser SDK a\n * personalization token — mount it at the endpoint `getToken` calls. Needs\n * only the SDK token you already created in the dashboard; mints a fresh\n * short-lived token per request so the long-lived one never reaches the\n * browser.\n *\n * @example\n * ```ts\n * export const GET = createTokenRoute({\n * sdkToken: process.env.XENIA_SDK_TOKEN!,\n * // baseUrl: process.env.XENIA_URL, // only to override the default hosted engine\n * });\n * ```\n */\nexport function createTokenRoute(config: RefreshTokenOptions): (request: Request) => Promise<Response> {\n return async (): Promise<Response> => {\n try {\n const refreshed = await refreshSdkToken(config);\n return new Response(JSON.stringify(refreshed), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"token refresh failed\";\n return new Response(JSON.stringify({ error: message }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n };\n}\n","import {\n CONSENT_COOKIE,\n VISITOR_COOKIE,\n consentCookieName,\n visitorCookieName,\n} from \"../core/cookie-names\";\nimport type { ConsentState } from \"../core/types\";\n\n/**\n * Cookie names the SDK reads server-side (set by the browser SDK / your app).\n *\n * The constants are the DEFAULT-prefix names; the functions build the name for any prefix. If you\n * pass `config.prefix` to the browser client, pass the same prefix here — the browser namespaces\n * the cookie by it, so the default name won't be there to find.\n */\nexport { CONSENT_COOKIE, VISITOR_COOKIE, consentCookieName, visitorCookieName };\n\n/**\n * Parse a raw `Cookie` header into a map. Framework-agnostic so it works with\n * `Request.headers.get(\"cookie\")`, Node `req.headers.cookie`, etc.\n */\nexport function parseCookies(header: string | null | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!header) return out;\n for (const part of header.split(\";\")) {\n const idx = part.indexOf(\"=\");\n if (idx === -1) continue;\n const key = part.slice(0, idx).trim();\n const value = part.slice(idx + 1).trim();\n if (key) out[key] = decodeURIComponent(value);\n }\n return out;\n}\n\n/**\n * Read the visitor id the browser SDK wrote, so a server-side event can be bound to the same\n * visitor (`createXeniaServer({ visitorId })`) and attribute to a browser-entered journey.\n *\n * @param prefix Must match the browser client's `config.prefix`. The cookie is namespaced by it, so\n * a mismatch finds nothing and quietly leaves the server with no visitor.\n */\nexport function getVisitorId(\n cookieHeader: string | null | undefined,\n prefix?: string,\n): string | null {\n return parseCookies(cookieHeader)[visitorCookieName(prefix)] ?? null;\n}\n\n/**\n * Read consent from a cookie header. Accepts a JSON blob or a comma-list of\n * granted scopes (e.g. `analytics,personalization`). Absent → all denied.\n *\n * @param prefix Must match the browser client's `config.prefix` — see {@link getVisitorId}.\n */\nexport function getConsent(\n cookieHeader: string | null | undefined,\n prefix?: string,\n): ConsentState {\n const raw = parseCookies(cookieHeader)[consentCookieName(prefix)];\n if (!raw) return { analytics: false, marketing: false, personalization: false };\n try {\n const parsed = JSON.parse(raw) as ConsentState;\n return parsed;\n } catch {\n const scopes = new Set(raw.split(\",\").map((s) => s.trim()));\n return {\n analytics: scopes.has(\"analytics\"),\n marketing: scopes.has(\"marketing\"),\n personalization: scopes.has(\"personalization\"),\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/token.ts"],"names":["HttpClient"],"mappings":";;;;;;;AAgCA,eAAsB,gBAAgB,IAAA,EAAoD;AACxF,EAAA,MAAM,IAAA,GAAO,IAAIA,4BAAA,CAAW,EAAE,OAAA,EAAS,KAAK,OAAA,EAAS,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,CAAA;AAC5E,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,KAAK,OAAA,CAAQ;AAAA,IAClC,MAAA,EAAQ,MAAA;AAAA,IACR,IAAA,EAAM,oCAAA;AAAA,IACN,SAAS,EAAE,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,QAAQ,CAAA,CAAA,EAAG;AAAA,IACpD,IAAA,EAAM,EAAE,UAAA,EAAY,IAAA,CAAK,cAAc,IAAA;AAAK,GAC7C,CAAA;AACD,EAAA,MAAM,GAAA,GAAO,QAAQ,EAAC;AACtB,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,MAAA,CAAO,GAAA,CAAI,KAAA,IAAS,EAAE,CAAA;AAAA,IAC7B,WAAW,OAAO,GAAA,CAAI,SAAA,KAAc,QAAA,GAAW,IAAI,SAAA,GAAY;AAAA,GACjE;AACF;AAiBO,SAAS,iBAAiB,MAAA,EAAsE;AACrG,EAAA,OAAO,YAA+B;AACpC,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,MAAM,eAAA,CAAgB,MAAM,CAAA;AAC9C,MAAA,OAAO,IAAI,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,SAAS,CAAA,EAAG;AAAA,QAC7C,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAoB,iBAAiB,UAAA;AAAW,OAC5E,CAAA;AAAA,IACH,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,OAAA,GAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,sBAAA;AACrD,MAAA,OAAO,IAAI,SAAS,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,OAAA,EAAS,CAAA,EAAG;AAAA,QACtD,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA;AAAmB,OAC/C,CAAA;AAAA,IACH;AAAA,EACF,CAAA;AACF","file":"index.cjs","sourcesContent":["import { HttpClient } from \"../core/http-client\";\nimport type { JsonObject } from \"../core/types\";\n\nexport interface RefreshTokenOptions {\n /**\n * Xenia base URL. Defaults to the hosted engine,\n * `https://engine.xeniamartech.com/xenia` — omit unless self-hosting.\n */\n baseUrl?: string;\n /**\n * The personalization SDK token created once in the dashboard (API Tokens →\n * Create token). Stays server-side; the browser never sees it — only the\n * short-lived token this exchanges it for does.\n */\n sdkToken: string;\n /** Lifetime of the returned token, in minutes. Default 15, capped server-side at 60. */\n ttlMinutes?: number;\n timeout?: number;\n}\n\nexport interface RefreshedToken {\n token: string;\n expiresAt?: string;\n}\n\n/**\n * Exchange the long-lived personalization SDK token for a short-lived one with\n * the same tenant/env scope, via `POST /api/personalization/token/refresh`.\n * Self-service — no dashboard credential needed, just the SDK token you\n * already have. Call this from your backend and hand the result to the\n * browser (e.g. from a token route the client SDK calls via `getToken`).\n */\nexport async function refreshSdkToken(opts: RefreshTokenOptions): Promise<RefreshedToken> {\n const http = new HttpClient({ baseUrl: opts.baseUrl, timeout: opts.timeout });\n const { data } = await http.request({\n method: \"POST\",\n path: \"/api/personalization/token/refresh\",\n headers: { Authorization: `Bearer ${opts.sdkToken}` },\n body: { ttlMinutes: opts.ttlMinutes ?? null },\n });\n const obj = (data ?? {}) as JsonObject;\n return {\n token: String(obj.token ?? \"\"),\n expiresAt: typeof obj.expiresAt === \"string\" ? obj.expiresAt : undefined,\n };\n}\n\n/**\n * Build a framework-agnostic request handler that hands the browser SDK a\n * personalization token — mount it at the endpoint `getToken` calls. Needs\n * only the SDK token you already created in the dashboard; mints a fresh\n * short-lived token per request so the long-lived one never reaches the\n * browser.\n *\n * @example\n * ```ts\n * export const GET = createTokenRoute({\n * sdkToken: process.env.XENIA_SDK_TOKEN!,\n * // baseUrl: process.env.XENIA_URL, // only to override the default hosted engine\n * });\n * ```\n */\nexport function createTokenRoute(config: RefreshTokenOptions): (request: Request) => Promise<Response> {\n return async (): Promise<Response> => {\n try {\n const refreshed = await refreshSdkToken(config);\n return new Response(JSON.stringify(refreshed), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"token refresh failed\";\n return new Response(JSON.stringify({ error: message }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n };\n}\n"]}
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,106 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Extra per-call identity/context for server-side webhook calls — an override on top of whatever the
|
|
6
|
-
* client already carries.
|
|
7
|
-
*
|
|
8
|
-
* There's no ambient `window` on the server to auto-collect `path`/`query`/`url`/UTM params the way
|
|
9
|
-
* the browser client does, so bind them ONCE per request via {@link XeniaConfig.context} (see
|
|
10
|
-
* `contextFromUrl`) and every call attaches them automatically. These per-call fields are for the
|
|
11
|
-
* remainder: values known only at this call site, or a deliberate override of the ambient ones.
|
|
12
|
-
*/
|
|
13
|
-
interface ServerWebhookOptions {
|
|
14
|
-
method?: "GET" | "POST";
|
|
15
|
-
journeyId?: string;
|
|
16
|
-
identity?: EventIdentity;
|
|
17
|
-
context?: EventContext;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Options for a server-side unified event (`POST /api/events/v1`). There's no
|
|
21
|
-
* ambient request context on the server — pass whatever's known at the call
|
|
22
|
-
* site (e.g. pathname/user-agent from the current request, session id from a
|
|
23
|
-
* cookie) via `identity`/`context`.
|
|
24
|
-
*/
|
|
25
|
-
interface ServerTrackOptions {
|
|
26
|
-
/** When true, also match/advance a journey (never sampled/shed). Default false. */
|
|
27
|
-
trigger?: boolean;
|
|
28
|
-
/** Stable dedup key (e.g. `"ord-1001"`) for idempotent conversions. */
|
|
29
|
-
eventId?: string;
|
|
30
|
-
eventVersion?: number;
|
|
31
|
-
occurredAtMs?: number;
|
|
32
|
-
/** Origin tag. Default `"xenia-server-sdk"`. */
|
|
33
|
-
source?: string;
|
|
34
|
-
/** Identity fields for this event (except the SDK-owned `xeniaVisitorId`,
|
|
35
|
-
* which comes from `config.visitorId` and can't be set per call). */
|
|
36
|
-
identity?: CallerIdentity;
|
|
37
|
-
context?: AnalyticsContext;
|
|
38
|
-
properties?: Record<string, JsonValue | undefined>;
|
|
39
|
-
consent?: ConsentState;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Server-native Xenia client. Same method names as the browser client, but
|
|
43
|
-
* every call is stateless: identity and context are passed explicitly rather
|
|
44
|
-
* than read from the DOM/localStorage. Uses global `fetch` (Node ≥18).
|
|
45
|
-
*/
|
|
46
|
-
declare class XeniaServer {
|
|
47
|
-
readonly api: XeniaApi;
|
|
48
|
-
/** SDK-owned visitor anchor for this (per-request) client — see {@link XeniaConfig.visitorId}. */
|
|
49
|
-
private readonly visitorId?;
|
|
50
|
-
/** Ambient request context attached to every call — see {@link XeniaConfig.context}. */
|
|
51
|
-
private readonly context?;
|
|
52
|
-
constructor(config: XeniaConfig);
|
|
53
|
-
/**
|
|
54
|
-
* Send a unified event server-side (`POST /api/events/v1`) and return the
|
|
55
|
-
* ingest response (including per-result `triggered`). `opts.trigger: true`
|
|
56
|
-
* also matches/advances a journey; pass `opts.eventId` (e.g. `"ord-1001"`)
|
|
57
|
-
* as a stable dedup key for idempotent conversions.
|
|
58
|
-
*/
|
|
59
|
-
track(eventName: AnalyticsEventName, opts?: ServerTrackOptions): Promise<AnalyticsIngestResponse>;
|
|
60
|
-
/**
|
|
61
|
-
* Send a webhook request and await the synchronous response. Merges
|
|
62
|
-
* `opts.identity`/`opts.context` into the payload the same way the browser
|
|
63
|
-
* client's auto-attached fields do (`path`, `query`, `url`, `referrer`,
|
|
64
|
-
* `deviceId`, `visitorId`, etc.) — see `ServerWebhookOptions`.
|
|
65
|
-
*/
|
|
66
|
-
webhook(triggerId: string, data?: Record<string, JsonValue | undefined>, opts?: ServerWebhookOptions): Promise<WebhookResponse>;
|
|
67
|
-
/** Bind visitor→user server-side — `POST /api/events/v1/identify`. */
|
|
68
|
-
identify(body: IdentifyRequest): Promise<IdentifyResponse>;
|
|
69
|
-
/**
|
|
70
|
-
* Resolve in-entry content personalization for one content key
|
|
71
|
-
* (`POST /api/personalization/resolve`). Requires a configured SDK token
|
|
72
|
-
* (`token`/`getToken`) — resolve is auth-required. With `merge: true` the
|
|
73
|
-
* returned `content` is server-spliced.
|
|
74
|
-
*
|
|
75
|
-
* Pass `opts.identity`/`opts.context` to attach the same SDK-provided
|
|
76
|
-
* fields (`deviceId`, `customerId`, `email`, `path`, `url`, `referrer`,
|
|
77
|
-
* `utm*`, …) the browser client collects ambiently — there's no ambient
|
|
78
|
-
* `window`/`document` server-side, so a server caller must supply whatever
|
|
79
|
-
* it knows (e.g. from the current request) explicitly. See
|
|
80
|
-
* {@link ResolveOptions}.
|
|
81
|
-
*/
|
|
82
|
-
resolve(contentKey: string, opts?: ResolveOptions): Promise<ResolveResult>;
|
|
83
|
-
/** Resolve several content keys in one round-trip (`POST /api/personalization/resolve-batch`). */
|
|
84
|
-
resolveBatch(items: ResolveBatchItem[], opts?: ResolveOptions): Promise<ResolveBatchResult[]>;
|
|
85
|
-
/**
|
|
86
|
-
* Merge the ambient `config.context` and `opts.identity`/`opts.context` into
|
|
87
|
-
* the resolve `data`, then stamp the SDK-owned visitor id
|
|
88
|
-
* (`config.visitorId`) under both the reserved `_xenia_vid` key (read by the
|
|
89
|
-
* backend for holdout/exposure bucketing) and the flat `visitorId` field —
|
|
90
|
-
* mirrors `webhook()`'s merge order/precedence exactly: caller `data` <
|
|
91
|
-
* ambient `config.context` < caller `context`/`identity` < SDK-owned
|
|
92
|
-
* `visitorId`/`_xenia_vid`, set LAST so neither can be overridden per call.
|
|
93
|
-
*
|
|
94
|
-
* `config.context` is what makes the SDK-provided fields automatic on the
|
|
95
|
-
* server: bound once per request, every `resolve()` carries `path`/`query`/
|
|
96
|
-
* `url`/`referrer`/UTMs with no per-call wiring, matching what the browser
|
|
97
|
-
* client collects from `window` on its own. A no-op when nothing is bound and
|
|
98
|
-
* nothing was passed — preserving today's behavior for callers using neither.
|
|
99
|
-
*/
|
|
100
|
-
private withVisitor;
|
|
101
|
-
}
|
|
102
|
-
/** Create a server-native client. */
|
|
103
|
-
declare function createXeniaServer(config: XeniaConfig): XeniaServer;
|
|
1
|
+
export { S as ServerResolveOptions, a as ServerTrackOptions, b as ServerWebhookOptions, W as WithFallback, X as XeniaServer, c as createXeniaServer } from '../xenia-server-rs3Gpbw7.cjs';
|
|
2
|
+
import { d as ConsentState } from '../api-URhxxi4x.cjs';
|
|
3
|
+
export { A as AnalyticsContext, a as AnalyticsEventName, b as AnalyticsIdentity, c as AnalyticsIngestResponse, C as CircuitBreakerConfig, E as EventContext, e as EventIdentity, I as IdentifyRequest, f as IdentifyResponse, J as JsonValue, P as PersonalizationSwap, R as ResolveBatchItem, j as ResolveBatchResult, k as ResolveOptions, l as ResolveResult, W as WebhookResponse, s as XeniaApi, t as XeniaConfig, u as XeniaEvent } from '../api-URhxxi4x.cjs';
|
|
4
|
+
export { C as CONSENT_COOKIE, a as CircuitOpenError, V as VISITOR_COOKIE, f as consentCookie, g as consentCookieName, p as parseActions, v as visitorCookieName } from '../cookie-names-QYMiEMAq.cjs';
|
|
104
5
|
|
|
105
6
|
interface RefreshTokenOptions {
|
|
106
7
|
/**
|
|
@@ -168,4 +69,4 @@ declare function getVisitorId(cookieHeader: string | null | undefined, prefix?:
|
|
|
168
69
|
*/
|
|
169
70
|
declare function getConsent(cookieHeader: string | null | undefined, prefix?: string): ConsentState;
|
|
170
71
|
|
|
171
|
-
export {
|
|
72
|
+
export { ConsentState, type RefreshTokenOptions, type RefreshedToken, createTokenRoute, getConsent, getVisitorId, parseCookies, refreshSdkToken };
|