@cellarnode/ui 0.169.4 → 0.170.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.
@@ -0,0 +1,98 @@
1
+ import * as React from "react";
2
+ import type * as Ably from "ably";
3
+ import type { ConnectionBannerState } from "../chat/connection-banner.js";
4
+ /**
5
+ * True when `error` is an `Ably.ErrorInfo`-shaped rejection in the
6
+ * token-capability class. Duck-typed on purpose: `@ably/chat` rewraps the
7
+ * underlying `ErrorInfo` (`failed to attach room: …`) preserving
8
+ * `code` / `statusCode`, and test doubles only need to carry those fields.
9
+ */
10
+ export declare function isRoomCapabilityDeniedError(error: unknown): boolean;
11
+ /**
12
+ * Room-level access status for CEL-1919 degradation surfaces.
13
+ *
14
+ * - `"available"` — the room is live (attached, or in a normal pre-attach
15
+ * state). This is the status for the overwhelming majority of rooms.
16
+ * - `"recovering"` — a capability-denied attach was observed and the lib is
17
+ * mid `authorize()` + re-attach cycle. Map to your "reconnecting" banner.
18
+ * - `"denied"` — the retry budget for this failure signal is exhausted; the
19
+ * live subscription is not coming back on this token. Map to your
20
+ * read-only/offline surface: disable the composer (REST history + REST
21
+ * sends still work) and show the connection notice.
22
+ */
23
+ export type ChatRoomAccessStatus = "available" | "recovering" | "denied";
24
+ /** Value returned by {@link useChatRoomAccess}. */
25
+ export interface ChatRoomAccess {
26
+ /** Coarse room-level access status; see {@link ChatRoomAccessStatus}. */
27
+ status: ChatRoomAccessStatus;
28
+ /**
29
+ * The `Ably.ErrorInfo`-shaped error behind a `"recovering"` / `"denied"`
30
+ * status, when the SDK surfaced one. Useful for diagnostics surfaces.
31
+ */
32
+ error?: Ably.ErrorInfo;
33
+ /**
34
+ * Manually re-run the `authorize()` + re-attach cycle, bypassing the
35
+ * once-per-failure automatic budget (the human clicking is the rate
36
+ * limiter; the backend mint limiter is 10/min per user, CEL-1916).
37
+ * No-op while a recovery is already in flight.
38
+ */
39
+ retry: () => void;
40
+ }
41
+ /**
42
+ * Map the room-level access status onto the lib's existing 3-state
43
+ * `ConnectionBannerState` contract, mirroring
44
+ * `ablyConnectionStatusToBannerState()`'s role for connection states. Feed
45
+ * the result to the same `<ConnectionBanner>` consumers already mount:
46
+ *
47
+ * ```tsx
48
+ * const access = useChatRoomAccess();
49
+ * const bannerState = chatRoomAccessToBannerState(access.status);
50
+ * return <ConnectionBanner state={bannerState} onRetry={access.retry} />;
51
+ * ```
52
+ */
53
+ export declare function chatRoomAccessToBannerState(status: ChatRoomAccessStatus): ConnectionBannerState;
54
+ /**
55
+ * Default English copy for the degraded notice `<ChatRoom>` renders when a
56
+ * room is capability-denied. Truthful about what still works on a capped
57
+ * room (REST history + REST sends; only the live subscription is dead).
58
+ * Exposed for consumer test fixtures / i18n alignment, matching the
59
+ * `CONNECTION_BANNER_DEFAULT_LABELS` convention.
60
+ */
61
+ export declare const CHAT_ROOM_DEGRADED_NOTICE = "Live updates are unavailable for this conversation. Messages still send, but new replies appear after reloading.";
62
+ /**
63
+ * Read the room-level access status injected by the nearest `<ChatRoom>`.
64
+ *
65
+ * Outside `<ChatRoom>` (or before the room resolves) this returns
66
+ * `{ status: "available" }` — descendant hooks tolerate missing providers
67
+ * with safe defaults, matching the Phase 5 pre-mount tolerance pattern.
68
+ *
69
+ * Consumer wiring for CEL-1919 (composer lock + localised banner):
70
+ *
71
+ * ```tsx
72
+ * const access = useChatRoomAccess();
73
+ * const canSend = access.status !== "denied" && roomIsActive;
74
+ * // …disable / replace the composer when !canSend, and render your own
75
+ * // <ConnectionBanner state={chatRoomAccessToBannerState(access.status)}
76
+ * // onRetry={access.retry} /> with localised copy.
77
+ * ```
78
+ */
79
+ export declare function useChatRoomAccess(): ChatRoomAccess;
80
+ export interface ChatRoomReauthProps {
81
+ /** Bare V2 `chat_rooms.id` UUID, as passed to `<ChatRoom roomId>`. */
82
+ roomId: string;
83
+ /**
84
+ * When `true`, render NO default degraded notice — for consumers that
85
+ * surface capability-denied rooms through their own (localised)
86
+ * `<ConnectionBanner>` via `useChatRoomAccess()`. Default `false`.
87
+ */
88
+ hideDegradedNotice?: boolean;
89
+ children: React.ReactNode;
90
+ }
91
+ /**
92
+ * CEL-1919 public wrapper: mounts the recovery controller keyed on `roomId`
93
+ * (each room pivot resets the retry budget) inside the caller's
94
+ * `<ChatRoomProvider>` tree. Rendered by `<ChatRoom>` itself; exported only
95
+ * so `<ChatRoom>`'s own file stays focused on provider wiring.
96
+ */
97
+ export declare function ChatRoomReauth({ roomId, hideDegradedNotice, children, }: ChatRoomReauthProps): React.ReactElement;
98
+ //# sourceMappingURL=chat-room-reauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-room-reauth.d.ts","sourceRoot":"","sources":["../../src/chat-ably/chat-room-reauth.tsx"],"names":[],"mappings":"AAwEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,KAAK,IAAI,MAAM,MAAM,CAAC;AAGlC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAsB1E;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAkBnE;AAMD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEzE,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,EAAE,oBAAoB,CAAC;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;IACvB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,GAAG,qBAAqB,CAS/F;AAED;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,qHAC8E,CAAC;AAWrH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAElD;AA+ND,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,kBAA0B,EAC1B,QAAQ,GACT,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAM1C"}
@@ -0,0 +1,356 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // CEL-1919 — per-room re-auth for orgs beyond the 50-room token capability cap.
3
+ //
4
+ // Backend context (cellarnode-backend-v2, `features/ably/service.ts`): org-user
5
+ // tokens carry explicit `chat:{chatRoomId}::$chat` capabilities for only the
6
+ // `ABLY_CHAT_ROOM_CAPABILITY_CAP` (50) most-recent ACTIVE chat rooms, ordered
7
+ // by `last_message_at DESC`. Older-but-active rooms are silently cut from the
8
+ // JWT (WARN-only server-side), so attaching to one fails with an Ably
9
+ // token-capability error (40160 / 40161 class, HTTP 401). The backend's own
10
+ // comment names this module's behaviour as the documented mitigation.
11
+ //
12
+ // What this module adds inside `<ChatRoom>`:
13
+ //
14
+ // 1. DETECT — watch the SDK room status; a `failed` room whose error is in
15
+ // the token-capability class (not a network drop, not a disposed client)
16
+ // is a capability-denied attach.
17
+ // 2. RETRY ONCE — call `realtime.auth.authorize()` (the same escape hatch
18
+ // `useAblyAuthorize()` wraps in `@cellarnode/ui/realtime`) to mint a
19
+ // fresh token, then re-attach the room with it. Bounded to ONE automatic
20
+ // attempt per failure signal: a second capability denial after the retry
21
+ // means the fresh token still omits the room, and hammering the mint
22
+ // endpoint would only trip the per-user rate limiter (CEL-1916, 10/min).
23
+ // 3. DEGRADE — once denied, surface a visible (dismissible) notice through
24
+ // the existing `<ConnectionBanner>` machinery and expose
25
+ // `useChatRoomAccess()` so consumers can disable their composer /
26
+ // localise the copy. REST history (`historyFetch`) and REST sends
27
+ // (`sendMessageRest`) keep working on a capped room — only the live
28
+ // channel subscription is dead — so the room degrades to
29
+ // "stored history, delayed replies", never a broken shell.
30
+ //
31
+ // ── Why the current retry usually fails, and the backend follow-up ──────────
32
+ //
33
+ // `POST /ably-token` today takes an EMPTY body (`AblyTokenRequest = t.Void()`)
34
+ // and always mints the same org-wide map (top-50 recency window). An
35
+ // `authorize()` therefore returns a token with the SAME capability set unless
36
+ // the room's recency moved it into the window between mints. The retry still
37
+ // lands for those genuine recency races (new message lands on the older room,
38
+ // user re-opens it seconds later) and costs one mint; for a truly capped room
39
+ // it fails again and we degrade. The COMPLETE fix needs the mint endpoint to
40
+ // accept a room hint so a re-auth can return a token scoped for the attempted
41
+ // room. Precisely (backend change, NOT in this PR):
42
+ //
43
+ // - Accept an optional body/query param on `POST /ably-token`, e.g.
44
+ // `{ roomId?: string }` (TypeBox: extend `AblyTokenRequest` from
45
+ // `t.Void()` to `t.Optional(t.Object({ roomId: t.String({ format: "uuid" }) }))`
46
+ // — Elysia must keep tolerating an absent body).
47
+ // - In `resolveCapabilities`'s org-user branch, when `roomId` is present and
48
+ // the org participates in that room (same producer/importer-org SQL guard
49
+ // as `chatCapabilitiesForOrgUser`), UNION the per-room capability
50
+ // `chat:{roomId}::$chat` into the minted map on top of the top-50 window.
51
+ // - Once shipped, this client can pass the hint: `authorize()` accepts
52
+ // `TokenParams` (`realtime.auth.authorize({ capability })` is too coarse —
53
+ // the clean wire shape is the authCallback reading a room hint, i.e. the
54
+ // `<AblyProvider>` `authCallback` gaining an optional `tokenParams.roomId`
55
+ // passthrough). Until then the client-side retry + graceful degrade below
56
+ // is the mitigation.
57
+ //
58
+ // ── Module placement ─────────────────────────────────────────────────────────
59
+ //
60
+ // The controller mounts INSIDE `<ChatRoomProvider>` (it needs `useRoom()`) and
61
+ // reads the `Ably.Realtime` from `ably/react`'s context defensively — the same
62
+ // CEL-260 pattern as `realtime/use-connection-state.ts` — rather than calling
63
+ // `useAbly()`, which throws outside `<AblyProvider>`. `<ChatRoom>`'s
64
+ // documented mount contract already requires both providers, but the
65
+ // defensive read keeps unit harnesses (and mis-wired consumers) degrading to
66
+ // "no auto-retry" instead of crashing the tree. That covers a missing
67
+ // provider VALUE; the context object itself is ALSO resolved defensively
68
+ // (see `ablyReactContext` below) so harnesses that stub `ably/react` at the
69
+ // module boundary without exporting `AblyContext` — e.g. the messenger-screen
70
+ // composite tests — render through an inert controller instead of throwing
71
+ // mid-render. It also avoids a runtime
72
+ // dependency from the `chat-ably` subpath onto the `realtime` subpath.
73
+ import * as React from "react";
74
+ import * as AblyReact from "ably/react";
75
+ import { useRoom } from "@ably/chat/react";
76
+ import { ConnectionBanner } from "../chat/connection-banner.js";
77
+ // ────────────────────────────────────────────────────────────────────────────
78
+ // Capability-denied classification.
79
+ // ────────────────────────────────────────────────────────────────────────────
80
+ /**
81
+ * Ably error codes that mean "your token does not grant this channel":
82
+ *
83
+ * - `40160` — ability denied to participate in channel (capability check
84
+ * failed on ATTACH). This is what a room beyond the top-50 recency window
85
+ * fails with.
86
+ * - `40161` — ability denied to participate in channel (capability in token
87
+ * invalid / unparsable). Same remedy: a fresh, well-formed token.
88
+ *
89
+ * Deliberately NOT included: `40142` (token expired) — the SDK renews expired
90
+ * tokens itself and retries, long before this room-level recovery runs; and
91
+ * the 40300-range request-size / connection caps, which have nothing to do
92
+ * with per-room capabilities.
93
+ */
94
+ const ROOM_CAPABILITY_DENIED_CODES = new Set([40160, 40161]);
95
+ /**
96
+ * True when `error` is an `Ably.ErrorInfo`-shaped rejection in the
97
+ * token-capability class. Duck-typed on purpose: `@ably/chat` rewraps the
98
+ * underlying `ErrorInfo` (`failed to attach room: …`) preserving
99
+ * `code` / `statusCode`, and test doubles only need to carry those fields.
100
+ */
101
+ export function isRoomCapabilityDeniedError(error) {
102
+ if (!error || typeof error !== "object")
103
+ return false;
104
+ const { code, statusCode, message } = error;
105
+ if (typeof code === "number" && ROOM_CAPABILITY_DENIED_CODES.has(code)) {
106
+ return true;
107
+ }
108
+ // Defensive net: some transport layers rewrap the ErrorInfo and lose the
109
+ // numeric code while keeping Ably's HTTP 401 + the capability wording.
110
+ // Kept narrow ("expired" must NOT match) so token-expiry stays out.
111
+ return (statusCode === 401 &&
112
+ typeof message === "string" &&
113
+ /capabilit|denied|permission/i.test(message));
114
+ }
115
+ /**
116
+ * Map the room-level access status onto the lib's existing 3-state
117
+ * `ConnectionBannerState` contract, mirroring
118
+ * `ablyConnectionStatusToBannerState()`'s role for connection states. Feed
119
+ * the result to the same `<ConnectionBanner>` consumers already mount:
120
+ *
121
+ * ```tsx
122
+ * const access = useChatRoomAccess();
123
+ * const bannerState = chatRoomAccessToBannerState(access.status);
124
+ * return <ConnectionBanner state={bannerState} onRetry={access.retry} />;
125
+ * ```
126
+ */
127
+ export function chatRoomAccessToBannerState(status) {
128
+ switch (status) {
129
+ case "denied":
130
+ return "offline";
131
+ case "recovering":
132
+ return "reconnecting";
133
+ case "available":
134
+ return "connected";
135
+ }
136
+ }
137
+ /**
138
+ * Default English copy for the degraded notice `<ChatRoom>` renders when a
139
+ * room is capability-denied. Truthful about what still works on a capped
140
+ * room (REST history + REST sends; only the live subscription is dead).
141
+ * Exposed for consumer test fixtures / i18n alignment, matching the
142
+ * `CONNECTION_BANNER_DEFAULT_LABELS` convention.
143
+ */
144
+ export const CHAT_ROOM_DEGRADED_NOTICE = "Live updates are unavailable for this conversation. Messages still send, but new replies appear after reloading.";
145
+ // ────────────────────────────────────────────────────────────────────────────
146
+ // Internal context.
147
+ // ────────────────────────────────────────────────────────────────────────────
148
+ const ChatRoomAccessContext = React.createContext({
149
+ status: "available",
150
+ retry: () => { },
151
+ });
152
+ /**
153
+ * Read the room-level access status injected by the nearest `<ChatRoom>`.
154
+ *
155
+ * Outside `<ChatRoom>` (or before the room resolves) this returns
156
+ * `{ status: "available" }` — descendant hooks tolerate missing providers
157
+ * with safe defaults, matching the Phase 5 pre-mount tolerance pattern.
158
+ *
159
+ * Consumer wiring for CEL-1919 (composer lock + localised banner):
160
+ *
161
+ * ```tsx
162
+ * const access = useChatRoomAccess();
163
+ * const canSend = access.status !== "denied" && roomIsActive;
164
+ * // …disable / replace the composer when !canSend, and render your own
165
+ * // <ConnectionBanner state={chatRoomAccessToBannerState(access.status)}
166
+ * // onRetry={access.retry} /> with localised copy.
167
+ * ```
168
+ */
169
+ export function useChatRoomAccess() {
170
+ return React.use(ChatRoomAccessContext);
171
+ }
172
+ // ────────────────────────────────────────────────────────────────────────────
173
+ // Controller component.
174
+ // ────────────────────────────────────────────────────────────────────────────
175
+ /**
176
+ * Resolve `ably/react`'s `AblyContext` ONCE at module scope, defensively.
177
+ *
178
+ * Two distinct "no authorize surface" conditions must not crash the tree:
179
+ *
180
+ * 1. No `<AblyProvider>` mounted — the real `AblyContext`'s VALUE is `{}`,
181
+ * handled by `realtimeFromContext` below (same CEL-260 pattern as
182
+ * `realtime/ably-react-context.ts`).
183
+ * 2. The ambient `ably/react` module surface doesn't EXPOSE a context
184
+ * object at all. Unit harnesses that stub `ably/react` at the module
185
+ * boundary (the messenger-screen composite tests define only `useAbly`)
186
+ * throw on the missing-export access — under Vitest that throw happens
187
+ * on the import-binding read itself, i.e. mid-render, taking the whole
188
+ * messenger tree down. Resolve through a namespace access inside
189
+ * try/catch so this degrades to "context unavailable" instead.
190
+ *
191
+ * In production `ably/react` always exports `AblyContext`, so the try/catch
192
+ * is unreachable and the resolved value is the real context.
193
+ */
194
+ const ablyReactContext = (() => {
195
+ try {
196
+ const ctx = AblyReact.AblyContext;
197
+ return ctx !== null && typeof ctx === "object" ? ctx : null;
198
+ }
199
+ catch {
200
+ return null;
201
+ }
202
+ })();
203
+ /**
204
+ * Inert stand-in read when `ably/react` exposes no usable context, so the
205
+ * `use()` call in the controller stays unconditional (rules of hooks). Its
206
+ * value is always `null`, which `realtimeFromContext` maps to "no recovery
207
+ * surface" — recovery disabled, children unaffected.
208
+ */
209
+ const NoAblySurfaceContext = React.createContext(null);
210
+ /**
211
+ * Read the `Ably.Realtime` from `ably/react`'s context WITHOUT `useAbly()`,
212
+ * which throws when no `<AblyProvider>` is mounted. Same CEL-260 defensive
213
+ * pattern as `realtime/ably-react-context.ts`, inlined here so the `chat-ably`
214
+ * subpath carries no runtime dependency on the `realtime` subpath.
215
+ */
216
+ function realtimeFromContext(ctx) {
217
+ if (!ctx || typeof ctx !== "object")
218
+ return null;
219
+ const slot = ctx["default"];
220
+ if (!slot || typeof slot !== "object")
221
+ return null;
222
+ const client = slot.client;
223
+ if (!client || typeof client !== "object")
224
+ return null;
225
+ return client;
226
+ }
227
+ /**
228
+ * Mounts inside `<ChatRoomProvider>`. Owns the CEL-1919 recovery cycle:
229
+ * detect capability-denied attach → one `authorize()` + re-attach → degrade.
230
+ *
231
+ * The status surfaced through {@link useChatRoomAccess} is DERIVED (failure
232
+ * signal + retry outcome), not mirrored SDK state:
233
+ *
234
+ * room failed + capability-denied + not yet re-denied → `"recovering"`
235
+ * room failed + capability-denied + retry exhausted → `"denied"`
236
+ * anything else → `"available"`
237
+ *
238
+ * Because "recovering" is derived from the failure itself, no setState has
239
+ * to happen synchronously inside the detection effect (the repo's
240
+ * `set-state-in-effect` discipline); the only committed state is the retry
241
+ * outcome, written from async continuations after `await` boundaries.
242
+ */
243
+ function ChatRoomReauthController({ roomId, hideDegradedNotice, children, }) {
244
+ const { roomStatus, roomError, attach } = useRoom();
245
+ // CEL-260 defensive read — see ablyReactContext above. When no
246
+ // `<AblyProvider>` is mounted the context VALUE is `{}` (the exact
247
+ // condition that crashes `useAbly()`); when the ambient `ably/react`
248
+ // surface is partial (module-boundary test doubles) `ablyReactContext`
249
+ // itself is null and we read the inert stand-in instead. Either way the
250
+ // helper yields null and recovery stays disabled rather than crashing the
251
+ // messenger tree.
252
+ const realtime = realtimeFromContext(React.use(ablyReactContext ?? NoAblySurfaceContext));
253
+ // Retry outcome for the CURRENT failure signal. "pending" = no exhausted
254
+ // retry yet (the automatic budget is unspent / recovery never needed).
255
+ // Reset by remount (roomId key), never written synchronously in an effect.
256
+ const [outcome, setOutcome] = React.useState("pending");
257
+ // Re-entry guard: true while an authorize+attach cycle is in flight.
258
+ // Lives in a ref because it only needs to be read by effect continuations
259
+ // and event handlers, never by render.
260
+ const inFlightRef = React.useRef(false);
261
+ const capabilityDenied = roomStatus === "failed" && isRoomCapabilityDeniedError(roomError);
262
+ const accessStatus = capabilityDenied
263
+ ? outcome === "denied"
264
+ ? "denied"
265
+ : "recovering"
266
+ : "available";
267
+ /**
268
+ * One authorize + re-attach cycle. Resolves true only when the room
269
+ * ended up attached. Never throws: every failure path resolves false so
270
+ * both the automatic effect and the manual `retry()` share it safely.
271
+ */
272
+ const runRecovery = React.useCallback(async () => {
273
+ if (!realtime)
274
+ return false;
275
+ try {
276
+ // The same escape hatch `useAblyAuthorize()` wraps: force the SDK to
277
+ // re-call its authCallback (→ POST /ably-token) and upgrade the
278
+ // connection to the fresh token before we re-attach.
279
+ await realtime.auth.authorize();
280
+ }
281
+ catch (err) {
282
+ console.warn(`[ChatRoom] per-room re-authorize failed (CEL-1919, roomId=${roomId})`, err);
283
+ return false;
284
+ }
285
+ try {
286
+ // `attach()` no-ops when already attached and rejects when the retry
287
+ // is denied again — exactly the signal we branch on.
288
+ await attach();
289
+ return true;
290
+ }
291
+ catch (err) {
292
+ console.warn(`[ChatRoom] room re-attach still denied after re-authorize (CEL-1919, roomId=${roomId})`, err);
293
+ return false;
294
+ }
295
+ }, [realtime, attach, roomId]);
296
+ // Automatic retry — fires at most once per failure signal:
297
+ // - `inFlightRef` blocks re-entry while a cycle runs;
298
+ // - `outcome === "denied"` blocks a second automatic attempt after the
299
+ // retry failed (a second identical mint would only trip CEL-1916's
300
+ // limiter);
301
+ // - a room pivot remounts the controller (keyed on roomId), which
302
+ // resets both, giving each room its own budget;
303
+ // - a LATER capability failure after a SUCCESSFUL retry (only possible
304
+ // under a newer token generation — attaching proves the active token
305
+ // included the room) sees `outcome` still "pending" and gets its own
306
+ // attempt. That is the "max one retry per roomId per token
307
+ // generation" contract, implemented against observable signals only.
308
+ React.useEffect(() => {
309
+ if (!capabilityDenied || outcome !== "pending" || inFlightRef.current) {
310
+ return;
311
+ }
312
+ inFlightRef.current = true;
313
+ void (async () => {
314
+ const attached = await runRecovery();
315
+ inFlightRef.current = false;
316
+ if (!attached) {
317
+ // Written from an async continuation (post-`await`), not
318
+ // synchronously in the effect body — keeps the repo's
319
+ // set-state-in-effect discipline intact.
320
+ setOutcome("denied");
321
+ }
322
+ // On success the SDK flips roomStatus to "attached"; the derived
323
+ // access status follows on the next render. No state to write.
324
+ })();
325
+ }, [capabilityDenied, outcome, runRecovery]);
326
+ /** Manual retry (banner Retry button / consumer surfaces). */
327
+ const retry = React.useCallback(() => {
328
+ if (inFlightRef.current)
329
+ return;
330
+ inFlightRef.current = true;
331
+ void (async () => {
332
+ const attached = await runRecovery();
333
+ inFlightRef.current = false;
334
+ // Reset the outcome so a successful retry clears "denied" (the SDK
335
+ // status change drives the derived status), and a failed retry
336
+ // re-marks it.
337
+ setOutcome(attached ? "pending" : "denied");
338
+ })();
339
+ }, [runRecovery]);
340
+ const access = React.useMemo(() => ({
341
+ status: accessStatus,
342
+ error: capabilityDenied ? roomError : undefined,
343
+ retry,
344
+ }), [accessStatus, capabilityDenied, roomError, retry]);
345
+ return (_jsxs(ChatRoomAccessContext, { value: access, children: [!hideDegradedNotice && accessStatus !== "available" ? (_jsx(ConnectionBanner, { state: chatRoomAccessToBannerState(accessStatus), onRetry: accessStatus === "denied" ? retry : undefined, offlineMessage: CHAT_ROOM_DEGRADED_NOTICE })) : null, children] }));
346
+ }
347
+ /**
348
+ * CEL-1919 public wrapper: mounts the recovery controller keyed on `roomId`
349
+ * (each room pivot resets the retry budget) inside the caller's
350
+ * `<ChatRoomProvider>` tree. Rendered by `<ChatRoom>` itself; exported only
351
+ * so `<ChatRoom>`'s own file stays focused on provider wiring.
352
+ */
353
+ export function ChatRoomReauth({ roomId, hideDegradedNotice = false, children, }) {
354
+ return (_jsx(ChatRoomReauthController, { roomId: roomId, hideDegradedNotice: hideDegradedNotice, children: children }, roomId));
355
+ }
356
+ //# sourceMappingURL=chat-room-reauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-room-reauth.js","sourceRoot":"","sources":["../../src/chat-ably/chat-room-reauth.tsx"],"names":[],"mappings":"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"}
@@ -57,6 +57,16 @@ export interface ChatRoomProps {
57
57
  * `<ChatMessageList>` virtualization story.
58
58
  */
59
59
  historyPageSize?: number;
60
+ /**
61
+ * CEL-1919 — suppress the default degraded notice `<ChatRoom>` renders
62
+ * when the room is capability-denied (org beyond the backend's 50-room
63
+ * token cap). Pass `true` when the consumer surfaces denial through its
64
+ * own localised `<ConnectionBanner>` driven by `useChatRoomAccess()` +
65
+ * `chatRoomAccessToBannerState()`. The detect → authorize → re-attach →
66
+ * degrade cycle itself always runs; this prop only hides the default
67
+ * (English) notice. Default `false`.
68
+ */
69
+ hideDegradedNotice?: boolean;
60
70
  }
61
71
  /**
62
72
  * `<ChatRoom>` mounts the SDK's `<ChatRoomProvider>` for a given room.
@@ -67,7 +77,7 @@ export interface ChatRoomProps {
67
77
  *
68
78
  * @see Spec §4.2, Arch §2.5
69
79
  */
70
- export declare function ChatRoom({ roomId, currentUser, children, sendMessageRest, historyFetch, historyPageSize, }: ChatRoomProps): React.ReactElement;
80
+ export declare function ChatRoom({ roomId, currentUser, children, sendMessageRest, historyFetch, historyPageSize, hideDegradedNotice, }: ChatRoomProps): React.ReactElement;
71
81
  /**
72
82
  * Reads the current user injected by the nearest `<ChatRoom>` ancestor.
73
83
  * Returns `undefined` when called outside `<ChatRoom>` — descendant
@@ -1 +1 @@
1
- {"version":3,"file":"chat-room.d.ts","sourceRoot":"","sources":["../../src/chat-ably/chat-room.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAM7B,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,EAAE;QACX;;;WAGG;QACH,EAAE,EAAE,MAAM,CAAC;QACX,gCAAgC;QAChC,IAAI,EAAE,MAAM,CAAC;QACb,2BAA2B;QAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;;;OAIG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EACN,WAAW,EACX,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,eAAe,GAChB,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY,CAyEpC;AAeD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,aAAa,CAAC,aAAa,CAAC,GAAG,SAAS,CAE1E"}
1
+ {"version":3,"file":"chat-room.d.ts","sourceRoot":"","sources":["../../src/chat-ably/chat-room.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAO7B,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,EAAE;QACX;;;WAGG;QACH,EAAE,EAAE,MAAM,CAAC;QACX,gCAAgC;QAChC,IAAI,EAAE,MAAM,CAAC;QACb,2BAA2B;QAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;;;OAIG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EACN,WAAW,EACX,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,eAAe,EACf,kBAAkB,GACnB,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY,CA+EpC;AAeD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,aAAa,CAAC,aAAa,CAAC,GAAG,SAAS,CAE1E"}
@@ -37,6 +37,7 @@ import * as React from "react";
37
37
  import { ChatRoomProvider } from "@ably/chat/react";
38
38
  import { MessageReactionType } from "@ably/chat";
39
39
  import { ChatRestCallbacksProvider, } from "./use-chat-messages.js";
40
+ import { ChatRoomReauth } from "./chat-room-reauth.js";
40
41
  // ────────────────────────────────────────────────────────────────────────────
41
42
  // Component implementation.
42
43
  // ────────────────────────────────────────────────────────────────────────────
@@ -49,7 +50,7 @@ import { ChatRestCallbacksProvider, } from "./use-chat-messages.js";
49
50
  *
50
51
  * @see Spec §4.2, Arch §2.5
51
52
  */
52
- export function ChatRoom({ roomId, currentUser, children, sendMessageRest, historyFetch, historyPageSize, }) {
53
+ export function ChatRoom({ roomId, currentUser, children, sendMessageRest, historyFetch, historyPageSize, hideDegradedNotice, }) {
53
54
  // RoomOptions MUST be memoized — a fresh reference on every render
54
55
  // causes the SDK to release+recreate the Room. Empty deps array keeps
55
56
  // the reference stable across the lifetime of the wrapper. If a future
@@ -101,7 +102,7 @@ export function ChatRoom({ roomId, currentUser, children, sendMessageRest, histo
101
102
  // `chat:chat:{roomId}::$chat` attach, which Ably correctly rejects because
102
103
  // the JWT only grants `chat:{roomId}::$chat`.
103
104
  const sdkRoomName = roomId.startsWith("chat:") ? roomId : `chat:${roomId}`;
104
- return (_jsx(ChatRoomProvider, { name: sdkRoomName, options: roomOptions, children: _jsx(ChatRestCallbacksProvider, { value: restCallbacks, children: _jsx(ChatRoomUserContext, { value: userContextValue, children: children }) }) }));
105
+ return (_jsx(ChatRoomProvider, { name: sdkRoomName, options: roomOptions, children: _jsx(ChatRoomReauth, { roomId: roomId, hideDegradedNotice: hideDegradedNotice ?? false, children: _jsx(ChatRestCallbacksProvider, { value: restCallbacks, children: _jsx(ChatRoomUserContext, { value: userContextValue, children: children }) }) }) }));
105
106
  }
106
107
  // ────────────────────────────────────────────────────────────────────────────
107
108
  // Internal context — exposes `currentUser` to descendant hooks.
@@ -1 +1 @@
1
- {"version":3,"file":"chat-room.js","sourceRoot":"","sources":["../../src/chat-ably/chat-room.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-room.js","sourceRoot":"","sources":["../../src/chat-ably/chat-room.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  export { ChatRoom, useChatRoomUser } from "./chat-room.js";
2
2
  export type { ChatRoomProps } from "./chat-room.js";
3
+ export { isRoomCapabilityDeniedError, useChatRoomAccess, chatRoomAccessToBannerState, CHAT_ROOM_DEGRADED_NOTICE, } from "./chat-room-reauth.js";
4
+ export type { ChatRoomAccess, ChatRoomAccessStatus } from "./chat-room-reauth.js";
3
5
  export { ManagedChatClientProvider } from "./managed-chat-client.js";
4
6
  export type { ManagedChatClientProviderProps } from "./managed-chat-client.js";
5
7
  export { useChatMessages } from "./use-chat-messages.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chat-ably/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAE5E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EACV,uBAAuB,EACvB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAE3E,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAMpF,OAAO,EAAE,iCAAiC,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEhG,OAAO,EACL,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chat-ably/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAM/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAE5E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EACV,uBAAuB,EACvB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAE3E,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAMpF,OAAO,EAAE,iCAAiC,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEhG,OAAO,EACL,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
@@ -9,6 +9,11 @@
9
9
  // (`createChatWs`, `useChat`) is `@deprecated` in
10
10
  // `@cellarnode/ui/chat` and will be removed in a future major.
11
11
  export { ChatRoom, useChatRoomUser } from "./chat-room.js";
12
+ // CEL-1919 — per-room re-auth + graceful degradation for orgs beyond the
13
+ // backend's 50-room token capability cap. The recovery cycle itself is
14
+ // internal to <ChatRoom>; consumers read the outcome via useChatRoomAccess()
15
+ // and can map it straight onto their existing <ConnectionBanner>.
16
+ export { isRoomCapabilityDeniedError, useChatRoomAccess, chatRoomAccessToBannerState, CHAT_ROOM_DEGRADED_NOTICE, } from "./chat-room-reauth.js";
12
17
  // CEL-978 — StrictMode-safe managed `ChatClient` provider. Replaces the
13
18
  // hand-rolled `useMemo(new ChatClient) + dispose()` wiring both dashboards
14
19
  // duplicated, which disposed the client on a StrictMode remount and left the
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat-ably/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,gEAAgE;AAChE,mEAAmE;AACnE,+DAA+D;AAC/D,0DAA0D;AAC1D,wDAAwD;AACxD,kDAAkD;AAClD,+DAA+D;AAE/D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGxD,wEAAwE;AACxE,2EAA2E;AAC3E,6EAA6E;AAC7E,iEAAiE;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAGlE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAWtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,oEAAoE;AACpE,qEAAqE;AACrE,uEAAuE;AACvE,oBAAoB;AACpB,OAAO,EAAE,iCAAiC,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEhG,OAAO,EACL,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat-ably/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,gEAAgE;AAChE,mEAAmE;AACnE,+DAA+D;AAC/D,0DAA0D;AAC1D,wDAAwD;AACxD,kDAAkD;AAClD,+DAA+D;AAE/D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGxD,yEAAyE;AACzE,uEAAuE;AACvE,6EAA6E;AAC7E,kEAAkE;AAClE,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAG5B,wEAAwE;AACxE,2EAA2E;AAC3E,6EAA6E;AAC7E,iEAAiE;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAGlE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAWtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,oEAAoE;AACpE,qEAAqE;AACrE,uEAAuE;AACvE,oBAAoB;AACpB,OAAO,EAAE,iCAAiC,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEhG,OAAO,EACL,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cellarnode/ui",
3
- "version": "0.169.4",
3
+ "version": "0.170.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",