import { getGetServerSideProps } from '@/api/ssrApollo' import Layout from '@/components/layout' import { SettingsHeader } from '../index' import { useVaultConfigurator } from '@/components/vault/use-vault-configurator' import { useMe } from '@/components/me' import { Button, InputGroup } from 'react-bootstrap' import bip39Words from '@/lib/bip39-words' import { Form, PasswordInput, SubmitButton } from '@/components/form' import { deviceSyncSchema } from '@/lib/validate' import RefreshIcon from '@/svgs/refresh-line.svg' import { useCallback, useEffect, useState } from 'react' import { useToast } from '@/components/toast' import { useWallets } from '@/wallets/index' export const getServerSideProps = getGetServerSideProps({ authRequired: true }) export default function DeviceSync ({ ssrData }) { const { me } = useMe() const { onVaultKeySet, beforeDisconnectVault } = useWallets() const { key, setVaultKey, clearVault, disconnectVault } = useVaultConfigurator({ onVaultKeySet, beforeDisconnectVault }) const [passphrase, setPassphrase] = useState() const setSeedPassphrase = useCallback(async (passphrase) => { await setVaultKey(passphrase) setPassphrase(passphrase) }, [setVaultKey]) const enabled = !!me?.privates?.vaultKeyHash const connected = !!key return ( <Layout> <div className='pb-3 w-100 mt-2'> <SettingsHeader /> <small className='line-height-md d-block mt-3' style={{ maxWidth: '600px' }}> <p> Device sync uses end-to-end encryption to securely synchronize your data across devices. Your sensitive data remains private and inaccessible to our servers while being synced across all your connected devices using only a passphrase. </p> </small> <div className='mt-4' style={{ maxWidth: '600px' }}> { (connected && passphrase && <Connect passphrase={passphrase} />) || (connected && <Connected disconnectVault={disconnectVault} />) || (enabled && <Enabled setVaultKey={setVaultKey} clearVault={clearVault} />) || <Setup setSeedPassphrase={setSeedPassphrase} /> } </div> </div> </Layout> ) } function Connect ({ passphrase }) { return ( <div> <h2>Connect other devices</h2> <p className='line-height-md'> On your other devices, navigate to device sync settings and enter this exact passphrase. </p> <p className='line-height-md'> <strong>Once you leave this page, this passphrase cannot be shown again.</strong> Connect all the devices you plan to use or write this passphrase down somewhere safe. </p> <PasswordInput label='passphrase' name='passphrase' placeholder='' required autoFocus as='textarea' value={passphrase} noForm rows={3} readOnly copy qr /> </div> ) } function Connected ({ disconnectVault }) { return ( <div> <h2>Device sync is enabled!</h2> <p> Sensitive data on this device is now securely synced between all connected devices. </p> <p className='text-muted text-sm'> Disconnect to prevent this device from syncing data or to reset your passphrase. </p> <div className='d-flex justify-content-between'> <div className='d-flex align-items-center ms-auto gap-2'> <Button variant='primary' onClick={disconnectVault} >disconnect </Button> </div> </div> </div> ) } function Enabled ({ setVaultKey, clearVault }) { const toaster = useToast() return ( <div> <h2>Device sync is enabled</h2> <p className='line-height-md'> This device is not connected. Enter or scan your passphrase to connect. If you've lost your passphrase you may reset it. </p> <Form schema={deviceSyncSchema} initial={{ passphrase: '' }} enableReinitialize onSubmit={async ({ passphrase }) => { try { await setVaultKey(passphrase) } catch (e) { console.error(e) toaster.danger('error setting vault key') } }} > <PasswordInput label='passphrase' name='passphrase' placeholder='' required autoFocus as='textarea' rows={3} qr /> <div className='mt-3'> <div className='d-flex justify-content-between align-items-center'> <Button variant='danger' onClick={clearVault}>reset</Button> <SubmitButton variant='primary'>enable</SubmitButton> </div> </div> </Form> </div> ) } const generatePassphrase = (n = 12) => { const rand = new Uint32Array(n) window.crypto.getRandomValues(rand) return Array.from(rand).map(i => bip39Words[i % bip39Words.length]).join(' ') } function Setup ({ setSeedPassphrase }) { const [passphrase, setPassphrase] = useState() const toaster = useToast() const newPassphrase = useCallback(() => { setPassphrase(() => generatePassphrase(12)) }, []) useEffect(() => { setPassphrase(() => generatePassphrase(12)) }, []) return ( <div> <h2>Enable device sync</h2> <p> Enable secure sync of sensitive data (like wallet credentials) between your devices. </p> <p className='text-muted text-sm line-height-md'> After enabled, your passphrase can be used to connect other devices. </p> <Form schema={deviceSyncSchema} initial={{ passphrase }} enableReinitialize onSubmit={async ({ passphrase }) => { try { await setSeedPassphrase(passphrase) } catch (e) { console.error(e) toaster.danger('error setting passphrase') } }} > <PasswordInput label='passphrase' name='passphrase' placeholder='' required autoFocus as='textarea' rows={3} readOnly append={ <InputGroup.Text style={{ cursor: 'pointer', userSelect: 'none' }} onClick={newPassphrase}> <RefreshIcon width={16} height={16} /> </InputGroup.Text> } /> <div className='mt-3'> <div className='d-flex justify-content-between'> <div className='d-flex align-items-center ms-auto gap-2'> <SubmitButton variant='primary'>enable</SubmitButton> </div> </div> </div> </Form> </div> ) }