ghost-keychain/packages/extension-ui/src/Popup/index.tsx
2021-08-26 10:38:54 +02:00

170 lines
7.2 KiB
TypeScript

// Copyright 2019-2021 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountJson, AccountsContext, AuthorizeRequest, MetadataRequest, SigningRequest } from '@polkadot/extension-base/background/types';
import type { SettingsStruct } from '@polkadot/ui-settings/types';
import React, { useCallback, useEffect, useState } from 'react';
import { Route, Switch } from 'react-router';
import { PHISHING_PAGE_REDIRECT } from '@polkadot/extension-base/defaults';
import { canDerive } from '@polkadot/extension-base/utils';
import uiSettings from '@polkadot/ui-settings';
import { ErrorBoundary, Loading } from '../components';
import { AccountContext, ActionContext, AuthorizeReqContext, MediaContext, MetadataReqContext, SettingsContext, SigningReqContext } from '../components/contexts';
import ToastProvider from '../components/Toast/ToastProvider';
import { subscribeAccounts, subscribeAuthorizeRequests, subscribeMetadataRequests, subscribeSigningRequests } from '../messaging';
import { buildHierarchy } from '../util/buildHierarchy';
import Accounts from './Accounts';
import AuthList from './AuthManagement';
import Authorize from './Authorize';
import CreateAccount from './CreateAccount';
import Derive from './Derive';
import Export from './Export';
import ExportAll from './ExportAll';
import Forget from './Forget';
import ImportLedger from './ImportLedger';
import ImportQr from './ImportQr';
import ImportSeed from './ImportSeed';
import Metadata from './Metadata';
import PhishingDetected from './PhishingDetected';
import RestoreJson from './RestoreJson';
import Signing from './Signing';
import Welcome from './Welcome';
const startSettings = uiSettings.get();
// Request permission for video, based on access we can hide/show import
async function requestMediaAccess (cameraOn: boolean): Promise<boolean> {
if (!cameraOn) {
return false;
}
try {
await navigator.mediaDevices.getUserMedia({ video: true });
return true;
} catch (error) {
console.error('Permission for video declined', (error as Error).message);
}
return false;
}
function initAccountContext (accounts: AccountJson[]): AccountsContext {
const hierarchy = buildHierarchy(accounts);
const master = hierarchy.find(({ isExternal, type }) => !isExternal && canDerive(type));
return {
accounts,
hierarchy,
master
};
}
export default function Popup (): React.ReactElement {
const [accounts, setAccounts] = useState<null | AccountJson[]>(null);
const [accountCtx, setAccountCtx] = useState<AccountsContext>({ accounts: [], hierarchy: [] });
const [authRequests, setAuthRequests] = useState<null | AuthorizeRequest[]>(null);
const [cameraOn, setCameraOn] = useState(startSettings.camera === 'on');
const [mediaAllowed, setMediaAllowed] = useState(false);
const [metaRequests, setMetaRequests] = useState<null | MetadataRequest[]>(null);
const [signRequests, setSignRequests] = useState<null | SigningRequest[]>(null);
const [isWelcomeDone, setWelcomeDone] = useState(false);
const [settingsCtx, setSettingsCtx] = useState<SettingsStruct>(startSettings);
const _onAction = useCallback(
(to?: string): void => {
setWelcomeDone(window.localStorage.getItem('welcome_read') === 'ok');
if (to) {
window.location.hash = to;
}
},
[]
);
useEffect((): void => {
Promise.all([
subscribeAccounts(setAccounts),
subscribeAuthorizeRequests(setAuthRequests),
subscribeMetadataRequests(setMetaRequests),
subscribeSigningRequests(setSignRequests)
]).catch(console.error);
uiSettings.on('change', (settings): void => {
setSettingsCtx(settings);
setCameraOn(settings.camera === 'on');
});
_onAction();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect((): void => {
setAccountCtx(initAccountContext(accounts || []));
}, [accounts]);
useEffect((): void => {
requestMediaAccess(cameraOn)
.then(setMediaAllowed)
.catch(console.error);
}, [cameraOn]);
function wrapWithErrorBoundary (component: React.ReactElement, trigger?: string): React.ReactElement {
return <ErrorBoundary trigger={trigger}>{component}</ErrorBoundary>;
}
const Root = isWelcomeDone
? authRequests && authRequests.length
? wrapWithErrorBoundary(<Authorize />, 'authorize')
: metaRequests && metaRequests.length
? wrapWithErrorBoundary(<Metadata />, 'metadata')
: signRequests && signRequests.length
? wrapWithErrorBoundary(<Signing/>, 'signing')
: wrapWithErrorBoundary(<Accounts/>, 'accounts')
: wrapWithErrorBoundary(<Welcome />, 'welcome');
return (
<Loading>{accounts && authRequests && metaRequests && signRequests && (
<ActionContext.Provider value={_onAction}>
<SettingsContext.Provider value={settingsCtx}>
<AccountContext.Provider value={accountCtx}>
<AuthorizeReqContext.Provider value={authRequests}>
<MediaContext.Provider value={cameraOn && mediaAllowed}>
<MetadataReqContext.Provider value={metaRequests}>
<SigningReqContext.Provider value={signRequests}>
<ToastProvider>
<Switch>
<Route path='/auth-list'>{wrapWithErrorBoundary(<AuthList />, 'auth-list')}</Route>
<Route path='/account/create'>{wrapWithErrorBoundary(<CreateAccount />, 'account-creation')}</Route>
<Route path='/account/forget/:address'>{wrapWithErrorBoundary(<Forget />, 'forget-address')}</Route>
<Route path='/account/export/:address'>{wrapWithErrorBoundary(<Export />, 'export-address')}</Route>
<Route path='/account/export-all'>{wrapWithErrorBoundary(<ExportAll />, 'export-all-address')}</Route>
<Route path='/account/import-ledger'>{wrapWithErrorBoundary(<ImportLedger />, 'import-ledger')}</Route>
<Route path='/account/import-qr'>{wrapWithErrorBoundary(<ImportQr />, 'import-qr')}</Route>
<Route path='/account/import-seed'>{wrapWithErrorBoundary(<ImportSeed />, 'import-seed')}</Route>
<Route path='/account/restore-json'>{wrapWithErrorBoundary(<RestoreJson />, 'restore-json')}</Route>
<Route path='/account/derive/:address/locked'>{wrapWithErrorBoundary(<Derive isLocked />, 'derived-address-locked')}</Route>
<Route path='/account/derive/:address'>{wrapWithErrorBoundary(<Derive />, 'derive-address')}</Route>
<Route path={`${PHISHING_PAGE_REDIRECT}/:website`}>{wrapWithErrorBoundary(<PhishingDetected />, 'phishing-page-redirect')}</Route>
<Route
exact
path='/'
>
{Root}
</Route>
</Switch>
</ToastProvider>
</SigningReqContext.Provider>
</MetadataReqContext.Provider>
</MediaContext.Provider>
</AuthorizeReqContext.Provider>
</AccountContext.Provider>
</SettingsContext.Provider>
</ActionContext.Provider>
)}</Loading>
);
}