1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-20 02:00:49 +00:00
strimertul/frontend/src/ui/pages/BackendIntegration.tsx

330 lines
8.9 KiB
TypeScript
Raw Normal View History

2021-12-16 16:01:24 +00:00
import React, { useEffect, useState } from 'react';
2021-12-15 11:12:19 +00:00
import { Trans, useTranslation } from 'react-i18next';
2021-12-16 16:01:24 +00:00
import { useDispatch, useSelector } from 'react-redux';
2021-12-15 11:12:19 +00:00
import { toast } from 'react-toastify';
2021-12-16 16:01:24 +00:00
import { ExternalLinkIcon } from '@radix-ui/react-icons';
2021-12-15 11:12:19 +00:00
import { useModule, useStatus } from '../../lib/react-utils';
import Stulbe from '../../lib/stulbe-lib';
import apiReducer, { modules } from '../../store/api/reducer';
import SaveButton from '../components/utils/SaveButton';
import {
Button,
ButtonGroup,
Field,
InputBox,
Label,
PageContainer,
PageHeader,
PageTitle,
2021-12-16 16:01:24 +00:00
SectionHeader,
styled,
TabButton,
2021-12-18 02:25:00 +00:00
TabContainer,
2021-12-16 16:01:24 +00:00
TabContent,
TabList,
2021-12-18 02:25:00 +00:00
TextBlock,
2021-12-15 11:12:19 +00:00
} from '../theme';
2021-12-16 16:01:24 +00:00
import eventsubTests from '../../data/eventsub-tests';
import { RootState } from '../../store';
2021-12-15 11:12:19 +00:00
2021-12-16 16:01:24 +00:00
interface UserData {
id: string;
login: string;
// eslint-disable-next-line camelcase
display_name: string;
// eslint-disable-next-line camelcase
profile_image_url: string;
}
interface SyncError {
ok: false;
error: string;
}
const eventSubTestFn = {
'channel.update': (send) => {
send(eventsubTests['channel.update']);
},
'channel.follow': (send) => {
send(eventsubTests['channel.follow']);
},
'channel.subscribe': (send) => {
send(eventsubTests['channel.subscribe']);
},
'channel.subscription.gift': (send) => {
send(eventsubTests['channel.subscription.gift']);
setTimeout(() => {
send(eventsubTests['channel.subscribe']);
}, 2000);
},
'channel.subscription.message': (send) => {
send(eventsubTests['channel.subscribe']);
setTimeout(() => {
send(eventsubTests['channel.subscription.message']);
}, 2000);
},
'channel.cheer': (send) => {
send(eventsubTests['channel.cheer']);
},
'channel.raid': (send) => {
send(eventsubTests['channel.raid']);
},
};
const TwitchUser = styled('div', {
display: 'flex',
gap: '0.8rem',
alignItems: 'center',
fontSize: '14pt',
fontWeight: '300',
});
const TwitchPic = styled('img', {
width: '48px',
borderRadius: '50%',
});
const TwitchName = styled('p', { fontWeight: 'bold' });
function WebhookIntegration() {
const { t } = useTranslation();
const [stulbeConfig] = useModule(modules.stulbeConfig);
const kv = useSelector((state: RootState) => state.api.client);
const [userStatus, setUserStatus] = useState<UserData | SyncError>(null);
const [client, setClient] = useState<Stulbe>(null);
const getUserInfo = async () => {
try {
const res = (await client.makeRequest(
'GET',
'api/twitch/user',
)) as UserData;
setUserStatus(res);
} catch (e) {
setUserStatus({ ok: false, error: e.message });
}
};
const startAuthFlow = async () => {
const res = (await client.makeRequest('POST', 'api/twitch/authorize')) as {
// eslint-disable-next-line camelcase
auth_url: string;
};
const win = window.open(
res.auth_url,
'_blank',
'height=800,width=520,scrollbars=yes,status=yes',
);
// Hack, have to poll because no events are reliable for this
const iv = setInterval(() => {
if (win.closed) {
clearInterval(iv);
setUserStatus(null);
getUserInfo();
}
}, 1000);
};
const sendFakeEvent = async (event: keyof typeof eventSubTestFn) => {
eventSubTestFn[event]((data) => {
kv.putJSON('stulbe/ev/webhook', {
...data,
subscription: {
...data.subscription,
created_at: new Date().toISOString(),
},
});
});
};
// eslint-disable-next-line consistent-return
useEffect(() => {
if (client) {
// Get user info
getUserInfo();
} else if (
stulbeConfig &&
stulbeConfig.enabled &&
stulbeConfig.endpoint &&
stulbeConfig.auth_key &&
stulbeConfig.username
) {
const tryAuth = async () => {
// Try authenticating
const stulbeClient = new Stulbe(stulbeConfig.endpoint);
await stulbeClient.auth(stulbeConfig.username, stulbeConfig.auth_key);
setClient(stulbeClient);
};
tryAuth();
}
}, [stulbeConfig, client]);
if (!stulbeConfig || !stulbeConfig.enabled) {
return <h1>{t('pages.stulbe.err-not-enabled')}</h1>;
}
let userBlock = <i>{t('pages.stulbe.loading-data')}</i>;
if (userStatus !== null) {
if ('id' in userStatus) {
userBlock = (
<>
<TwitchUser>
<p>{t('pages.stulbe.authenticated-as')}</p>
<TwitchPic
src={userStatus.profile_image_url}
alt={t('pages.stulbe.profile-picture')}
/>
<TwitchName>{userStatus.display_name}</TwitchName>
</TwitchUser>
</>
);
} else {
userBlock = t('pages.stulbe.err-no-user');
}
}
return (
<>
<p>{t('pages.stulbe.auth-message')}</p>
<Button onClick={startAuthFlow} disabled={!client}>
<ExternalLinkIcon /> {t('pages.stulbe.auth-button')}
</Button>
<SectionHeader>{t('pages.stulbe.current-status')}</SectionHeader>
{userBlock}
<SectionHeader>{t('pages.stulbe.sim-events')}</SectionHeader>
<ButtonGroup>
{Object.keys(eventSubTestFn).map((ev: keyof typeof eventsubTests) => (
<Button onClick={() => sendFakeEvent(ev)}>
{t(`pages.stulbe.sim.${ev}`, { defaultValue: ev })}
</Button>
))}
</ButtonGroup>
</>
);
}
function BackendConfiguration() {
2021-12-15 11:12:19 +00:00
const [stulbeConfig, setStulbeConfig, loadStatus] = useModule(
modules.stulbeConfig,
);
const { t } = useTranslation();
const dispatch = useDispatch();
const status = useStatus(loadStatus.save);
const active = stulbeConfig?.enabled ?? false;
const busy =
loadStatus.load?.type !== 'success' || loadStatus.save?.type === 'pending';
const test = async () => {
try {
const client = new Stulbe(stulbeConfig.endpoint);
await client.auth(stulbeConfig.username, stulbeConfig.auth_key);
toast.success(t('pages.stulbe.test-success'));
} catch (e) {
toast.error(e.message);
}
};
2021-12-16 16:01:24 +00:00
return (
<form
onSubmit={(ev) => {
dispatch(setStulbeConfig(stulbeConfig));
ev.preventDefault();
}}
>
<Field size="fullWidth">
<Label htmlFor="endpoint">{t('pages.stulbe.endpoint')}</Label>
<InputBox
type="text"
id="endpoint"
placeholder={t('pages.stulbe.bind-placeholder')}
value={stulbeConfig?.endpoint ?? ''}
disabled={busy}
onChange={(e) =>
dispatch(
apiReducer.actions.stulbeConfigChanged({
...stulbeConfig,
enabled: e.target.value.length > 0,
endpoint: e.target.value,
}),
)
}
/>
</Field>
<Field size="fullWidth">
<Label htmlFor="username">{t('pages.stulbe.username')}</Label>
<InputBox
type="text"
id="username"
value={stulbeConfig?.username ?? ''}
required={true}
disabled={!active || busy}
onChange={(e) =>
dispatch(
apiReducer.actions.stulbeConfigChanged({
...stulbeConfig,
username: e.target.value,
}),
)
}
/>
</Field>
<Field size="fullWidth">
<Label htmlFor="password">{t('pages.stulbe.auth-key')}</Label>
<InputBox
type="password"
id="password"
value={stulbeConfig?.auth_key ?? ''}
disabled={!active || busy}
required={true}
onChange={(e) =>
dispatch(
apiReducer.actions.stulbeConfigChanged({
...stulbeConfig,
auth_key: e.target.value,
}),
)
}
/>
</Field>
<ButtonGroup>
<SaveButton status={status} />
<Button type="button" disabled={!active || busy} onClick={() => test()}>
{t('pages.stulbe.test-button')}
</Button>
</ButtonGroup>
</form>
);
}
export default function BackendIntegrationPage(): React.ReactElement {
const { t } = useTranslation();
2021-12-15 11:12:19 +00:00
return (
<PageContainer>
<PageHeader>
<PageTitle>{t('pages.stulbe.title')}</PageTitle>
2021-12-18 02:25:00 +00:00
<TextBlock>
2021-12-15 11:12:19 +00:00
<Trans i18nKey="pages.stulbe.subtitle">
2021-12-16 16:01:24 +00:00
{' '}
<a href="https://github.com/strimertul/stulbe/">stulbe</a>
2021-12-15 11:12:19 +00:00
</Trans>
2021-12-18 02:25:00 +00:00
</TextBlock>
2021-12-15 11:12:19 +00:00
</PageHeader>
2021-12-16 16:01:24 +00:00
2021-12-18 02:25:00 +00:00
<TabContainer defaultValue="configuration">
2021-12-16 16:01:24 +00:00
<TabList>
<TabButton value="configuration">
{t('pages.stulbe.configuration')}
</TabButton>
<TabButton value="webhook">
{t('pages.stulbe.twitch-events')}
</TabButton>
</TabList>
<TabContent value="configuration">
<BackendConfiguration />
</TabContent>
<TabContent value="webhook">
<WebhookIntegration />
</TabContent>
2021-12-18 02:25:00 +00:00
</TabContainer>
2021-12-15 11:12:19 +00:00
</PageContainer>
);
}