S7 review: align OAuth UI with S6's actual contract (server-26#225)

- complete: S6 returns streams_stopped, not stopped — the "N radio
  channels were stopped" note never showed.
- start: bot name is nested as bot.name.
- "The bot for a system" with an org-owned preferred bot now sends
  token_id (S6 accepts it today); system_id only for a platform-pinned
  pool bot, which still needs S6 to accept system_id.
- A 404 for one chosen bot no longer hides Connect and the picker
  (the message told the user to try Automatic, but the button was gone).
- Server count uses GET /tokens' per-bot server_cap when present.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-05 02:56:05 -04:00
co-authored by Claude Opus 5.5
parent dd3e5c9655
commit eaa0be07c5
4 changed files with 30 additions and 16 deletions
+2
View File
@@ -30,6 +30,8 @@ export interface RadioBot {
/** "Connect a Discord server" is on for this bot: its OAuth client secret is
* set server-side (server-26#225 S6). The secret itself is never sent. */
oauth_enabled?: boolean | null;
/** GET /tokens (S6): the pool cap for a DRB pool bot, null for a BYO bot. */
server_cap?: number | null;
}
export interface RadioBotAddResult {
+8 -5
View File
@@ -35,7 +35,9 @@ const post = <T,>(path: string, body: object) => send<T>("POST", path, body);
export interface OAuthStartRequest {
/** One of the org's own (BYO) bots, by token_id. */
token_id?: string;
/** The bot marked preferred for this system (works for a DRB bot a platform admin pinned too). */
/** The bot marked preferred for this system, when it is a DRB pool bot a platform
* admin pinned (its token_id is not shown to the org). NEEDS S6 to accept
* system_id — until it does, c2-core ignores it and picks Automatic. */
system_id?: string;
/** Kept for the plan's node-page entry point: the preferred bot of this node's system. */
node_id?: string;
@@ -52,7 +54,7 @@ export interface OAuthStartResult {
export class OAuthNotConfigured extends Error {}
export async function startDiscordOAuth(req: OAuthStartRequest): Promise<OAuthStartResult> {
let raw: { url?: string; authorize_url?: string; bot_name?: string | null; token_name?: string | null };
let raw: { url?: string; authorize_url?: string; bot?: { name?: string | null } | null; bot_name?: string | null };
try {
raw = await post("/discord/oauth/start", req);
} catch (err) {
@@ -63,7 +65,7 @@ export async function startDiscordOAuth(req: OAuthStartRequest): Promise<OAuthSt
// Only ever send the browser to Discord from here — never to a URL that
// merely came back in a response body without being Discord's.
if (!isDiscordAuthorizeUrl(url)) throw new Error("C2 returned an unexpected sign-in link. Nothing was changed.");
return { url, bot_name: raw.bot_name ?? raw.token_name ?? null };
return { url, bot_name: raw.bot?.name ?? raw.bot_name ?? null };
}
export function isDiscordAuthorizeUrl(url: string): boolean {
@@ -104,8 +106,9 @@ export async function completeDiscordOAuth(code: string, state: string): Promise
channels_ready: count(r.channels_ready),
channels_blocked: count(r.channels_blocked),
return_to: safeReturnTo(str(r.return_to)),
bot_name: str(r.bot_name) ?? str(r.token_name),
stopped: count(r.stopped) ?? 0,
bot_name: str(r.bot_name),
// S6 answers streams_stopped (a count).
stopped: count(r.streams_stopped) ?? count(r.stopped) ?? 0,
};
}