Defer keyring init until chain type is available (#8980)
* Defer keyring init until chain type is available * Ensure optionsSubject operation * Attempt to fix mocked hook * Adjust bounties spec context order * Fix is* flags on mock
This commit is contained in:
parent
a8b60d3afd
commit
3fc3b1edbf
@ -43,13 +43,13 @@ function Root ({ isElectron, store }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<Suspense fallback='...'>
|
||||
<ThemeProvider theme={theme}>
|
||||
<KeyringCtxRoot>
|
||||
<QueueCtxRoot>
|
||||
<ApiCtxRoot
|
||||
apiUrl={settings.apiUrl}
|
||||
isElectron={isElectron}
|
||||
store={store}
|
||||
>
|
||||
<QueueCtxRoot>
|
||||
<ApiCtxRoot
|
||||
apiUrl={settings.apiUrl}
|
||||
isElectron={isElectron}
|
||||
store={store}
|
||||
>
|
||||
<KeyringCtxRoot>
|
||||
<ApiStatsCtxRoot>
|
||||
<BlockAuthorsCtxRoot>
|
||||
<BlockEventsCtxRoot>
|
||||
@ -61,9 +61,9 @@ function Root ({ isElectron, store }: Props): React.ReactElement<Props> {
|
||||
</BlockEventsCtxRoot>
|
||||
</BlockAuthorsCtxRoot>
|
||||
</ApiStatsCtxRoot>
|
||||
</ApiCtxRoot>
|
||||
</QueueCtxRoot>
|
||||
</KeyringCtxRoot>
|
||||
</KeyringCtxRoot>
|
||||
</ApiCtxRoot>
|
||||
</QueueCtxRoot>
|
||||
</ThemeProvider>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@ -111,6 +111,10 @@ export class BountiesPage {
|
||||
}
|
||||
}
|
||||
},
|
||||
isApiConnected: true,
|
||||
isApiInitialized: true,
|
||||
isApiReady: true,
|
||||
isEthereum: false,
|
||||
systemName: 'substrate'
|
||||
} as unknown as ApiProps;
|
||||
|
||||
@ -124,15 +128,15 @@ export class BountiesPage {
|
||||
<div id='tooltips' />
|
||||
<Suspense fallback='...'>
|
||||
<QueueCtx.Provider value={queue}>
|
||||
<KeyringCtxRoot>
|
||||
<MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApiCtx.Provider value={mockApi}>
|
||||
<MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApiCtx.Provider value={mockApi}>
|
||||
<KeyringCtxRoot>
|
||||
<Bounties />
|
||||
</ApiCtx.Provider>
|
||||
</ThemeProvider>
|
||||
</MemoryRouter>
|
||||
</KeyringCtxRoot>
|
||||
</KeyringCtxRoot>
|
||||
</ApiCtx.Provider>
|
||||
</ThemeProvider>
|
||||
</MemoryRouter>
|
||||
</QueueCtx.Provider>
|
||||
</Suspense>
|
||||
</>
|
||||
|
||||
@ -6,9 +6,22 @@ import type { Option } from './types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
import { decodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import KeyPair from './KeyPair';
|
||||
|
||||
export default function createItem (option: KeyringSectionOption, isUppercase = true): Option {
|
||||
export default function createItem (option: KeyringSectionOption, isUppercase = true): Option | null {
|
||||
try {
|
||||
const u8a = decodeAddress(option.key);
|
||||
|
||||
if (u8a.length !== (keyring.keyring.type === 'ethereum' ? 20 : 32)) {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...option,
|
||||
text: (
|
||||
|
||||
@ -45,7 +45,7 @@ interface Props {
|
||||
}
|
||||
|
||||
type ExportedType = React.ComponentType<Props> & {
|
||||
createOption: (option: KeyringSectionOption, isUppercase?: boolean) => Option;
|
||||
createOption: (option: KeyringSectionOption, isUppercase?: boolean) => Option | null;
|
||||
setLastValue: (type: KeyringOption$Type, value: string) => void;
|
||||
};
|
||||
|
||||
@ -80,7 +80,7 @@ function transformToAccountId (value: string): string | null {
|
||||
: accountId;
|
||||
}
|
||||
|
||||
function createOption (address: string): Option {
|
||||
function createOption (address: string): Option | null {
|
||||
let isRecent: boolean | undefined;
|
||||
const pair = keyring.getAccount(address);
|
||||
let name: string | undefined;
|
||||
@ -177,9 +177,13 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
: (lastOption && lastOption.value)
|
||||
);
|
||||
const actualOptions: Option[] = options
|
||||
? dedupe(options.map((o) => createItem(o)))
|
||||
? dedupe(
|
||||
options
|
||||
.map((o) => createItem(o))
|
||||
.filter((o): o is Option => !!o)
|
||||
)
|
||||
: isDisabled && actualValue
|
||||
? [createOption(actualValue)]
|
||||
? [createOption(actualValue)].filter((o): o is Option => !!o)
|
||||
: actualValue
|
||||
? this.addActual(actualValue)
|
||||
: this.getFiltered();
|
||||
@ -225,7 +229,7 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
|
||||
return this.hasValue(actualValue)
|
||||
? base
|
||||
: base.concat(createOption(actualValue));
|
||||
: base.concat(...[createOption(actualValue)].filter((o): o is Option => !!o));
|
||||
}
|
||||
|
||||
private renderLabel = ({ value }: KeyringSectionOption): React.ReactNode => {
|
||||
@ -377,11 +381,13 @@ const ExportedComponent = withMulti(
|
||||
propName: 'optionsAll',
|
||||
transform: (optionsAll: KeyringOptions): Record<string, (Option | React.ReactNode)[]> =>
|
||||
Object.entries(optionsAll).reduce((result: Record<string, (Option | React.ReactNode)[]>, [type, options]): Record<string, (Option | React.ReactNode)[]> => {
|
||||
result[type] = options.map((option): Option | React.ReactNode =>
|
||||
option.value === null
|
||||
? createHeader(option)
|
||||
: createItem(option)
|
||||
);
|
||||
result[type] = options
|
||||
.map((option): Option | React.ReactNode | null =>
|
||||
option.value === null
|
||||
? createHeader(option)
|
||||
: createItem(option)
|
||||
)
|
||||
.filter((o): o is Option | React.ReactNode => !!o);
|
||||
|
||||
return result;
|
||||
}, {})
|
||||
|
||||
@ -11,6 +11,8 @@ import { keyring } from '@polkadot/ui-keyring';
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
import { decodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
@ -38,10 +40,24 @@ export const KeyringCtx = React.createContext<State>(EMPTY);
|
||||
* The first check ensures that we never have dupes in the original. The second
|
||||
* ensures that e.g. an address is not also available as an account
|
||||
**/
|
||||
function filter (items: string[], others: string[] = []): string[] {
|
||||
function filter (isEthereum: boolean, items: string[], others: string[] = []): string[] {
|
||||
const allowedLength = isEthereum
|
||||
? 20
|
||||
: 32;
|
||||
|
||||
return items.reduce<string[]>((result, a) => {
|
||||
if (!result.includes(a) && !others.includes(a)) {
|
||||
result.push(a);
|
||||
try {
|
||||
const u8a = decodeAddress(a);
|
||||
|
||||
if (u8a.length === allowedLength) {
|
||||
result.push(a);
|
||||
} else {
|
||||
console.warn(`Not adding address ${a}, not in correct format for chain (requires ${allowedLength}-byte publickey)`);
|
||||
}
|
||||
} catch {
|
||||
console.error(a, allowedLength);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -75,8 +91,8 @@ function createCheck (items: string[]): Accounts['isAccount'] {
|
||||
!!a && items.includes(a.toString());
|
||||
}
|
||||
|
||||
function extractAccounts (accounts: SubjectInfo = {}): Accounts {
|
||||
const allAccounts = filter(Object.keys(accounts));
|
||||
function extractAccounts (isEthereum: boolean, accounts: SubjectInfo = {}): Accounts {
|
||||
const allAccounts = filter(isEthereum, Object.keys(accounts));
|
||||
|
||||
return {
|
||||
allAccounts,
|
||||
@ -87,8 +103,8 @@ function extractAccounts (accounts: SubjectInfo = {}): Accounts {
|
||||
};
|
||||
}
|
||||
|
||||
function extractAddresses (addresses: SubjectInfo = {}, accounts: string[]): Addresses {
|
||||
const allAddresses = filter(Object.keys(addresses), accounts);
|
||||
function extractAddresses (isEthereum: boolean, addresses: SubjectInfo = {}, accounts: string[]): Addresses {
|
||||
const allAddresses = filter(isEthereum, Object.keys(addresses), accounts);
|
||||
|
||||
return {
|
||||
allAddresses,
|
||||
@ -100,27 +116,34 @@ function extractAddresses (addresses: SubjectInfo = {}, accounts: string[]): Add
|
||||
}
|
||||
|
||||
export function KeyringCtxRoot ({ children }: Props): React.ReactElement<Props> {
|
||||
const { isApiReady, isEthereum } = useApi();
|
||||
const [state, setState] = useState(EMPTY);
|
||||
|
||||
useEffect((): () => void => {
|
||||
const sub = combineLatest([
|
||||
keyring.accounts.subject.pipe(
|
||||
map((accInfo) => extractAccounts(accInfo))
|
||||
),
|
||||
keyring.addresses.subject
|
||||
])
|
||||
.pipe(
|
||||
map(([accounts, addrInfo]): State => ({
|
||||
accounts,
|
||||
addresses: extractAddresses(addrInfo, accounts.allAccounts)
|
||||
}))
|
||||
)
|
||||
.subscribe((state) => setState(state));
|
||||
let sub: null | { unsubscribe: () => void } = null;
|
||||
|
||||
// Defer keyring injection until the API is ready - we need to have the chain
|
||||
// info to determine which type of addresses we can use (before subscribing)
|
||||
if (isApiReady) {
|
||||
sub = combineLatest([
|
||||
keyring.accounts.subject.pipe(
|
||||
map((accInfo) => extractAccounts(isEthereum, accInfo))
|
||||
),
|
||||
keyring.addresses.subject
|
||||
])
|
||||
.pipe(
|
||||
map(([accounts, addrInfo]): State => ({
|
||||
accounts,
|
||||
addresses: extractAddresses(isEthereum, addrInfo, accounts.allAccounts)
|
||||
}))
|
||||
)
|
||||
.subscribe((state) => setState(state));
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
sub && sub.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
}, [isApiReady, isEthereum]);
|
||||
|
||||
return (
|
||||
<KeyringCtx.Provider value={state}>
|
||||
|
||||
@ -197,6 +197,10 @@ export abstract class Page {
|
||||
...api,
|
||||
isReady: Promise.resolve(api)
|
||||
},
|
||||
isApiConnected: true,
|
||||
isApiInitialized: true,
|
||||
isApiReady: true,
|
||||
isEthereum: false,
|
||||
systemName: 'substrate'
|
||||
} as unknown as ApiProps;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user