@xenia-martech/sdk-js 1.0.0-dev.27 → 1.0.0-dev.29
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/CSS_VARIABLES.md +7 -0
- package/README.md +50 -1
- package/dist/{actions-CkiSdvSa.d.cts → actions-CcBYV--3.d.cts} +177 -1
- package/dist/{actions-CkiSdvSa.d.ts → actions-CcBYV--3.d.ts} +177 -1
- package/dist/{chunk-UOW3GK4I.cjs → chunk-WYM4CQVG.cjs} +96 -4
- package/dist/chunk-WYM4CQVG.cjs.map +1 -0
- package/dist/{chunk-7D2HJMW6.js → chunk-XXNDHYAS.js} +96 -4
- package/dist/chunk-XXNDHYAS.js.map +1 -0
- package/dist/client/index.cjs +543 -27
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +112 -3
- package/dist/client/index.d.ts +112 -3
- package/dist/client/index.js +540 -28
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +539 -27
- 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 +539 -27
- package/dist/client/react/index.js.map +1 -1
- package/dist/{cookie-names-C3LKDhsg.d.cts → cookie-names-B3b0XNc2.d.cts} +153 -1
- package/dist/{cookie-names-C3LKDhsg.d.ts → cookie-names-B3b0XNc2.d.ts} +153 -1
- package/dist/index.cjs +20 -20
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/server/index.cjs +15 -15
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +2 -2
- package/dist/xenia-sdk.iife.js +37 -3
- package/dist/xenia-sdk.iife.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7D2HJMW6.js.map +0 -1
- package/dist/chunk-UOW3GK4I.cjs.map +0 -1
package/CSS_VARIABLES.md
CHANGED
|
@@ -105,6 +105,13 @@ declaring the variables on a narrower ancestor element instead of `:root`.
|
|
|
105
105
|
| `--xenia-close-size` | `20px` | Close (`×`) glyph font size. |
|
|
106
106
|
| `--xenia-close-color` | `#999` | Close (`×`) glyph color. |
|
|
107
107
|
|
|
108
|
+
### Spin-wheel widget
|
|
109
|
+
|
|
110
|
+
| Variable | Default | Affects |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `--xenia-cta-bg` / `--xenia-cta-color` / `--xenia-cta-radius` / `--xenia-cta-padding` / `--xenia-cta-size` | see [Call-to-action button](#call-to-action-button) | The wheel's "Spin" button — shares the same variables as the generic CTA button, so both stay visually consistent by default. |
|
|
113
|
+
| `--xenia-wheel-pointer-color` | `#111` | Color of the pointer triangle above the wheel and the center hub cap. |
|
|
114
|
+
|
|
108
115
|
## Notes
|
|
109
116
|
|
|
110
117
|
- Variables only affect the SDK's **built-in** card markup. If you set `html` on an action, or
|
package/README.md
CHANGED
|
@@ -502,6 +502,54 @@ function OfferCard({ action, close, fireCta }: XeniaMessageComponentProps) {
|
|
|
502
502
|
|
|
503
503
|
Your component owns 100% of the card's markup and styling (same as a per-action `html` override) — the SDK still owns placement, z-index, and (for `modal`) the backdrop, so `--xenia-z-index`/`--xenia-popup-offset`/`--xenia-modal-backdrop`/etc. from [Theming with CSS variables](#theming-with-css-variables) still apply; the card-specific variables (`--xenia-card-*`, `--xenia-title-*`, `--xenia-cta-*`, …) don't, since there's no built-in card element to style.
|
|
504
504
|
|
|
505
|
+
### Interactive widgets (spin-wheel)
|
|
506
|
+
|
|
507
|
+
An action can also carry `widgetType: "spin-wheel"` — a gamified outcome the server resolves (a discount code, a "try again"), rendered and submitted differently from a plain message action:
|
|
508
|
+
|
|
509
|
+
```jsonc
|
|
510
|
+
{
|
|
511
|
+
"type": "popup",
|
|
512
|
+
"widgetType": "spin-wheel",
|
|
513
|
+
"segments": [
|
|
514
|
+
{ "id": "s1", "label": "10% off", "colorHex": "#7c3aed" },
|
|
515
|
+
{ "id": "s2", "label": "Try again", "colorHex": "#2563eb", "isLoss": true }
|
|
516
|
+
],
|
|
517
|
+
"formSubmit": { "requiresResolve": true, "mode": "journey", "bindingRef": "wsb_abc123" },
|
|
518
|
+
"spinnerHtml": "<div class=\"wheel-screen\">…</div>",
|
|
519
|
+
"winHtml": "<div class=\"win-screen\">…</div>",
|
|
520
|
+
"lossHtml": "<div class=\"loss-screen\">…</div>"
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
`segments` is label/color/`isLoss` **only** — the real prize weighting and any prize code are marketer-configured server-side on the webhook-response node and never reach the browser. `formSubmit` is likewise entirely server-driven: `requiresResolve: true` means the widget must call the server and get back a resolved segment before showing any outcome — there is never a client-guessed or optimistic result.
|
|
525
|
+
|
|
526
|
+
**Authoring the three screens.** `spinnerHtml`/`winHtml`/`lossHtml` are full marketer/AI-authored HTML, exactly like a popup's `html` override — there's no generated fallback design, so your markup **is** the widget. The SDK only needs a few `data-xenia-*` hooks inside it to wire behavior:
|
|
527
|
+
|
|
528
|
+
| Attribute | Goes in | Effect |
|
|
529
|
+
|---|---|---|
|
|
530
|
+
| `data-xenia-wheel-slot` | `spinnerHtml` | Where the SDK mounts its generated SVG wheel + pointer. |
|
|
531
|
+
| `data-xenia-spin` | `spinnerHtml` | Click starts the spin — calls `submitWidgetForm()`, waits for the server-resolved segment, then animates the wheel to land on it. |
|
|
532
|
+
| `data-xenia-wheel-prize` | `winHtml` / `lossHtml` (either or both) | Filled with the resolved segment's `label` once known. A losing segment still has a label (e.g. "Try again"), so this works on the loss screen too. |
|
|
533
|
+
| `data-xenia-wheel-cta` | `winHtml` / `lossHtml` | Click fires the action's `cta.event`/`cta.url` (same as a message action's CTA) before dismissing. |
|
|
534
|
+
| `data-xenia-spin-again` | `winHtml` / `lossHtml` | Optional "try again" button — swaps back to `spinnerHtml` and re-arms the spin button. Hidden automatically once the server reports no plays remain (`alreadyPlayed`). This never grants another spin by itself — the server's own play-limit/win-lock decides what the next `data-xenia-spin` click actually resolves to. |
|
|
535
|
+
| `data-xenia-close`, or any element whose class/id contains "close"/"backdrop" | any of the three | Dismisses the widget — the same close-affordance convention every custom-HTML surface in the SDK honors, so a design pasted from elsewhere (e.g. an inline `onclick`) still closes correctly. |
|
|
536
|
+
| `data-xenia-field="<name>"` / `data-xenia-submit` | `spinnerHtml`, or any widget's markup | The shared field-capture convention (see below) — wrap real inputs in a `<form>` for free Enter-to-submit, or mark a plain click trigger with `data-xenia-submit`. |
|
|
537
|
+
|
|
538
|
+
If a click on `data-xenia-spin`/`data-xenia-submit` fires before the widget has a configured `formSubmit` (the marketer hasn't wired the webhook-response node's Form-submission panel yet), the SDK shows an inert `data-xenia-error` message instead of silently doing nothing or reloading the page.
|
|
539
|
+
|
|
540
|
+
**Replays.** A visitor who already played sees the same resolved outcome again rather than a fresh roll — `WidgetSubmitResponse.alreadyPlayed` is `true` in that case, and the wheel skips its spin animation and jumps straight to the reveal (there's nothing new to animate toward).
|
|
541
|
+
|
|
542
|
+
**The shared field/submit/success convention** (`collectFields`/`wireFormSubmit`, also used by plain `standard` popups with a capturable form) applies inside widget HTML too:
|
|
543
|
+
|
|
544
|
+
- Mark real inputs `data-xenia-field="<name>"`; the submit trigger is `data-xenia-submit` (a `<form>` wrapper is optional — the SDK intercepts its native `submit` either way).
|
|
545
|
+
- The submit control grows a small built-in spinner while the request is in flight (no markup needed for this).
|
|
546
|
+
- On success, if you authored a `<div data-xenia-success hidden>…</div>` block anywhere in the HTML, it's revealed in place of the form for at least `formSubmit.successDisplayMs` (default 900ms — long enough for a visitor to register it worked, since the real round-trip is often under 100ms) before the widget dismisses itself. No success block just means a plain wait-then-dismiss.
|
|
547
|
+
- On error, the spinner clears, the control re-enables, and `data-xenia-error` shows a retry message.
|
|
548
|
+
|
|
549
|
+
**Per-widget style overrides** — `style: { hideCloseButton?, hideOverlay? }` — control the SDK's own chrome around the widget (not authored content). `hideCloseButton` isn't currently exposed in Content Studio's AI schema or read by any renderer yet; `hideOverlay` suppresses the dimmed backdrop, default shown.
|
|
550
|
+
|
|
551
|
+
See [Theming with CSS variables](#theming-with-css-variables) for the wheel's own variables (`--xenia-wheel-pointer-color`, plus the shared `--xenia-cta-*` set for the Spin button).
|
|
552
|
+
|
|
505
553
|
---
|
|
506
554
|
|
|
507
555
|
## Theming with CSS variables
|
|
@@ -841,6 +889,7 @@ Never block the page on this — render `baseHero` immediately and swap in the p
|
|
|
841
889
|
- `resolve(contentKey, opts?: ResolveOptions)` → `ResolveResult` · `resolveBatch(items, opts?)` → `ResolveBatchResult[]` — REST personalization (requires the SDK token)
|
|
842
890
|
- `identify(visitorId)` · `setJourneyId(id)` · `consent(state)`
|
|
843
891
|
- `request(triggerId, data?, { apply?, method? })` → `WebhookResponse`
|
|
892
|
+
- `submitWidgetForm(formSubmit, fields, opts?: { idempotencyKey?, apply? })` → `WidgetSubmitResponse` — submits a widget's captured fields (spin-wheel resolve, or a plain custom-widget form submit); see [Interactive widgets](#interactive-widgets-spin-wheel)
|
|
844
893
|
- `getResponse(key)` → `WebhookResponse | undefined`
|
|
845
894
|
- `swapContent(target, {html?|text?})` · `hideContent(target)` · `showContent(target)` · `applyActions(actions)`
|
|
846
895
|
- `on(event, cb)` — `"event" | "response" | "actions" | "error" | "identify"`
|
|
@@ -899,7 +948,7 @@ Never block the page on this — render `baseHero` immediately and swap in the p
|
|
|
899
948
|
- `parseActions(body)`, `generateUlid()`, `toEventId(raw)`, `MAX_EVENT_ID_LENGTH`, `spliceSlots(slots, swaps)`, `extractContentSwaps(body)`, `VISITOR_COOKIE`, `CONSENT_COOKIE`.
|
|
900
949
|
- `HttpClient`, `HttpError`, `buildUrl(base, path)`, `isBrowser()` — the underlying HTTP layer; `isBrowser()` is handy for guarding SSR-unsafe code in your own app.
|
|
901
950
|
- `MemoryResponseStore`, `LocalStorageResponseStore` (implementations of the `ResponseStore` interface).
|
|
902
|
-
- All types (`XeniaConfig`, `VisitorCookieOptions`, `XeniaEvent`, `TrackOptions`, `WebhookResponse`, `XeniaAction`, `MessagePosition`, `AnalyticsEventName`, `AnalyticsIngestResponse`, `AnalyticsContext`, `ResolveResult`, `ResolveBatchResult`, `PersonalizationSwap`, `ContentSwap`, …).
|
|
951
|
+
- All types (`XeniaConfig`, `VisitorCookieOptions`, `XeniaEvent`, `TrackOptions`, `WebhookResponse`, `XeniaAction`, `MessagePosition`, `AnalyticsEventName`, `AnalyticsIngestResponse`, `AnalyticsContext`, `ResolveResult`, `ResolveBatchResult`, `PersonalizationSwap`, `ContentSwap`, `WidgetType`, `WidgetSegment`, `WidgetFormSubmit`, `WidgetStyle`, `WidgetPrize`, `WidgetSubmitResponse`, …).
|
|
903
952
|
|
|
904
953
|
</details>
|
|
905
954
|
|
|
@@ -375,6 +375,109 @@ interface MessageCta {
|
|
|
375
375
|
/** Optional event fired when the CTA is clicked. */
|
|
376
376
|
event?: string;
|
|
377
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Widget-specific rendering/interaction behavior for a message-placement action —
|
|
380
|
+
* ORTHOGONAL to `type` (the existing `banner|popup|modal|inline` placement enum, which
|
|
381
|
+
* stays exactly as-is). `type` says WHERE it renders; `widgetType` says WHAT it is.
|
|
382
|
+
* Keeping these separate is what lets `core/actions.ts`'s `MESSAGE_TYPES` allowlist AND
|
|
383
|
+
* the PHP SDK's `XeniaActions::KNOWN_TYPES` allowlist (same value set) stay completely
|
|
384
|
+
* unchanged — the PHP SDK needs zero code changes for widget support.
|
|
385
|
+
*
|
|
386
|
+
* `"standard"` (or omitted) is a plain message/popup — the existing card path, unmodified.
|
|
387
|
+
* A future 3rd widget type is one new literal here + one new dispatch-table entry in
|
|
388
|
+
* `client/widgets/index.ts`, not a rewrite.
|
|
389
|
+
*/
|
|
390
|
+
type WidgetType = "standard" | "spin-wheel";
|
|
391
|
+
/**
|
|
392
|
+
* A prize/outcome segment shown to the visitor for a spin-wheel widget — label/color ONLY.
|
|
393
|
+
* Real weight/prize-code are marketer-authored server-side config (`config.widgetSubmissions`)
|
|
394
|
+
* and never leave the backend; they never appear here.
|
|
395
|
+
*/
|
|
396
|
+
interface WidgetSegment {
|
|
397
|
+
id: string;
|
|
398
|
+
label: string;
|
|
399
|
+
colorHex?: string;
|
|
400
|
+
/** Marks this segment as a non-winning outcome (e.g. "Try Again") — drives which of the
|
|
401
|
+
* win/loss reveal screens (see WidgetStyle) the widget shows once this segment resolves. */
|
|
402
|
+
isLoss?: boolean;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The SAFE runtime envelope describing how a widget's captured form submission is
|
|
406
|
+
* dispatched — never a credential, never a prize weight/code (see the cross-repo
|
|
407
|
+
* contract). The SDK does not branch on `mode`'s value itself; it only follows whichever
|
|
408
|
+
* ref field is present (`flowRef` vs `bindingRef`) to pick an endpoint — `mode`/`ref` are
|
|
409
|
+
* otherwise relayed to the backend verbatim, uninterpreted.
|
|
410
|
+
*/
|
|
411
|
+
interface WidgetFormSubmit {
|
|
412
|
+
/** `true` for spin-wheel (server must resolve an outcome before anything renders). */
|
|
413
|
+
requiresResolve: boolean;
|
|
414
|
+
/** `"journey"` or `"endpoint"` — informational only; the SDK never branches on this value. */
|
|
415
|
+
mode: "journey" | "endpoint" | (string & {});
|
|
416
|
+
/** Present only when `mode === "journey" && !requiresResolve` — the direct `/flow` fast path, no secret. */
|
|
417
|
+
flowRef?: string;
|
|
418
|
+
/** Present whenever `requiresResolve || mode === "endpoint"`. */
|
|
419
|
+
bindingRef?: string;
|
|
420
|
+
/**
|
|
421
|
+
* Minimum time (ms) to keep the success state visible after a submit resolves, before the
|
|
422
|
+
* card dismisses itself — configured marketer-side on the webhook-response node's
|
|
423
|
+
* Form-submission panel. The real dispatch typically responds in well under 100ms, which
|
|
424
|
+
* would otherwise make the card vanish before a visitor registers it succeeded at all.
|
|
425
|
+
* Defaults to 900 when absent (an older/unconfigured binding).
|
|
426
|
+
*/
|
|
427
|
+
successDisplayMs?: number;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Per-action visual overrides for a widget's own window chrome (spin-wheel).
|
|
431
|
+
* Deliberately narrow — everything about the widget's actual CONTENT (spinner/win/loss screens)
|
|
432
|
+
* is full marketer/AI-authored HTML (`spinnerHtml`/`winHtml`/`lossHtml` on `XeniaAction`, same
|
|
433
|
+
* pattern as a popup's `body`), not a field here. This only covers the two things that live
|
|
434
|
+
* OUTSIDE that HTML, in the SDK's own generated chrome around it.
|
|
435
|
+
*/
|
|
436
|
+
interface WidgetStyle {
|
|
437
|
+
/**
|
|
438
|
+
* Hides the built-in close (×) button. No spin-wheel renderer draws one of its own anymore
|
|
439
|
+
* (see `spin-wheel.ts`'s `html()`) — every screen's own `spinnerHtml`/`winHtml`/`lossHtml`
|
|
440
|
+
* carries its own close affordance, matched by the same heuristic every custom-HTML surface
|
|
441
|
+
* honors (`wireCloseAffordances`). Currently vestigial: not exposed in Content Studio's AI
|
|
442
|
+
* schema, and not read by any widget renderer's `html()`.
|
|
443
|
+
*/
|
|
444
|
+
hideCloseButton?: boolean;
|
|
445
|
+
/** Hides the dimmed backdrop behind the modal/popup/banner. Defaults to shown. */
|
|
446
|
+
hideOverlay?: boolean;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Server-resolved outcome for a spin-wheel submission
|
|
450
|
+
* (`XeniaClient.submitWidgetForm`'s response). Absent for a plain form submit.
|
|
451
|
+
* `segmentId` matches an `id` in the action's `segments` list. Never the whole prize
|
|
452
|
+
* config (weight/internal code) — only what the backend decides is safe to reveal.
|
|
453
|
+
*/
|
|
454
|
+
interface WidgetPrize {
|
|
455
|
+
segmentId?: string;
|
|
456
|
+
label?: string;
|
|
457
|
+
colorHex?: string;
|
|
458
|
+
[key: string]: JsonValue | undefined;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Response from a widget form submission (`XeniaClient.submitWidgetForm`) — whichever
|
|
462
|
+
* endpoint was actually hit (the `/flow` fast path or `/widget-submissions`), normalized
|
|
463
|
+
* to one shape. `actions` flows through the existing `parseActions()`/`ActionRenderer.apply()`
|
|
464
|
+
* pipeline unchanged; `prize` is read directly by the spin-wheel widget module to drive
|
|
465
|
+
* its reveal.
|
|
466
|
+
*/
|
|
467
|
+
interface WidgetSubmitResponse {
|
|
468
|
+
status: number;
|
|
469
|
+
body: JsonValue;
|
|
470
|
+
actions: XeniaAction[];
|
|
471
|
+
prize?: WidgetPrize;
|
|
472
|
+
/** True when this visitor already played and the stored result is being replayed, not re-rolled. */
|
|
473
|
+
alreadyPlayed?: boolean;
|
|
474
|
+
/** Whether the configured dispatch (journey invoke / outbound endpoint call) actually fired. */
|
|
475
|
+
dispatched?: boolean;
|
|
476
|
+
/** Present when `dispatched` is false and the server attempted but failed the configured dispatch. */
|
|
477
|
+
dispatchError?: string;
|
|
478
|
+
widgetType?: WidgetType;
|
|
479
|
+
receivedAt: number;
|
|
480
|
+
}
|
|
378
481
|
/**
|
|
379
482
|
* A render directive the SDK executes in the browser. Marketers emit these in
|
|
380
483
|
* a `webhook-response` node body under the `xeniaActions` key.
|
|
@@ -410,6 +513,43 @@ type XeniaAction = {
|
|
|
410
513
|
/** Anchor within the placement (see {@link MessagePosition}). Defaults to the
|
|
411
514
|
* SDK's built-in default per type (popup: bottom-right; banner: bottom). */
|
|
412
515
|
position?: MessagePosition;
|
|
516
|
+
/** Orthogonal widget-behavior driver — see {@link WidgetType}. Omitted/`"standard"` renders
|
|
517
|
+
* exactly like today. */
|
|
518
|
+
widgetType?: WidgetType;
|
|
519
|
+
/** Label/color-only outcome segments for `spin-wheel` widgets. */
|
|
520
|
+
segments?: WidgetSegment[];
|
|
521
|
+
/** Safe form-submission envelope — see {@link WidgetFormSubmit}. Absent when the action has
|
|
522
|
+
* no capturable outcome. */
|
|
523
|
+
formSubmit?: WidgetFormSubmit;
|
|
524
|
+
/** Per-action visual overrides for the widget's own window — see {@link WidgetStyle}. */
|
|
525
|
+
style?: WidgetStyle;
|
|
526
|
+
/**
|
|
527
|
+
* Full marketer/AI-authored HTML for `spin-wheel`'s pre-spin face (title/subtitle/spin
|
|
528
|
+
* button/background — the whole visual design) — independent of the generic `html` field
|
|
529
|
+
* above, which spin-wheel never reads. The primary, ordinary way this screen
|
|
530
|
+
* is authored (same role `body` plays for a popup) — there is no separate generated
|
|
531
|
+
* template to fall back to. To keep the wheel and spin button working, include an element
|
|
532
|
+
* carrying `data-xenia-wheel-slot` (the SDK mounts the generated SVG wheel + pointer
|
|
533
|
+
* there) and one carrying `data-xenia-spin` (the SDK wires the spin click there) —
|
|
534
|
+
* omitting either simply drops that capability, same as any other custom-HTML widget.
|
|
535
|
+
*/
|
|
536
|
+
spinnerHtml?: string;
|
|
537
|
+
/**
|
|
538
|
+
* Full marketer/AI-authored HTML for the win-reveal screen (`spin-wheel`), shown once a
|
|
539
|
+
* spin resolves to a non-`isLoss` segment — the primary way this screen is authored,
|
|
540
|
+
* same role as {@link spinnerHtml}. Include `data-xenia-wheel-prize` on any element to
|
|
541
|
+
* receive the resolved prize label, and `data-xenia-wheel-cta` on a button to wire its
|
|
542
|
+
* click to dismiss (same close-affordance heuristics as any other custom-HTML widget also
|
|
543
|
+
* apply here — see `wireCloseAffordances`).
|
|
544
|
+
*/
|
|
545
|
+
winHtml?: string;
|
|
546
|
+
/**
|
|
547
|
+
* Full marketer/AI-authored HTML for the loss/"try again" screen, shown once a
|
|
548
|
+
* spin resolves to a segment with {@link WidgetSegment.isLoss} set. Same hooks and
|
|
549
|
+
* same primary-authoring role as {@link winHtml} (a losing outcome still has a `label`,
|
|
550
|
+
* e.g. "Try Again", so `data-xenia-wheel-prize` is still honored here).
|
|
551
|
+
*/
|
|
552
|
+
lossHtml?: string;
|
|
413
553
|
} | {
|
|
414
554
|
type: "redirect";
|
|
415
555
|
url: string;
|
|
@@ -496,6 +636,18 @@ declare class XeniaApi {
|
|
|
496
636
|
* duplicate the journey run.
|
|
497
637
|
*/
|
|
498
638
|
webhook(triggerId: string, data?: JsonValue, method?: "GET" | "POST"): Promise<WebhookResponse>;
|
|
639
|
+
/**
|
|
640
|
+
* Widget form submission — `POST /widget-submissions?ref={bindingRef}`. Used whenever the
|
|
641
|
+
* action's `formSubmit.requiresResolve` is true (spin-wheel, which must have a
|
|
642
|
+
* server-resolved outcome before anything renders) or `mode === "endpoint"` — the common
|
|
643
|
+
* plain-popup case (`mode === "journey" && !requiresResolve`) bypasses this entirely and calls
|
|
644
|
+
* {@link webhook} against `flowRef` instead (see `XeniaClient.submitWidgetForm`'s endpoint
|
|
645
|
+
* selection).
|
|
646
|
+
*
|
|
647
|
+
* Not retried (`noRetry: true`), styled like {@link webhook} — a retried POST must never
|
|
648
|
+
* double-fire a journey or double-resolve a prize server-side.
|
|
649
|
+
*/
|
|
650
|
+
submitWidget(ref: string, body?: JsonValue): Promise<WidgetSubmitResponse>;
|
|
499
651
|
/**
|
|
500
652
|
* Unified event batch (max 100/request per the backend) — `POST /api/events/v1`.
|
|
501
653
|
* Records analytics for every event and matches/advances a journey for any
|
|
@@ -754,6 +906,30 @@ declare class XeniaClient {
|
|
|
754
906
|
}): Promise<WebhookResponse>;
|
|
755
907
|
/** Read the last saved webhook response for a key (triggerId/eventId). */
|
|
756
908
|
getResponse(key: string): WebhookResponse | undefined;
|
|
909
|
+
/**
|
|
910
|
+
* Submit a widget's captured form values (standard/custom popup submit, or a spin-wheel/
|
|
911
|
+
* spin-wheel's resolve request) and apply any returned `xeniaActions`, exactly like
|
|
912
|
+
* {@link request}.
|
|
913
|
+
*
|
|
914
|
+
* **Endpoint selection is config-driven, not SDK-decided** (per the cross-repo contract): a
|
|
915
|
+
* `flowRef` (present only when `mode === "journey" && !requiresResolve` — the common
|
|
916
|
+
* plain-popup case) bypasses the resolve endpoint entirely and POSTs straight to the existing
|
|
917
|
+
* public `/flow` fast path; otherwise this POSTs to `/widget-submissions` via `bindingRef`. The
|
|
918
|
+
* SDK is agnostic to *why* — it just follows whichever ref field the backend populated.
|
|
919
|
+
*
|
|
920
|
+
* The full ambient-context parity list {@link request}/`withVisitor()` already build
|
|
921
|
+
* (visitorId, deviceId, url, referrer, path, query, `utm*`) rides on every call, plus `fields`
|
|
922
|
+
* and `submit.mode`/the resolved ref, relayed verbatim/uninterpreted — the SDK never branches
|
|
923
|
+
* on `mode`'s value itself, that's server-side.
|
|
924
|
+
*
|
|
925
|
+
* `opts.idempotencyKey` defaults to a freshly generated ULID; pass the SAME key back in on a
|
|
926
|
+
* retried submit of the same interaction so a network retry can't double-fire a journey or
|
|
927
|
+
* double-resolve a prize server-side.
|
|
928
|
+
*/
|
|
929
|
+
submitWidgetForm(submit: WidgetFormSubmit, fields: Record<string, string>, opts?: {
|
|
930
|
+
idempotencyKey?: string;
|
|
931
|
+
apply?: boolean;
|
|
932
|
+
}): Promise<WidgetSubmitResponse>;
|
|
757
933
|
swapContent(target: string, content: {
|
|
758
934
|
html?: string;
|
|
759
935
|
text?: string;
|
|
@@ -808,4 +984,4 @@ declare function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined
|
|
|
808
984
|
*/
|
|
809
985
|
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
810
986
|
|
|
811
|
-
export { type AnalyticsContext as A, type ClientEvent as C, type InteractionType as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type
|
|
987
|
+
export { type AnalyticsContext as A, extractContentSwaps as B, type ClientEvent as C, parseActions as D, spliceSlots as E, type InteractionType as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type WidgetFormSubmit as W, type XeniaConfig as X, type XeniaEvent as a, type MessageTemplateFn as b, type MessageComponentRenderer as c, type WidgetSubmitResponse as d, type XeniaAction as e, type MessagePlacement as f, type AnalyticsEventName as g, type AnalyticsIdentity as h, type AnalyticsIngestResponse as i, type ConsentState as j, type ContentSwap as k, type IdentifyRequest as l, type IdentifyResponse as m, Identity as n, type MessageMountContext as o, type MessageRenderContext as p, type ResolveBatchResult as q, type ResolveOptions as r, type ResolveResult as s, type WebhookResponse as t, type WidgetPrize as u, type WidgetSegment as v, type WidgetType as w, XeniaClient as x, type XeniaClientOptions as y, createXeniaClient as z };
|
|
@@ -375,6 +375,109 @@ interface MessageCta {
|
|
|
375
375
|
/** Optional event fired when the CTA is clicked. */
|
|
376
376
|
event?: string;
|
|
377
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Widget-specific rendering/interaction behavior for a message-placement action —
|
|
380
|
+
* ORTHOGONAL to `type` (the existing `banner|popup|modal|inline` placement enum, which
|
|
381
|
+
* stays exactly as-is). `type` says WHERE it renders; `widgetType` says WHAT it is.
|
|
382
|
+
* Keeping these separate is what lets `core/actions.ts`'s `MESSAGE_TYPES` allowlist AND
|
|
383
|
+
* the PHP SDK's `XeniaActions::KNOWN_TYPES` allowlist (same value set) stay completely
|
|
384
|
+
* unchanged — the PHP SDK needs zero code changes for widget support.
|
|
385
|
+
*
|
|
386
|
+
* `"standard"` (or omitted) is a plain message/popup — the existing card path, unmodified.
|
|
387
|
+
* A future 3rd widget type is one new literal here + one new dispatch-table entry in
|
|
388
|
+
* `client/widgets/index.ts`, not a rewrite.
|
|
389
|
+
*/
|
|
390
|
+
type WidgetType = "standard" | "spin-wheel";
|
|
391
|
+
/**
|
|
392
|
+
* A prize/outcome segment shown to the visitor for a spin-wheel widget — label/color ONLY.
|
|
393
|
+
* Real weight/prize-code are marketer-authored server-side config (`config.widgetSubmissions`)
|
|
394
|
+
* and never leave the backend; they never appear here.
|
|
395
|
+
*/
|
|
396
|
+
interface WidgetSegment {
|
|
397
|
+
id: string;
|
|
398
|
+
label: string;
|
|
399
|
+
colorHex?: string;
|
|
400
|
+
/** Marks this segment as a non-winning outcome (e.g. "Try Again") — drives which of the
|
|
401
|
+
* win/loss reveal screens (see WidgetStyle) the widget shows once this segment resolves. */
|
|
402
|
+
isLoss?: boolean;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The SAFE runtime envelope describing how a widget's captured form submission is
|
|
406
|
+
* dispatched — never a credential, never a prize weight/code (see the cross-repo
|
|
407
|
+
* contract). The SDK does not branch on `mode`'s value itself; it only follows whichever
|
|
408
|
+
* ref field is present (`flowRef` vs `bindingRef`) to pick an endpoint — `mode`/`ref` are
|
|
409
|
+
* otherwise relayed to the backend verbatim, uninterpreted.
|
|
410
|
+
*/
|
|
411
|
+
interface WidgetFormSubmit {
|
|
412
|
+
/** `true` for spin-wheel (server must resolve an outcome before anything renders). */
|
|
413
|
+
requiresResolve: boolean;
|
|
414
|
+
/** `"journey"` or `"endpoint"` — informational only; the SDK never branches on this value. */
|
|
415
|
+
mode: "journey" | "endpoint" | (string & {});
|
|
416
|
+
/** Present only when `mode === "journey" && !requiresResolve` — the direct `/flow` fast path, no secret. */
|
|
417
|
+
flowRef?: string;
|
|
418
|
+
/** Present whenever `requiresResolve || mode === "endpoint"`. */
|
|
419
|
+
bindingRef?: string;
|
|
420
|
+
/**
|
|
421
|
+
* Minimum time (ms) to keep the success state visible after a submit resolves, before the
|
|
422
|
+
* card dismisses itself — configured marketer-side on the webhook-response node's
|
|
423
|
+
* Form-submission panel. The real dispatch typically responds in well under 100ms, which
|
|
424
|
+
* would otherwise make the card vanish before a visitor registers it succeeded at all.
|
|
425
|
+
* Defaults to 900 when absent (an older/unconfigured binding).
|
|
426
|
+
*/
|
|
427
|
+
successDisplayMs?: number;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Per-action visual overrides for a widget's own window chrome (spin-wheel).
|
|
431
|
+
* Deliberately narrow — everything about the widget's actual CONTENT (spinner/win/loss screens)
|
|
432
|
+
* is full marketer/AI-authored HTML (`spinnerHtml`/`winHtml`/`lossHtml` on `XeniaAction`, same
|
|
433
|
+
* pattern as a popup's `body`), not a field here. This only covers the two things that live
|
|
434
|
+
* OUTSIDE that HTML, in the SDK's own generated chrome around it.
|
|
435
|
+
*/
|
|
436
|
+
interface WidgetStyle {
|
|
437
|
+
/**
|
|
438
|
+
* Hides the built-in close (×) button. No spin-wheel renderer draws one of its own anymore
|
|
439
|
+
* (see `spin-wheel.ts`'s `html()`) — every screen's own `spinnerHtml`/`winHtml`/`lossHtml`
|
|
440
|
+
* carries its own close affordance, matched by the same heuristic every custom-HTML surface
|
|
441
|
+
* honors (`wireCloseAffordances`). Currently vestigial: not exposed in Content Studio's AI
|
|
442
|
+
* schema, and not read by any widget renderer's `html()`.
|
|
443
|
+
*/
|
|
444
|
+
hideCloseButton?: boolean;
|
|
445
|
+
/** Hides the dimmed backdrop behind the modal/popup/banner. Defaults to shown. */
|
|
446
|
+
hideOverlay?: boolean;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Server-resolved outcome for a spin-wheel submission
|
|
450
|
+
* (`XeniaClient.submitWidgetForm`'s response). Absent for a plain form submit.
|
|
451
|
+
* `segmentId` matches an `id` in the action's `segments` list. Never the whole prize
|
|
452
|
+
* config (weight/internal code) — only what the backend decides is safe to reveal.
|
|
453
|
+
*/
|
|
454
|
+
interface WidgetPrize {
|
|
455
|
+
segmentId?: string;
|
|
456
|
+
label?: string;
|
|
457
|
+
colorHex?: string;
|
|
458
|
+
[key: string]: JsonValue | undefined;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Response from a widget form submission (`XeniaClient.submitWidgetForm`) — whichever
|
|
462
|
+
* endpoint was actually hit (the `/flow` fast path or `/widget-submissions`), normalized
|
|
463
|
+
* to one shape. `actions` flows through the existing `parseActions()`/`ActionRenderer.apply()`
|
|
464
|
+
* pipeline unchanged; `prize` is read directly by the spin-wheel widget module to drive
|
|
465
|
+
* its reveal.
|
|
466
|
+
*/
|
|
467
|
+
interface WidgetSubmitResponse {
|
|
468
|
+
status: number;
|
|
469
|
+
body: JsonValue;
|
|
470
|
+
actions: XeniaAction[];
|
|
471
|
+
prize?: WidgetPrize;
|
|
472
|
+
/** True when this visitor already played and the stored result is being replayed, not re-rolled. */
|
|
473
|
+
alreadyPlayed?: boolean;
|
|
474
|
+
/** Whether the configured dispatch (journey invoke / outbound endpoint call) actually fired. */
|
|
475
|
+
dispatched?: boolean;
|
|
476
|
+
/** Present when `dispatched` is false and the server attempted but failed the configured dispatch. */
|
|
477
|
+
dispatchError?: string;
|
|
478
|
+
widgetType?: WidgetType;
|
|
479
|
+
receivedAt: number;
|
|
480
|
+
}
|
|
378
481
|
/**
|
|
379
482
|
* A render directive the SDK executes in the browser. Marketers emit these in
|
|
380
483
|
* a `webhook-response` node body under the `xeniaActions` key.
|
|
@@ -410,6 +513,43 @@ type XeniaAction = {
|
|
|
410
513
|
/** Anchor within the placement (see {@link MessagePosition}). Defaults to the
|
|
411
514
|
* SDK's built-in default per type (popup: bottom-right; banner: bottom). */
|
|
412
515
|
position?: MessagePosition;
|
|
516
|
+
/** Orthogonal widget-behavior driver — see {@link WidgetType}. Omitted/`"standard"` renders
|
|
517
|
+
* exactly like today. */
|
|
518
|
+
widgetType?: WidgetType;
|
|
519
|
+
/** Label/color-only outcome segments for `spin-wheel` widgets. */
|
|
520
|
+
segments?: WidgetSegment[];
|
|
521
|
+
/** Safe form-submission envelope — see {@link WidgetFormSubmit}. Absent when the action has
|
|
522
|
+
* no capturable outcome. */
|
|
523
|
+
formSubmit?: WidgetFormSubmit;
|
|
524
|
+
/** Per-action visual overrides for the widget's own window — see {@link WidgetStyle}. */
|
|
525
|
+
style?: WidgetStyle;
|
|
526
|
+
/**
|
|
527
|
+
* Full marketer/AI-authored HTML for `spin-wheel`'s pre-spin face (title/subtitle/spin
|
|
528
|
+
* button/background — the whole visual design) — independent of the generic `html` field
|
|
529
|
+
* above, which spin-wheel never reads. The primary, ordinary way this screen
|
|
530
|
+
* is authored (same role `body` plays for a popup) — there is no separate generated
|
|
531
|
+
* template to fall back to. To keep the wheel and spin button working, include an element
|
|
532
|
+
* carrying `data-xenia-wheel-slot` (the SDK mounts the generated SVG wheel + pointer
|
|
533
|
+
* there) and one carrying `data-xenia-spin` (the SDK wires the spin click there) —
|
|
534
|
+
* omitting either simply drops that capability, same as any other custom-HTML widget.
|
|
535
|
+
*/
|
|
536
|
+
spinnerHtml?: string;
|
|
537
|
+
/**
|
|
538
|
+
* Full marketer/AI-authored HTML for the win-reveal screen (`spin-wheel`), shown once a
|
|
539
|
+
* spin resolves to a non-`isLoss` segment — the primary way this screen is authored,
|
|
540
|
+
* same role as {@link spinnerHtml}. Include `data-xenia-wheel-prize` on any element to
|
|
541
|
+
* receive the resolved prize label, and `data-xenia-wheel-cta` on a button to wire its
|
|
542
|
+
* click to dismiss (same close-affordance heuristics as any other custom-HTML widget also
|
|
543
|
+
* apply here — see `wireCloseAffordances`).
|
|
544
|
+
*/
|
|
545
|
+
winHtml?: string;
|
|
546
|
+
/**
|
|
547
|
+
* Full marketer/AI-authored HTML for the loss/"try again" screen, shown once a
|
|
548
|
+
* spin resolves to a segment with {@link WidgetSegment.isLoss} set. Same hooks and
|
|
549
|
+
* same primary-authoring role as {@link winHtml} (a losing outcome still has a `label`,
|
|
550
|
+
* e.g. "Try Again", so `data-xenia-wheel-prize` is still honored here).
|
|
551
|
+
*/
|
|
552
|
+
lossHtml?: string;
|
|
413
553
|
} | {
|
|
414
554
|
type: "redirect";
|
|
415
555
|
url: string;
|
|
@@ -496,6 +636,18 @@ declare class XeniaApi {
|
|
|
496
636
|
* duplicate the journey run.
|
|
497
637
|
*/
|
|
498
638
|
webhook(triggerId: string, data?: JsonValue, method?: "GET" | "POST"): Promise<WebhookResponse>;
|
|
639
|
+
/**
|
|
640
|
+
* Widget form submission — `POST /widget-submissions?ref={bindingRef}`. Used whenever the
|
|
641
|
+
* action's `formSubmit.requiresResolve` is true (spin-wheel, which must have a
|
|
642
|
+
* server-resolved outcome before anything renders) or `mode === "endpoint"` — the common
|
|
643
|
+
* plain-popup case (`mode === "journey" && !requiresResolve`) bypasses this entirely and calls
|
|
644
|
+
* {@link webhook} against `flowRef` instead (see `XeniaClient.submitWidgetForm`'s endpoint
|
|
645
|
+
* selection).
|
|
646
|
+
*
|
|
647
|
+
* Not retried (`noRetry: true`), styled like {@link webhook} — a retried POST must never
|
|
648
|
+
* double-fire a journey or double-resolve a prize server-side.
|
|
649
|
+
*/
|
|
650
|
+
submitWidget(ref: string, body?: JsonValue): Promise<WidgetSubmitResponse>;
|
|
499
651
|
/**
|
|
500
652
|
* Unified event batch (max 100/request per the backend) — `POST /api/events/v1`.
|
|
501
653
|
* Records analytics for every event and matches/advances a journey for any
|
|
@@ -754,6 +906,30 @@ declare class XeniaClient {
|
|
|
754
906
|
}): Promise<WebhookResponse>;
|
|
755
907
|
/** Read the last saved webhook response for a key (triggerId/eventId). */
|
|
756
908
|
getResponse(key: string): WebhookResponse | undefined;
|
|
909
|
+
/**
|
|
910
|
+
* Submit a widget's captured form values (standard/custom popup submit, or a spin-wheel/
|
|
911
|
+
* spin-wheel's resolve request) and apply any returned `xeniaActions`, exactly like
|
|
912
|
+
* {@link request}.
|
|
913
|
+
*
|
|
914
|
+
* **Endpoint selection is config-driven, not SDK-decided** (per the cross-repo contract): a
|
|
915
|
+
* `flowRef` (present only when `mode === "journey" && !requiresResolve` — the common
|
|
916
|
+
* plain-popup case) bypasses the resolve endpoint entirely and POSTs straight to the existing
|
|
917
|
+
* public `/flow` fast path; otherwise this POSTs to `/widget-submissions` via `bindingRef`. The
|
|
918
|
+
* SDK is agnostic to *why* — it just follows whichever ref field the backend populated.
|
|
919
|
+
*
|
|
920
|
+
* The full ambient-context parity list {@link request}/`withVisitor()` already build
|
|
921
|
+
* (visitorId, deviceId, url, referrer, path, query, `utm*`) rides on every call, plus `fields`
|
|
922
|
+
* and `submit.mode`/the resolved ref, relayed verbatim/uninterpreted — the SDK never branches
|
|
923
|
+
* on `mode`'s value itself, that's server-side.
|
|
924
|
+
*
|
|
925
|
+
* `opts.idempotencyKey` defaults to a freshly generated ULID; pass the SAME key back in on a
|
|
926
|
+
* retried submit of the same interaction so a network retry can't double-fire a journey or
|
|
927
|
+
* double-resolve a prize server-side.
|
|
928
|
+
*/
|
|
929
|
+
submitWidgetForm(submit: WidgetFormSubmit, fields: Record<string, string>, opts?: {
|
|
930
|
+
idempotencyKey?: string;
|
|
931
|
+
apply?: boolean;
|
|
932
|
+
}): Promise<WidgetSubmitResponse>;
|
|
757
933
|
swapContent(target: string, content: {
|
|
758
934
|
html?: string;
|
|
759
935
|
text?: string;
|
|
@@ -808,4 +984,4 @@ declare function extractContentSwaps(body: JsonValue): ContentSwap[] | undefined
|
|
|
808
984
|
*/
|
|
809
985
|
declare function parseActions(body: JsonValue): XeniaAction[];
|
|
810
986
|
|
|
811
|
-
export { type AnalyticsContext as A, type ClientEvent as C, type InteractionType as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type
|
|
987
|
+
export { type AnalyticsContext as A, extractContentSwaps as B, type ClientEvent as C, parseActions as D, spliceSlots as E, type InteractionType as I, type JsonValue as J, type MessageCta as M, type PersonalizationSwap as P, type ResolveBatchItem as R, type TrackOptions as T, type WidgetFormSubmit as W, type XeniaConfig as X, type XeniaEvent as a, type MessageTemplateFn as b, type MessageComponentRenderer as c, type WidgetSubmitResponse as d, type XeniaAction as e, type MessagePlacement as f, type AnalyticsEventName as g, type AnalyticsIdentity as h, type AnalyticsIngestResponse as i, type ConsentState as j, type ContentSwap as k, type IdentifyRequest as l, type IdentifyResponse as m, Identity as n, type MessageMountContext as o, type MessageRenderContext as p, type ResolveBatchResult as q, type ResolveOptions as r, type ResolveResult as s, type WebhookResponse as t, type WidgetPrize as u, type WidgetSegment as v, type WidgetType as w, XeniaClient as x, type XeniaClientOptions as y, createXeniaClient as z };
|
|
@@ -15,6 +15,7 @@ var MESSAGE_POSITIONS = /* @__PURE__ */ new Set([
|
|
|
15
15
|
"top",
|
|
16
16
|
"bottom"
|
|
17
17
|
]);
|
|
18
|
+
var WIDGET_TYPES = /* @__PURE__ */ new Set(["standard", "spin-wheel"]);
|
|
18
19
|
var ALL_TYPES = /* @__PURE__ */ new Set([
|
|
19
20
|
"swap",
|
|
20
21
|
"hide",
|
|
@@ -61,15 +62,67 @@ function toAction(raw) {
|
|
|
61
62
|
message: asString(raw.message),
|
|
62
63
|
imageUrl: asString(raw.imageUrl),
|
|
63
64
|
html: asString(raw.html),
|
|
64
|
-
|
|
65
|
+
// A link-only CTA (empty label, e.g. a spin-wheel's data-xenia-wheel-cta —
|
|
66
|
+
// label is meaningless there, the winHtml/lossHtml design supplies its own button text)
|
|
67
|
+
// must still parse through — gating on `cta.label` alone silently dropped it.
|
|
68
|
+
cta: cta && (cta.label || cta.url) ? cta : void 0,
|
|
65
69
|
dismissAfterMs: asNumber(raw.dismissAfterMs),
|
|
66
|
-
position: asMessagePosition(raw.position)
|
|
70
|
+
position: asMessagePosition(raw.position),
|
|
71
|
+
widgetType: asWidgetType(raw.widgetType),
|
|
72
|
+
segments: asSegments(raw.segments),
|
|
73
|
+
formSubmit: asFormSubmit(raw.formSubmit),
|
|
74
|
+
style: asWidgetStyle(raw.style),
|
|
75
|
+
spinnerHtml: asString(raw.spinnerHtml),
|
|
76
|
+
winHtml: asString(raw.winHtml),
|
|
77
|
+
lossHtml: asString(raw.lossHtml)
|
|
67
78
|
};
|
|
68
79
|
}
|
|
69
80
|
function asMessagePosition(v) {
|
|
70
81
|
const s = asString(v);
|
|
71
82
|
return s && MESSAGE_POSITIONS.has(s) ? s : void 0;
|
|
72
83
|
}
|
|
84
|
+
function asWidgetType(v) {
|
|
85
|
+
const s = asString(v);
|
|
86
|
+
return s && WIDGET_TYPES.has(s) ? s : void 0;
|
|
87
|
+
}
|
|
88
|
+
function asSegments(v) {
|
|
89
|
+
if (!Array.isArray(v)) return void 0;
|
|
90
|
+
const segments = [];
|
|
91
|
+
for (const entry of v) {
|
|
92
|
+
if (!isObject(entry)) continue;
|
|
93
|
+
const id = asString(entry.id);
|
|
94
|
+
const label = asString(entry.label);
|
|
95
|
+
if (!id || !label) continue;
|
|
96
|
+
segments.push({
|
|
97
|
+
id,
|
|
98
|
+
label,
|
|
99
|
+
colorHex: asString(entry.colorHex),
|
|
100
|
+
isLoss: entry.isLoss === true
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return segments.length ? segments : void 0;
|
|
104
|
+
}
|
|
105
|
+
function asFormSubmit(v) {
|
|
106
|
+
if (!isObject(v)) return void 0;
|
|
107
|
+
const mode = asString(v.mode);
|
|
108
|
+
if (!mode) return void 0;
|
|
109
|
+
return {
|
|
110
|
+
requiresResolve: v.requiresResolve === true,
|
|
111
|
+
mode,
|
|
112
|
+
flowRef: asString(v.flowRef),
|
|
113
|
+
bindingRef: asString(v.bindingRef),
|
|
114
|
+
successDisplayMs: asNumber(v.successDisplayMs)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function asWidgetStyle(v) {
|
|
118
|
+
if (!isObject(v)) return void 0;
|
|
119
|
+
const style = {
|
|
120
|
+
hideCloseButton: v.hideCloseButton === true,
|
|
121
|
+
hideOverlay: v.hideOverlay === true
|
|
122
|
+
};
|
|
123
|
+
const hasAny = style.hideCloseButton || style.hideOverlay;
|
|
124
|
+
return hasAny ? style : void 0;
|
|
125
|
+
}
|
|
73
126
|
function parseActions(body) {
|
|
74
127
|
let list;
|
|
75
128
|
if (Array.isArray(body)) {
|
|
@@ -252,6 +305,12 @@ var TokenProvider = class {
|
|
|
252
305
|
};
|
|
253
306
|
|
|
254
307
|
// src/core/api.ts
|
|
308
|
+
function isJsonObject(v) {
|
|
309
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
310
|
+
}
|
|
311
|
+
function asWidgetPrize(v) {
|
|
312
|
+
return v !== void 0 && isJsonObject(v) ? v : void 0;
|
|
313
|
+
}
|
|
255
314
|
function compact(obj) {
|
|
256
315
|
const out = {};
|
|
257
316
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -321,6 +380,39 @@ var XeniaApi = class {
|
|
|
321
380
|
receivedAt: Date.now()
|
|
322
381
|
};
|
|
323
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Widget form submission — `POST /widget-submissions?ref={bindingRef}`. Used whenever the
|
|
385
|
+
* action's `formSubmit.requiresResolve` is true (spin-wheel, which must have a
|
|
386
|
+
* server-resolved outcome before anything renders) or `mode === "endpoint"` — the common
|
|
387
|
+
* plain-popup case (`mode === "journey" && !requiresResolve`) bypasses this entirely and calls
|
|
388
|
+
* {@link webhook} against `flowRef` instead (see `XeniaClient.submitWidgetForm`'s endpoint
|
|
389
|
+
* selection).
|
|
390
|
+
*
|
|
391
|
+
* Not retried (`noRetry: true`), styled like {@link webhook} — a retried POST must never
|
|
392
|
+
* double-fire a journey or double-resolve a prize server-side.
|
|
393
|
+
*/
|
|
394
|
+
async submitWidget(ref, body) {
|
|
395
|
+
const { response, data: rawBody } = await this.http.request({
|
|
396
|
+
method: "POST",
|
|
397
|
+
path: "/widget-submissions",
|
|
398
|
+
query: { ref },
|
|
399
|
+
headers: await this.authHeaders(),
|
|
400
|
+
body: body ?? {},
|
|
401
|
+
noRetry: true
|
|
402
|
+
});
|
|
403
|
+
const obj = isJsonObject(rawBody) ? rawBody : {};
|
|
404
|
+
return {
|
|
405
|
+
status: response.status,
|
|
406
|
+
body: rawBody,
|
|
407
|
+
actions: parseActions(rawBody),
|
|
408
|
+
prize: asWidgetPrize(obj.prize),
|
|
409
|
+
alreadyPlayed: obj.alreadyPlayed === true,
|
|
410
|
+
dispatched: obj.dispatched === true,
|
|
411
|
+
dispatchError: typeof obj.dispatchError === "string" ? obj.dispatchError : void 0,
|
|
412
|
+
widgetType: typeof obj.widgetType === "string" ? obj.widgetType : void 0,
|
|
413
|
+
receivedAt: Date.now()
|
|
414
|
+
};
|
|
415
|
+
}
|
|
324
416
|
/**
|
|
325
417
|
* Unified event batch (max 100/request per the backend) — `POST /api/events/v1`.
|
|
326
418
|
* Records analytics for every event and matches/advances a journey for any
|
|
@@ -620,5 +712,5 @@ exports.parseActions = parseActions;
|
|
|
620
712
|
exports.spliceSlots = spliceSlots;
|
|
621
713
|
exports.toEventId = toEventId;
|
|
622
714
|
exports.visitorCookieName = visitorCookieName;
|
|
623
|
-
//# sourceMappingURL=chunk-
|
|
624
|
-
//# sourceMappingURL=chunk-
|
|
715
|
+
//# sourceMappingURL=chunk-WYM4CQVG.cjs.map
|
|
716
|
+
//# sourceMappingURL=chunk-WYM4CQVG.cjs.map
|