From 86a767de68f58cb9fa093287343dda63ac2b529f Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 23 May 2019 12:43:39 +0200 Subject: [PATCH] Make enable() call to extensions (#1194) * Make enable() call to extensions * It works * Add overlay for injected (prompt) * Use signer from injectedPromise --- packages/apps/src/index.tsx | 24 ++++----- packages/apps/src/overlays/Connecting.tsx | 18 +++++++ packages/ui-api/src/Api.tsx | 66 +++++++++++------------ packages/ui-api/src/Injected.tsx | 44 +++++++++++++++ packages/ui-api/src/index.ts | 1 + packages/ui-api/src/types.ts | 29 ++++++++++ packages/ui-signer/src/Modal.tsx | 26 ++++----- 7 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 packages/ui-api/src/Injected.tsx diff --git a/packages/apps/src/index.tsx b/packages/apps/src/index.tsx index 309252698e..9005e373ed 100644 --- a/packages/apps/src/index.tsx +++ b/packages/apps/src/index.tsx @@ -43,19 +43,17 @@ ReactDOM.render( - {({ queueExtrinsic, queueSetTxStatus }) => { - return ( - - - - - - ); - }} + {({ queueExtrinsic, queueSetTxStatus }) => ( + + + + + + )} , diff --git a/packages/apps/src/overlays/Connecting.tsx b/packages/apps/src/overlays/Connecting.tsx index 0a98fd12ee..065df037e4 100644 --- a/packages/apps/src/overlays/Connecting.tsx +++ b/packages/apps/src/overlays/Connecting.tsx @@ -19,6 +19,24 @@ const isFirefox = typeof InstallTrigger !== 'undefined'; class Connecting extends React.PureComponent { render () { + return this.renderExtension() || this.renderConnecting(); + } + + private renderExtension () { + const { isWaitingInjected, t } = this.props; + + if (!isWaitingInjected) { + return null; + } + + return ( + +
{t('Waiting for authorization from the extension. Please open the installed extension and approve or reject access.')}
+
+ ); + } + + private renderConnecting () { const { isApiConnected, t } = this.props; if (isApiConnected) { diff --git a/packages/ui-api/src/Api.tsx b/packages/ui-api/src/Api.tsx index c52f5a252c..1172983d41 100644 --- a/packages/ui-api/src/Api.tsx +++ b/packages/ui-api/src/Api.tsx @@ -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> - }, - signer: Signer - } - } -}; - export { api }; -export default class ApiWrapper extends React.PureComponent { +class ApiWrapper extends React.PureComponent { 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 { 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 { } private async retrieveInjected (): Promise> { + const { injectedPromise } = this.props; const injected: Array = []; - 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 { } render () { - const { api, apiDefaultTx, chain, isApiConnected, isApiReady, isDevelopment, setApiUrl } = this.state; + const { api, apiDefaultTx, chain, isApiConnected, isApiReady, isDevelopment, isWaitingInjected, setApiUrl } = this.state; return ( { isApiConnected, isApiReady: isApiReady && !!chain, isDevelopment, + isWaitingInjected, setApiUrl }} > @@ -194,3 +190,5 @@ export default class ApiWrapper extends React.PureComponent { ); } } + +export default withInjected(ApiWrapper); diff --git a/packages/ui-api/src/Injected.tsx b/packages/ui-api/src/Injected.tsx new file mode 100644 index 0000000000..c3eddd47ac --- /dev/null +++ b/packages/ui-api/src/Injected.tsx @@ -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) + : 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); + +const InjectedContext = React.createContext({ injectedAvailable, injectedPromise }); +const InjectedConsumer = InjectedContext.Consumer; + +export { + InjectedConsumer +}; + +export function withInjected

(Component: React.ComponentType

): React.ComponentType> { + return (props: Subtract) => { + return ( + + {(injected) => ( + // @ts-ignore Something here with the props are going wonky + + )} + + ); + }; +} diff --git a/packages/ui-api/src/index.ts b/packages/ui-api/src/index.ts index e0cb450546..42081a22de 100644 --- a/packages/ui-api/src/index.ts +++ b/packages/ui-api/src/index.ts @@ -5,6 +5,7 @@ import Api, { api } from './Api'; import ApiContext from './ApiContext'; +export * from './Injected'; export * from './with'; export { diff --git a/packages/ui-api/src/types.ts b/packages/ui-api/src/types.ts index 80fd3feadb..6741d6d1c6 100644 --- a/packages/ui-api/src/types.ts +++ b/packages/ui-api/src/types.ts @@ -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> + }, + signer: Signer +}; + +export type WindowInjected = Window & { + injectedWeb3: { + [index: string]: WindowInjectedInfo & { + enable: (origin: string) => Promise + } + } +}; + +export type ApiInjected = WindowInjectedInfo & WindowInjectedResult; + +export type ApiInjectedProps = { + injectedAvailable: boolean, + injectedPromise: Promise> +}; + export type OnChangeCb$Obs = { next: (value?: any) => any }; export type OnChangeCb$Fn = (value?: any) => any; diff --git a/packages/ui-signer/src/Modal.tsx b/packages/ui-signer/src/Modal.tsx index 150d085260..0dd1cf89b5 100644 --- a/packages/ui-signer/src/Modal.tsx +++ b/packages/ui-signer/src/Modal.tsx @@ -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 { state: State = { isSendable: false, @@ -322,7 +313,7 @@ class Signer extends React.PureComponent { } private async makeExtrinsicCall (extrinsic: SubmittableExtrinsic, { id, txFailedCb, txSuccessCb, txUpdateCb }: QueueTx, extrinsicCall: (...params: Array) => any, pair?: KeyringPair): Promise { - 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 { 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' }) );