import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useRecoilState, useRecoilValue } from "recoil"; import QRCode from 'qrcode-svg' import { filesystemStore, sessionStore, themeStore } from '../stores'; import { addNotification } from '../lib/notifications'; import { createAccountLinkingProducer } from '../lib/auth/linking'; import { ThemeOptions } from '../lib/theme'; import { getBackupStatus, setBackupStatus } from '../lib/auth/backup'; import ConnectBackupDevice from '../components/auth/delegate-account/ConnectBackupDevice'; import DelegateAccount from '../components/auth/delegate-account/DelegateAccount'; import type { DelegateAccountView } from '../lib/views'; const DelegateAccountRoute = () => { const navigate = useNavigate(); const fs = useRecoilValue(filesystemStore); const [session, setSession] = useRecoilState(sessionStore); const theme = useRecoilValue(themeStore); const [backupCreated, setBackupCreated] = useState(true); const [connectionLink, setConnectionLink] = useState('') const [view, setView] = useState<DelegateAccountView>( 'connect-backup-device' ); const [qrcode, setQrcode] = useState('') const [pin, setPin] = useState<number[]>(); const [pinInput] = useState(''); const [pinError, setPinError] = useState(false); const [confirmPin, setConfirmPin] = useState<() => void>(() => undefined); const [rejectPin, setRejectPin] = useState<() => void>(() => undefined); const initAccountLinkingProducer = async (username: string) => { const accountLinkingProducer = await createAccountLinkingProducer(username) accountLinkingProducer.on('challenge', detail => { setPin(detail.pin) setConfirmPin(detail.confirmPin); setRejectPin(detail.rejectPin); setView('delegate-account') }) accountLinkingProducer.on('link', async ({ approved }) => { if (approved) { setSession({ ...session, backupCreated: true, }); if (fs) { await setBackupStatus({ created: true }) addNotification({ msg: 'You\'ve connected a backup device!', type: 'success' }) navigate('/') } else { addNotification({ msg: 'Missing filesystem. Unable to create a backup device.', type: 'error' }) } } }) } const cancelConnection = () => { rejectPin(); addNotification({ msg: 'The connection attempt was cancelled', type: 'info' }) navigate('/'); }; const checkPin = () => { if (pin?.join('') === pinInput) { confirmPin(); } else { setPinError(true); } }; const useMountEffect = () => useEffect(() => { if (fs) { const updateBackupCreated = async () => { const backupStatus = await getBackupStatus(fs); setBackupCreated(!!backupStatus?.created); } updateBackupCreated(); } const hashedUsername = session.username.hashed; const fullUsername = session.username.full; if (hashedUsername && fullUsername) { const origin = window.location.origin; const updatedConnectionLink = `${origin}/link-device?hashedUsername=${hashedUsername}&username=${encodeURIComponent( fullUsername )}`; setConnectionLink(updatedConnectionLink); setQrcode( new QRCode({ content: updatedConnectionLink, color: theme.selectedTheme === ThemeOptions.LIGHT ? "#171717" : "#FAFAFA", background: theme.selectedTheme === ThemeOptions.LIGHT ? "#FAFAFA" : "#171717", padding: 0, width: 250, height: 250, join: true }).svg() ); initAccountLinkingProducer(hashedUsername); } }, []); useMountEffect() if (view === 'connect-backup-device') { return ( <ConnectBackupDevice qrcode={qrcode} connectionLink={connectionLink} backupCreated={backupCreated} /> ); } else if (view === 'delegate-account') { return ( <DelegateAccount pinInput={pinInput} pinError={pinError} cancelConnection={cancelConnection} checkPin={checkPin} /> ); } return null; }; export default DelegateAccountRoute;