@seatlayer/js 0.47.2 → 0.48.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 +16 -1
- package/dist/{channelsMode-33ZLORVF.js → channelsMode-NCH5GYHN.js} +3 -3
- package/dist/{chunk-X2R4AQSZ.js → chunk-H4OJF6LE.js} +176 -7
- package/dist/chunk-H4OJF6LE.js.map +1 -0
- package/dist/{chunk-DMEFXZIL.js → chunk-KNMZQZXR.js} +2 -2
- package/dist/{chunk-CF5MS7UR.js → chunk-Q5QT3YLA.js} +186 -59
- package/dist/chunk-Q5QT3YLA.js.map +1 -0
- package/dist/{hostedCheckout-SHQCWN5Y.js → hostedCheckout-F6C6VCCG.js} +1 -1
- package/dist/hostedCheckout-F6C6VCCG.js.map +1 -0
- package/dist/index.cjs +612 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +258 -62
- package/dist/index.js.map +1 -1
- package/dist/{manager-CAIPkUYl.d.cts → manager-DEjbKiqJ.d.cts} +200 -17
- package/dist/{manager-CAIPkUYl.d.ts → manager-DEjbKiqJ.d.ts} +200 -17
- package/dist/manager.cjs +358 -62
- package/dist/manager.cjs.map +1 -1
- package/dist/manager.d.cts +1 -1
- package/dist/manager.d.ts +1 -1
- package/dist/manager.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-CF5MS7UR.js.map +0 -1
- package/dist/chunk-X2R4AQSZ.js.map +0 -1
- package/dist/hostedCheckout-SHQCWN5Y.js.map +0 -1
- /package/dist/{channelsMode-33ZLORVF.js.map → channelsMode-NCH5GYHN.js.map} +0 -0
- /package/dist/{chunk-DMEFXZIL.js.map → chunk-KNMZQZXR.js.map} +0 -0
package/dist/manager.cjs
CHANGED
|
@@ -42,6 +42,99 @@ async function parse(res) {
|
|
|
42
42
|
}
|
|
43
43
|
return data;
|
|
44
44
|
}
|
|
45
|
+
function record(value) {
|
|
46
|
+
return value && typeof value === "object" ? value : {};
|
|
47
|
+
}
|
|
48
|
+
function finite(primary, legacy, fallback = 0) {
|
|
49
|
+
if (typeof primary === "number" && Number.isFinite(primary)) return primary;
|
|
50
|
+
if (typeof legacy === "number" && Number.isFinite(legacy)) return legacy;
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
function nullableFinite(primary, legacy) {
|
|
54
|
+
if (primary === null) return null;
|
|
55
|
+
if (typeof primary === "number" && Number.isFinite(primary)) return primary;
|
|
56
|
+
if (primary !== void 0) return null;
|
|
57
|
+
if (typeof legacy === "number" && Number.isFinite(legacy)) return legacy;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function normalizeSection(value) {
|
|
61
|
+
const row = record(value);
|
|
62
|
+
const bookedValue = finite(row.bookedValue, row.bookedRevenue);
|
|
63
|
+
return { ...row, bookedValue, bookedRevenue: bookedValue };
|
|
64
|
+
}
|
|
65
|
+
function normalizeReportResult(value) {
|
|
66
|
+
const source = record(value);
|
|
67
|
+
const report = record(source.report);
|
|
68
|
+
const byCategory = Array.isArray(report.byCategory) ? report.byCategory.map((value2) => {
|
|
69
|
+
const row = record(value2);
|
|
70
|
+
const bookedValue = finite(row.bookedValue, row.bookedRevenue);
|
|
71
|
+
return { ...row, bookedValue, bookedRevenue: bookedValue };
|
|
72
|
+
}) : [];
|
|
73
|
+
const bySection = Array.isArray(report.bySection) ? report.bySection.map(normalizeSection) : void 0;
|
|
74
|
+
return {
|
|
75
|
+
...source,
|
|
76
|
+
report: { ...report, byCategory, ...bySection ? { bySection } : {} }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function normalizeControlRoomSnapshot(value) {
|
|
80
|
+
const source = record(value);
|
|
81
|
+
const canonical = record(source.bookedValue);
|
|
82
|
+
const legacy = record(source.revenue);
|
|
83
|
+
const selected = Object.keys(canonical).length ? canonical : legacy;
|
|
84
|
+
const bySectionSource = Array.isArray(canonical.bySection) ? canonical.bySection : Array.isArray(legacy.bySection) ? legacy.bySection : [];
|
|
85
|
+
const bookedValue = {
|
|
86
|
+
...selected,
|
|
87
|
+
gross: finite(canonical.gross, legacy.gross),
|
|
88
|
+
bySection: bySectionSource.map(normalizeSection)
|
|
89
|
+
};
|
|
90
|
+
const velocity = record(source.velocity);
|
|
91
|
+
const velocityRows = Array.isArray(velocity.bySection) ? velocity.bySection.map((value2) => {
|
|
92
|
+
const row = record(value2);
|
|
93
|
+
const rowValue = finite(row.bookedValue, row.grossRevenue);
|
|
94
|
+
return { ...row, bookedValue: rowValue, grossRevenue: rowValue };
|
|
95
|
+
}) : [];
|
|
96
|
+
return {
|
|
97
|
+
...source,
|
|
98
|
+
bookedValue,
|
|
99
|
+
revenue: bookedValue,
|
|
100
|
+
velocity: { ...velocity, bySection: velocityRows }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function normalizeChannelReportResult(value) {
|
|
104
|
+
const source = record(value);
|
|
105
|
+
const report = record(source.report);
|
|
106
|
+
const includesBookedValue = typeof report.includesBookedValue === "boolean" ? report.includesBookedValue : report.includesRevenue === true;
|
|
107
|
+
const rows = Array.isArray(report.rows) ? report.rows.map((value2) => {
|
|
108
|
+
const row = record(value2);
|
|
109
|
+
const attribution = record(row.attribution);
|
|
110
|
+
const bookedValue = nullableFinite(attribution.bookedValue, attribution.revenue);
|
|
111
|
+
return {
|
|
112
|
+
...row,
|
|
113
|
+
attribution: { ...attribution, bookedValue, revenue: bookedValue }
|
|
114
|
+
};
|
|
115
|
+
}) : [];
|
|
116
|
+
const totals = record(report.totals);
|
|
117
|
+
const totalBookedValue = nullableFinite(totals.bookedValue, totals.revenue);
|
|
118
|
+
return {
|
|
119
|
+
...source,
|
|
120
|
+
report: {
|
|
121
|
+
...report,
|
|
122
|
+
includesBookedValue,
|
|
123
|
+
includesRevenue: includesBookedValue,
|
|
124
|
+
rows,
|
|
125
|
+
totals: { ...totals, bookedValue: totalBookedValue, revenue: totalBookedValue }
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function normalizeChannelReportLink(value) {
|
|
130
|
+
const link = record(value);
|
|
131
|
+
const includesBookedValue = typeof link.includesBookedValue === "boolean" ? link.includesBookedValue : link.includesRevenue === true;
|
|
132
|
+
return {
|
|
133
|
+
...link,
|
|
134
|
+
includesBookedValue,
|
|
135
|
+
includesRevenue: includesBookedValue
|
|
136
|
+
};
|
|
137
|
+
}
|
|
45
138
|
var ManageApiError, ManageApi;
|
|
46
139
|
var init_manageApi = __esm({
|
|
47
140
|
"src/manageApi.ts"() {
|
|
@@ -76,13 +169,28 @@ var init_manageApi = __esm({
|
|
|
76
169
|
}
|
|
77
170
|
return fetch(`${this.base}${path}`, { method, headers, body, credentials: "omit" }).then((r) => parse(r));
|
|
78
171
|
}
|
|
79
|
-
|
|
80
|
-
|
|
172
|
+
async authBlob(path) {
|
|
173
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
174
|
+
method: "GET",
|
|
175
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
176
|
+
credentials: "omit"
|
|
177
|
+
});
|
|
178
|
+
if (!res.ok) await parse(res);
|
|
179
|
+
return res.blob();
|
|
81
180
|
}
|
|
82
181
|
// ---- realtime read ----
|
|
83
|
-
/**
|
|
182
|
+
/** Event-pinned organizer geometry. A manage token is never sent to `/pub`. */
|
|
84
183
|
chart(key) {
|
|
85
|
-
return this.
|
|
184
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/chart`);
|
|
185
|
+
}
|
|
186
|
+
/** Authenticated bytes for an Event-scoped organizer chart asset. */
|
|
187
|
+
asset(key, asset) {
|
|
188
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
|
|
189
|
+
return Promise.reject(new ManageApiError(404, "not_found", "not_found"));
|
|
190
|
+
}
|
|
191
|
+
return this.authBlob(
|
|
192
|
+
`/v1/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
|
|
193
|
+
);
|
|
86
194
|
}
|
|
87
195
|
/**
|
|
88
196
|
* The ORGANIZER's seat map: physical state, token-authed.
|
|
@@ -142,6 +250,31 @@ var init_manageApi = __esm({
|
|
|
142
250
|
setHoldTtl(key, holdTtlMs) {
|
|
143
251
|
return this.auth(`/v1/events/${encodeURIComponent(key)}/hold-ttl`, { method: "POST", body: { holdTtlMs } });
|
|
144
252
|
}
|
|
253
|
+
// ---- Platform inventory booking history (token) ----
|
|
254
|
+
/** Inventory lifecycle by stable integrator bookingRef. Absent on Managed. */
|
|
255
|
+
bookings(key, query = {}) {
|
|
256
|
+
const params = new URLSearchParams();
|
|
257
|
+
if (query.q) params.set("q", query.q);
|
|
258
|
+
if (query.state) params.set("state", query.state);
|
|
259
|
+
if (query.cursor) params.set("cursor", query.cursor);
|
|
260
|
+
if (query.limit != null) params.set("limit", String(query.limit));
|
|
261
|
+
const qs = params.toString();
|
|
262
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/bookings${qs ? `?${qs}` : ""}`);
|
|
263
|
+
}
|
|
264
|
+
/** Exact configured-value snapshot plus book/replay/cancellation audit. */
|
|
265
|
+
booking(key, bookingRef) {
|
|
266
|
+
return this.auth(
|
|
267
|
+
`/v1/events/${encodeURIComponent(key)}/bookings/${encodeURIComponent(bookingRef)}`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
/** Alias matching the server SDK vocabulary. */
|
|
271
|
+
listBookings(key, query = {}) {
|
|
272
|
+
return this.bookings(key, query);
|
|
273
|
+
}
|
|
274
|
+
/** Alias matching the server SDK vocabulary. */
|
|
275
|
+
retrieveBooking(key, bookingRef) {
|
|
276
|
+
return this.booking(key, bookingRef);
|
|
277
|
+
}
|
|
145
278
|
// ---- availability windows (token) ----
|
|
146
279
|
/** The organizer's current per section/zone availability windows (needs
|
|
147
280
|
* `event:view`). Ids absent from `rules` are open / on sale. */
|
|
@@ -323,10 +456,46 @@ var init_manageApi = __esm({
|
|
|
323
456
|
}
|
|
324
457
|
// ---- reports (token) ----
|
|
325
458
|
report(key) {
|
|
326
|
-
return this.auth(`/v1/events/${encodeURIComponent(key)}/report`);
|
|
459
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/report`).then(normalizeReportResult);
|
|
327
460
|
}
|
|
328
461
|
controlRoom(key, windowMinutes = 15) {
|
|
329
|
-
return this.auth(
|
|
462
|
+
return this.auth(
|
|
463
|
+
`/v1/events/${encodeURIComponent(key)}/control-room?window=${windowMinutes}`
|
|
464
|
+
).then(normalizeControlRoomSnapshot);
|
|
465
|
+
}
|
|
466
|
+
/** Allocation beside immutable booking-time channel attribution. */
|
|
467
|
+
channelReport(key) {
|
|
468
|
+
return this.auth(
|
|
469
|
+
`/v1/events/${encodeURIComponent(key)}/channels/report`
|
|
470
|
+
).then(normalizeChannelReportResult);
|
|
471
|
+
}
|
|
472
|
+
createChannelReportLink(key, channelId, input = {}) {
|
|
473
|
+
return this.auth(
|
|
474
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links`,
|
|
475
|
+
{ method: "POST", body: input }
|
|
476
|
+
).then((value) => {
|
|
477
|
+
const reveal = record(value);
|
|
478
|
+
return { ...reveal, link: normalizeChannelReportLink(reveal.link) };
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
channelReportLinks(key, channelId) {
|
|
482
|
+
return this.auth(
|
|
483
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links`
|
|
484
|
+
).then((value) => {
|
|
485
|
+
const result = record(value);
|
|
486
|
+
return {
|
|
487
|
+
links: Array.isArray(result.links) ? result.links.map(normalizeChannelReportLink) : []
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
revokeChannelReportLink(key, channelId, linkId) {
|
|
492
|
+
return this.auth(
|
|
493
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links/${encodeURIComponent(linkId)}`,
|
|
494
|
+
{ method: "DELETE" }
|
|
495
|
+
).then((value) => {
|
|
496
|
+
const result = record(value);
|
|
497
|
+
return { ...result, link: normalizeChannelReportLink(result.link) };
|
|
498
|
+
});
|
|
330
499
|
}
|
|
331
500
|
log(key, opts = {}) {
|
|
332
501
|
const params = new URLSearchParams();
|
|
@@ -3533,6 +3702,108 @@ module.exports = __toCommonJS(manager_exports);
|
|
|
3533
3702
|
// src/SeatManager.ts
|
|
3534
3703
|
var import_core = require("@seatlayer/core");
|
|
3535
3704
|
init_manageApi();
|
|
3705
|
+
|
|
3706
|
+
// src/manageAssets.ts
|
|
3707
|
+
var SAFE_ASSET = /^[a-zA-Z0-9._-]+$/;
|
|
3708
|
+
function organizerEventAssetReference(value) {
|
|
3709
|
+
let url;
|
|
3710
|
+
try {
|
|
3711
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
3712
|
+
} catch {
|
|
3713
|
+
return null;
|
|
3714
|
+
}
|
|
3715
|
+
if (url.search || url.hash) return null;
|
|
3716
|
+
const match = /^\/v1\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
3717
|
+
if (!match) return null;
|
|
3718
|
+
try {
|
|
3719
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
3720
|
+
const asset = decodeURIComponent(match[2]);
|
|
3721
|
+
if (!eventKey || !SAFE_ASSET.test(asset)) return null;
|
|
3722
|
+
return { eventKey, asset };
|
|
3723
|
+
} catch {
|
|
3724
|
+
return null;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
function looksLikeOrganizerAsset(value) {
|
|
3728
|
+
try {
|
|
3729
|
+
return /^\/v1\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
3730
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
3731
|
+
);
|
|
3732
|
+
} catch {
|
|
3733
|
+
return false;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
var OrganizerAssetObjectUrls = class {
|
|
3737
|
+
constructor(eventKey, load) {
|
|
3738
|
+
this.eventKey = eventKey;
|
|
3739
|
+
this.load = load;
|
|
3740
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
3741
|
+
this.created = /* @__PURE__ */ new Set();
|
|
3742
|
+
this.disposed = false;
|
|
3743
|
+
}
|
|
3744
|
+
resolve(reference) {
|
|
3745
|
+
const parsed = organizerEventAssetReference(reference);
|
|
3746
|
+
if (!parsed) {
|
|
3747
|
+
return Promise.resolve(looksLikeOrganizerAsset(reference) ? null : reference);
|
|
3748
|
+
}
|
|
3749
|
+
if (parsed.eventKey !== this.eventKey || this.disposed) return Promise.resolve(null);
|
|
3750
|
+
const cacheKey = `${parsed.eventKey}/${parsed.asset}`;
|
|
3751
|
+
const existing = this.pending.get(cacheKey);
|
|
3752
|
+
if (existing) return existing;
|
|
3753
|
+
const task = this.load(parsed.eventKey, parsed.asset).then((blob) => {
|
|
3754
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
3755
|
+
if (this.disposed) {
|
|
3756
|
+
URL.revokeObjectURL(objectUrl);
|
|
3757
|
+
return null;
|
|
3758
|
+
}
|
|
3759
|
+
this.created.add(objectUrl);
|
|
3760
|
+
return objectUrl;
|
|
3761
|
+
}).catch((error) => {
|
|
3762
|
+
this.pending.delete(cacheKey);
|
|
3763
|
+
throw error;
|
|
3764
|
+
});
|
|
3765
|
+
this.pending.set(cacheKey, task);
|
|
3766
|
+
return task;
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Resolve the image fields the synchronous map renderer loads immediately.
|
|
3770
|
+
* View-from-seat media stays lazy: SeatManager does not open that buyer
|
|
3771
|
+
* surface, and eagerly downloading every row panorama would be unbounded.
|
|
3772
|
+
*/
|
|
3773
|
+
async prepareRendererChart(doc) {
|
|
3774
|
+
const prepareBackground = async (background) => {
|
|
3775
|
+
if (!background?.url) return;
|
|
3776
|
+
const resolved = await this.resolve(background.url);
|
|
3777
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
3778
|
+
background.url = resolved;
|
|
3779
|
+
};
|
|
3780
|
+
const prepareObjects = async (objects) => {
|
|
3781
|
+
for (const object of objects) {
|
|
3782
|
+
if (object.type !== "decorImage") continue;
|
|
3783
|
+
const image = object;
|
|
3784
|
+
const resolved = await this.resolve(image.href);
|
|
3785
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
3786
|
+
image.href = resolved;
|
|
3787
|
+
}
|
|
3788
|
+
};
|
|
3789
|
+
const prepareOwner = async (owner) => {
|
|
3790
|
+
await prepareBackground(owner.backgroundImage);
|
|
3791
|
+
await prepareObjects(owner.objects);
|
|
3792
|
+
};
|
|
3793
|
+
await prepareOwner(doc);
|
|
3794
|
+
for (const floor of doc.floors ?? []) await prepareOwner(floor);
|
|
3795
|
+
return doc;
|
|
3796
|
+
}
|
|
3797
|
+
dispose() {
|
|
3798
|
+
if (this.disposed) return;
|
|
3799
|
+
this.disposed = true;
|
|
3800
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
3801
|
+
this.created.clear();
|
|
3802
|
+
this.pending.clear();
|
|
3803
|
+
}
|
|
3804
|
+
};
|
|
3805
|
+
|
|
3806
|
+
// src/SeatManager.ts
|
|
3536
3807
|
function availabilityModeOf(rule) {
|
|
3537
3808
|
return rule ? rule.mode : "open";
|
|
3538
3809
|
}
|
|
@@ -3811,8 +4082,8 @@ var MANAGER_CSS = (
|
|
|
3811
4082
|
.slm.compact .slm-modes{min-width:0}.slm.compact .slm-mode{padding-inline:11px}
|
|
3812
4083
|
.slm.compact .slm-live{justify-self:end}.slm.compact .slm-bar-actions{grid-column:1/-1;justify-self:stretch}
|
|
3813
4084
|
.slm.compact .slm-barbtn{flex:1;padding:6px 9px}.slm.compact .slm-kpis{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
|
|
3814
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
3815
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
4085
|
+
.slm.compact .slm-kpi[data-kpi="viewing-map"],.slm.compact .slm-kpi[data-kpi="active-holds"],
|
|
4086
|
+
.slm.compact .slm-kpi[data-kpi="booked-pct"],.slm.compact .slm-kpi[data-kpi="booked-value"]{display:none}
|
|
3816
4087
|
/* Reduced motion, as a BLANKET over the cockpit subtree rather than a list of
|
|
3817
4088
|
selectors. The list this replaces named four animations and two transitions,
|
|
3818
4089
|
and had silently fallen behind the stylesheet: the zoom hint, the toast and
|
|
@@ -4022,6 +4293,10 @@ var SeatManager = class {
|
|
|
4022
4293
|
this.currency = options.currency ?? "USD";
|
|
4023
4294
|
this.tokenExpiresAt = options.tokenExpiresAt ?? null;
|
|
4024
4295
|
this.api = new ManageApi(options.apiBase ?? DEFAULT_API_BASE, options.token);
|
|
4296
|
+
this.organizerAssetUrls = new OrganizerAssetObjectUrls(
|
|
4297
|
+
this.key,
|
|
4298
|
+
(key, asset) => this.withAuthRetry(() => this.api.asset(key, asset))
|
|
4299
|
+
);
|
|
4025
4300
|
this.host = resolveContainer(options.container);
|
|
4026
4301
|
}
|
|
4027
4302
|
/** Build the DOM, load the chart, subscribe to realtime, mount the board. */
|
|
@@ -4029,10 +4304,10 @@ var SeatManager = class {
|
|
|
4029
4304
|
injectStyle();
|
|
4030
4305
|
this.buildChrome();
|
|
4031
4306
|
try {
|
|
4032
|
-
const res = await this.api.chart(this.key);
|
|
4033
|
-
this.doc = res.doc;
|
|
4307
|
+
const res = await this.withAuthRetry(() => this.api.chart(this.key));
|
|
4308
|
+
this.doc = await this.organizerAssetUrls.prepareRendererChart(res.doc);
|
|
4034
4309
|
this.currency = res.event.currency ?? this.opts.currency ?? this.currency;
|
|
4035
|
-
this.buildUnitUniverse(
|
|
4310
|
+
this.buildUnitUniverse(this.doc);
|
|
4036
4311
|
this.buildRenderer();
|
|
4037
4312
|
this.buildSectionOptions();
|
|
4038
4313
|
const [, controlRoom] = await Promise.all([
|
|
@@ -4379,10 +4654,10 @@ var SeatManager = class {
|
|
|
4379
4654
|
try {
|
|
4380
4655
|
await this.api.unbook(this.key, targets, bookingRef);
|
|
4381
4656
|
this.clearSelection();
|
|
4382
|
-
this.done("cancelBooking", targets, `
|
|
4657
|
+
this.done("cancelBooking", targets, `Released ${targets.length} booked unit${targets.length === 1 ? "" : "s"}.`);
|
|
4383
4658
|
} catch (err) {
|
|
4384
4659
|
this.setSeatsLocal(targets, "booked");
|
|
4385
|
-
this.toastErr("Couldn't
|
|
4660
|
+
this.toastErr("Couldn't release that booked inventory. Check the booking reference.");
|
|
4386
4661
|
this.opts.onError?.(err);
|
|
4387
4662
|
}
|
|
4388
4663
|
}
|
|
@@ -4435,9 +4710,9 @@ var SeatManager = class {
|
|
|
4435
4710
|
async setHoldTtl(ms) {
|
|
4436
4711
|
try {
|
|
4437
4712
|
await this.api.setHoldTtl(this.key, ms);
|
|
4438
|
-
this.done("setHoldTtl", [], ms ? `
|
|
4713
|
+
this.done("setHoldTtl", [], ms ? `Hold window set to ${Math.round(ms / 6e4)} min.` : "Hold window reset.");
|
|
4439
4714
|
} catch (err) {
|
|
4440
|
-
this.toastErr("Couldn't update the
|
|
4715
|
+
this.toastErr("Couldn't update the hold window.");
|
|
4441
4716
|
this.opts.onError?.(err);
|
|
4442
4717
|
}
|
|
4443
4718
|
}
|
|
@@ -4482,6 +4757,7 @@ var SeatManager = class {
|
|
|
4482
4757
|
}
|
|
4483
4758
|
this.renderer?.destroy();
|
|
4484
4759
|
this.renderer = null;
|
|
4760
|
+
this.organizerAssetUrls.dispose();
|
|
4485
4761
|
if (this.root && this.root.parentNode === this.host) this.host.removeChild(this.root);
|
|
4486
4762
|
}
|
|
4487
4763
|
// ---- renderer lifecycle ---------------------------------------------------
|
|
@@ -4602,24 +4878,28 @@ var SeatManager = class {
|
|
|
4602
4878
|
* projects its deltas, so any change inside a private channel allocation is
|
|
4603
4879
|
* structurally suppressed and the map silently drifts.
|
|
4604
4880
|
*
|
|
4605
|
-
* If the mint fails
|
|
4606
|
-
*
|
|
4607
|
-
*
|
|
4608
|
-
* the authenticated HTTP read.
|
|
4881
|
+
* If the mint fails, remain reconnecting. An unticketed socket is a buyer
|
|
4882
|
+
* projection, so applying it to organizer state would be worse than staying
|
|
4883
|
+
* visibly offline while the host refreshes authority or upgrades the API.
|
|
4609
4884
|
*/
|
|
4610
4885
|
async connect() {
|
|
4611
4886
|
if (this.closed) return;
|
|
4612
4887
|
let protocols;
|
|
4613
4888
|
try {
|
|
4614
|
-
protocols = (await this.api.subscribeTicket(this.key)).protocols;
|
|
4615
|
-
|
|
4616
|
-
|
|
4889
|
+
protocols = (await this.withAuthRetry(() => this.api.subscribeTicket(this.key))).protocols;
|
|
4890
|
+
if (!protocols.length) throw new Error("manage_subscribe_ticket_missing");
|
|
4891
|
+
} catch (err) {
|
|
4892
|
+
this.setLive(false);
|
|
4893
|
+
this.opts.onError?.(err);
|
|
4894
|
+
this.scheduleReconnect();
|
|
4895
|
+
return;
|
|
4617
4896
|
}
|
|
4618
4897
|
if (this.closed) return;
|
|
4619
4898
|
let ws;
|
|
4620
4899
|
try {
|
|
4621
|
-
ws =
|
|
4622
|
-
} catch {
|
|
4900
|
+
ws = new WebSocket(this.api.socketUrl(this.key), protocols);
|
|
4901
|
+
} catch (err) {
|
|
4902
|
+
this.opts.onError?.(err);
|
|
4623
4903
|
this.scheduleReconnect();
|
|
4624
4904
|
return;
|
|
4625
4905
|
}
|
|
@@ -4710,8 +4990,9 @@ var SeatManager = class {
|
|
|
4710
4990
|
this.lastSyncedAt = Date.now();
|
|
4711
4991
|
this.afterPaint();
|
|
4712
4992
|
}
|
|
4713
|
-
|
|
4714
|
-
|
|
4993
|
+
const liveBookedValue = typeof m.bookedValue?.gross === "number" ? m.bookedValue.gross : m.revenue?.gross;
|
|
4994
|
+
if (typeof liveBookedValue === "number" && Number.isFinite(liveBookedValue)) {
|
|
4995
|
+
this.applyLiveGross(liveBookedValue);
|
|
4715
4996
|
}
|
|
4716
4997
|
this.recomputeTallies();
|
|
4717
4998
|
}
|
|
@@ -4728,9 +5009,12 @@ var SeatManager = class {
|
|
|
4728
5009
|
this.authoritativeGrossRevenue = gross;
|
|
4729
5010
|
this.revenueStatus = "current";
|
|
4730
5011
|
if (this.controlRoomSnapshot) {
|
|
5012
|
+
const current = this.controlRoomSnapshot.bookedValue ?? this.controlRoomSnapshot.revenue;
|
|
5013
|
+
const bookedValue = { ...current, gross };
|
|
4731
5014
|
this.controlRoomSnapshot = {
|
|
4732
5015
|
...this.controlRoomSnapshot,
|
|
4733
|
-
|
|
5016
|
+
bookedValue,
|
|
5017
|
+
revenue: bookedValue
|
|
4734
5018
|
};
|
|
4735
5019
|
this.opts.onControlRoom?.(this.controlRoomSnapshot);
|
|
4736
5020
|
}
|
|
@@ -4908,7 +5192,10 @@ var SeatManager = class {
|
|
|
4908
5192
|
// ---- tallies + feed -------------------------------------------------------
|
|
4909
5193
|
applyReportRevenue(report) {
|
|
4910
5194
|
this.authoritativeGrossRevenue = report.report.byCategory.reduce(
|
|
4911
|
-
(sum, row) =>
|
|
5195
|
+
(sum, row) => {
|
|
5196
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
5197
|
+
return sum + (Number.isFinite(value) ? value : 0);
|
|
5198
|
+
},
|
|
4912
5199
|
0
|
|
4913
5200
|
);
|
|
4914
5201
|
this.revenueStatus = "current";
|
|
@@ -4927,7 +5214,13 @@ var SeatManager = class {
|
|
|
4927
5214
|
const requestedAt = Date.now();
|
|
4928
5215
|
try {
|
|
4929
5216
|
const fetched = await this.api.controlRoom(this.key, this.trendWindowMinutes);
|
|
4930
|
-
|
|
5217
|
+
const incoming = fetched.bookedValue ?? fetched.revenue ?? { gross: 0, bySection: [] };
|
|
5218
|
+
const normalizedSections = (incoming.bySection ?? []).map((row) => {
|
|
5219
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
5220
|
+
return { ...row, bookedValue: value ?? 0, bookedRevenue: value ?? 0 };
|
|
5221
|
+
});
|
|
5222
|
+
const canonical = { ...incoming, bySection: normalizedSections };
|
|
5223
|
+
let snapshot = { ...fetched, bookedValue: canonical, revenue: canonical };
|
|
4931
5224
|
if (request === this.revenueRequest) {
|
|
4932
5225
|
if (this.livePresence && this.livePresence.at >= requestedAt) {
|
|
4933
5226
|
snapshot = { ...snapshot, presence: this.livePresence.value };
|
|
@@ -4935,14 +5228,15 @@ var SeatManager = class {
|
|
|
4935
5228
|
this.livePresence = null;
|
|
4936
5229
|
}
|
|
4937
5230
|
if (this.liveGross && this.liveGross.at >= requestedAt) {
|
|
4938
|
-
|
|
5231
|
+
const bookedValue = { ...snapshot.bookedValue, gross: this.liveGross.value };
|
|
5232
|
+
snapshot = { ...snapshot, bookedValue, revenue: bookedValue };
|
|
4939
5233
|
} else {
|
|
4940
5234
|
this.liveGross = null;
|
|
4941
5235
|
}
|
|
4942
5236
|
this.controlRoomSnapshot = snapshot;
|
|
4943
5237
|
this.rebaseServerTotals(snapshot);
|
|
4944
5238
|
this.lastSyncedAt = Date.now();
|
|
4945
|
-
this.authoritativeGrossRevenue = snapshot.
|
|
5239
|
+
this.authoritativeGrossRevenue = snapshot.bookedValue.gross;
|
|
4946
5240
|
this.currency = snapshot.currency;
|
|
4947
5241
|
this.revenueStatus = "current";
|
|
4948
5242
|
this.recomputeTallies();
|
|
@@ -5000,7 +5294,9 @@ var SeatManager = class {
|
|
|
5000
5294
|
total: Number.isFinite(seatTotal) ? seatTotal : this.unitTotal(),
|
|
5001
5295
|
capacityPct: 0,
|
|
5002
5296
|
sellThroughPct: 0,
|
|
5297
|
+
bookedValue: this.authoritativeGrossRevenue,
|
|
5003
5298
|
grossRevenue: this.authoritativeGrossRevenue,
|
|
5299
|
+
bookedValueStatus: this.revenueStatus,
|
|
5004
5300
|
revenueStatus: this.revenueStatus,
|
|
5005
5301
|
currency: this.currency
|
|
5006
5302
|
};
|
|
@@ -5155,8 +5451,8 @@ var SeatManager = class {
|
|
|
5155
5451
|
<button class="slm-barbtn follow" data-ref="follow" aria-pressed="false"
|
|
5156
5452
|
title="Stay on the current map view unless enabled">Follow live</button>
|
|
5157
5453
|
<button class="slm-barbtn" data-ref="heat" aria-pressed="false"
|
|
5158
|
-
aria-label="
|
|
5159
|
-
title="Highlight sections
|
|
5454
|
+
aria-label="Booking momentum overlay off"
|
|
5455
|
+
title="Highlight sections booking fastest in the selected time window">Booking momentum</button>
|
|
5160
5456
|
<button class="slm-barbtn" data-ref="fullscreen" title="Full screen (F)" aria-keyshortcuts="F">Full screen</button>
|
|
5161
5457
|
</div>
|
|
5162
5458
|
<div class="slm-kpis" data-ref="kpis"></div>
|
|
@@ -5259,16 +5555,16 @@ var SeatManager = class {
|
|
|
5259
5555
|
if (!button) return;
|
|
5260
5556
|
button.classList.toggle("on", this.followLive);
|
|
5261
5557
|
button.setAttribute("aria-pressed", String(this.followLive));
|
|
5262
|
-
button.setAttribute("title", this.followLive ? "Following new
|
|
5558
|
+
button.setAttribute("title", this.followLive ? "Following new holds and bookings. Turn off to keep the current view." : "Stay on the current map view. Enable to follow new holds and bookings.");
|
|
5263
5559
|
}
|
|
5264
5560
|
paintHeatButton() {
|
|
5265
5561
|
const button = this.els.heat;
|
|
5266
5562
|
if (!button) return;
|
|
5267
5563
|
button.classList.toggle("on", this.heatEnabled);
|
|
5268
5564
|
button.setAttribute("aria-pressed", String(this.heatEnabled));
|
|
5269
|
-
button.setAttribute("aria-label", `
|
|
5270
|
-
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections
|
|
5271
|
-
button.textContent = "
|
|
5565
|
+
button.setAttribute("aria-label", `Booking momentum overlay ${this.heatEnabled ? "on" : "off"}`);
|
|
5566
|
+
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections booking fastest in the selected time window`);
|
|
5567
|
+
button.textContent = "Booking momentum";
|
|
5272
5568
|
this.paintMomentumHelp();
|
|
5273
5569
|
}
|
|
5274
5570
|
paintMomentumHelp() {
|
|
@@ -5312,23 +5608,23 @@ var SeatManager = class {
|
|
|
5312
5608
|
formatKpiDelta(key, delta, currency) {
|
|
5313
5609
|
const sign = delta > 0 ? "+" : "\u2212";
|
|
5314
5610
|
const absolute = Math.abs(delta);
|
|
5315
|
-
if (key === "
|
|
5316
|
-
if (key === "
|
|
5611
|
+
if (key === "booked-value") return `${sign}${fmtMoney(absolute, currency)}`;
|
|
5612
|
+
if (key === "booked-pct") return `${sign}${absolute.toLocaleString()}pt`;
|
|
5317
5613
|
return `${sign}${absolute.toLocaleString()}`;
|
|
5318
5614
|
}
|
|
5319
5615
|
paintKpis(t) {
|
|
5320
5616
|
if (!this.els.kpis) return;
|
|
5321
|
-
const
|
|
5617
|
+
const bookedValue = t.bookedValueStatus === "current" ? fmtMoney(t.bookedValue, t.currency) : "\u2014";
|
|
5322
5618
|
const presence = this.presenceCounts();
|
|
5323
5619
|
const items = [
|
|
5324
|
-
{ key: "
|
|
5325
|
-
{ key: "held-seats", raw: t.held, n: t.held.toLocaleString(), l: "Held
|
|
5326
|
-
{ key: "free-seats", raw: t.free, n: t.free.toLocaleString(), l: "
|
|
5327
|
-
{ key: "blocked", raw: t.blocked, n: t.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "
|
|
5328
|
-
{ key: "
|
|
5329
|
-
{ key: "
|
|
5330
|
-
{ key: "
|
|
5331
|
-
{ key: "
|
|
5620
|
+
{ key: "booked-inventory", raw: t.booked, n: t.booked.toLocaleString(), l: "Booked inventory", dot: "#22a06b", title: "Inventory units booked" },
|
|
5621
|
+
{ key: "held-seats", raw: t.held, n: t.held.toLocaleString(), l: "Held inventory", dot: "#f4b740", title: "Inventory currently held" },
|
|
5622
|
+
{ key: "free-seats", raw: t.free, n: t.free.toLocaleString(), l: "Available", dot: "#6e7bff", title: "Inventory available to book" },
|
|
5623
|
+
{ key: "blocked", raw: t.blocked, n: t.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "Inventory withheld from booking" },
|
|
5624
|
+
{ key: "viewing-map", raw: presence?.shoppingSessions ?? null, n: presence ? presence.shoppingSessions.toLocaleString() : "\u2014", l: "Viewing map", title: "Active map sessions right now" },
|
|
5625
|
+
{ key: "active-holds", raw: presence?.activeHolds ?? null, n: presence ? presence.activeHolds.toLocaleString() : "\u2014", l: "Active holds", title: "Sessions currently holding inventory" },
|
|
5626
|
+
{ key: "booked-pct", raw: t.capacityPct, n: `${t.capacityPct}%`, l: "Booked", title: "Booked inventory as a share of the whole event" },
|
|
5627
|
+
{ key: "booked-value", raw: t.bookedValueStatus === "current" ? t.bookedValue : null, n: bookedValue, l: "Booked value", title: "Configured value attached to booked inventory" }
|
|
5332
5628
|
];
|
|
5333
5629
|
let hasChanges = false;
|
|
5334
5630
|
this.els.kpis.innerHTML = items.map((item) => {
|
|
@@ -5377,12 +5673,12 @@ var SeatManager = class {
|
|
|
5377
5673
|
renderViewRail() {
|
|
5378
5674
|
this.els.rail.innerHTML = `
|
|
5379
5675
|
<p class="slm-eyebrow">Monitor</p>
|
|
5380
|
-
<p class="slm-hint">Read-only. Inventory,
|
|
5676
|
+
<p class="slm-hint">Read-only. Inventory, map activity and booking movement update on the same live board.</p>
|
|
5381
5677
|
<div class="slm-health" data-ref="presence"></div>
|
|
5382
5678
|
<div class="slm-legend" data-ref="legend"></div>
|
|
5383
5679
|
<div class="slm-sectionhead">
|
|
5384
|
-
<div><p class="slm-eyebrow">Section
|
|
5385
|
-
<div class="slm-windows" aria-label="
|
|
5680
|
+
<div><p class="slm-eyebrow">Section inventory</p><p class="slm-note">Configured booked value \xB7 booking momentum</p></div>
|
|
5681
|
+
<div class="slm-windows" aria-label="Booking momentum window">
|
|
5386
5682
|
${[5, 15, 30, 60].map((window2) => `<button class="slm-window" data-window="${window2}">${window2}m</button>`).join("")}
|
|
5387
5683
|
</div>
|
|
5388
5684
|
</div>
|
|
@@ -5421,8 +5717,8 @@ var SeatManager = class {
|
|
|
5421
5717
|
const sync = this.lastSyncedAt ? relTime(this.lastSyncedAt, Date.now()) : "waiting";
|
|
5422
5718
|
const presence = this.presenceCounts();
|
|
5423
5719
|
this.els.presence.innerHTML = `
|
|
5424
|
-
<div class="slm-healthitem" title="
|
|
5425
|
-
<div class="slm-healthitem" title="
|
|
5720
|
+
<div class="slm-healthitem" title="Active map sessions right now"><b>${presence ? presence.shoppingSessions.toLocaleString() : "\u2014"}</b><span>Viewing map</span></div>
|
|
5721
|
+
<div class="slm-healthitem" title="Sessions currently holding inventory"><b>${presence ? presence.activeHolds.toLocaleString() : "\u2014"}</b><span>Active holds</span></div>
|
|
5426
5722
|
<div class="slm-healthitem"><b>${connected ? "Healthy" : "Reconnecting"}</b><span>Live connection</span></div>
|
|
5427
5723
|
<div class="slm-healthitem"><b>${sync}</b><span>Last sync</span></div>`;
|
|
5428
5724
|
}
|
|
@@ -5432,10 +5728,10 @@ var SeatManager = class {
|
|
|
5432
5728
|
return;
|
|
5433
5729
|
}
|
|
5434
5730
|
const velocity = new Map(snapshot.velocity.bySection.map((row) => [row.sectionId, row]));
|
|
5435
|
-
const rows = [...snapshot.
|
|
5731
|
+
const rows = [...snapshot.bookedValue.bySection].sort((a, b) => {
|
|
5436
5732
|
const av = velocity.get(a.sectionId)?.netBooked ?? 0;
|
|
5437
5733
|
const bv = velocity.get(b.sectionId)?.netBooked ?? 0;
|
|
5438
|
-
return bv - av || b.
|
|
5734
|
+
return bv - av || b.bookedValue - a.bookedValue;
|
|
5439
5735
|
});
|
|
5440
5736
|
this.els.sections.innerHTML = rows.length ? rows.map((row) => {
|
|
5441
5737
|
const speed = velocity.get(row.sectionId);
|
|
@@ -5443,8 +5739,8 @@ var SeatManager = class {
|
|
|
5443
5739
|
const netLabel = `${net > 0 ? "+" : ""}${net}`;
|
|
5444
5740
|
const trend = speed?.trend === "rising" || speed?.trend === "cooling" ? speed.trend : "steady";
|
|
5445
5741
|
return `<button type="button" class="slm-sectionrow" data-section-focus="${esc2(row.sectionId)}" title="Focus ${esc2(row.sectionLabel)} on the map">
|
|
5446
|
-
<span class="slm-sectiontop"><span>${esc2(row.sectionLabel)}</span><span>${fmtMoney(row.
|
|
5447
|
-
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()}
|
|
5742
|
+
<span class="slm-sectiontop"><span>${esc2(row.sectionLabel)}</span><span>${fmtMoney(row.bookedValue, snapshot.currency)}</span></span>
|
|
5743
|
+
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()} booked \xB7 ${netLabel} in ${snapshot.velocity.windowMinutes}m</span><span class="slm-trend ${trend}">${trend}</span><span class="slm-sectionlocate">Locate</span></span>
|
|
5448
5744
|
</button>`;
|
|
5449
5745
|
}).join("") : '<div class="slm-empty">No section metrics are available for this chart.</div>';
|
|
5450
5746
|
this.paintTrendWindow();
|
|
@@ -5456,7 +5752,7 @@ var SeatManager = class {
|
|
|
5456
5752
|
this.renderer?.setSectionHeat(null);
|
|
5457
5753
|
return;
|
|
5458
5754
|
}
|
|
5459
|
-
const capacity = new Map(snapshot.
|
|
5755
|
+
const capacity = new Map(snapshot.bookedValue.bySection.map((row) => [row.sectionId, Math.max(1, row.total)]));
|
|
5460
5756
|
const rates = snapshot.velocity.bySection.map((row) => ({
|
|
5461
5757
|
sectionId: row.sectionId,
|
|
5462
5758
|
rate: Math.max(0, row.netBooked) / (capacity.get(row.sectionId) ?? 1) / snapshot.velocity.windowMinutes
|
|
@@ -5471,7 +5767,7 @@ var SeatManager = class {
|
|
|
5471
5767
|
if (!seat) {
|
|
5472
5768
|
this.els.rail.innerHTML = `
|
|
5473
5769
|
<p class="slm-eyebrow">Inspect seats</p>
|
|
5474
|
-
<p class="slm-hint">Select a seat to see its availability and
|
|
5770
|
+
<p class="slm-hint">Select a seat to see its availability and booking context. Nothing changes in this view.</p>
|
|
5475
5771
|
<div class="slm-empty">Select a seat on the map.</div>`;
|
|
5476
5772
|
return;
|
|
5477
5773
|
}
|
|
@@ -5480,7 +5776,7 @@ var SeatManager = class {
|
|
|
5480
5776
|
const sectionId = this.sectionByObject.get(seat.rowId) ?? import_core.UNGROUPED_ID;
|
|
5481
5777
|
const sectionLabel = this.sectionLabelById.get(sectionId) ?? "Other seats";
|
|
5482
5778
|
const category = this.doc?.categories.find((item) => item.key === seat.categoryKey);
|
|
5483
|
-
const sectionMetric = this.controlRoomSnapshot?.
|
|
5779
|
+
const sectionMetric = this.controlRoomSnapshot?.bookedValue.bySection.find((row) => row.sectionId === sectionId);
|
|
5484
5780
|
const object = this.doc?.objects.find((item) => item.id === seat.rowId);
|
|
5485
5781
|
const location = object?.type === "row" ? { label: "Row", value: object.label } : object?.type === "table" ? { label: "Table", value: object.label } : seat.kind === "booth" ? { label: "Type", value: "Booth" } : null;
|
|
5486
5782
|
const itemKind = seat.kind === "booth" ? "Booth" : "Seat";
|
|
@@ -5494,8 +5790,8 @@ var SeatManager = class {
|
|
|
5494
5790
|
<div><span>Section</span><b>${esc2(sectionLabel)}</b></div>
|
|
5495
5791
|
${location ? `<div><span>${location.label}</span><b>${esc2(location.value)}</b></div>` : ""}
|
|
5496
5792
|
<div><span>Category</span><b>${esc2(category?.label ?? seat.categoryKey)}</b></div>
|
|
5497
|
-
<div><span>
|
|
5498
|
-
<div><span>Section
|
|
5793
|
+
<div><span>Booked in section</span><b>${sectionMetric ? `${sectionMetric.booked} of ${sectionMetric.total}` : "\u2014"}</b></div>
|
|
5794
|
+
<div><span>Section booked value</span><b>${sectionMetric && this.controlRoomSnapshot ? fmtMoney(sectionMetric.bookedValue, this.controlRoomSnapshot.currency) : "\u2014"}</b></div>
|
|
5499
5795
|
</div>
|
|
5500
5796
|
</div>`;
|
|
5501
5797
|
}
|