@vio-live/web-sdk 0.23.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -21
- package/dist/{chunk-BRJMT7P4.js → chunk-5KWICUME.js} +1321 -333
- package/dist/chunk-5KWICUME.js.map +1 -0
- package/dist/{chunk-UVDOKFGD.js → chunk-A3LN5JEM.js} +235 -58
- package/dist/chunk-A3LN5JEM.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/core/index.d.ts +1284 -596
- 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-DDfZuHF6.d.ts → tokens-Bn7fK9ta.d.ts} +72 -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-BRJMT7P4.js.map +0 -1
- package/dist/chunk-UVDOKFGD.js.map +0 -1
package/README.md
CHANGED
|
@@ -179,36 +179,94 @@ 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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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.
|
|
188
|
+
The browser remembers **each id — under its label and the campaign it was
|
|
189
|
+
declared with — for 24 h** in `localStorage` (under the platform and a hash of
|
|
190
|
+
the key — never the key itself; one state per id), so a multi-page site posts
|
|
191
|
+
only the ids a page adds, a revisit posts nothing, a renamed block or a page that
|
|
192
|
+
changed campaign posts again; without storage it simply posts each load. A failed
|
|
190
193
|
attempt (or a server that ignored `merge`) is not retried for 1 h; a declaration
|
|
191
194
|
sends at most 100 locations and a page load at most 3 manifests (each cap warns
|
|
192
195
|
once). Best-effort, never throws. By hand, e.g. from an editor (`force` re-sends
|
|
193
196
|
everything and ignores the per-load cap):
|
|
194
197
|
|
|
195
198
|
```ts
|
|
196
|
-
await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar' }], { force: true })
|
|
199
|
+
await Vio.declareSlots(['hero', { id: 'sidebar', displayName: 'Sidebar', kind: 'carousel' }], { force: true })
|
|
197
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 })
|
|
198
204
|
```
|
|
199
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
|
+
|
|
200
209
|
### Which campaign a slot shows
|
|
201
210
|
|
|
202
211
|
Among the active campaigns of the bootstrap, the one whose assignment names the
|
|
203
212
|
slot (for this platform, or for all) and whose window is open right now; if
|
|
204
|
-
several, the default campaign, else the first listed. No assignment → the
|
|
213
|
+
several, the default campaign, else the first listed. No assignment → the page's
|
|
214
|
+
campaign (see *Page campaign*) when the bootstrap lists it as active → the default
|
|
205
215
|
campaign. `Vio.campaignForSlot('hero')` answers from the last bootstrap (`null`
|
|
206
|
-
before it); the pure `resolveSlotCampaign(boot, slot, now?, platform?)`
|
|
207
|
-
from `/core`. A carousel or card **without a `sponsor-id`** uses the primary sponsor
|
|
216
|
+
before it); the pure `resolveSlotCampaign(boot, slot, now?, platform?, pageCampaignId?)`
|
|
217
|
+
is exported from `/core`. A carousel or card **without a `sponsor-id`** uses the primary sponsor
|
|
208
218
|
of its slot's campaign (the default campaign's when the slot has none), and the
|
|
209
219
|
detail opened by such a card does the same — before 0.23 a sponsorless card did
|
|
210
220
|
nothing. An explicit `sponsor-id` still wins.
|
|
211
221
|
|
|
222
|
+
### Page campaign — an editorial page that belongs to one campaign
|
|
223
|
+
|
|
224
|
+
Native-SDK style placements stay the primary model: the **dashboard assigns**
|
|
225
|
+
a campaign to a slot. But an editorial page — a Vev article, a Replit article —
|
|
226
|
+
often *is* part of one campaign, and the page can say so:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
Vio.init({ apiKey: '…', campaignId: 12 }) // the whole page
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```html
|
|
233
|
+
<body data-vio-campaign="12"> <!-- inheritable, like the scope -->
|
|
234
|
+
<vio-product-carousel vio-campaign="10" …> <!-- one element of another campaign -->
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Precedence per element: `vio-campaign` on the element → the nearest ancestor's
|
|
238
|
+
`data-vio-campaign` → `Vio.init({ campaignId })`; an unusable level (not a positive
|
|
239
|
+
integer, paused, unknown) falls through to the next. The attributes are read when
|
|
240
|
+
the element mounts or its own attribute changes — there is no observer on the
|
|
241
|
+
ancestors. On web/Replit a **per-article** campaign needs a per-article scope too
|
|
242
|
+
(`data-vio-scope`, see above): without it the auto ids of every article are the
|
|
243
|
+
same `carousel-1`, and the articles' campaigns would overwrite one another's
|
|
244
|
+
assignment; Vev ids are per block, so it does not apply there. What it does:
|
|
245
|
+
|
|
246
|
+
- **Declaration.** The manifest carries the page's campaign (top-level) and an
|
|
247
|
+
element's own campaign per location when it differs. The campaigns service
|
|
248
|
+
(campaigns micro PR #16) turns that into assignments of the page's slots
|
|
249
|
+
(`source: 'page'`) — **unless the dashboard already assigned the slot: the
|
|
250
|
+
dashboard wins**, and the SDK warns once per such slot (and once when this page
|
|
251
|
+
moved a slot from an earlier page assignment). A page that changes campaign
|
|
252
|
+
declares again; a campaign the micro did not take up is retried after an hour.
|
|
253
|
+
- **Resolution, immediately.** `Vio.campaignForSlot()` / `Vio.attribution()` (and
|
|
254
|
+
so the elements, the cart origins and the events) resolve as: the slot's
|
|
255
|
+
assignment (a dashboard one always; a page-sourced one unless the page now
|
|
256
|
+
declares a different active campaign or the known sponsor takes no part in it)
|
|
257
|
+
→ the page's campaign, when the bootstrap lists it as active and — with a known
|
|
258
|
+
sponsor — that sponsor takes part in it → the sponsor's campaign → the default. An editor sees the effect in the Vev
|
|
259
|
+
editor before the manifest round-trips.
|
|
260
|
+
- `Vio.pageCampaign()` returns the init-level campaign as the bootstrap lists it
|
|
261
|
+
(active), else `null`.
|
|
262
|
+
- With a **known sponsor** (a card with `sponsor-id`), the page's campaign — and a
|
|
263
|
+
slot assignment the page made itself (`source: 'page'`) — count only when that
|
|
264
|
+
sponsor takes part in the campaign; otherwise the sponsor's own campaign applies.
|
|
265
|
+
A dashboard assignment counts unconditionally.
|
|
266
|
+
- `Vio.init({ debug: true })` logs the micro's `assignment` per slot at
|
|
267
|
+
`console.debug`. It is independent of `Vio.analytics.start({ debug })`, which
|
|
268
|
+
logs the analytics events.
|
|
269
|
+
|
|
212
270
|
## Attribution — the slot, the campaign, the article
|
|
213
271
|
|
|
214
272
|
`Vio.attribution(slot?)` is **the** attribution helper — the SDK elements use it,
|
|
@@ -216,7 +274,7 @@ and so do the Vev blocks and the Replit site:
|
|
|
216
274
|
|
|
217
275
|
```ts
|
|
218
276
|
Vio.attribution('hero')
|
|
219
|
-
// → { 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
|
|
220
278
|
```
|
|
221
279
|
|
|
222
280
|
`campaignId`/`sponsorId` are the slot's campaign and its primary sponsor (`{}` before
|
|
@@ -241,17 +299,25 @@ titles are cut — in one place, before anything is sent. Per element,
|
|
|
241
299
|
tracks carries `slot` (the effective id), `location_id` (= the slot), `content_id`
|
|
242
300
|
and `content_title`. `campaign_id` resolves as explicit `campaignId` (the element's
|
|
243
301
|
own attribution travels in its event detail and is honored as such) → the slot's
|
|
244
|
-
assigned campaign → the
|
|
302
|
+
assigned campaign → the page's campaign (when the sponsor takes part in it) → the
|
|
303
|
+
sponsor's campaign → the default. Two things changed from
|
|
245
304
|
0.22 for carousels: a carousel's impressions and `view_item_list` report
|
|
246
305
|
`location_id` as its slot id (`carousel-1`, or the `vio-slot`) instead of its
|
|
247
306
|
`label`, and every event now carries `content_id` / `content_title` — including
|
|
248
|
-
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`).
|
|
249
313
|
|
|
250
314
|
### Cart origins and the purchase per campaign
|
|
251
315
|
|
|
252
316
|
The cart remembers **where every unit came from**. The add paths take an optional
|
|
253
|
-
`origin` (`{ campaignId?, slot?, contentId
|
|
254
|
-
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 })`:
|
|
255
321
|
|
|
256
322
|
```ts
|
|
257
323
|
Vio.cart.addProduct({ product, sponsorId: 1, origin: Vio.attribution('hero') })
|
|
@@ -259,10 +325,13 @@ Vio.cart.addProduct({ product, sponsorId: 1, origin: Vio.attribution('hero') })
|
|
|
259
325
|
onProductAdd={(e) => Vio.cart.addProduct({ product, sponsorId, origin: e.detail.origin })}
|
|
260
326
|
```
|
|
261
327
|
|
|
262
|
-
Each line keeps `origins: Array<{ campaignId?, slot?, contentId?,
|
|
263
|
-
add with an origin increments that
|
|
264
|
-
`<vio-cart>` +/− buttons pass none), and
|
|
265
|
-
|
|
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.
|
|
266
335
|
|
|
267
336
|
At checkout and purchase the cart is **split per campaign**: `begin_checkout` and
|
|
268
337
|
`purchase` go out **once per campaign in the cart**, each with that campaign's
|
|
@@ -283,6 +352,107 @@ Vio.cartSlices(undefined, { items }) // pure, over a snapshot of lines
|
|
|
283
352
|
// → CartSlice[]: { campaignId?, sponsorId?, slot?, contentId?, items: VioItem[], value, currency?, slice, slices }
|
|
284
353
|
```
|
|
285
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
|
+
|
|
286
456
|
## Theming
|
|
287
457
|
|
|
288
458
|
Every component reads its colors and fonts from `--vio-*` CSS custom properties
|