@seatlayer/js 0.40.0 → 0.41.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/dist/index.d.cts CHANGED
@@ -603,6 +603,21 @@ interface SeatingChartOptions {
603
603
  */
604
604
  onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
605
605
  onError?: (err: unknown) => void;
606
+ /**
607
+ * What the BUYER sees when the chart cannot load.
608
+ *
609
+ * `'message'` (the default) renders a plain, styleable notice with a Try
610
+ * again button. This used to be silent unconditionally: `render()` returned
611
+ * with an EMPTY mounted div and only `onError` fired, so a host that had not
612
+ * wired `onError` — or had wired it to a logger — showed buyers a blank
613
+ * rectangle where the seat map belongs, on the host's own domain, which
614
+ * reads as a broken website rather than a temporary fault. `SeatPicker` has
615
+ * always failed loud with a retry; this is the embed class catching up.
616
+ *
617
+ * `'none'` restores the silent behaviour for hosts that render their own
618
+ * failure UI from `onError`.
619
+ */
620
+ errorDisplay?: 'message' | 'none';
606
621
  /**
607
622
  * Multi-floor charts only: fires when the buyer taps a deck in the stacked
608
623
  * 3D view, after the picker switches to that floor — lets the host page sync
@@ -624,6 +639,7 @@ declare class SeatingChart {
624
639
  private mount;
625
640
  private hostEl;
626
641
  private rendered;
642
+ private retryBtn;
627
643
  private mode_;
628
644
  private tipEl;
629
645
  private tipPos;
@@ -768,6 +784,13 @@ declare class SeatingChart {
768
784
  * true when a fresh bearer is held; the realtime feed restarts with it.
769
785
  */
770
786
  refreshAccess(): Promise<boolean>;
787
+ /**
788
+ * The visible failure state. Deliberately inline-styled and dependency-free:
789
+ * this renders on a stranger's website, where our stylesheet may not have
790
+ * loaded (the chart fetch just failed) and where inheriting the host's own
791
+ * styles is likelier to produce something unreadable than something on-brand.
792
+ */
793
+ private showLoadFailure;
771
794
  destroy(): void;
772
795
  }
773
796
 
@@ -1274,17 +1297,33 @@ interface SeatPickerOptions {
1274
1297
  * 1. It needs the widget's own transport. A host-supplied `transport` owns its
1275
1298
  * credentials and its backend, so hosted checkout stays off there (with one
1276
1299
  * console warning) rather than reaching past it to api.seatlayer.io.
1277
- * 2. WHERE A HOSTED GATEWAY RETURNS THE BUYER IS THE SERVER'S CHOICE. The
1278
- * checkout session's return URL is built from the deployment's own allowed
1279
- * origins, so a buyer paying by card from an embed on your domain comes back
1280
- * to SeatLayer's buyer page and is confirmed THERE, not in this widget. The
1281
- * widget resumes in place only when it is mounted on a page that actually
1282
- * receives `?order=…&status=success` (a page on an allowed origin, and the
1283
- * in-page gateways, which never navigate away at all). Until the server
1284
- * accepts a caller-supplied return URL, treat a card payment from a
1285
- * third-party embed as "the buyer finishes on our page".
1300
+ * 2. WHERE A HOSTED GATEWAY RETURNS THE BUYER is settled by {@link returnUrl}
1301
+ * and by the organizer. Without one — or from an origin the organizer has
1302
+ * not declared the buyer comes back to SeatLayer's own buyer page and is
1303
+ * confirmed THERE, not in this widget. Declare the embedding site under
1304
+ * Embed domains in the dashboard and pass `returnUrl`, and the buyer
1305
+ * returns to your page instead. In-page gateways never navigate away at
1306
+ * all, so they are unaffected either way.
1286
1307
  */
1287
1308
  checkout?: 'handoff' | 'hosted';
1309
+ /**
1310
+ * Where a redirecting gateway should send the buyer back to, for
1311
+ * `checkout: 'hosted'`.
1312
+ *
1313
+ * The server keeps this URL verbatim — path and query included — and only
1314
+ * stamps `?order=…&status=success|cancelled` onto it, so point it at
1315
+ * whichever of YOUR pages should confirm the purchase (often just
1316
+ * `window.location.href`). Mount a picker on that page and it resumes in
1317
+ * place from those parameters.
1318
+ *
1319
+ * It is validated, not trusted: the organizer declares their embed origins
1320
+ * in the dashboard, and an undeclared origin is ignored rather than
1321
+ * refused — the sale still completes, the buyer just finishes on
1322
+ * SeatLayer's page. Supplying a URL therefore cannot authorize it, which is
1323
+ * what stops a copied snippet from redirecting a paid buyer anywhere it
1324
+ * likes.
1325
+ */
1326
+ returnUrl?: string;
1288
1327
  /**
1289
1328
  * Buyer pressed the CTA and the hold succeeded — hand off to YOUR checkout.
1290
1329
  * `hold` and `seats` are the legacy args (unchanged since 0.6). `handoff` (P4)
package/dist/index.d.ts CHANGED
@@ -603,6 +603,21 @@ interface SeatingChartOptions {
603
603
  */
604
604
  onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
605
605
  onError?: (err: unknown) => void;
606
+ /**
607
+ * What the BUYER sees when the chart cannot load.
608
+ *
609
+ * `'message'` (the default) renders a plain, styleable notice with a Try
610
+ * again button. This used to be silent unconditionally: `render()` returned
611
+ * with an EMPTY mounted div and only `onError` fired, so a host that had not
612
+ * wired `onError` — or had wired it to a logger — showed buyers a blank
613
+ * rectangle where the seat map belongs, on the host's own domain, which
614
+ * reads as a broken website rather than a temporary fault. `SeatPicker` has
615
+ * always failed loud with a retry; this is the embed class catching up.
616
+ *
617
+ * `'none'` restores the silent behaviour for hosts that render their own
618
+ * failure UI from `onError`.
619
+ */
620
+ errorDisplay?: 'message' | 'none';
606
621
  /**
607
622
  * Multi-floor charts only: fires when the buyer taps a deck in the stacked
608
623
  * 3D view, after the picker switches to that floor — lets the host page sync
@@ -624,6 +639,7 @@ declare class SeatingChart {
624
639
  private mount;
625
640
  private hostEl;
626
641
  private rendered;
642
+ private retryBtn;
627
643
  private mode_;
628
644
  private tipEl;
629
645
  private tipPos;
@@ -768,6 +784,13 @@ declare class SeatingChart {
768
784
  * true when a fresh bearer is held; the realtime feed restarts with it.
769
785
  */
770
786
  refreshAccess(): Promise<boolean>;
787
+ /**
788
+ * The visible failure state. Deliberately inline-styled and dependency-free:
789
+ * this renders on a stranger's website, where our stylesheet may not have
790
+ * loaded (the chart fetch just failed) and where inheriting the host's own
791
+ * styles is likelier to produce something unreadable than something on-brand.
792
+ */
793
+ private showLoadFailure;
771
794
  destroy(): void;
772
795
  }
773
796
 
@@ -1274,17 +1297,33 @@ interface SeatPickerOptions {
1274
1297
  * 1. It needs the widget's own transport. A host-supplied `transport` owns its
1275
1298
  * credentials and its backend, so hosted checkout stays off there (with one
1276
1299
  * console warning) rather than reaching past it to api.seatlayer.io.
1277
- * 2. WHERE A HOSTED GATEWAY RETURNS THE BUYER IS THE SERVER'S CHOICE. The
1278
- * checkout session's return URL is built from the deployment's own allowed
1279
- * origins, so a buyer paying by card from an embed on your domain comes back
1280
- * to SeatLayer's buyer page and is confirmed THERE, not in this widget. The
1281
- * widget resumes in place only when it is mounted on a page that actually
1282
- * receives `?order=…&status=success` (a page on an allowed origin, and the
1283
- * in-page gateways, which never navigate away at all). Until the server
1284
- * accepts a caller-supplied return URL, treat a card payment from a
1285
- * third-party embed as "the buyer finishes on our page".
1300
+ * 2. WHERE A HOSTED GATEWAY RETURNS THE BUYER is settled by {@link returnUrl}
1301
+ * and by the organizer. Without one — or from an origin the organizer has
1302
+ * not declared the buyer comes back to SeatLayer's own buyer page and is
1303
+ * confirmed THERE, not in this widget. Declare the embedding site under
1304
+ * Embed domains in the dashboard and pass `returnUrl`, and the buyer
1305
+ * returns to your page instead. In-page gateways never navigate away at
1306
+ * all, so they are unaffected either way.
1286
1307
  */
1287
1308
  checkout?: 'handoff' | 'hosted';
1309
+ /**
1310
+ * Where a redirecting gateway should send the buyer back to, for
1311
+ * `checkout: 'hosted'`.
1312
+ *
1313
+ * The server keeps this URL verbatim — path and query included — and only
1314
+ * stamps `?order=…&status=success|cancelled` onto it, so point it at
1315
+ * whichever of YOUR pages should confirm the purchase (often just
1316
+ * `window.location.href`). Mount a picker on that page and it resumes in
1317
+ * place from those parameters.
1318
+ *
1319
+ * It is validated, not trusted: the organizer declares their embed origins
1320
+ * in the dashboard, and an undeclared origin is ignored rather than
1321
+ * refused — the sale still completes, the buyer just finishes on
1322
+ * SeatLayer's page. Supplying a URL therefore cannot authorize it, which is
1323
+ * what stops a copied snippet from redirecting a paid buyer anywhere it
1324
+ * likes.
1325
+ */
1326
+ returnUrl?: string;
1288
1327
  /**
1289
1328
  * Buyer pressed the CTA and the hold succeeded — hand off to YOUR checkout.
1290
1329
  * `hold` and `seats` are the legacy args (unchanged since 0.6). `handoff` (P4)
package/dist/index.js CHANGED
@@ -906,6 +906,7 @@ var SeatingChart = class {
906
906
  this.mount = null;
907
907
  this.hostEl = null;
908
908
  this.rendered = false;
909
+ this.retryBtn = null;
909
910
  this.mode_ = null;
910
911
  this.tipEl = null;
911
912
  this.tipPos = { x: 0, y: 0 };
@@ -967,6 +968,7 @@ var SeatingChart = class {
967
968
  const info = await this.controller.render(host);
968
969
  if (!info) {
969
970
  this.rendered = false;
971
+ if (this.opts.errorDisplay !== "none") this.showLoadFailure(host);
970
972
  return this;
971
973
  }
972
974
  this.controller.setViewMode(this.opts.initialView ?? "flat");
@@ -1257,10 +1259,36 @@ var SeatingChart = class {
1257
1259
  }
1258
1260
  return ok;
1259
1261
  }
1262
+ /**
1263
+ * The visible failure state. Deliberately inline-styled and dependency-free:
1264
+ * this renders on a stranger's website, where our stylesheet may not have
1265
+ * loaded (the chart fetch just failed) and where inheriting the host's own
1266
+ * styles is likelier to produce something unreadable than something on-brand.
1267
+ */
1268
+ showLoadFailure(host) {
1269
+ const box = document.createElement("div");
1270
+ box.setAttribute("role", "status");
1271
+ box.style.cssText = 'display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;width:100%;height:100%;min-height:180px;box-sizing:border-box;padding:24px;text-align:center;font:500 14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#3b4256;';
1272
+ const text = document.createElement("div");
1273
+ text.textContent = "The seat map didn\u2019t load.";
1274
+ box.appendChild(text);
1275
+ const btn = document.createElement("button");
1276
+ btn.type = "button";
1277
+ btn.textContent = "Try again";
1278
+ btn.style.cssText = "appearance:none;border:1px solid #c9cede;background:#fff;color:#10162a;border-radius:8px;padding:8px 16px;font:600 13px/1 inherit;cursor:pointer;";
1279
+ btn.addEventListener("click", () => {
1280
+ this.destroy();
1281
+ void this.render().catch((err) => this.opts.onError?.(err));
1282
+ });
1283
+ box.appendChild(btn);
1284
+ this.retryBtn = btn;
1285
+ host.appendChild(box);
1286
+ }
1260
1287
  destroy() {
1261
1288
  this.realtime?.stop();
1262
1289
  this.realtime = null;
1263
1290
  this.access?.clear();
1291
+ this.retryBtn = null;
1264
1292
  if (this.hostEl && this.onTipMove) this.hostEl.removeEventListener("mousemove", this.onTipMove);
1265
1293
  this.tipEl = null;
1266
1294
  this.onTipMove = null;
@@ -5447,7 +5475,15 @@ var SeatPicker = class _SeatPicker {
5447
5475
  this.checkoutPanel = mountCheckout({
5448
5476
  root: this.root,
5449
5477
  state,
5450
- startSession: (input) => this.pubApi.startCheckout(this.opts.event, input),
5478
+ // `returnUrl` rides along so a redirecting gateway can come back to the
5479
+ // HOST's page. The server validates its origin against the organizer's
5480
+ // declared embed domains and silently falls back to our own buyer page
5481
+ // when it does not match, so passing one can never redirect a paid buyer
5482
+ // somewhere the organizer did not sanction.
5483
+ startSession: (input) => this.pubApi.startCheckout(this.opts.event, {
5484
+ ...input,
5485
+ ...this.opts.returnUrl ? { returnUrl: this.opts.returnUrl } : {}
5486
+ }),
5451
5487
  orderStatus: (orderId) => this.pubApi.orderStatus(orderId),
5452
5488
  onCancel: () => {
5453
5489
  this.checkoutPanel = null;