@seatlayer/js 0.38.0 → 0.40.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.cjs +1027 -784
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +268 -160
- package/dist/index.d.ts +268 -160
- package/dist/index.js +1029 -784
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -185,6 +185,176 @@ declare function createBuyerAccessContext(options: {
|
|
|
185
185
|
buyerAccessToken?: string | BuyerAccessToken;
|
|
186
186
|
}, hooks?: Pick<BuyerAccessContextOptions, 'onExpired' | 'onUnavailable'>): BuyerAccessContext | null;
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* BuyerRealtimeClient — the client half of `docs/realtime-protocol-2026-08-01.md`.
|
|
190
|
+
*
|
|
191
|
+
* Why this exists as a separate socket rather than inside PickerController:
|
|
192
|
+
* a private scope authenticates with a one-use **subscribe ticket** carried in
|
|
193
|
+
* `Sec-WebSocket-Protocol`, and a browser can only set that at construction —
|
|
194
|
+
* `new WebSocket(url, protocols)`. The controller's socket is built from a URL
|
|
195
|
+
* alone (`PickerTransport.socketUrl`), and a bearer must never travel in a URL
|
|
196
|
+
* because URLs are routinely logged. So an access-scoped picker asks the
|
|
197
|
+
* transport for an empty `socketUrl()` (the controller then skips its own
|
|
198
|
+
* connection entirely) and this client owns the wire instead.
|
|
199
|
+
*
|
|
200
|
+
* A tokenless public picker never reaches this file. It keeps the controller's
|
|
201
|
+
* original socket, offers no subprotocol, and therefore receives byte-for-byte
|
|
202
|
+
* the frames it received before this module existed (protocol doc §1).
|
|
203
|
+
*
|
|
204
|
+
* What it implements:
|
|
205
|
+
* - protocol negotiation: offer `seatlayer.v1`, believe the 101 echo, and fall
|
|
206
|
+
* back to legacy frame handling when the server (or a proxy) does not echo;
|
|
207
|
+
* - the ticket exchange, one mint per connection attempt, ticket in the
|
|
208
|
+
* subprotocol list and never in the URL;
|
|
209
|
+
* - compact `{default, exceptions}` snapshot reconstruction;
|
|
210
|
+
* - `sv.<n>` resume, handling BOTH outcomes (a `resumed` delta or a full
|
|
211
|
+
* snapshot) on every reconnect;
|
|
212
|
+
* - close code 4401 as a typed access-revoked state, never a reconnect loop;
|
|
213
|
+
* - liveness by ping/pong only. Silence is normal and carries no information
|
|
214
|
+
* (protocol doc §5) — a quiet socket is never treated as a dead one.
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
/** The projected status of one unit, as the server words it on the wire. */
|
|
218
|
+
type WireStatus = string;
|
|
219
|
+
/** A scope's projection: one default plus the units that differ from it. */
|
|
220
|
+
interface Projection {
|
|
221
|
+
default: WireStatus;
|
|
222
|
+
exceptions: Record<string, WireStatus>;
|
|
223
|
+
}
|
|
224
|
+
interface StatusChange {
|
|
225
|
+
label: string;
|
|
226
|
+
status: WireStatus;
|
|
227
|
+
}
|
|
228
|
+
/** Where reconstructed inventory goes. Implemented over PickerController. */
|
|
229
|
+
interface RealtimeSink {
|
|
230
|
+
/** Apply a batch of label→status changes. Implementations must paint this as
|
|
231
|
+
* ONE pass, not one pass per label (motion system §4 rule 2). */
|
|
232
|
+
applyStatuses(changes: StatusChange[]): void;
|
|
233
|
+
/** Re-pull authoritative state over the scoped HTTP route. Used when a frame
|
|
234
|
+
* cannot be diffed against what we hold (first snapshot, or the scope's
|
|
235
|
+
* default itself changed, which redefines every unit we were never told
|
|
236
|
+
* about). */
|
|
237
|
+
resync(): void | Promise<void>;
|
|
238
|
+
/** Section availability changed (channel-agnostic; identical for every scope). */
|
|
239
|
+
onSections?(hidden: string[], closed: string[]): void;
|
|
240
|
+
/** Scope-projected presence counters. */
|
|
241
|
+
onPresence?(counts: {
|
|
242
|
+
shoppingSessions: number;
|
|
243
|
+
activeHolds: number;
|
|
244
|
+
}): void;
|
|
245
|
+
}
|
|
246
|
+
interface SubscribeTicket$1 {
|
|
247
|
+
ticket?: string;
|
|
248
|
+
/** Exactly what to hand `new WebSocket(url, protocols)`, per protocol doc §3. */
|
|
249
|
+
protocols?: string[];
|
|
250
|
+
}
|
|
251
|
+
interface BuyerRealtimeOptions {
|
|
252
|
+
/** The subscribe URL. Must never carry a credential — asserted below. */
|
|
253
|
+
url: string;
|
|
254
|
+
sink: RealtimeSink;
|
|
255
|
+
/** Mint a one-use ticket for THIS connection attempt. Returns null for the
|
|
256
|
+
* anonymous public case (no ticket needed). Throwing stops the client. */
|
|
257
|
+
mintTicket?: () => Promise<SubscribeTicket$1 | null>;
|
|
258
|
+
/** Typed access states. 4401 arrives here as `revoked`. */
|
|
259
|
+
onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
|
|
260
|
+
/** Test seam. Defaults to the global WebSocket. */
|
|
261
|
+
socketFactory?: (url: string, protocols: string[]) => WebSocket;
|
|
262
|
+
/** Test seam for the keepalive/backoff timers. */
|
|
263
|
+
now?: () => number;
|
|
264
|
+
}
|
|
265
|
+
declare class BuyerRealtimeClient {
|
|
266
|
+
private readonly opts;
|
|
267
|
+
private ws;
|
|
268
|
+
private stopped;
|
|
269
|
+
private attempt;
|
|
270
|
+
private reconnectTimer;
|
|
271
|
+
private pingTimer;
|
|
272
|
+
private pongTimer;
|
|
273
|
+
private resumeTimer;
|
|
274
|
+
/** Our model of this scope's projection. Null until the first snapshot. */
|
|
275
|
+
private projection;
|
|
276
|
+
/** Last `snapshotVersion` seen on any frame that carried one — the resume point. */
|
|
277
|
+
private version;
|
|
278
|
+
/** True once the 101 echoed `seatlayer.v1`. */
|
|
279
|
+
private v1;
|
|
280
|
+
/** Set when we offered v1 and the handshake came back without it — a proxy
|
|
281
|
+
* most likely stripped the header, so the next attempt selects the v1 frame
|
|
282
|
+
* format with the `?pv=1` marker instead (protocol doc §1). The marker
|
|
283
|
+
* selects a format and can never carry a credential or widen a scope. */
|
|
284
|
+
private useQueryMarker;
|
|
285
|
+
private hidden;
|
|
286
|
+
private closedSections;
|
|
287
|
+
constructor(options: BuyerRealtimeOptions);
|
|
288
|
+
/** Negotiated protocol, for tests and diagnostics. */
|
|
289
|
+
get protocol(): 'v1' | 'legacy' | null;
|
|
290
|
+
get snapshotVersion(): number | null;
|
|
291
|
+
start(): void;
|
|
292
|
+
/** Stop for good (destroy, or a revocation). Safe to call twice. */
|
|
293
|
+
stop(): void;
|
|
294
|
+
/** Restart after the host re-authorized a revoked buyer (`refreshAccess()`). */
|
|
295
|
+
restart(): void;
|
|
296
|
+
private connect;
|
|
297
|
+
private handleFrame;
|
|
298
|
+
/** The server answered our resume; cancel the fallback resync. */
|
|
299
|
+
private answered;
|
|
300
|
+
private reportIfAccessError;
|
|
301
|
+
/**
|
|
302
|
+
* Liveness is ping/pong, and only ping/pong. A socket that receives nothing
|
|
303
|
+
* for minutes is the normal, correct state for a narrowly-scoped buyer on a
|
|
304
|
+
* busy event (protocol doc §5), so quiet time never triggers a reconnect.
|
|
305
|
+
*/
|
|
306
|
+
private startKeepalive;
|
|
307
|
+
/**
|
|
308
|
+
* FULL jitter, not plain exponential backoff.
|
|
309
|
+
*
|
|
310
|
+
* A deterministic `2**attempt` schedule makes every browser that lost the same
|
|
311
|
+
* socket — a worker redeploy, a DO eviction, a flaky edge PoP — come back in
|
|
312
|
+
* the same millisecond, and an on-sale crowd reconnecting in lockstep is the
|
|
313
|
+
* thing that turns one blip into a self-sustaining thundering herd. Full
|
|
314
|
+
* jitter (`random() * ceiling`) spreads the same crowd across the whole
|
|
315
|
+
* window; the ceiling still doubles, so a persistent outage still backs off.
|
|
316
|
+
*
|
|
317
|
+
* `Math.random` is correct here: this is client code choosing a delay, not a
|
|
318
|
+
* Workflow step that has to replay deterministically.
|
|
319
|
+
*/
|
|
320
|
+
private scheduleReconnect;
|
|
321
|
+
private clearPongTimer;
|
|
322
|
+
private clearTimers;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* The slice of PickerController this module drives. Structural on purpose — it
|
|
326
|
+
* keeps this file free of an `@seatlayer/core` import, and every member here is
|
|
327
|
+
* public controller API, so nothing in the engine mirror has to change.
|
|
328
|
+
*/
|
|
329
|
+
interface PickerControllerLike {
|
|
330
|
+
idForLabel(label: string): string | undefined;
|
|
331
|
+
tableSelection(seatIdOrLabel: string): {
|
|
332
|
+
physicalSeatIds: string[];
|
|
333
|
+
} | null;
|
|
334
|
+
setStatus(ids: string[], status: 'free' | 'held' | 'booked' | 'not_for_sale'): void;
|
|
335
|
+
getStatus(id: string): string | undefined;
|
|
336
|
+
flashSeat(id: string, color?: string): void;
|
|
337
|
+
currentHold(): {
|
|
338
|
+
labels: string[];
|
|
339
|
+
} | null;
|
|
340
|
+
getSelection(): Array<{
|
|
341
|
+
id: string;
|
|
342
|
+
label: string;
|
|
343
|
+
}>;
|
|
344
|
+
deselect(ids: string[]): void;
|
|
345
|
+
refresh(): Promise<void>;
|
|
346
|
+
}
|
|
347
|
+
interface ControllerSinkOptions {
|
|
348
|
+
/** Pulse seats other buyers take, as the controller's own socket does. */
|
|
349
|
+
flashOnLiveChange?: boolean;
|
|
350
|
+
/** Selected-but-unheld units that stopped being selectable. */
|
|
351
|
+
onSelectedObjectUnavailable?: (labels: string[], reason: 'ineligible' | 'taken') => void;
|
|
352
|
+
/** Section availability changed and the chart itself needs rebuilding. */
|
|
353
|
+
onSections?: (hidden: string[], closed: string[]) => void;
|
|
354
|
+
onStatusChange?: () => void;
|
|
355
|
+
}
|
|
356
|
+
declare function createControllerSink(controller: PickerControllerLike, options?: ControllerSinkOptions): RealtimeSink;
|
|
357
|
+
|
|
188
358
|
/**
|
|
189
359
|
* Minimal client for the public embed surface of workers/api (the `/pub/*`
|
|
190
360
|
* routes). Deliberately self-contained — it does NOT reuse src/lib/api.ts,
|
|
@@ -217,7 +387,15 @@ declare class ApiError extends Error {
|
|
|
217
387
|
conflicts?: HoldConflict[];
|
|
218
388
|
/** Present when best-available 409s ('not_enough_together' | 'sold_out'). */
|
|
219
389
|
reason?: string;
|
|
220
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Seconds the server asked the caller to wait, off a 429's `Retry-After`.
|
|
392
|
+
*
|
|
393
|
+
* Present ONLY on a rate-limit error, and it is the server's number — never a
|
|
394
|
+
* guess. A widget that catches this can say "try again in N seconds" instead
|
|
395
|
+
* of rendering the blank map a swallowed 429 used to produce.
|
|
396
|
+
*/
|
|
397
|
+
retryAfterS?: number;
|
|
398
|
+
constructor(status: number, message: string, code?: string, conflicts?: HoldConflict[], reason?: string, retryAfterS?: number);
|
|
221
399
|
}
|
|
222
400
|
interface HoldResult {
|
|
223
401
|
holdId: string;
|
|
@@ -593,163 +771,6 @@ declare class SeatingChart {
|
|
|
593
771
|
destroy(): void;
|
|
594
772
|
}
|
|
595
773
|
|
|
596
|
-
/**
|
|
597
|
-
* BuyerRealtimeClient — the client half of `docs/realtime-protocol-2026-08-01.md`.
|
|
598
|
-
*
|
|
599
|
-
* Why this exists as a separate socket rather than inside PickerController:
|
|
600
|
-
* a private scope authenticates with a one-use **subscribe ticket** carried in
|
|
601
|
-
* `Sec-WebSocket-Protocol`, and a browser can only set that at construction —
|
|
602
|
-
* `new WebSocket(url, protocols)`. The controller's socket is built from a URL
|
|
603
|
-
* alone (`PickerTransport.socketUrl`), and a bearer must never travel in a URL
|
|
604
|
-
* because URLs are routinely logged. So an access-scoped picker asks the
|
|
605
|
-
* transport for an empty `socketUrl()` (the controller then skips its own
|
|
606
|
-
* connection entirely) and this client owns the wire instead.
|
|
607
|
-
*
|
|
608
|
-
* A tokenless public picker never reaches this file. It keeps the controller's
|
|
609
|
-
* original socket, offers no subprotocol, and therefore receives byte-for-byte
|
|
610
|
-
* the frames it received before this module existed (protocol doc §1).
|
|
611
|
-
*
|
|
612
|
-
* What it implements:
|
|
613
|
-
* - protocol negotiation: offer `seatlayer.v1`, believe the 101 echo, and fall
|
|
614
|
-
* back to legacy frame handling when the server (or a proxy) does not echo;
|
|
615
|
-
* - the ticket exchange, one mint per connection attempt, ticket in the
|
|
616
|
-
* subprotocol list and never in the URL;
|
|
617
|
-
* - compact `{default, exceptions}` snapshot reconstruction;
|
|
618
|
-
* - `sv.<n>` resume, handling BOTH outcomes (a `resumed` delta or a full
|
|
619
|
-
* snapshot) on every reconnect;
|
|
620
|
-
* - close code 4401 as a typed access-revoked state, never a reconnect loop;
|
|
621
|
-
* - liveness by ping/pong only. Silence is normal and carries no information
|
|
622
|
-
* (protocol doc §5) — a quiet socket is never treated as a dead one.
|
|
623
|
-
*/
|
|
624
|
-
|
|
625
|
-
/** The projected status of one unit, as the server words it on the wire. */
|
|
626
|
-
type WireStatus = string;
|
|
627
|
-
/** A scope's projection: one default plus the units that differ from it. */
|
|
628
|
-
interface Projection {
|
|
629
|
-
default: WireStatus;
|
|
630
|
-
exceptions: Record<string, WireStatus>;
|
|
631
|
-
}
|
|
632
|
-
interface StatusChange {
|
|
633
|
-
label: string;
|
|
634
|
-
status: WireStatus;
|
|
635
|
-
}
|
|
636
|
-
/** Where reconstructed inventory goes. Implemented over PickerController. */
|
|
637
|
-
interface RealtimeSink {
|
|
638
|
-
/** Apply a batch of label→status changes. Implementations must paint this as
|
|
639
|
-
* ONE pass, not one pass per label (motion system §4 rule 2). */
|
|
640
|
-
applyStatuses(changes: StatusChange[]): void;
|
|
641
|
-
/** Re-pull authoritative state over the scoped HTTP route. Used when a frame
|
|
642
|
-
* cannot be diffed against what we hold (first snapshot, or the scope's
|
|
643
|
-
* default itself changed, which redefines every unit we were never told
|
|
644
|
-
* about). */
|
|
645
|
-
resync(): void | Promise<void>;
|
|
646
|
-
/** Section availability changed (channel-agnostic; identical for every scope). */
|
|
647
|
-
onSections?(hidden: string[], closed: string[]): void;
|
|
648
|
-
/** Scope-projected presence counters. */
|
|
649
|
-
onPresence?(counts: {
|
|
650
|
-
shoppingSessions: number;
|
|
651
|
-
activeHolds: number;
|
|
652
|
-
}): void;
|
|
653
|
-
}
|
|
654
|
-
interface SubscribeTicket$1 {
|
|
655
|
-
ticket?: string;
|
|
656
|
-
/** Exactly what to hand `new WebSocket(url, protocols)`, per protocol doc §3. */
|
|
657
|
-
protocols?: string[];
|
|
658
|
-
}
|
|
659
|
-
interface BuyerRealtimeOptions {
|
|
660
|
-
/** The subscribe URL. Must never carry a credential — asserted below. */
|
|
661
|
-
url: string;
|
|
662
|
-
sink: RealtimeSink;
|
|
663
|
-
/** Mint a one-use ticket for THIS connection attempt. Returns null for the
|
|
664
|
-
* anonymous public case (no ticket needed). Throwing stops the client. */
|
|
665
|
-
mintTicket?: () => Promise<SubscribeTicket$1 | null>;
|
|
666
|
-
/** Typed access states. 4401 arrives here as `revoked`. */
|
|
667
|
-
onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
|
|
668
|
-
/** Test seam. Defaults to the global WebSocket. */
|
|
669
|
-
socketFactory?: (url: string, protocols: string[]) => WebSocket;
|
|
670
|
-
/** Test seam for the keepalive/backoff timers. */
|
|
671
|
-
now?: () => number;
|
|
672
|
-
}
|
|
673
|
-
declare class BuyerRealtimeClient {
|
|
674
|
-
private readonly opts;
|
|
675
|
-
private ws;
|
|
676
|
-
private stopped;
|
|
677
|
-
private attempt;
|
|
678
|
-
private reconnectTimer;
|
|
679
|
-
private pingTimer;
|
|
680
|
-
private pongTimer;
|
|
681
|
-
private resumeTimer;
|
|
682
|
-
/** Our model of this scope's projection. Null until the first snapshot. */
|
|
683
|
-
private projection;
|
|
684
|
-
/** Last `snapshotVersion` seen on any frame that carried one — the resume point. */
|
|
685
|
-
private version;
|
|
686
|
-
/** True once the 101 echoed `seatlayer.v1`. */
|
|
687
|
-
private v1;
|
|
688
|
-
/** Set when we offered v1 and the handshake came back without it — a proxy
|
|
689
|
-
* most likely stripped the header, so the next attempt selects the v1 frame
|
|
690
|
-
* format with the `?pv=1` marker instead (protocol doc §1). The marker
|
|
691
|
-
* selects a format and can never carry a credential or widen a scope. */
|
|
692
|
-
private useQueryMarker;
|
|
693
|
-
private hidden;
|
|
694
|
-
private closedSections;
|
|
695
|
-
constructor(options: BuyerRealtimeOptions);
|
|
696
|
-
/** Negotiated protocol, for tests and diagnostics. */
|
|
697
|
-
get protocol(): 'v1' | 'legacy' | null;
|
|
698
|
-
get snapshotVersion(): number | null;
|
|
699
|
-
start(): void;
|
|
700
|
-
/** Stop for good (destroy, or a revocation). Safe to call twice. */
|
|
701
|
-
stop(): void;
|
|
702
|
-
/** Restart after the host re-authorized a revoked buyer (`refreshAccess()`). */
|
|
703
|
-
restart(): void;
|
|
704
|
-
private connect;
|
|
705
|
-
private handleFrame;
|
|
706
|
-
/** The server answered our resume; cancel the fallback resync. */
|
|
707
|
-
private answered;
|
|
708
|
-
private reportIfAccessError;
|
|
709
|
-
/**
|
|
710
|
-
* Liveness is ping/pong, and only ping/pong. A socket that receives nothing
|
|
711
|
-
* for minutes is the normal, correct state for a narrowly-scoped buyer on a
|
|
712
|
-
* busy event (protocol doc §5), so quiet time never triggers a reconnect.
|
|
713
|
-
*/
|
|
714
|
-
private startKeepalive;
|
|
715
|
-
private scheduleReconnect;
|
|
716
|
-
private clearPongTimer;
|
|
717
|
-
private clearTimers;
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* The slice of PickerController this module drives. Structural on purpose — it
|
|
721
|
-
* keeps this file free of an `@seatlayer/core` import, and every member here is
|
|
722
|
-
* public controller API, so nothing in the engine mirror has to change.
|
|
723
|
-
*/
|
|
724
|
-
interface PickerControllerLike {
|
|
725
|
-
idForLabel(label: string): string | undefined;
|
|
726
|
-
tableSelection(seatIdOrLabel: string): {
|
|
727
|
-
physicalSeatIds: string[];
|
|
728
|
-
} | null;
|
|
729
|
-
setStatus(ids: string[], status: 'free' | 'held' | 'booked' | 'not_for_sale'): void;
|
|
730
|
-
getStatus(id: string): string | undefined;
|
|
731
|
-
flashSeat(id: string, color?: string): void;
|
|
732
|
-
currentHold(): {
|
|
733
|
-
labels: string[];
|
|
734
|
-
} | null;
|
|
735
|
-
getSelection(): Array<{
|
|
736
|
-
id: string;
|
|
737
|
-
label: string;
|
|
738
|
-
}>;
|
|
739
|
-
deselect(ids: string[]): void;
|
|
740
|
-
refresh(): Promise<void>;
|
|
741
|
-
}
|
|
742
|
-
interface ControllerSinkOptions {
|
|
743
|
-
/** Pulse seats other buyers take, as the controller's own socket does. */
|
|
744
|
-
flashOnLiveChange?: boolean;
|
|
745
|
-
/** Selected-but-unheld units that stopped being selectable. */
|
|
746
|
-
onSelectedObjectUnavailable?: (labels: string[], reason: 'ineligible' | 'taken') => void;
|
|
747
|
-
/** Section availability changed and the chart itself needs rebuilding. */
|
|
748
|
-
onSections?: (hidden: string[], closed: string[]) => void;
|
|
749
|
-
onStatusChange?: () => void;
|
|
750
|
-
}
|
|
751
|
-
declare function createControllerSink(controller: PickerControllerLike, options?: ControllerSinkOptions): RealtimeSink;
|
|
752
|
-
|
|
753
774
|
/**
|
|
754
775
|
* A secure, framework-neutral host for the SeatLayer chart Designer.
|
|
755
776
|
*
|
|
@@ -2946,13 +2967,39 @@ declare class SeatManager {
|
|
|
2946
2967
|
private labelToId;
|
|
2947
2968
|
private labelToSeat;
|
|
2948
2969
|
private allIds;
|
|
2970
|
+
/**
|
|
2971
|
+
* GA inventory units — real sellable labels the server counts, with NO seat
|
|
2972
|
+
* geometry and therefore no renderer binding. They live here rather than in
|
|
2973
|
+
* `labelToId`/`allIds` so every paint path keeps addressing paintable nodes
|
|
2974
|
+
* only, while the tally denominator finally covers the same universe the
|
|
2975
|
+
* numerator does. Without them a GA sale hit `booked` but not `total`:
|
|
2976
|
+
* Free under-reported by GA capacity and SOLD% could exceed 100%.
|
|
2977
|
+
*/
|
|
2978
|
+
private gaUnitLabelSet;
|
|
2949
2979
|
private status;
|
|
2980
|
+
/** Live non-free counters, moved by each delta rather than re-walked. */
|
|
2981
|
+
private counts;
|
|
2982
|
+
/** Bumped whenever the seat model is replaced wholesale (a full snapshot). */
|
|
2983
|
+
private modelVersion;
|
|
2950
2984
|
private currency;
|
|
2951
2985
|
private authoritativeGrossRevenue;
|
|
2952
2986
|
private revenueStatus;
|
|
2953
2987
|
private revenueRequest;
|
|
2954
|
-
private revenueRefreshTimer;
|
|
2955
2988
|
private controlRoomSnapshot;
|
|
2989
|
+
/**
|
|
2990
|
+
* The server's own totals, pinned to the client model they were read against.
|
|
2991
|
+
* Display = server baseline + (client now − client then), so the authoritative
|
|
2992
|
+
* numbers land exactly on arrival and deltas still move them between reads.
|
|
2993
|
+
* A wholesale model replacement invalidates the pairing (`model`), and the
|
|
2994
|
+
* client tallies — themselves a fresh authenticated read — take over.
|
|
2995
|
+
*/
|
|
2996
|
+
private serverBaseline;
|
|
2997
|
+
/** Latest presence frame, held whether or not a snapshot has landed yet. */
|
|
2998
|
+
private livePresence;
|
|
2999
|
+
/** Latest cumulative booked gross pushed on a delta frame. */
|
|
3000
|
+
private liveGross;
|
|
3001
|
+
/** Coalesces a burst of deltas into one KPI/rail repaint. */
|
|
3002
|
+
private paintHandle;
|
|
2956
3003
|
private trendWindowMinutes;
|
|
2957
3004
|
private heatEnabled;
|
|
2958
3005
|
private followLive;
|
|
@@ -3082,6 +3129,22 @@ declare class SeatManager {
|
|
|
3082
3129
|
private selectableStatuses;
|
|
3083
3130
|
private updateRendererInteraction;
|
|
3084
3131
|
private handleSeatSelect;
|
|
3132
|
+
/**
|
|
3133
|
+
* Build the client's inventory universe from the chart.
|
|
3134
|
+
*
|
|
3135
|
+
* `expandChart` yields SEATS — it has no output for a GA area, whose capacity
|
|
3136
|
+
* is sold as N synthetic unit labels. The server's seat map keys, its deltas
|
|
3137
|
+
* and its `totals` all speak those labels, so a client that only knows seats
|
|
3138
|
+
* counts GA sales in the numerator (every key of the snapshot is written into
|
|
3139
|
+
* `status`) while leaving them out of the denominator. Registering the GA
|
|
3140
|
+
* units here — labels only, never a render binding — is what makes the two
|
|
3141
|
+
* agree.
|
|
3142
|
+
*/
|
|
3143
|
+
private buildUnitUniverse;
|
|
3144
|
+
/** Every sellable unit the client knows: seats + GA capacity. */
|
|
3145
|
+
private unitTotal;
|
|
3146
|
+
/** Every label the client models, whether or not it can be painted. */
|
|
3147
|
+
private knownLabels;
|
|
3085
3148
|
private repaintAll;
|
|
3086
3149
|
/**
|
|
3087
3150
|
* Open the cockpit's realtime socket AS THE ORGANIZER.
|
|
@@ -3101,6 +3164,16 @@ declare class SeatManager {
|
|
|
3101
3164
|
private connect;
|
|
3102
3165
|
private scheduleReconnect;
|
|
3103
3166
|
private onMessage;
|
|
3167
|
+
/**
|
|
3168
|
+
* Adopt the cumulative booked gross a delta frame carried.
|
|
3169
|
+
*
|
|
3170
|
+
* Stashed with its arrival time so an in-flight control-room read can decide
|
|
3171
|
+
* whether it is holding the newer number: a frame that landed after the
|
|
3172
|
+
* request started is newer than the response, one that landed before is not.
|
|
3173
|
+
*/
|
|
3174
|
+
private applyLiveGross;
|
|
3175
|
+
/** The single writer for a label's status, so the counters never drift. */
|
|
3176
|
+
private setStatusLabel;
|
|
3104
3177
|
private resnapshot;
|
|
3105
3178
|
/**
|
|
3106
3179
|
* Replace the whole seat model.
|
|
@@ -3111,6 +3184,8 @@ declare class SeatManager {
|
|
|
3111
3184
|
* the mode really is free, wrong the moment it is not.
|
|
3112
3185
|
*/
|
|
3113
3186
|
private applySnapshot;
|
|
3187
|
+
/** The one O(n) walk left: a wholesale model replacement re-bases the counters. */
|
|
3188
|
+
private recountAll;
|
|
3114
3189
|
/** Optimistic local write shared by organizer actions. Paint and tally once,
|
|
3115
3190
|
* even when an arena-sized operation changes hundreds of seats. */
|
|
3116
3191
|
private setSeatsLocal;
|
|
@@ -3125,9 +3200,39 @@ declare class SeatManager {
|
|
|
3125
3200
|
private locateActivity;
|
|
3126
3201
|
private showLiveEvent;
|
|
3127
3202
|
private applyReportRevenue;
|
|
3203
|
+
/**
|
|
3204
|
+
* Read the server's own control-room projection.
|
|
3205
|
+
*
|
|
3206
|
+
* Called on mount, on every socket (re)connect and after an organizer action —
|
|
3207
|
+
* never on a timer and never per delta frame. Presence and gross that arrived
|
|
3208
|
+
* on the socket AFTER this request started are newer than the response, so
|
|
3209
|
+
* they survive it; anything older defers to the read.
|
|
3210
|
+
*/
|
|
3128
3211
|
private refreshControlRoom;
|
|
3129
|
-
|
|
3212
|
+
/** Pin the server's totals to the client model they were read against. */
|
|
3213
|
+
private rebaseServerTotals;
|
|
3214
|
+
/** What the client's own model says — GA units included since `render()`. */
|
|
3215
|
+
private clientTallies;
|
|
3216
|
+
/**
|
|
3217
|
+
* The numbers the KPI bar and rail render.
|
|
3218
|
+
*
|
|
3219
|
+
* The server is the authority: its totals land exactly as read, and the
|
|
3220
|
+
* delta-driven client model carries them forward until the next read. Before
|
|
3221
|
+
* the first snapshot — and after a wholesale model replacement invalidates the
|
|
3222
|
+
* pairing — the client model stands alone.
|
|
3223
|
+
*/
|
|
3224
|
+
private buildTallies;
|
|
3225
|
+
/**
|
|
3226
|
+
* Queue one KPI/rail repaint for this burst of changes.
|
|
3227
|
+
*
|
|
3228
|
+
* A delta frame can carry hundreds of seats and `paintKpis` rebuilds eight
|
|
3229
|
+
* nodes from scratch, so painting per change is what made an arena-sized
|
|
3230
|
+
* frame expensive. Coalescing on a frame keeps the burst to a single rebuild;
|
|
3231
|
+
* without `requestAnimationFrame` (SSR, an older test env) it paints inline
|
|
3232
|
+
* rather than dropping the update.
|
|
3233
|
+
*/
|
|
3130
3234
|
private recomputeTallies;
|
|
3235
|
+
private flushTallies;
|
|
3131
3236
|
private verbFor;
|
|
3132
3237
|
private pushActivity;
|
|
3133
3238
|
private seedFeed;
|
|
@@ -3150,6 +3255,9 @@ declare class SeatManager {
|
|
|
3150
3255
|
private paintKpis;
|
|
3151
3256
|
private paintRail;
|
|
3152
3257
|
private renderViewRail;
|
|
3258
|
+
/** Live presence wins over the snapshot's copy — it is the fresher channel,
|
|
3259
|
+
* and it exists from the first frame rather than the first fetch. */
|
|
3260
|
+
private presenceCounts;
|
|
3153
3261
|
private paintMonitorInsights;
|
|
3154
3262
|
private applyHeatOverlay;
|
|
3155
3263
|
private renderInspectRail;
|