on e366ef3 help sign in
bridge/src/views/Shards.js
219 lines 6.1 kB history raw
1import { Just, Nothing } from 'folktale/maybe';
2import React from 'react';
3import { Button } from '../components/old/Base';
4import {
5 InnerLabel,
6 ValidatedSigil,
7 PointInput,
8 ShardInput,
9 InputCaption,
10 Input,
11} from '../components/old/Base';
12import { Row, Col, H1, P } from '../components/old/Base';
13import * as kg from 'urbit-key-generation/dist';
14import * as ob from 'urbit-ob';
16import { ROUTE_NAMES } from '../lib/routeNames';
17import { withHistory } from '../store/history';
18import { urbitWalletFromTicket } from '../lib/wallet';
19import { compose } from '../lib/lib';
20import { withWallet } from '../store/wallet';
22const placeholder = len => {
23 let bytes = window.crypto.getRandomValues(new Uint8Array(len));
24 let hex = bytes.reduce(
25 (acc, byt) => acc + byt.toString(16).padStart(2, '0'),
26 ''
27 );
28 return ob.hex2patq(hex);
29};
31const SHARDS = {
32 SHARD1: Symbol('SHARD1'),
33 SHARD2: Symbol('SHARD2'),
34 SHARD3: Symbol('SHARD3'),
35};
37class Shards extends React.Component {
38 constructor(props) {
39 super(props);
41 this.state = {
42 shard1: '',
43 shard2: '',
44 shard3: '',
45 passphrase: '',
46 pointName: '',
47 };
49 this.pointPlaceholder = placeholder(4);
50 this.ticketPlaceholder = placeholder(16);
52 this.handlePassphraseInput = this.handlePassphraseInput.bind(this);
53 this.handleShardInput = this.handleShardInput.bind(this);
54 this.handlePointNameInput = this.handlePointNameInput.bind(this);
55 }
57 handleShardInput(shard, input) {
58 if (shard === SHARDS.SHARD1) {
59 this.setState({ shard1: input });
60 } else if (shard === SHARDS.SHARD2) {
61 this.setState({ shard2: input });
62 } else if (shard === SHARDS.SHARD3) {
63 this.setState({ shard3: input });
64 }
65 }
67 handlePassphraseInput(passphrase) {
68 this.setState({ passphrase });
69 }
71 handlePointNameInput(pointName) {
72 if (pointName.length < 15) {
73 this.setState({ pointName });
74 }
75 }
77 async walletFromShards(shard1, shard2, shard3, pointName, passphrase) {
78 const s1 = shard1 === '' ? undefined : shard1;
79 const s2 = shard2 === '' ? undefined : shard2;
80 const s3 = shard3 === '' ? undefined : shard3;
82 let ticket = undefined;
83 try {
84 ticket = kg.combine([s1, s2, s3]);
85 } catch (_) {
86 // do nothing
87 }
89 if (ticket !== undefined) {
90 const uhdw = await urbitWalletFromTicket(ticket, pointName, passphrase);
91 this.props.setUrbitWallet(Just(uhdw));
92 }
93 }
95 render() {
96 const { history, wallet } = this.props;
97 const { shard1, shard2, shard3, pointName, passphrase } = this.state;
99 const phPoint = this.pointPlaceholder;
100 const phTick = this.ticketPlaceholder;
102 const shards = [shard1, shard2, shard3];
103 const ready = shards.filter(x => x !== '').length > 1;
105 return (
106 <Row>
107 <Col>
108 <H1>{'Authenticate'}</H1>
110 <P>
111 {`Enter your point and at least two of your three Urbit master
112 ticket shards here. The index of the input field needs to
113 match the index of the shard.`}
114 </P>
116 <PointInput
117 className="mono mt-8"
118 prop-size="lg"
119 prop-format="innerLabel"
120 type="text"
121 autoFocus
122 placeholder={`e.g. ${phPoint}`}
123 value={pointName}
124 onChange={this.handlePointNameInput}>
125 <InnerLabel>{'Point'}</InnerLabel>
126 <ValidatedSigil
127 className={'tr-0 mt-05 mr-0 abs'}
128 patp={pointName}
129 size={68}
130 margin={8}
131 />
132 </PointInput>
134 <ShardInput
135 className="mono mt-8"
136 prop-size="md"
137 prop-format="innerLabel"
138 type="text"
139 name="shard1"
140 placeholder={`e.g. ${phTick}`}
141 value={shard1}
142 onChange={inp => this.handleShardInput(SHARDS.SHARD1, inp)}>
143 <InnerLabel>{'Shard 1'}</InnerLabel>
144 </ShardInput>
146 <ShardInput
147 className="mono mt-8"
148 prop-size="md"
149 prop-format="innerLabel"
150 type="text"
151 name="shard2"
152 placeholder={`e.g. ${phTick}`}
153 value={shard2}
154 onChange={inp => this.handleShardInput(SHARDS.SHARD2, inp)}>
155 <InnerLabel>{'Shard 2'}</InnerLabel>
156 </ShardInput>
158 <ShardInput
159 className="mono mt-8"
160 prop-size="md"
161 prop-format="innerLabel"
162 type="text"
163 name="shard3"
164 placeholder={`e.g. ${phTick}`}
165 value={shard3}
166 onChange={inp => this.handleShardInput(SHARDS.SHARD3, inp)}>
167 <InnerLabel>{'Shard 3'}</InnerLabel>
168 </ShardInput>
170 <InputCaption>
171 {'If your wallet requires a passphrase, you may enter it below.'}
172 </InputCaption>
174 <Input
175 className="pt-8"
176 prop-size="md"
177 prop-format="innerLabel"
178 name="passphrase"
179 type="password"
180 value={passphrase}
181 autocomplete="off"
182 onChange={this.handlePassphraseInput}>
183 <InnerLabel>{'Passphrase'}</InnerLabel>
184 </Input>
186 <Button
187 disabled={!ready}
188 className={'mt-8'}
189 prop-size={'lg wide'}
190 // prop-color={this.buttonTriState(wallet)}
191 onClick={() =>
192 this.walletFromShards(
193 shard1,
194 shard2,
195 shard3,
196 pointName,
197 passphrase
198 )
199 }>
200 {'Unlock Wallet →'}
201 </Button>
203 <Button
204 className={'mt-4'}
205 prop-size={'xl wide'}
206 disabled={Nothing.hasInstance(wallet)}
207 onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
208 {'Continue →'}
209 </Button>
210 </Col>
211 </Row>
212 );
213 }
216export default compose(
217 withHistory,
218 withWallet
219)(Shards);