@seatlayer/js 0.36.0 → 0.36.2
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 +123 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -8
- package/dist/index.d.ts +98 -8
- package/dist/index.js +121 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5945,8 +5945,11 @@ import {
|
|
|
5945
5945
|
} from "@seatlayer/core";
|
|
5946
5946
|
|
|
5947
5947
|
// src/channelPlan.ts
|
|
5948
|
-
var PUBLIC_CHANNEL_ID = "";
|
|
5948
|
+
var PUBLIC_CHANNEL_ID = "public";
|
|
5949
5949
|
var PUBLIC_CHANNEL_NAME = "Public sale";
|
|
5950
|
+
function isPublicChannelId(id) {
|
|
5951
|
+
return id == null || id === "" || id === PUBLIC_CHANNEL_ID;
|
|
5952
|
+
}
|
|
5950
5953
|
var CHANNEL_COLORS = [
|
|
5951
5954
|
"#a78bfa",
|
|
5952
5955
|
"#2dd4bf",
|
|
@@ -5961,27 +5964,35 @@ var CHANNEL_COLORS = [
|
|
|
5961
5964
|
];
|
|
5962
5965
|
var PUBLIC_CHANNEL_COLOR = "#f4b740";
|
|
5963
5966
|
var LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
5967
|
+
function markerLetter(raw, fallback) {
|
|
5968
|
+
const text = (raw ?? "").trim();
|
|
5969
|
+
const letter = /\p{L}/u.exec(text)?.[0] ?? text[0] ?? "";
|
|
5970
|
+
return (letter || fallback).toUpperCase().slice(0, 1);
|
|
5971
|
+
}
|
|
5964
5972
|
function suggestMarker(name, taken) {
|
|
5965
|
-
const used = new Set([...taken].map((m) => m
|
|
5966
|
-
const first = (name
|
|
5973
|
+
const used = new Set([...taken].map((m) => markerLetter(m, "")).filter(Boolean));
|
|
5974
|
+
const first = markerLetter(name, "");
|
|
5967
5975
|
const letter = LETTERS.includes(first) && !used.has(first) ? first : [...LETTERS].find((candidate) => !used.has(candidate)) ?? (first || "X");
|
|
5968
5976
|
return { letter, color: CHANNEL_COLORS[used.size % CHANNEL_COLORS.length] };
|
|
5969
5977
|
}
|
|
5970
5978
|
function markerOf(channel, index = 0) {
|
|
5971
|
-
if (channel.id
|
|
5972
|
-
return {
|
|
5979
|
+
if (isPublicChannelId(channel.id)) {
|
|
5980
|
+
return {
|
|
5981
|
+
letter: markerLetter(channel.marker, "P"),
|
|
5982
|
+
color: channel.color || PUBLIC_CHANNEL_COLOR
|
|
5983
|
+
};
|
|
5973
5984
|
}
|
|
5974
|
-
const letter = (channel.marker || channel.name
|
|
5985
|
+
const letter = markerLetter(channel.marker || channel.name, "?");
|
|
5975
5986
|
return { letter, color: channel.color || CHANNEL_COLORS[index % CHANNEL_COLORS.length] };
|
|
5976
5987
|
}
|
|
5977
5988
|
function selectionSources(labels, allocation, list) {
|
|
5978
5989
|
const counts = /* @__PURE__ */ new Map();
|
|
5979
5990
|
for (const label of labels) {
|
|
5980
|
-
const channelId = allocation.get(label)
|
|
5991
|
+
const channelId = normalizeChannelId(allocation.get(label));
|
|
5981
5992
|
counts.set(channelId, (counts.get(channelId) ?? 0) + 1);
|
|
5982
5993
|
}
|
|
5983
5994
|
const order = [
|
|
5984
|
-
{ id: PUBLIC_CHANNEL_ID, name: list?.publicSale
|
|
5995
|
+
{ id: PUBLIC_CHANNEL_ID, name: list?.publicSale?.name ?? PUBLIC_CHANNEL_NAME },
|
|
5985
5996
|
...(list?.channels ?? []).map((channel) => ({ id: channel.id, name: channel.name }))
|
|
5986
5997
|
];
|
|
5987
5998
|
const rows = [];
|
|
@@ -5991,10 +6002,17 @@ function selectionSources(labels, allocation, list) {
|
|
|
5991
6002
|
counts.delete(entry.id);
|
|
5992
6003
|
}
|
|
5993
6004
|
for (const [channelId, count] of counts) {
|
|
5994
|
-
rows.push({
|
|
6005
|
+
rows.push({
|
|
6006
|
+
channelId,
|
|
6007
|
+
name: isPublicChannelId(channelId) ? PUBLIC_CHANNEL_NAME : "Another channel",
|
|
6008
|
+
count
|
|
6009
|
+
});
|
|
5995
6010
|
}
|
|
5996
6011
|
return rows;
|
|
5997
6012
|
}
|
|
6013
|
+
function normalizeChannelId(id) {
|
|
6014
|
+
return isPublicChannelId(id) ? PUBLIC_CHANNEL_ID : id;
|
|
6015
|
+
}
|
|
5998
6016
|
var SKIP_SAMPLE = 12;
|
|
5999
6017
|
function skipBucket(labels) {
|
|
6000
6018
|
return {
|
|
@@ -6004,7 +6022,8 @@ function skipBucket(labels) {
|
|
|
6004
6022
|
};
|
|
6005
6023
|
}
|
|
6006
6024
|
function planAssignment(input) {
|
|
6007
|
-
const { labels,
|
|
6025
|
+
const { labels, allocation, statusOf, nameOf } = input;
|
|
6026
|
+
const targetChannelId = normalizeChannelId(input.targetChannelId);
|
|
6008
6027
|
const seen = /* @__PURE__ */ new Set();
|
|
6009
6028
|
let fromPublic = 0;
|
|
6010
6029
|
let alreadyIn = 0;
|
|
@@ -6020,7 +6039,7 @@ function planAssignment(input) {
|
|
|
6020
6039
|
missing.push(label);
|
|
6021
6040
|
continue;
|
|
6022
6041
|
}
|
|
6023
|
-
const current = allocation.get(label)
|
|
6042
|
+
const current = normalizeChannelId(allocation.get(label));
|
|
6024
6043
|
if (current === targetChannelId) {
|
|
6025
6044
|
alreadyIn += 1;
|
|
6026
6045
|
continue;
|
|
@@ -6203,12 +6222,38 @@ var ManageApi = class {
|
|
|
6203
6222
|
pub(path) {
|
|
6204
6223
|
return fetch(`${this.base}${path}`, { credentials: "omit" }).then((r) => parse(r));
|
|
6205
6224
|
}
|
|
6206
|
-
// ---- realtime read
|
|
6225
|
+
// ---- realtime read ----
|
|
6226
|
+
/** The chart geometry. Genuinely public — it is the same map buyers see. */
|
|
6207
6227
|
chart(key) {
|
|
6208
6228
|
return this.pub(`/pub/events/${encodeURIComponent(key)}/chart`);
|
|
6209
6229
|
}
|
|
6230
|
+
/**
|
|
6231
|
+
* The ORGANIZER's seat map: physical state, token-authed.
|
|
6232
|
+
*
|
|
6233
|
+
* This used to read `/pub/events/:key/objects` with no credential, which
|
|
6234
|
+
* answers with the BUYER projection — every unit the caller may not buy
|
|
6235
|
+
* collapses to a neutral `blocked`. An anonymous caller may buy only Public
|
|
6236
|
+
* sale inventory, so the cockpit rendered every channel-allocated seat as
|
|
6237
|
+
* blocked and then computed its KPIs, sell-through and (worse) its
|
|
6238
|
+
* block/unblock target sets from that. `/v1/events/:key/objects` returns the
|
|
6239
|
+
* unprojected snapshot the control-room read model already trusts.
|
|
6240
|
+
*/
|
|
6210
6241
|
objects(key) {
|
|
6211
|
-
return this.
|
|
6242
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/objects`);
|
|
6243
|
+
}
|
|
6244
|
+
/**
|
|
6245
|
+
* Exchange the manage token for a one-use organizer socket ticket.
|
|
6246
|
+
*
|
|
6247
|
+
* A browser `WebSocket` cannot send an Authorization header, so the socket's
|
|
6248
|
+
* scope is established here, over ordinary HTTPS. Without it the DO treats a
|
|
6249
|
+
* manager socket as an anonymous public buyer and projects its deltas — so a
|
|
6250
|
+
* hold inside a private allocation is structurally suppressed and the map
|
|
6251
|
+
* drifts away from the truth `objects()` just established.
|
|
6252
|
+
*
|
|
6253
|
+
* Tickets are single-redemption and expire in ~30s: mint one per connect.
|
|
6254
|
+
*/
|
|
6255
|
+
subscribeTicket(key) {
|
|
6256
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/subscribe-tickets`, { method: "POST" });
|
|
6212
6257
|
}
|
|
6213
6258
|
socketUrl(key) {
|
|
6214
6259
|
return `${this.base.replace(/^http/, "ws")}/pub/events/${encodeURIComponent(key)}/subscribe?surface=manager`;
|
|
@@ -6705,7 +6750,7 @@ var ChannelsMode = class {
|
|
|
6705
6750
|
limit: 1e3
|
|
6706
6751
|
});
|
|
6707
6752
|
for (const row of res.allocations) {
|
|
6708
|
-
if (row.channelId
|
|
6753
|
+
if (!isPublicChannelId(row.channelId)) next.set(row.label, row.channelId);
|
|
6709
6754
|
}
|
|
6710
6755
|
this.assignmentVersion = res.assignmentVersion;
|
|
6711
6756
|
if (!res.nextAfterLabel) break;
|
|
@@ -6715,8 +6760,13 @@ var ChannelsMode = class {
|
|
|
6715
6760
|
}
|
|
6716
6761
|
// ---- lookups --------------------------------------------------------------
|
|
6717
6762
|
channelById(id) {
|
|
6718
|
-
if (id
|
|
6719
|
-
return {
|
|
6763
|
+
if (isPublicChannelId(id)) {
|
|
6764
|
+
return {
|
|
6765
|
+
id: PUBLIC_CHANNEL_ID,
|
|
6766
|
+
name: this.list?.publicSale?.name ?? PUBLIC_CHANNEL_NAME,
|
|
6767
|
+
marker: "P",
|
|
6768
|
+
color: null
|
|
6769
|
+
};
|
|
6720
6770
|
}
|
|
6721
6771
|
const found = this.list?.channels.find((channel) => channel.id === id);
|
|
6722
6772
|
return found ? { id, name: found.name, marker: found.marker, color: found.color } : null;
|
|
@@ -6937,7 +6987,10 @@ var ChannelsMode = class {
|
|
|
6937
6987
|
this.setBanner(true, names);
|
|
6938
6988
|
try {
|
|
6939
6989
|
this.previewProjection = await this.host.api.channelPreview(this.host.eventKey, audience, {
|
|
6940
|
-
|
|
6990
|
+
// Naming Public sale as the audience IS asking for public inventory; the
|
|
6991
|
+
// route filters the 'public' sentinel out of `channelIds`, so without
|
|
6992
|
+
// this the request would resolve to an empty scope and 422.
|
|
6993
|
+
includePublic: this.previewIncludePublic || audience.some(isPublicChannelId)
|
|
6941
6994
|
});
|
|
6942
6995
|
this.previewSupported = true;
|
|
6943
6996
|
} catch (err) {
|
|
@@ -7138,7 +7191,7 @@ var ChannelsMode = class {
|
|
|
7138
7191
|
const counts = this.previewProjection?.counts;
|
|
7139
7192
|
const summary = counts?.eligible != null && this.previewProjection?.available !== false ? `<div class="slm-ch-alert warn"><span>\u2139</span><span>${counts.eligible.toLocaleString()} seats are buyable
|
|
7140
7193
|
through this access.${this.previewProjection?.includePublic === false ? " Public sale seats are <b>not</b> included in this grant." : ""}</span></div>` : "";
|
|
7141
|
-
const includePublic = current
|
|
7194
|
+
const includePublic = isPublicChannelId(current) ? "" : `
|
|
7142
7195
|
<label class="slm-note" style="display:flex;gap:8px;align-items:center;margin:10px 0">
|
|
7143
7196
|
<input type="checkbox" data-ch-includepublic ${this.previewIncludePublic ? "checked" : ""} />
|
|
7144
7197
|
Also include Public sale seats in this grant
|
|
@@ -7350,7 +7403,7 @@ var ChannelsMode = class {
|
|
|
7350
7403
|
this.lastFocus = null;
|
|
7351
7404
|
}
|
|
7352
7405
|
renderCreateDialog(state) {
|
|
7353
|
-
const taken = (this.list?.channels ?? []).map((channel) => channel.marker
|
|
7406
|
+
const taken = (this.list?.channels ?? []).map((channel) => markerLetter(channel.marker || channel.name, ""));
|
|
7354
7407
|
const suggestion = suggestMarker("", taken);
|
|
7355
7408
|
this.renderScrim(`
|
|
7356
7409
|
<h3 id="slm-ch-dlg-title">Create channel</h3>
|
|
@@ -7408,7 +7461,8 @@ var ChannelsMode = class {
|
|
|
7408
7461
|
try {
|
|
7409
7462
|
const res = await this.host.api.createChannel(this.host.eventKey, {
|
|
7410
7463
|
name: trimmed,
|
|
7411
|
-
|
|
7464
|
+
// The column is free text; we only ever store what the chip can draw.
|
|
7465
|
+
marker: markerLetter(letter, "") || null,
|
|
7412
7466
|
color: color || null,
|
|
7413
7467
|
externalRef: externalRef.trim() || null
|
|
7414
7468
|
});
|
|
@@ -7477,7 +7531,9 @@ var ChannelsMode = class {
|
|
|
7477
7531
|
this.renderDialog();
|
|
7478
7532
|
try {
|
|
7479
7533
|
const result = await this.host.api.applyChannelAssignment(this.host.eventKey, {
|
|
7480
|
-
|
|
7534
|
+
// `null` is the wire spelling of "back to public sale" every worker
|
|
7535
|
+
// accepts, including ones that predate the 'public' sentinel.
|
|
7536
|
+
targetChannelId: isPublicChannelId(this.targetChannelId) ? null : this.targetChannelId,
|
|
7481
7537
|
labels,
|
|
7482
7538
|
assignmentVersion: this.assignmentVersion
|
|
7483
7539
|
});
|
|
@@ -8155,7 +8211,7 @@ var SeatManager = class {
|
|
|
8155
8211
|
if (controlRoom?.activity) this.seedFeed(controlRoom.activity);
|
|
8156
8212
|
else this.api.log(this.key, { limit: 24 }).then((page) => this.seedFeed(page.entries)).catch(() => {
|
|
8157
8213
|
});
|
|
8158
|
-
this.connect();
|
|
8214
|
+
void this.connect();
|
|
8159
8215
|
this.startFeedClock();
|
|
8160
8216
|
this.ready = true;
|
|
8161
8217
|
await this.resolveChannelCapabilities();
|
|
@@ -8579,11 +8635,33 @@ var SeatManager = class {
|
|
|
8579
8635
|
});
|
|
8580
8636
|
}
|
|
8581
8637
|
// ---- realtime -------------------------------------------------------------
|
|
8582
|
-
|
|
8638
|
+
/**
|
|
8639
|
+
* Open the cockpit's realtime socket AS THE ORGANIZER.
|
|
8640
|
+
*
|
|
8641
|
+
* The scope has to be established before the upgrade, because a browser
|
|
8642
|
+
* `WebSocket` cannot send an Authorization header: the manage token is traded
|
|
8643
|
+
* over HTTPS for a one-use ticket which rides in `Sec-WebSocket-Protocol`.
|
|
8644
|
+
* Without it the server treats this socket as an anonymous public buyer and
|
|
8645
|
+
* projects its deltas, so any change inside a private channel allocation is
|
|
8646
|
+
* structurally suppressed and the map silently drifts.
|
|
8647
|
+
*
|
|
8648
|
+
* If the mint fails (an expired token, a worker that predates the route) we
|
|
8649
|
+
* still connect unticketed rather than going dark — the public-sale stream is
|
|
8650
|
+
* worth having, and every `resnapshot()` re-establishes physical truth from
|
|
8651
|
+
* the authenticated HTTP read.
|
|
8652
|
+
*/
|
|
8653
|
+
async connect() {
|
|
8654
|
+
if (this.closed) return;
|
|
8655
|
+
let protocols;
|
|
8656
|
+
try {
|
|
8657
|
+
protocols = (await this.api.subscribeTicket(this.key)).protocols;
|
|
8658
|
+
} catch {
|
|
8659
|
+
protocols = void 0;
|
|
8660
|
+
}
|
|
8583
8661
|
if (this.closed) return;
|
|
8584
8662
|
let ws;
|
|
8585
8663
|
try {
|
|
8586
|
-
ws = new WebSocket(this.api.socketUrl(this.key));
|
|
8664
|
+
ws = protocols ? new WebSocket(this.api.socketUrl(this.key), protocols) : new WebSocket(this.api.socketUrl(this.key));
|
|
8587
8665
|
} catch {
|
|
8588
8666
|
this.scheduleReconnect();
|
|
8589
8667
|
return;
|
|
@@ -8613,7 +8691,7 @@ var SeatManager = class {
|
|
|
8613
8691
|
const delay = Math.min(1e3 * 2 ** Math.min(this.attempt++, 5), 15e3);
|
|
8614
8692
|
this.reconnectTimer = setTimeout(() => {
|
|
8615
8693
|
this.reconnectTimer = null;
|
|
8616
|
-
this.connect();
|
|
8694
|
+
void this.connect();
|
|
8617
8695
|
}, delay);
|
|
8618
8696
|
}
|
|
8619
8697
|
onMessage(e) {
|
|
@@ -8643,7 +8721,7 @@ var SeatManager = class {
|
|
|
8643
8721
|
}
|
|
8644
8722
|
if (m.type === "hidden") return;
|
|
8645
8723
|
if (m.seats && typeof m.seats === "object") {
|
|
8646
|
-
this.applySnapshot(m.seats);
|
|
8724
|
+
this.applySnapshot(m.seats, typeof m.default === "string" ? m.default : void 0);
|
|
8647
8725
|
} else if (Array.isArray(m.changes)) {
|
|
8648
8726
|
const ids = [];
|
|
8649
8727
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -8683,10 +8761,23 @@ var SeatManager = class {
|
|
|
8683
8761
|
} catch {
|
|
8684
8762
|
}
|
|
8685
8763
|
}
|
|
8686
|
-
|
|
8764
|
+
/**
|
|
8765
|
+
* Replace the whole seat model.
|
|
8766
|
+
*
|
|
8767
|
+
* `fallback` is the compact frame's modal status: those snapshots list only
|
|
8768
|
+
* the seats that DIFFER from it, so every other known label takes it. Without
|
|
8769
|
+
* this the omitted majority would silently fall back to `free` — fine when
|
|
8770
|
+
* the mode really is free, wrong the moment it is not.
|
|
8771
|
+
*/
|
|
8772
|
+
applySnapshot(seats, fallback) {
|
|
8773
|
+
const known = (st) => ["free", "held", "booked", "blocked"].includes(st) ? st : "free";
|
|
8687
8774
|
const next = /* @__PURE__ */ new Map();
|
|
8775
|
+
if (fallback !== void 0) {
|
|
8776
|
+
const base = known(fallback);
|
|
8777
|
+
for (const label of this.labelToId.keys()) next.set(label, base);
|
|
8778
|
+
}
|
|
8688
8779
|
for (const [label, st] of Object.entries(seats)) {
|
|
8689
|
-
next.set(label,
|
|
8780
|
+
next.set(label, known(st));
|
|
8690
8781
|
}
|
|
8691
8782
|
this.status = next;
|
|
8692
8783
|
this.lastSyncedAt = Date.now();
|
|
@@ -9906,6 +9997,8 @@ export {
|
|
|
9906
9997
|
createBuyerAccessContext,
|
|
9907
9998
|
createControllerSink,
|
|
9908
9999
|
dropReviewRows,
|
|
10000
|
+
isPublicChannelId,
|
|
10001
|
+
markerLetter,
|
|
9909
10002
|
markerOf,
|
|
9910
10003
|
mutationCount,
|
|
9911
10004
|
needsMoveConfirmation,
|