@moonbase.sh/storefront 3.9.0 → 3.11.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 CHANGED
@@ -496,10 +496,82 @@ Available events include:
496
496
  - `downloaded-product`
497
497
  - `activated-product`
498
498
  - `added-to-cart`
499
+ - `coupon-rejected`
499
500
  - `checkout-initiated`
501
+ - `checkout-billing-details-added`
502
+ - `checkout-coupon-applied`
503
+ - `checkout-coupon-removed`
504
+ - `checkout-payment-started`
505
+ - `checkout-payment-failed`
500
506
  - `checkout-closed`
501
507
  - `checkout-completed`
502
508
 
509
+ The checkout's own progress, from billing details to payment, is reported only by the checkout
510
+ that opens on your page:
511
+
512
+ - `checkout-billing-details-added` fires each time the buyer saves their billing details.
513
+ - `checkout-payment-started` fires for each attempt to pay, and is followed by
514
+ `checkout-payment-failed` or `checkout-completed`.
515
+ - `checkout-payment-failed` carries `error`, with `reason` and `paymentMethod` (such as `card`).
516
+ The reason is one of `Declined` (by the card issuer or fraud screening: try another card),
517
+ `AuthenticationFailed` (3-D Secure not passed), `CardDetails` (a wrong number, security code or
518
+ postcode, or an expired card), `ProcessingError` (trying again may work) or `Other`. The card
519
+ issuer's own decline code is never passed on. The checkout stays open for the buyer to try again.
520
+ - `checkout-coupon-applied` and `checkout-coupon-removed` carry the `code`. A code the checkout
521
+ refuses fires `coupon-rejected`, with the `message` the buyer saw.
522
+ - `checkout-closed` carries `completed`, and `checkoutOrder`, the order the checkout was open for.
523
+ Its `order` is the cart, which for a direct purchase is a different order.
524
+
525
+ During a full-page redirect to the hosted checkout (`checkout.redirect`, and on phones by default)
526
+ your page is not loaded, so only `checkout-completed` reaches it, once the buyer is back. To follow
527
+ every checkout, hosted included, subscribe a webhook to `OrderCheckoutInitiated` (billing details
528
+ saved) and `OrderPaymentFailed`.
529
+
530
+ ```ts
531
+ Moonbase.on(MoonbaseEvent.CheckoutPaymentFailed, ({ order, error }) => {
532
+ console.log(`Payment for ${order.id} failed: ${error.reason}`)
533
+ })
534
+ ```
535
+
536
+ `signed-in` and `signed-up` fire on the page where the customer signs in, including through a link in an email. `signed-out` also fires when the session ends without anyone signing out on the page: in another tab, or elsewhere (for example a password change on another device), which the widget finds out when it next renews the token.
537
+
538
+ ## Calling your own API
539
+
540
+ `Moonbase.getAccessToken()` resolves to the signed-in customer's access token, or `null` when nobody is signed in. Send it to your own API to act on the customer's behalf. It's renewed first when it's about to run out, so call it for each request rather than keeping a copy.
541
+
542
+ ```ts
543
+ const token = await Moonbase.getAccessToken()
544
+ const response = await fetch('https://api.example.com/presets', {
545
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
546
+ })
547
+ ```
548
+
549
+ The token is a JWT signed with your account's key. Verify it on your server against the JWK Set at `https://<account-id>.moonbase.sh/api/key/jwks`, and check all of:
550
+
551
+ - the `typ` header is `at+jwt` and the algorithm is `RS256`
552
+ - the issuer (`iss`) is your Account ID
553
+ - the audience (`aud`) is `https://moonbase.sh`
554
+ - the expiry (`exp`) is present and hasn't passed
555
+
556
+ License tokens are signed with the same key and issuer, so a check that skips the audience or the expiry accepts a license file as a sign-in. The customer's ID is the `sub` claim. With [jose](https://github.com/panva/jose):
557
+
558
+ ```ts
559
+ import { createRemoteJWKSet, jwtVerify } from 'jose'
560
+
561
+ const jwks = createRemoteJWKSet(new URL('https://acme.moonbase.sh/api/key/jwks'))
562
+
563
+ const { payload } = await jwtVerify(token, jwks, {
564
+ typ: 'at+jwt',
565
+ algorithms: ['RS256'],
566
+ issuer: 'acme',
567
+ audience: 'https://moonbase.sh',
568
+ requiredClaims: ['exp'],
569
+ })
570
+ // payload.sub is the customer's ID
571
+ ```
572
+
573
+ Don't read the widget's `localStorage` entry yourself: it holds the refresh token as well, which must never leave the browser.
574
+
503
575
  ## Integrations
504
576
 
505
577
  The widget can forward its commerce and auth events to common marketing/analytics SDKs without any extra glue code. Enable a provider by passing its config in `integrations`:
@@ -1,5 +1,6 @@
1
1
  import { ActivationRequestFulfillmentType } from '@moonbase.sh/vue';
2
2
  import { CartItem } from '@moonbase.sh/vue';
3
+ import { CheckoutPaymentFailure } from '@moonbase.sh/vue';
3
4
  import { CheckoutRedirectMode } from '@moonbase.sh/vue';
4
5
  import { CurrencyDisplay } from '@moonbase.sh/vue';
5
6
  import { Download } from '@moonbase.sh/vue';
@@ -28,10 +29,7 @@ export declare interface GoogleAnalyticsConfig {
28
29
  }
29
30
 
30
31
  export declare interface GoogleTagManagerConfig {
31
- /**
32
- * Informational/reserved. GTM containers read from `window.dataLayer`
33
- * regardless of which container loaded them, so this is not used at runtime.
34
- */
32
+ /** Not used: GTM reads `window.dataLayer` whichever container loaded it. */
35
33
  containerId?: string;
36
34
  }
37
35
 
@@ -88,6 +86,11 @@ export declare enum MoonbaseEvent {
88
86
  AddedToCart = "added-to-cart",
89
87
  CouponRejected = "coupon-rejected",
90
88
  CheckoutInitiated = "checkout-initiated",
89
+ CheckoutBillingDetailsAdded = "checkout-billing-details-added",
90
+ CheckoutCouponApplied = "checkout-coupon-applied",
91
+ CheckoutCouponRemoved = "checkout-coupon-removed",
92
+ CheckoutPaymentStarted = "checkout-payment-started",
93
+ CheckoutPaymentFailed = "checkout-payment-failed",
91
94
  CheckoutClosed = "checkout-closed",
92
95
  CheckoutCompleted = "checkout-completed"
93
96
  }
@@ -166,6 +169,11 @@ export declare interface MoonbaseEventArgs {
166
169
  };
167
170
  [MoonbaseEvent.CouponRejected]: {
168
171
  code: string;
172
+ /**
173
+ * Why, as the buyer was told, when the checkout refused a code typed into
174
+ * it. Absent for a code passed to the `purchase` intent.
175
+ */
176
+ message?: string;
169
177
  user?: User | null;
170
178
  };
171
179
  [MoonbaseEvent.CheckoutInitiated]: {
@@ -173,8 +181,61 @@ export declare interface MoonbaseEventArgs {
173
181
  total: Money;
174
182
  user?: User | null;
175
183
  };
184
+ /**
185
+ * The buyer saved their billing details in the checkout. Sent again each time
186
+ * they change them. Only from the checkout that opens on the page, not the
187
+ * hosted one a redirect goes to.
188
+ */
189
+ [MoonbaseEvent.CheckoutBillingDetailsAdded]: {
190
+ order: OpenOrder;
191
+ user?: User | null;
192
+ };
193
+ /** The buyer applied a coupon code in the checkout. On-page checkout only. */
194
+ [MoonbaseEvent.CheckoutCouponApplied]: {
195
+ order: OpenOrder;
196
+ code: string;
197
+ user?: User | null;
198
+ };
199
+ /** The buyer took a coupon off the order in the checkout. On-page checkout only. */
200
+ [MoonbaseEvent.CheckoutCouponRemoved]: {
201
+ order: OpenOrder;
202
+ code: string;
203
+ user?: User | null;
204
+ };
205
+ /**
206
+ * The buyer submitted payment in the checkout. Each attempt is followed by
207
+ * `checkout-payment-failed` or `checkout-completed`. On-page checkout only.
208
+ */
209
+ [MoonbaseEvent.CheckoutPaymentStarted]: {
210
+ order: OpenOrder;
211
+ user?: User | null;
212
+ };
213
+ /**
214
+ * An attempt to pay failed: a declined card, or a 3-D Secure check the buyer
215
+ * did not pass. The checkout stays open for them to try again. `error.reason`
216
+ * says what kind of failure it was; the card issuer's own decline code is
217
+ * never passed on. On-page checkout only: for every checkout, hosted
218
+ * included, subscribe a webhook to `OrderPaymentFailed`.
219
+ */
220
+ [MoonbaseEvent.CheckoutPaymentFailed]: {
221
+ order: OpenOrder;
222
+ error: CheckoutPaymentFailure;
223
+ user?: User | null;
224
+ };
176
225
  [MoonbaseEvent.CheckoutClosed]: {
226
+ /**
227
+ * The cart. For a direct purchase (the `purchase` intent, a buy button)
228
+ * this is not the order the checkout was for: read `checkoutOrder`.
229
+ */
177
230
  order: Order;
231
+ /** The order the checkout was open for. Absent if it closed before it loaded. */
232
+ checkoutOrder?: Order;
233
+ /**
234
+ * Whether the checkout completed the order before it closed, the same
235
+ * moment `checkout-completed` fires. An order that was paid but could not
236
+ * be fulfilled is not completed, and fires neither.
237
+ */
238
+ completed: boolean;
178
239
  user?: User | null;
179
240
  };
180
241
  [MoonbaseEvent.CheckoutCompleted]: {
@@ -202,6 +263,7 @@ declare class MoonbaseImpl implements MoonbaseInstance {
202
263
  setup(url: string, options?: DeepPartial<MoonbaseOptions>): Promise<void>;
203
264
  configure(options: DeepPartial<MoonbaseOptions>): void;
204
265
  on<TEvent extends MoonbaseEvent>(eventType: TEvent, callback: (event: MoonbaseEventArgs[TEvent]) => void): void;
266
+ getAccessToken(): Promise<string | null>;
205
267
  private handleLocationIntent;
206
268
  sign_in(parameters?: MoonbaseIntentArgs['sign_in']): void;
207
269
  sign_up(parameters?: MoonbaseIntentArgs['sign_up']): void;
@@ -221,6 +283,7 @@ declare class MoonbaseImpl implements MoonbaseInstance {
221
283
  redeem_voucher(parameters?: MoonbaseIntentArgs['redeem_voucher']): void;
222
284
  view_product(parameters?: MoonbaseIntentArgs['view_product']): void;
223
285
  download_product(parameters?: MoonbaseIntentArgs['download_product']): void;
286
+ view_release_history(parameters?: MoonbaseIntentArgs['view_release_history']): void;
224
287
  activate_product(parameters?: MoonbaseIntentArgs['activate_product']): void;
225
288
  manage_subscription(parameters?: MoonbaseIntentArgs['manage_subscription']): void;
226
289
  view_cart(): void;
@@ -236,6 +299,13 @@ export declare type MoonbaseInstance = MoonbaseInstanceIntents & {
236
299
  setup: (url: string, options: DeepPartial<MoonbaseOptions>) => Promise<void>;
237
300
  configure: (options: DeepPartial<MoonbaseOptions>) => void;
238
301
  on: <TEvent extends MoonbaseEvent>(eventType: TEvent, callback: (event: MoonbaseEventArgs[TEvent]) => void) => void;
302
+ /**
303
+ * The signed-in customer's access token, for calling your own API on their behalf, or
304
+ * null when nobody is signed in. Renewed first when it is about to run out, so it is
305
+ * always good for at least the next half minute. Verify it on your server with your
306
+ * account's public key, at `/api/key/jwks` on your Moonbase address.
307
+ */
308
+ getAccessToken: () => Promise<string | null>;
239
309
  };
240
310
 
241
311
  declare type MoonbaseInstanceIntents = {
@@ -261,6 +331,7 @@ export declare enum MoonbaseIntent {
261
331
  JoinGroup = "join_group",
262
332
  ViewProduct = "view_product",
263
333
  DownloadProduct = "download_product",
334
+ ViewReleaseHistory = "view_release_history",
264
335
  ActivateProduct = "activate_product",
265
336
  ManageSubscription = "manage_subscription",
266
337
  ViewCart = "view_cart",
@@ -341,6 +412,9 @@ export declare interface MoonbaseIntentArgs {
341
412
  version?: string;
342
413
  key?: string;
343
414
  };
415
+ [MoonbaseIntent.ViewReleaseHistory]: {
416
+ product_id: string;
417
+ };
344
418
  [MoonbaseIntent.ActivateProduct]: {
345
419
  token?: string;
346
420
  };
@@ -405,23 +479,10 @@ export declare interface MoonbaseOptions {
405
479
  };
406
480
  };
407
481
  /**
408
- * Merchant-owned customer groups a visitor joins when they use one of these
409
- * forms. Named by ID, because the API deliberately tells a storefront nothing
410
- * about a group: an ID that names a group the merchant has not opened to
411
- * public sign-ups is skipped in silence, so a stale one here can never fail a
412
- * registration or a sale. Enrollment is therefore silent too, and there is
413
- * nothing to label.
414
- *
415
- * `signUp` and `subscribe` can be added to per link with `mb_groups`, e.g.
416
- * `?mb_intent=sign_up&mb_groups=beta,vip`. The two are unioned: the options
417
- * say which lists everyone signing up through this site joins, the link adds
418
- * whichever the campaign is for. A host-page `data-moonbase-form="subscribe"`
419
- * form adds to `subscribe` the same way, with a field named `groups`.
420
- *
421
- * `checkout` is options-only. The cart outlives any one intent, and a buyer
422
- * pressing the drawer's own checkout button never went through a link, so a
423
- * URL-scoped group there would apply or not depending on which button they
424
- * happened to press.
482
+ * IDs of the customer groups a visitor joins through each form. A group not open to public
483
+ * sign-ups is skipped silently. A link adds to `signUp` and `subscribe` with `mb_groups`
484
+ * (`?mb_intent=sign_up&mb_groups=beta,vip`), and a `data-moonbase-form="subscribe"` form with a
485
+ * `groups` field. `checkout` is set here only.
425
486
  */
426
487
  groups: {
427
488
  signUp: string[];
@@ -430,68 +491,36 @@ export declare interface MoonbaseOptions {
430
491
  };
431
492
  checkout: {
432
493
  /**
433
- * Where checkout happens.
434
- *
435
- * - `'auto'` (default) — overlay on desktop, full-page redirect on mobile.
436
- * The redirect is what makes Apple Pay and Google Pay available: those
437
- * wallets require the *top-level* domain to be a registered payment-method
438
- * domain, which it never is when checkout runs in an iframe on the host's
439
- * own site.
440
- * - `'always'` / `true` — always redirect to the hosted checkout.
441
- * - `'never'` / `false` — always use the overlay, wallets included in the cost.
494
+ * Where checkout happens:
495
+ * - `'auto'` (default): an overlay on desktop, the hosted checkout on mobile.
496
+ * - `'always'` / `true`: always the hosted checkout.
497
+ * - `'never'` / `false`: always the overlay, which can't offer Apple Pay or Google Pay.
442
498
  */
443
499
  redirect: boolean | CheckoutRedirectMode;
444
500
  };
445
501
  cart: {
446
502
  quantity: 'selectable' | 'single';
447
503
  bundles: {
504
+ /**
505
+ * `'replace'` (default) takes a bundle's products out of the cart when it is added, if they are
506
+ * sold the same way (subscription or one-off) as the bundle. `'append'` leaves them in.
507
+ */
448
508
  onAdd: 'append' | 'replace';
449
509
  showProducts: boolean;
450
510
  };
451
511
  offers: {
452
512
  label: string;
453
513
  /**
454
- * Apply an eligible offer to the cart line it targets without waiting for the buyer to press
455
- * its upsell card. Applies to offers keyed off a single product; offers keyed off the cart as a
456
- * whole have always applied themselves; see `cartWide` below.
457
- *
458
- * Off by default: an offer is a merchant's to give, and applying one unasked discounts a cart
459
- * the buyer never asked to have discounted. Turn it on to close the one case the upsell cannot
460
- * reach. The card is hidden once the offer's targets are in the cart, so a cart assembled in
461
- * that order (target first, then the item that satisfies the offer's condition) leaves a real
462
- * discount with no way of being claimed.
463
- *
464
- * Never adds anything to the cart, and never takes an offer the cart is still selling: an offer
465
- * that names a product the buyer has not added keeps showing as a card and still takes a press,
466
- * since an offer is good for one line only and spending it early would remove that card. Nor
467
- * does it replace an offer a line already carries.
468
- *
469
- * That last part governs when an offer is taken, not where it stays. Remove one of the products
470
- * that qualified an offer and it stays on the line it landed on, exactly as an offer claimed
471
- * from the card would. A buyer can put a multi-target offer on one product and never add the
472
- * other, so this is a cart they could have built by hand. Losing the discount because of an
473
- * unrelated removal would be the surprising behaviour, not keeping it.
514
+ * Put an eligible single-product offer on the line it targets without the buyer pressing its
515
+ * card, which hides once the target is in the cart. Off by default. Never adds to the cart or
516
+ * replaces an offer a line already has.
474
517
  */
475
518
  autoApply: boolean;
476
- /**
477
- * Offers keyed off the cart as a whole, such as "10% off orders over $50", rather than off a single
478
- * product. They apply themselves the moment the cart qualifies; there is nothing for the buyer
479
- * to press.
480
- */
519
+ /** Offers on the whole cart, such as "10% off orders over $50". They apply once the cart qualifies. */
481
520
  cartWide: {
482
- /**
483
- * Show the eligibility meter in the cart: how close the cart is to the offer's threshold
484
- * while it is short, and the discount confirmed once it is not.
485
- *
486
- * Set to `false` to let the offer apply silently: the saving still shows in the totals, the
487
- * nudge does not. With no such offer configured the meter never renders either way.
488
- */
521
+ /** Show how far the cart is from the offer's threshold. `false` applies it without the meter. */
489
522
  showProgress: boolean;
490
- /**
491
- * Label for the saving row in the cart totals, above `Total`. Kept in the widget's own voice
492
- * rather than taking the offer's name, so the totals column can't be pushed around by
493
- * merchant-length copy.
494
- */
523
+ /** Label for the saving row in the cart totals. */
495
524
  savingLabel: string;
496
525
  };
497
526
  };
@@ -499,19 +528,10 @@ export declare interface MoonbaseOptions {
499
528
  quantity: 'selectable' | 'single';
500
529
  };
501
530
  /**
502
- * The smallest order the cart will check out, keyed by currency code in major units, e.g.
503
- * `{ USD: 25, EUR: 25 }`. Below it the checkout button gives way to a meter showing how much
504
- * further the buyer has to go, and the `checkout` intent opens the cart instead of checking out.
505
- *
506
- * Per currency because the API picks each visitor's currency and the widget never converts one
507
- * into another. A currency with no entry has no minimum, so a buyer priced in one you did not
508
- * list checks out as before.
509
- *
510
- * Measured on the cart's own Total: after product discounts and offers, before coupons and tax.
511
- * A cart of nothing but free items is exempt, since there is no purchase to hold back.
512
- *
513
- * The widget enforces this, not the API: `purchase()` and checkouts built outside the widget are
514
- * not held to it. A value you pass replaces the previous one outright; pass `{}` to remove it.
531
+ * The smallest order the cart checks out, per currency in major units, e.g. `{ USD: 25, EUR: 25 }`.
532
+ * Measured after discounts and offers, before coupons and tax. A currency left out has no
533
+ * minimum, and a cart of only free items is exempt. `purchase()` is not held to it. Pass `{}` to
534
+ * remove it.
515
535
  */
516
536
  minimumTotal: Record<string, number>;
517
537
  };
@@ -525,22 +545,13 @@ export declare interface MoonbaseOptions {
525
545
  };
526
546
  promotions: {
527
547
  /**
528
- * Whether to render the sales a merchant is running. When a promotion is live, the widget
529
- * surfaces it as a banner and/or a popup depending on what the merchant asked for. Turn this
530
- * off to render the campaign yourself from `Moonbase.on('promotion-shown', …)` or the
531
- * `usePromotions()` composable in `@moonbase.sh/vue`.
548
+ * Show running promotions as a banner or popup. Turn off to render them yourself from the
549
+ * `promotion-shown` event or `usePromotions()` in `@moonbase.sh/vue`.
532
550
  */
533
551
  enabled: boolean;
534
- /**
535
- * Where the banner sits when the host page has no `[data-moonbase-promotion]` container for
536
- * it. Ignored when one exists — then the banner renders inline, wherever you put it.
537
- */
552
+ /** Where the banner goes when the page has no `[data-moonbase-promotion]` container. */
538
553
  location: 'top' | 'bottom';
539
- /**
540
- * The label on the dismiss action, and the primary action when a sale has no destination. The
541
- * wording for a sale that *does* lead somewhere comes from the promotion itself, since only
542
- * the merchant knows what the campaign is offering.
543
- */
554
+ /** Label for dismissing a promotion, and its only action when it has no link. */
544
555
  dismissLabel: string;
545
556
  };
546
557
  theme: {
@@ -551,19 +562,11 @@ export declare interface MoonbaseOptions {
551
562
  };
552
563
  fonts: {
553
564
  /**
554
- * Font for headings and display text. Either a built-in family, which the widget loads from
555
- * Moonbase's own asset host, or one of:
556
- *
557
- * - `'inherit'` — render in whatever font the host page uses at that spot. Loads nothing.
558
- * - `'system'` — the widget's own system stack. Loads nothing.
559
- * - `{ family }` — name any family the page already loads.
560
- * - `{ family, stylesheet }` — name any family and the URL to load the face from.
561
- *
562
- * The distinction that catches people out: `'Inter'` means "load Inter for me",
563
- * `{ family: 'Inter' }` means "the family is Inter, I already load it".
565
+ * A built-in family the widget loads (`'Inter'`), `'inherit'` for the page's font, `'system'`,
566
+ * `{ family }` for one the page already loads, or `{ family, stylesheet }` to load one.
564
567
  */
565
568
  heading: MoonbaseFont;
566
- /** As `heading`, for body copy. Either slot accepts any of the built-in families. */
569
+ /** As `heading`, for body text. */
567
570
  body: MoonbaseFont;
568
571
  };
569
572
  corners: 'sharp' | 'soft' | 'round';
@@ -571,44 +574,17 @@ export declare interface MoonbaseOptions {
571
574
  cards: 'outlined' | 'shadow' | 'white';
572
575
  };
573
576
  pricing: {
574
- /**
575
- * How to render a price that lands on a whole number.
576
- * - `'auto'` (default) drops the fraction entirely, so `10` renders `$10`.
577
- * - `'always'` keeps the currency's own fraction digits, so `10` renders
578
- * `$10.00` — and still `¥1000` for zero-decimal currencies like JPY.
579
- *
580
- * Prices that aren't whole are unaffected; they always show their decimals.
581
- */
577
+ /** Whole prices: `'auto'` (default) shows `$10`, `'always'` shows `$10.00`. */
582
578
  trailingZeros: TrailingZeros;
583
- /**
584
- * Which marker identifies the currency. Taking `10` USD read in a British
585
- * locale:
586
- * - `'narrowSymbol'` (default) renders the shortest symbol, `$10`.
587
- * - `'symbol'` renders the locale's usual symbol, `US$10`.
588
- * - `'code'` renders the ISO 4217 code, `USD 10`.
589
- * - `'name'` spells it out in the visitor's language, `10 US dollars`.
590
- *
591
- * The symbol and its placement always follow the visitor's own locale;
592
- * this only picks which of the four forms to use.
593
- */
579
+ /** `'narrowSymbol'` (default) `$10`, `'symbol'` `US$10`, `'code'` `USD 10`, `'name'` `10 US dollars`. */
594
580
  currencyDisplay: CurrencyDisplay;
595
581
  };
596
582
  target: HTMLElement | undefined;
597
- /**
598
- * When the host page declares a fixed-width mobile viewport (e.g. Wix's
599
- * legacy `<meta name="viewport" content="width=320, ...">`), the browser
600
- * upscales every CSS pixel to fit the device. Set this to `true` to opt out
601
- * of the widget's automatic counter-scaling and match the host's density.
602
- */
583
+ /** Turn off the widget's scaling on pages with a fixed-width mobile viewport, such as Wix's `width=320`. */
603
584
  disableViewportCompensation: boolean;
604
585
  /**
605
- * Opt in to built-in forwarding of storefront events to common
606
- * marketing/analytics SDKs. Each provider is enabled by supplying its
607
- * config object. The widget never loads SDK scripts — it only forwards
608
- * to globals the host page has already initialised (e.g. `window.fbq`,
609
- * `window.gtag`, `window.dataLayer`, `window.klaviyo`, `window.ttq`).
610
- * If a provider's global is missing at the time of an event, the
611
- * forwarding is a silent no-op.
586
+ * Forward storefront events to analytics SDKs the page already loads (`fbq`, `gtag`,
587
+ * `dataLayer`, `klaviyo`, `ttq`). Pass a provider's config to turn it on.
612
588
  */
613
589
  integrations: IntegrationsConfig;
614
590
  }