ghost-portal/packages/react-hooks/src/ctx/Keyring.tsx
Jaco 37f44367b4
Rework context usage (& move most to react-hooks) (#8731)
* Rework context usage (& move most to react-hooks)

* Move use{Accounts, Addresses} to use contexts

* Cleanups

* Global alignment renames

* Combine keyring subjects into KeyringCtx

* Cleanups to contexts

* Small adjustment to window size usage

* Popup test via data-testid

* Fix Bounties tests

* Move getStats to correct location

* Adjust ApiStats

* Do add unsubs (although unneeded)
2023-01-09 12:56:38 +02:00

131 lines
3.7 KiB
TypeScript

// Copyright 2017-2023 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import type { Accounts, Addresses } from './types';
import React, { useEffect, useState } from 'react';
import { combineLatest, map } from 'rxjs';
import { keyring } from '@polkadot/ui-keyring';
import { u8aToHex } from '@polkadot/util';
import { decodeAddress } from '@polkadot/util-crypto';
interface Props {
children?: React.ReactNode;
}
interface State {
accounts: Accounts;
addresses: Addresses;
}
const EMPTY_IS = () => false;
const EMPTY: State = {
accounts: { allAccounts: [], allAccountsHex: [], areAccountsLoaded: false, hasAccounts: false, isAccount: EMPTY_IS },
addresses: { allAddresses: [], allAddressesHex: [], areAddressesLoaded: false, hasAddresses: false, isAddress: EMPTY_IS }
};
export const KeyringCtx = React.createContext<State>(EMPTY);
/**
* @internal Helper function to dedupe a list of items, only adding it if
*
* 1. It is not already present in our list of results
* 2. It does not exist against a secondary list to check
*
* 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[] {
return items.reduce<string[]>((result, a) => {
if (!result.includes(a) && !others.includes(a)) {
result.push(a);
}
return result;
}, []);
}
/**
* @internal Helper function to convert a list of ss58 addresses into hex
**/
function toHex (items: string[]): string[] {
return items
.map((a): string | null => {
try {
return u8aToHex(decodeAddress(a));
} catch (error) {
// This is actually just a failsafe - the keyring really should
// not be passing through invalid ss58 values, but never say never
console.error(`Unable to convert address ${a} to hex`, (error as Error).message);
return null;
}
})
.filter((a): a is string => !!a);
}
/**
* @internal Helper to create an is{Account, Address} check
**/
function createCheck (items: string[]): Accounts['isAccount'] {
return (a?: string | null | { toString: () => string }): boolean =>
!!a && items.includes(a.toString());
}
function extractAccounts (accounts: SubjectInfo = {}): Accounts {
const allAccounts = filter(Object.keys(accounts));
return {
allAccounts,
allAccountsHex: toHex(allAccounts),
areAccountsLoaded: true,
hasAccounts: allAccounts.length !== 0,
isAccount: createCheck(allAccounts)
};
}
function extractAddresses (addresses: SubjectInfo = {}, accounts: string[]): Addresses {
const allAddresses = filter(Object.keys(addresses), accounts);
return {
allAddresses,
allAddressesHex: toHex(allAddresses),
areAddressesLoaded: true,
hasAddresses: allAddresses.length !== 0,
isAddress: createCheck(allAddresses)
};
}
export function KeyringCtxRoot ({ children }: Props): React.ReactElement<Props> {
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));
return (): void => {
sub && sub.unsubscribe();
};
}, []);
return (
<KeyringCtx.Provider value={state}>
{children}
</KeyringCtx.Provider>
);
}