show OAuth scope status

This commit is contained in:
2026-10-25 05:05:30 +01:00
parent c256ce36e5
commit ba84f9d778
2 changed files with 43 additions and 0 deletions
+28
View File
@@ -60,6 +60,10 @@
let oauthIdentity: 'streamer' | 'bot' = 'streamer';
let oauthBusy = false;
let oauthError = '';
let oauthScopes: Record<'streamer' | 'bot', { connected: boolean; missing_scopes: string[]; reauthorization_required: boolean }> = {
streamer: { connected: false, missing_scopes: [], reauthorization_required: false },
bot: { connected: false, missing_scopes: [], reauthorization_required: false },
};
async function checkServer() {
connectionState = 'CONNECTING';
@@ -106,6 +110,7 @@
await loadActivity();
await loadNotifications();
await loadDiagnostics();
await loadOAuthStatus();
await loadCollaboration();
await loadStreamInfo();
connectRealtime();
@@ -491,6 +496,21 @@
}
}
async function loadOAuthStatus() {
for (const identity of ['streamer', 'bot'] as const) {
try {
const response = await fetch(`${serverAddress}/api/v1/twitch/oauth/status?identity=${identity}`, {
headers: { authorization: `Bearer ${sessionToken}` },
});
if (!response.ok) continue;
const result = await response.json();
oauthScopes = { ...oauthScopes, [identity]: result };
} catch {
// The diagnostics panel remains usable if scope status is temporarily unavailable.
}
}
}
async function revokeOAuth(identity: 'streamer' | 'bot') {
oauthBusy = true;
oauthError = '';
@@ -500,6 +520,7 @@
headers: { authorization: `Bearer ${sessionToken}` },
});
if (!response.ok) throw new Error('The server could not revoke this Twitch authorization.');
await loadOAuthStatus();
await loadStreamInfo();
} catch (error) {
oauthError = error instanceof Error ? error.message : 'The Twitch authorization could not be revoked.';
@@ -561,6 +582,13 @@
<button class="secondary-action" onclick={() => revokeOAuth('streamer')} disabled={oauthBusy}>DISCONNECT STREAMER</button>
<button class="secondary-action" onclick={() => createOAuthLink('bot')} disabled={oauthBusy}>CONNECT BOT</button>
<button class="secondary-action" onclick={() => revokeOAuth('bot')} disabled={oauthBusy}>DISCONNECT BOT</button>
<button class="secondary-action" onclick={loadOAuthStatus} disabled={oauthBusy}>REFRESH SCOPE STATUS</button>
</div>
<div class="oauth-status-grid">
{#each ['streamer', 'bot'] as identity}
<div class="oauth-status-row"><span class="micro">{identity.toUpperCase()}</span><span class:good={oauthScopes[identity as 'streamer' | 'bot'].connected} class="mono">{oauthScopes[identity as 'streamer' | 'bot'].connected ? 'READY' : oauthScopes[identity as 'streamer' | 'bot'].reauthorization_required ? 'REAUTHORIZE' : 'NOT CONNECTED'}</span></div>
{#if oauthScopes[identity as 'streamer' | 'bot'].missing_scopes.length}<div class="oauth-hint">Missing: {oauthScopes[identity as 'streamer' | 'bot'].missing_scopes.join(', ')}</div>{/if}
{/each}
</div>
{#if oauthLink}
<div class="oauth-link-card">
+15
View File
@@ -463,6 +463,21 @@ label {
gap: 6px;
margin-top: 9px;
}
.oauth-status-grid {
display: grid;
gap: 6px;
margin-top: 10px;
padding-top: 9px;
border-top: 1px solid var(--ui-divider);
}
.oauth-status-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.oauth-status-row .good {
color: var(--ed-green);
}
.secondary-action {
width: 100%;
padding: 6px;