Make enable() call to extensions (#1194)

* Make enable() call to extensions

* It works

* Add overlay for injected (prompt)

* Use signer from injectedPromise
This commit is contained in:
Jaco Greeff 2019-05-23 12:43:39 +02:00 committed by GitHub
parent cbc041fce5
commit 86a767de68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 144 additions and 64 deletions

View File

@ -43,19 +43,17 @@ ReactDOM.render(
<Suspense fallback='...'>
<Queue>
<QueueConsumer>
{({ queueExtrinsic, queueSetTxStatus }) => {
return (
<Api
queueExtrinsic={queueExtrinsic}
queueSetTxStatus={queueSetTxStatus}
url={url}
>
<HashRouter>
<Apps />
</HashRouter>
</Api>
);
}}
{({ queueExtrinsic, queueSetTxStatus }) => (
<Api
queueExtrinsic={queueExtrinsic}
queueSetTxStatus={queueSetTxStatus}
url={url}
>
<HashRouter>
<Apps />
</HashRouter>
</Api>
)}
</QueueConsumer>
</Queue>
</Suspense>,

View File

@ -19,6 +19,24 @@ const isFirefox = typeof InstallTrigger !== 'undefined';
class Connecting extends React.PureComponent<Props> {
render () {
return this.renderExtension() || this.renderConnecting();
}
private renderExtension () {
const { isWaitingInjected, t } = this.props;
if (!isWaitingInjected) {
return null;
}
return (
<Wrapper>
<div>{t('Waiting for authorization from the extension. Please open the installed extension and approve or reject access.')}</div>
</Wrapper>
);
}
private renderConnecting () {
const { isApiConnected, t } = this.props;
if (isApiConnected) {

View File

@ -2,10 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Signer } from '@polkadot/api/types';
import { ProviderInterface } from '@polkadot/rpc-provider/types';
import { QueueTx$ExtrinsicAdd, QueueTx$MessageSetStatus } from '@polkadot/ui-app/Status/types';
import { ApiProps } from './types';
import { ApiProps, ApiInjectedProps } from './types';
import React from 'react';
import ApiPromise from '@polkadot/api/promise';
@ -18,10 +17,11 @@ import { ChainProperties } from '@polkadot/types';
import { formatBalance, isTestChain } from '@polkadot/util';
import ApiContext from './ApiContext';
import { withInjected } from './Injected';
let api: ApiPromise;
type Props = {
type Props = ApiInjectedProps & {
children: React.ReactNode,
queueExtrinsic: QueueTx$ExtrinsicAdd,
queueSetTxStatus: QueueTx$MessageSetStatus,
@ -34,26 +34,15 @@ type State = ApiProps & {
type InjectedAccount = { address: string, meta: { name: string } };
type WindowInjected = Window & {
injectedWeb3: {
[index: string]: {
accounts: {
get: () => Promise<Array<{ address: string, name: string }>>
},
signer: Signer
}
}
};
export { api };
export default class ApiWrapper extends React.PureComponent<Props, State> {
class ApiWrapper extends React.PureComponent<Props, State> {
state: State = {} as State;
constructor (props: Props) {
super(props);
const { queueExtrinsic, queueSetTxStatus, url } = props;
const { injectedAvailable, queueExtrinsic, queueSetTxStatus, url } = props;
const provider = new WsProvider(url);
const signer = new ApiSigner(queueExtrinsic, queueSetTxStatus);
@ -72,13 +61,20 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
this.state = {
isApiConnected: false,
isApiReady: false,
isWaitingInjected: injectedAvailable,
api,
setApiUrl
} as State;
}
componentDidMount () {
const { injectedPromise } = this.props;
this.subscribeEvents();
injectedPromise
.then(() => this.setState({ isWaitingInjected: false }))
.catch(console.error);
}
private subscribeEvents () {
@ -102,28 +98,27 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
}
private async retrieveInjected (): Promise<Array<InjectedAccount>> {
const { injectedPromise } = this.props;
const injected: Array<InjectedAccount> = [];
const injWindow = window as WindowInjected;
const result = await injectedPromise;
if (injWindow.injectedWeb3) {
console.log('api: found injectedWeb3', Object.keys(injWindow.injectedWeb3));
if (result.length) {
const providers = result.map(({ name, version }) => `${name}/${version}`);
console.log(`api: found injectedWeb3: ${providers.join(', ')}`);
const rerieved = await Promise.all(
Object
.entries(injWindow.injectedWeb3)
.map(([source, { accounts }]) =>
accounts
.get()
.then((accounts) =>
accounts.map(({ address, name }) => ({
address,
meta: {
name: `${name} (${source === 'polkadot-js' ? 'extension' : source})`,
source
}
}))
)
result.map(({ accounts, name: source }) =>
accounts.get().then((accounts) =>
accounts.map(({ address, name }) => ({
address,
meta: {
name: `${name} (${source === 'polkadot-js' ? 'extension' : source})`,
source
}
}))
)
)
);
rerieved.forEach((accounts) =>
@ -176,7 +171,7 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
}
render () {
const { api, apiDefaultTx, chain, isApiConnected, isApiReady, isDevelopment, setApiUrl } = this.state;
const { api, apiDefaultTx, chain, isApiConnected, isApiReady, isDevelopment, isWaitingInjected, setApiUrl } = this.state;
return (
<ApiContext.Provider
@ -186,6 +181,7 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
isApiConnected,
isApiReady: isApiReady && !!chain,
isDevelopment,
isWaitingInjected,
setApiUrl
}}
>
@ -194,3 +190,5 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
);
}
}
export default withInjected(ApiWrapper);

View File

@ -0,0 +1,44 @@
// Copyright 2017-2019 @polkadot/ui-api authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiInjected, ApiInjectedProps, Subtract, WindowInjected } from './types';
import React from 'react';
const objmap = (window as WindowInjected).injectedWeb3;
const injectedAvailable = !!objmap && Object.keys(objmap).length !== 0;
const injectedPromise = !injectedAvailable
? Promise.resolve([] as Array<ApiInjected>)
: Promise.all(Object.values(objmap).map(({ name, version, enable }) =>
Promise.all([
Promise.resolve({ name, version }),
enable('polkadot-js/apps').catch(() => null)
])))
.then((values) => values.filter(([, result]) => result !== null))
.then((values) => values.map(([info, result]) => ({ ...info, ...result })))
.catch(() => [] as Array<ApiInjected>);
const InjectedContext = React.createContext({ injectedAvailable, injectedPromise });
const InjectedConsumer = InjectedContext.Consumer;
export {
InjectedConsumer
};
export function withInjected<P extends ApiInjectedProps> (Component: React.ComponentType<P>): React.ComponentType<Subtract<P, ApiInjectedProps>> {
return (props: Subtract<P, ApiInjectedProps>) => {
return (
<InjectedContext.Consumer>
{(injected) => (
// @ts-ignore Something here with the props are going wonky
<Component
{...props}
injectedAvailable={injectedAvailable}
injectedPromise={injectedPromise}
/>
)}
</InjectedContext.Consumer>
);
};
}

View File

@ -5,6 +5,7 @@
import Api, { api } from './Api';
import ApiContext from './ApiContext';
export * from './Injected';
export * from './with';
export {

View File

@ -2,6 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Signer } from '@polkadot/api/types';
import { SubmittableExtrinsicFunction } from '@polkadot/api/promise/types';
import ApiPromise from '@polkadot/api/promise';
@ -20,9 +21,37 @@ export type ApiProps = {
isApiConnected: boolean,
isApiReady: boolean,
isDevelopment: boolean,
isWaitingInjected: boolean,
setApiUrl: (url?: string) => void
};
export type WindowInjectedInfo = {
name: string,
version: string
};
export type WindowInjectedResult = {
accounts: {
get: () => Promise<Array<{ address: string, name: string }>>
},
signer: Signer
};
export type WindowInjected = Window & {
injectedWeb3: {
[index: string]: WindowInjectedInfo & {
enable: (origin: string) => Promise<WindowInjectedResult>
}
}
};
export type ApiInjected = WindowInjectedInfo & WindowInjectedResult;
export type ApiInjectedProps = {
injectedAvailable: boolean,
injectedPromise: Promise<Array<ApiInjected>>
};
export type OnChangeCb$Obs = { next: (value?: any) => any };
export type OnChangeCb$Fn = (value?: any) => any;

View File

@ -2,10 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Signer as ISigner } from '@polkadot/api/types';
import { SubmittableResult } from '@polkadot/api/SubmittableExtrinsic';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { ApiProps, ApiInjectedProps } from '@polkadot/ui-api/types';
import { I18nProps, BareProps } from '@polkadot/ui-app/types';
import { RpcMethod } from '@polkadot/jsonrpc/types';
import { KeyringPair } from '@polkadot/keyring/types';
@ -14,8 +13,8 @@ import { QueueTx, QueueTx$MessageSetStatus, QueueTx$Result, QueueTx$Status } fro
import React from 'react';
import { Button, Modal } from '@polkadot/ui-app';
import { withApi, withInjected, withMulti, withObservable } from '@polkadot/ui-api';
import keyring from '@polkadot/ui-keyring';
import { withApi, withMulti, withObservable } from '@polkadot/ui-api';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { assert, isFunction } from '@polkadot/util';
import { format } from '@polkadot/util/logger';
@ -29,7 +28,7 @@ type BaseProps = BareProps & {
queueSetTxStatus: QueueTx$MessageSetStatus
};
type Props = I18nProps & ApiProps & BaseProps & {
type Props = I18nProps & ApiProps & ApiInjectedProps & BaseProps & {
allAccounts?: SubjectInfo
};
@ -40,14 +39,6 @@ type State = {
unlockError?: string | null
};
type WindowInjected = Window & {
injectedWeb3: {
[index: string]: {
signer: ISigner
}
}
};
class Signer extends React.PureComponent<Props, State> {
state: State = {
isSendable: false,
@ -322,7 +313,7 @@ class Signer extends React.PureComponent<Props, State> {
}
private async makeExtrinsicCall (extrinsic: SubmittableExtrinsic, { id, txFailedCb, txSuccessCb, txUpdateCb }: QueueTx, extrinsicCall: (...params: Array<any>) => any, pair?: KeyringPair): Promise<void> {
const { api, queueSetTxStatus } = this.props;
const { api, injectedPromise, queueSetTxStatus } = this.props;
console.log('makeExtrinsicCall: extrinsic ::', extrinsic.toHex());
@ -331,13 +322,13 @@ class Signer extends React.PureComponent<Props, State> {
if (pair) {
// set the signer
if (pair.getMeta().isInjected) {
const injWindow = window as WindowInjected;
const source = pair.getMeta().source;
const signer = source && injWindow.injectedWeb3 && injWindow.injectedWeb3[source].signer;
const sources = await injectedPromise;
const injected = source && sources.find(({ name }) => name === source);
assert(signer, 'Unable to find a signer');
assert(injected, 'Unable to find a signer');
api.setSigner(signer);
api.setSigner((injected as any).signer);
params.push(pair.address());
} else {
@ -403,5 +394,6 @@ export default withMulti(
Signer,
translate,
withApi,
withInjected,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
);