@vio-live/web-sdk 0.24.0 → 0.26.1
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 +132 -13
- package/dist/{chunk-N6U65DWG.js → chunk-6Q2M3MIK.js} +147 -41
- package/dist/chunk-6Q2M3MIK.js.map +1 -0
- package/dist/chunk-NCP76AXW.js +3 -0
- package/dist/{chunk-45YKS5UG.js.map → chunk-NCP76AXW.js.map} +1 -1
- package/dist/{chunk-AXJKB2CW.js → chunk-NXIYIY2D.js} +959 -140
- package/dist/chunk-NXIYIY2D.js.map +1 -0
- package/dist/core/index.d.ts +1695 -1102
- package/dist/core/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{tokens-C3qiGvLV.d.ts → tokens-Bn7fK9ta.d.ts} +42 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -3
- package/package.json +2 -1
- package/dist/chunk-45YKS5UG.js +0 -3
- package/dist/chunk-AXJKB2CW.js.map +0 -1
- package/dist/chunk-N6U65DWG.js.map +0 -1
package/README.md
CHANGED
|
@@ -179,10 +179,12 @@ template that wants slots shared across articles simply sets no scope.
|
|
|
179
179
|
|
|
180
180
|
### Declaration
|
|
181
181
|
|
|
182
|
-
Every mounted element registers its id
|
|
183
|
-
`
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
Every mounted element registers its id, its label and its **kind** (`card` |
|
|
183
|
+
`carousel` | `detail` — each manifest location is `{ id, displayName, kind }`; the
|
|
184
|
+
campaigns micro stores the kind from 2026-10 on and ignored the key before); after
|
|
185
|
+
the first successful `Vio.bootstrap()` the SDK sends **one** manifest for the whole
|
|
186
|
+
page (`POST /v2/mobile/components/manifest`, `mode: 'merge'`, so one page never
|
|
187
|
+
deprecates another page's slots) and again whenever a new id, label or kind appears.
|
|
186
188
|
The browser remembers **each id — under its label and the campaign it was
|
|
187
189
|
declared with — for 24 h** in `localStorage` (under the platform and a hash of
|
|
188
190
|
the key — never the key itself; one state per id), so a multi-page site posts
|
|
@@ -194,10 +196,16 @@ once). Best-effort, never throws. By hand, e.g. from an editor (`force` re-sends
|
|
|
194
196
|
everything and ignores the per-load cap):
|
|
195
197
|
|
|
196
198
|
```ts
|
|
197
|
-
await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar' }], { force: true })
|
|
199
|
+
await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar', kind: 'carousel' }], { force: true })
|
|
198
200
|
// → ManifestResult | null (null = skipped as already declared, or failed — see the console)
|
|
201
|
+
|
|
202
|
+
// What an element does on mount (a host block can too). `(id, name, campaignId)` still works.
|
|
203
|
+
const release = Vio.registerSlot('sidebar', 'Sidebar', { kind: 'carousel', campaignId: 12 })
|
|
199
204
|
```
|
|
200
205
|
|
|
206
|
+
The first kind an id gets stays — the cards a carousel renders share the carousel's
|
|
207
|
+
id, and the slot is the carousel's.
|
|
208
|
+
|
|
201
209
|
### Which campaign a slot shows
|
|
202
210
|
|
|
203
211
|
Among the active campaigns of the bootstrap, the one whose assignment names the
|
|
@@ -266,7 +274,7 @@ and so do the Vev blocks and the Replit site:
|
|
|
266
274
|
|
|
267
275
|
```ts
|
|
268
276
|
Vio.attribution('hero')
|
|
269
|
-
// → { slot: 'hero', campaignId: 12, sponsorId: 7, contentId: 'article-42', contentTitle: 'Why the lamp matters' }
|
|
277
|
+
// → { slot: 'hero', campaignId: 12, sponsorId: 7, contentId: 'article-42', contentTitle: 'Why the lamp matters', contentUrl: 'https://host.example/article-42' } // contentUrl: origin + path
|
|
270
278
|
```
|
|
271
279
|
|
|
272
280
|
`campaignId`/`sponsorId` are the slot's campaign and its primary sponsor (`{}` before
|
|
@@ -296,13 +304,20 @@ sponsor's campaign → the default. Two things changed from
|
|
|
296
304
|
0.22 for carousels: a carousel's impressions and `view_item_list` report
|
|
297
305
|
`location_id` as its slot id (`carousel-1`, or the `vio-slot`) instead of its
|
|
298
306
|
`label`, and every event now carries `content_id` / `content_title` — including
|
|
299
|
-
the single-campaign `begin_checkout` / `purchase`.
|
|
307
|
+
the single-campaign `begin_checkout` / `purchase`. Since 0.25 every element also
|
|
308
|
+
says **what it is**: `component_template_id` is `product-card` for a card,
|
|
309
|
+
`product-carousel` for a carousel — and for the cards it renders, so a carousel's
|
|
310
|
+
impressions and its clicks count under one component type — and `product-detail`
|
|
311
|
+
for the detail (its `view_item`, `add_to_cart` and `begin_checkout`). A host that
|
|
312
|
+
lays cards out itself can set `component-template-id` on them (`product-grid`).
|
|
300
313
|
|
|
301
314
|
### Cart origins and the purchase per campaign
|
|
302
315
|
|
|
303
316
|
The cart remembers **where every unit came from**. The add paths take an optional
|
|
304
|
-
`origin` (`{ campaignId?, slot?, contentId
|
|
305
|
-
answers
|
|
317
|
+
`origin` (`{ campaignId?, slot?, contentId?, componentKind?, contentUrl?, contentTitle? }`
|
|
318
|
+
— what `Vio.attribution(slot)` answers, plus the kind of element; the elements pass
|
|
319
|
+
their `componentTemplateId` as it), and so does
|
|
320
|
+
`adjustQuantity(lineItemId, sponsorId, delta, { origin })`:
|
|
306
321
|
|
|
307
322
|
```ts
|
|
308
323
|
Vio.cart.addProduct({ product, sponsorId: 1, origin: Vio.attribution('hero') })
|
|
@@ -310,10 +325,13 @@ Vio.cart.addProduct({ product, sponsorId: 1, origin: Vio.attribution('hero') })
|
|
|
310
325
|
onProductAdd={(e) => Vio.cart.addProduct({ product, sponsorId, origin: e.detail.origin })}
|
|
311
326
|
```
|
|
312
327
|
|
|
313
|
-
Each line keeps `origins: Array<{ campaignId?, slot?, contentId?,
|
|
314
|
-
add with an origin increments that
|
|
315
|
-
`<vio-cart>` +/− buttons pass none), and
|
|
316
|
-
|
|
328
|
+
Each line keeps `origins: Array<{ campaignId?, slot?, contentId?, componentKind?,
|
|
329
|
+
contentUrl?, contentTitle?, quantity }>`: an add with an origin increments that
|
|
330
|
+
origin, without one the most recent (the `<vio-cart>` +/− buttons pass none), and
|
|
331
|
+
−1/remove comes off the most recent first. A place is campaign + slot + content +
|
|
332
|
+
kind of element (an add from a card and one from the detail of the same slot are two
|
|
333
|
+
origins); the page's URL and title are attributes of it. They persist with the cart;
|
|
334
|
+
carts from before origins read as one empty origin.
|
|
317
335
|
|
|
318
336
|
At checkout and purchase the cart is **split per campaign**: `begin_checkout` and
|
|
319
337
|
`purchase` go out **once per campaign in the cart**, each with that campaign's
|
|
@@ -334,6 +352,107 @@ Vio.cartSlices(undefined, { items }) // pure, over a snapshot of lines
|
|
|
334
352
|
// → CartSlice[]: { campaignId?, sponsorId?, slot?, contentId?, items: VioItem[], value, currency?, slice, slices }
|
|
335
353
|
```
|
|
336
354
|
|
|
355
|
+
### Attribution on the order — what Commerce receives (0.26)
|
|
356
|
+
|
|
357
|
+
Since 0.26 the origins do not stay in the browser: **Commerce receives them**
|
|
358
|
+
(ADR-0035 phase B), so the order is born knowing where it was sold from.
|
|
359
|
+
|
|
360
|
+
- **Per line.** Every `AddItem` carries the line's `origin` — `{ campaign_id,
|
|
361
|
+
location_id (the slot), component_kind, content_url, content_title }` — and a
|
|
362
|
+
quantity **increase** carries, on `UpdateItem`, the origin of the units it adds (the
|
|
363
|
+
given one, else the line's most recent; a burst of +1 from two places is two
|
|
364
|
+
updates, each with its own origin); a decrease and a removal carry none, and an add
|
|
365
|
+
without an origin sends none (the line inherits the order's campaign on the
|
|
366
|
+
server). `component_kind` is the kind of element as the collector names it too
|
|
367
|
+
(`card` | `carousel` | `detail`: the element's `componentTemplateId` without its
|
|
368
|
+
`product-` / `vev-` prefix, else the kind word in the slot id), so
|
|
369
|
+
`order_item.component_kind` and `breakdown?by=component_kind` agree. shopcart keeps
|
|
370
|
+
every origin of a line with its units and writes `order_item.campaign_id` /
|
|
371
|
+
`location_id` / `component_kind` from them.
|
|
372
|
+
- **Per checkout.** `CreateCheckout` and the `UpdateCheckout` right before paying
|
|
373
|
+
carry `attribution`: `surface_key` (the SDK key — the gateway sets it from
|
|
374
|
+
`X-API-Key` anyway, and shopcart checks it against the cart's channel, so a page
|
|
375
|
+
cannot credit a sale to another surface), `campaign_id` (the page's campaign,
|
|
376
|
+
`Vio.pageCampaign()` — else the campaign that explains most units of the cart under
|
|
377
|
+
the same rule `purchase` is split by), `content_url` / `content_title` (the page at
|
|
378
|
+
pay time) and `entry` (`{ referrer, utm_source, utm_medium, utm_campaign,
|
|
379
|
+
landing_url }`: the landing the session **remembers** — the same memory the
|
|
380
|
+
`client` block reads, which now keeps the landing page too — never the page the
|
|
381
|
+
shopper pays on taken for a landing; without a remembered landing there is no
|
|
382
|
+
`entry`). They become `order.surface_id` / `campaign_id` / `content_url` /
|
|
383
|
+
`content_title` / `entry_source`. `Vio.orderAttribution(sponsorId?)` answers what
|
|
384
|
+
would be sent — synchronously: nothing on the way to a payment waits for it.
|
|
385
|
+
- **`content_url` is origin + path**, no query string, no fragment — on the order,
|
|
386
|
+
on the lines and in the landing: the query is where a page carries e-mails, tokens
|
|
387
|
+
and tracking tails, and none of that belongs on an order row. A site whose article
|
|
388
|
+
lives in the query or the hash identifies it through `contentId` (`Vio.init({
|
|
389
|
+
contentId })`, `?vio_content=`, the `vio:content-id` meta), not through the URL.
|
|
390
|
+
The analytics events keep `location.href` as before.
|
|
391
|
+
- **Privacy.** On a **`minimal`** surface what says *which screen* a unit was sold
|
|
392
|
+
from and *why the shopper was there* stays off Commerce — the same promise the
|
|
393
|
+
collector keeps for the events (vio-analytics `privacy.ts`): no `content_url`, no
|
|
394
|
+
`content_title`, no `location_id` on the lines, no `entry`; `surface_key`,
|
|
395
|
+
`campaign_id` and `component_kind` do travel. The mode comes from
|
|
396
|
+
`Vio.init({ privacyMode: 'minimal' })` when the host knows it (always wins), else
|
|
397
|
+
the bootstrap's `surface.privacyMode`, else the surface itself: `GET
|
|
398
|
+
/v2/mobile/surface`, asked **once at `Vio.init()`** (and again by `bootstrap()` if
|
|
399
|
+
that failed), given 1.5 s, believed for the page load — a failure or a timeout is
|
|
400
|
+
believed for a minute, then asked again; a service that predates the endpoint (404)
|
|
401
|
+
means `standard`; a word the SDK does not know does not. The payment path reads
|
|
402
|
+
what is known at that moment (`Vio.privacyModeNow()`) and never waits; an unknown
|
|
403
|
+
mode counts as `minimal`. The cart's lines wait for the ask in flight, at most its
|
|
404
|
+
cap (`Vio.privacyMode()`).
|
|
405
|
+
- **A gateway that predates these arguments** (`origin` on `LineItemInput` /
|
|
406
|
+
`UpdateItem`, `attribution` on `CreateCheckout` / `UpdateCheckout`) refuses the
|
|
407
|
+
operation at validation, before anything runs: the SDK sends it again without them,
|
|
408
|
+
once per page load, says so in the console, and the cart works as in 0.25. Nothing
|
|
409
|
+
that ran — data in the answer, or an error with a `path` — is ever repeated.
|
|
410
|
+
|
|
411
|
+
Everything is capped to Commerce's columns in one place (`lineOriginWire`,
|
|
412
|
+
`checkoutAttributionOf`, exported from `/core`): slot 100, kind 32, URL 2 048, title
|
|
413
|
+
255, referrer 2 048, utm 128; `campaign_id` only as a positive 32-bit `Int`.
|
|
414
|
+
|
|
415
|
+
### The visit and the buyer — `client` and `buyer_id` (0.25)
|
|
416
|
+
|
|
417
|
+
Every event carries a **`client`** block, the standard context of the visit:
|
|
418
|
+
`language` (`navigator.language`), the **landing page's** `referrer` and
|
|
419
|
+
`utm_source` / `utm_medium` / `utm_campaign` / `utm_term` / `utm_content`,
|
|
420
|
+
`screen_w` / `screen_h` and `tz` (IANA). The landing is read once and remembered
|
|
421
|
+
for the session in `sessionStorage` (`vio.landing.v1`), so the third page of a visit
|
|
422
|
+
still says it came from the newsletter — a page's own internal referrer is never
|
|
423
|
+
sent. The referrer is kept as origin + path (no query string, no fragment) and
|
|
424
|
+
dropped when it is this same host (a self-referral says nothing about the entry). A
|
|
425
|
+
URL with utm parameters is a new landing that replaces the memory — unless it was
|
|
426
|
+
reached from this same host, i.e. an internal link that propagates utm, which keeps
|
|
427
|
+
the landing that was. With `requireConsent` nothing is written until
|
|
428
|
+
`setConsent(true)`, and `setConsent(false)` forgets it. Device type, OS, browser and
|
|
429
|
+
country are **not** sent: the collector derives them from the request itself. The
|
|
430
|
+
block is computed once per page load (capped to the collector's limits) and the
|
|
431
|
+
same object rides on every event. `resolveClientContext()` / `resolveLanding()` are
|
|
432
|
+
exported from `/core` for hosts with their own pipeline.
|
|
433
|
+
|
|
434
|
+
A `purchase` carries **`commerce.buyer_id`**: shopcart's `buyer_ref`, the keyed hash
|
|
435
|
+
Commerce computes from the buyer's email — one person, one ref across orders and
|
|
436
|
+
devices, nothing reversible — so a campaign can count distinct and repeat buyers.
|
|
437
|
+
The SDK never sends an email and never hashes one itself: it forwards the reference
|
|
438
|
+
the checkout answers (the order-number query, the Apple Pay and the Klarna native
|
|
439
|
+
confirmations select `buyer_ref`; a gateway that predates the field is asked again
|
|
440
|
+
without it, once per page load) and omits it otherwise. `Vio.checkout.getOrderReference(checkoutId, sponsorId?)`
|
|
441
|
+
answers `{ orderNumber, buyerRef }` (never throws; concurrent asks share one
|
|
442
|
+
request); `getOrderNumber` is its order-number half. The `vio:payment-success`
|
|
443
|
+
detail carries it as `buyerRef`; an emitter that has to ask for it asks at once (and
|
|
444
|
+
again at 1.2 s while the checkout has neither order nor reference — a webhook may
|
|
445
|
+
still be writing it), waits at most 2.5 s, fires the event at once without it when the
|
|
446
|
+
page hides or the element is removed, and never asks when it cannot help (the
|
|
447
|
+
gateway has no `buyer_ref` yet; a confirmation that already answered). The
|
|
448
|
+
confirmation screen never waits. A host with its own checkout passes `buyerRef` (or
|
|
449
|
+
`buyer_ref`) in its `vio:payment-success` / `vio:checkout-open` detail, or
|
|
450
|
+
`commerce.buyerId` to `Vio.analytics.track()`; anything that is not a reference
|
|
451
|
+
(an email, more than 128 chars, anything outside `[A-Za-z0-9._~-]`) is dropped
|
|
452
|
+
(`normalizeBuyerRef` / `buyerRefOf`, exported from `/core`). **Without consent**
|
|
453
|
+
(`requireConsent` until `setConsent(true)`) `buyer_id` is not sent at all: a visit
|
|
454
|
+
measured with ephemeral ids carries no stable identifier of a person.
|
|
455
|
+
|
|
337
456
|
## Theming
|
|
338
457
|
|
|
339
458
|
Every component reads its colors and fonts from `--vio-*` CSS custom properties
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vio, __decorateClass, primaryImageUrl, displayPrice, toCampaignId, getGlobalCurrency, ensureVippsButton, formatPrice, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, normalizeScope, campaignCandidates } from './chunk-
|
|
1
|
+
import { Vio, __decorateClass, primaryImageUrl, displayPrice, toCampaignId, getGlobalCurrency, ensureVippsButton, formatPrice, buyerRefOf, nexiReturnPaymentId, readNexiPending, kustomReturnOutcome, KUSTOM_RETURN_QUERY_KEYS, ADYEN_RETURN_METHOD, adyenReturnParams, ADYEN_RETURN_QUERY_KEYS, adyenReturnOutcome, gatewayHasBuyerRef, AdyenOriginError, normalizeSlotId, autoSlotDisplayName, autoSlotId, normalizeScope, campaignCandidates } from './chunk-NXIYIY2D.js';
|
|
2
2
|
import { LitElement, css, html } from 'lit';
|
|
3
3
|
import { property, state, query } from 'lit/decorators.js';
|
|
4
4
|
|
|
@@ -226,7 +226,7 @@ var SlotRegistration = class {
|
|
|
226
226
|
this.unregister();
|
|
227
227
|
if (!next) return;
|
|
228
228
|
try {
|
|
229
|
-
this.release = typeof Vio.registerSlot !== "function" ? null : next.campaignId !== void 0 ? Vio.registerSlot(next.id, next.displayName, next.campaignId) : Vio.registerSlot(next.id, next.displayName);
|
|
229
|
+
this.release = typeof Vio.registerSlot !== "function" ? null : next.campaignId !== void 0 ? Vio.registerSlot(next.id, next.displayName, next.campaignId, { kind: this.kind }) : Vio.registerSlot(next.id, next.displayName, { kind: this.kind });
|
|
230
230
|
this.identity = next;
|
|
231
231
|
} catch {
|
|
232
232
|
this.release = null;
|
|
@@ -339,6 +339,7 @@ var VioProduct = class extends LitElement {
|
|
|
339
339
|
this.vioCampaign = "";
|
|
340
340
|
this.vioContentId = "";
|
|
341
341
|
this.vioContentTitle = "";
|
|
342
|
+
this.componentTemplateId = "";
|
|
342
343
|
this.slotRegistration = new SlotRegistration(this, "card");
|
|
343
344
|
this.hasVariants = false;
|
|
344
345
|
this.variants = void 0;
|
|
@@ -383,6 +384,7 @@ var VioProduct = class extends LitElement {
|
|
|
383
384
|
}) : { slot: this.effectiveSlot || void 0 };
|
|
384
385
|
return {
|
|
385
386
|
...base,
|
|
387
|
+
componentTemplateId: this.componentTemplateId || "product-card",
|
|
386
388
|
...this.vioContentId ? { contentId: this.vioContentId } : {},
|
|
387
389
|
...this.vioContentTitle ? { contentTitle: this.vioContentTitle } : {}
|
|
388
390
|
};
|
|
@@ -581,11 +583,15 @@ var VioProduct = class extends LitElement {
|
|
|
581
583
|
// The host's sponsor-id wins; without one, the slot's campaign sponsor.
|
|
582
584
|
sponsorId: this.sponsorId || this.sponsorFromAttribution(),
|
|
583
585
|
// The host adds to the cart: pass this through as `origin` so the
|
|
584
|
-
// purchase is attributed to this place
|
|
586
|
+
// purchase is attributed to this place, campaign, kind of element
|
|
587
|
+
// and page — the cart keeps it and Commerce receives it (ADR-0035).
|
|
585
588
|
origin: {
|
|
586
589
|
campaignId: attribution.campaignId,
|
|
587
590
|
slot: attribution.slot,
|
|
588
|
-
contentId: attribution.contentId
|
|
591
|
+
contentId: attribution.contentId,
|
|
592
|
+
componentKind: attribution.componentTemplateId,
|
|
593
|
+
contentUrl: attribution.contentUrl,
|
|
594
|
+
contentTitle: attribution.contentTitle
|
|
589
595
|
}
|
|
590
596
|
}
|
|
591
597
|
})
|
|
@@ -675,6 +681,9 @@ __decorateClass([
|
|
|
675
681
|
__decorateClass([
|
|
676
682
|
property({ type: String, attribute: "vio-content-title" })
|
|
677
683
|
], VioProduct.prototype, "vioContentTitle", 2);
|
|
684
|
+
__decorateClass([
|
|
685
|
+
property({ type: String, attribute: "component-template-id" })
|
|
686
|
+
], VioProduct.prototype, "componentTemplateId", 2);
|
|
678
687
|
__decorateClass([
|
|
679
688
|
property({ type: Boolean, attribute: "has-variants" })
|
|
680
689
|
], VioProduct.prototype, "hasVariants", 2);
|
|
@@ -753,6 +762,7 @@ var VioProductCarousel = class extends LitElement {
|
|
|
753
762
|
}) : { slot: this.effectiveSlot || void 0 };
|
|
754
763
|
return {
|
|
755
764
|
...base,
|
|
765
|
+
componentTemplateId: "product-carousel",
|
|
756
766
|
...this.vioContentId ? { contentId: this.vioContentId } : {},
|
|
757
767
|
...this.vioContentTitle ? { contentTitle: this.vioContentTitle } : {}
|
|
758
768
|
};
|
|
@@ -994,7 +1004,6 @@ var VioProductCarousel = class extends LitElement {
|
|
|
994
1004
|
this,
|
|
995
1005
|
{
|
|
996
1006
|
sponsorId: this.sponsorId > 0 ? this.sponsorId : void 0,
|
|
997
|
-
componentTemplateId: "product-carousel",
|
|
998
1007
|
// The slot is the real location id; the label only stands in for it.
|
|
999
1008
|
locationId: this.effectiveSlot || this.label || void 0,
|
|
1000
1009
|
...this.attribution()
|
|
@@ -1022,7 +1031,6 @@ var VioProductCarousel = class extends LitElement {
|
|
|
1022
1031
|
Vio.analytics.track("view_item_list", {
|
|
1023
1032
|
context: {
|
|
1024
1033
|
sponsorId: sponsorId > 0 ? sponsorId : void 0,
|
|
1025
|
-
componentTemplateId: "product-carousel",
|
|
1026
1034
|
locationId: this.effectiveSlot || this.label || void 0,
|
|
1027
1035
|
...this.attribution()
|
|
1028
1036
|
},
|
|
@@ -1081,6 +1089,7 @@ var VioProductCarousel = class extends LitElement {
|
|
|
1081
1089
|
image-url=${img}
|
|
1082
1090
|
?has-variants=${(p.variants?.length ?? 0) > 1}
|
|
1083
1091
|
.vioSlot=${this.effectiveSlot}
|
|
1092
|
+
.componentTemplateId=${"product-carousel"}
|
|
1084
1093
|
.vioCampaign=${this.effectiveCampaignId !== void 0 ? String(this.effectiveCampaignId) : ""}
|
|
1085
1094
|
.vioContentId=${this.vioContentId}
|
|
1086
1095
|
.vioContentTitle=${this.vioContentTitle}
|
|
@@ -1519,6 +1528,7 @@ var VioProductDetail = class extends LitElement {
|
|
|
1519
1528
|
const contentTitle = this.fromCard && this.eventContentTitle || this.vioContentTitle;
|
|
1520
1529
|
return {
|
|
1521
1530
|
...base,
|
|
1531
|
+
componentTemplateId: "product-detail",
|
|
1522
1532
|
...contentId ? { contentId } : {},
|
|
1523
1533
|
...contentTitle ? { contentTitle } : {}
|
|
1524
1534
|
};
|
|
@@ -2055,7 +2065,6 @@ var VioProductDetail = class extends LitElement {
|
|
|
2055
2065
|
Vio.analytics.track("view_item", {
|
|
2056
2066
|
context: {
|
|
2057
2067
|
sponsorId: this.sponsorId > 0 ? Number(this.sponsorId) : void 0,
|
|
2058
|
-
componentTemplateId: "product-detail",
|
|
2059
2068
|
...this.attribution()
|
|
2060
2069
|
},
|
|
2061
2070
|
commerce: {
|
|
@@ -2378,14 +2387,14 @@ var VioProductDetail = class extends LitElement {
|
|
|
2378
2387
|
const sponsor = Vio.bootstrapCache?.primarySponsor;
|
|
2379
2388
|
const secondaries = Vio.bootstrapCache?.secondarySponsors ?? [];
|
|
2380
2389
|
const sponsorName = sponsor?.id === this.sponsorId ? sponsor.name : secondaries.find((s) => s.id === this.sponsorId)?.name;
|
|
2381
|
-
const { campaignId, slot, contentId } = this.attribution();
|
|
2390
|
+
const { campaignId, slot, contentId, componentTemplateId, contentUrl, contentTitle } = this.attribution();
|
|
2382
2391
|
Vio.cart.addProduct({
|
|
2383
2392
|
product: this.product,
|
|
2384
2393
|
sponsorId: this.sponsorId,
|
|
2385
2394
|
sponsorName,
|
|
2386
2395
|
quantity: this.quantity,
|
|
2387
2396
|
variantId: variant?.id,
|
|
2388
|
-
origin: { campaignId, slot, contentId }
|
|
2397
|
+
origin: { campaignId, slot, contentId, componentKind: componentTemplateId, contentUrl, contentTitle }
|
|
2389
2398
|
});
|
|
2390
2399
|
return variant?.id;
|
|
2391
2400
|
}
|
|
@@ -3353,11 +3362,12 @@ var VioCart = class extends LitElement {
|
|
|
3353
3362
|
for (const sponsorId of [...this.carts.keys()]) {
|
|
3354
3363
|
Vio.cart.clearSponsorCart(sponsorId);
|
|
3355
3364
|
}
|
|
3365
|
+
const buyerRef = buyerRefOf({ result });
|
|
3356
3366
|
this.dispatchEvent(
|
|
3357
3367
|
new CustomEvent("vio:payment-success", {
|
|
3358
3368
|
bubbles: true,
|
|
3359
3369
|
composed: true,
|
|
3360
|
-
detail: { method: "apple-pay", result }
|
|
3370
|
+
detail: { method: "apple-pay", result, ...buyerRef ? { buyerRef } : {} }
|
|
3361
3371
|
})
|
|
3362
3372
|
);
|
|
3363
3373
|
});
|
|
@@ -3927,6 +3937,8 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
3927
3937
|
this.paymentError = detail.method === "klarna" ? `Klarna-betalingen kunne ikke fullf\xF8res: ${detail.error}` : friendlyPaymentError(detail.error, detail.method);
|
|
3928
3938
|
this.open = true;
|
|
3929
3939
|
};
|
|
3940
|
+
/** Payment events still waiting for their buyer reference: settled early (without it) when the page hides or the element leaves. */
|
|
3941
|
+
this.pendingSuccessEmits = /* @__PURE__ */ new Set();
|
|
3930
3942
|
}
|
|
3931
3943
|
/** A payment is being started or confirmed right now: the overlay must not close under it. */
|
|
3932
3944
|
get paymentInFlight() {
|
|
@@ -4617,13 +4629,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4617
4629
|
this.orderConfirmed = true;
|
|
4618
4630
|
this.open = true;
|
|
4619
4631
|
void this.fillOrderNumber(checkoutId, sponsorId);
|
|
4620
|
-
this.
|
|
4621
|
-
new CustomEvent("vio:payment-success", {
|
|
4622
|
-
bubbles: true,
|
|
4623
|
-
composed: true,
|
|
4624
|
-
detail: { method: vioMethod, sponsorId, orderId: checkoutId }
|
|
4625
|
-
})
|
|
4626
|
-
);
|
|
4632
|
+
void this.emitPaymentSuccess({ method: vioMethod, sponsorId, orderId: checkoutId }, checkoutId, sponsorId);
|
|
4627
4633
|
} else if (outcome === "duplicate") {
|
|
4628
4634
|
this.paymentError = CART_PAID_MESSAGE;
|
|
4629
4635
|
this.open = true;
|
|
@@ -4965,6 +4971,7 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
4965
4971
|
this.unmountKlarna();
|
|
4966
4972
|
this.unmountNexi();
|
|
4967
4973
|
this.unmountAdyen();
|
|
4974
|
+
for (const settle of [...this.pendingSuccessEmits]) settle();
|
|
4968
4975
|
super.disconnectedCallback();
|
|
4969
4976
|
}
|
|
4970
4977
|
updated(changed) {
|
|
@@ -5476,18 +5483,122 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5476
5483
|
this.confirmedMethod = method;
|
|
5477
5484
|
this.orderConfirmed = true;
|
|
5478
5485
|
this.open = true;
|
|
5479
|
-
this.
|
|
5480
|
-
|
|
5486
|
+
void this.fillOrderNumber(checkoutId, sponsorId);
|
|
5487
|
+
void this.emitPaymentSuccess(
|
|
5488
|
+
{ method, sponsorId, result, checkoutId },
|
|
5489
|
+
checkoutId,
|
|
5490
|
+
sponsorId,
|
|
5491
|
+
() => Vio.cart.clearSponsorCart(sponsorId)
|
|
5492
|
+
);
|
|
5493
|
+
}
|
|
5494
|
+
static {
|
|
5495
|
+
/** Longest `vio:payment-success` waits for the checkout's buyer reference, and when it asks a second time. */
|
|
5496
|
+
this.BUYER_REF_WAIT_MS = 2500;
|
|
5497
|
+
}
|
|
5498
|
+
static {
|
|
5499
|
+
this.BUYER_REF_RETRY_AT_MS = 1200;
|
|
5500
|
+
}
|
|
5501
|
+
/**
|
|
5502
|
+
* `vio:payment-success` for the host and analytics — with the buyer
|
|
5503
|
+
* reference (`buyerRef`: shopcart's keyed hash of the email → analytics
|
|
5504
|
+
* `buyer_id`) when the checkout can say it. An emitter whose own answer
|
|
5505
|
+
* carried it (Apple Pay, Klarna native) sends at once; the others (a
|
|
5506
|
+
* redirect return, Stripe / Adyen / Nexi in the drawer, a provider's
|
|
5507
|
+
* receipt) ask the checkout — at once and, while it has neither order nor
|
|
5508
|
+
* reference, again at 1.2 s, since a webhook may write the checkout a
|
|
5509
|
+
* moment after the redirect — within 2.5 s, and
|
|
5510
|
+
* send without it when the answer is late or empty. Nothing is asked when
|
|
5511
|
+
* it cannot help (the gateway has no `buyer_ref` yet; a confirmation that
|
|
5512
|
+
* already answered). Only the event waits — the screen is already the
|
|
5513
|
+
* confirmation — and not past the page: when the page hides (`pagehide`,
|
|
5514
|
+
* `visibilitychange`) or the element leaves, the event goes out at once
|
|
5515
|
+
* without the reference. Exactly one event per call. `afterwards` runs
|
|
5516
|
+
* right after the event, so a path keeps the order it always had (the cart
|
|
5517
|
+
* is cleared after the event).
|
|
5518
|
+
*/
|
|
5519
|
+
async emitPaymentSuccess(detail, checkoutId, sponsorId, afterwards) {
|
|
5520
|
+
let done = false;
|
|
5521
|
+
const finish = (buyerRef) => {
|
|
5522
|
+
if (done) return;
|
|
5523
|
+
done = true;
|
|
5524
|
+
const event = new CustomEvent("vio:payment-success", {
|
|
5481
5525
|
bubbles: true,
|
|
5482
5526
|
composed: true,
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5527
|
+
detail: buyerRef ? { ...detail, buyerRef } : detail
|
|
5528
|
+
});
|
|
5529
|
+
this.dispatchEvent(event);
|
|
5530
|
+
if (!this.isConnected && typeof window !== "undefined") {
|
|
5531
|
+
window.dispatchEvent(new CustomEvent("vio:payment-success", { detail: event.detail }));
|
|
5532
|
+
}
|
|
5533
|
+
try {
|
|
5534
|
+
afterwards?.();
|
|
5535
|
+
} catch {
|
|
5536
|
+
}
|
|
5537
|
+
};
|
|
5538
|
+
const known = buyerRefOf(detail);
|
|
5539
|
+
if (known || !checkoutId || !this.shouldAskBuyerRef(detail)) {
|
|
5540
|
+
finish(known);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
const settleEarly = () => finish();
|
|
5544
|
+
const onVisibility = () => {
|
|
5545
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") settleEarly();
|
|
5546
|
+
};
|
|
5547
|
+
this.pendingSuccessEmits.add(settleEarly);
|
|
5548
|
+
window.addEventListener("pagehide", settleEarly);
|
|
5549
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
5550
|
+
try {
|
|
5551
|
+
finish(await this.askBuyerRef(checkoutId, sponsorId, () => done));
|
|
5552
|
+
} catch {
|
|
5553
|
+
finish();
|
|
5554
|
+
} finally {
|
|
5555
|
+
this.pendingSuccessEmits.delete(settleEarly);
|
|
5556
|
+
window.removeEventListener("pagehide", settleEarly);
|
|
5557
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
/** Whether asking the checkout for the buyer reference can still help. */
|
|
5561
|
+
shouldAskBuyerRef(detail) {
|
|
5562
|
+
if (typeof Vio.checkout.getOrderReference !== "function") return false;
|
|
5563
|
+
if (!gatewayHasBuyerRef()) return false;
|
|
5564
|
+
if ((detail.method === "apple-pay" || detail.method === "klarna") && detail.result) return false;
|
|
5565
|
+
return true;
|
|
5566
|
+
}
|
|
5567
|
+
/**
|
|
5568
|
+
* The buyer reference of a checkout: asked at once and, when that answer
|
|
5569
|
+
* failed or has neither the reference nor an order yet (the webhook has not
|
|
5570
|
+
* written the checkout), again at 1.2 s — all within 2.5 s. An order
|
|
5571
|
+
* without a reference is a settled answer: the backend has none, asking
|
|
5572
|
+
* again cannot help. `undefined` when the time is up, when the answer
|
|
5573
|
+
* stopped mattering (`stopped()`) or on failure.
|
|
5574
|
+
*/
|
|
5575
|
+
async askBuyerRef(checkoutId, sponsorId, stopped) {
|
|
5576
|
+
const started = Date.now();
|
|
5577
|
+
const remaining = () => _VioCheckout.BUYER_REF_WAIT_MS - (Date.now() - started);
|
|
5578
|
+
let deadline;
|
|
5579
|
+
const timeUp = new Promise((resolve) => {
|
|
5580
|
+
deadline = setTimeout(() => resolve(null), _VioCheckout.BUYER_REF_WAIT_MS);
|
|
5581
|
+
});
|
|
5582
|
+
try {
|
|
5583
|
+
for (const at of [0, _VioCheckout.BUYER_REF_RETRY_AT_MS]) {
|
|
5584
|
+
const wait = Math.min(at - (Date.now() - started), remaining());
|
|
5585
|
+
if (wait > 0) await Promise.race([new Promise((resolve) => setTimeout(resolve, wait)), timeUp]);
|
|
5586
|
+
if (stopped() || remaining() <= 0) return void 0;
|
|
5587
|
+
try {
|
|
5588
|
+
const reference = await Promise.race([
|
|
5589
|
+
Vio.checkout.getOrderReference(checkoutId, sponsorId || void 0),
|
|
5590
|
+
timeUp
|
|
5591
|
+
]);
|
|
5592
|
+
if (reference === null) return void 0;
|
|
5593
|
+
if (reference.buyerRef) return reference.buyerRef;
|
|
5594
|
+
if (reference.orderNumber !== null) return void 0;
|
|
5595
|
+
} catch {
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
return void 0;
|
|
5599
|
+
} finally {
|
|
5600
|
+
clearTimeout(deadline);
|
|
5601
|
+
}
|
|
5491
5602
|
}
|
|
5492
5603
|
static {
|
|
5493
5604
|
/** Waits between asks for the Vio order number: the order is often born a
|
|
@@ -5638,19 +5749,14 @@ var _VioCheckout = class _VioCheckout extends LitElement {
|
|
|
5638
5749
|
this.confirmedMethod = method;
|
|
5639
5750
|
this.open = true;
|
|
5640
5751
|
const checkoutId = returnCheckoutId || this.checkoutState?.checkoutId;
|
|
5641
|
-
this.
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
);
|
|
5648
|
-
if (sponsorId) {
|
|
5649
|
-
try {
|
|
5650
|
-
Vio.cart.clearSponsorCart(sponsorId);
|
|
5651
|
-
} catch {
|
|
5752
|
+
void this.emitPaymentSuccess(
|
|
5753
|
+
{ method, sponsorId, result, checkoutId },
|
|
5754
|
+
checkoutId,
|
|
5755
|
+
sponsorId,
|
|
5756
|
+
() => {
|
|
5757
|
+
if (sponsorId) Vio.cart.clearSponsorCart(sponsorId);
|
|
5652
5758
|
}
|
|
5653
|
-
|
|
5759
|
+
);
|
|
5654
5760
|
}
|
|
5655
5761
|
/** Back from the payment page, asking whether it was paid — and nothing
|
|
5656
5762
|
* else on screen while we do. */
|
|
@@ -7105,5 +7211,5 @@ function registerVioElements() {
|
|
|
7105
7211
|
}
|
|
7106
7212
|
|
|
7107
7213
|
export { VioCart, VioCheckout, VioProduct, VioProductCarousel, VioProductDetail, VioTokens, applyVioTheme, registerVioElements };
|
|
7108
|
-
//# sourceMappingURL=chunk-
|
|
7109
|
-
//# sourceMappingURL=chunk-
|
|
7214
|
+
//# sourceMappingURL=chunk-6Q2M3MIK.js.map
|
|
7215
|
+
//# sourceMappingURL=chunk-6Q2M3MIK.js.map
|