@seatlayer/js 0.34.0 → 0.36.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 +2890 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1171 -5
- package/dist/index.d.ts +1171 -5
- package/dist/index.js +2868 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,190 @@
|
|
|
1
1
|
import { PickerSeat, RendererViewMode, SeatHoverDetails, PickerTransport, ChartDoc, AvailabilityRule, ChartTheme, ExpandedSeat } from '@seatlayer/core';
|
|
2
2
|
export { ExpandedSeat, SeatHoverDetails } from '@seatlayer/core';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Buyer access context — the browser half of the Sales Channels contract
|
|
6
|
+
* (`docs/sales-channels-integration-guide-2026-08-01.md` §6, §9, §10).
|
|
7
|
+
*
|
|
8
|
+
* A promoter's own backend mints a short-lived, opaque buyer-access session
|
|
9
|
+
* (`bse_…`) that grants exactly one event's private channel scope. That bearer
|
|
10
|
+
* reaches the browser, and only the browser — this module is where it lives,
|
|
11
|
+
* and the rules it enforces are the ones the guide states outright:
|
|
12
|
+
*
|
|
13
|
+
* - the token is held in memory on a private field. It is never written to
|
|
14
|
+
* localStorage/sessionStorage/cookies, never appended to a URL, and never
|
|
15
|
+
* placed in a log, an Error message, telemetry, or JSON. `toJSON()` and
|
|
16
|
+
* `toString()` are overridden so an accidental `JSON.stringify(context)` or
|
|
17
|
+
* template interpolation cannot leak it;
|
|
18
|
+
* - refresh goes through the host's `buyerAccessTokenProvider`, which is
|
|
19
|
+
* called with a `reason` so the host can distinguish a first acquisition
|
|
20
|
+
* from an expiry from a 401;
|
|
21
|
+
* - **a configured context never falls back to anonymous Public sale.** When
|
|
22
|
+
* no bearer can be obtained the operation fails with a typed access error
|
|
23
|
+
* instead of going out unauthenticated. Sending the request without the
|
|
24
|
+
* bearer would silently widen the buyer's scope to Public — the exact
|
|
25
|
+
* failure the feature exists to prevent (guide §7).
|
|
26
|
+
*
|
|
27
|
+
* Deliberately free of any `@seatlayer/core` import: it deals in tokens, HTTP
|
|
28
|
+
* status codes and callbacks only, so it vendors into the app's widget copy
|
|
29
|
+
* with no engine coupling.
|
|
30
|
+
*/
|
|
31
|
+
/** Why the SDK is asking the host for a token. Passed to the provider. */
|
|
32
|
+
type BuyerAccessRefreshReason =
|
|
33
|
+
/** First acquisition, before the chart is fetched. */
|
|
34
|
+
'initial'
|
|
35
|
+
/** Proactive: the held token is inside the renewal skew. */
|
|
36
|
+
| 'expiring'
|
|
37
|
+
/** Reactive: the held token's own expiry has passed. */
|
|
38
|
+
| 'expired'
|
|
39
|
+
/** Reactive: the server answered 401 `buyer_access_expired`. */
|
|
40
|
+
| 'unauthorized'
|
|
41
|
+
/** A realtime reconnect needs a live bearer to mint a subscribe ticket. */
|
|
42
|
+
| 'reconnect'
|
|
43
|
+
/** The host called `refreshAccess()`. */
|
|
44
|
+
| 'manual';
|
|
45
|
+
/** What a `buyerAccessTokenProvider` resolves to — the response body of the
|
|
46
|
+
* host's own mint endpoint, unchanged. */
|
|
47
|
+
interface BuyerAccessToken {
|
|
48
|
+
/** The opaque `bse_…` buyer-access session bearer. */
|
|
49
|
+
token: string;
|
|
50
|
+
/** Epoch ms. Optional — absent means "trust the server", and the SDK then
|
|
51
|
+
* refreshes only reactively on a 401. */
|
|
52
|
+
expiresAt?: number;
|
|
53
|
+
}
|
|
54
|
+
type BuyerAccessTokenProvider = (context: {
|
|
55
|
+
reason: BuyerAccessRefreshReason;
|
|
56
|
+
}) => BuyerAccessToken | Promise<BuyerAccessToken>;
|
|
57
|
+
/**
|
|
58
|
+
* Why private inventory is not available. Never collapsed into a generic
|
|
59
|
+
* network failure (guide §10) and never carrying channel identity — the buyer
|
|
60
|
+
* is told the state, not which allocation they missed.
|
|
61
|
+
*/
|
|
62
|
+
type BuyerAccessUnavailableReason =
|
|
63
|
+
/** The session was revoked (HTTP 401 after refresh, or WS close 4401). */
|
|
64
|
+
'revoked'
|
|
65
|
+
/** The channel is paused — a legitimate, temporary organizer state. */
|
|
66
|
+
| 'paused'
|
|
67
|
+
/** 401 `buyer_access_invalid`: do not retry this bearer. */
|
|
68
|
+
| 'invalid'
|
|
69
|
+
/** 403 `buyer_access_origin_mismatch`. */
|
|
70
|
+
| 'origin_mismatch'
|
|
71
|
+
/** 403 `buyer_access_event_mismatch`. */
|
|
72
|
+
| 'event_mismatch'
|
|
73
|
+
/** 403 `buyer_access_mode_mismatch` (test bearer on a live event or v.v.). */
|
|
74
|
+
| 'mode_mismatch'
|
|
75
|
+
/** 403 `channel_access_denied`. */
|
|
76
|
+
| 'channel_denied'
|
|
77
|
+
/** 422 `invalid_channel_scope` — an integration configuration error. */
|
|
78
|
+
| 'invalid_scope'
|
|
79
|
+
/** The host's token provider threw or returned nothing usable. */
|
|
80
|
+
| 'provider_failed'
|
|
81
|
+
/** A one-shot `buyerAccessToken` lapsed and no provider was configured. */
|
|
82
|
+
| 'no_token';
|
|
83
|
+
/** The access session expired. Carries whether the refresh recovered it. */
|
|
84
|
+
interface BuyerAccessExpiredEvent {
|
|
85
|
+
reason: BuyerAccessRefreshReason;
|
|
86
|
+
/** The server's machine code when the expiry was observed over HTTP. */
|
|
87
|
+
code?: string;
|
|
88
|
+
/** True when the provider handed back a fresh token and work continues. */
|
|
89
|
+
refreshed: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** Private inventory is unavailable, and refreshing will not fix it. */
|
|
92
|
+
interface BuyerAccessUnavailableEvent {
|
|
93
|
+
reason: BuyerAccessUnavailableReason;
|
|
94
|
+
/** The server's machine code, when there was one. */
|
|
95
|
+
code?: string;
|
|
96
|
+
/** The HTTP status, when the state came from an HTTP response. */
|
|
97
|
+
status?: number;
|
|
98
|
+
/** True only for states a later retry could clear (`paused`). */
|
|
99
|
+
retryable: boolean;
|
|
100
|
+
}
|
|
101
|
+
/** One or more selected-but-unheld units stopped being selectable. */
|
|
102
|
+
interface SelectedObjectUnavailableEvent {
|
|
103
|
+
/** Inventory labels (never channel identity). */
|
|
104
|
+
labels: string[];
|
|
105
|
+
reason:
|
|
106
|
+
/** An allocation change moved it out of this buyer's scope (guide §9). */
|
|
107
|
+
'ineligible'
|
|
108
|
+
/** Someone else held or booked it. */
|
|
109
|
+
| 'taken'
|
|
110
|
+
/** 409 `allocation_exhausted` — this private allocation has none left. */
|
|
111
|
+
| 'exhausted';
|
|
112
|
+
code?: string;
|
|
113
|
+
}
|
|
114
|
+
interface BuyerAccessContextOptions {
|
|
115
|
+
provider?: BuyerAccessTokenProvider;
|
|
116
|
+
/** One-shot escape hatch for hosts that already own the token lifecycle. */
|
|
117
|
+
token?: string | BuyerAccessToken;
|
|
118
|
+
/** Renew this long before the stated expiry. Default 30s. */
|
|
119
|
+
skewMs?: number;
|
|
120
|
+
onExpired?: (event: BuyerAccessExpiredEvent) => void;
|
|
121
|
+
onUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Thrown instead of letting a scoped request go out unauthenticated. Carries no
|
|
125
|
+
* bearer and no channel identity, so it is safe to log or hand to an error
|
|
126
|
+
* reporter verbatim.
|
|
127
|
+
*/
|
|
128
|
+
declare class BuyerAccessUnavailableError extends Error {
|
|
129
|
+
readonly reason: BuyerAccessUnavailableReason;
|
|
130
|
+
readonly code?: string;
|
|
131
|
+
readonly status?: number;
|
|
132
|
+
constructor(event: BuyerAccessUnavailableEvent);
|
|
133
|
+
}
|
|
134
|
+
declare class BuyerAccessContext {
|
|
135
|
+
#private;
|
|
136
|
+
constructor(options: BuyerAccessContextOptions);
|
|
137
|
+
/**
|
|
138
|
+
* True when this picker is access-scoped at all. A false here is the
|
|
139
|
+
* tokenless public picker, which must behave exactly as it always has.
|
|
140
|
+
*
|
|
141
|
+
* Answered from what the HOST asked for, never from live token state. It used
|
|
142
|
+
* to be `!!#provider || !!#token`, which quietly inverted this file's central
|
|
143
|
+
* rule for a one-shot `buyerAccessToken` host: `#fail()` clears `#token`, so
|
|
144
|
+
* the first refusal turned a configured context into an "unconfigured" one,
|
|
145
|
+
* `authorization()` then returned null instead of throwing, and the very next
|
|
146
|
+
* call went out with no bearer — the anonymous Public sale fallback this
|
|
147
|
+
* module exists to prevent. A provider host never saw it, because `#provider`
|
|
148
|
+
* held `configured` true. Found against a live worker in the M9 pass.
|
|
149
|
+
*/
|
|
150
|
+
get configured(): boolean;
|
|
151
|
+
/** Set once a state arrives that refreshing cannot clear. */
|
|
152
|
+
get unavailable(): BuyerAccessUnavailableEvent | null;
|
|
153
|
+
/** True while a usable bearer is held (ignores skew). */
|
|
154
|
+
get hasToken(): boolean;
|
|
155
|
+
/** Epoch ms the current token expires, or 0 when the host didn't say. */
|
|
156
|
+
get expiresAt(): number;
|
|
157
|
+
/**
|
|
158
|
+
* The `Authorization` header value for a scoped operation.
|
|
159
|
+
*
|
|
160
|
+
* Returns null only when this context is not configured at all (the ordinary
|
|
161
|
+
* anonymous public picker). A configured context either returns a bearer or
|
|
162
|
+
* throws `BuyerAccessUnavailableError` — it never returns null, because a
|
|
163
|
+
* null here would send the request as anonymous Public sale.
|
|
164
|
+
*/
|
|
165
|
+
authorization(reason?: BuyerAccessRefreshReason): Promise<string | null>;
|
|
166
|
+
/**
|
|
167
|
+
* Handle a 401/403 from a scoped call. Returns true when the caller should
|
|
168
|
+
* retry the same request once with the refreshed bearer.
|
|
169
|
+
*/
|
|
170
|
+
handleFailure(status: number, code: string | undefined): Promise<boolean>;
|
|
171
|
+
/** Host-driven re-acquisition (after the buyer signs in again, say). */
|
|
172
|
+
refresh(reason?: BuyerAccessRefreshReason): Promise<boolean>;
|
|
173
|
+
/** Drop the bearer. Called on destroy so nothing outlives the widget. */
|
|
174
|
+
clear(): void;
|
|
175
|
+
/** Redaction: the bearer must not survive a stringify or an interpolation. */
|
|
176
|
+
toJSON(): {
|
|
177
|
+
configured: boolean;
|
|
178
|
+
hasToken: boolean;
|
|
179
|
+
};
|
|
180
|
+
toString(): string;
|
|
181
|
+
}
|
|
182
|
+
/** Build a context from widget options, or null for the tokenless public path. */
|
|
183
|
+
declare function createBuyerAccessContext(options: {
|
|
184
|
+
buyerAccessTokenProvider?: BuyerAccessTokenProvider;
|
|
185
|
+
buyerAccessToken?: string | BuyerAccessToken;
|
|
186
|
+
}, hooks?: Pick<BuyerAccessContextOptions, 'onExpired' | 'onUnavailable'>): BuyerAccessContext | null;
|
|
187
|
+
|
|
4
188
|
/**
|
|
5
189
|
* Minimal client for the public embed surface of workers/api (the `/pub/*`
|
|
6
190
|
* routes). Deliberately self-contained — it does NOT reuse src/lib/api.ts,
|
|
@@ -55,6 +239,19 @@ interface BestAvailableResult {
|
|
|
55
239
|
items?: HoldResult['items'];
|
|
56
240
|
zoneId?: string;
|
|
57
241
|
}
|
|
242
|
+
interface PubApiOptions {
|
|
243
|
+
/**
|
|
244
|
+
* Buyer access session. When present, EVERY scoped operation on this client
|
|
245
|
+
* carries `Authorization: Bearer bse_…` — chart, objects, hold, replace-hold,
|
|
246
|
+
* best-available, resume, release, extend, resnapshot and the realtime
|
|
247
|
+
* subscribe ticket. The binding is immutable for the client's lifetime: there
|
|
248
|
+
* is no method that turns it off, so no operation can silently downgrade to
|
|
249
|
+
* anonymous Public sale (guide §6, §7).
|
|
250
|
+
*/
|
|
251
|
+
access?: BuyerAccessContext;
|
|
252
|
+
/** A 409 named specific inventory the buyer can no longer have. */
|
|
253
|
+
onObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
|
|
254
|
+
}
|
|
58
255
|
|
|
59
256
|
/**
|
|
60
257
|
* SeatingChart — the embeddable buyer picker.
|
|
@@ -92,8 +289,32 @@ interface SeatingChartOptions {
|
|
|
92
289
|
event: string;
|
|
93
290
|
/** API origin. Defaults to https://api.seatlayer.io. */
|
|
94
291
|
apiBase?: string;
|
|
95
|
-
/** Reserved for future authenticated rendering — accepted + stored, not yet sent.
|
|
292
|
+
/** Reserved for future authenticated rendering — accepted + stored, not yet sent.
|
|
293
|
+
* NOT the channel-access credential: a buyer access session is a different
|
|
294
|
+
* thing with different authority, and uses the two options below. */
|
|
96
295
|
publicKey?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Buyer access session provider — the recommended way to render private
|
|
298
|
+
* channel inventory (Sales Channels guide §6).
|
|
299
|
+
*
|
|
300
|
+
* Called with a `reason` whenever the SDK needs a bearer: first acquisition,
|
|
301
|
+
* a near/actual expiry, a 401 `buyer_access_expired`, a realtime reconnect,
|
|
302
|
+
* or `refreshAccess()`. It should POST to YOUR backend, which mints the
|
|
303
|
+
* session with your secret key and returns `{ token, expiresAt }`.
|
|
304
|
+
*
|
|
305
|
+
* The token lives in memory for the widget's lifetime and nowhere else: never
|
|
306
|
+
* in storage, never in a URL, never in a log or an error message. Refresh
|
|
307
|
+
* returns the same or a narrower scope — the SDK never widens to Public sale
|
|
308
|
+
* on its own, and a failed refresh stops the scoped operation rather than
|
|
309
|
+
* retrying it anonymously.
|
|
310
|
+
*/
|
|
311
|
+
buyerAccessTokenProvider?: BuyerAccessTokenProvider;
|
|
312
|
+
/**
|
|
313
|
+
* One-shot escape hatch for hosts that already own the session lifecycle.
|
|
314
|
+
* Cannot be renewed — when it lapses the widget reports `onAccessExpired`
|
|
315
|
+
* and then `onAccessUnavailable`. Prefer `buyerAccessTokenProvider`.
|
|
316
|
+
*/
|
|
317
|
+
buyerAccessToken?: string | BuyerAccessToken;
|
|
97
318
|
/** Max seats selectable at once (default 10). */
|
|
98
319
|
maxSelection?: number;
|
|
99
320
|
/**
|
|
@@ -138,6 +359,25 @@ interface SeatingChartOptions {
|
|
|
138
359
|
onHoldRestored?: (result: HoldResult) => void;
|
|
139
360
|
onHoldExpired?: () => void;
|
|
140
361
|
onGAClick?: (area: GAAreaAvailability) => void;
|
|
362
|
+
/**
|
|
363
|
+
* The buyer access session lapsed. `refreshed` says whether the provider
|
|
364
|
+
* already recovered it — false means private inventory is now unavailable and
|
|
365
|
+
* `onAccessUnavailable` follows. Distinct from `onError` on purpose: this is
|
|
366
|
+
* never a network failure (guide §10).
|
|
367
|
+
*/
|
|
368
|
+
onAccessExpired?: (event: BuyerAccessExpiredEvent) => void;
|
|
369
|
+
/**
|
|
370
|
+
* Private inventory is unavailable and refreshing will not fix it — revoked,
|
|
371
|
+
* paused, wrong origin/event/mode, or the provider failed. Carries a reason,
|
|
372
|
+
* never a channel name, id, colour or count.
|
|
373
|
+
*/
|
|
374
|
+
onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
|
|
375
|
+
/**
|
|
376
|
+
* Selected-but-unheld units stopped being selectable — someone else took
|
|
377
|
+
* them, or an allocation change moved them out of this buyer's scope. The
|
|
378
|
+
* widget has already dropped them from the selection.
|
|
379
|
+
*/
|
|
380
|
+
onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
|
|
141
381
|
onError?: (err: unknown) => void;
|
|
142
382
|
/**
|
|
143
383
|
* Multi-floor charts only: fires when the buyer taps a deck in the stacked
|
|
@@ -164,6 +404,10 @@ declare class SeatingChart {
|
|
|
164
404
|
private tipEl;
|
|
165
405
|
private tipPos;
|
|
166
406
|
private onTipMove;
|
|
407
|
+
/** Null for the ordinary public chart — the tokenless path is untouched. */
|
|
408
|
+
private readonly access;
|
|
409
|
+
private readonly api;
|
|
410
|
+
private realtime;
|
|
167
411
|
constructor(options: SeatingChartOptions);
|
|
168
412
|
/** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
|
|
169
413
|
render(): Promise<this>;
|
|
@@ -286,9 +530,180 @@ declare class SeatingChart {
|
|
|
286
530
|
/** Release selected labels from the current hold while keeping the remainder. */
|
|
287
531
|
releaseLabels(labels: string[]): Promise<boolean>;
|
|
288
532
|
/** Tear everything down: close the socket, stop timers, drop the canvas. */
|
|
533
|
+
/**
|
|
534
|
+
* Realtime for an access-scoped chart.
|
|
535
|
+
*
|
|
536
|
+
* A tokenless chart never gets here: `access` is null, `PubApi.socketUrl()`
|
|
537
|
+
* returns the URL it always has, and PickerController keeps its own socket
|
|
538
|
+
* and its own legacy frames. Nothing about the public path changes.
|
|
539
|
+
*/
|
|
540
|
+
private startRealtime;
|
|
541
|
+
/**
|
|
542
|
+
* Re-acquire the buyer access session — call after your app has re-authorized
|
|
543
|
+
* the buyer (a revoked session cannot be recovered any other way). Resolves
|
|
544
|
+
* true when a fresh bearer is held; the realtime feed restarts with it.
|
|
545
|
+
*/
|
|
546
|
+
refreshAccess(): Promise<boolean>;
|
|
289
547
|
destroy(): void;
|
|
290
548
|
}
|
|
291
549
|
|
|
550
|
+
/**
|
|
551
|
+
* BuyerRealtimeClient — the client half of `docs/realtime-protocol-2026-08-01.md`.
|
|
552
|
+
*
|
|
553
|
+
* Why this exists as a separate socket rather than inside PickerController:
|
|
554
|
+
* a private scope authenticates with a one-use **subscribe ticket** carried in
|
|
555
|
+
* `Sec-WebSocket-Protocol`, and a browser can only set that at construction —
|
|
556
|
+
* `new WebSocket(url, protocols)`. The controller's socket is built from a URL
|
|
557
|
+
* alone (`PickerTransport.socketUrl`), and a bearer must never travel in a URL
|
|
558
|
+
* because URLs are routinely logged. So an access-scoped picker asks the
|
|
559
|
+
* transport for an empty `socketUrl()` (the controller then skips its own
|
|
560
|
+
* connection entirely) and this client owns the wire instead.
|
|
561
|
+
*
|
|
562
|
+
* A tokenless public picker never reaches this file. It keeps the controller's
|
|
563
|
+
* original socket, offers no subprotocol, and therefore receives byte-for-byte
|
|
564
|
+
* the frames it received before this module existed (protocol doc §1).
|
|
565
|
+
*
|
|
566
|
+
* What it implements:
|
|
567
|
+
* - protocol negotiation: offer `seatlayer.v1`, believe the 101 echo, and fall
|
|
568
|
+
* back to legacy frame handling when the server (or a proxy) does not echo;
|
|
569
|
+
* - the ticket exchange, one mint per connection attempt, ticket in the
|
|
570
|
+
* subprotocol list and never in the URL;
|
|
571
|
+
* - compact `{default, exceptions}` snapshot reconstruction;
|
|
572
|
+
* - `sv.<n>` resume, handling BOTH outcomes (a `resumed` delta or a full
|
|
573
|
+
* snapshot) on every reconnect;
|
|
574
|
+
* - close code 4401 as a typed access-revoked state, never a reconnect loop;
|
|
575
|
+
* - liveness by ping/pong only. Silence is normal and carries no information
|
|
576
|
+
* (protocol doc §5) — a quiet socket is never treated as a dead one.
|
|
577
|
+
*/
|
|
578
|
+
|
|
579
|
+
/** The projected status of one unit, as the server words it on the wire. */
|
|
580
|
+
type WireStatus = string;
|
|
581
|
+
/** A scope's projection: one default plus the units that differ from it. */
|
|
582
|
+
interface Projection {
|
|
583
|
+
default: WireStatus;
|
|
584
|
+
exceptions: Record<string, WireStatus>;
|
|
585
|
+
}
|
|
586
|
+
interface StatusChange {
|
|
587
|
+
label: string;
|
|
588
|
+
status: WireStatus;
|
|
589
|
+
}
|
|
590
|
+
/** Where reconstructed inventory goes. Implemented over PickerController. */
|
|
591
|
+
interface RealtimeSink {
|
|
592
|
+
/** Apply a batch of label→status changes. Implementations must paint this as
|
|
593
|
+
* ONE pass, not one pass per label (motion system §4 rule 2). */
|
|
594
|
+
applyStatuses(changes: StatusChange[]): void;
|
|
595
|
+
/** Re-pull authoritative state over the scoped HTTP route. Used when a frame
|
|
596
|
+
* cannot be diffed against what we hold (first snapshot, or the scope's
|
|
597
|
+
* default itself changed, which redefines every unit we were never told
|
|
598
|
+
* about). */
|
|
599
|
+
resync(): void | Promise<void>;
|
|
600
|
+
/** Section availability changed (channel-agnostic; identical for every scope). */
|
|
601
|
+
onSections?(hidden: string[], closed: string[]): void;
|
|
602
|
+
/** Scope-projected presence counters. */
|
|
603
|
+
onPresence?(counts: {
|
|
604
|
+
shoppingSessions: number;
|
|
605
|
+
activeHolds: number;
|
|
606
|
+
}): void;
|
|
607
|
+
}
|
|
608
|
+
interface SubscribeTicket {
|
|
609
|
+
ticket?: string;
|
|
610
|
+
/** Exactly what to hand `new WebSocket(url, protocols)`, per protocol doc §3. */
|
|
611
|
+
protocols?: string[];
|
|
612
|
+
}
|
|
613
|
+
interface BuyerRealtimeOptions {
|
|
614
|
+
/** The subscribe URL. Must never carry a credential — asserted below. */
|
|
615
|
+
url: string;
|
|
616
|
+
sink: RealtimeSink;
|
|
617
|
+
/** Mint a one-use ticket for THIS connection attempt. Returns null for the
|
|
618
|
+
* anonymous public case (no ticket needed). Throwing stops the client. */
|
|
619
|
+
mintTicket?: () => Promise<SubscribeTicket | null>;
|
|
620
|
+
/** Typed access states. 4401 arrives here as `revoked`. */
|
|
621
|
+
onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
|
|
622
|
+
/** Test seam. Defaults to the global WebSocket. */
|
|
623
|
+
socketFactory?: (url: string, protocols: string[]) => WebSocket;
|
|
624
|
+
/** Test seam for the keepalive/backoff timers. */
|
|
625
|
+
now?: () => number;
|
|
626
|
+
}
|
|
627
|
+
declare class BuyerRealtimeClient {
|
|
628
|
+
private readonly opts;
|
|
629
|
+
private ws;
|
|
630
|
+
private stopped;
|
|
631
|
+
private attempt;
|
|
632
|
+
private reconnectTimer;
|
|
633
|
+
private pingTimer;
|
|
634
|
+
private pongTimer;
|
|
635
|
+
private resumeTimer;
|
|
636
|
+
/** Our model of this scope's projection. Null until the first snapshot. */
|
|
637
|
+
private projection;
|
|
638
|
+
/** Last `snapshotVersion` seen on any frame that carried one — the resume point. */
|
|
639
|
+
private version;
|
|
640
|
+
/** True once the 101 echoed `seatlayer.v1`. */
|
|
641
|
+
private v1;
|
|
642
|
+
/** Set when we offered v1 and the handshake came back without it — a proxy
|
|
643
|
+
* most likely stripped the header, so the next attempt selects the v1 frame
|
|
644
|
+
* format with the `?pv=1` marker instead (protocol doc §1). The marker
|
|
645
|
+
* selects a format and can never carry a credential or widen a scope. */
|
|
646
|
+
private useQueryMarker;
|
|
647
|
+
private hidden;
|
|
648
|
+
private closedSections;
|
|
649
|
+
constructor(options: BuyerRealtimeOptions);
|
|
650
|
+
/** Negotiated protocol, for tests and diagnostics. */
|
|
651
|
+
get protocol(): 'v1' | 'legacy' | null;
|
|
652
|
+
get snapshotVersion(): number | null;
|
|
653
|
+
start(): void;
|
|
654
|
+
/** Stop for good (destroy, or a revocation). Safe to call twice. */
|
|
655
|
+
stop(): void;
|
|
656
|
+
/** Restart after the host re-authorized a revoked buyer (`refreshAccess()`). */
|
|
657
|
+
restart(): void;
|
|
658
|
+
private connect;
|
|
659
|
+
private handleFrame;
|
|
660
|
+
/** The server answered our resume; cancel the fallback resync. */
|
|
661
|
+
private answered;
|
|
662
|
+
private reportIfAccessError;
|
|
663
|
+
/**
|
|
664
|
+
* Liveness is ping/pong, and only ping/pong. A socket that receives nothing
|
|
665
|
+
* for minutes is the normal, correct state for a narrowly-scoped buyer on a
|
|
666
|
+
* busy event (protocol doc §5), so quiet time never triggers a reconnect.
|
|
667
|
+
*/
|
|
668
|
+
private startKeepalive;
|
|
669
|
+
private scheduleReconnect;
|
|
670
|
+
private clearPongTimer;
|
|
671
|
+
private clearTimers;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* The slice of PickerController this module drives. Structural on purpose — it
|
|
675
|
+
* keeps this file free of an `@seatlayer/core` import, and every member here is
|
|
676
|
+
* public controller API, so nothing in the engine mirror has to change.
|
|
677
|
+
*/
|
|
678
|
+
interface PickerControllerLike {
|
|
679
|
+
idForLabel(label: string): string | undefined;
|
|
680
|
+
tableSelection(seatIdOrLabel: string): {
|
|
681
|
+
physicalSeatIds: string[];
|
|
682
|
+
} | null;
|
|
683
|
+
setStatus(ids: string[], status: 'free' | 'held' | 'booked' | 'not_for_sale'): void;
|
|
684
|
+
getStatus(id: string): string | undefined;
|
|
685
|
+
flashSeat(id: string, color?: string): void;
|
|
686
|
+
currentHold(): {
|
|
687
|
+
labels: string[];
|
|
688
|
+
} | null;
|
|
689
|
+
getSelection(): Array<{
|
|
690
|
+
id: string;
|
|
691
|
+
label: string;
|
|
692
|
+
}>;
|
|
693
|
+
deselect(ids: string[]): void;
|
|
694
|
+
refresh(): Promise<void>;
|
|
695
|
+
}
|
|
696
|
+
interface ControllerSinkOptions {
|
|
697
|
+
/** Pulse seats other buyers take, as the controller's own socket does. */
|
|
698
|
+
flashOnLiveChange?: boolean;
|
|
699
|
+
/** Selected-but-unheld units that stopped being selectable. */
|
|
700
|
+
onSelectedObjectUnavailable?: (labels: string[], reason: 'ineligible' | 'taken') => void;
|
|
701
|
+
/** Section availability changed and the chart itself needs rebuilding. */
|
|
702
|
+
onSections?: (hidden: string[], closed: string[]) => void;
|
|
703
|
+
onStatusChange?: () => void;
|
|
704
|
+
}
|
|
705
|
+
declare function createControllerSink(controller: PickerControllerLike, options?: ControllerSinkOptions): RealtimeSink;
|
|
706
|
+
|
|
292
707
|
/**
|
|
293
708
|
* A secure, framework-neutral host for the SeatLayer chart Designer.
|
|
294
709
|
*
|
|
@@ -653,8 +1068,37 @@ interface SeatPickerOptions {
|
|
|
653
1068
|
* SeatLayer dashboard's own transport) or a fully local mock (demos).
|
|
654
1069
|
*/
|
|
655
1070
|
transport?: PickerTransport;
|
|
656
|
-
/** Reserved for future authenticated rendering.
|
|
1071
|
+
/** Reserved for future authenticated rendering. NOT the channel-access
|
|
1072
|
+
* credential — a buyer access session is a different thing with different
|
|
1073
|
+
* authority, and uses the two options below. */
|
|
657
1074
|
publicKey?: string;
|
|
1075
|
+
/**
|
|
1076
|
+
* Buyer access session provider — the recommended way to show private channel
|
|
1077
|
+
* inventory (Sales Channels guide §6).
|
|
1078
|
+
*
|
|
1079
|
+
* Called with a `reason` whenever the widget needs a bearer: first
|
|
1080
|
+
* acquisition, a near/actual expiry, a 401 `buyer_access_expired`, a realtime
|
|
1081
|
+
* reconnect, or `refreshAccess()`. It should POST to YOUR backend, which
|
|
1082
|
+
* mints the session with your secret key and returns `{ token, expiresAt }`.
|
|
1083
|
+
*
|
|
1084
|
+
* The token lives in memory for the widget's lifetime and nowhere else: never
|
|
1085
|
+
* in storage, never in a URL, never in a log or an error message. Refresh
|
|
1086
|
+
* returns the same or a narrower scope; the widget never widens to Public
|
|
1087
|
+
* sale on its own, and a failed refresh stops the scoped operation rather
|
|
1088
|
+
* than retrying it anonymously. Any held seats stay held — a hold is
|
|
1089
|
+
* relinquished by its own opaque capability, not by channel access, so
|
|
1090
|
+
* losing access never strands inventory (guide §9).
|
|
1091
|
+
*
|
|
1092
|
+
* Ignored when a custom `transport` is supplied: that host owns its own
|
|
1093
|
+
* credentials.
|
|
1094
|
+
*/
|
|
1095
|
+
buyerAccessTokenProvider?: BuyerAccessTokenProvider;
|
|
1096
|
+
/**
|
|
1097
|
+
* One-shot escape hatch for hosts that already own the session lifecycle.
|
|
1098
|
+
* Cannot be renewed — when it lapses the widget reports `onAccessExpired`
|
|
1099
|
+
* and then `onAccessUnavailable`. Prefer `buyerAccessTokenProvider`.
|
|
1100
|
+
*/
|
|
1101
|
+
buyerAccessToken?: string | BuyerAccessToken;
|
|
658
1102
|
/** Max seats selectable at once (default 10). */
|
|
659
1103
|
maxSelection?: number;
|
|
660
1104
|
/** BCP 47 language for the widget UI. Built-in: en, es, de, fr. */
|
|
@@ -767,11 +1211,37 @@ interface SeatPickerOptions {
|
|
|
767
1211
|
onHoldRestored?: (hold: HoldResult, seats: PickerSeat[], handoff: CheckoutHandoff) => void;
|
|
768
1212
|
/** Modal only: the buyer closed the picker (ESC / scrim / ✕). */
|
|
769
1213
|
onClose?: () => void;
|
|
1214
|
+
/**
|
|
1215
|
+
* The buyer access session lapsed. `refreshed` says whether the provider
|
|
1216
|
+
* already recovered it — false means private inventory is now unavailable and
|
|
1217
|
+
* `onAccessUnavailable` follows. Never collapsed into `onError`: an expiry is
|
|
1218
|
+
* a recoverable, buyer-explainable state, not a network failure (guide §10).
|
|
1219
|
+
*/
|
|
1220
|
+
onAccessExpired?: (event: BuyerAccessExpiredEvent) => void;
|
|
1221
|
+
/**
|
|
1222
|
+
* Private inventory is unavailable and refreshing will not fix it — revoked,
|
|
1223
|
+
* paused, wrong origin/event/mode, or the provider failed. Carries a reason,
|
|
1224
|
+
* never a channel name, id, colour or count. The widget shows its own
|
|
1225
|
+
* explanatory panel; return nothing to keep it, or handle the state yourself.
|
|
1226
|
+
*/
|
|
1227
|
+
onAccessUnavailable?: (event: BuyerAccessUnavailableEvent) => void;
|
|
1228
|
+
/**
|
|
1229
|
+
* Selected-but-unheld units stopped being selectable — someone else took
|
|
1230
|
+
* them, or an allocation change moved them out of this buyer's scope. The
|
|
1231
|
+
* widget has already dropped them from the tray.
|
|
1232
|
+
*/
|
|
1233
|
+
onSelectedObjectUnavailable?: (event: SelectedObjectUnavailableEvent) => void;
|
|
770
1234
|
onError?: (err: unknown) => void;
|
|
771
1235
|
}
|
|
772
1236
|
declare class SeatPicker {
|
|
773
1237
|
private readonly opts;
|
|
774
1238
|
private readonly api;
|
|
1239
|
+
/** Our own public client, or null when the host injected a transport. */
|
|
1240
|
+
private readonly pubApi;
|
|
1241
|
+
/** Null for the ordinary public picker — the tokenless path is untouched. */
|
|
1242
|
+
private readonly access;
|
|
1243
|
+
private realtime;
|
|
1244
|
+
private accessEl;
|
|
775
1245
|
private readonly apiBase;
|
|
776
1246
|
private readonly controller;
|
|
777
1247
|
private readonly maxTickets;
|
|
@@ -1277,6 +1747,32 @@ declare class SeatPicker {
|
|
|
1277
1747
|
zoneId?: string;
|
|
1278
1748
|
}): Promise<HoldResult | null>;
|
|
1279
1749
|
release(): Promise<void>;
|
|
1750
|
+
/**
|
|
1751
|
+
* Realtime for an access-scoped picker.
|
|
1752
|
+
*
|
|
1753
|
+
* A tokenless picker never gets here: `access` is null, `PubApi.socketUrl()`
|
|
1754
|
+
* returns the URL it always has, and PickerController keeps its own socket
|
|
1755
|
+
* and its own legacy frames. Nothing about the public path changes.
|
|
1756
|
+
*/
|
|
1757
|
+
private startRealtime;
|
|
1758
|
+
/**
|
|
1759
|
+
* Re-acquire the buyer access session — call after your app has re-authorized
|
|
1760
|
+
* the buyer. A revoked session cannot recover any other way. Resolves true
|
|
1761
|
+
* when a fresh bearer is held; the map and the realtime feed resume with it.
|
|
1762
|
+
*/
|
|
1763
|
+
refreshAccess(): Promise<boolean>;
|
|
1764
|
+
/**
|
|
1765
|
+
* The buyer-facing access state. Plain language, no internal vocabulary, and
|
|
1766
|
+
* never a channel name, id or count — the buyer is told what happened and
|
|
1767
|
+
* what to do, not which allocation they missed (guide §7, §10).
|
|
1768
|
+
*
|
|
1769
|
+
* Held seats are deliberately left alone: a hold is relinquished by its own
|
|
1770
|
+
* opaque capability, not by channel access, so losing access never strands
|
|
1771
|
+
* inventory and never silently drops a buyer's cart (guide §9).
|
|
1772
|
+
*/
|
|
1773
|
+
private showAccessPanel;
|
|
1774
|
+
private dismissAccessPanel;
|
|
1775
|
+
private accessCopy;
|
|
1280
1776
|
destroy(): void;
|
|
1281
1777
|
}
|
|
1282
1778
|
|
|
@@ -1319,6 +1815,214 @@ interface AttachPickerFrameOptions {
|
|
|
1319
1815
|
*/
|
|
1320
1816
|
declare function attachPickerFrame(iframe: HTMLIFrameElement, opts?: AttachPickerFrameOptions): () => void;
|
|
1321
1817
|
|
|
1818
|
+
/**
|
|
1819
|
+
* Sales-channel planning — the pure, DOM-free half of Channels mode.
|
|
1820
|
+
*
|
|
1821
|
+
* Everything here is deterministic and testable without a canvas: the marker
|
|
1822
|
+
* palette, the mixed-source selection summary, the LOCAL staged preview of an
|
|
1823
|
+
* assignment, and the bucket rows the Review sheet renders.
|
|
1824
|
+
*
|
|
1825
|
+
* The local preview deliberately produces the SAME `AssignmentBuckets` shape the
|
|
1826
|
+
* server returns from `POST /channels/assignments`. There is no dry-run endpoint,
|
|
1827
|
+
* so the review sheet is drawn from this local computation and then REDRAWN from
|
|
1828
|
+
* the authoritative server response after Apply. One renderer, two sources —
|
|
1829
|
+
* which is why the shapes must match exactly.
|
|
1830
|
+
*
|
|
1831
|
+
* Spec: sales-channels-product-ux-spec §8.4–8.5.
|
|
1832
|
+
*/
|
|
1833
|
+
/** Public sale is a built-in pseudo-channel; the server uses '' as its id. */
|
|
1834
|
+
declare const PUBLIC_CHANNEL_ID = "";
|
|
1835
|
+
declare const PUBLIC_CHANNEL_NAME = "Public sale";
|
|
1836
|
+
type ChannelState = 'active' | 'paused' | 'archived';
|
|
1837
|
+
/** Physical inventory status, as the manage surface speaks it. */
|
|
1838
|
+
type ChannelSeatStatus = 'free' | 'held' | 'booked' | 'blocked';
|
|
1839
|
+
interface ChannelCounts {
|
|
1840
|
+
allocated: number;
|
|
1841
|
+
free: number;
|
|
1842
|
+
held: number;
|
|
1843
|
+
booked: number;
|
|
1844
|
+
blocked: number;
|
|
1845
|
+
units: number;
|
|
1846
|
+
}
|
|
1847
|
+
/** Buyer-access intents the server stores per channel. */
|
|
1848
|
+
type ChannelAccessIntent = 'none' | 'internal' | 'server' | 'hosted_link';
|
|
1849
|
+
/**
|
|
1850
|
+
* Buyer-access summary on a channel row. Shipped by the access hardening branch
|
|
1851
|
+
* (merged to app main). Still optional in this type: a worker that predates the
|
|
1852
|
+
* merge simply omits it and the rail reads "—" rather than inventing a state.
|
|
1853
|
+
*/
|
|
1854
|
+
interface ChannelAccessSummary {
|
|
1855
|
+
intent?: ChannelAccessIntent | string;
|
|
1856
|
+
hasActiveGrants?: boolean;
|
|
1857
|
+
lastMintAt?: number | null;
|
|
1858
|
+
/** Free-text detail (partner host, who paused it) when the server offers one. */
|
|
1859
|
+
detail?: string | null;
|
|
1860
|
+
}
|
|
1861
|
+
interface ChannelRecord {
|
|
1862
|
+
id: string;
|
|
1863
|
+
name: string;
|
|
1864
|
+
color: string | null;
|
|
1865
|
+
marker: string | null;
|
|
1866
|
+
externalRef: string | null;
|
|
1867
|
+
state: ChannelState;
|
|
1868
|
+
archiveDestination: string | null;
|
|
1869
|
+
createdAt: number;
|
|
1870
|
+
updatedAt: number;
|
|
1871
|
+
archivedAt: number | null;
|
|
1872
|
+
counts: ChannelCounts;
|
|
1873
|
+
access?: ChannelAccessSummary | null;
|
|
1874
|
+
}
|
|
1875
|
+
interface PublicSaleChannel {
|
|
1876
|
+
id: typeof PUBLIC_CHANNEL_ID;
|
|
1877
|
+
name: string;
|
|
1878
|
+
state: 'active';
|
|
1879
|
+
counts: ChannelCounts;
|
|
1880
|
+
access?: ChannelAccessSummary | null;
|
|
1881
|
+
}
|
|
1882
|
+
interface ChannelListResult {
|
|
1883
|
+
assignmentVersion: number;
|
|
1884
|
+
publicSale: PublicSaleChannel;
|
|
1885
|
+
channels: ChannelRecord[];
|
|
1886
|
+
}
|
|
1887
|
+
interface AssignmentBucketCount {
|
|
1888
|
+
count: number;
|
|
1889
|
+
}
|
|
1890
|
+
interface AssignmentSkippedBucket extends AssignmentBucketCount {
|
|
1891
|
+
labels: string[];
|
|
1892
|
+
truncated: boolean;
|
|
1893
|
+
}
|
|
1894
|
+
interface AssignmentBuckets {
|
|
1895
|
+
changedFromPublic: AssignmentBucketCount;
|
|
1896
|
+
movedFromOtherChannel: AssignmentBucketCount & {
|
|
1897
|
+
channels: Array<{
|
|
1898
|
+
channelId: string;
|
|
1899
|
+
name: string | null;
|
|
1900
|
+
count: number;
|
|
1901
|
+
}>;
|
|
1902
|
+
};
|
|
1903
|
+
alreadyInTarget: AssignmentBucketCount;
|
|
1904
|
+
skippedHeld: AssignmentSkippedBucket;
|
|
1905
|
+
skippedBooked: AssignmentSkippedBucket;
|
|
1906
|
+
/** Requested labels that are not inventory in this event. */
|
|
1907
|
+
notFound: AssignmentSkippedBucket;
|
|
1908
|
+
}
|
|
1909
|
+
interface AssignmentResult {
|
|
1910
|
+
ok: true;
|
|
1911
|
+
targetChannelId: string;
|
|
1912
|
+
assignmentVersion: number;
|
|
1913
|
+
requested: number;
|
|
1914
|
+
applied: number;
|
|
1915
|
+
buckets: AssignmentBuckets;
|
|
1916
|
+
}
|
|
1917
|
+
interface ArchiveBlockedDetails {
|
|
1918
|
+
activeHolds?: number;
|
|
1919
|
+
heldUnits?: number;
|
|
1920
|
+
latestHoldExpiresAt?: number | null;
|
|
1921
|
+
retryAfterMs?: number;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Suggest a marker for a new channel: the first letter of its name when that
|
|
1925
|
+
* letter is still free, otherwise the next unused letter. Deterministic so the
|
|
1926
|
+
* Create dialog's preview matches what actually gets stored.
|
|
1927
|
+
*/
|
|
1928
|
+
declare function suggestMarker(name: string, taken: Iterable<string>): {
|
|
1929
|
+
letter: string;
|
|
1930
|
+
color: string;
|
|
1931
|
+
};
|
|
1932
|
+
/** The letter + color a channel actually renders with (server value wins). */
|
|
1933
|
+
declare function markerOf(channel: {
|
|
1934
|
+
id: string;
|
|
1935
|
+
name: string;
|
|
1936
|
+
marker?: string | null;
|
|
1937
|
+
color?: string | null;
|
|
1938
|
+
}, index?: number): {
|
|
1939
|
+
letter: string;
|
|
1940
|
+
color: string;
|
|
1941
|
+
};
|
|
1942
|
+
/** One line of the rail's mixed-source selection summary (§8.4). */
|
|
1943
|
+
interface SelectionSourceRow {
|
|
1944
|
+
channelId: string;
|
|
1945
|
+
name: string;
|
|
1946
|
+
count: number;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Group the current selection by the channel each unit is allocated to today.
|
|
1950
|
+
* Public sale is listed first; the rest follow in list order so the rail's
|
|
1951
|
+
* ordering never jitters as the selection changes.
|
|
1952
|
+
*/
|
|
1953
|
+
declare function selectionSources(labels: string[], allocation: Map<string, string>, list: ChannelListResult | null): SelectionSourceRow[];
|
|
1954
|
+
/**
|
|
1955
|
+
* The LOCAL staged preview of "move these labels to this channel".
|
|
1956
|
+
*
|
|
1957
|
+
* Mirrors the DO's rules exactly (eventChannels.applyAssignment):
|
|
1958
|
+
* - a unit already in the target is `alreadyInTarget`, whatever its status;
|
|
1959
|
+
* - otherwise held and booked units are skipped and never rewritten;
|
|
1960
|
+
* - otherwise a public unit is `changedFromPublic`, a private one is
|
|
1961
|
+
* `movedFromOtherChannel` (itemised per source);
|
|
1962
|
+
* - a label that is not inventory in this event is `notFound`.
|
|
1963
|
+
*
|
|
1964
|
+
* Every requested label lands in exactly one bucket — the property the Review
|
|
1965
|
+
* sheet's "every selected seat is in exactly one line" promise depends on.
|
|
1966
|
+
*/
|
|
1967
|
+
declare function planAssignment(input: {
|
|
1968
|
+
labels: string[];
|
|
1969
|
+
targetChannelId: string;
|
|
1970
|
+
allocation: Map<string, string>;
|
|
1971
|
+
statusOf: (label: string) => ChannelSeatStatus | undefined;
|
|
1972
|
+
nameOf: (channelId: string) => string | null;
|
|
1973
|
+
}): AssignmentBuckets;
|
|
1974
|
+
/** Units this plan will actually mutate — what the Apply button counts. */
|
|
1975
|
+
declare function mutationCount(buckets: AssignmentBuckets): number;
|
|
1976
|
+
/** True when moving inventory out of another PRIVATE channel — §8.5 requires an
|
|
1977
|
+
* explicit confirmation line for exactly this case. */
|
|
1978
|
+
declare function needsMoveConfirmation(buckets: AssignmentBuckets): boolean;
|
|
1979
|
+
interface BucketRow {
|
|
1980
|
+
kind: 'add' | 'move' | 'same' | 'skip';
|
|
1981
|
+
icon: string;
|
|
1982
|
+
count: number;
|
|
1983
|
+
text: string;
|
|
1984
|
+
why?: string;
|
|
1985
|
+
/** Sampled seat labels for a skipped bucket, when the server sent any. */
|
|
1986
|
+
peek?: string;
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Render-ready rows for the Review sheet. The comp shows five lines; the server
|
|
1990
|
+
* carries a sixth bucket (`notFound`) which is emitted only when it is non-zero,
|
|
1991
|
+
* so a normal review still reads exactly like the approved design.
|
|
1992
|
+
*
|
|
1993
|
+
* Empty buckets are dropped — a zero line is noise, not honesty.
|
|
1994
|
+
*/
|
|
1995
|
+
declare function bucketRows(buckets: AssignmentBuckets, targetName: string): BucketRow[];
|
|
1996
|
+
/**
|
|
1997
|
+
* "try again in ~N minutes" for the archive-blocked-by-holds 409 (§8.8).
|
|
1998
|
+
* Rounds up so the organizer never comes back one tick early.
|
|
1999
|
+
*/
|
|
2000
|
+
declare function retryAfterCopy(details: ArchiveBlockedDetails | null | undefined): string;
|
|
2001
|
+
/** The access line under a channel row. Falls back to "—" before the hardening
|
|
2002
|
+
* branch lands the `access` field, never to a guess. */
|
|
2003
|
+
declare function accessLine(access: ChannelAccessSummary | null | undefined): string;
|
|
2004
|
+
/** Plain-language label for the access-intent control. */
|
|
2005
|
+
declare function accessIntentLabel(intent: ChannelAccessIntent): string;
|
|
2006
|
+
/**
|
|
2007
|
+
* The chart-update refusal `channel_assignment_would_drop` (409) deliberately
|
|
2008
|
+
* mirrors the Apply skipped buckets, so ONE review component renders both.
|
|
2009
|
+
* This adapts it into the same `BucketRow[]` the Review sheet already draws.
|
|
2010
|
+
*/
|
|
2011
|
+
interface AssignmentDropDetails {
|
|
2012
|
+
droppedUnits?: number;
|
|
2013
|
+
channels?: Array<{
|
|
2014
|
+
channelId: string;
|
|
2015
|
+
name: string | null;
|
|
2016
|
+
count: number;
|
|
2017
|
+
labels?: string[];
|
|
2018
|
+
truncated?: boolean;
|
|
2019
|
+
}>;
|
|
2020
|
+
acknowledgeWith?: string;
|
|
2021
|
+
}
|
|
2022
|
+
declare function dropReviewRows(details: AssignmentDropDetails | null | undefined): BucketRow[];
|
|
2023
|
+
/** Plain-language state badge text (§9: no internal vocabulary on user surfaces). */
|
|
2024
|
+
declare function stateBadge(state: ChannelState | 'builtin'): string;
|
|
2025
|
+
|
|
1322
2026
|
/**
|
|
1323
2027
|
* Organizer manage-surface client for workers/api (the `/v1/events/:key/*`
|
|
1324
2028
|
* inventory routes + the public realtime channel). Companion to api.ts (the
|
|
@@ -1340,6 +2044,59 @@ declare function attachPickerFrame(iframe: HTMLIFrameElement, opts?: AttachPicke
|
|
|
1340
2044
|
* route is still session-only server-side).
|
|
1341
2045
|
*/
|
|
1342
2046
|
|
|
2047
|
+
/** One page of the organizer-only label → channel projection. */
|
|
2048
|
+
interface ChannelAllocationPage {
|
|
2049
|
+
assignmentVersion: number;
|
|
2050
|
+
allocations: Array<{
|
|
2051
|
+
label: string;
|
|
2052
|
+
channelId: string;
|
|
2053
|
+
}>;
|
|
2054
|
+
nextAfterLabel: string | null;
|
|
2055
|
+
}
|
|
2056
|
+
interface ChannelAuditEntry {
|
|
2057
|
+
id: number;
|
|
2058
|
+
at: number;
|
|
2059
|
+
actor: string | null;
|
|
2060
|
+
action: string;
|
|
2061
|
+
channelId: string | null;
|
|
2062
|
+
assignmentVersion: number;
|
|
2063
|
+
before: unknown;
|
|
2064
|
+
after: unknown;
|
|
2065
|
+
reason: string | null;
|
|
2066
|
+
}
|
|
2067
|
+
interface ChannelAuditPage {
|
|
2068
|
+
entries: ChannelAuditEntry[];
|
|
2069
|
+
nextBefore: number | null;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Buyer projection for a preview audience — the same scoped server view the
|
|
2073
|
+
* buyer SDK receives. When an audience cannot be previewed (a paused or
|
|
2074
|
+
* archived channel), the server answers `{available:false, unavailable:[…]}`
|
|
2075
|
+
* and the UI shows the real paused/unavailable landing state instead of
|
|
2076
|
+
* rendering those seats as eligible.
|
|
2077
|
+
*
|
|
2078
|
+
* Fields stay optional: a worker that predates the hardening merge 404s here,
|
|
2079
|
+
* and Channels mode says the preview needs a newer server rather than faking a
|
|
2080
|
+
* projection client-side.
|
|
2081
|
+
*/
|
|
2082
|
+
interface ChannelPreviewProjection {
|
|
2083
|
+
available?: boolean;
|
|
2084
|
+
unavailable?: Array<{
|
|
2085
|
+
channelId: string;
|
|
2086
|
+
state: 'paused' | 'archived' | string;
|
|
2087
|
+
}>;
|
|
2088
|
+
channelIds?: string[];
|
|
2089
|
+
includePublic?: boolean;
|
|
2090
|
+
/** Labels this audience may buy. Everything else renders as ONE neutral
|
|
2091
|
+
* unavailable state so preview never leaks which channel holds a seat. */
|
|
2092
|
+
eligible?: string[];
|
|
2093
|
+
counts?: {
|
|
2094
|
+
eligible?: number;
|
|
2095
|
+
free?: number;
|
|
2096
|
+
held?: number;
|
|
2097
|
+
booked?: number;
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
1343
2100
|
declare class ManageApiError extends Error {
|
|
1344
2101
|
status: number;
|
|
1345
2102
|
code?: string;
|
|
@@ -1348,10 +2105,17 @@ declare class ManageApiError extends Error {
|
|
|
1348
2105
|
label: string;
|
|
1349
2106
|
reason?: string;
|
|
1350
2107
|
}[];
|
|
2108
|
+
/**
|
|
2109
|
+
* Structured refusal detail. The channel routes use it for the two 409s a UI
|
|
2110
|
+
* must render rather than merely report: `channel_archive_blocked_by_holds`
|
|
2111
|
+
* carries {activeHolds, heldUnits, latestHoldExpiresAt, retryAfterMs}, and
|
|
2112
|
+
* `channel_assignment_conflict` carries the current assignmentVersion.
|
|
2113
|
+
*/
|
|
2114
|
+
details?: Record<string, unknown>;
|
|
1351
2115
|
constructor(status: number, message: string, code?: string, conflicts?: {
|
|
1352
2116
|
label: string;
|
|
1353
2117
|
reason?: string;
|
|
1354
|
-
}[]);
|
|
2118
|
+
}[], details?: Record<string, unknown>);
|
|
1355
2119
|
}
|
|
1356
2120
|
interface ReportByStatus {
|
|
1357
2121
|
free: number;
|
|
@@ -1536,6 +2300,76 @@ declare class ManageApi {
|
|
|
1536
2300
|
hidden: string[];
|
|
1537
2301
|
rules: Record<string, AvailabilityRule>;
|
|
1538
2302
|
}>;
|
|
2303
|
+
/** Allocation list with exact per-channel counts. `includeArchived` adds the
|
|
2304
|
+
* read-only archived rows behind the rail's "Show archived" control. */
|
|
2305
|
+
channels(key: string, opts?: {
|
|
2306
|
+
includeArchived?: boolean;
|
|
2307
|
+
}): Promise<ChannelListResult>;
|
|
2308
|
+
/** One page of the label → channel map that paints the allocation overlay.
|
|
2309
|
+
* Paged by label; follow `nextAfterLabel` until it is null. */
|
|
2310
|
+
channelAllocation(key: string, opts?: {
|
|
2311
|
+
afterLabel?: string;
|
|
2312
|
+
limit?: number;
|
|
2313
|
+
}): Promise<ChannelAllocationPage>;
|
|
2314
|
+
channelAudit(key: string, opts?: {
|
|
2315
|
+
limit?: number;
|
|
2316
|
+
before?: number;
|
|
2317
|
+
}): Promise<ChannelAuditPage>;
|
|
2318
|
+
createChannel(key: string, input: {
|
|
2319
|
+
name: string;
|
|
2320
|
+
color?: string | null;
|
|
2321
|
+
marker?: string | null;
|
|
2322
|
+
externalRef?: string | null;
|
|
2323
|
+
}): Promise<{
|
|
2324
|
+
ok: true;
|
|
2325
|
+
channel: ChannelRecord;
|
|
2326
|
+
}>;
|
|
2327
|
+
renameChannel(key: string, channelId: string, name: string): Promise<{
|
|
2328
|
+
ok: true;
|
|
2329
|
+
channel: ChannelRecord;
|
|
2330
|
+
}>;
|
|
2331
|
+
setChannelPaused(key: string, channelId: string, paused: boolean): Promise<{
|
|
2332
|
+
ok: true;
|
|
2333
|
+
channel: ChannelRecord;
|
|
2334
|
+
}>;
|
|
2335
|
+
/** Archive with a mandatory destination for the remaining allocation.
|
|
2336
|
+
* Throws ManageApiError 409 `channel_archive_blocked_by_holds` while any hold
|
|
2337
|
+
* is live; `err.details` carries the exact counts + retry window. */
|
|
2338
|
+
archiveChannel(key: string, channelId: string, destination: string | null): Promise<{
|
|
2339
|
+
ok: true;
|
|
2340
|
+
channel: ChannelRecord;
|
|
2341
|
+
assignmentVersion: number;
|
|
2342
|
+
moved: number;
|
|
2343
|
+
}>;
|
|
2344
|
+
/**
|
|
2345
|
+
* Versioned Apply. A stale `assignmentVersion` mutates NOTHING and throws
|
|
2346
|
+
* ManageApiError 409 `channel_assignment_conflict` — the caller keeps its
|
|
2347
|
+
* selection and offers "Refresh and review". There is no dry-run: the review
|
|
2348
|
+
* sheet previews locally, this call returns the authoritative buckets.
|
|
2349
|
+
*/
|
|
2350
|
+
applyChannelAssignment(key: string, input: {
|
|
2351
|
+
targetChannelId: string | null;
|
|
2352
|
+
labels: string[];
|
|
2353
|
+
assignmentVersion: number;
|
|
2354
|
+
}): Promise<AssignmentResult>;
|
|
2355
|
+
/**
|
|
2356
|
+
* Read-only buyer projection for an audience (§8.6) — the SAME scoped server
|
|
2357
|
+
* view the buyer SDK receives, never a local approximation.
|
|
2358
|
+
*
|
|
2359
|
+
* Ships on the access-hardening branch. Older workers 404/405 here; callers
|
|
2360
|
+
* MUST feature-detect and quietly say the preview needs a newer server rather
|
|
2361
|
+
* than faking a projection client-side.
|
|
2362
|
+
*/
|
|
2363
|
+
channelPreview(key: string, channelIds: string[], opts?: {
|
|
2364
|
+
includePublic?: boolean;
|
|
2365
|
+
}): Promise<ChannelPreviewProjection>;
|
|
2366
|
+
/** Declare how buyers are meant to reach this channel. Drives the rail's
|
|
2367
|
+
* access line and turns "No buyer access configured" from information into a
|
|
2368
|
+
* warning when the organizer says the channel is for buyer self-service. */
|
|
2369
|
+
setChannelAccessIntent(key: string, channelId: string, accessIntent: ChannelAccessIntent): Promise<{
|
|
2370
|
+
ok: true;
|
|
2371
|
+
channel: ChannelRecord;
|
|
2372
|
+
}>;
|
|
1539
2373
|
report(key: string): Promise<ReportResult>;
|
|
1540
2374
|
controlRoom(key: string, windowMinutes?: number): Promise<ControlRoomSnapshot>;
|
|
1541
2375
|
log(key: string, opts?: {
|
|
@@ -1569,7 +2403,14 @@ declare class ManageApi {
|
|
|
1569
2403
|
* {@link ManageApi}. Box office + Sections + full Reports UI are M2/M3.
|
|
1570
2404
|
*/
|
|
1571
2405
|
|
|
1572
|
-
type SeatManagerMode = 'view' | 'inspect' | 'block' | 'sections';
|
|
2406
|
+
type SeatManagerMode = 'view' | 'inspect' | 'block' | 'sections' | 'channels';
|
|
2407
|
+
/**
|
|
2408
|
+
* Capabilities the cockpit's token was minted with. Channels mode is gated on
|
|
2409
|
+
* these and fails CLOSED: no `event:channels:view` ⇒ no Channels pill at all;
|
|
2410
|
+
* view without `event:channels:manage` ⇒ read-only inspection with every
|
|
2411
|
+
* mutation control absent, not merely disabled.
|
|
2412
|
+
*/
|
|
2413
|
+
type SeatManagerCapability = 'event:view' | 'event:block' | 'event:cancel' | 'event:reports' | 'event:channels:view' | 'event:channels:manage';
|
|
1573
2414
|
/** DO seat status — 'blocked' has no engine analogue (→ 'not_for_sale'). */
|
|
1574
2415
|
type DoStatus = 'free' | 'held' | 'booked' | 'blocked';
|
|
1575
2416
|
/** Live KPI snapshot pushed to `onTallies` on every state change. */
|
|
@@ -1625,6 +2466,14 @@ interface SeatManagerOptions {
|
|
|
1625
2466
|
tokenExpiresAt?: number;
|
|
1626
2467
|
/** Initial mode. Default 'view'. */
|
|
1627
2468
|
mode?: SeatManagerMode;
|
|
2469
|
+
/**
|
|
2470
|
+
* The capability set this token was minted with. Supply it whenever you mint
|
|
2471
|
+
* an `mse_…` grant — it is the only way the widget can know a delegated token
|
|
2472
|
+
* carries `event:channels:manage`, and without it Channels mode stays
|
|
2473
|
+
* read-only (fail-closed). A tenant secret (`sk_…`) is org authority and is
|
|
2474
|
+
* never narrowed server-side, so it is treated as fully capable.
|
|
2475
|
+
*/
|
|
2476
|
+
capabilities?: SeatManagerCapability[] | string[];
|
|
1628
2477
|
/** ISO-4217 fallback currency for revenue (chart/event currency wins). */
|
|
1629
2478
|
currency?: string;
|
|
1630
2479
|
/** Chart theme override for the chrome (rails/bar). Chart colors come from the doc. */
|
|
@@ -1719,6 +2568,8 @@ declare class SeatManager {
|
|
|
1719
2568
|
private blockedSection;
|
|
1720
2569
|
private blockedResultLimit;
|
|
1721
2570
|
private unblockAllConfirmTimer;
|
|
2571
|
+
private channels;
|
|
2572
|
+
private channelCaps;
|
|
1722
2573
|
private readonly onFullscreenChange;
|
|
1723
2574
|
private readonly onKeyDown;
|
|
1724
2575
|
private readonly onRailClick;
|
|
@@ -1726,6 +2577,21 @@ declare class SeatManager {
|
|
|
1726
2577
|
/** Build the DOM, load the chart, subscribe to realtime, mount the board. */
|
|
1727
2578
|
render(): Promise<this>;
|
|
1728
2579
|
setMode(mode: SeatManagerMode): void;
|
|
2580
|
+
/**
|
|
2581
|
+
* Decide what this token may do with sales channels.
|
|
2582
|
+
*
|
|
2583
|
+
* Declared capabilities win — a host that mints an `mse_…` grant knows exactly
|
|
2584
|
+
* what it asked for. Otherwise a tenant secret (`sk_…`) is org authority the
|
|
2585
|
+
* worker never narrows, so it is fully capable; and a delegated token with no
|
|
2586
|
+
* declaration is probed for read access and then treated as READ-ONLY, because
|
|
2587
|
+
* "we could not tell" must never render mutation controls.
|
|
2588
|
+
*/
|
|
2589
|
+
private resolveChannelCapabilities;
|
|
2590
|
+
/** The adapter between the cockpit's internals and Channels mode. */
|
|
2591
|
+
private buildChannelsHost;
|
|
2592
|
+
/** Approximate on-screen seat size, for the channel overlay's marks. Derived
|
|
2593
|
+
* from the live camera so the overlay tracks zoom without a renderer hook. */
|
|
2594
|
+
private seatPixelSize;
|
|
1729
2595
|
/** Toggle the normalized sales-velocity outline overlay without changing seat colors. */
|
|
1730
2596
|
setHeatOverlay(enabled: boolean): void;
|
|
1731
2597
|
/** Toggle opt-in camera following for new buyer hold/book events. */
|
|
@@ -1769,6 +2635,16 @@ declare class SeatManager {
|
|
|
1769
2635
|
zoomToFit(): void;
|
|
1770
2636
|
destroy(): void;
|
|
1771
2637
|
private buildRenderer;
|
|
2638
|
+
/** Block and Channels are both bulk-selection tools: marquee, ⌘A, category,
|
|
2639
|
+
* section. The two differ only in WHICH statuses they may act on. */
|
|
2640
|
+
private isBulkSelectMode;
|
|
2641
|
+
/**
|
|
2642
|
+
* Block never touches held or booked inventory, so it cannot select it.
|
|
2643
|
+
* Channels must be able to select it — the Review sheet's honesty depends on
|
|
2644
|
+
* counting the held and sold units inside a marquee and saying they will not
|
|
2645
|
+
* move, rather than silently omitting them from the selection.
|
|
2646
|
+
*/
|
|
2647
|
+
private selectableStatuses;
|
|
1772
2648
|
private updateRendererInteraction;
|
|
1773
2649
|
private handleSeatSelect;
|
|
1774
2650
|
private repaintAll;
|
|
@@ -1870,4 +2746,294 @@ declare class SeatManager {
|
|
|
1870
2746
|
private fail;
|
|
1871
2747
|
}
|
|
1872
2748
|
|
|
1873
|
-
|
|
2749
|
+
/**
|
|
2750
|
+
* Channels mode — the sales-channel management surface inside the SeatManager
|
|
2751
|
+
* cockpit. It ships in the SDK, so every embedding platform (and our own Control
|
|
2752
|
+
* Room) gets the same organizer experience.
|
|
2753
|
+
*
|
|
2754
|
+
* Design contract: `SeatmapUX/05 Event Manager.dc.html` (all 8 desktop states +
|
|
2755
|
+
* the mobile artboard). Behaviour: sales-channels-product-ux-spec §8 (states),
|
|
2756
|
+
* §9 (language), §13 (a11y + compact detents). Motion: motion-system §2 tokens,
|
|
2757
|
+
* mirrored here as `--slm-mo-*` so an embed is self-contained, and §3 cockpit
|
|
2758
|
+
* choreography.
|
|
2759
|
+
*
|
|
2760
|
+
* Boundaries this module keeps:
|
|
2761
|
+
*
|
|
2762
|
+
* - **Capability gating is fail-closed.** Without `event:channels:view` the
|
|
2763
|
+
* cockpit never renders the pill (SeatManager's job). Without
|
|
2764
|
+
* `event:channels:manage` every mutation control is ABSENT — not disabled —
|
|
2765
|
+
* so a read-only operator is never shown authority they do not have.
|
|
2766
|
+
* - **Nothing here mutates physical inventory.** Assignment moves an
|
|
2767
|
+
* allocation; held and booked units are never rewritten.
|
|
2768
|
+
* - **Staging is local, truth is the server.** There is no dry-run endpoint,
|
|
2769
|
+
* so the Review sheet previews the buckets client-side and then re-renders
|
|
2770
|
+
* the AUTHORITATIVE bucket counts from the Apply response. A stale
|
|
2771
|
+
* `assignmentVersion` mutates nothing: the selection survives and the only
|
|
2772
|
+
* action offered is Refresh and review.
|
|
2773
|
+
* - **Forward-compatible reads.** The `access` field and the buyer-preview
|
|
2774
|
+
* projection land on the access-hardening branch. Both are feature-detected;
|
|
2775
|
+
* absent, the access line reads "—" and the Preview segment says it needs a
|
|
2776
|
+
* newer server. Neither ever blocks allocation work.
|
|
2777
|
+
*/
|
|
2778
|
+
|
|
2779
|
+
/** The seat facts the overlay needs. Chart space, not screen space. */
|
|
2780
|
+
interface ChannelsSeatView {
|
|
2781
|
+
id: string;
|
|
2782
|
+
label: string;
|
|
2783
|
+
x: number;
|
|
2784
|
+
y: number;
|
|
2785
|
+
}
|
|
2786
|
+
/** The `ManageApi` subset Channels mode uses — structural so tests can pass a
|
|
2787
|
+
* hand-rolled double without constructing a real client. */
|
|
2788
|
+
interface ChannelsClient {
|
|
2789
|
+
channels(key: string, opts?: {
|
|
2790
|
+
includeArchived?: boolean;
|
|
2791
|
+
}): Promise<ChannelListResult>;
|
|
2792
|
+
channelAllocation(key: string, opts?: {
|
|
2793
|
+
afterLabel?: string;
|
|
2794
|
+
limit?: number;
|
|
2795
|
+
}): Promise<ChannelAllocationPage>;
|
|
2796
|
+
createChannel(key: string, input: {
|
|
2797
|
+
name: string;
|
|
2798
|
+
color?: string | null;
|
|
2799
|
+
marker?: string | null;
|
|
2800
|
+
externalRef?: string | null;
|
|
2801
|
+
}): Promise<{
|
|
2802
|
+
ok: true;
|
|
2803
|
+
channel: ChannelRecord;
|
|
2804
|
+
}>;
|
|
2805
|
+
renameChannel(key: string, channelId: string, name: string): Promise<{
|
|
2806
|
+
ok: true;
|
|
2807
|
+
channel: ChannelRecord;
|
|
2808
|
+
}>;
|
|
2809
|
+
setChannelPaused(key: string, channelId: string, paused: boolean): Promise<{
|
|
2810
|
+
ok: true;
|
|
2811
|
+
channel: ChannelRecord;
|
|
2812
|
+
}>;
|
|
2813
|
+
archiveChannel(key: string, channelId: string, destination: string | null): Promise<{
|
|
2814
|
+
ok: true;
|
|
2815
|
+
channel: ChannelRecord;
|
|
2816
|
+
assignmentVersion: number;
|
|
2817
|
+
moved: number;
|
|
2818
|
+
}>;
|
|
2819
|
+
applyChannelAssignment(key: string, input: {
|
|
2820
|
+
targetChannelId: string | null;
|
|
2821
|
+
labels: string[];
|
|
2822
|
+
assignmentVersion: number;
|
|
2823
|
+
}): Promise<AssignmentResult>;
|
|
2824
|
+
channelPreview(key: string, channelIds: string[], opts?: {
|
|
2825
|
+
includePublic?: boolean;
|
|
2826
|
+
}): Promise<ChannelPreviewProjection>;
|
|
2827
|
+
setChannelAccessIntent(key: string, channelId: string, accessIntent: ChannelAccessIntent): Promise<{
|
|
2828
|
+
ok: true;
|
|
2829
|
+
channel: ChannelRecord;
|
|
2830
|
+
}>;
|
|
2831
|
+
}
|
|
2832
|
+
interface ChannelsCapabilities {
|
|
2833
|
+
view: boolean;
|
|
2834
|
+
manage: boolean;
|
|
2835
|
+
}
|
|
2836
|
+
/** Everything Channels mode needs from the cockpit around it. */
|
|
2837
|
+
interface ChannelsModeHost {
|
|
2838
|
+
eventKey: string;
|
|
2839
|
+
api: ChannelsClient;
|
|
2840
|
+
/** The rail scroll container the mode paints into. */
|
|
2841
|
+
rail: HTMLElement;
|
|
2842
|
+
/** An absolutely-positioned layer over the map (overlay canvas, flags, bars). */
|
|
2843
|
+
mapLayer: HTMLElement;
|
|
2844
|
+
/** The widget root — dialogs mount here so they inherit the widget's tokens. */
|
|
2845
|
+
root: HTMLElement;
|
|
2846
|
+
seats(): ChannelsSeatView[];
|
|
2847
|
+
statusOf(label: string): ChannelSeatStatus | undefined;
|
|
2848
|
+
selectionLabels(): string[];
|
|
2849
|
+
selectByLabels(labels: string[]): void;
|
|
2850
|
+
clearSelection(): void;
|
|
2851
|
+
selectSection(sectionId: string): void;
|
|
2852
|
+
sections(): Array<{
|
|
2853
|
+
id: string;
|
|
2854
|
+
label: string;
|
|
2855
|
+
}>;
|
|
2856
|
+
categories(): Array<{
|
|
2857
|
+
key: string;
|
|
2858
|
+
label: string;
|
|
2859
|
+
color?: string;
|
|
2860
|
+
}>;
|
|
2861
|
+
labelsInCategory(key: string): string[];
|
|
2862
|
+
sectionOfLabel(label: string): {
|
|
2863
|
+
id: string;
|
|
2864
|
+
label: string;
|
|
2865
|
+
} | null;
|
|
2866
|
+
/** Chart-space → container pixels. Null when there is no live renderer. */
|
|
2867
|
+
worldToScreen(point: {
|
|
2868
|
+
x: number;
|
|
2869
|
+
y: number;
|
|
2870
|
+
}): {
|
|
2871
|
+
x: number;
|
|
2872
|
+
y: number;
|
|
2873
|
+
} | null;
|
|
2874
|
+
/** Approximate on-screen seat size in CSS pixels, for the overlay marks. */
|
|
2875
|
+
seatPixelSize(): number;
|
|
2876
|
+
isCompact(): boolean;
|
|
2877
|
+
/** Make the canvas non-interactive behind a full-detent sheet (§13). */
|
|
2878
|
+
setMapInert(inert: boolean): void;
|
|
2879
|
+
toast(message: string, kind: 'ok' | 'err'): void;
|
|
2880
|
+
onError(err: unknown): void;
|
|
2881
|
+
/** Fired whenever the staged mutation count changes, for host telemetry. */
|
|
2882
|
+
onStagedChange?(staged: number): void;
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Render bucket rows to markup. Deliberately generic over `BucketRow`, because
|
|
2886
|
+
* three different refusals share this exact presentation: the local staged
|
|
2887
|
+
* preview, the authoritative Apply response, and the chart-update
|
|
2888
|
+
* `channel_assignment_would_drop` review (via `dropReviewRows`). One component,
|
|
2889
|
+
* one visual language for "here is every affected unit, in exactly one line".
|
|
2890
|
+
*/
|
|
2891
|
+
declare function bucketRowsHtml(rows: BucketRow[]): string;
|
|
2892
|
+
declare class ChannelsMode {
|
|
2893
|
+
private readonly host;
|
|
2894
|
+
private caps;
|
|
2895
|
+
private active;
|
|
2896
|
+
private list;
|
|
2897
|
+
private allocation;
|
|
2898
|
+
private assignmentVersion;
|
|
2899
|
+
private loadError;
|
|
2900
|
+
private loading;
|
|
2901
|
+
private view;
|
|
2902
|
+
private showArchived;
|
|
2903
|
+
private detailChannelId;
|
|
2904
|
+
private targetChannelId;
|
|
2905
|
+
private conflict;
|
|
2906
|
+
private dialog;
|
|
2907
|
+
private detent;
|
|
2908
|
+
private seatListLimit;
|
|
2909
|
+
private previewAudience;
|
|
2910
|
+
private previewIncludePublic;
|
|
2911
|
+
private previewProjection;
|
|
2912
|
+
private previewSupported;
|
|
2913
|
+
private pollTimer;
|
|
2914
|
+
private layer;
|
|
2915
|
+
private canvas;
|
|
2916
|
+
/** undefined = not resolved yet, null = this environment has no 2d canvas. */
|
|
2917
|
+
private ctx;
|
|
2918
|
+
private bannerEl;
|
|
2919
|
+
private stagedEl;
|
|
2920
|
+
private liveEl;
|
|
2921
|
+
private scrimEl;
|
|
2922
|
+
private lastFocus;
|
|
2923
|
+
private stagedDoneTimer;
|
|
2924
|
+
private lastSelectionCount;
|
|
2925
|
+
private lastCounts;
|
|
2926
|
+
constructor(host: ChannelsModeHost, capabilities: ChannelsCapabilities);
|
|
2927
|
+
/** Called when the cockpit switches into Channels mode. */
|
|
2928
|
+
enter(): void;
|
|
2929
|
+
/** Called when the cockpit leaves Channels mode. Everything this mode painted
|
|
2930
|
+
* over the map goes with it — no other tool ever inherits a channel overlay. */
|
|
2931
|
+
leave(): void;
|
|
2932
|
+
destroy(): void;
|
|
2933
|
+
/** Capabilities can change when a token rotates. Re-render, fail-closed. */
|
|
2934
|
+
setCapabilities(capabilities: ChannelsCapabilities): void;
|
|
2935
|
+
isActive(): boolean;
|
|
2936
|
+
/**
|
|
2937
|
+
* Whether the map should accept bulk selection right now. Preview is a
|
|
2938
|
+
* read-only simulation of somebody else's view, and a view-only token has no
|
|
2939
|
+
* assignment to stage — in both cases the canvas must not offer selection at
|
|
2940
|
+
* all rather than collect a selection nothing can act on.
|
|
2941
|
+
*/
|
|
2942
|
+
canSelect(): boolean;
|
|
2943
|
+
/**
|
|
2944
|
+
* Organizer realtime integration point. M5 ships a per-scope socket for
|
|
2945
|
+
* buyers; the organizer channel-count stream is a later milestone. When it
|
|
2946
|
+
* arrives, call this from the cockpit's WS handler instead of waiting for the
|
|
2947
|
+
* poll — everything downstream already reacts to a fresh list.
|
|
2948
|
+
*/
|
|
2949
|
+
applyRealtimeHint(): void;
|
|
2950
|
+
/** The cockpit's selection changed (marquee / click / section / category). */
|
|
2951
|
+
handleSelectionChange(): void;
|
|
2952
|
+
/** Camera moved or the container resized — the overlay is screen-space. */
|
|
2953
|
+
handleViewChange(): void;
|
|
2954
|
+
handleLayoutChange(): void;
|
|
2955
|
+
private refresh;
|
|
2956
|
+
/** Walk every allocation page. Bounded by the event's seat count, and the
|
|
2957
|
+
* server caps each page, so an arena is a handful of round trips. */
|
|
2958
|
+
private loadAllocation;
|
|
2959
|
+
private channelById;
|
|
2960
|
+
private nameOf;
|
|
2961
|
+
private markerFor;
|
|
2962
|
+
/** Channels an organizer may assign INTO: public sale plus every live channel. */
|
|
2963
|
+
private assignableChannels;
|
|
2964
|
+
private currentPlan;
|
|
2965
|
+
private ensureLayer;
|
|
2966
|
+
private announce;
|
|
2967
|
+
/**
|
|
2968
|
+
* Repaint the allocation (or preview) overlay in ONE canvas pass.
|
|
2969
|
+
*
|
|
2970
|
+
* Channel identity on the map is a fill in the administrative color PLUS the
|
|
2971
|
+
* letter flags below — never color alone. Physical status keeps its own cue:
|
|
2972
|
+
* only FREE units take a channel fill, so sold/held/blocked seats still read
|
|
2973
|
+
* exactly as they do in every other tool.
|
|
2974
|
+
*/
|
|
2975
|
+
private paintOverlay;
|
|
2976
|
+
/** Letter flags at each channel's centroid — the non-color identity cue. */
|
|
2977
|
+
private paintFlags;
|
|
2978
|
+
private setStaged;
|
|
2979
|
+
private paintStagedBar;
|
|
2980
|
+
private setBanner;
|
|
2981
|
+
private setView;
|
|
2982
|
+
/** Set by the cockpit so a view switch can re-arm canvas selection. */
|
|
2983
|
+
onInteractionChange?: () => void;
|
|
2984
|
+
private loadPreview;
|
|
2985
|
+
paintRail(): void;
|
|
2986
|
+
private viewSegmentHtml;
|
|
2987
|
+
private countsHtml;
|
|
2988
|
+
private channelRowHtml;
|
|
2989
|
+
private listRailHtml;
|
|
2990
|
+
private selectionRailHtml;
|
|
2991
|
+
private detailRailHtml;
|
|
2992
|
+
private previewRailHtml;
|
|
2993
|
+
private paintSelection;
|
|
2994
|
+
private wireRail;
|
|
2995
|
+
private railAction;
|
|
2996
|
+
private pickSection;
|
|
2997
|
+
private pickCategory;
|
|
2998
|
+
/** A tiny modal chooser reusing the dialog primitive (focus trap + Escape). */
|
|
2999
|
+
private promptChoice;
|
|
3000
|
+
private openDialog;
|
|
3001
|
+
private renderDialog;
|
|
3002
|
+
/**
|
|
3003
|
+
* Mount a modal: `aria-modal` dialog, programmatic name, focus moved inside,
|
|
3004
|
+
* Tab trapped, Escape closes WITHOUT mutating, focus restored on close (§13).
|
|
3005
|
+
*/
|
|
3006
|
+
private renderScrim;
|
|
3007
|
+
private closeDialog;
|
|
3008
|
+
private renderCreateDialog;
|
|
3009
|
+
private createChannel;
|
|
3010
|
+
private showDialogError;
|
|
3011
|
+
private renderReviewDialog;
|
|
3012
|
+
/**
|
|
3013
|
+
* Apply. On success the review sheet re-renders with the AUTHORITATIVE server
|
|
3014
|
+
* buckets and the staged bar morphs to a ✓ for 1.2s. On a stale version the
|
|
3015
|
+
* server mutated nothing: keep the selection, shake the bar once, and offer
|
|
3016
|
+
* exactly one action — Refresh and review.
|
|
3017
|
+
*/
|
|
3018
|
+
private apply;
|
|
3019
|
+
private showApplied;
|
|
3020
|
+
private shakeStaged;
|
|
3021
|
+
private renderRenameDialog;
|
|
3022
|
+
private setAccessIntent;
|
|
3023
|
+
private togglePause;
|
|
3024
|
+
private renderArchiveDialog;
|
|
3025
|
+
private archive;
|
|
3026
|
+
/**
|
|
3027
|
+
* The synchronized inventory list (§13): the keyboard and screen-reader
|
|
3028
|
+
* equivalent of canvas click / marquee / brush, grouped by section with
|
|
3029
|
+
* per-section select actions.
|
|
3030
|
+
*/
|
|
3031
|
+
private renderSeatListDialog;
|
|
3032
|
+
private applySheetClasses;
|
|
3033
|
+
private cycleDetent;
|
|
3034
|
+
/** Back/Close from the full detent returns to the previous one and keeps the
|
|
3035
|
+
* selection — losing a hard-won selection to a Back press is unforgivable. */
|
|
3036
|
+
handleBack(): boolean;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
export { ApiError, type ArchiveBlockedDetails, type AssignmentBuckets, type AssignmentDropDetails, type AssignmentResult, type AttachPickerFrameOptions, type BestAvailableResult, type BucketRow, BuyerAccessContext, type BuyerAccessExpiredEvent, type BuyerAccessRefreshReason, type BuyerAccessToken, type BuyerAccessTokenProvider, BuyerAccessUnavailableError, type BuyerAccessUnavailableEvent, type BuyerAccessUnavailableReason, BuyerRealtimeClient, type BuyerRealtimeOptions, type ChannelAccessIntent, type ChannelAccessSummary, type ChannelAllocationPage, type ChannelAuditEntry, type ChannelAuditPage, type ChannelCounts, type ChannelListResult, type ChannelPreviewProjection, type ChannelRecord, type ChannelSeatStatus, type ChannelState, type ChannelsCapabilities, type ChannelsClient, ChannelsMode, type ChannelsModeHost, type ChannelsSeatView, type CheckoutHandoff, type CheckoutLineItem, type ControlRoomActivityEntry, type ControlRoomSectionMetric, type ControlRoomSnapshot, EmbeddedDesigner, type EmbeddedDesignerEventType, type EmbeddedDesignerMessage, type EmbeddedDesignerOptions, type GAAreaAvailability, type HoldConflict, type HoldLineItem, type HoldResult, type LogEntry, type LogPage, ManageApi, ManageApiError, PUBLIC_CHANNEL_ID, PUBLIC_CHANNEL_NAME, type Projection, type PubApiOptions, type RealtimeSink, type ReportByStatus, type ReportCategoryMeta, type ReportCategoryRow, type ReportResult, type ResumedHoldResult, SeatManager, type SeatManagerActionResult, type SeatManagerActivity, type SeatManagerCapability, type SeatManagerMode, type SeatManagerOptions, type SeatManagerTallies, SeatPicker, type SeatPickerOptions, type SeatPickerTheme, SeatingChart, type SeatingChartOptions, type SelectedObjectUnavailableEvent, type SelectedSeat, type SelectionSourceRow, type StatusChange, type SubscribeTicket, accessIntentLabel, accessLine, attachPickerFrame, bucketRows, bucketRowsHtml, createBuyerAccessContext, createControllerSink, dropReviewRows, markerOf, mutationCount, needsMoveConfirmation, planAssignment, retryAfterCopy, selectionSources, stateBadge, suggestMarker };
|