Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the enable() interface (prompts user) #20

Merged
merged 15 commits into from
May 23, 2019
44 changes: 30 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,45 @@ Once added, you can create an account (via a generated seed) or import via an ex
The extension injects `injectedWeb3` into the global `window` object, exposing the following:

```js
// a version that identifies the actual injection version (future-use)
type Version = 0;
window.injectedWeb3 = {
// this is the name for this extension, there could be multiples injected,
// each with their own keys
'polkadot-js': {
// same as above, just easier to manage having accessible here
name: 'polkadot-js',
// semver for the package
version: '0.1.0',

// this is called to enable the injection, and returns an injected
// object containing the accounts, signer and provider interfaces
// (or it will reject if not authorized)
enable (originName: string): Promise<Injected>
}
}
```

When there is more than one extension, each will populate an entry above, so from an extension implementation perspective, the structure should not be overridded. The injected interface, when returned via `enable`, contains the following -

// an interface describing an account
interface Account {
readonly address: string; // ss-58 encoded address
readonly name?: string; // optional name for display
```js
interface Injected {
readonly accounts: Accounts;
readonly signer: Signer;
// not injected as of yet, subscriptable provider for polkadot-js injection
// readonly provider: Provider
}

// exposes accounts
interface Accounts {
get: () => Promise<Array<Account>>;
get (): Promise<Array<{
readonly address: string; // ss-58 encoded address
readonly name?: string; // optional name for display
}>>;
}

// a signer that communicates with the extension via sendMessage
interface Signer extends SignerInterface {
// no specific signer extensions
}

interface Injected {
readonly accounts: Accounts;
readonly signer: Signer;
readonly version: Version;
// no specific signer extensions, exposes the `sign` interface for use by
// the polkadot-js API
}
```

Expand Down
7 changes: 3 additions & 4 deletions packages/extension-ui/src/Popup/Accounts/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import { OnActionFromCtx } from '../../components/types';

import React, { useState } from 'react';
import { Link } from 'react-router-dom';

import { ActionBar, Address, withAction } from '../../components';
import { ActionBar, Address, Link, withOnAction } from '../../components';
import { editAccount } from '../../messaging';
import { Name } from '../../partials';

Expand Down Expand Up @@ -53,11 +52,11 @@ function Account ({ address, className, onAction }: Props) {
}
>
<ActionBar>
<a href='#' onClick={toggleEdit}>Edit</a>
<Link onClick={toggleEdit}>Edit</Link>
<Link to={`/account/forget/${address}`}>Forget</Link>
</ActionBar>
</Address>
);
}

export default withAction(Account);
export default withOnAction(Account);
56 changes: 56 additions & 0 deletions packages/extension-ui/src/Popup/Authorize/Request.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2019 @polkadot/extension-ui 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 { MessageAuthorize } from '@polkadot/extension/background/types';
import { OnActionFromCtx } from '../../components/types';

import React from 'react';
import styled from 'styled-components';

import { ActionBar, Box, Button, Link, defaults, withOnAction } from '../../components';
import { approveAuthRequest, rejectAuthRequest } from '../../messaging';

type Props = {
authId: number,
className?: string,
isFirst: boolean,
onAction: OnActionFromCtx,
request: MessageAuthorize,
url: string
};

function Request ({ authId, className, isFirst, onAction, request: { origin }, url }: Props) {
const onApprove = (): Promise<void> =>
approveAuthRequest(authId)
.then(() => onAction())
.catch(console.error);
const onReject = (): void => {
rejectAuthRequest(authId)
.then(() => onAction())
.catch(console.error);
};

return (
<Box className={className}>
<div>The application, identified as <div className='tab-name'>{origin}</div> is requesting access from <div className='tab-url'>{url}</div> to the accounts and signing capabilities of this extension. Only approve the request if you trust the application.</div>
<ActionBar>
<Link isDanger onClick={onReject}>Reject</Link>
</ActionBar>
{isFirst && (
<Button
label='Yes, allow this application access'
onClick={onApprove}
/>
)}
</Box>
);
}

export default withOnAction(styled(Request)`
.tab-name,
.tab-url {
color: ${defaults.linkColor};
display: inline-block;
}
`);
33 changes: 33 additions & 0 deletions packages/extension-ui/src/Popup/Authorize/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2019 @polkadot/extension-ui 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 { AuthRequestsFromCtx } from '../../components/types';

import React from 'react';

import { Header, withAuthRequests } from '../../components';
import Request from './Request';

type Props = {
requests: AuthRequestsFromCtx
};

function Authorize ({ requests }: Props) {
return (
<div>
<Header label='authorize' />
{requests.map(([id, request, url], index) => (
<Request
authId={id}
isFirst={index === 0}
key={id}
request={request}
url={url}
/>
))}
</div>
);
}

export default withAuthRequests(Authorize);
4 changes: 2 additions & 2 deletions packages/extension-ui/src/Popup/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OnActionFromCtx } from '../components/types';

import React, { useState, useEffect } from 'react';

import { Address, Button, Header, Loading, TextArea, withAction } from '../components';
import { Address, Button, Header, Loading, TextArea, withOnAction } from '../components';
import { createAccount, createSeed } from '../messaging';
import { Back, Name, Password } from '../partials';

Expand Down Expand Up @@ -69,4 +69,4 @@ function Create ({ onAction }: Props) {
);
}

export default withAction(Create);
export default withOnAction(Create);
4 changes: 2 additions & 2 deletions packages/extension-ui/src/Popup/Forget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { OnActionFromCtx } from '../components/types';
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';

import { Address, Button, Header, Tip, withAction } from '../components';
import { Address, Button, Header, Tip, withOnAction } from '../components';
import { forgetAccount } from '../messaging';
import { Back } from '../partials';

Expand Down Expand Up @@ -37,4 +37,4 @@ function Forget ({ match: { params: { address } }, onAction }: Props) {
);
}

export default withAction(withRouter(Forget));
export default withOnAction(withRouter(Forget));
4 changes: 2 additions & 2 deletions packages/extension-ui/src/Popup/Import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OnActionFromCtx } from '../components/types';

import React, { useState } from 'react';

import { Address, Button, Header, TextArea, withAction } from '../components';
import { Address, Button, Header, TextArea, withOnAction } from '../components';
import { createAccount, validateSeed } from '../messaging';
import { Back, Name, Password } from '../partials';

Expand Down Expand Up @@ -67,4 +67,4 @@ function Import ({ onAction }: Props) {
);
}

export default withAction(Import);
export default withOnAction(Import);
12 changes: 6 additions & 6 deletions packages/extension-ui/src/Popup/Signing/Request.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { OnActionFromCtx } from '../../components/types';

import React from 'react';

import { ActionBar, Address, withAction } from '../../components';
import { approveRequest, cancelRequest } from '../../messaging';
import { ActionBar, Address, Link, withOnAction } from '../../components';
import { approveSignRequest, cancelSignRequest } from '../../messaging';
import Details from './Details';
import Unlock from './Unlock';

Expand All @@ -22,12 +22,12 @@ type Props = {

function Request ({ isFirst, onAction, request: { address, genesisHash, method, nonce }, signId, url }: Props) {
const onCancel = (): void => {
cancelRequest(signId)
cancelSignRequest(signId)
.then(() => onAction())
.catch(console.error);
};
const onSign = (password: string): Promise<void> =>
approveRequest(signId, password).then(() => onAction());
approveSignRequest(signId, password).then(() => onAction());

return (
<Address address={address}>
Expand All @@ -38,11 +38,11 @@ function Request ({ isFirst, onAction, request: { address, genesisHash, method,
url={url}
/>
<ActionBar>
<a href='#' onClick={onCancel}>Cancel</a>
<Link isDanger onClick={onCancel}>Cancel</Link>
</ActionBar>
{isFirst && <Unlock onSign={onSign} />}
</Address>
);
}

export default withAction(Request);
export default withOnAction(Request);
1 change: 1 addition & 0 deletions packages/extension-ui/src/Popup/Signing/Unlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ function Unlock ({ className, onSign }: Props) {

export default styled(Unlock)`
margin-bottom: -0.75rem;
margin-left: -3rem;
`;
10 changes: 5 additions & 5 deletions packages/extension-ui/src/Popup/Signing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { RequestsFromCtx } from '../../components/types';
import { SignRequestsFromCtx } from '../../components/types';

import React from 'react';

import { Header, withRequests } from '../../components';
import { Header, withSignRequests } from '../../components';
import Request from './Request';

type Props = {
requests: RequestsFromCtx
requests: SignRequestsFromCtx
};

function Signing ({ requests }: Props) {
return (
<div>
<Header label='requests' />
<Header label='transactions' />
{requests.map(([id, request, url], index) => (
<Request
isFirst={index === 0}
Expand All @@ -30,4 +30,4 @@ function Signing ({ requests }: Props) {
);
}

export default withRequests(Signing);
export default withSignRequests(Signing);
43 changes: 27 additions & 16 deletions packages/extension-ui/src/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { MessageExtrinsicSign } from '@polkadot/extension/background/types';
import { AuthorizeRequest, SigningRequest } from '@polkadot/extension/background/types';
import { KeyringJson } from '@polkadot/ui-keyring/types';

import React, { useEffect, useState } from 'react';
import { Route, Switch } from 'react-router';

import { Loading } from '../components';
import { AccountContext, ActionContext, SigningContext } from '../components/contexts';
import { getAccounts, getRequests } from '../messaging';
import { AccountContext, ActionContext, AuthorizeContext, SigningContext } from '../components/contexts';
import { getAccounts, getAuthRequests, getSignRequests } from '../messaging';
import Accounts from './Accounts';
import Authorize from './Authorize';
import Create from './Create';
import Forget from './Forget';
import Import from './Import';
Expand All @@ -21,15 +22,17 @@ type Props = {};

export default function Popup (props: Props) {
const [accounts, setAccounts] = useState(null as null | Array<KeyringJson>);
const [requests, setRequests] = useState(null as null | Array<[number, MessageExtrinsicSign, string]>);
const [authRequests, setAuthRequests] = useState(null as null | Array<AuthorizeRequest>);
const [signRequests, setSignRequests] = useState(null as null | Array<SigningRequest>);

const onAction = (to?: string): void => {
// loads all accounts & requests (this is passed through to children to trigger changes)
Promise
.all([getAccounts(), getRequests()])
.then(([accounts, requests]) => {
.all([getAccounts(), getAuthRequests(), getSignRequests()])
.then(([accounts, authRequests, signRequests]) => {
setAccounts(accounts);
setRequests(requests);
setAuthRequests(authRequests);
setSignRequests(signRequests);
})
.catch(console.error);

Expand All @@ -43,18 +46,26 @@ export default function Popup (props: Props) {
onAction();
}, []);

const Root = authRequests && authRequests.length
? Authorize
: signRequests && signRequests.length
? Signing
: Accounts;

return (
<Loading>{accounts && requests && (
<Loading>{accounts && authRequests && signRequests && (
<ActionContext.Provider value={onAction}>
<AccountContext.Provider value={accounts}>
<SigningContext.Provider value={requests}>
<Switch>
<Route path='/account/create' component={Create} />
<Route path='/account/forget/:address' component={Forget} />
<Route path='/account/import' component={Import} />
<Route exact path='/' component={requests.length ? Signing : Accounts} />
</Switch>
</SigningContext.Provider>
<AuthorizeContext.Provider value={authRequests}>
<SigningContext.Provider value={signRequests}>
<Switch>
<Route path='/account/create' component={Create} />
<Route path='/account/forget/:address' component={Forget} />
<Route path='/account/import' component={Import} />
<Route exact path='/' component={Root} />
</Switch>
</SigningContext.Provider>
</AuthorizeContext.Provider>
</AccountContext.Provider>
</ActionContext.Provider>
)}</Loading>
Expand Down
5 changes: 3 additions & 2 deletions packages/extension-ui/src/components/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ function Box ({ children, className }: Props) {
}

export default styled(Box)`
background: transparent;
border-top: ${defaults.hdrBorder};
background: white;
border: ${defaults.boxBorder};
border-radius: ${defaults.borderRadius};
box-shadow: ${defaults.boxShadow};
color: ${defaults.color};
font-family: ${defaults.fontFamily};
font-size: ${defaults.fontSize};
Expand Down
1 change: 0 additions & 1 deletion packages/extension-ui/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ function Header ({ children, className, label }: Props) {

export default styled(Header)`
background: ${defaults.hdrBg};
border-top: ${defaults.hdrBorder};
box-sizing: border-box;
color: ${defaults.hdrColor};
font-family: ${defaults.fontFamily};
Expand Down
Loading