feat: add Twitch account authorization controls
This commit is contained in:
+51
-1
@@ -12,6 +12,10 @@
|
|||||||
let newPassword = '';
|
let newPassword = '';
|
||||||
let authError = '';
|
let authError = '';
|
||||||
let authBusy = false;
|
let authBusy = false;
|
||||||
|
let oauthLink = '';
|
||||||
|
let oauthIdentity: 'streamer' | 'bot' = 'streamer';
|
||||||
|
let oauthBusy = false;
|
||||||
|
let oauthError = '';
|
||||||
|
|
||||||
async function checkServer() {
|
async function checkServer() {
|
||||||
connectionState = 'CONNECTING';
|
connectionState = 'CONNECTING';
|
||||||
@@ -82,6 +86,33 @@
|
|||||||
sessionToken = '';
|
sessionToken = '';
|
||||||
authenticated = false;
|
authenticated = false;
|
||||||
mustReplaceCredentials = false;
|
mustReplaceCredentials = false;
|
||||||
|
oauthLink = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOAuthLink(identity: 'streamer' | 'bot') {
|
||||||
|
oauthBusy = true;
|
||||||
|
oauthError = '';
|
||||||
|
oauthIdentity = identity;
|
||||||
|
oauthLink = '';
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${serverAddress}/api/v1/twitch/oauth/link?identity=${identity}`, {
|
||||||
|
headers: { authorization: `Bearer ${sessionToken}` },
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('The server could not create the Twitch authorization link.');
|
||||||
|
const result: { authorization_url: string } = await response.json();
|
||||||
|
oauthLink = result.authorization_url;
|
||||||
|
} catch (error) {
|
||||||
|
oauthError = error instanceof Error ? error.message : 'The Twitch authorization link could not be created.';
|
||||||
|
} finally {
|
||||||
|
oauthBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyOAuthLink() {
|
||||||
|
if (!oauthLink) return;
|
||||||
|
await navigator.clipboard.writeText(oauthLink).catch(() => {
|
||||||
|
oauthError = 'Copy was unavailable. Select and copy the link manually.';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -122,7 +153,26 @@
|
|||||||
{#if authError}<div class="auth-error">{authError}</div>{/if}
|
{#if authError}<div class="auth-error">{authError}</div>{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="side-section"><div class="micro">CHANNEL</div><div class="empty-row">No Twitch account connected</div></div>
|
<div class="side-section channel-section">
|
||||||
|
<div class="micro">TWITCH ACCOUNTS</div>
|
||||||
|
{#if authenticated}
|
||||||
|
<div class="oauth-actions">
|
||||||
|
<button class="secondary-action" onclick={() => createOAuthLink('streamer')} disabled={oauthBusy}>CONNECT STREAMER</button>
|
||||||
|
<button class="secondary-action" onclick={() => createOAuthLink('bot')} disabled={oauthBusy}>CONNECT BOT</button>
|
||||||
|
</div>
|
||||||
|
{#if oauthLink}
|
||||||
|
<div class="oauth-link-card">
|
||||||
|
<div class="micro">{oauthIdentity.toUpperCase()} AUTHORIZATION LINK</div>
|
||||||
|
<textarea class="oauth-link" readonly value={oauthLink} aria-label="Twitch authorization link"></textarea>
|
||||||
|
<button class="primary control-button" onclick={copyOAuthLink}>COPY LINK</button>
|
||||||
|
<div class="oauth-hint">Open the copied link in the intended Twitch account session. It expires in 10 minutes.</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if oauthError}<div class="auth-error">{oauthError}</div>{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="empty-row">Sign in as administrator to connect accounts.</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div class="side-section side-footer"><div class="micro">SERVER VERSION</div><div class="mono">{serverVersion}</div></div>
|
<div class="side-section side-footer"><div class="micro">SERVER VERSION</div><div class="mono">{serverVersion}</div></div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,36 @@ label {
|
|||||||
color: var(--ed-dim);
|
color: var(--ed-dim);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
.oauth-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 9px;
|
||||||
|
}
|
||||||
|
.secondary-action {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
color: var(--ed-dim);
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.oauth-link-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--ui-divider);
|
||||||
|
}
|
||||||
|
.oauth-link {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 72px;
|
||||||
|
resize: vertical;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: var(--ed-dim);
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
.oauth-hint {
|
||||||
|
color: var(--ed-dim);
|
||||||
|
font: 9px/1.4 var(--font-data);
|
||||||
|
}
|
||||||
.mono {
|
.mono {
|
||||||
font-family: var(--font-data);
|
font-family: var(--font-data);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user