Swap to eslint (#72)

* 626 problems (430 errors, 196 warnings)

* A start.

* < 200

* 80

* 0
This commit is contained in:
Jaco Greeff 2019-07-12 20:09:44 +02:00 committed by GitHub
parent da16a494e8
commit c70d9a0e63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 598 additions and 643 deletions

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
**/build/*
**/coverage/*
**/node_modules/*

1
.eslintrc.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('@polkadot/dev-react/config/eslint');

View File

@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const config = require('@polkadot/dev/config/jest'); const config = require('@polkadot/dev/config/jest');
module.exports = Object.assign({}, config, { module.exports = Object.assign({}, config, {

View File

@ -19,7 +19,7 @@
"build": "NODE_ENV=production polkadot-dev-build-ts", "build": "NODE_ENV=production polkadot-dev-build-ts",
"build:zip": "(cd packages/extension/build && zip -r -FS ../../../master-build.zip *)", "build:zip": "(cd packages/extension/build && zip -r -FS ../../../master-build.zip *)",
"check": "yarn lint", "check": "yarn lint",
"lint": "tslint --project . && tsc --noEmit --pretty", "lint": "eslint --ext .js,.jsx,.ts,.tsx . && tsc --noEmit --pretty",
"clean": "polkadot-dev-clean-build", "clean": "polkadot-dev-clean-build",
"postinstall": "polkadot-dev-yarn-only", "postinstall": "polkadot-dev-yarn-only",
"test": "echo \"no tests... yet\"", "test": "echo \"no tests... yet\"",

View File

@ -8,5 +8,5 @@ import singleSource from './singleSource';
export default function initCompat (): Promise<boolean> { export default function initCompat (): Promise<boolean> {
return Promise.all([ return Promise.all([
singleSource() singleSource()
]).then(() => true); ]).then((): boolean => true);
} }

View File

@ -3,34 +3,34 @@
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { Signer } from '@polkadot/api/types'; import { Signer } from '@polkadot/api/types';
import { InjectedAccount, InjectedWindow } from '../types'; import { Injected, InjectedAccount, InjectedWindow } from '../types';
// RxJs interface, only the bare-bones of what we need here // RxJs interface, only the bare-bones of what we need here
type Subscriber<T> = { interface Subscriber<T> {
subscribe: (cb: (value: T) => void) => { subscribe: (cb: (value: T) => void) => {
unsubscribe (): any unsubscribe (): void;
} };
}; }
type SingleSourceAccount = { interface SingleSourceAccount {
address: string, address: string;
assets: Array<{ assetId: number }>, assets: { assetId: number }[];
name: string name: string;
}; }
type SingleSource = { interface SingleSource {
accounts$: Subscriber<Array<SingleSourceAccount>>, accounts$: Subscriber<SingleSourceAccount[]>;
environment$: Subscriber<string>, environment$: string[];
signer: Signer signer: Signer;
}; }
type SingleWindow = Window & InjectedWindow & { type SingleWindow = Window & InjectedWindow & {
SingleSource: SingleSource SingleSource: SingleSource;
}; };
// transfor the SingleSource accounts into a simple address/name array // transfor the SingleSource accounts into a simple address/name array
function transformAccounts (accounts: Array<SingleSourceAccount>): Array<InjectedAccount> { function transformAccounts (accounts: SingleSourceAccount[]): InjectedAccount[] {
return accounts.map(({ address, name }) => ({ return accounts.map(({ address, name }): InjectedAccount => ({
address, address,
name name
})); }));
@ -38,24 +38,25 @@ function transformAccounts (accounts: Array<SingleSourceAccount>): Array<Injecte
// add a compat interface of SingleSource to window.injectedWeb3 // add a compat interface of SingleSource to window.injectedWeb3
function injectSingleSource (win: SingleWindow): void { function injectSingleSource (win: SingleWindow): void {
let accounts: Array<InjectedAccount> = []; let accounts: InjectedAccount[] = [];
// we don't yet have an accounts subscribe on the interface, simply get the // we don't yet have an accounts subscribe on the interface, simply get the
// accounts and store them, any get will resolve the last found values // accounts and store them, any get will resolve the last found values
win.SingleSource.accounts$.subscribe((_accounts) => { win.SingleSource.accounts$.subscribe((_accounts): void => {
accounts = transformAccounts(_accounts); accounts = transformAccounts(_accounts);
}); });
// decorate the compat interface // decorate the compat interface
win.injectedWeb3['SingleSource'] = { win.injectedWeb3['SingleSource'] = {
enable: async (origin: string) => ({ // eslint-disable-next-line @typescript-eslint/no-unused-vars
enable: async (origin: string): Promise<Injected> => ({
accounts: { accounts: {
get: async () => get: async (): Promise<InjectedAccount[]> =>
accounts, accounts,
subscribe: (cb: (accounts: Array<InjectedAccount>) => any) => { subscribe: (cb: (accounts: InjectedAccount[]) => void): () => void => {
const sub = win.SingleSource.accounts$.subscribe((accounts) => const sub = win.SingleSource.accounts$.subscribe((accounts): void => {
cb(transformAccounts(accounts)) cb(transformAccounts(accounts));
); });
return (): void => { return (): void => {
sub.unsubscribe(); sub.unsubscribe();
@ -71,8 +72,8 @@ function injectSingleSource (win: SingleWindow): void {
// returns the SingleSource instance, as per // returns the SingleSource instance, as per
// https://github.com/cennznet/singlesource-extension/blob/f7cb35b54e820bf46339f6b88ffede1b8e140de0/react-example/src/App.js#L19 // https://github.com/cennznet/singlesource-extension/blob/f7cb35b54e820bf46339f6b88ffede1b8e140de0/react-example/src/App.js#L19
export default function initSingleSource (): Promise<boolean> { export default function initSingleSource (): Promise<boolean> {
return new Promise((resolve) => { return new Promise((resolve): void => {
window.addEventListener('load', () => { window.addEventListener('load', (): void => {
const win = window as SingleWindow; const win = window as SingleWindow;
if (win.SingleSource) { if (win.SingleSource) {

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { InjectedAccount, InjectedAccountWithMeta, InjectedExtension, InjectedWindow, Unsubcall } from './types'; import { Injected, InjectedAccount, InjectedAccountWithMeta, InjectedExtension, InjectedExtensionInfo, InjectedWindow, Unsubcall } from './types';
// our extension adaptor for other kinds of extensions // our extension adaptor for other kinds of extensions
import compatInjector from './compat'; import compatInjector from './compat';
@ -24,8 +24,8 @@ function throwError (method: string): never {
} }
// internal helper to map from Array<InjectedAccount> -> Array<InjectedAccountWithMeta> // internal helper to map from Array<InjectedAccount> -> Array<InjectedAccountWithMeta>
function mapAccounts (source: string, list: Array<InjectedAccount>): Array<InjectedAccountWithMeta> { function mapAccounts (source: string, list: InjectedAccount[]): InjectedAccountWithMeta[] {
return list.map(({ address, name }) => ({ return list.map(({ address, name }): InjectedAccountWithMeta => ({
address, address,
meta: { name, source } meta: { name, source }
})); }));
@ -35,65 +35,70 @@ function mapAccounts (source: string, list: Array<InjectedAccount>): Array<Injec
let isWeb3Injected = web3IsInjected(); let isWeb3Injected = web3IsInjected();
// we keep the last promise created around (for queries) // we keep the last promise created around (for queries)
let web3EnablePromise: Promise<Array<InjectedExtension>> | null = null; let web3EnablePromise: Promise<InjectedExtension[]> | null = null;
export { isWeb3Injected, web3EnablePromise }; export { isWeb3Injected, web3EnablePromise };
// enables all the providers found on the injected window interface // enables all the providers found on the injected window interface
export function web3Enable (originName: string): Promise<Array<InjectedExtension>> { export function web3Enable (originName: string): Promise<InjectedExtension[]> {
web3EnablePromise = compatInjector().then(() => web3EnablePromise = compatInjector()
Promise.all( .then((): Promise<InjectedExtension[]> =>
Object.entries(win.injectedWeb3).map(([name, { enable, version }]) => Promise
Promise.all([ .all(
Promise.resolve({ name, version }), Object.entries(win.injectedWeb3).map(([name, { enable, version }]): Promise<[InjectedExtensionInfo, Injected | void]> =>
enable(originName).catch((error: Error) => { Promise.all([
console.error(`Error initializing ${name}: ${error.message}`); Promise.resolve({ name, version }),
}) enable(originName).catch((error: Error): void => {
]) console.error(`Error initializing ${name}: ${error.message}`);
) })
) ])
.then((values) => )
values )
.filter(([, ext]) => ext) .then((values: [InjectedExtensionInfo, Injected | void][]): InjectedExtension[] =>
.map(([info, ext]) => { values
// if we don't have an accounts subscriber, add a single-shot version .filter(([, ext]): boolean => !!ext)
if (ext && !ext.accounts.subscribe) { .map(([info, ext]): InjectedExtension => {
ext.accounts.subscribe = (cb: (accounts: Array<InjectedAccount>) => any): Unsubcall => { // if we don't have an accounts subscriber, add a single-shot version
ext.accounts.get().then(cb).catch(console.error); if (ext && !ext.accounts.subscribe) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ext.accounts.subscribe = (cb: (accounts: InjectedAccount[]) => any): Unsubcall => {
ext.accounts.get().then(cb).catch(console.error);
return (): void => { return (): void => {
// no ubsubscribe needed, this is a single-shot // no ubsubscribe needed, this is a single-shot
}; };
}; };
} }
return { ...info, ...ext } as InjectedExtension; const injected: Partial<InjectedExtension> = { ...info, ...ext };
return injected as InjectedExtension;
})
)
.catch((): InjectedExtension[] => [] as InjectedExtension[])
.then((values): InjectedExtension[] => {
const names = values.map(({ name, version }): string => `${name}/${version}`);
isWeb3Injected = web3IsInjected();
console.log(`web3Enable: Enabled ${values.length} extension${values.length !== 1 ? 's' : ''}: ${names.join(', ')}`);
return values;
}) })
) );
.catch(() => [] as Array<InjectedExtension>)
.then((values) => {
const names = values.map(({ name, version }) => `${name}/${version}`);
isWeb3Injected = web3IsInjected();
console.log(`web3Enable: Enabled ${values.length} extension${values.length !== 1 ? 's' : ''}: ${names.join(', ')}`);
return values;
})
);
return web3EnablePromise; return web3EnablePromise;
} }
// retrieve all the accounts accross all providers // retrieve all the accounts accross all providers
export async function web3Accounts (): Promise<Array<InjectedAccountWithMeta>> { export async function web3Accounts (): Promise<InjectedAccountWithMeta[]> {
if (!web3EnablePromise) { if (!web3EnablePromise) {
return throwError('web3Accounts'); return throwError('web3Accounts');
} }
const accounts: Array<InjectedAccountWithMeta> = []; const accounts: InjectedAccountWithMeta[] = [];
const injected = await web3EnablePromise; const injected = await web3EnablePromise;
const retrieved = await Promise.all( const retrieved = await Promise.all(
injected.map(async ({ accounts, name: source }) => { injected.map(async ({ accounts, name: source }): Promise<InjectedAccountWithMeta[]> => {
try { try {
const list = await accounts.get(); const list = await accounts.get();
@ -105,57 +110,46 @@ export async function web3Accounts (): Promise<Array<InjectedAccountWithMeta>> {
}) })
); );
retrieved.forEach((result) => accounts.push(...result)); retrieved.forEach((result): void => {
accounts.push(...result);
});
const addresses = accounts.map(({ address }) => address); const addresses = accounts.map(({ address }): string => address);
console.log(`web3Accounts: Found ${accounts.length} address${accounts.length !== 1 ? 'es' : ''}: ${addresses.join(', ')}`); console.log(`web3Accounts: Found ${accounts.length} address${accounts.length !== 1 ? 'es' : ''}: ${addresses.join(', ')}`);
return accounts; return accounts;
} }
export async function web3AccountsSubscribe (cb: (accounts: Array<InjectedAccountWithMeta>) => any): Promise<Unsubcall> { // eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function web3AccountsSubscribe (cb: (accounts: InjectedAccountWithMeta[]) => any): Promise<Unsubcall> {
if (!web3EnablePromise) { if (!web3EnablePromise) {
return throwError('web3AccountsSubscribe'); return throwError('web3AccountsSubscribe');
} }
const accounts: { [source: string]: Array<InjectedAccount> } = {}; const accounts: Record<string, InjectedAccount[]> = {};
const triggerUpdate = (): void => { const triggerUpdate = (): void => {
cb(Object.entries(accounts).reduce((result, [source, list]) => { cb(Object.entries(accounts).reduce((result, [source, list]): InjectedAccountWithMeta[] => {
result.push(...mapAccounts(source, list)); result.push(...mapAccounts(source, list));
return result; return result;
}, [] as Array<InjectedAccountWithMeta>)); }, [] as InjectedAccountWithMeta[]));
}; };
const unsubs = (await web3EnablePromise).map(({ accounts: { subscribe }, name: source }) => const unsubs = (await web3EnablePromise).map(({ accounts: { subscribe }, name: source }): Unsubcall =>
subscribe((result) => { subscribe((result): void => {
accounts[source] = result; accounts[source] = result;
triggerUpdate(); triggerUpdate();
}) })
); );
return (): void => { return (): void => {
unsubs.forEach((unsub) => unsub()); unsubs.forEach((unsub): void => {
unsub();
});
}; };
} }
// find a specific provider based on an address
export async function web3FromAddress (address: string): Promise<InjectedExtension> {
if (!web3EnablePromise) {
return throwError('web3FromAddress');
}
const accounts = await web3Accounts();
const found = address && accounts.find((account) => account.address === address);
if (!found) {
throw new Error(`web3FromAddress: Unable to find injected ${address}`);
}
return web3FromSource(found.meta.source);
}
// find a specific provider based on the name // find a specific provider based on the name
export async function web3FromSource (source: string): Promise<InjectedExtension> { export async function web3FromSource (source: string): Promise<InjectedExtension> {
if (!web3EnablePromise) { if (!web3EnablePromise) {
@ -163,7 +157,7 @@ export async function web3FromSource (source: string): Promise<InjectedExtension
} }
const sources = await web3EnablePromise; const sources = await web3EnablePromise;
const found = source && sources.find(({ name }) => name === source); const found = source && sources.find(({ name }): boolean => name === source);
if (!found) { if (!found) {
throw new Error(`web3FromSource: Unable to find an injected ${source}`); throw new Error(`web3FromSource: Unable to find an injected ${source}`);
@ -171,3 +165,19 @@ export async function web3FromSource (source: string): Promise<InjectedExtension
return found; return found;
} }
// find a specific provider based on an address
export async function web3FromAddress (address: string): Promise<InjectedExtension> {
if (!web3EnablePromise) {
return throwError('web3FromAddress');
}
const accounts = await web3Accounts();
const found = address && accounts.find((account): boolean => account.address === address);
if (!found) {
throw new Error(`web3FromAddress: Unable to find injected ${address}`);
}
return web3FromSource(found.meta.source);
}

View File

@ -14,17 +14,18 @@ export interface InjectedAccount {
export interface InjectedAccountWithMeta { export interface InjectedAccountWithMeta {
address: string; address: string;
meta: { meta: {
name: string, name: string;
source: string source: string;
}; };
} }
export interface InjectedAccounts { export interface InjectedAccounts {
get: () => Promise<Array<InjectedAccount>>; get: () => Promise<InjectedAccount[]>;
subscribe: (cb: (accounts: Array<InjectedAccount>) => any) => Unsubcall; // eslint-disable-next-line @typescript-eslint/no-explicit-any
subscribe: (cb: (accounts: InjectedAccount[]) => any) => Unsubcall;
} }
export interface InjectedSigner extends Signer {} export type InjectedSigner = Signer;
export interface InjectedExtensionInfo { export interface InjectedExtensionInfo {
name: string; name: string;
@ -41,10 +42,8 @@ export interface InjectedWindowProvider {
version: string; version: string;
} }
export type InjectedWindow = Window & { export interface InjectedWindow extends Window {
injectedWeb3: { injectedWeb3: Record<string, InjectedWindowProvider>;
[index: string]: InjectedWindowProvider }
}
};
export type InjectedExtension = InjectedExtensionInfo & Injected; export type InjectedExtension = InjectedExtensionInfo & Injected;

View File

@ -11,22 +11,22 @@ import { ActionBar, Address, Link, withOnAction } from '../../components';
import { editAccount } from '../../messaging'; import { editAccount } from '../../messaging';
import { Name } from '../../partials'; import { Name } from '../../partials';
type Props = { interface Props {
address: string, address: string;
className?: string, className?: string;
onAction: OnActionFromCtx onAction: OnActionFromCtx;
}; }
function Account ({ address, className, onAction }: Props) { function Account ({ address, className, onAction }: Props): React.ReactElement<Props> {
const [isEditing, setEditing] = useState(false); const [isEditing, setEditing] = useState(false);
const [editedname, setName] = useState<string | null>(null); const [editedname, setName] = useState<string | null>(null);
const toggleEdit = () => const toggleEdit = (): void =>
setEditing(!isEditing); setEditing(!isEditing);
const saveChanges = () => { const saveChanges = (): void => {
if (editedname && editedname !== name) { if (editedname && editedname !== name) {
editAccount(address, editedname) editAccount(address, editedname)
.then(() => onAction()) .then((): void => onAction())
.catch(console.error); .catch(console.error);
} }

View File

@ -9,18 +9,18 @@ import React from 'react';
import { Button, Header, Tip, withAccounts } from '../../components'; import { Button, Header, Tip, withAccounts } from '../../components';
import Account from './Account'; import Account from './Account';
type Props = { interface Props {
accounts: AccountsFromCtx accounts: AccountsFromCtx;
}; }
function Accounts ({ accounts }: Props) { function Accounts ({ accounts }: Props): React.ReactElement<Props> {
return ( return (
<div> <div>
<Header label='accounts' /> <Header label='accounts' />
{ {
(accounts.length === 0) (accounts.length === 0)
? <Tip header='add accounts' type='warn'>You currently don't have any accounts. Either create a new account or if you have an existing account you wish to use, import it with the seed phrase</Tip> ? <Tip header='add accounts' type='warn'>You currently don&apos;t have any accounts. Either create a new account or if you have an existing account you wish to use, import it with the seed phrase</Tip>
: accounts.map(({ address }) => ( : accounts.map(({ address }): React.ReactNode => (
<Account <Account
address={address} address={address}
key={address} key={address}

View File

@ -11,23 +11,23 @@ import styled from 'styled-components';
import { ActionBar, Button, Icon, IconBox, Link, Tip, defaults, withOnAction } from '../../components'; import { ActionBar, Button, Icon, IconBox, Link, Tip, defaults, withOnAction } from '../../components';
import { approveAuthRequest, rejectAuthRequest } from '../../messaging'; import { approveAuthRequest, rejectAuthRequest } from '../../messaging';
type Props = { interface Props {
authId: string, authId: string;
className?: string, className?: string;
isFirst: boolean, isFirst: boolean;
onAction: OnActionFromCtx, onAction: OnActionFromCtx;
request: MessageAuthorize, request: MessageAuthorize;
url: string url: string;
}; }
function Request ({ authId, className, isFirst, onAction, request: { origin }, url }: Props) { function Request ({ authId, className, isFirst, onAction, request: { origin }, url }: Props): React.ReactElement<Props> {
const onApprove = () => const onApprove = (): Promise<void> =>
approveAuthRequest(authId) approveAuthRequest(authId)
.then(() => onAction()) .then((): void => onAction())
.catch(console.error); .catch(console.error);
const onReject = () => const onReject = (): Promise<void> =>
rejectAuthRequest(authId) rejectAuthRequest(authId)
.then(() => onAction()) .then((): void => onAction())
.catch(console.error); .catch(console.error);
return ( return (

View File

@ -9,15 +9,15 @@ import React from 'react';
import { Header, withAuthRequests } from '../../components'; import { Header, withAuthRequests } from '../../components';
import Request from './Request'; import Request from './Request';
type Props = { interface Props {
requests: AuthRequestsFromCtx requests: AuthRequestsFromCtx;
}; }
function Authorize ({ requests }: Props) { function Authorize ({ requests }: Props): React.ReactElement<Props> {
return ( return (
<div> <div>
<Header label='authorize' /> <Header label='authorize' />
{requests.map(([id, request, url], index) => ( {requests.map(([id, request, url], index): React.ReactNode => (
<Request <Request
authId={id} authId={id}
isFirst={index === 0} isFirst={index === 0}

View File

@ -10,27 +10,27 @@ import { Address, Button, Header, Loading, TextArea, withOnAction } from '../com
import { createAccount, createSeed } from '../messaging'; import { createAccount, createSeed } from '../messaging';
import { Back, Name, Password } from '../partials'; import { Back, Name, Password } from '../partials';
type Props = { interface Props {
onAction: OnActionFromCtx onAction: OnActionFromCtx;
}; }
function Create ({ onAction }: Props) { function Create ({ onAction }: Props): React.ReactElement<Props> {
const [account, setAccount] = useState<null | { address: string, seed: string }>(null); const [account, setAccount] = useState<null | { address: string; seed: string }>(null);
const [name, setName] = useState<string | null>(null); const [name, setName] = useState<string | null>(null);
const [password, setPassword] = useState<string | null>(null); const [password, setPassword] = useState<string | null>(null);
useEffect(() => { useEffect((): void => {
createSeed() createSeed()
.then(setAccount) .then(setAccount)
.catch(console.error); .catch(console.error);
}, []); }, []);
// FIXME Duplicated between here and Import.tsx // FIXME Duplicated between here and Import.tsx
const onCreate = () => { const onCreate = (): void => {
// this should always be the case // this should always be the case
if (name && password && account) { if (name && password && account) {
createAccount(name, password, account.seed) createAccount(name, password, account.seed)
.then(() => onAction('/')) .then((): void => onAction('/'))
.catch(console.error); .catch(console.error);
} }
}; };

View File

@ -11,14 +11,14 @@ import { Address, Button, Header, Tip, withOnAction } from '../components';
import { forgetAccount } from '../messaging'; import { forgetAccount } from '../messaging';
import { Back } from '../partials'; import { Back } from '../partials';
type Props = RouteComponentProps<{ address: string }> & { interface Props extends RouteComponentProps<{ address: string }> {
onAction: OnActionFromCtx onAction: OnActionFromCtx;
}; }
function Forget ({ match: { params: { address } }, onAction }: Props) { function Forget ({ match: { params: { address } }, onAction }: Props): React.ReactElement<Props> {
const onClick = () => const onClick = (): Promise<void> =>
forgetAccount(address) forgetAccount(address)
.then(() => onAction('/')) .then((): void => onAction('/'))
.catch(console.error); .catch(console.error);
return ( return (

View File

@ -10,26 +10,26 @@ import { Address, Button, Header, TextArea, withOnAction } from '../components';
import { createAccount, validateSeed } from '../messaging'; import { createAccount, validateSeed } from '../messaging';
import { Back, Name, Password } from '../partials'; import { Back, Name, Password } from '../partials';
type Props = { interface Props {
onAction: OnActionFromCtx onAction: OnActionFromCtx;
}; }
function Import ({ onAction }: Props) { function Import ({ onAction }: Props): React.ReactElement<Props> {
const [account, setAccount] = useState<null | { address: string, seed: string }>(null); const [account, setAccount] = useState<null | { address: string; seed: string }>(null);
const [name, setName] = useState<string | null>(null); const [name, setName] = useState<string | null>(null);
const [password, setPassword] = useState<string | null>(null); const [password, setPassword] = useState<string | null>(null);
const onChangeSeed = (seed: string) => const onChangeSeed = (seed: string): Promise<void> =>
validateSeed(seed) validateSeed(seed)
.then(setAccount) .then(setAccount)
.catch(() => setAccount(null)); .catch((): void => setAccount(null));
// FIXME Duplicated between here and Create.tsx // FIXME Duplicated between here and Create.tsx
const onCreate = () => { const onCreate = (): void => {
// this should always be the case // this should always be the case
if (name && password && account) { if (name && password && account) {
createAccount(name, password, account.seed) createAccount(name, password, account.seed)
.then(() => onAction('/')) .then((): void => onAction('/'))
.catch(console.error); .catch(console.error);
} }
}; };

View File

@ -9,9 +9,10 @@ import findChain from '@polkadot/extension/chains';
import { Metadata, Method, ExtrinsicEra } from '@polkadot/types'; import { Metadata, Method, ExtrinsicEra } from '@polkadot/types';
import { formatNumber } from '@polkadot/util'; import { formatNumber } from '@polkadot/util';
type MethodJson = { interface MethodJson {
args: { [index: string]: any } // eslint-disable-next-line @typescript-eslint/no-explicit-any
}; args: Record<string, any>;
}
interface Props { interface Props {
blockNumber: number; blockNumber: number;
@ -24,7 +25,7 @@ interface Props {
url: string; url: string;
} }
function renderMethod (data: string, meta?: Metadata | null) { function renderMethod (data: string, meta?: Metadata | null): React.ReactNode {
if (!meta) { if (!meta) {
return ( return (
<tr> <tr>
@ -73,7 +74,7 @@ function renderMortality (era: ExtrinsicEra, blockNumber: number): string {
return `mortal (birth #${formatNumber(mortal.birth(blockNumber))}, death #${formatNumber(mortal.death(blockNumber))})`; return `mortal (birth #${formatNumber(mortal.birth(blockNumber))}, death #${formatNumber(mortal.death(blockNumber))})`;
} }
function Details ({ blockNumber, className, genesisHash, isDecoded, era, method, nonce, url }: Props) { function Details ({ blockNumber, className, genesisHash, isDecoded, era, method, nonce, url }: Props): React.ReactElement<Props> {
const chain = findChain(genesisHash); const chain = findChain(genesisHash);
const eera = new ExtrinsicEra(era); const eera = new ExtrinsicEra(era);

View File

@ -12,22 +12,22 @@ import { approveSignRequest, cancelSignRequest } from '../../messaging';
import Details from './Details'; import Details from './Details';
import Unlock from './Unlock'; import Unlock from './Unlock';
type Props = { interface Props {
isFirst: boolean, isFirst: boolean;
onAction: OnActionFromCtx, onAction: OnActionFromCtx;
request: MessageExtrinsicSign, request: MessageExtrinsicSign;
signId: string, signId: string;
url: string url: string;
}; }
function Request ({ isFirst, onAction, request: { address, blockNumber, era, genesisHash, method, nonce }, signId, url }: Props) { function Request ({ isFirst, onAction, request: { address, blockNumber, era, genesisHash, method, nonce }, signId, url }: Props): React.ReactElement<Props> {
const onCancel = () => const onCancel = (): Promise<void> =>
cancelSignRequest(signId) cancelSignRequest(signId)
.then(() => onAction()) .then((): void => onAction())
.catch(console.error); .catch(console.error);
const onSign = (password: string) => const onSign = (password: string): Promise<void> =>
approveSignRequest(signId, password) approveSignRequest(signId, password)
.then(() => onAction()); .then((): void => onAction());
return ( return (
<Address address={address}> <Address address={address}>

View File

@ -6,20 +6,20 @@ import React, { useState, useEffect } from 'react';
import { Button, Input } from '../../components'; import { Button, Input } from '../../components';
type Props = { interface Props {
className?: string, className?: string;
onSign: (password: string) => Promise<void> onSign: (password: string) => Promise<void>;
}; }
export default function Unlock ({ className, onSign }: Props) { export default function Unlock ({ className, onSign }: Props): React.ReactElement<Props> {
const [error, setError] = useState(''); const [error, setError] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const onClick = () => const onClick = (): Promise<void> =>
onSign(password) onSign(password)
.catch((error) => setError(error.message)); .catch((error): void => setError(error.message));
useEffect(() => { useEffect((): void => {
if (error) { if (error) {
setError(''); setError('');
} }

View File

@ -9,15 +9,15 @@ import React from 'react';
import { Header, withSignRequests } from '../../components'; import { Header, withSignRequests } from '../../components';
import Request from './Request'; import Request from './Request';
type Props = { interface Props {
requests: SignRequestsFromCtx requests: SignRequestsFromCtx;
}; }
function Signing ({ requests }: Props) { function Signing ({ requests }: Props): React.ReactElement<Props> {
return ( return (
<div> <div>
<Header label='transactions' /> <Header label='transactions' />
{requests.map(([id, request, url], index) => ( {requests.map(([id, request, url], index): React.ReactNode => (
<Request <Request
isFirst={index === 0} isFirst={index === 0}
key={id} key={id}

View File

@ -8,11 +8,11 @@ import React from 'react';
import { Box, Button, Header, withOnAction } from '../components'; import { Box, Button, Header, withOnAction } from '../components';
type Props = { interface Props {
onAction: OnActionFromCtx onAction: OnActionFromCtx;
}; }
function Welcome ({ onAction }: Props) { function Welcome ({ onAction }: Props): React.ReactElement<Props> {
const onClick = (): void => { const onClick = (): void => {
window.localStorage.setItem('welcome_read', 'ok'); window.localStorage.setItem('welcome_read', 'ok');
onAction(); onAction();
@ -26,7 +26,7 @@ function Welcome ({ onAction }: Props) {
<ul> <ul>
<li>We do not send any clicks, pageviews or events to a central server</li> <li>We do not send any clicks, pageviews or events to a central server</li>
<li>We do not use any trackers or analytics</li> <li>We do not use any trackers or analytics</li>
<li>We don't collect keys, addresses or any information - your information never leaves this machine</li> <li>We don&apos;t collect keys, addresses or any information - your information never leaves this machine</li>
</ul> </ul>
... we are not in the information collection business (even anonymized). ... we are not in the information collection business (even anonymized).
<Button <Button

View File

@ -19,12 +19,11 @@ import Import from './Import';
import Signing from './Signing'; import Signing from './Signing';
import Welcome from './Welcome'; import Welcome from './Welcome';
type Props = {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function Popup (props: {}): React.ReactElement<{}> {
export default function Popup (props: Props) { const [accounts, setAccounts] = useState<null | KeyringJson[]>(null);
const [accounts, setAccounts] = useState<null | Array<KeyringJson>>(null); const [authRequests, setAuthRequests] = useState<null | AuthorizeRequest[]>(null);
const [authRequests, setAuthRequests] = useState<null | Array<AuthorizeRequest>>(null); const [signRequests, setSignRequests] = useState<null | SigningRequest[]>(null);
const [signRequests, setSignRequests] = useState<null | Array<SigningRequest>>(null);
const [isWelcomeDone, setWelcomeDone] = useState(false); const [isWelcomeDone, setWelcomeDone] = useState(false);
const onAction = (to?: string): void => { const onAction = (to?: string): void => {

View File

@ -5,12 +5,12 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
type Props = { interface Props {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
}; }
function ActionBar ({ children, className }: Props) { function ActionBar ({ children, className }: Props): React.ReactElement<Props> {
return ( return (
<div className={className}> <div className={className}>
{children} {children}

View File

@ -11,17 +11,17 @@ import Identicon from '@polkadot/ui-identicon';
import IconBox from './IconBox'; import IconBox from './IconBox';
import { withAccounts } from './contexts'; import { withAccounts } from './contexts';
type Props = { interface Props {
accounts: AccountsFromCtx, accounts: AccountsFromCtx;
address?: string | null, address?: string | null;
children?: React.ReactNode; children?: React.ReactNode;
className?: string, className?: string;
name?: React.ReactNode | null, name?: React.ReactNode | null;
theme?: 'polkadot' | 'substrate' theme?: 'polkadot' | 'substrate';
}; }
function Address ({ accounts, address, children, className, name, theme = 'polkadot' }: Props) { function Address ({ accounts, address, children, className, name, theme = 'polkadot' }: Props): React.ReactElement<Props> {
const account = accounts.find((account) => account.address === address); const account = accounts.find((account): boolean => account.address === address);
return ( return (
<IconBox <IconBox

View File

@ -7,12 +7,12 @@ import styled from 'styled-components';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
}; }
function Box ({ children, className }: Props) { function Box ({ children, className }: Props): React.ReactElement<Props> {
return ( return (
<article className={className}> <article className={className}>
{children} {children}

View File

@ -7,20 +7,20 @@ import styled from 'styled-components';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
className?: string; className?: string;
children?: React.ReactNode, children?: React.ReactNode;
isDanger?: boolean, isDanger?: boolean;
isDisabled?: boolean, isDisabled?: boolean;
isSmall?: boolean, isSmall?: boolean;
label?: string, label?: string;
onClick?: () => any, onClick?: () => void | Promise<void>;
to?: string to?: string;
}; }
const DISABLED_OPACITY = '0.3'; const DISABLED_OPACITY = '0.3';
function Button ({ children, className, isDisabled, label, onClick, to }: Props) { function Button ({ children, className, isDisabled, label, onClick, to }: Props): React.ReactElement<Props> {
const _onClick = (): void => { const _onClick = (): void => {
if (isDisabled) { if (isDisabled) {
return; return;
@ -44,54 +44,26 @@ function Button ({ children, className, isDisabled, label, onClick, to }: Props)
export default styled(Button)` export default styled(Button)`
box-sizing: border-box; box-sizing: border-box;
display: ${({ isSmall }) => display: ${({ isSmall }): string => isSmall ? 'inline-block' : 'block'};
isSmall
? 'inline-block'
: 'block'
};
margin: ${defaults.boxMargin}; margin: ${defaults.boxMargin};
padding: ${defaults.boxPadding}; padding: ${defaults.boxPadding};
width: ${({ isSmall }) => width: ${({ isSmall }): string => isSmall ? 'auto' : '100%'};
isSmall
? 'auto'
: '100%'
};
button { button {
background: ${({ isDanger }) => background: ${({ isDanger }): string => isDanger ? defaults.btnBgDanger : defaults.btnBg};
isDanger border: ${defaults.btnBorder}${({ isDanger }): string => isDanger ? defaults.btnColorDanger : defaults.btnColor};
? defaults.btnBgDanger
: defaults.btnBg
};
border: ${defaults.btnBorder}${({ isDanger }) =>
isDanger
? defaults.btnColorDanger
: defaults.btnColor
};
border-radius: ${defaults.borderRadius}; border-radius: ${defaults.borderRadius};
color: ${({ isDanger }) => color: ${({ isDanger }): string => isDanger ? defaults.btnColorDanger : defaults.btnColor};
isDanger
? defaults.btnColorDanger
: defaults.btnColor
};
cursor: pointer; cursor: pointer;
display: block; display: block;
font-size: ${defaults.fontSize}; font-size: ${defaults.fontSize};
opacity: ${({ isDisabled }) => opacity: ${({ isDisabled }): string => isDisabled ? DISABLED_OPACITY : '0.8'};
isDisabled
? DISABLED_OPACITY
: '0.8'
};
padding: ${defaults.btnPadding}; padding: ${defaults.btnPadding};
text-align: center; text-align: center;
width: 100%; width: 100%;
&:hover { &:hover {
opacity: ${({ isDisabled }) => opacity: ${({ isDisabled }): string => isDisabled ? DISABLED_OPACITY : '1.0'};
isDisabled
? DISABLED_OPACITY
: '1.0'
};
} }
} }
`; `;

View File

@ -7,13 +7,13 @@ import styled from 'styled-components';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
children?: React.ReactNode, children?: React.ReactNode;
className?: string, className?: string;
label?: string label?: string;
}; }
function Header ({ children, className, label }: Props) { function Header ({ children, className, label }: Props): React.ReactElement<Props> {
return ( return (
<h2 className={className}> <h2 className={className}>
{label}{children} {label}{children}

View File

@ -5,13 +5,13 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
type Props = { interface Props {
className?: string, className?: string;
icon: string, icon: string;
onClick?: () => any onClick?: () => void | Promise<void>;
}; }
function Icon ({ className, icon, onClick }: Props) { function Icon ({ className, icon, onClick }: Props): React.ReactElement<Props> {
return ( return (
<div <div
className={`${className} icon`} className={`${className} icon`}
@ -26,10 +26,9 @@ export default styled(Icon)`
background: white; background: white;
border-radius: 50%; border-radius: 50%;
box-sizing: border-box; box-sizing: border-box;
cursor: ${({ onClick }) => cursor: ${({ onClick }): string =>
onClick onClick
? 'pointer' ? 'pointer'
: 'inherit' : 'inherit'};
};
text-align: center; text-align: center;
`; `;

View File

@ -8,16 +8,16 @@ import styled from 'styled-components';
import Box from './Box'; import Box from './Box';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
children?: React.ReactNode; children?: React.ReactNode;
className?: string, className?: string;
icon: React.ReactNode, icon: React.ReactNode;
intro: React.ReactNode, intro: React.ReactNode;
name?: React.ReactNode | null, name?: React.ReactNode | null;
theme?: 'polkadot' | 'substrate' theme?: 'polkadot' | 'substrate';
}; }
function IconBox ({ children, className, icon, intro }: Props) { function IconBox ({ children, className, icon, intro }: Props): React.ReactElement<Props> {
return ( return (
<div className={className}> <div className={className}>
<Box className='details'> <Box className='details'>

View File

@ -8,20 +8,20 @@ import styled from 'styled-components';
import Label from './Label'; import Label from './Label';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
className?: string, className?: string;
defaultValue?: string | null, defaultValue?: string | null;
isError?: boolean, isError?: boolean;
isFocussed?: boolean, isFocussed?: boolean;
isReadOnly?: boolean, isReadOnly?: boolean;
label?: string | null, label?: string | null;
onBlur?: () => void, onBlur?: () => void;
onChange?: (value: string) => void, onChange?: (value: string) => void;
type?: 'text' | 'password', type?: 'text' | 'password';
value?: string value?: string;
}; }
function Input ({ className, defaultValue, label, isFocussed, isReadOnly, onBlur, onChange, type = 'text', value }: Props) { function Input ({ className, defaultValue, label, isFocussed, isReadOnly, onBlur, onChange, type = 'text', value }: Props): React.ReactElement<Props> {
const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>): void => { const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>): void => {
onChange && onChange(value.trim()); onChange && onChange(value.trim());
}; };
@ -46,35 +46,17 @@ function Input ({ className, defaultValue, label, isFocussed, isReadOnly, onBlur
export default styled(Input)` export default styled(Input)`
input { input {
background: ${({ isError, isReadOnly }) => background: ${({ isError, isReadOnly }): string => isError ? defaults.box.error.background : (isReadOnly ? '#eee' : '#fff')};
isError border-color: ${({ isError }): string => isError ? defaults.box.error.border : defaults.inputBorder};
? defaults.box.error.background
: isReadOnly
? '#eee'
: '#fff'
};
border-color: ${({ isError }) =>
isError
? defaults.box.error.border
: defaults.inputBorder
};
border-radius: ${defaults.borderRadius}; border-radius: ${defaults.borderRadius};
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
box-sizing: border-box; box-sizing: border-box;
color: ${({ isError }) => color: ${({ isError }): string => isError ? defaults.box.error.border : defaults.color};
isError
? defaults.box.error.border
: defaults.color
};
display: block; display: block;
font-family: ${defaults.fontFamily}; font-family: ${defaults.fontFamily};
font-size: ${defaults.fontSize}; font-size: ${defaults.fontSize};
padding: ${({ label }) => padding: ${({ label }): string => label ? defaults.inputPaddingLabel : defaults.inputPadding};
label
? defaults.inputPaddingLabel
: defaults.inputPadding
};
width: 100%; width: 100%;
&:read-only { &:read-only {

View File

@ -7,13 +7,13 @@ import styled from 'styled-components';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
children: React.ReactNode, children: React.ReactNode;
className?: string, className?: string;
label?: string | null label?: string | null;
}; }
function Label ({ children, className, label }: Props) { function Label ({ children, className, label }: Props): React.ReactElement<Props> {
return ( return (
<div className={className}> <div className={className}>
{label && <label>{label}</label>} {label && <label>{label}</label>}

View File

@ -8,15 +8,15 @@ import { Link as RouterLink } from 'react-router-dom';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
children?: React.ReactNode, children?: React.ReactNode;
className?: string, className?: string;
isDanger?: boolean, isDanger?: boolean;
onClick?: () => void, onClick?: () => void;
to?: string to?: string;
}; }
function Link ({ children, className, onClick, to }: Props) { function Link ({ children, className, onClick, to }: Props): React.ReactElement<Props> {
return ( return (
to to
? <RouterLink className={className} onClick={onClick} to={to}>{children}</RouterLink> ? <RouterLink className={className} onClick={onClick} to={to}>{children}</RouterLink>
@ -25,28 +25,16 @@ function Link ({ children, className, onClick, to }: Props) {
} }
export default styled(Link)` export default styled(Link)`
color: ${({ isDanger }) => color: ${({ isDanger }): string => isDanger ? defaults.linkColorDanger : defaults.linkColor};
isDanger
? defaults.linkColorDanger
: defaults.linkColor
};
opacity: 0.9; opacity: 0.9;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
color: ${({ isDanger }) => color: ${({ isDanger }): string => isDanger ? defaults.linkColorDanger : defaults.linkColor};
isDanger
? defaults.linkColorDanger
: defaults.linkColor
};
opacity: 1.0; opacity: 1.0;
} }
&:visited { &:visited {
color: ${({ isDanger }) => color: ${({ isDanger }): string => isDanger ? defaults.linkColorDanger : defaults.linkColor};
isDanger
? defaults.linkColorDanger
: defaults.linkColor
};
} }
`; `;

View File

@ -4,11 +4,11 @@
import React from 'react'; import React from 'react';
type Props = { interface Props {
children?: React.ReactNode children?: React.ReactNode;
}; }
export default function Loading ({ children }: Props) { export default function Loading ({ children }: Props): React.ReactElement<Props> {
if (!children) { if (!children) {
return ( return (
<div>... loading ...</div> <div>... loading ...</div>

View File

@ -8,17 +8,17 @@ import styled from 'styled-components';
import Label from './Label'; import Label from './Label';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
className?: string, className?: string;
isError?: boolean, isError?: boolean;
isFocussed?: boolean, isFocussed?: boolean;
isReadOnly?: boolean, isReadOnly?: boolean;
label: string, label: string;
onChange?: (value: string) => void, onChange?: (value: string) => void;
value?: string value?: string;
}; }
function TextArea ({ className, isFocussed, isReadOnly, label, onChange, value }: Props) { function TextArea ({ className, isFocussed, isReadOnly, label, onChange, value }: Props): React.ReactElement<Props> {
const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>): void => { const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>): void => {
onChange && onChange(value.trim()); onChange && onChange(value.trim());
}; };
@ -40,35 +40,17 @@ function TextArea ({ className, isFocussed, isReadOnly, label, onChange, value }
export default styled(TextArea)` export default styled(TextArea)`
textarea { textarea {
background: ${({ isError, isReadOnly }) => background: ${({ isError, isReadOnly }): string => isError ? defaults.box.error.background : (isReadOnly ? '#eee' : '#fff')};
isError border-color: ${({ isError }): string => isError ? defaults.box.error.border : defaults.inputBorder};
? defaults.box.error.background
: isReadOnly
? '#eee'
: '#fff'
};
border-color: ${({ isError }) =>
isError
? defaults.box.error.border
: defaults.inputBorder
};
border-radius: ${defaults.borderRadius}; border-radius: ${defaults.borderRadius};
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
box-sizing: border-box; box-sizing: border-box;
color: ${({ isError }) => color: ${({ isError }): string => isError ? defaults.box.error.border : defaults.color};
isError
? defaults.box.error.border
: defaults.color
};
display: block; display: block;
font-family: ${defaults.fontFamily}; font-family: ${defaults.fontFamily};
font-size: ${defaults.fontSize}; font-size: ${defaults.fontSize};
padding: ${({ label }) => padding: ${({ label }): string => label ? defaults.inputPaddingLabel : defaults.inputPadding};
label
? defaults.inputPaddingLabel
: defaults.inputPadding
};
resize: none; resize: none;
width: 100%; width: 100%;

View File

@ -7,26 +7,26 @@ import styled from 'styled-components';
import defaults from './defaults'; import defaults from './defaults';
type Color = { interface Color {
background: string, background: string;
border: string, border: string;
color: string color: string;
}; }
type Type = keyof typeof defaults.box; type Type = keyof typeof defaults.box;
type Props = { interface Props {
children: React.ReactNode, children: React.ReactNode;
className?: string, className?: string;
header?: React.ReactNode, header?: React.ReactNode;
type?: Type type?: Type;
}; }
function getColor ({ type }: Props): Color { function getColor ({ type }: Props): Color {
return defaults.box[type || 'info'] || defaults.box.info; return defaults.box[type || 'info'] || defaults.box.info;
} }
function Tip ({ children, className, header }: Props) { function Tip ({ children, className, header }: Props): React.ReactElement<Props> {
return ( return (
<article className={className}> <article className={className}>
{header && <h3>{header}</h3>} {header && <h3>{header}</h3>}
@ -37,22 +37,14 @@ function Tip ({ children, className, header }: Props) {
// box-shadow: ${defaults.boxShadow}; // box-shadow: ${defaults.boxShadow};
export default styled(Tip)` export default styled(Tip)`
background: ${(props) => background: ${(p): string => getColor(p).background};
getColor(props).background border-left: 0.25rem solid ${(p): string => getColor(p).border};
}; color: ${(p): string => getColor(p).color};
border-left: 0.25rem solid ${(props) =>
getColor(props).border
};
color: ${(props) =>
getColor(props).color
};
margin: 0.75rem -1rem; margin: 0.75rem -1rem;
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
h3 { h3 {
color: ${(props) => color: ${(p): string => getColor(p).border};
getColor(props).border
};
font-weight: normal; font-weight: normal;
} }
`; `;

View File

@ -7,12 +7,12 @@ import styled from 'styled-components';
import defaults from './defaults'; import defaults from './defaults';
type Props = { interface Props {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
}; }
function View ({ children, className }: Props) { function View ({ children, className }: Props): React.ReactElement<Props> {
return ( return (
<main className={className}> <main className={className}>
{children} {children}

View File

@ -6,7 +6,8 @@ import { AccountsFromCtx, OnActionFromCtx, AuthRequestsFromCtx, SignRequestsFrom
import React from 'react'; import React from 'react';
const noop = (to?: string) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars
const noop = (to?: string): void => {
// do nothing // do nothing
}; };
@ -23,10 +24,11 @@ export {
}; };
export function withAccounts<P extends { accounts: AccountsFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { accounts: AccountsFromCtx }>> { export function withAccounts<P extends { accounts: AccountsFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { accounts: AccountsFromCtx }>> {
// eslint-disable-next-line react/display-name,@typescript-eslint/explicit-function-return-type
return (props: SubtractProps<P, { accounts: AccountsFromCtx }>) => { return (props: SubtractProps<P, { accounts: AccountsFromCtx }>) => {
return ( return (
<AccountContext.Consumer> <AccountContext.Consumer>
{(accounts) => ( {(accounts): React.ReactNode => (
// @ts-ignore Something here with the props are going wonky // @ts-ignore Something here with the props are going wonky
<Component <Component
{...props} {...props}
@ -39,10 +41,11 @@ export function withAccounts<P extends { accounts: AccountsFromCtx }> (Component
} }
export function withOnAction<P extends { onAction: OnActionFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { onAction: OnActionFromCtx }>> { export function withOnAction<P extends { onAction: OnActionFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { onAction: OnActionFromCtx }>> {
// eslint-disable-next-line react/display-name,@typescript-eslint/explicit-function-return-type
return (props: SubtractProps<P, { onAction: OnActionFromCtx }>) => { return (props: SubtractProps<P, { onAction: OnActionFromCtx }>) => {
return ( return (
<ActionContext.Consumer> <ActionContext.Consumer>
{(onAction) => ( {(onAction): React.ReactNode => (
// @ts-ignore Something here with the props are going wonky // @ts-ignore Something here with the props are going wonky
<Component <Component
{...props} {...props}
@ -55,10 +58,11 @@ export function withOnAction<P extends { onAction: OnActionFromCtx }> (Component
} }
export function withAuthRequests<P extends { requests: AuthRequestsFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { requests: AuthRequestsFromCtx }>> { export function withAuthRequests<P extends { requests: AuthRequestsFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { requests: AuthRequestsFromCtx }>> {
// eslint-disable-next-line react/display-name,@typescript-eslint/explicit-function-return-type
return (props: SubtractProps<P, { requests: AuthRequestsFromCtx }>) => { return (props: SubtractProps<P, { requests: AuthRequestsFromCtx }>) => {
return ( return (
<AuthorizeContext.Consumer> <AuthorizeContext.Consumer>
{(requests) => ( {(requests): React.ReactNode => (
// @ts-ignore Something here with the props are going wonky // @ts-ignore Something here with the props are going wonky
<Component <Component
{...props} {...props}
@ -71,10 +75,11 @@ export function withAuthRequests<P extends { requests: AuthRequestsFromCtx }> (C
} }
export function withSignRequests<P extends { requests: SignRequestsFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { requests: SignRequestsFromCtx }>> { export function withSignRequests<P extends { requests: SignRequestsFromCtx }> (Component: React.ComponentType<P>): React.ComponentType<SubtractProps<P, { requests: SignRequestsFromCtx }>> {
// eslint-disable-next-line react/display-name,@typescript-eslint/explicit-function-return-type
return (props: SubtractProps<P, { requests: SignRequestsFromCtx }>) => { return (props: SubtractProps<P, { requests: SignRequestsFromCtx }>) => {
return ( return (
<SigningContext.Consumer> <SigningContext.Consumer>
{(requests) => ( {(requests): React.ReactNode => (
// @ts-ignore Something here with the props are going wonky // @ts-ignore Something here with the props are going wonky
<Component <Component
{...props} {...props}

View File

@ -7,7 +7,7 @@ const LABEL_COLOR = '#878786';
const LINK_COLOR = '#3367d6'; const LINK_COLOR = '#3367d6';
const TEXT_COLOR = '#4d4e4f'; const TEXT_COLOR = '#4d4e4f';
const defaults: { [index: string]: any } = { const defaults = {
borderRadius: '0.25rem', borderRadius: '0.25rem',
btnBg: TEXT_COLOR, // LINK_COLOR, btnBg: TEXT_COLOR, // LINK_COLOR,
btnBgDanger: DANGER_COLOR, btnBgDanger: DANGER_COLOR,

View File

@ -8,7 +8,7 @@ import { KeyringJson } from '@polkadot/ui-keyring/types';
export type OmitProps<T, K> = Pick<T, Exclude<keyof T, K>>; export type OmitProps<T, K> = Pick<T, Exclude<keyof T, K>>;
export type SubtractProps<T, K> = OmitProps<T, keyof K>; export type SubtractProps<T, K> = OmitProps<T, keyof K>;
export type AccountsFromCtx = Array<KeyringJson>; export type AccountsFromCtx = KeyringJson[];
export type OnActionFromCtx = (to?: string) => void; export type OnActionFromCtx = (to?: string) => void;
export type AuthRequestsFromCtx = Array<AuthorizeRequest>; export type AuthRequestsFromCtx = AuthorizeRequest[];
export type SignRequestsFromCtx = Array<SigningRequest>; export type SignRequestsFromCtx = SigningRequest[];

View File

@ -6,7 +6,7 @@
const unicode = { const unicode = {
BACK: '\u21E6', // &#8678; BACK: '\u21E6', // &#8678;
DOWN: '\u21E9', // &#8681; DOWN: '\u21E9', // &#8681;
FWD: '\u21E8', // &#8680; FWD: '\u21E8', // &#8680;
UP: '\u21E7' // &#8679; UP: '\u21E7' // &#8679;
}; };

View File

@ -8,7 +8,7 @@ import { HashRouter } from 'react-router-dom';
import { View } from './components'; import { View } from './components';
export default function createView (Entry: React.ComponentType<any>, rootId: string = 'root'): void { export default function createView (Entry: React.ComponentType, rootId: string = 'root'): void {
const rootElement = document.getElementById(rootId); const rootElement = document.getElementById(rootId);
if (!rootElement) { if (!rootElement) {

View File

@ -9,22 +9,22 @@ import { KeypairType } from '@polkadot/util-crypto/types';
import extension from 'extensionizer'; import extension from 'extensionizer';
import { PORT_POPUP } from '@polkadot/extension/defaults'; import { PORT_POPUP } from '@polkadot/extension/defaults';
type Handler = { interface Handler {
resolve: (data: any) => void, // eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (error: Error) => void, resolve: (data: any) => void;
subscriber?: (data: any) => void reject: (error: Error) => void;
}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
subscriber?: (data: any) => void;
}
type Handlers = { type Handlers = Record<string, Handler>;
[index: string]: Handler
};
const port = extension.runtime.connect({ name: PORT_POPUP }); const port = extension.runtime.connect({ name: PORT_POPUP });
const handlers: Handlers = {}; const handlers: Handlers = {};
let idCounter = 0; let idCounter = 0;
// setup a listener for messages, any incoming resolves the promise // setup a listener for messages, any incoming resolves the promise
port.onMessage.addListener((data) => { port.onMessage.addListener((data): void => {
const handler = handlers[data.id]; const handler = handlers[data.id];
if (!handler) { if (!handler) {
@ -45,8 +45,9 @@ port.onMessage.addListener((data) => {
} }
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function sendMessage (message: MessageTypes, request: any = {}, subscriber?: (data: any) => void): Promise<any> { function sendMessage (message: MessageTypes, request: any = {}, subscriber?: (data: any) => void): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject): void => {
const id = `${Date.now()}.${++idCounter}`; const id = `${Date.now()}.${++idCounter}`;
handlers[id] = { resolve, reject, subscriber }; handlers[id] = { resolve, reject, subscriber };
@ -63,11 +64,11 @@ export async function forgetAccount (address: string): Promise<boolean> {
return sendMessage('accounts.forget', { address }); return sendMessage('accounts.forget', { address });
} }
export async function getAccounts (): Promise<Array<KeyringJson>> { export async function getAccounts (): Promise<KeyringJson[]> {
return sendMessage('accounts.list'); return sendMessage('accounts.list');
} }
export async function getAuthRequests (): Promise<Array<AuthorizeRequest>> { export async function getAuthRequests (): Promise<AuthorizeRequest[]> {
return sendMessage('authorize.requests'); return sendMessage('authorize.requests');
} }
@ -79,7 +80,7 @@ export async function approveAuthRequest (id: string): Promise<boolean> {
return sendMessage('authorize.approve', { id }); return sendMessage('authorize.approve', { id });
} }
export async function getSignRequests (): Promise<Array<SigningRequest>> { export async function getSignRequests (): Promise<SigningRequest[]> {
return sendMessage('signing.requests'); return sendMessage('signing.requests');
} }
@ -95,22 +96,22 @@ export async function createAccount (name: string, password: string, suri: strin
return sendMessage('accounts.create', { name, password, suri, type }); return sendMessage('accounts.create', { name, password, suri, type });
} }
export async function createSeed (length?: number, type?: KeypairType): Promise<{ address: string, seed: string }> { export async function createSeed (length?: number, type?: KeypairType): Promise<{ address: string; seed: string }> {
return sendMessage('seed.create', { length, type }); return sendMessage('seed.create', { length, type });
} }
export async function subscribeAccounts (cb: (accounts: Array<KeyringJson>) => void): Promise<boolean> { export async function subscribeAccounts (cb: (accounts: KeyringJson[]) => void): Promise<boolean> {
return sendMessage('accounts.subscribe', {}, cb); return sendMessage('accounts.subscribe', {}, cb);
} }
export async function subscribeAuthorize (cb: (accounts: Array<AuthorizeRequest>) => void): Promise<boolean> { export async function subscribeAuthorize (cb: (accounts: AuthorizeRequest[]) => void): Promise<boolean> {
return sendMessage('authorize.subscribe', {}, cb); return sendMessage('authorize.subscribe', {}, cb);
} }
export async function subscribeSigning (cb: (accounts: Array<SigningRequest>) => void): Promise<boolean> { export async function subscribeSigning (cb: (accounts: SigningRequest[]) => void): Promise<boolean> {
return sendMessage('signing.subscribe', {}, cb); return sendMessage('signing.subscribe', {}, cb);
} }
export async function validateSeed (seed: string, type?: KeypairType): Promise<{ address: string, seed: string }> { export async function validateSeed (seed: string, type?: KeypairType): Promise<{ address: string; seed: string }> {
return sendMessage('seed.validate', { seed, type }); return sendMessage('seed.validate', { seed, type });
} }

View File

@ -7,12 +7,12 @@ import styled from 'styled-components';
import { Link, unicode } from '../components'; import { Link, unicode } from '../components';
type Props = { interface Props {
className?: string, className?: string;
to?: string to?: string;
}; }
function Back ({ className, to = '/' }: Props) { function Back ({ className, to = '/' }: Props): React.ReactElement<Props> {
return ( return (
<div className={className}> <div className={className}>
<Link to={to}>{unicode.BACK} Back</Link> <Link to={to}>{unicode.BACK} Back</Link>

View File

@ -8,28 +8,28 @@ import React, { useEffect, useState } from 'react';
import { Input, withAccounts } from '../components'; import { Input, withAccounts } from '../components';
type Props = { interface Props {
accounts: AccountsFromCtx, accounts: AccountsFromCtx;
address?: string, address?: string;
className?: string, className?: string;
defaultValue?: string | null, defaultValue?: string | null;
isFocussed?: boolean, isFocussed?: boolean;
label?: string | null, label?: string | null;
onBlur?: () => void, onBlur?: () => void;
onChange: (name: string | null) => void onChange: (name: string | null) => void;
}; }
const MIN_LENGTH = 3; const MIN_LENGTH = 3;
function Name ({ accounts, address, className, defaultValue, isFocussed, label = 'a descriptive name for this account', onBlur, onChange }: Props) { function Name ({ accounts, address, className, defaultValue, isFocussed, label = 'a descriptive name for this account', onBlur, onChange }: Props): React.ReactElement<Props> {
const [name, setName] = useState(''); const [name, setName] = useState('');
const account = accounts.find((account) => account.address === address); const account = accounts.find((account): boolean => account.address === address);
const startValue = (account && account.meta.name) || defaultValue; const startValue = (account && account.meta.name) || defaultValue;
const isError = !name && startValue const isError = !name && startValue
? false ? false
: (name.length < MIN_LENGTH); : (name.length < MIN_LENGTH);
useEffect(() => { useEffect((): void => {
onChange( onChange(
name && (name.length >= MIN_LENGTH) name && (name.length >= MIN_LENGTH)
? name ? name

View File

@ -6,18 +6,18 @@ import React, { useEffect, useState } from 'react';
import { Input } from '../components'; import { Input } from '../components';
type Props = { interface Props {
isFocussed?: boolean, isFocussed?: boolean;
onChange: (password: string | null) => void onChange: (password: string | null) => void;
}; }
const MIN_LENGTH = 6; const MIN_LENGTH = 6;
export default function Password ({ isFocussed, onChange }: Props) { export default function Password ({ isFocussed, onChange }: Props): React.ReactElement<Props> {
const [pass1, setPass1] = useState(''); const [pass1, setPass1] = useState('');
const [pass2, setPass2] = useState(''); const [pass2, setPass2] = useState('');
useEffect(() => { useEffect((): void => {
onChange( onChange(
(pass1 && pass2 && (pass1.length >= MIN_LENGTH) && (pass1 === pass2)) (pass1 && pass2 && (pass1.length >= MIN_LENGTH) && (pass1 === pass2))
? pass1 ? pass1

View File

@ -4,7 +4,7 @@
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { KeyringJson } from '@polkadot/ui-keyring/types'; import { KeyringJson } from '@polkadot/ui-keyring/types';
import { AuthorizeRequest, MessageTypes, MessageAccountCreate, MessageAccountEdit, MessageAuthorizeApprove, MessageAuthorizeReject, MessageExtrinsicSignApprove, MessageExtrinsicSignCancel, MessageSeedCreate, MessageSeedCreate$Response, MessageSeedValidate, MessageSeedValidate$Response, MessageAccountForget, SigningRequest } from '../types'; import { AuthorizeRequest, MessageTypes, MessageAccountCreate, MessageAccountEdit, MessageAuthorizeApprove, MessageAuthorizeReject, MessageExtrinsicSignApprove, MessageExtrinsicSignCancel, MessageSeedCreate, MessageSeedCreateResponse, MessageSeedValidate, MessageSeedValidateResponse, MessageAccountForget, SigningRequest } from '../types';
import keyring from '@polkadot/ui-keyring'; import keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts'; import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
@ -18,14 +18,14 @@ import { createSubscription, unsubscribe } from './subscriptions';
const SEED_DEFAULT_LENGTH = 12; const SEED_DEFAULT_LENGTH = 12;
const SEED_LENGTHS = [12, 24]; const SEED_LENGTHS = [12, 24];
function transformAccounts (accounts: SubjectInfo): Array<KeyringJson> { function transformAccounts (accounts: SubjectInfo): KeyringJson[] {
return Object.values(accounts).map(({ json }) => json); return Object.values(accounts).map(({ json }): KeyringJson => json);
} }
export default class Extension { export default class Extension {
state: State; private state: State;
constructor (state: State) { public constructor (state: State) {
this.state = state; this.state = state;
} }
@ -51,18 +51,18 @@ export default class Extension {
return true; return true;
} }
private accountsList (): Array<KeyringJson> { private accountsList (): KeyringJson[] {
return transformAccounts(accountsObservable.subject.getValue()); return transformAccounts(accountsObservable.subject.getValue());
} }
// FIXME This looks very much like what we have in Tabs // FIXME This looks very much like what we have in Tabs
private accountsSubscribe (id: string, port: chrome.runtime.Port): boolean { private accountsSubscribe (id: string, port: chrome.runtime.Port): boolean {
const cb = createSubscription(id, port); const cb = createSubscription(id, port);
const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo) => const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo): void =>
cb(transformAccounts(accounts)) cb(transformAccounts(accounts))
); );
port.onDisconnect.addListener(() => { port.onDisconnect.addListener((): void => {
unsubscribe(id); unsubscribe(id);
subscription.unsubscribe(); subscription.unsubscribe();
}); });
@ -94,18 +94,18 @@ export default class Extension {
return true; return true;
} }
private authorizeRequests (): Array<AuthorizeRequest> { private authorizeRequests (): AuthorizeRequest[] {
return this.state.allAuthRequests; return this.state.allAuthRequests;
} }
// FIXME This looks very much like what we have in accounts // FIXME This looks very much like what we have in accounts
private authorizeSubscribe (id: string, port: chrome.runtime.Port): boolean { private authorizeSubscribe (id: string, port: chrome.runtime.Port): boolean {
const cb = createSubscription(id, port); const cb = createSubscription(id, port);
const subscription = this.state.authSubject.subscribe((requests: Array<AuthorizeRequest>) => const subscription = this.state.authSubject.subscribe((requests: AuthorizeRequest[]): void =>
cb(requests) cb(requests)
); );
port.onDisconnect.addListener(() => { port.onDisconnect.addListener((): void => {
unsubscribe(id); unsubscribe(id);
subscription.unsubscribe(); subscription.unsubscribe();
}); });
@ -113,7 +113,7 @@ export default class Extension {
return true; return true;
} }
private seedCreate ({ length = SEED_DEFAULT_LENGTH, type }: MessageSeedCreate): MessageSeedCreate$Response { private seedCreate ({ length = SEED_DEFAULT_LENGTH, type }: MessageSeedCreate): MessageSeedCreateResponse {
const seed = mnemonicGenerate(length); const seed = mnemonicGenerate(length);
return { return {
@ -122,7 +122,7 @@ export default class Extension {
}; };
} }
private seedValidate ({ seed, type }: MessageSeedValidate): MessageSeedValidate$Response { private seedValidate ({ seed, type }: MessageSeedValidate): MessageSeedValidateResponse {
assert(SEED_LENGTHS.includes(seed.split(' ').length), `Mnemonic needs to contain ${SEED_LENGTHS.join(', ')} words`); assert(SEED_LENGTHS.includes(seed.split(' ').length), `Mnemonic needs to contain ${SEED_LENGTHS.join(', ')} words`);
assert(mnemonicValidate(seed), 'Not a valid mnemonic seed'); assert(mnemonicValidate(seed), 'Not a valid mnemonic seed');
@ -175,18 +175,18 @@ export default class Extension {
return true; return true;
} }
private signingRequests (): Array<SigningRequest> { private signingRequests (): SigningRequest[] {
return this.state.allSignRequests; return this.state.allSignRequests;
} }
// FIXME This looks very much like what we have in authorization // FIXME This looks very much like what we have in authorization
private signingSubscribe (id: string, port: chrome.runtime.Port): boolean { private signingSubscribe (id: string, port: chrome.runtime.Port): boolean {
const cb = createSubscription(id, port); const cb = createSubscription(id, port);
const subscription = this.state.signSubject.subscribe((requests: Array<SigningRequest>) => const subscription = this.state.signSubject.subscribe((requests: SigningRequest[]): void =>
cb(requests) cb(requests)
); );
port.onDisconnect.addListener(() => { port.onDisconnect.addListener((): void => {
unsubscribe(id); unsubscribe(id);
subscription.unsubscribe(); subscription.unsubscribe();
}); });
@ -194,7 +194,8 @@ export default class Extension {
return true; return true;
} }
async handle (id: string, type: MessageTypes, request: any, port: chrome.runtime.Port): Promise<any> { // eslint-disable-next-line @typescript-eslint/no-explicit-any
public async handle (id: string, type: MessageTypes, request: any, port: chrome.runtime.Port): Promise<any> {
switch (type) { switch (type) {
case 'authorize.approve': case 'authorize.approve':
return this.authorizeApprove(request); return this.authorizeApprove(request);

View File

@ -2,38 +2,36 @@
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { AuthorizeRequest, MessageAuthorize, MessageExtrinsicSign, MessageExtrinsicSign$Response, SigningRequest } from '../types'; import { AuthorizeRequest, MessageAuthorize, MessageExtrinsicSign, MessageExtrinsicSignResponse, SigningRequest } from '../types';
import extension from 'extensionizer'; import extension from 'extensionizer';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { assert } from '@polkadot/util'; import { assert } from '@polkadot/util';
type AuthRequest = { interface AuthRequest {
id: string, id: string;
idStr: string, idStr: string;
request: MessageAuthorize, request: MessageAuthorize;
resolve: (result: boolean) => void, resolve: (result: boolean) => void;
reject: (error: Error) => void, reject: (error: Error) => void;
url: string url: string;
}; }
type AuthUrls = { type AuthUrls = Record<string, {
[index: string]: { count: number;
count: number, id: string;
id: string, isAllowed: boolean;
isAllowed: boolean, origin: string;
origin: string, url: string;
url: string }>;
}
};
type SignRequest = { interface SignRequest {
id: string, id: string;
request: MessageExtrinsicSign, request: MessageExtrinsicSign;
resolve: (result: MessageExtrinsicSign$Response) => void, resolve: (result: MessageExtrinsicSignResponse) => void;
reject: (error: Error) => void, reject: (error: Error) => void;
url: string url: string;
}; }
let idCounter = 0; let idCounter = 0;
@ -42,44 +40,48 @@ function getId (): string {
} }
export default class State { export default class State {
// at the moment, we are keeping the list in memory - this should be persisted
private _authUrls: AuthUrls = {}; private _authUrls: AuthUrls = {};
private _authRequests: { [index: string]: AuthRequest } = {};
private _signRequests: { [index: string]: SignRequest } = {};
private _windows: Array<number> = [];
readonly authSubject: BehaviorSubject<Array<AuthorizeRequest>> = new BehaviorSubject([] as Array<AuthorizeRequest>);
readonly signSubject: BehaviorSubject<Array<SigningRequest>> = new BehaviorSubject([] as Array<SigningRequest>);
get hasAuthRequests (): boolean { private _authRequests: Record<string, AuthRequest> = {};
private _signRequests: Record<string, SignRequest> = {};
private _windows: number[] = [];
public readonly authSubject: BehaviorSubject<AuthorizeRequest[]> = new BehaviorSubject([] as AuthorizeRequest[]);
public readonly signSubject: BehaviorSubject<SigningRequest[]> = new BehaviorSubject([] as SigningRequest[]);
public get hasAuthRequests (): boolean {
return this.numAuthRequests === 0; return this.numAuthRequests === 0;
} }
get hasSignRequests (): boolean { public get hasSignRequests (): boolean {
return this.numSignRequests === 0; return this.numSignRequests === 0;
} }
get numAuthRequests (): number { public get numAuthRequests (): number {
return Object.keys(this._authRequests).length; return Object.keys(this._authRequests).length;
} }
get numSignRequests (): number { public get numSignRequests (): number {
return Object.keys(this._signRequests).length; return Object.keys(this._signRequests).length;
} }
get allAuthRequests (): Array<AuthorizeRequest> { public get allAuthRequests (): AuthorizeRequest[] {
return Object return Object
.values(this._authRequests) .values(this._authRequests)
.map(({ id, request, url }) => [id, request, url]); .map(({ id, request, url }): AuthorizeRequest => [id, request, url]);
} }
get allSignRequests (): Array<SigningRequest> { public get allSignRequests (): SigningRequest[] {
return Object return Object
.values(this._signRequests) .values(this._signRequests)
.map(({ id, request, url }) => [id, request, url]); .map(({ id, request, url }): SigningRequest => [id, request, url]);
} }
private popupClose (): void { private popupClose (): void {
this._windows.map((id: number) => this._windows.forEach((id: number): void =>
extension.windows.remove(id) extension.windows.remove(id)
); );
this._windows = []; this._windows = [];
@ -94,14 +96,14 @@ export default class State {
type: 'popup', type: 'popup',
url: extension.extension.getURL('popup.html'), url: extension.extension.getURL('popup.html'),
width: 480 width: 480
}, (window?: chrome.windows.Window) => { }, (window?: chrome.windows.Window): void => {
if (window) { if (window) {
this._windows.push(window.id); this._windows.push(window.id);
} }
}); });
} }
private authComplete = (id: string, fn: Function) => { private authComplete = (id: string, fn: Function): (result: boolean | Error) => void => {
return (result: boolean | Error): void => { return (result: boolean | Error): void => {
const isAllowed = result === true; const isAllowed = result === true;
const { idStr, request: { origin }, url } = this._authRequests[id]; const { idStr, request: { origin }, url } = this._authRequests[id];
@ -121,8 +123,8 @@ export default class State {
}; };
} }
private signComplete = (id: string, fn: Function) => { private signComplete = (id: string, fn: Function): (result: MessageExtrinsicSignResponse | Error) => void => {
return (result: MessageExtrinsicSign$Response | Error): void => { return (result: MessageExtrinsicSignResponse | Error): void => {
delete this._signRequests[id]; delete this._signRequests[id];
this.updateIconSign(true); this.updateIconSign(true);
@ -164,7 +166,7 @@ export default class State {
this.updateIcon(shouldClose); this.updateIcon(shouldClose);
} }
async authorizeUrl (url: string, request: MessageAuthorize): Promise<boolean> { public async authorizeUrl (url: string, request: MessageAuthorize): Promise<boolean> {
const idStr = this.stripUrl(url); const idStr = this.stripUrl(url);
if (this._authUrls[idStr]) { if (this._authUrls[idStr]) {
@ -173,7 +175,7 @@ export default class State {
return true; return true;
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject): void => {
const id = getId(); const id = getId();
this._authRequests[id] = { this._authRequests[id] = {
@ -190,7 +192,7 @@ export default class State {
}); });
} }
isUrlAuthorized (url: string): boolean { public isUrlAuthorized (url: string): boolean {
const entry = this._authUrls[this.stripUrl(url)]; const entry = this._authUrls[this.stripUrl(url)];
assert(entry, `The source ${url} has not been enabled yet`); assert(entry, `The source ${url} has not been enabled yet`);
@ -199,18 +201,18 @@ export default class State {
return true; return true;
} }
getAuthRequest (id: string): AuthRequest { public getAuthRequest (id: string): AuthRequest {
return this._authRequests[id]; return this._authRequests[id];
} }
getSignRequest (id: string): SignRequest { public getSignRequest (id: string): SignRequest {
return this._signRequests[id]; return this._signRequests[id];
} }
signQueue (url: string, request: MessageExtrinsicSign): Promise<MessageExtrinsicSign$Response> { public signQueue (url: string, request: MessageExtrinsicSign): Promise<MessageExtrinsicSignResponse> {
const id = getId(); const id = getId();
return new Promise((resolve, reject) => { return new Promise((resolve, reject): void => {
this._signRequests[id] = { this._signRequests[id] = {
id, id,
request, request,

View File

@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { MessageTypes, MessageAuthorize, MessageExtrinsicSign, MessageExtrinsicSign$Response } from '../types'; import { MessageTypes, MessageAuthorize, MessageExtrinsicSign, MessageExtrinsicSignResponse } from '../types';
import keyring from '@polkadot/ui-keyring'; import keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts'; import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
@ -12,25 +12,30 @@ import { assert } from '@polkadot/util';
import State from './State'; import State from './State';
import { createSubscription, unsubscribe } from './subscriptions'; import { createSubscription, unsubscribe } from './subscriptions';
type Accounts = Array<{ address: string, name?: string }>; interface Account {
address: string;
name?: string;
}
type Accounts = Account[];
function transformAccounts (accounts: SubjectInfo): Accounts { function transformAccounts (accounts: SubjectInfo): Accounts {
return Object.values(accounts).map(({ json: { address, meta: { name } } }) => ({ return Object.values(accounts).map(({ json: { address, meta: { name } } }): Account => ({
address, name address, name
})); }));
} }
export default class Tabs { export default class Tabs {
state: State; private state: State;
constructor (state: State) { public constructor (state: State) {
this.state = state; this.state = state;
} }
private authorize (url: string, request: MessageAuthorize) { private authorize (url: string, request: MessageAuthorize): Promise<boolean> {
return this.state.authorizeUrl(url, request); return this.state.authorizeUrl(url, request);
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private accountsList (url: string): Accounts { private accountsList (url: string): Accounts {
return transformAccounts(accountsObservable.subject.getValue()); return transformAccounts(accountsObservable.subject.getValue());
} }
@ -38,11 +43,11 @@ export default class Tabs {
// FIXME This looks very much like what we have in Extension // FIXME This looks very much like what we have in Extension
private accountsSubscribe (url: string, id: string, port: chrome.runtime.Port): boolean { private accountsSubscribe (url: string, id: string, port: chrome.runtime.Port): boolean {
const cb = createSubscription(id, port); const cb = createSubscription(id, port);
const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo) => const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo): void =>
cb(transformAccounts(accounts)) cb(transformAccounts(accounts))
); );
port.onDisconnect.addListener(() => { port.onDisconnect.addListener((): void => {
unsubscribe(id); unsubscribe(id);
subscription.unsubscribe(); subscription.unsubscribe();
}); });
@ -50,7 +55,7 @@ export default class Tabs {
return true; return true;
} }
private extrinsicSign (url: string, request: MessageExtrinsicSign): Promise<MessageExtrinsicSign$Response> { private extrinsicSign (url: string, request: MessageExtrinsicSign): Promise<MessageExtrinsicSignResponse> {
const { address } = request; const { address } = request;
const pair = keyring.getPair(address); const pair = keyring.getPair(address);
@ -59,7 +64,8 @@ export default class Tabs {
return this.state.signQueue(url, request); return this.state.signQueue(url, request);
} }
async handle (id: string, type: MessageTypes, request: any, url: string, port: chrome.runtime.Port): Promise<any> { // eslint-disable-next-line @typescript-eslint/no-explicit-any
public async handle (id: string, type: MessageTypes, request: any, url: string, port: chrome.runtime.Port): Promise<any> {
switch (type) { switch (type) {
case 'authorize.tab': case 'authorize.tab':
return this.authorize(url, request); return this.authorize(url, request);

View File

@ -28,12 +28,12 @@ export default function handler ({ id, message, request }: MessageRequest, port:
: tabs.handle(id, message, request, from, port); : tabs.handle(id, message, request, from, port);
promise promise
.then((response) => { .then((response): void => {
console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`); console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`);
port.postMessage({ id, response }); port.postMessage({ id, response });
}) })
.catch((error) => { .catch((error): void => {
console.log(`[err] ${source}:: ${error.message}`); console.log(`[err] ${source}:: ${error.message}`);
port.postMessage({ id, error: error.message }); port.postMessage({ id, error: error.message });

View File

@ -2,17 +2,17 @@
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
type Subscriptions = { type Subscriptions = Record<string, chrome.runtime.Port>;
[index: string]: chrome.runtime.Port
};
const subscriptions: Subscriptions = {}; const subscriptions: Subscriptions = {};
// return a subscription callback, that will send the data to the caller via the port // return a subscription callback, that will send the data to the caller via the port
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function createSubscription (id: string, port: chrome.runtime.Port): (data: any) => void { export function createSubscription (id: string, port: chrome.runtime.Port): (data: any) => void {
subscriptions[id] = port; subscriptions[id] = port;
return (subscription: any) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any
return (subscription: any): void => {
if (subscriptions[id]) { if (subscriptions[id]) {
port.postMessage({ id, subscription }); port.postMessage({ id, subscription });
} }

View File

@ -17,18 +17,18 @@ import handlers from './handlers';
extension.browserAction.setBadgeBackgroundColor({ color: '#d90000' }); extension.browserAction.setBadgeBackgroundColor({ color: '#d90000' });
// listen to all messages and handle appropriately // listen to all messages and handle appropriately
extension.runtime.onConnect.addListener((port) => { extension.runtime.onConnect.addListener((port): void => {
// shouldn't happen, however... only listen to what we know about // shouldn't happen, however... only listen to what we know about
assert([PORT_CONTENT, PORT_POPUP].includes(port.name), `Unknown connection from ${port.name}`); assert([PORT_CONTENT, PORT_POPUP].includes(port.name), `Unknown connection from ${port.name}`);
// message and disconnect handlers // message and disconnect handlers
port.onMessage.addListener((data) => handlers(data, port)); port.onMessage.addListener((data): void => handlers(data, port));
port.onDisconnect.addListener(() => console.log(`Disconnected from ${port.name}`)); port.onDisconnect.addListener((): void => console.log(`Disconnected from ${port.name}`));
}); });
// initial setup // initial setup
cryptoWaitReady() cryptoWaitReady()
.then(() => { .then((): void => {
console.log('crypto initialized'); console.log('crypto initialized');
// load all the keyring data // load all the keyring data
@ -36,6 +36,6 @@ cryptoWaitReady()
console.log('initialization completed'); console.log('initialization completed');
}) })
.catch((error) => { .catch((error): void => {
console.error('initialization failed', error); console.error('initialization failed', error);
}); });

View File

@ -10,55 +10,58 @@ export type AuthorizeRequest = [string, MessageAuthorize, string];
export type SigningRequest = [string, MessageExtrinsicSign, string]; export type SigningRequest = [string, MessageExtrinsicSign, string];
export type MessageAuthorize = { export interface MessageAuthorize {
origin: string origin: string;
}; }
export type MessageAuthorizeApprove = { export interface MessageAuthorizeApprove {
id: string; id: string;
}; }
export type MessageAuthorizeReject = { export interface MessageAuthorizeReject {
id: string; id: string;
}; }
export type MessageRequest = { export interface MessageRequest {
id: string, id: string;
message: MessageTypes, message: MessageTypes;
request: any // eslint-disable-next-line @typescript-eslint/no-explicit-any
}; request: any;
}
export type MessageResponse = { export interface MessageResponse {
error?: string, error?: string;
id: string, id: string;
response?: any, // eslint-disable-next-line @typescript-eslint/no-explicit-any
subscription?: any response?: any;
}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
subscription?: any;
}
export type MessageAccountCreate = { export interface MessageAccountCreate {
name: string, name: string;
password: string, password: string;
suri: string, suri: string;
type?: KeypairType type?: KeypairType;
}; }
export type MessageAccountEdit= { export interface MessageAccountEdit {
address: string, address: string;
name: string name: string;
}; }
export type MessageAccountForget = { export interface MessageAccountForget {
address: string address: string;
}; }
export type MessageExtrinsicSignApprove = { export interface MessageExtrinsicSignApprove {
id: string, id: string;
password: string password: string;
}; }
export type MessageExtrinsicSignCancel = { export interface MessageExtrinsicSignCancel {
id: string id: string;
}; }
export interface MessageExtrinsicSign { export interface MessageExtrinsicSign {
address: string; address: string;
@ -70,27 +73,27 @@ export interface MessageExtrinsicSign {
nonce: string; nonce: string;
} }
export type MessageExtrinsicSign$Response = { export interface MessageExtrinsicSignResponse {
id: string, id: string;
signature: string signature: string;
}; }
export type MessageSeedCreate$Response = { export interface MessageSeedCreateResponse {
address: string, address: string;
seed: string seed: string;
}; }
export type MessageSeedCreate = { export interface MessageSeedCreate {
length?: 12 | 24, length?: 12 | 24;
type?: KeypairType type?: KeypairType;
}; }
export type MessageSeedValidate = { export interface MessageSeedValidate {
seed: string, seed: string;
type?: KeypairType type?: KeypairType;
}; }
export type MessageSeedValidate$Response = { export interface MessageSeedValidateResponse {
address: string, address: string;
seed: string seed: string;
}; }

View File

@ -8,12 +8,12 @@ import { Metadata } from '@polkadot/types';
// curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933 // curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933
import alexander from './alexander'; import alexander from './alexander';
type Chain = { interface Chain {
meta?: Metadata, meta?: Metadata;
name: string name: string;
}; }
const chains: { [genesisHash: string]: Chain } = { const chains: Record<string, Chain> = {
'0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b': { '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b': {
name: 'Alexander', name: 'Alexander',
meta: new Metadata(alexander.meta) meta: new Metadata(alexander.meta)

View File

@ -10,12 +10,12 @@ import { PORT_CONTENT } from './defaults';
const port = extension.runtime.connect({ name: PORT_CONTENT }); const port = extension.runtime.connect({ name: PORT_CONTENT });
// send any messages from the extension back to the page // send any messages from the extension back to the page
port.onMessage.addListener((data) => { port.onMessage.addListener((data): void => {
window.postMessage({ ...data, origin: 'content' }, '*'); window.postMessage({ ...data, origin: 'content' }, '*');
}); });
// all messages from the page, pass them to the extension // all messages from the page, pass them to the extension
window.addEventListener('message', ({ data, source }) => { window.addEventListener('message', ({ data, source }): void => {
// only allow messages from our window, by the inject // only allow messages from our window, by the inject
if (source !== window || data.origin !== 'page') { if (source !== window || data.origin !== 'page') {
return; return;

View File

@ -8,19 +8,20 @@ import { SendRequest } from './types';
let sendRequest: SendRequest; let sendRequest: SendRequest;
export default class Accounts implements InjectedAccounts { export default class Accounts implements InjectedAccounts {
constructor (_sendRequest: SendRequest) { public constructor (_sendRequest: SendRequest) {
sendRequest = _sendRequest; sendRequest = _sendRequest;
} }
get (): Promise<Array<InjectedAccount>> { public get (): Promise<InjectedAccount[]> {
return sendRequest('accounts.list'); return sendRequest('accounts.list');
} }
subscribe (cb: (accounts: Array<InjectedAccount>) => any): Unsubcall { // eslint-disable-next-line @typescript-eslint/no-explicit-any
public subscribe (cb: (accounts: InjectedAccount[]) => any): Unsubcall {
sendRequest('accounts.subscribe', null, cb) sendRequest('accounts.subscribe', null, cb)
.catch(console.error); .catch(console.error);
return () => { return (): void => {
// FIXME we need the ability to unsubscribe // FIXME we need the ability to unsubscribe
}; };
} }

View File

@ -9,10 +9,11 @@ import Accounts from './Accounts';
import Signer from './Signer'; import Signer from './Signer';
export default class Injected implements InjectedInjected { export default class Injected implements InjectedInjected {
readonly accounts: Accounts; public readonly accounts: Accounts;
readonly signer: Signer;
constructor (sendRequest: SendRequest) { public readonly signer: Signer;
public constructor (sendRequest: SendRequest) {
this.accounts = new Accounts(sendRequest); this.accounts = new Accounts(sendRequest);
this.signer = new Signer(sendRequest); this.signer = new Signer(sendRequest);
} }

View File

@ -34,6 +34,7 @@ export default class Signer implements InjectedSigner {
return id; return id;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public update (id: number, status: Hash | SubmittableResult): void { public update (id: number, status: Hash | SubmittableResult): void {
// something // something
} }

View File

@ -10,19 +10,19 @@ import Injected from './Injected';
// when sending a message from the injector to the extension, we // when sending a message from the injector to the extension, we
// - create an event - this we send to the loader // - create an event - this we send to the loader
// - the loader takes this event and uses port.postMessage to background // - the loader takes this event and uses port.postMessage to background
// - on resposnse, the loader creates a reponse event // - on response, the loader creates a reponse event
// - this injector, listens on the events, maps it to the original // - this injector, listens on the events, maps it to the original
// - resolves/rejects the promise with the result (or sub data) // - resolves/rejects the promise with the result (or sub data)
type Handler = { interface Handler {
resolve: (data: any) => void, // eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (error: Error) => void, resolve: (data: any) => void;
subscriber?: (data: any) => void reject: (error: Error) => void;
}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
subscriber?: (data: any) => void;
}
type Handlers = { type Handlers = Record<string, Handler>;
[index: string]: Handler
};
// small helper with the typescript types, just cast window // small helper with the typescript types, just cast window
const windowInject = window as InjectedWindow; const windowInject = window as InjectedWindow;
@ -31,8 +31,9 @@ let idCounter = 0;
// a generic message sender that creates an event, returning a promise that will // a generic message sender that creates an event, returning a promise that will
// resolve once the event is resolved (by the response listener just below this) // resolve once the event is resolved (by the response listener just below this)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function sendMessage (message: MessageTypes, request: any = null, subscriber?: (data: any) => void): Promise<any> { function sendMessage (message: MessageTypes, request: any = null, subscriber?: (data: any) => void): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject): void => {
const id = `${Date.now()}.${++idCounter}`; const id = `${Date.now()}.${++idCounter}`;
handlers[id] = { resolve, reject, subscriber }; handlers[id] = { resolve, reject, subscriber };
@ -49,7 +50,7 @@ async function enable (origin: string): Promise<Injected> {
} }
// setup a response listener (events created by the loader for extension responses) // setup a response listener (events created by the loader for extension responses)
window.addEventListener('message', ({ data, source }) => { window.addEventListener('message', ({ data, source }): void => {
// only allow messages from our window, by the loader // only allow messages from our window, by the loader
if (source !== window || data.origin !== 'content') { if (source !== window || data.origin !== 'content') {
return; return;

View File

@ -5,5 +5,6 @@
import { MessageTypes } from '../background/types'; import { MessageTypes } from '../background/types';
export interface SendRequest { export interface SendRequest {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(message: MessageTypes, request?: any, subscriber?: (data: any) => any): Promise<any>; (message: MessageTypes, request?: any, subscriber?: (data: any) => any): Promise<any>;
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Copyright 2019 @polkadot/extension authors & contributors // Copyright 2019 @polkadot/extension authors & contributors
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
@ -68,6 +69,7 @@ function createWebpack ({ alias = {}, context }) {
] ]
}, },
node: { node: {
// eslint-disable-next-line @typescript-eslint/camelcase
child_process: 'empty', child_process: 'empty',
dgram: 'empty', dgram: 'empty',
fs: 'empty', fs: 'empty',
@ -83,7 +85,7 @@ function createWebpack ({ alias = {}, context }) {
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify(ENV), NODE_ENV: JSON.stringify(ENV),
PKG_NAME: JSON.stringify(pkgJson.name), PKG_NAME: JSON.stringify(pkgJson.name),
PKG_VERSION: JSON.stringify(pkgJson.version), PKG_VERSION: JSON.stringify(pkgJson.version)
} }
}), }),
new CopyPlugin([{ from: 'public' }]), new CopyPlugin([{ from: 'public' }]),