@seatlayer/js 0.23.0 → 0.24.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 +54 -3
- package/dist/index.cjs +204 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -10
- package/dist/index.d.ts +117 -10
- package/dist/index.js +204 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -101,8 +101,19 @@ designer.setDesignerUrl(nextSession.designerUrl);
|
|
|
101
101
|
designer.destroy();
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
The default `height: 'fill'` is **container-aware**. On mount the SDK probes the
|
|
105
|
+
container: if you gave it a definite height — a fixed-height block, `height` /
|
|
106
|
+
`max-height`, a `flex:1; min-h-0` child, a resolved `%` — the iframe fills 100% of
|
|
107
|
+
that block and tracks its size live via a `ResizeObserver`. If the container is
|
|
108
|
+
content-sized (it collapses to whatever the iframe measures — typical full-page
|
|
109
|
+
usage), the iframe instead grows so its bottom edge meets the bottom of the
|
|
110
|
+
viewport. Either way the result is clamped to `minHeight` (default `480`), and the
|
|
111
|
+
verdict is re-probed on resize so a responsive layout can flip between the two. So
|
|
112
|
+
you can either drop the Designer into a sized block (it fills the block) or give it
|
|
113
|
+
`min-height: 760px` for full-page use (it fills the viewport). Every SDK-managed
|
|
114
|
+
height is written with `!important`, so a host theme's `iframe { height: … }` rule
|
|
115
|
+
can't override it. Keep the default `referrerPolicy: 'origin'`; the Designer uses
|
|
116
|
+
it to verify the parent origin.
|
|
106
117
|
|
|
107
118
|
### Built-in loading, error, and expiry states
|
|
108
119
|
|
|
@@ -117,11 +128,51 @@ CSS files or external assets.
|
|
|
117
128
|
| --- | --- | --- | --- |
|
|
118
129
|
| `showLoadingState` | `boolean` | `true` | Render the built-in skeleton and error card. Set `false` when you draw your own chrome. |
|
|
119
130
|
| `loadingTimeoutMs` | `number` | `20000` | If `ready` never arrives within this window, show the error card with a timeout message. |
|
|
120
|
-
| `onRequestRelaunch` | `() => void` | — | Called by **"Try again"
|
|
131
|
+
| `onRequestRelaunch` | `() => void` | — | Called by **"Try again"** _and_ by automatic renewal (below). Mint a fresh session and call `setDesignerUrl()`; the iframe recreates and returns to loading. When omitted, "Try again" reloads the current URL in place. |
|
|
132
|
+
| `autoRenewSession` | `boolean` | `true`¹ | Silently renew the session before it expires and auto-recover once if an expiry error slips through. ¹Defaults `true` only when `onRequestRelaunch` is provided; a no-op without it. Set `false` for fully manual "Try again". |
|
|
121
133
|
|
|
122
134
|
`setDesignerUrl()` always returns the host to the loading state, so a relaunch
|
|
123
135
|
flow needs no extra bookkeeping.
|
|
124
136
|
|
|
137
|
+
### Session lifecycle
|
|
138
|
+
|
|
139
|
+
Designer sessions are **short-lived by design**: your backend mints a `dse_`
|
|
140
|
+
token (default 1 hour, up to 4 hours via `expiresInSeconds`) and bakes it into
|
|
141
|
+
`designerUrl`. Pick a TTL that fits how long organizers actually edit — longer is
|
|
142
|
+
not automatically better; the renewal below keeps even a multi-hour session alive.
|
|
143
|
+
|
|
144
|
+
Provide `onRequestRelaunch` returning (or awaiting) a freshly minted session, and
|
|
145
|
+
the SDK turns expiry into a non-event:
|
|
146
|
+
|
|
147
|
+
- **Silent proactive renewal.** From each `ready` message's `expiresAt` the SDK
|
|
148
|
+
schedules an automatic relaunch shortly before the session lapses — ~3 minutes
|
|
149
|
+
ahead, or, for a TTL under 15 minutes, after 80% of the remaining life (never
|
|
150
|
+
sooner than 30s after `ready`). Your `onRequestRelaunch` mints a fresh session
|
|
151
|
+
and swaps `designerUrl`, so editing continues with no expiry card. The timer
|
|
152
|
+
re-arms from every `ready`.
|
|
153
|
+
- **Automatic expiry recovery.** If an expiry error still arrives (a laptop that
|
|
154
|
+
slept past the renewal window, say), the SDK makes **one** automatic relaunch
|
|
155
|
+
attempt before showing the "Try again" card, and only falls back to the card if
|
|
156
|
+
that attempt also fails.
|
|
157
|
+
|
|
158
|
+
Relaunching is safe: **in-progress work is autosaved server-side**, so a fresh
|
|
159
|
+
iframe restores the organizer's chart where they left off.
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
const designer = new EmbeddedDesigner({
|
|
163
|
+
container: '#venue-designer',
|
|
164
|
+
designerUrl: session.designerUrl,
|
|
165
|
+
expectedChartId: session.chartId,
|
|
166
|
+
// Mint a fresh session on renewal, expiry recovery, or "Try again":
|
|
167
|
+
onRequestRelaunch: async () => {
|
|
168
|
+
const next = await mintDesignerSession(session.chartId); // your backend, up to 4h TTL
|
|
169
|
+
designer.setDesignerUrl(next.designerUrl); // recreates the iframe
|
|
170
|
+
},
|
|
171
|
+
// autoRenewSession defaults to true because onRequestRelaunch is present.
|
|
172
|
+
});
|
|
173
|
+
designer.mount();
|
|
174
|
+
```
|
|
175
|
+
|
|
125
176
|
## API
|
|
126
177
|
|
|
127
178
|
`new SeatingChart(options)` — options: `container` (selector or element, required),
|
package/dist/index.cjs
CHANGED
|
@@ -205,8 +205,28 @@ var SeatingChart = class {
|
|
|
205
205
|
ribbon.style.cssText = "position:absolute;top:18px;right:-34px;z-index:6;transform:rotate(45deg);width:140px;text-align:center;padding:4px 0;background:#f4b740;color:#1a1200;font:800 10.5px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;letter-spacing:.12em;box-shadow:0 2px 8px rgba(0,0,0,.25);pointer-events:none;";
|
|
206
206
|
host.appendChild(ribbon);
|
|
207
207
|
}
|
|
208
|
+
this.buildBadge(host);
|
|
208
209
|
return this;
|
|
209
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Attribution badge pinned to the embed's bottom-right, linking to
|
|
213
|
+
* seatlayer.io. Rendered as an absolutely-positioned overlay with
|
|
214
|
+
* self-contained inline styles — the SDK embed ships no widget CSS, and an
|
|
215
|
+
* overlay keeps it out of the layout flow so it never disturbs the SDK v0.22
|
|
216
|
+
* fill-height resize contract. Mirrors the full widget's mark + wordmark and
|
|
217
|
+
* reuses the `picker.poweredBy` i18n string.
|
|
218
|
+
*/
|
|
219
|
+
buildBadge(host) {
|
|
220
|
+
if (this.controller.doc?.theme?.hideBadge) return;
|
|
221
|
+
const badge = document.createElement("a");
|
|
222
|
+
badge.href = "https://seatlayer.io";
|
|
223
|
+
badge.target = "_blank";
|
|
224
|
+
badge.rel = "noopener noreferrer";
|
|
225
|
+
badge.setAttribute("aria-label", (0, import_core.t)("picker.poweredBy"));
|
|
226
|
+
badge.style.cssText = 'position:absolute;bottom:10px;right:12px;z-index:5;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:999px;background:rgba(255,255,255,.92);color:#4a5163;text-decoration:none;font:600 11px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:.02em;box-shadow:0 2px 8px rgba(0,0,0,.12);';
|
|
227
|
+
badge.innerHTML = `<span aria-hidden="true" style="width:16px;height:16px;border-radius:4px;flex:none;display:flex;align-items:center;justify-content:center;background:#f4b740;color:#1a1200"><svg viewBox="0 0 24 24" style="width:11px;height:11px;fill:currentColor"><path d="M4 15c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v3h-3v-2H7v2H4v-3Z"/><rect x="7" y="7" width="10" height="5" rx="1.6"/></svg></span><span>${(0, import_core.t)("picker.poweredBy")}</span>`;
|
|
228
|
+
host.appendChild(badge);
|
|
229
|
+
}
|
|
210
230
|
placeTooltip() {
|
|
211
231
|
if (!this.tipEl || !this.hostEl) return;
|
|
212
232
|
const hw = this.hostEl.clientWidth;
|
|
@@ -361,6 +381,13 @@ var TYPES = /* @__PURE__ */ new Set([
|
|
|
361
381
|
]);
|
|
362
382
|
var DEFAULT_LOADING_TIMEOUT_MS = 2e4;
|
|
363
383
|
var DEFAULT_MIN_FILL_HEIGHT = 480;
|
|
384
|
+
var RENEW_LEAD_MS = 3 * 60 * 1e3;
|
|
385
|
+
var RENEW_SHORT_TTL_MS = 15 * 60 * 1e3;
|
|
386
|
+
var RENEW_SHORT_TTL_FRACTION = 0.8;
|
|
387
|
+
var RENEW_MIN_DELAY_MS = 30 * 1e3;
|
|
388
|
+
var FILL_PROBE_HEIGHT_PX = 1e5;
|
|
389
|
+
var FILL_PROBE_TRACK_EPSILON_PX = 4;
|
|
390
|
+
var FILL_MIN_DEFINITE_HEIGHT_PX = 50;
|
|
364
391
|
function resolveContainer2(container) {
|
|
365
392
|
if (typeof container !== "string") return container;
|
|
366
393
|
const element = document.querySelector(container);
|
|
@@ -398,6 +425,14 @@ var EmbeddedDesigner = class {
|
|
|
398
425
|
this.designerOrigin = "";
|
|
399
426
|
this.overlay = null;
|
|
400
427
|
this.timeoutTimer = null;
|
|
428
|
+
/** Proactive session-renewal timer; armed from each `ready`, cleared on re-mount. */
|
|
429
|
+
this.renewTimer = null;
|
|
430
|
+
/**
|
|
431
|
+
* One automatic recovery relaunch is allowed per expiry. Reset ONLY when a fresh
|
|
432
|
+
* `ready` arrives — deliberately not on re-mount — so a session that keeps failing
|
|
433
|
+
* to load can't loop the host through endless silent relaunches.
|
|
434
|
+
*/
|
|
435
|
+
this.autoRecoverUsed = false;
|
|
401
436
|
this.phase = "loading";
|
|
402
437
|
this.restoreContainerPosition = null;
|
|
403
438
|
// Host-side fullscreen pin: saved state we restore on `off`/Escape/destroy.
|
|
@@ -408,10 +443,17 @@ var EmbeddedDesigner = class {
|
|
|
408
443
|
this.fsKeyHandler = null;
|
|
409
444
|
/** Latest height (px string) the Designer reported; re-applied after unpin. */
|
|
410
445
|
this.lastAutoHeight = "";
|
|
411
|
-
//
|
|
446
|
+
// Fill sizing: pending rAF handles + whether window listeners are attached.
|
|
412
447
|
this.fillRaf = null;
|
|
448
|
+
this.reprobeRaf = null;
|
|
413
449
|
this.fillListening = false;
|
|
414
|
-
/**
|
|
450
|
+
/** Resolved container element (fill measurement + ResizeObserver target). */
|
|
451
|
+
this.containerEl = null;
|
|
452
|
+
/** Cached fill verdict: 'container' = bounded block, 'viewport' = full page. */
|
|
453
|
+
this.fillMode = null;
|
|
454
|
+
/** Live block-size tracking in container-fill mode; disconnected on destroy. */
|
|
455
|
+
this.resizeObs = null;
|
|
456
|
+
/** rAF-throttled fill recompute, so a burst of scroll/RO ticks coalesces. */
|
|
415
457
|
this.scheduleFill = () => {
|
|
416
458
|
if (this.fillRaf !== null) return;
|
|
417
459
|
this.fillRaf = requestAnimationFrame(() => {
|
|
@@ -419,6 +461,21 @@ var EmbeddedDesigner = class {
|
|
|
419
461
|
this.applyFill();
|
|
420
462
|
});
|
|
421
463
|
};
|
|
464
|
+
/**
|
|
465
|
+
* rAF-throttled re-probe: a host layout change (responsive breakpoint, a block
|
|
466
|
+
* gaining/losing a definite height) can flip the verdict, so `resize` /
|
|
467
|
+
* `orientationchange` re-detect and swap the container observer accordingly.
|
|
468
|
+
*/
|
|
469
|
+
this.scheduleReprobe = () => {
|
|
470
|
+
if (this.reprobeRaf !== null) return;
|
|
471
|
+
this.reprobeRaf = requestAnimationFrame(() => {
|
|
472
|
+
this.reprobeRaf = null;
|
|
473
|
+
if (this.pinned) return;
|
|
474
|
+
this.fillMode = this.detectFillMode();
|
|
475
|
+
this.syncContainerObserver();
|
|
476
|
+
this.applyFill();
|
|
477
|
+
});
|
|
478
|
+
};
|
|
422
479
|
this.handleMessage = (event) => {
|
|
423
480
|
if (!this.frame || event.origin !== this.designerOrigin || event.source !== this.frame.contentWindow) return;
|
|
424
481
|
if (!event.data || typeof event.data !== "object") return;
|
|
@@ -426,7 +483,7 @@ var EmbeddedDesigner = class {
|
|
|
426
483
|
if (data.type === "seatlayer.designer.resize") {
|
|
427
484
|
if (!this.fillEnabled() && this.autoResizeEnabled() && typeof data.px === "number" && Number.isFinite(data.px) && data.px > 0) {
|
|
428
485
|
this.lastAutoHeight = `${Math.round(data.px)}px`;
|
|
429
|
-
if (!this.pinned) this.
|
|
486
|
+
if (!this.pinned) this.setFrameHeight(this.lastAutoHeight);
|
|
430
487
|
}
|
|
431
488
|
return;
|
|
432
489
|
}
|
|
@@ -454,6 +511,8 @@ var EmbeddedDesigner = class {
|
|
|
454
511
|
this.phase = "ready";
|
|
455
512
|
this.clearTimeoutTimer();
|
|
456
513
|
this.removeOverlay();
|
|
514
|
+
this.autoRecoverUsed = false;
|
|
515
|
+
this.scheduleRenewal(message.expiresAt);
|
|
457
516
|
this.options.onReady?.(message);
|
|
458
517
|
break;
|
|
459
518
|
case "seatlayer.designer.saved":
|
|
@@ -465,10 +524,18 @@ var EmbeddedDesigner = class {
|
|
|
465
524
|
case "seatlayer.designer.close":
|
|
466
525
|
this.options.onClose?.(message);
|
|
467
526
|
break;
|
|
468
|
-
case "seatlayer.designer.error":
|
|
469
|
-
|
|
527
|
+
case "seatlayer.designer.error": {
|
|
528
|
+
const cause = causeFromCode(message.code);
|
|
529
|
+
if (cause === "expired" && this.autoRenewEnabled() && !this.autoRecoverUsed) {
|
|
530
|
+
this.autoRecoverUsed = true;
|
|
531
|
+
this.clearRenewTimer();
|
|
532
|
+
this.options.onRequestRelaunch();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
this.showError(cause);
|
|
470
536
|
this.options.onError?.(message);
|
|
471
537
|
break;
|
|
538
|
+
}
|
|
472
539
|
}
|
|
473
540
|
};
|
|
474
541
|
this.options = options;
|
|
@@ -485,12 +552,17 @@ var EmbeddedDesigner = class {
|
|
|
485
552
|
frame.allow = this.options.allow ?? "fullscreen; clipboard-write";
|
|
486
553
|
frame.referrerPolicy = this.options.referrerPolicy ?? "origin";
|
|
487
554
|
frame.src = url.toString();
|
|
488
|
-
frame.style.width
|
|
489
|
-
frame.style.
|
|
555
|
+
frame.style.setProperty("width", "100%", "important");
|
|
556
|
+
frame.style.setProperty(
|
|
557
|
+
"height",
|
|
558
|
+
typeof this.options.height === "number" ? `${this.options.height}px` : "100%",
|
|
559
|
+
"important"
|
|
560
|
+
);
|
|
490
561
|
frame.style.border = "0";
|
|
491
562
|
Object.assign(frame.style, this.options.style);
|
|
492
563
|
if (this.options.className) frame.className = this.options.className;
|
|
493
564
|
const container = resolveContainer2(this.options.container);
|
|
565
|
+
this.containerEl = container;
|
|
494
566
|
window.addEventListener("message", this.handleMessage);
|
|
495
567
|
container.append(frame);
|
|
496
568
|
this.frame = frame;
|
|
@@ -521,10 +593,13 @@ var EmbeddedDesigner = class {
|
|
|
521
593
|
this.stopFill();
|
|
522
594
|
this.unpinFullscreen();
|
|
523
595
|
this.clearTimeoutTimer();
|
|
596
|
+
this.clearRenewTimer();
|
|
524
597
|
this.removeOverlay();
|
|
525
598
|
this.restoreContainerStyle();
|
|
526
599
|
this.frame?.remove();
|
|
527
600
|
this.frame = null;
|
|
601
|
+
this.containerEl = null;
|
|
602
|
+
this.fillMode = null;
|
|
528
603
|
this.designerOrigin = "";
|
|
529
604
|
this.phase = "loading";
|
|
530
605
|
this.lastAutoHeight = "";
|
|
@@ -539,24 +614,77 @@ var EmbeddedDesigner = class {
|
|
|
539
614
|
fillEnabled() {
|
|
540
615
|
return typeof this.options.height !== "number";
|
|
541
616
|
}
|
|
617
|
+
/** Write an SDK-managed height with `!important` so a host theme can't win. */
|
|
618
|
+
setFrameHeight(value) {
|
|
619
|
+
this.frame?.style.setProperty("height", value, "important");
|
|
620
|
+
}
|
|
542
621
|
/**
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
622
|
+
* Decide whether the host gave the container a DEFINITE (bounded) height — a
|
|
623
|
+
* fixed block the embed should fill 100% of — versus a content-sized container
|
|
624
|
+
* that collapses to whatever the iframe measures (full-page usage).
|
|
625
|
+
*
|
|
626
|
+
* We drive the iframe to two extreme heights within a single synchronous task
|
|
627
|
+
* and watch whether the container follows: a bounded box barely moves, a
|
|
628
|
+
* content-sized one grows with the iframe. Because we restore the height before
|
|
629
|
+
* yielding, the browser only lays out — it never paints the extremes, so there
|
|
630
|
+
* is no visible flash. Works for px, resolved `%`, and flex (`flex:1;min-h:0`)
|
|
631
|
+
* heights, and leaves a mere `min-height` floor classified as content-sized so
|
|
632
|
+
* full-page hosts keep the old viewport-fill behavior.
|
|
633
|
+
*/
|
|
634
|
+
detectFillMode() {
|
|
635
|
+
const container = this.containerEl;
|
|
636
|
+
const frame = this.frame;
|
|
637
|
+
if (this.pinned || !container || !frame) return this.fillMode ?? "viewport";
|
|
638
|
+
const measure = () => container.getBoundingClientRect().height;
|
|
639
|
+
const savedValue = frame.style.getPropertyValue("height");
|
|
640
|
+
const savedPriority = frame.style.getPropertyPriority("height");
|
|
641
|
+
frame.style.setProperty("height", "0px", "important");
|
|
642
|
+
const collapsed = measure();
|
|
643
|
+
frame.style.setProperty("height", `${FILL_PROBE_HEIGHT_PX}px`, "important");
|
|
644
|
+
const expanded = measure();
|
|
645
|
+
if (savedValue) frame.style.setProperty("height", savedValue, savedPriority);
|
|
646
|
+
else frame.style.removeProperty("height");
|
|
647
|
+
const tracksIframe = expanded - collapsed > FILL_PROBE_TRACK_EPSILON_PX;
|
|
648
|
+
const bounded = !tracksIframe && collapsed >= FILL_MIN_DEFINITE_HEIGHT_PX;
|
|
649
|
+
return bounded ? "container" : "viewport";
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Size the iframe for the current fill verdict, clamped to `minHeight`. In
|
|
653
|
+
* container mode it fills 100% of the bounded block; in viewport mode its
|
|
654
|
+
* bottom edge meets the bottom of the viewport (`window.innerHeight - top`).
|
|
655
|
+
* No-op while pinned fullscreen (the pin fills the viewport itself).
|
|
546
656
|
*/
|
|
547
657
|
applyFill() {
|
|
548
658
|
if (!this.frame || this.pinned) return;
|
|
549
659
|
const min = this.options.minHeight ?? DEFAULT_MIN_FILL_HEIGHT;
|
|
660
|
+
if (this.fillMode === "container" && this.containerEl) {
|
|
661
|
+
const target2 = Math.max(min, Math.round(this.containerEl.getBoundingClientRect().height));
|
|
662
|
+
this.setFrameHeight(`${target2}px`);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
550
665
|
const top = this.frame.getBoundingClientRect().top;
|
|
551
666
|
const target = Math.max(min, Math.round(window.innerHeight - top));
|
|
552
|
-
this.
|
|
667
|
+
this.setFrameHeight(`${target}px`);
|
|
668
|
+
}
|
|
669
|
+
/** Attach/detach the container ResizeObserver to match the current verdict. */
|
|
670
|
+
syncContainerObserver() {
|
|
671
|
+
const want = this.fillMode === "container" && !!this.containerEl && typeof ResizeObserver !== "undefined";
|
|
672
|
+
if (want && !this.resizeObs) {
|
|
673
|
+
this.resizeObs = new ResizeObserver(() => this.scheduleFill());
|
|
674
|
+
this.resizeObs.observe(this.containerEl);
|
|
675
|
+
} else if (!want && this.resizeObs) {
|
|
676
|
+
this.resizeObs.disconnect();
|
|
677
|
+
this.resizeObs = null;
|
|
678
|
+
}
|
|
553
679
|
}
|
|
554
680
|
startFill() {
|
|
681
|
+
this.fillMode = this.detectFillMode();
|
|
682
|
+
this.syncContainerObserver();
|
|
555
683
|
this.applyFill();
|
|
556
684
|
if (this.fillListening) return;
|
|
557
685
|
this.fillListening = true;
|
|
558
|
-
window.addEventListener("resize", this.
|
|
559
|
-
window.addEventListener("orientationchange", this.
|
|
686
|
+
window.addEventListener("resize", this.scheduleReprobe);
|
|
687
|
+
window.addEventListener("orientationchange", this.scheduleReprobe);
|
|
560
688
|
window.addEventListener("scroll", this.scheduleFill, { passive: true });
|
|
561
689
|
}
|
|
562
690
|
stopFill() {
|
|
@@ -564,10 +692,18 @@ var EmbeddedDesigner = class {
|
|
|
564
692
|
cancelAnimationFrame(this.fillRaf);
|
|
565
693
|
this.fillRaf = null;
|
|
566
694
|
}
|
|
695
|
+
if (this.reprobeRaf !== null) {
|
|
696
|
+
cancelAnimationFrame(this.reprobeRaf);
|
|
697
|
+
this.reprobeRaf = null;
|
|
698
|
+
}
|
|
699
|
+
if (this.resizeObs) {
|
|
700
|
+
this.resizeObs.disconnect();
|
|
701
|
+
this.resizeObs = null;
|
|
702
|
+
}
|
|
567
703
|
if (!this.fillListening) return;
|
|
568
704
|
this.fillListening = false;
|
|
569
|
-
window.removeEventListener("resize", this.
|
|
570
|
-
window.removeEventListener("orientationchange", this.
|
|
705
|
+
window.removeEventListener("resize", this.scheduleReprobe);
|
|
706
|
+
window.removeEventListener("orientationchange", this.scheduleReprobe);
|
|
571
707
|
window.removeEventListener("scroll", this.scheduleFill);
|
|
572
708
|
}
|
|
573
709
|
/**
|
|
@@ -579,16 +715,22 @@ var EmbeddedDesigner = class {
|
|
|
579
715
|
if (this.pinned || !this.frame) return;
|
|
580
716
|
this.pinned = true;
|
|
581
717
|
this.frameStyleBeforeFs = this.frame.getAttribute("style");
|
|
582
|
-
|
|
718
|
+
const pin = {
|
|
583
719
|
position: "fixed",
|
|
584
|
-
|
|
720
|
+
top: "0",
|
|
721
|
+
right: "0",
|
|
722
|
+
bottom: "0",
|
|
723
|
+
left: "0",
|
|
585
724
|
width: "100vw",
|
|
586
725
|
height: "100vh",
|
|
587
726
|
margin: "0",
|
|
588
727
|
border: "0",
|
|
589
|
-
|
|
728
|
+
"z-index": "2147483000",
|
|
590
729
|
background: "#101625"
|
|
591
|
-
}
|
|
730
|
+
};
|
|
731
|
+
for (const [property, value] of Object.entries(pin)) {
|
|
732
|
+
this.frame.style.setProperty(property, value, "important");
|
|
733
|
+
}
|
|
592
734
|
const docEl = document.documentElement;
|
|
593
735
|
this.docOverflowBeforeFs = docEl.style.overflow;
|
|
594
736
|
docEl.style.overflow = "hidden";
|
|
@@ -609,7 +751,7 @@ var EmbeddedDesigner = class {
|
|
|
609
751
|
if (this.frameStyleBeforeFs === null) this.frame.removeAttribute("style");
|
|
610
752
|
else this.frame.setAttribute("style", this.frameStyleBeforeFs);
|
|
611
753
|
if (this.fillEnabled()) this.applyFill();
|
|
612
|
-
else if (this.autoResizeEnabled() && this.lastAutoHeight) this.
|
|
754
|
+
else if (this.autoResizeEnabled() && this.lastAutoHeight) this.setFrameHeight(this.lastAutoHeight);
|
|
613
755
|
}
|
|
614
756
|
this.frameStyleBeforeFs = null;
|
|
615
757
|
if (this.docOverflowBeforeFs !== null) {
|
|
@@ -631,6 +773,48 @@ var EmbeddedDesigner = class {
|
|
|
631
773
|
this.timeoutTimer = null;
|
|
632
774
|
}
|
|
633
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* Auto-renewal (proactive + one expiry recovery) is on when the host wired a
|
|
778
|
+
* relaunch hook and did not opt out. Without the hook there is nothing to call,
|
|
779
|
+
* so it is a no-op.
|
|
780
|
+
*/
|
|
781
|
+
autoRenewEnabled() {
|
|
782
|
+
return !!this.options.onRequestRelaunch && this.options.autoRenewSession !== false;
|
|
783
|
+
}
|
|
784
|
+
clearRenewTimer() {
|
|
785
|
+
if (this.renewTimer !== null) {
|
|
786
|
+
clearTimeout(this.renewTimer);
|
|
787
|
+
this.renewTimer = null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Arm the proactive renewal timer from a `ready` message's `expiresAt` (epoch
|
|
792
|
+
* ms). We relaunch a comfortable lead before expiry so the host can mint a fresh
|
|
793
|
+
* session and swap `designerUrl` without the user ever seeing the expiry card:
|
|
794
|
+
*
|
|
795
|
+
* - normal TTL (≥ 15 min): renew {@link RENEW_LEAD_MS} (~3 min) before expiry;
|
|
796
|
+
* - short TTL (< 15 min): renew after {@link RENEW_SHORT_TTL_FRACTION} (80%) of
|
|
797
|
+
* the remaining life, so the lead can't overshoot the whole session;
|
|
798
|
+
* - either way, never sooner than {@link RENEW_MIN_DELAY_MS} (30s) after `ready`
|
|
799
|
+
* so a burst of `ready` messages can't spin the host.
|
|
800
|
+
*
|
|
801
|
+
* Re-armed on every `ready`; cleared on destroy / setDesignerUrl (via re-mount).
|
|
802
|
+
* A no-op when auto-renewal is off or `expiresAt` is missing/already past — the
|
|
803
|
+
* expiry-error path recovers a session that has already lapsed.
|
|
804
|
+
*/
|
|
805
|
+
scheduleRenewal(expiresAt) {
|
|
806
|
+
this.clearRenewTimer();
|
|
807
|
+
if (!this.autoRenewEnabled()) return;
|
|
808
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return;
|
|
809
|
+
const remaining = expiresAt - Date.now();
|
|
810
|
+
if (remaining <= 0) return;
|
|
811
|
+
const lead = remaining < RENEW_SHORT_TTL_MS ? remaining * RENEW_SHORT_TTL_FRACTION : remaining - RENEW_LEAD_MS;
|
|
812
|
+
const delay = Math.max(RENEW_MIN_DELAY_MS, lead);
|
|
813
|
+
this.renewTimer = setTimeout(() => {
|
|
814
|
+
this.renewTimer = null;
|
|
815
|
+
if (this.autoRenewEnabled()) this.options.onRequestRelaunch();
|
|
816
|
+
}, delay);
|
|
817
|
+
}
|
|
634
818
|
ensureContainerPositioned(container) {
|
|
635
819
|
const position = getComputedStyle(container).position;
|
|
636
820
|
if (position === "static") {
|