diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index 1a4fa11..21ce389 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -286,7 +286,10 @@ }, "quick-links": "Useful links", "link-user-guide": "User guide", - "link-api": "API reference" + "link-api": "API reference", + "problems": { + "eventsub-scope": "{{APPNAME}} needs new permissions in your Twitch app to work correctly.
Click here to re-authenticate." + } }, "onboarding": { "welcome-header": "Welcome to {{APPNAME}}", diff --git a/frontend/src/ui/pages/Dashboard.tsx b/frontend/src/ui/pages/Dashboard.tsx index 19056ec..8db3bd1 100644 --- a/frontend/src/ui/pages/Dashboard.tsx +++ b/frontend/src/ui/pages/Dashboard.tsx @@ -1,4 +1,9 @@ -import { CircleIcon, InfoCircledIcon, UpdateIcon } from '@radix-ui/react-icons'; +import { + CircleIcon, + ExclamationTriangleIcon, + InfoCircledIcon, + UpdateIcon, +} from '@radix-ui/react-icons'; import { Trans, useTranslation } from 'react-i18next'; import { EventSubNotification, @@ -9,11 +14,16 @@ import { useLiveKey, useModule } from '~/lib/react'; import { useAppDispatch, useAppSelector } from '~/store'; import { modules } from '~/store/api/reducer'; import * as HoverCard from '@radix-ui/react-hover-card'; +import { useEffect, useState } from 'react'; +import { main } from '@wailsapp/go/models'; +import { GetProblems, GetTwitchAuthURL } from '@wailsapp/go/main/App'; +import { BrowserOpenURL } from '@wailsapp/runtime/runtime'; import { PageContainer, SectionHeader, styled, TextBlock, + theme, TooltipContent, } from '../theme'; import BrowserLink from '../components/BrowserLink'; @@ -440,8 +450,88 @@ function TwitchSection() { ); } +const ProblemBlock = styled('div', { + border: '2px solid $gray6', + padding: '0.5rem 1rem', + borderRadius: theme.borderRadius.toolbar, + variants: { + severity: { + warn: { + borderColor: '$yellow6', + backgroundColor: '$yellow3', + color: '$yellow12', + svg: { + color: '$yellow11', + }, + }, + }, + }, + display: 'flex', + gap: '1rem', + alignItems: 'center', + lineHeight: '1.4', + svg: { + marginTop: '0.25rem', + }, + a: { + cursor: 'pointer', + }, +}); + function ProblemList() { - return <>; + const [problems, setProblems] = useState([]); + const { t } = useTranslation(); + const kv = useAppSelector((state) => state.api.client); + + useEffect(() => { + void GetProblems().then(setProblems); + }, []); + + const reauthenticate = async () => { + // Wait for re-auth so we can clear the banner + const onKeyChange = () => { + void GetProblems().then(setProblems); + void kv.unsubscribeKey('twitch/auth-keys', onKeyChange); + }; + void kv.subscribeKey('twitch/auth-keys', onKeyChange); + + const url = await GetTwitchAuthURL(); + BrowserOpenURL(url); + }; + + return ( + <> + {problems.map((p) => { + switch (p.id) { + case 'twitch:eventsub_scope': + return ( + + +
+ { + void reauthenticate(); + }} + > + ), + }} + /> +
+
+ ); + default: + return null; + } + })} + + ); } export default function Dashboard(): React.ReactElement { diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 2e06400..076a83a 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -16,6 +16,8 @@ export function GetKilovoltBind():Promise; export function GetLastLogs():Promise>; +export function GetProblems():Promise>; + export function GetTwitchAuthURL():Promise; export function GetTwitchLoggedUser():Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index cea3de4..890f9aa 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -26,6 +26,10 @@ export function GetLastLogs() { return window['go']['main']['App']['GetLastLogs'](); } +export function GetProblems() { + return window['go']['main']['App']['GetProblems'](); +} + export function GetTwitchAuthURL() { return window['go']['main']['App']['GetTwitchAuthURL'](); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 72a84f2..9a70c56 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -92,6 +92,20 @@ export namespace main { this.data = source["data"]; } } + export class Problem { + id: string; + details: any; + + static createFrom(source: any = {}) { + return new Problem(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.details = source["details"]; + } + } export class VersionInfo { release: string; // Go type: debug diff --git a/go.mod b/go.mod index 3298e32..4a4d875 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,8 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) +replace github.com/nicklaw5/helix/v2 => github.com/ashkeel/helix/v2 v2.26.0-chat.2 + require ( github.com/DataDog/zstd v1.4.5 // indirect github.com/Masterminds/goutils v1.1.1 // indirect diff --git a/go.sum b/go.sum index 504727f..e1416d1 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DD github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29/go.mod h1:JYWahgHer+Z2xbsgHPtaDYVWzeHDminu+YIBWkxpCAY= github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab h1:CMGzRRCjnD50RjUFSArBLuCxiDvdp7b8YPAcikBEQ+k= github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab/go.mod h1:nfFtvHn2Hgs9G1u0/J6LHQv//EksNC+7G8vXmd1VTJ8= +github.com/ashkeel/helix/v2 v2.26.0-chat.2 h1:dedyfwwLEAegbeBuyMhvs4X608bN7YBYjuZ6rT5IOTA= +github.com/ashkeel/helix/v2 v2.26.0-chat.2/go.mod h1:zZcKsyyBWDli34x3QleYsVMiiNGMXPAEU5NjsiZDtvY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -253,8 +255,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nicklaw5/helix/v2 v2.26.0 h1:Qkc/R0eCDdWtUmnczk2g03+mObPUfc49Kz2Bt4B5d0g= -github.com/nicklaw5/helix/v2 v2.26.0/go.mod h1:zZcKsyyBWDli34x3QleYsVMiiNGMXPAEU5NjsiZDtvY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= diff --git a/problem.go b/problem.go new file mode 100644 index 0000000..7b7ac70 --- /dev/null +++ b/problem.go @@ -0,0 +1,38 @@ +package main + +import "go.uber.org/zap" + +type ProblemID string + +const ( + ProblemIDTwitchNoApp = "twitch:app_missing" + ProblemIDEventSubNoAuth = "twitch:eventsub_no_auth" + ProblemIDEventSubScope = "twitch:eventsub_scope" +) + +type Problem struct { + ID ProblemID `json:"id"` + Details any `json:"details"` +} + +func (a *App) GetProblems() (problems []Problem) { + problems = []Problem{} + if a.twitchManager != nil { + client := a.twitchManager.Client() + if client != nil { + // Check if the app needs to be authorized again + scopesMatch, err := client.CheckScopes() + if err != nil { + logger.Warn("Could not check scopes for problems", zap.Error(err)) + } else { + if !scopesMatch { + problems = append(problems, Problem{ + ID: ProblemIDEventSubScope, + Details: nil, + }) + } + } + } + } + return +}