on master help sign in
Merged
commita1fc4e9e4ed96e71553c401d930d4e02a5cc2b3b authorChris Johnson <chris@cjohnson.io> dateFri 3 May 2019, 13:09 (8 years ago) parent6cdf1970bf12cd
9 files changed+46−414
src/bridge/components/Passport.js+1−1 src/bridge/lib/inviteMail.js+3−5 src/bridge/lib/lib.js+10 src/bridge/lib/tank.js+22−53 src/bridge/views/InviteTicket.js+4−11 src/bridge/views/InviteTransactions.js−107 src/bridge/views/InviteWallet.js−226 src/bridge/views/InvitesSend.js+3 src/bridge/views/Ticket.js+3−11
src/bridge/components/Passport.js+1−1
@@ -33,7 +33,7 @@ class Passport extends React.Component {
3333 let censor = "";
3434 const blocks = ['▖','▗','▘','▙','▚','▛','▜','▝','▞','▟'];
3535
36− (new UInt32Array(12)).forEach((_, i) => {
36+ (new Uint32Array(12)).forEach((_, i) => {
3737 let index = Math.floor(Math.random() * blocks.length)
3838 censor += blocks[index];
3939 })
src/bridge/lib/inviteMail.js+3−5
@@ -7,12 +7,10 @@ const baseUrl = 'https://localhost:3002'
77function sendMail(recipient, ticket) {
88 return new Promise((resolve, reject) => {
99 fetch(baseUrl + '/send-ticket', {
10− method: "POST",
11− // mode: "cors", // no-cors, cors, *same-origin
12− cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
13− // credentials: "omit", // include, *same-origin, omit
10+ method: 'POST',
11+ cache: 'no-cache',
1412 headers: {
15− "Content-Type": "application/json",
13+ 'Content-Type': 'application/json'
1614 },
1715 body: JSON.stringify({recipient,ticket}),
1816 })
src/bridge/lib/lib.js+10
@@ -29,6 +29,15 @@ const isValidGalaxy = name => {
2929 return point >= 0 && point < 256
3030}
3131
32+const randomPatq = (len) => {
33+ let bytes = window.crypto.getRandomValues(new Uint8Array(len))
34+ let hex = bytes.reduce((acc, byt) =>
35+ acc + byt.toString(16).padStart(2, '0'),
36+ ''
37+ )
38+ return ob.hex2patq(hex)
39+}
40+
3241export {
3342 compose,
3443 allFalse,
@@ -40,4 +49,5 @@ export {
4049 t,
4150 f,
4251 isValidGalaxy,
52+ randomPatq
4353}
src/bridge/lib/tank.js+22−53
@@ -1,67 +1,36 @@
1−import * as http from 'http';
2−import Maybe from 'folktale/maybe';
3−
41//NOTE if accessing this in a localhost configuration fails with "CORS request
52// did not succeed", you might need to visit localhost:3001 or whatever
63// explicitly and tell your browser that's safe to access.
74// https://stackoverflow.com/a/53011185/1334324
8−const baseOptions = {
9− hostname: 'localhost',
10− port: 3001,
11− protocol: 'https:',
12− headers: { 'Content-Type': 'application/json' }, //TODO text/plain vs application/json
13− json: true
14−}
5+const baseUrl = 'https://localhost:3001';
156
16−const reqHandler = (resolve, reject) => {
17− return (response) => {
18− console.log('xx got response', response)
19− // handle http errors
20− if (response.statusCode < 200 || response.statusCode > 299) {
21− reject(new Error('Failed to load, status code: ' + response.statusCode));
22− }
23− // temporary data holder
24− const body = [];
25− // on every content chunk, push it to the data array
26− response.on('data', (chunk) => body.push(chunk));
27− // we are done, resolve promise with those joined chunks
28− response.on('end', () => {
29− return resolve(JSON.parse(body.join('')))
30− });
31− }
7+function sendRequest(where, what) {
8+ return new Promise((resolve, reject) => {
9+ fetch(baseUrl + where, {
10+ method: 'POST',
11+ cache: 'no-cache',
12+ headers: {
13+ 'Content-Type': 'application/json'
14+ },
15+ body: JSON.stringify(what)
16+ })
17+ .then(response => {
18+ if (response.ok) {
19+ resolve(response.json());
20+ } else {
21+ reject(response);
22+ }
23+ })
24+ .catch(reject);
25+ });
3226}
3327
3428const remainingTransactions = point => {
35− return new Promise((resolve, reject) => {
36− const options = {
37− path: '/point',
38− method: 'POST'
39− }
40− const request = http.request(
41− Object.assign(baseOptions, options),
42− reqHandler(resolve, reject)
43− );
44− request.on('error', (e) => reject(new Error(e)));
45− request.write(JSON.stringify({point:point}));
46− request.end();
47− });
29+ return sendRequest('/point', {point:point});
4830};
4931
50−//TODO refactor, add sendRequest(path, json)
5132const fundTransactions = signedTxs => {
52− return new Promise((resolve, reject) => {
53− const options = {
54− path: '/request',
55− method: 'POST'
56− }
57− const request = http.request(
58− Object.assign(baseOptions, options),
59− reqHandler(resolve, reject)
60− );
61− request.on('error', (e) => reject(new Error(e)));
62− request.write(JSON.stringify({txs:signedTxs}));
63− request.end();
64− });
33+ return sendRequest('/request', {txs:signedTxs});
6534};
6635
6736export {
src/bridge/views/InviteTicket.js+4−11
@@ -6,6 +6,7 @@ import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
66import * as ob from 'urbit-ob'
77import * as azimuth from 'azimuth-js'
88
9+import { randomPatq } from '../lib/lib'
910import { simpleValidatorWrapper } from '../lib/validators'
1011import { ROUTE_NAMES } from '../lib/router'
1112import { DEFAULT_HD_PATH, urbitWalletFromTicket,
@@ -14,15 +15,6 @@ import { BRIDGE_ERROR } from '../lib/error'
1415import { INVITE_STAGES, WALLET_STATES, TRANSACTION_STATES } from '../lib/invite'
1516import { generateWallet, startTransactions } from '../lib/invite'
1617
17−const placeholder = (len) => {
18− let bytes = window.crypto.getRandomValues(new Uint8Array(len))
19− let hex = bytes.reduce((acc, byt) =>
20− acc + byt.toString(16).padStart(2, '0'),
21− ''
22− )
23− return ob.hex2patq(hex)
24−}
25−
2618class InviteTicket extends React.Component {
2719
2820 constructor(props) {
@@ -42,8 +34,8 @@ class InviteTicket extends React.Component {
4234 transactionProgress: TRANSACTION_STATES.GENERATING
4335 }
4436
45− this.pointPlaceholder = placeholder(4)
46− this.ticketPlaceholder = placeholder(12)
37+ this.pointPlaceholder = randomPatq(4)
38+ this.ticketPlaceholder = randomPatq(12)
4739
4840 this.handleInviteTicketInput = this.handleInviteTicketInput.bind(this)
4941 this.handleVerifyTicketInput = this.handleVerifyTicketInput.bind(this)
@@ -81,6 +73,7 @@ class InviteTicket extends React.Component {
8173 stage: INVITE_STAGES.INVITE_WALLET
8274 });
8375
76+ //NOTE ~zod because tmp wallet
8477 const uhdw = await urbitWalletFromTicket(inviteTicket, '~zod');
8578 const mnemonic = uhdw.ownership.seed;
8679 const inviteWallet = walletFromMnemonic(
src/bridge/views/InviteTransactions.jsdeleted−107
@@ -1,107 +0,0 @@
1−import { Just, Nothing } from 'folktale/maybe'
2−import React from 'react'
3−import { pour } from 'sigil-js'
4−import * as ob from 'urbit-ob'
5−import * as azimuth from 'azimuth-js'
6−
7−import {
8− EthereumWallet,
9− addressFromSecp256k1Public,
10− urbitWalletFromTicket,
11− addHexPrefix,
12− WALLET_NAMES
13−} from '../lib/wallet'
14−
15−import ReactSVGComponents from '../components/ReactSVGComponents'
16−import KeysAndMetadata from './Point/KeysAndMetadata'
17−import Actions from './Point/Actions'
18−import { BRIDGE_ERROR } from '../lib/error'
19−import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
20−
21−// for transaction generation and signing
22−import {
23− signTransaction,
24− sendSignedTransaction,
25− waitForTransactionConfirm,
26− isTransactionConfirmed
27−} from '../lib/txn'
28−import { attemptSeedDerivation, genKey } from '../lib/keys'
29−import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
30−import * as tank from '../lib/tank'
31−import Tx from 'ethereumjs-tx'
32−
33−const PROGRESS_STATES = {
34− GENERATING: 'Generating transactions',
35− SIGNING: 'Signing transactions',
36− FUNDING: 'Funding transactions',
37− CLAIMING: 'Claiming invite',
38− CONFIGURING: 'Configuring planet',
39− CLEANING: 'Cleaning up',
40− DONE: 'Done'
41−}
42−
43−class InviteTransactions extends React.Component {
44−
45− constructor(props) {
46− super(props)
47−
48− this.state = {
49− point: this.props.routeData.point,
50− ticket: this.props.routeData.ticket,
51− progress: 'Starting'
52− }
53−
54− this.startTransactions = this.startTransactions.bind(this);
55− }
56−
57− componentDidMount() {
58− //NOTE delayed so that we hang *after* rendering the page
59− this.updateProgress('Generating transactions');
60− setTimeout(this.startTransactions, 100);
61− }
62−
63− componentDidUpdate(prevProps) {
64− //
65− }
66−
67− updateProgress(msg) {
68− this.setState({progress: msg+'...'});
69− }
70−
71− render() {
72−
73− const { point } = this.state;
74−
75− const name = ob.patp(point);
76− const sigil = pour({
77− patp: name,
78− renderer: ReactSVGComponents,
79− size: 256
80− });
81− let pointOverview = (
82− //TODO Passport display component
83− <>
84− <div className={'mt-12 pt-6'}>
85− { sigil }
86− </div>
87− <H3><code>{ name }</code></H3>
88− </>
89− );
90−
91− return (
92− <Row>
93− <Col>
94−
95− { pointOverview }
96−
97− <p>{ 'Please wait while your wallet and point are prepared for use. This may take up to five minutes.' }</p>
98−
99− <p>{ this.state.progress }</p>
100−
101− </Col>
102− </Row>
103− )
104− }
105−}
106−
107−export default InviteTransactions
src/bridge/views/InviteWallet.jsdeleted−226
@@ -1,226 +0,0 @@
1−import { Just, Nothing } from 'folktale/maybe'
2−import React from 'react'
3−import { pour } from 'sigil-js'
4−import * as ob from 'urbit-ob'
5−import * as azimuth from 'azimuth-js'
6−
7−import { addressFromSecp256k1Public, EthereumWallet } from '../lib/wallet'
8−import { ROUTE_NAMES } from '../lib/router'
9−
10−import PointList from '../components/PointList'
11−import ReactSVGComponents from '../components/ReactSVGComponents'
12−import KeysAndMetadata from './Point/KeysAndMetadata'
13−import Actions from './Point/Actions'
14−import { BRIDGE_ERROR } from '../lib/error'
15−import { Row, Col, Warning, Button, H1, H3 } from '../components/Base'
16−
17−// for wallet generation
18−import * as wg from '../../walletgen/lib/lib'
19−import PaperCollateralRenderer from 'PaperCollateralRenderer'
20−import JSZip from 'jszip'
21−import saveAs from 'file-saver'
22−
23−class InviteWallet extends React.Component {
24−
25− constructor(props) {
26− super(props)
27−
28− this.state = {
29− loaded: false,
30− point: Nothing(),
31− wallet: Nothing(),
32− paper: Nothing(),
33− downloaded: false
34− }
35−
36− this.download = this.download.bind(this)
37− this.proceed = this.proceed.bind(this)
38− }
39−
40− componentDidMount() {
41− const { web3, wallet, contracts, setPointCursor } = this.props
42−
43− web3.chain(_ =>
44− contracts.chain(ctrcs =>
45− wallet.chain(async wal => {
46− const addr = addressFromSecp256k1Public(wal.publicKey);
47− console.log('fetching incoming for', addr)
48− const incoming = await azimuth.azimuth.getTransferringFor(ctrcs, addr)
49− console.log('incoming', incoming)
50−
51− let point = Nothing();
52− if (incoming.length > 0) {
53− let pointNum = parseInt(incoming[0]);
54− point = Just(pointNum);
55− this.generateWallet(pointNum);
56− if (incoming.length > 1) {
57− //TODO notify user "...and others / ticket reusable"
58− }
59− }
60− this.setState({loaded: true, point: point})
61− })))
62− }
63−
64− componentDidUpdate(prevProps) {
65− //
66− }
67−
68− async generateWallet(point) {
69− const ticket = await wg.makeTicket(point);
70− const wallet = await wg.generateWallet(point, ticket);
71− this.setState({ wallet: Just(wallet) });
72− }
73−
74− download() {
75− const zip = new JSZip();
76− //TODO the categories here aren't explained in bridge at all...
77−
78− const bin0 = this.state.paper.filter(item => item.bin === '0');
79− const bin1 = this.state.paper.filter(item => item.bin === '1');
80− const bin2 = this.state.paper.filter(item => item.bin === '2');
81− const bin3 = this.state.paper.filter(item => item.bin === '3');
82− const bin4 = this.state.paper.filter(item => item.bin === '4');
83−
84− const bin0Folder = zip.folder("0. Public");
85− const bin1Folder = zip.folder("1. Very High Friction Custody");
86− const bin2Folder = zip.folder("2. High Friction Custody");
87− const bin3Folder = zip.folder("3. Medium Friction Custody");
88− const bin4Folder = zip.folder("4. Low Friction Custody");
89−
90− bin0.forEach(item => bin0Folder.file(`${item.pageTitle}.png`, item.png))
91− bin1.forEach(item => bin1Folder.file(`${item.pageTitle}.png`, item.png));
92− bin2.forEach(item => bin2Folder.file(`${item.pageTitle}.png`, item.png));
93− bin3.forEach(item => bin3Folder.file(`${item.pageTitle}.png`, item.png));
94− bin4.forEach(item => bin4Folder.file(`${item.pageTitle}.png`, item.png));
95−
96− zip.generateAsync({type:"blob"}).then((content) => {
97− saveAs(content, 'urbit-wallet.zip');
98− this.setState({ downloaded: true });
99− });
100− }
101−
102− proceed() {
103− this.props.pushRoute(ROUTE_NAMES.INVITE_VERIFY, {
104− ticket: this.state.wallet.value.ticket,
105− point: parseInt(this.state.point.value)
106− });
107− }
108−
109− render() {
110−
111− const { loaded, point, wallet, paper, downloaded } = this.state;
112−
113− let loading;
114− if (!loaded) {
115− loading = ( <div>{ 'Loading...' }</div> );
116− }
117−
118− let balanceWarning;
119− if (loaded && Nothing.hasInstance(point)) {
120− balanceWarning = (
121− <Warning>
122− <h3 className={'mb-2'}>{'Warning'}</h3>
123− {
124− 'This invite has no claimable balance.'
125− }
126− </Warning>
127− );
128− }
129−
130− let pointOverview;
131− if (loaded && Just.hasInstance(point)) {
132− const name = ob.patp(point.value);
133− const sigil = pour({
134− patp: name,
135− renderer: ReactSVGComponents,
136− size: 256
137− });
138− pointOverview = (
139− //TODO Passport display component
140− <>
141− <div className={'mt-12 pt-6'}>
142− { sigil }
143− </div>
144− <H3><code>{ name }</code></H3>
145− </>
146− );
147− }
148−
149− let downloadButtonContents = 'Download wallet';
150− if ( Nothing.hasInstance(this.state.wallet) ||
151− Nothing.hasInstance(this.state.paper) ) {
152− downloadButtonContents = (
153− <>
154− <span className="btn-spinner"></span>
155− {'Generating wallet...'}
156− </>
157− );
158− }
159−
160− let downloadButton;
161− let nextButton;
162− if (loaded) {
163− downloadButton = (
164− <Button
165− className={'mt-8'}
166− prop-size={'lg wide'}
167− disabled={( Nothing.hasInstance(this.state.wallet) ||
168− Nothing.hasInstance(this.state.paper) )}
169− onClick={this.download}
170− >
171− <span className="relative">
172− { downloadButtonContents }
173− </span>
174− </Button>
175− );
176−
177− nextButton = (
178− <Button
179− className={'mt-8'}
180− prop-size={'lg wide'}
181− disabled={!this.state.downloaded}
182− onClick={this.proceed}
183− >
184− {'Proceed'}
185− </Button>
186− );
187− }
188−
189− let paperRenderer;
190− if ( Just.hasInstance(this.state.wallet) &&
191− Nothing.hasInstance(this.state.paper) ) {
192− console.log('rendering for', point.value)
193− paperRenderer = (
194− <PaperCollateralRenderer
195− wallet={{[point.value]: wallet.value}}
196− className={'extremely-hidden'}
197− callback={data => {
198− console.log('got paper', data);
199− this.setState({isRendering: false, paper: data})
200− }}
201− mode={'REGISTRATION'} />
202− );
203− }
204−
205− return (
206− <Row>
207− <Col>
208−
209− { loading }
210− { balanceWarning }
211− { pointOverview }
212−
213− <p>{'TODO copy. you need a wallet to receive the point into. download it below. also explanation of wallet files/storage'}</p>
214−
215− { downloadButton }
216− { nextButton }
217−
218− { paperRenderer }
219−
220− </Col>
221− </Row>
222− )
223− }
224−}
225−
226−export default InviteWallet
src/bridge/views/InvitesSend.js+3
@@ -153,6 +153,9 @@ class InvitesSend extends React.Component {
153153 return Just(txn)
154154 }
155155
156+ //TODO find a better way of making the user wait for submission success
157+ // and email sending, so that we can catch failure cases and can
158+ // always fall back to retrying or "please send them this"
156159 txnConfirmation(txHash, confirmations) {
157160 this.props.setTxnConfirmations(txHash, confirmations);
158161 if (confirmations === 1) {
src/bridge/views/Ticket.js+3−11
@@ -13,18 +13,10 @@ import { Row, Col, H1, P } from '../components/Base'
1313import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
1414import * as ob from 'urbit-ob'
1515
16+import { randomPatq } from '../lib/lib'
1617import { ROUTE_NAMES } from '../lib/router'
1718import { DEFAULT_HD_PATH, urbitWalletFromTicket } from '../lib/wallet'
1819
19−const placeholder = (len) => {
20− let bytes = window.crypto.getRandomValues(new Uint8Array(len))
21− let hex = bytes.reduce((acc, byt) =>
22− acc + byt.toString(16).padStart(2, '0'),
23− ''
24− )
25− return ob.hex2patq(hex)
26−}
27−
2820class Ticket extends React.Component {
2921
3022 constructor(props) {
@@ -37,8 +29,8 @@ class Ticket extends React.Component {
3729 isUnlocking: false
3830 }
3931
40− this.pointPlaceholder = placeholder(4)
41− this.ticketPlaceholder = placeholder(8)
32+ this.pointPlaceholder = randomPatq(4)
33+ this.ticketPlaceholder = randomPatq(8)
4234
4335 this.handleTicketInput = this.handleTicketInput.bind(this)
4436 this.handlePassphraseInput = this.handlePassphraseInput.bind(this)