on master help sign in
Make /views/Login/Ledger functional/stateless
commit06a779f6fa993ecbec7af1e152fe1972b3248311 authorFang <git@fang.io> dateTue 25 Jun 2019, 00:35 (7 years ago) parentd2c2674
1 file changed+149−181
src/views/Login/Ledger.js+149−181
src/views/Login/Ledger.js+149−181
@@ -1,69 +1,55 @@
11import * as bip32 from 'bip32';
2−import React from 'react';
2+import React, { useState } from 'react';
33import Maybe from 'folktale/maybe';
4−import { Button } from '../../components/old/Base';
5−import { Row, Col, H1, P, H2 } from '../../components/old/Base';
4+import Transport from '@ledgerhq/hw-transport-u2f';
5+import Eth from '@ledgerhq/hw-app-eth';
6+import * as secp256k1 from 'secp256k1';
7+
8+import View from '../../components/View';
9+import { ForwardButton } from '../../components/Buttons';
610import {
11+ H1,
12+ P,
13+ H2,
714 Input,
815 InnerLabel,
916 InnerLabelDropdown,
1017} from '../../components/old/Base';
11−import Transport from '@ledgerhq/hw-transport-u2f';
12−import Eth from '@ledgerhq/hw-app-eth';
13−import * as secp256k1 from 'secp256k1';
1418
1519import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../../lib/ledger';
16−import { compose } from '../../lib/lib';
17−import { withWallet } from '../../store/wallet';
20+import { useWallet } from '../../store/wallet';
1821
1922const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
2023
2124const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
2225
23−class Ledger extends React.Component {
24− constructor(props) {
25− super(props);
26−
27− this.state = {
28− basePath: LEDGER_LIVE_PATH,
29− hdpath: LEDGER_LIVE_PATH.replace(/x/g, 0),
30− account: 0,
31− };
32−
33− this.pollDevice = this.pollDevice.bind(this);
34− this.handleHdPathInput = this.handleHdPathInput.bind(this);
35− this.handlePathSelection = this.handlePathSelection.bind(this);
36− this.handleAccountSelection = this.handleAccountSelection.bind(this);
37− this.updateHdPath = this.updateHdPath.bind(this);
38− }
39−
40− handlePathSelection(basePath) {
41− this.updateHdPath(basePath, this.state.account);
42− }
26+export default function Ledger({ loginCompleted }) {
27+ const { wallet, setWallet, setWalletHdPath } = useWallet();
4328
44− handleAccountSelection(account) {
45− this.updateHdPath(this.state.basePath, account);
46− }
29+ const [basePath, setBasePath] = useState(LEDGER_LIVE_PATH);
30+ const [account, setAccount] = useState(0);
31+ const [hdPath, setHdPath] = useState(LEDGER_LIVE_PATH.replace(/x/g, 0));
4732
48− updateHdPath(basePath, account) {
49− let hdpath = this.state.hdpath;
33+ const updateHdPath = (basePath, account) => {
5034 if (basePath !== 'custom') {
51− hdpath = basePath.replace(/x/g, account);
35+ setHdPath(basePath.replace(/x/g, account));
5236 }
53− this.setState({ basePath, account, hdpath });
54− }
37+ };
5538
56− handleHdPathInput(hdpath) {
57− this.setState({ hdpath });
58− }
39+ const handlePathSelection = basePath => {
40+ setBasePath(basePath);
41+ updateHdPath(basePath, account);
42+ };
5943
60− async pollDevice() {
61− const { setWallet, setWalletHdPath } = this.props;
62− const { hdpath } = this.state;
44+ const handleAccountSelection = account => {
45+ setAccount(account);
46+ updateHdPath(basePath, account);
47+ };
6348
49+ const pollDevice = async () => {
6450 const transport = await Transport.create();
6551 const eth = new Eth(transport);
66− const path = chopHdPrefix(hdpath);
52+ const path = chopHdPrefix(hdPath);
6753
6854 eth.getAddress(path, false, true).then(
6955 info => {
@@ -72,153 +58,135 @@ class Ledger extends React.Component {
7258 const pub = secp256k1.publicKeyConvert(publicKey, true);
7359 const hd = bip32.fromPublicKey(pub, chainCode);
7460 setWallet(Maybe.Just(hd));
75− setWalletHdPath(addHdPrefix(hdpath));
61+ setWalletHdPath(addHdPrefix(hdPath));
7662 },
7763 _ => {
7864 setWallet(Maybe.Nothing());
7965 }
8066 );
67+ };
68+
69+ const pathOptions = [
70+ { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
71+ { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
72+ {
73+ title: 'Custom path',
74+ value: 'custom',
75+ },
76+ ];
77+ const basePathTitle = pathOptions.find(o => o.value === basePath).title;
78+
79+ let accountOptions = [];
80+ for (let i = 0; i < 20; i++) {
81+ accountOptions.push({
82+ title: 'Account #' + (i + 1),
83+ value: i,
84+ });
8185 }
82−
83− render() {
84− const { wallet } = this.props;
85− const { basePath, hdpath, account } = this.state;
86− const { handlePathSelection, handleAccountSelection } = this;
87−
88− const pathOptions = [
89− { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
90− { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
91− {
92− title: 'Custom path',
93− value: 'custom',
94− },
95− ];
96− let basePathTitle = pathOptions.find(o => o.value === basePath).title;
97−
98− let accountOptions = [];
99− for (let i = 0; i < 20; i++) {
100− accountOptions.push({
101− title: 'Account #' + (i + 1),
102− value: i,
103− });
104− }
105− let accountTitle = accountOptions.find(o => o.value === account).title;
106−
107− const basePathSelection = (
86+ const accountTitle = accountOptions.find(o => o.value === account).title;
87+
88+ const basePathSelection = (
89+ <InnerLabelDropdown
90+ className="mt-8"
91+ options={pathOptions}
92+ handleUpdate={handlePathSelection}
93+ title={'Derivation path'}
94+ currentSelectionTitle={basePathTitle}
95+ fullWidth={true}
96+ />
97+ );
98+
99+ const truePathSelection =
100+ basePath === 'custom' ? (
101+ <Input
102+ className="pt-8 mt-4 text-mono"
103+ prop-size="md"
104+ prop-format="innerLabel"
105+ name="hdPath"
106+ value={addHdPrefix(hdPath)}
107+ autocomplete="off"
108+ onChange={setHdPath}>
109+ <InnerLabel>{'HD Path'}</InnerLabel>
110+ </Input>
111+ ) : (
108112 <InnerLabelDropdown
109− className="mt-8"
110− options={pathOptions}
111− handleUpdate={handlePathSelection}
112− title={'Derivation path'}
113− currentSelectionTitle={basePathTitle}
113+ className="mt-4"
114+ prop-size="md"
115+ prop-format="innerLabel"
116+ options={accountOptions}
117+ handleUpdate={handleAccountSelection}
118+ title={'Account'}
119+ currentSelectionTitle={accountTitle}
114120 fullWidth={true}
115121 />
116122 );
117123
118− const truePathSelection =
119− basePath === 'custom' ? (
120− <Input
121− className="pt-8 mt-4 text-mono"
122− prop-size="md"
123− prop-format="innerLabel"
124− name="hdpath"
125− value={addHdPrefix(hdpath)}
126− autocomplete="off"
127− onChange={this.handleHdPathInput}>
128− <InnerLabel>{'HD Path'}</InnerLabel>
129− </Input>
130− ) : (
131− <InnerLabelDropdown
132− className="mt-4"
133− prop-size="md"
134− prop-format="innerLabel"
135− options={accountOptions}
136− handleUpdate={handleAccountSelection}
137− title={'Account'}
138− currentSelectionTitle={accountTitle}
139− fullWidth={true}
140− />
141− );
142−
143− return (
144− <Row>
145− <Col className={'measure-md'}>
146− <H1>{'Authenticate With Your Ledger'}</H1>
147−
148− <H2>{'Running on HTTPS?'}</H2>
149−
150− <P>
151− {`Connect and authenticate to your Ledger, and then open the
152− "Ethereum" application. If you're running on older firmware,
153− make sure the "browser support" option is turned on. To sign
154− transactions, you'll also need to enable the "contract data"
155− option.`}
156− </P>
157−
158− <P>
159− {`If you'd like to use a custom derivation path, you may enter
160− it below.`}
161− </P>
162−
163− <H2>{'Running on HTTP?'}</H2>
164−
165− <P>{`To authenticate and sign transactions with a Ledger, Bridge
166− must be serving over HTTPS on localhost. You can do this via the
167− following:`}</P>
168−
169− <ol className={'measure-md'}>
170− <li className={'mt-4'}>
171− {'Install'}{' '}
172− <a
173− target={'_blank'}
174− href={'https://github.com/FiloSottile/mkcert'}>
175− {'mkcert'}
176− </a>
177− </li>
178− <li className={'mt-4'}>
179− {'Install a local certificate authority via '}
180− <code>{'mkcert -install'}</code>
181− </li>
182− <li className={'mt-4'}>
183− {'In your '}
184− <code>{'bridge'}</code>
185− {
186− ' directory, generate a certificate valid for localhost via '
187− }{' '}
188− <code>{'mkcert localhost'}</code>
189− {'.'}
190− {'This will produce two files: '}
191− <code>{'localhost.pem'}</code> {', the local certificate, and '}
192− <code>{'localhost-key.pem'}</code>
193− {', its corresponding private key.'}
194− </li>
195− <li className={'mt-4'}>
196− {'Run '}
197− <code>{'python bridge-https.py'}</code>
198− </li>
199− </ol>
200−
201− {basePathSelection}
202− {truePathSelection}
203−
204− <Button
205− prop-size={'wide lg'}
206− className={'mt-8'}
207− onClick={this.pollDevice}>
208− {'Authenticate →'}
209− </Button>
210−
211− <Button
212− className={'mt-8'}
213− prop-size={'wide lg'}
214− disabled={Maybe.Nothing.hasInstance(wallet)}
215− onClick={this.props.loginCompleted}>
216− {'Continue →'}
217− </Button>
218− </Col>
219− </Row>
220− );
221− }
124+ return (
125+ <View>
126+ <H1>{'Authenticate With Your Ledger'}</H1>
127+
128+ <H2>{'Running on HTTPS?'}</H2>
129+
130+ <P>
131+ {`Connect and authenticate to your Ledger, and then open the
132+ "Ethereum" application. If you're running on older firmware,
133+ make sure the "browser support" option is turned on. To sign
134+ transactions, you'll also need to enable the "contract data"
135+ option.`}
136+ </P>
137+
138+ <P>
139+ {`If you'd like to use a custom derivation path, you may enter
140+ it below.`}
141+ </P>
142+
143+ <H2>{'Running on HTTP?'}</H2>
144+
145+ <P>{`To authenticate and sign transactions with a Ledger, Bridge
146+ must be serving over HTTPS on localhost. You can do this via the
147+ following:`}</P>
148+
149+ <ol className={'measure-md'}>
150+ <li className={'mt-4'}>
151+ {'Install'}{' '}
152+ <a target={'_blank'} href={'https://github.com/FiloSottile/mkcert'}>
153+ {'mkcert'}
154+ </a>
155+ </li>
156+ <li className={'mt-4'}>
157+ {'Install a local certificate authority via '}
158+ <code>{'mkcert -install'}</code>
159+ </li>
160+ <li className={'mt-4'}>
161+ {'In your '}
162+ <code>{'bridge'}</code>
163+ {' directory, generate a certificate valid for localhost via '}{' '}
164+ <code>{'mkcert localhost'}</code>
165+ {'.'}
166+ {'This will produce two files: '}
167+ <code>{'localhost.pem'}</code> {', the local certificate, and '}
168+ <code>{'localhost-key.pem'}</code>
169+ {', its corresponding private key.'}
170+ </li>
171+ <li className={'mt-4'}>
172+ {'Run '}
173+ <code>{'python bridge-https.py'}</code>
174+ </li>
175+ </ol>
176+
177+ {basePathSelection}
178+ {truePathSelection}
179+
180+ <ForwardButton className={'mt3'} onClick={pollDevice}>
181+ {'Authenticate'}
182+ </ForwardButton>
183+
184+ <ForwardButton
185+ className={'mt3'}
186+ disabled={Maybe.Nothing.hasInstance(wallet)}
187+ onClick={loginCompleted}>
188+ {'Continue'}
189+ </ForwardButton>
190+ </View>
191+ );
222192}
223−
224−export default compose(withWallet)(Ledger);