From 3593bbe6d4ae7631d6d8d178385b0eee3ad68d51 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 1 Jun 2020 15:49:05 +0200 Subject: [PATCH] Allow accounts to be hidden, i.e. not injected (#354) --- CHANGELOG.md | 1 + .../src/background/handlers/Extension.ts | 27 ++++++++--- .../src/background/handlers/Tabs.ts | 9 ++-- .../extension-base/src/background/types.ts | 9 +++- .../src/Popup/Accounts/Account.tsx | 48 ++++++++++++++----- .../extension-ui/src/components/Checkbox.tsx | 21 ++++++-- packages/extension-ui/src/messaging.ts | 4 ++ 7 files changed, 91 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb055bf3..a0abd032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add the ability to import JSON keystore files (Thanks to https://github.com/shawntabrizi) - updated to derivation documentation (Thanks to https://github.com/EthWorks) +- Allow accounts to be hidden, i.e. not injected (per account setting) - Adjust allowed mnemonic seed strengths, 12, 15, 18, 21 & 24 all allowed - Remove duplication with Default/Substrate prefixes in dropdown (equivalent, only generic displayed) - Display child accounts when no parent has been found (orphans) diff --git a/packages/extension-base/src/background/handlers/Extension.ts b/packages/extension-base/src/background/handlers/Extension.ts index 09bec0b0..74f0e92b 100644 --- a/packages/extension-base/src/background/handlers/Extension.ts +++ b/packages/extension-base/src/background/handlers/Extension.ts @@ -4,7 +4,7 @@ import { MetadataDef } from '@polkadot/extension-inject/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; -import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestDeriveCreate, ResponseDeriveValidate, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, RequestDeriveValidate, ResponseSeedValidate, ResponseType, SigningRequest, RequestJsonRestore, ResponseJsonRestore } from '../types'; +import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountShow, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestDeriveCreate, ResponseDeriveValidate, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, RequestDeriveValidate, ResponseSeedValidate, ResponseType, SigningRequest, RequestJsonRestore, ResponseJsonRestore } from '../types'; import chrome from '@polkadot/extension-inject/chrome'; import keyring from '@polkadot/ui-keyring'; @@ -63,6 +63,22 @@ export default class Extension { return { exportedJson: JSON.stringify(keyring.backupAccount(keyring.getPair(address), password)) }; } + private accountsForget ({ address }: RequestAccountForget): boolean { + keyring.forgetAccount(address); + + return true; + } + + private accountsShow ({ address, isShowing }: RequestAccountShow): boolean { + const pair = keyring.getPair(address); + + assert(pair, 'Unable to find pair'); + + keyring.saveAccountMeta(pair, { ...pair.meta, isHidden: !isShowing }); + + return true; + } + private accountsValidate ({ address, password }: RequestAccountValidate): boolean { try { keyring.backupAccount(keyring.getPair(address), password); @@ -73,12 +89,6 @@ export default class Extension { } } - private accountsForget ({ address }: RequestAccountForget): boolean { - keyring.forgetAccount(address); - - return true; - } - // FIXME This looks very much like what we have in Tabs private accountsSubscribe (id: string, port: chrome.runtime.Port): boolean { const cb = createSubscription<'pri(accounts.subscribe)'>(id, port); @@ -381,6 +391,9 @@ export default class Extension { case 'pri(accounts.forget)': return this.accountsForget(request as RequestAccountForget); + case 'pri(accounts.show)': + return this.accountsShow(request as RequestAccountShow); + case 'pri(accounts.validate)': return this.accountsValidate(request as RequestAccountValidate); diff --git a/packages/extension-base/src/background/handlers/Tabs.ts b/packages/extension-base/src/background/handlers/Tabs.ts index 86f9e705..1d0af43b 100644 --- a/packages/extension-base/src/background/handlers/Tabs.ts +++ b/packages/extension-base/src/background/handlers/Tabs.ts @@ -19,9 +19,12 @@ import State from './State'; import { createSubscription, unsubscribe } from './subscriptions'; function transformAccounts (accounts: SubjectInfo): InjectedAccount[] { - return Object.values(accounts).map(({ json: { address, meta: { genesisHash, name } } }): InjectedAccount => ({ - address, genesisHash, name - })); + return Object + .values(accounts) + .filter(({ json: { meta: { isHidden } } }) => !isHidden) + .map(({ json: { address, meta: { genesisHash, name } } }): InjectedAccount => ({ + address, genesisHash, name + })); } export default class Tabs { diff --git a/packages/extension-base/src/background/types.ts b/packages/extension-base/src/background/types.ts index 1feb8577..d047da91 100644 --- a/packages/extension-base/src/background/types.ts +++ b/packages/extension-base/src/background/types.ts @@ -27,6 +27,7 @@ export interface AccountJson extends KeyringPair$Meta { address: string; genesisHash?: string | null; isExternal?: boolean; + isHidden?: boolean; name?: string; parentAddress?: string; suri?: string; @@ -70,8 +71,9 @@ export interface RequestSignatures { 'pri(accounts.edit)': [RequestAccountEdit, boolean]; 'pri(accounts.export)': [RequestAccountExport, ResponseAccountExport]; 'pri(accounts.forget)': [RequestAccountForget, boolean]; - 'pri(accounts.validate)': [RequestAccountValidate, boolean]; + 'pri(accounts.show)': [RequestAccountShow, boolean]; 'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]]; + 'pri(accounts.validate)': [RequestAccountValidate, boolean]; 'pri(authorize.approve)': [RequestAuthorizeApprove, boolean]; 'pri(authorize.reject)': [RequestAuthorizeReject, boolean]; 'pri(authorize.requests)': [RequestAuthorizeSubscribe, boolean, AuthorizeRequest[]]; @@ -173,6 +175,11 @@ export interface RequestAccountForget { address: string; } +export interface RequestAccountShow { + address: string; + isShowing: boolean; +} + export interface RequestAccountValidate { address: string; password: string; diff --git a/packages/extension-ui/src/Popup/Accounts/Account.tsx b/packages/extension-ui/src/Popup/Accounts/Account.tsx index 9861b00d..4963c3ab 100644 --- a/packages/extension-ui/src/Popup/Accounts/Account.tsx +++ b/packages/extension-ui/src/Popup/Accounts/Account.tsx @@ -8,8 +8,8 @@ import { ThemeProps } from '../../types'; import React, { useCallback, useContext, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { ActionContext, Address, Link } from '../../components'; -import { editAccount } from '../../messaging'; +import { ActionContext, Address, Checkbox, Link } from '../../components'; +import { editAccount, showAccount } from '../../messaging'; import { Name } from '../../partials'; interface Props extends AccountJson { @@ -17,21 +17,35 @@ interface Props extends AccountJson { parentName?: string; } -function Account ({ address, className, isExternal, parentName, suri }: Props): React.ReactElement { +function Account ({ address, className, isExternal, isHidden, parentName, suri }: Props): React.ReactElement { const onAction = useContext(ActionContext); const [isEditing, setEditing] = useState(false); const [editedName, setName] = useState(null); - const _toggleEdit = useCallback((): void => setEditing(!isEditing), [isEditing]); - const _saveChanges = useCallback((): void => { - if (editedName && editedName !== name) { - editAccount(address, editedName) - .then(() => onAction()) - .catch(console.error); - } + const _toggleEdit = useCallback( + (): void => setEditing(!isEditing), + [isEditing] + ); - _toggleEdit(); - }, [editedName, address, _toggleEdit, onAction]); + const _saveChanges = useCallback( + (): void => { + if (editedName && editedName !== name) { + editAccount(address, editedName) + .then(() => onAction()) + .catch(console.error); + } + + _toggleEdit(); + }, + [editedName, address, _toggleEdit, onAction] + ); + const _toggleVisibility = useCallback( + (): void => { + showAccount(address, isHidden || false) + .catch(console.error); + }, + [address, isHidden] + ); const _actions = useMemo(() => ( <> @@ -66,8 +80,15 @@ function Account ({ address, className, isExternal, parentName, suri }: Props): > Forget Account +
+ - ), [_toggleEdit, address, isExternal]); + ), [_toggleEdit, _toggleVisibility, address, isExternal, isHidden]); return (
@@ -119,6 +140,7 @@ export default styled(Account)(({ theme }: ThemeProps) => ` font-weight: 600; font-size: 15px; line-height: 20px; + margin: 0; padding: 4px 16px; } `); diff --git a/packages/extension-ui/src/components/Checkbox.tsx b/packages/extension-ui/src/components/Checkbox.tsx index d2eb8038..8b86dadb 100644 --- a/packages/extension-ui/src/components/Checkbox.tsx +++ b/packages/extension-ui/src/components/Checkbox.tsx @@ -4,25 +4,38 @@ import { ThemeProps } from '../types'; -import React from 'react'; +import React, { useCallback } from 'react'; import styled from 'styled-components'; + import Checkmark from '../assets/checkmark.svg'; interface Props { checked: boolean; - onChange: (checked: boolean) => void; + onChange?: (checked: boolean) => void; + onClick?: () => void; label: string; className?: string; } -function Checkbox ({ checked, className, label, onChange }: Props): React.ReactElement { +function Checkbox ({ checked, className, label, onChange, onClick }: Props): React.ReactElement { + const _onChange = useCallback( + (event: React.ChangeEvent) => onChange && onChange(event.target.checked), + [onChange] + ); + + const _onClick = useCallback( + () => onClick && onClick(), + [onClick] + ); + return (