1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-12-30 19:00:53 +00:00

device clone, device connect priority + BIG refactoring for

components/containers
This commit is contained in:
Szymon Lesisz 2018-04-11 12:06:46 +02:00
parent 1e316ea9d2
commit 60ead5a452
79 changed files with 1021 additions and 1266 deletions

View File

@ -46,7 +46,7 @@
{
"name": "Ethereum Classic",
"symbol": "ETC",
"network": "ethereumclassic",
"network": "ethereum-classic",
"bip44": "m/44'/61'/0'/0",
"defaultGasPrice": 64,
"defaultGasLimit": 21000,

View File

View File

@ -41,9 +41,6 @@ export function loadTokensFromJSON(): any {
return collection;
}, Promise.resolve({}));
console.log("JADE DAL")
const devices: ?string = get('devices');
if (devices) {
dispatch({

View File

@ -2,7 +2,6 @@
'use strict';
import TrezorConnect, { UI, UI_EVENT } from 'trezor-connect';
import * as ACTIONS from './index';
import * as MODAL from './constants/Modal';
import * as CONNECT from './constants/TrezorConnect';
@ -10,7 +9,7 @@ import * as CONNECT from './constants/TrezorConnect';
export function onPinSubmit(value: string): any {
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
return {
type: ACTIONS.CLOSE_MODAL
type: MODAL.CLOSE
}
}
@ -25,7 +24,7 @@ export function onPassphraseSubmit(passphrase: string): any {
});
dispatch({
type: ACTIONS.CLOSE_MODAL
type: MODAL.CLOSE
});
}
}
@ -60,7 +59,7 @@ export const onForgetSingleDevice = (device: any) => {
export const onCancel = () => {
return {
type: ACTIONS.CLOSE_MODAL
type: MODAL.CLOSE
}
}

View File

@ -0,0 +1,24 @@
/* @flow */
'use strict';
import * as NOTIFICATION from './constants/notification';
// called from RouterService
export const clear = (currentParams, requestedParams): any => {
return async (dispatch, getState) => {
// if route has been changed from device view into something else (like other device, settings...)
// try to remove all Notifications which are linked to previous device (they are not cancelable by user)
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
const entries = getState().notifications.filter(entry => typeof entry.devicePath === 'string');
entries.forEach(entry => {
dispatch({
type: NOTIFICATION.CLOSE,
payload: {
devicePath: entry.devicePath
}
})
});
}
}
}

View File

@ -21,6 +21,8 @@ export const init = (): any => {
const state: State = {
...initialState,
deviceState: selected.state,
deviceId: selected.features.device_id,
deviceInstance: selected.instance,
accountIndex: parseInt(urlParams.address),
network: urlParams.network,
location: location.pathname,
@ -67,9 +69,10 @@ export const showAddress = (address_n: string): any => {
const selected = findSelectedDevice(getState().connect);
if (!selected) return;
if (selected && !selected.connected) {
if (selected && (!selected.connected || !selected.available)) {
dispatch({
type: RECEIVE.REQUEST_UNVERIFIED,
device: selected
});
return;
}

View File

@ -117,6 +117,8 @@ export const init = (): any => {
const state: State = {
...initialState,
deviceState: selected.state,
deviceId: selected.features.device_id,
deviceInstance: selected.instance,
accountIndex: parseInt(urlParams.address),
network: urlParams.network,
coinSymbol: coin.symbol,

View File

@ -2,7 +2,6 @@
'use strict';
import EthereumjsUtil from 'ethereumjs-util';
import * as ACTIONS from './index';
import * as SUMMARY from './constants/summary';
import * as TOKEN from './constants/Token';
import * as ADDRESS from './constants/Address';
@ -20,11 +19,13 @@ export const init = (): any => {
const urlParams = location.params;
const selected = findSelectedDevice( getState().connect );
if (!selected) return;
if (!selected || !selected.state) return;
const state: State = {
...initialState,
deviceState: selected.state,
deviceId: selected.features.device_id,
deviceInstance: selected.instance,
accountIndex: parseInt(urlParams.address),
network: urlParams.network,
location: location.pathname,
@ -46,7 +47,7 @@ export const update = (newProps: any): any => {
} = getState();
const isLocationChanged: boolean = router.location.pathname !== summary.location;
if (isLocationChanged) {
if (isLocationChanged || !summary.deviceState) {
dispatch( init() );
return;
}
@ -137,98 +138,3 @@ export const removeToken = (token: any): any => {
token
}
}
export const onTokenSearch = (search: string): any => {
return {
type: ACTIONS.TOKENS_SEARCH,
search
}
}
export const onCustomTokenToggle = (): any => {
return {
type: ACTIONS.TOKENS_CUSTOM_TOGGLE
}
}
export const onCustomTokenAddressChange = (value: string): any => {
// todo:
// -validate address
// - if adresss is ok, try to fetch token info
// return {
// type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
// value
// }
return async (dispatch, getState) => {
const valid: boolean = EthereumjsUtil.isValidAddress(value);
if (valid) {
dispatch({
type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
value,
valid,
fetching: true
});
const { web3, abi } = getState().web3;
const contract = web3.eth.contract(abi).at(value);
contract.name.call((error, name) => {
if (error) {
// TODO: skip
}
contract.symbol.call((error, symbol) => {
if (error) {
// TODO: skip
}
contract.decimals.call((error, decimals) => {
console.log("fetched!", name, symbol, decimals)
})
});
})
} else {
dispatch({
type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
value,
valid
});
}
console.log("VALID!!!", valid);
}
}
export const onCustomTokenNameChange = (value: string): any => {
return {
type: ACTIONS.TOKENS_CUSTOM_NAME_CHANGE,
value
}
}
export const onCustomTokenShortcutChange = (value: string): any => {
return {
type: ACTIONS.TOKENS_CUSTOM_SHORTCUT_CHANGE,
value
}
}
export const onCustomTokenDecimalChange = (value: string): any => {
return {
type: ACTIONS.TOKENS_CUSTOM_DECIMAL_CHANGE,
value
}
}
export const onCustomTokenAdd = (): any => {
return {
type: ACTIONS.TOKENS_CUSTOM_ADD
}
}

View File

@ -2,20 +2,18 @@
'use strict';
import TrezorConnect, { UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT } from 'trezor-connect';
import * as ACTIONS from './index';
import * as ADDRESS from './constants/Address';
import * as TOKEN from './constants/Token';
import * as CONNECT from './constants/TrezorConnect';
import * as DISCOVERY from './constants/Discovery';
import * as NOTIFICATION from './constants/notification';
import * as WALLET from './constants/wallet';
import HDKey from 'hdkey';
import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsTx from 'ethereumjs-tx';
//import { getBalance } from '../services/Web3Service';
import { getBalance } from './Web3Actions';
import { getTransactionHistory } from '../services/EtherscanService';
import { push } from 'react-router-redux';
@ -24,7 +22,7 @@ import { init as initWeb3, getNonceAsync, getBalanceAsync, getTokenBalanceAsync
import type { Discovery } from '../reducers/DiscoveryReducer';
import { resolveAfter } from '../utils/promiseUtils';
import { getAccounts } from '../utils/reducerUtils';
import { findSelectedDevice, isSavedDevice } from '../reducers/TrezorConnectReducer';
import { findSelectedDevice, isSavedDevice, TrezorDevice } from '../reducers/TrezorConnectReducer';
export const init = (): any => {
return async (dispatch, getState): Promise<void> => {
@ -57,15 +55,18 @@ export const init = (): any => {
await TrezorConnect.init({
transportReconnect: true,
connectSrc: 'https://localhost:8088/',
// connectSrc: 'https://connect.trezor.io/tpm/',
// connectSrc: 'https://sisyfos.trezor.io/',
debug: true,
popup: false,
webusb: false
webusb: true
});
setTimeout(() => {
dispatch( initWeb3() );
}, 2000)
// wait for init
// setTimeout(() => {
// dispatch( initWeb3() );
//}, 2000)
} catch (error) {
dispatch({
@ -102,37 +103,53 @@ export const postInit = (): any => {
// devices were connected before Web3 initialized. force DEVICE.CONNECT event on them
const { devices } = getState().connect;
const { initialPathname, initialParams } = getState().wallet
if (initialPathname) {
dispatch({
type: WALLET.SET_INITIAL_URL,
pathname: null,
params: null
});
}
if (devices.length > 0) {
const unacquired = devices.find(d => d.unacquired);
if (unacquired) {
handleDeviceConnect(unacquired);
dispatch( onSelectDevice(unacquired) );
} else {
const latest = devices.sort((a, b) => {
if (!a.ts || !b.ts) {
return -1;
} else {
return a.ts > b.ts ? 1 : -1;
}
});
const latest: Array<TrezorDevice> = sortDevices(devices);
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
dispatch( onSelectDevice(firstConnected || latest[0]) );
console.log("LATEST", latest)
// TODO
if (initialParams) {
if (!initialParams.hasOwnProperty("network") && initialPathname !== getState().router.location.pathname) {
// dispatch( push(initialPathname) );
} else {
}
}
}
}
for (let d of devices) {
handleDeviceConnect(d);
}
}
}
export const initConnectedDevice = (device: any): any => {
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => {
return devices.sort((a, b) => {
if (!a.ts || !b.ts) {
return -1;
} else {
return a.ts > b.ts ? -1 : 1;
}
});
}
export const initConnectedDevice = (device: any, force): any => {
return (dispatch, getState): void => {
//dispatch( onSelectDevice(device) );
const selected = findSelectedDevice(getState().connect);
if (selected && selected.state) {
dispatch( onSelectDevice(device) );
} else if (!selected) {
if (!selected || (selected && selected.state)) {
dispatch( onSelectDevice(device) );
}
// if (device.unacquired && selected && selected.path !== device.path && !selected.connected) {
@ -143,11 +160,19 @@ export const initConnectedDevice = (device: any): any => {
}
}
// selection from Aside dropdown
// selection from Aside dropdown button
// after device_connect event
// or after acquiring device
export function onSelectDevice(device: any): any {
// device type could be local TrezorDevice or Device (from trezor-connect device_connect event)
export const onSelectDevice = (device: any): any => {
return (dispatch, getState): void => {
// || device.isUsedElsewhere
console.log("------> REDITTO", device, getState().wallet.initialUrl);
// switch to initial url and reset this value
if (device.unacquired) {
dispatch( push(`/device/${ device.path }/acquire`) );
} else if (device.features.bootloader_mode) {
@ -155,58 +180,50 @@ export function onSelectDevice(device: any): any {
} else if (device.instance) {
dispatch( push(`/device/${ device.features.device_id }:${ device.instance }`) );
} else {
const urlParams: any = getState().router.location.params;
// let url: string = `/device/${ device.features.device_id }/network/ethereum/address/0`;
let url: string = `/device/${ device.features.device_id }`;
let instance: ?string;
// check if device is not TrezorDevice type
if (!device.hasOwnProperty('ts')) {
// its device from trezor-connect (called in initConnectedDevice triggered by device_connect event)
// need to lookup if there are unavailable instances
const available: Array<TrezorDevice> = getState().connect.devices.filter(d => d.path === device.path);
const latest: Array<TrezorDevice> = sortDevices(available);
if (latest.length > 0 && latest[0].instance) {
url += `:${ latest[0].instance }`;
instance = latest[0].instance;
}
}
// check if current location is not set to this device
//dispatch( push(`/device/${ device.features.device_id }/network/etc/address/0`) );
dispatch( push(`/device/${ device.features.device_id }`) );
if (urlParams.deviceInstance !== instance || urlParams.device !== device.features.device_id) {
dispatch( push(url) );
}
}
}
}
// TODO: as TrezorConnect method
const __getDeviceState = async (path, instance, state): Promise<any> => {
// return await TrezorConnect.getPublicKey({
// device: {
// path,
// instance
// },
// path: "m/1'/0'/0'",
// confirmation: false
// });
return await TrezorConnect.getDeviceState({
device: {
path,
instance,
state
},
path: "m/1'/0'/0'",
confirmation: false
});
}
export const switchToFirstAvailableDevice = (): any => {
return async (dispatch, getState): Promise<void> => {
const { devices } = getState().connect;
if (devices.length > 0) {
// TODO: Priority:
// 1. Unacquired
// 1. First Unacquired
// 2. First connected
// 3. Saved with latest timestamp
// 4. First from the list
const unacquired = devices.find(d => d.unacquired);
if (unacquired) {
dispatch( initConnectedDevice(unacquired) );
} else {
const latest = devices.sort((a, b) => {
if (!a.ts || !b.ts) {
return -1;
} else {
return a.ts > b.ts ? 1 : -1;
}
});
dispatch( initConnectedDevice(devices[0]) );
const latest: Array<TrezorDevice> = sortDevices(devices);
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
dispatch( onSelectDevice(firstConnected || latest[0]) );
}
} else {
dispatch( push('/') );
dispatch({
@ -217,17 +234,24 @@ export const switchToFirstAvailableDevice = (): any => {
}
}
export const getSelectedDeviceState = (): any => {
return async (dispatch, getState): Promise<void> => {
const selected = findSelectedDevice(getState().connect);
console.warn("init selected", selected)
if (selected
&& selected.connected
&& !selected.unacquired
&& selected.features
&& !selected.acquiring
&& !selected.state) {
const response = await __getDeviceState(selected.path, selected.instance, selected.state);
const response = await TrezorConnect.getDeviceState({
device: {
path: selected.path,
instance: selected.instance,
state: selected.state
}
});
if (response && response.success) {
dispatch({
@ -239,20 +263,25 @@ export const getSelectedDeviceState = (): any => {
dispatch({
type: NOTIFICATION.ADD,
payload: {
devicePath: selected.path,
type: 'error',
title: 'Authentification error',
title: 'Authentication error',
message: response.payload.error,
cancelable: true,
cancelable: false,
actions: [
{
label: 'Try again',
callback: () => {
dispatch( {
type: NOTIFICATION.CLOSE,
payload: { devicePath: selected.path }
});
dispatch( getSelectedDeviceState() );
}
}
]
}
})
});
}
}
}
@ -273,7 +302,7 @@ export const deviceDisconnect = (device: any): any => {
dispatch({
type: CONNECT.REMEMBER_REQUEST,
device,
allInstances: affected
instances: affected
});
}
}
@ -296,22 +325,26 @@ export const coinChanged = (network: ?string): any => {
}
}
export function reload(): any {
return async (dispatch, getState) => {
}
}
export function acquire(): any {
return async (dispatch, getState) => {
const selected = findSelectedDevice(getState().connect);
const saved = getState().connect.devices.map(d => {
if (d.state) {
return {
instance: d.instance,
state: d.state
}
} else {
return null;
}
});
// const saved = getState().connect.devices.map(d => {
// if (d.state) {
// return {
// instance: d.instance,
// state: d.state
// }
// } else {
// return null;
// }
// });
//const response = await __acquire(selected.path, selected.instance);
@ -374,6 +407,12 @@ export const forgetDevice = (device: any) => {
}
}
export const gotoDeviceSettings = (device: any) => {
return (dispatch: any, getState: any): any => {
dispatch( push(`/device/${ device.features.device_id }/settings`) );
}
}
// called from Aside - device menu (forget single instance)
export const forget = (device: any) => {
return {
@ -404,6 +443,12 @@ export const beginDiscoveryProcess = (device: any, network: string): any => {
const { config } = getState().localStorage;
const coinToDiscover = config.coins.find(c => c.network === network);
dispatch({
type: DISCOVERY.WAITING,
device,
network
});
// TODO: validate device deviceState
// const deviceState = await __acquire(device.path, device.instance);
// if (deviceState && deviceState.success) {
@ -425,9 +470,10 @@ export const beginDiscoveryProcess = (device: any, network: string): any => {
keepSession: true // acquire and hold session
});
// handle TREZOR response error
if (!response.success) {
// TODO: check message
console.warn("DISCO ERROR", response)
console.warn("DISCOVERY ERROR", response)
dispatch({
type: NOTIFICATION.ADD,
payload: {
@ -448,9 +494,10 @@ export const beginDiscoveryProcess = (device: any, network: string): any => {
return;
}
// TODO: check for interruption
// check for interruption
let discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === device.state && d.network === network);
if (discoveryProcess && discoveryProcess.interrupted) return;
// TODO: handle response error
const basePath: Array<number> = response.payload.path;
const hdKey = new HDKey();
hdKey.publicKey = new Buffer(response.payload.publicKey, 'hex');
@ -502,6 +549,15 @@ export const discoverAddress = (device: any, discoveryProcess: Discovery): any =
});
if (discoveryProcess.interrupted) return;
// const discoveryA = await TrezorConnect.accountDiscovery({
// device: {
// path: device.path,
// instance: device.instance,
// state: device.state
// },
// });
// if (discoveryProcess.interrupted) return;
if (verifyAddress && verifyAddress.success) {
//const trezorAddress: string = '0x' + verifyAddress.payload.address;
const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.payload.address);
@ -630,6 +686,9 @@ export function startDiscoveryProcess(device: any, network: string, ignoreComple
} else if (!selected.state) {
console.warn("Start discovery: Selected device wasn't authenticated yet...")
return;
} else if (selected.connected && !selected.available) {
console.warn("Start discovery: Selected device is unavailable...")
return;
}
const discovery = getState().discovery;
@ -679,8 +738,8 @@ export const restoreDiscovery = (): any => {
}
// there is no discovery process but it should be
// this is possible race condition when "network" was changed in url but device wasn't authenticated yet
// try to discovery after CONNECT.AUTH_DEVICE action
// this is possible race condition when "network" was changed in url but device was not authenticated yet
// try to start discovery after CONNECT.AUTH_DEVICE action
export const checkDiscoveryStatus = (): any => {
return (dispatch, getState): void => {
const selected = findSelectedDevice(getState().connect);

View File

@ -2,9 +2,8 @@
'use strict';
export const ON_RESIZE: string = 'ON_RESIZE';
export const ON_BEFORE_UNLOAD: string = 'app__on_before_unload';
export const TOGGLE_DEVICE_DROPDOWN: string = 'TOGGLE_DEVICE_DROPDOWN';
export const RESIZE_CONTAINER: string = 'RESIZE_CONTAINER';
import * as WALLET from './constants/wallet';
export const onResize = (): any => {
return {
@ -14,20 +13,13 @@ export const onResize = (): any => {
export const onBeforeUnload = (): any => {
return {
type: ON_BEFORE_UNLOAD
}
}
export const resizeAppContainer = (opened: boolean): any => {
return {
type: RESIZE_CONTAINER,
opened
type: WALLET.ON_BEFORE_UNLOAD
}
}
export const toggleDeviceDropdown = (opened: boolean): any => {
return {
type: TOGGLE_DEVICE_DROPDOWN,
type: WALLET.TOGGLE_DEVICE_DROPDOWN,
opened
}
}

View File

@ -7,10 +7,8 @@ import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsTx from 'ethereumjs-tx';
import TrezorConnect from 'trezor-connect';
import { strip } from '../utils/ethUtils';
import * as ACTIONS from './index';
import * as ADDRESS from './constants/Address';
import * as WEB3 from './constants/Web3';
import { loadHistory } from '../services/EtherscanService';
import { httpRequest } from '../utils/networkUtils';
type ActionMethod = (dispatch: any, getState: any) => Promise<any>;
@ -192,9 +190,6 @@ export function init(web3: ?Web3, coinIndex: number = 0): ActionMethod {
}
}
function initBlockTicker() {
}
export function initContracts(): ActionMethod {
return async (dispatch, getState) => {
@ -241,14 +236,17 @@ export function getGasPrice(network: string): ActionMethod {
const index: number = getState().web3.findIndex(w3 => w3.network === network);
const web3 = getState().web3[ index ].web3;
const web3instance = getState().web3[ index ];
const web3 = web3instance.web3;
web3.eth.getGasPrice((error, gasPrice) => {
if (!error) {
dispatch({
type: WEB3.GAS_PRICE_UPDATED,
network: network,
gasPrice
});
if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) {
dispatch({
type: WEB3.GAS_PRICE_UPDATED,
network: network,
gasPrice
});
}
}
});
}
@ -313,7 +311,6 @@ export function getTransactionReceipt(tx: any): any {
web3.eth.getBlock(receipt.blockHash, (error, block) => {
console.log("---MAMM BLOCK", error, block, receipt, receipt.blockHash)
dispatch({
//type: ACTIONS.TX_CONFIRMED,
type: WEB3.PENDING_TX_RESOLVED,
tx,
receipt,

View File

@ -11,3 +11,4 @@ export const FORGET: string = 'modal__forget';
export const REMEMBER: string = 'modal__remember';
export const ON_FORGET: string = 'modal__on_forget';
export const ON_REMEMBER: string = 'modal__on_remember';
export const CLOSE: string = 'modal__close';

View File

@ -0,0 +1,6 @@
/* @flow */
'use strict';
export const ON_BEFORE_UNLOAD: string = 'wallet__on_before_unload';
export const TOGGLE_DEVICE_DROPDOWN: string = 'wallet_toggle_dropdown';
export const SET_INITIAL_URL: string = 'wallet_set_initial_url';

View File

@ -1,45 +0,0 @@
/* @flow */
'use strict';
export const CLOSE_MODAL: string = 'action__close_modal';
export const ON_PIN_SUBMIT: string = 'action__on_pin_submit';
export const ON_PASSPHRASE_SUBMIT: string = 'action__on_passphrase_submit';
export const ON_ADDRESS_CHANGE: string = 'send__on_address_change';
export const ON_AMOUNT_CHANGE: string = 'send__on_amount_change';
export const ON_FEE_LEVEL_CHANGE: string = 'send__on_fee_level_change';
export const ON_GAS_PRICE_CHANGE: string = 'send__on_gas_price_change';
export const ON_GAS_LIMIT_CHANGE: string = 'send__on_gas_limit_change';
export const ON_TX_DATA_CHANGE: string = 'send__on_data_change';
export const ON_TX_SEND: string = 'send__on_send';
export const ON_GAS_PRICE_UPDATE: string = 'send__on_gas_price_update';
export const ADDRESS_CREATE: string = 'address__create';
export const ADDRESS_DELETE: string = 'address__delete';
export const ADDRESS_SET_BALANCE: string = 'address2__set_balance';
export const ADDRESS_SET_HISTORY: string = 'address__set_history';
export const ADDRESS_UPDATE_BALANCE: string = 'address__update_balance';
export const ADDRESS_ADD_TO_HISTORY: string = 'address__add_to_history';
export const TX_STATUS_OK: string = 'tx__status_ok';
export const TX_STATUS_ERROR: string = 'tx__status_error';
export const TX_STATUS_UNKNOWN: string = 'tx__status_unknown';
export const TX_CONFIRMED: string = 'tx__confirmed';
export const TOKENS_TOGGLE_SUMMARY: string = 'tokens_toggle_summary';
export const TOKENS_SEARCH: string = 'tokens_search';
export const TOKENS_CUSTOM_TOGGLE: string = 'tokens_custom_toggle';
export const TOKENS_CUSTOM_ADDRESS_CHANGE: string = 'tokens_custom_address_change';
export const TOKENS_CUSTOM_NAME_CHANGE: string = 'tokens_custom_name_change';
export const TOKENS_CUSTOM_SHORTCUT_CHANGE: string = 'tokens_custom_shortcut_change';
export const TOKENS_CUSTOM_DECIMAL_CHANGE: string = 'tokens_custom_decimal_change';
export const TOKENS_CUSTOM_ADD: string = 'tokens_custom_add';

View File

@ -26,7 +26,7 @@ export const Notification = (props: any) => {
) : null }
<div className="notification-body">
<h2>{ props.title }</h2>
<p dangerouslySetInnerHTML={{__html: props.message }}></p>
{ props.message ? (<p dangerouslySetInnerHTML={{__html: props.message }}></p>) : null }
</div>
{ props.actions && props.actions.length > 0 ? (
<div className="notification-action">

View File

@ -6,19 +6,28 @@ import TrezorConnect from 'trezor-connect';
export default class InstallBridge extends Component {
componentDidMount(): void {
const transport: any = this.props.transport;
if (transport && transport.version.indexOf('webusb') >= 0)
TrezorConnect.renderWebUSBButton();
}
componentDidUpdate() {
const transport = this.props.transport;
if (transport && transport.type.indexOf('webusb') >= 0)
if (transport && transport.version.indexOf('webusb') >= 0)
TrezorConnect.renderWebUSBButton();
}
render() {
let css = 'row';
let webusb = null;
let connectClaim = 'Connect TREZOR to continue';
let and = null;
let bridgeClaim = null;
const transport = this.props.transport;
if (transport && transport.type.indexOf('webusb') >= 0) {
if (transport && transport.version.indexOf('webusb') >= 0) {
css = 'row webusb'
webusb = <button className="trezor-webusb-button">Check for devices</button>;
connectClaim = 'Connect TREZOR';
and = <p>and</p>;
@ -30,7 +39,7 @@ export default class InstallBridge extends Component {
<h2 className="claim">The private bank in your hands.</h2>
<p>TREZOR Wallet is an easy-to-use interface for your TREZOR.</p>
<p>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</p>
<div className="row">
<div className={ css }>
<p className="connect">
<span>
<svg width="12px" height="35px" viewBox="0 0 20 57">
@ -44,8 +53,8 @@ export default class InstallBridge extends Component {
{ connectClaim }
</span>
</p>
{ and }
{ webusb }
<p className="webusb-and">and</p>
<button className="trezor-webusb-button">Check for devices</button>
</div>
<div className="image">
<p>

View File

@ -2,6 +2,7 @@
'use strict';
import React, { Component } from 'react';
import Preloader from './Preloader';
import Select from 'react-select';
type State = {
@ -28,9 +29,9 @@ export default class InstallBridge extends Component {
const currentTarget = installers.find(i => i.id === props.browserState.osname);
this.state = {
version: '2.0.11',
target: currentTarget,
url: 'https://wallet.trezor.io/data/bridge/2.0.11/'
version: '2.0.12',
url: 'https://wallet.trezor.io/data/bridge/2.0.12/',
target: null,
};
}
@ -40,11 +41,25 @@ export default class InstallBridge extends Component {
});
}
componentWillUpdate() {
if (this.props.browserState.osname && !this.state.target) {
const currentTarget = installers.find(i => i.id === this.props.browserState.osname);
this.setState({
target: currentTarget
})
}
}
render() {
if (!this.state.target) {
return <Preloader />;
}
const url = `${ this.state.url }${ this.state.target.value }`;
return (
<main>
<h3 className="claim">TREZOR Bridge. <span>Version 2.0.11</span></h3>
<h3 className="claim">TREZOR Bridge. <span>Version 2.0.12</span></h3>
<p>New communication tool to facilitate the connection between your TREZOR and your internet browser.</p>
<div className="row">
<Select

View File

@ -58,7 +58,7 @@ export default (props: any): any => {
} else if (connectError || bridgeRoute) {
css += ' install-bridge';
body = <InstallBridge browserState={ props.connect.browserState } />;
} else if (web3.length > 0 && devices.length < 1) {
} else if (props.wallet.ready && devices.length < 1) {
css += ' connect-device';
body = <ConnectDevice transport={ transport } />;
}

View File

@ -1,24 +1,24 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import LandingPage from '../components/landing/LandingPage';
import * as LogActions from '../actions/LogActions';
import LandingPage from './LandingPage';
function mapStateToProps(state, own) {
const mapStateToProps = (state, own) => {
return {
localStorage: state.localStorage,
modal: state.modal,
web3: state.web3,
wallet: state.wallet,
connect: state.connect,
router: state.router
};
}
function mapDispatchToProps(dispatch) {
const mapDispatchToProps = (dispatch) => {
return {
};

View File

@ -38,20 +38,42 @@ export const ConfirmUnverifiedAddress = (props: any): any => {
showAddress
} = props.receiveActions;
const {
device
} = props.modal;
return (
<div className="confirm-address-unverified">
<button className="close-modal transparent" onClick={ onCancel }></button>
<h3>Your TREZOR is not connected</h3>
<p>To prevent phishing attacks, you should verify the address on your TREZOR first. Please reconnect your device to continue with the verification process.</p>
<button onClick={ event => {
onCancel();
showAddress(account.addressPath);
} }>Try again</button>
<button className="white" onClick={ event => {
onCancel();
showUnverifiedAddress();
} }>Show unverified address</button>
</div>
);
if (!device.connected) {
return (
<div className="confirm-address-unverified">
<button className="close-modal transparent" onClick={ onCancel }></button>
<h3>{ device.instanceLabel } is not connected</h3>
<p>To prevent phishing attacks, you should verify the address on your TREZOR first. Please reconnect your device to continue with the verification process.</p>
<button onClick={ event => {
onCancel();
showAddress(account.addressPath);
} }>Try again</button>
<button className="white" onClick={ event => {
onCancel();
showUnverifiedAddress();
} }>Show unverified address</button>
</div>
);
} else {
const enable: string = device.features.passphrase_protection ? 'Enable' : 'Disable';
return (
<div className="confirm-address-unverified">
<button className="close-modal transparent" onClick={ onCancel }></button>
<h3>{ device.instanceLabel } is unavailable</h3>
<p>To prevent phishing attacks, you should verify the address on your TREZOR first. { enable } passphrase settings to continue with the verification process.</p>
<button onClick={ event => {
onCancel();
showAddress(account.addressPath);
} }>Try again</button>
<button className="white" onClick={ event => {
onCancel();
showUnverifiedAddress();
} }>Show unverified address</button>
</div>
);
}
}

View File

@ -7,13 +7,11 @@ const RememberDevice = (props: any): any => {
const { device } = props.modal;
const { onCancel, onDuplicateDevice } = props.modalActions;
return (
<div className="pin">
<h3>Duplicate { device.label } ?</h3>
<label>Device label</label>
<input type="text" />
<button onClick={ onCancel }>Cancel</button>
<button onClick={ event => onDuplicateDevice( { ...device, instanceLabel: "kokot" } ) }>Duplicate</button>
<div className="duplicate">
<h3>Clone { device.instanceLabel }?</h3>
<p>This will create new instance of device which can be used with different passphrase</p>
<button onClick={ event => onDuplicateDevice( { ...device, instanceLabel: "TODO: user label from input" } ) }>Create new instance</button>
<button className="white" onClick={ onCancel }>Cancel</button>
</div>
);
}

View File

@ -3,8 +3,10 @@
import React, { Component } from 'react';
import raf from 'raf';
import { findSelectedDevice } from '../../reducers/TrezorConnectReducer';
type State = {
deviceLabel: string;
singleInput: boolean;
passphrase: string;
passphraseRevision: string;
@ -25,10 +27,20 @@ export default class PinModal extends Component {
super(props);
// check if this device is already known
const isSavedDevice = props.devices.find(d => d.path === props.modal.device.path && d.remember);
// const isSavedDevice = props.devices.find(d => d.path === props.modal.device.path && d.remember);
const selected = findSelectedDevice(props.connect);
let deviceLabel = props.modal.device.label;
let singleInput = false;
if (selected && selected.path === props.modal.device.path) {
deviceLabel = selected.instanceLabel;
singleInput = selected.remember;
}
console.warn("-----PASSS", selected)
this.state = {
singleInput: isSavedDevice ? true : false,
deviceLabel,
singleInput,
passphrase: '',
passphraseRevision: '',
passphraseFocused: false,
@ -220,6 +232,7 @@ export default class PinModal extends Component {
} = this.props.modal;
const {
deviceLabel,
singleInput,
passphrase,
passphraseRevision,
@ -242,7 +255,7 @@ export default class PinModal extends Component {
return (
<div className="passphrase">
{/* <button className="close-modal transparent" onClick={ event => this.submit(true) }></button> */}
<h3>Enter { device.label } passphrase</h3>
<h3>Enter { deviceLabel } passphrase</h3>
<p>Note that passphrase is case-sensitive.</p>
<div className="row">
<label>Passphrase</label>

View File

@ -63,11 +63,20 @@ export default class RememberDevice extends Component {
}
render(): any {
const { device } = this.props.modal;
const { device, instances } = this.props.modal;
const { onForgetDevice, onRememberDevice } = this.props.modalActions;
let label = device.label;
let devicePlural = false;
if (instances && instances.length > 0) {
label = instances.map(i => {
return (<span>{i.instanceLabel}</span>);
})
devicePlural = instances.length > 1;
}
return (
<div className="remember">
<h3>Forget { device.label } ?</h3>
<h3>Forget {label}?</h3>
<p>Would you like TREZOR Wallet to forget your device or to remember it, so that it is still visible even while disconnected?</p>
<button onClick={ event => onForgetDevice(device) }>Forget</button>
<button className="white" onClick={ event => onRememberDevice(device) }><span>Remember <Loader size={ 28 } label={ this.state.countdown } /></span></button>
@ -81,7 +90,7 @@ export const ForgetDevice = (props: any): any => {
const { onForgetSingleDevice, onCancel } = props.modalActions;
return (
<div className="remember">
<h3>Forget { device.label } ?</h3>
<h3>Forget { device.instanceLabel } ?</h3>
<p>Forgetting only removes the device from the list on the left, your bitcoins are still safe and you can access them by reconnecting your TREZOR again.</p>
<button onClick={ event => onForgetSingleDevice(device) }>Forget</button>
<button className="white" onClick={ onCancel }>Don't forget</button>
@ -94,7 +103,7 @@ export const DisconnectDevice = (props: any): any => {
const { onForgetSingleDevice, onCancel } = props.modalActions;
return (
<div className="remember">
<h3>Unplug { device.label }</h3>
<h3>Unplug { device.instanceLabel }</h3>
<p>TREZOR Wallet will forget your TREZOR right after you disconnect it.</p>
<b>TODO: its not true, actually i've already forget those data!!!</b>
</div>

View File

@ -105,6 +105,7 @@ const mapStateToProps = (state: any, own: any): any => {
modal: state.modal,
accounts: state.accounts,
devices: state.connect.devices,
connect: state.connect,
sendForm: state.sendForm,
receive: state.receive,
localStorage: state.localStorage

View File

@ -2,7 +2,10 @@
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Notification } from '../common/Notification';
import * as TrezorConnectActions from '../../actions/TrezorConnectActions';
const Acquire = (props: any): any => {
@ -21,19 +24,24 @@ const Acquire = (props: any): any => {
title="Device is used in other window"
message="Do you want to use your device in this window?"
className="info"
cancelable={false}
actions={actions}
cancelable={ false }
actions={ actions }
close={ () => {} }
/>
{/* <div className="warning">
<div>
<h2></h2>
<p></p>
</div>
<button onClick={ event => props.acquireDevice() }>Acquire device</button>
</div> */}
</section>
);
}
export default Acquire;
const mapStateToProps = (state, own) => {
return {
connect: state.connect
};
}
const mapDispatchToProps = (dispatch) => {
return {
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Acquire);

View File

@ -2,6 +2,8 @@
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
const Bootloader = (props: any): any => {
return (
@ -11,4 +13,15 @@ const Bootloader = (props: any): any => {
);
}
export default Bootloader;
const mapStateToProps = (state, own) => {
return {
};
}
const mapDispatchToProps = (dispatch) => {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Bootloader);

View File

@ -2,6 +2,8 @@
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
const Dashboard = (props: any): any => {
return (
@ -16,4 +18,15 @@ const Dashboard = (props: any): any => {
);
}
export default Dashboard;
const mapStateToProps = (state, own) => {
return {
};
}
const mapDispatchToProps = (dispatch) => {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);

View File

@ -0,0 +1,27 @@
/* @flow */
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
export const DeviceSettings = (props: any): any => {
return (
<section className="settings">
Device settings
</section>
);
}
const mapStateToProps = (state, own) => {
return {
};
}
const mapDispatchToProps = (dispatch) => {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DeviceSettings);

View File

@ -2,18 +2,23 @@
'use strict';
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Tooltip from 'rc-tooltip';
import { QRCode } from 'react-qr-svg';
import AbstractAccount from './account/AbstractAccount';
import { Notification } from '../common/Notification';
import Tooltip from 'rc-tooltip';
import * as ReceiveActions from '../../actions/ReceiveActions';
export default class Receive extends AbstractAccount {
class Receive extends AbstractAccount {
render() {
return super.render(this.props.receive) || _render(this.props);
return super.render(this.props.receive) || _render(this.props, this.device, this.account, this.deviceStatusNotification);
}
}
const _render = (props: any): any => {
const _render = (props: any, device, account, deviceStatusNotification): any => {
const {
network,
@ -23,8 +28,8 @@ const _render = (props: any): any => {
addressUnverified,
} = props.receive;
const device = props.devices.find(d => d.state === deviceState);
const account = props.accounts.find(a => a.deviceState === deviceState && a.index === accountIndex && a.network === network);
// const device = props.devices.find(d => d.state === deviceState);
// const account = props.accounts.find(a => a.deviceState === deviceState && a.index === accountIndex && a.network === network);
let qrCode = null;
let address = `${account.address.substring(0, 20)}...`;
@ -50,7 +55,7 @@ const _render = (props: any): any => {
className = addressUnverified ? 'address unverified' : 'address';
const tooltip = addressUnverified ?
(<div>Unverified address.<br/>{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }</div>)
(<div>Unverified address.<br/>{ device.connected && device.available ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }</div>)
:
(<div>{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify address.' }</div>);
@ -68,9 +73,7 @@ const _render = (props: any): any => {
return (
<section className="receive">
{ !device.connected ? (
<Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />
) : null }
{ deviceStatusNotification }
<h2>Receive Ethereum or tokens</h2>
<div className={ className }>
@ -86,3 +89,23 @@ const _render = (props: any): any => {
}
const mapStateToProps = (state, own) => {
return {
location: state.router.location,
devices: state.connect.devices,
accounts: state.accounts,
discovery: state.discovery,
receive: state.receive
};
}
const mapDispatchToProps = (dispatch) => {
return {
initAccount: bindActionCreators(ReceiveActions.init, dispatch),
updateAccount: bindActionCreators(ReceiveActions.update, dispatch),
disposeAccount: bindActionCreators(ReceiveActions.dispose, dispatch),
showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Receive);

View File

@ -1,12 +0,0 @@
/* @flow */
'use strict';
import React from 'react';
export default (props: any): any => {
return (
<section className="settings">
Settings
</section>
);
}

View File

@ -2,8 +2,10 @@
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
export default (props: any): any => {
const SignVerify = (props: any): any => {
return (
<section className="signverify">
<div className="sign">
@ -27,3 +29,16 @@ export default (props: any): any => {
</section>
);
}
const mapStateToProps = (state, own) => {
return {
};
}
const mapDispatchToProps = (dispatch) => {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignVerify);

View File

@ -0,0 +1,27 @@
/* @flow */
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
export const WalletSettings = (props: any): any => {
return (
<section className="settings">
Wallet settings
</section>
);
}
const mapStateToProps = (state, own) => {
return {
};
}
const mapDispatchToProps = (dispatch) => {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(WalletSettings);

View File

@ -3,19 +3,54 @@
import React, { Component } from 'react';
import { Notification } from '../../common/Notification';
import { findDevice } from '../../../utils/reducerUtils';
import type { TrezorDevice } from '../../../reducers/TrezorConnectReducer';
export type AccountState = {
device: TrezorDevice;
discovery: any;
account: any;
}
export default class AbstractAccount extends Component {
device: TrezorDevice;
discovery: any;
account: any;
deviceStatusNotification: any;
constructor(props: any) {
super(props);
this.state = {
}
}
setLocalVars(vars: any) {
this.device = vars.device;
this.discovery = vars.discovery;
}
componentDidMount() {
this.props.initAccount();
}
componentWillUpdate(newProps: any) {
this.device = null;
this.discovery = null;
this.account = null;
this.deviceStatusNotification = null;
this.props.updateAccount();
}
componentWillUnmount() {
this.props.disposeAccount();
this.device = null;
this.discovery = null;
this.account = null;
this.deviceStatusNotification = null;
}
render(state: any): any {
@ -23,31 +58,49 @@ export default class AbstractAccount extends Component {
const props = this.props;
if (!state.deviceState) {
return (<section></section>);
return (<section><Notification className="info" title="Loading device" /></section>);
}
const device = this.props.devices.find(d => d.state === state.deviceState);
const device = findDevice(this.props.devices, state.deviceState, state.deviceId, state.deviceInstance);
if (!device) {
return (<section>Device with state {state.deviceState} not found</section>);
}
const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === state.network);
const account = props.accounts.find(a => a.deviceState === state.deviceState && a.index === state.accountIndex && a.network === state.network);
let deviceStatusNotification = null;
if (!account) {
if (!discovery || discovery.waitingForDevice) {
return (
<section>
<Notification
className="info"
title="Loading account"
message=""
/>
</section>
);
if (device.connected) {
if (device.available) {
return (
<section>
<Notification className="info" title="Loading account" />
</section>
);
} else {
return (
<section>
<Notification
className="info"
title={ `Device ${ device.instanceLabel } is unavailable` }
message="Change passphrase settings to use this device"
/>
</section>
);
}
} else {
return (
<section>
<Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />
</section>
);
}
} else if (discovery.completed) {
return (
<section>
<Notification className="warning" title="Account is not exist" />
<Notification className="warning" title="Account does not exist" />
</section>
);
} else {
@ -57,8 +110,20 @@ export default class AbstractAccount extends Component {
</section>
);
}
} else {
if (!device.connected) {
deviceStatusNotification = <Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />;
} else if (!device.available) {
deviceStatusNotification = <Notification className="info" title={ `Device ${ device.instanceLabel } is unavailable` } message="Change passphrase settings to use this device" />;
}
}
// Set class variables for extender classes
this.device = device;
this.discovery = discovery;
this.account = account;
this.deviceStatusNotification = deviceStatusNotification;
return null;
}
}

View File

@ -17,8 +17,9 @@ const AccountSelection = (props: any): any => {
if (!selected) return null;
const { location } = props.router;
const urlParams = location.params;
const accounts = props.accounts;
const baseUrl: string = `/device/${location.params.device}`;
const baseUrl: string = urlParams.deviceInstance ? `/device/${urlParams.device}:${urlParams.deviceInstance}` : `/device/${urlParams.device}`;
const { config } = props.localStorage;
const selectedCoin = config.coins.find(c => c.network === location.params.network);

View File

@ -48,7 +48,7 @@ const Aside = (props: any): any => {
// );
// }
let menu = null;
let menu = <section></section>;
if (props.deviceDropdownOpened) {
menu = <DeviceDropdown {...props} />;

View File

@ -34,6 +34,10 @@ const Value = (props: any): any => {
const deviceMenuItems: Array<any> = [];
if (device.features && device.features.passphrase_protection) {
deviceMenuItems.push("settings"); // TODO: clone
}
if (device.unacquired) {
css += " unacquired";
deviceStatus = "Used in other window";
@ -49,6 +53,9 @@ const Value = (props: any): any => {
if (!device.connected) {
css += " reload-features";
deviceStatus = "Disconnected";
} else if (!device.available) {
css += " unavailable";
deviceStatus = "Unavailable";
}
if (device.remember) {
@ -59,6 +66,8 @@ const Value = (props: any): any => {
css += " trezor-t";
}
const deviceMenuButtons = deviceMenuItems.map((item, index) => {
return (
<div key={ item } className={ item } onClick={ event => onClick(event, item, device) }></div>
@ -81,9 +90,6 @@ const Value = (props: any): any => {
<span className="label">{ device.instanceLabel }</span>
<span className="status">{ deviceStatus }</span>
</div>
<div className="device-menu">
{ deviceMenuButtons }
</div>
<div className="arrow">
</div>
</div>
@ -143,8 +149,7 @@ export const DeviceSelect = (props: any): any => {
}
}
console.log("DEVSEL", props)
const disabled: boolean = (devices && devices.length <= 1 && transport && transport.type.indexOf('webusb') < 0);
const disabled: boolean = (devices.length < 1 && transport && transport.version.indexOf('webusb') < 0);
return (
<Value
@ -169,17 +174,16 @@ export class DeviceDropdown extends Component {
componentDidUpdate() {
const transport: any = this.props.connect.transport;
if (transport && transport.type.indexOf('webusb') >= 0)
if (transport && transport.version.indexOf('webusb') >= 0)
TrezorConnect.renderWebUSBButton();
}
mouseDownHandler(event: MouseEvent): void {
console.log("HANDLE DOWN!!!!", event)
let elem = event.target;
let block: boolean = false;
while (elem.parentElement) {
// if (elem.className.indexOf('aside-button') >= 0) {
if (elem.tagName.toLowerCase() === 'aside') {
if (elem.tagName.toLowerCase() === 'aside' || (elem.className && elem.className.indexOf('modal-container') >= 0)) {
block = true;
break;
}
@ -199,7 +203,7 @@ export class DeviceDropdown extends Component {
window.addEventListener('mousedown', this.mouseDownHandler, false);
// window.addEventListener('blur', this.blurHandler, false);
const transport: any = this.props.connect.transport;
if (transport && transport.type.indexOf('webusb') >= 0)
if (transport && transport.version.indexOf('webusb') >= 0)
TrezorConnect.renderWebUSBButton();
}
@ -208,16 +212,69 @@ export class DeviceDropdown extends Component {
// window.removeEventListener('blur', this.blurHandler, false);
}
onDeviceMenuClick(item, device): void {
if (item.type === 'reload') {
this.props.acquireDevice(device);
} else if (item.type === 'forget') {
// this.props.toggleDeviceDropdown(false);
this.props.forgetDevice(device);
} else if (item.type === 'clone') {
this.props.duplicateDevice(device);
} else if (item.type === 'settings') {
this.props.toggleDeviceDropdown(false);
this.props.gotoDeviceSettings(device);
}
}
render() {
const { devices, transport } = this.props.connect;
const selected = findSelectedDevice(this.props.connect);
let webUsbButton = null;
if (transport && transport.type.indexOf('webusb') >= 0) {
if (transport && transport.version.indexOf('webusb') >= 0) {
webUsbButton = <button className="trezor-webusb-button">Check for devices</button>;
}
let currentDeviceMenu = null;
if (selected.features) {
const deviceMenuItems: Array<any> = [];
if (selected.isUsedElsewhere) {
deviceMenuItems.push({ type: "reload", label: "Renew session" });
} else if (selected.featuresNeedsReload) {
deviceMenuItems.push({ type: "reload", label: "Reload device" });
}
deviceMenuItems.push({ type: "settings", label: "Device settings" });
if (selected.features.passphrase_protection && selected.connected && selected.available) {
deviceMenuItems.push({ type: "clone", label: "Clone device" });
}
if (selected.remember) {
deviceMenuItems.push({ type: "forget", label: "Forget device" });
}
const deviceMenuButtons = deviceMenuItems.map((item, index) => {
return (
<div key={ item.type } className={ item.type } onClick={ event => this.onDeviceMenuClick(item, selected) }>{ item.label}</div>
)
});
currentDeviceMenu = deviceMenuButtons.length < 1 ? null : (
<div className="device-menu">
{ deviceMenuButtons }
</div>
);
}
// const currentDeviceMenu = (
// <div className="device-menu">
// <div className="settings">Device settings</div>
// <div className="clone">Clone device</div>
// <div className="forget">Forget device</div>
// </div>
// );
const deviceList: Array<any> = devices.map((dev, index) => {
if (dev === selected) return null;
@ -227,6 +284,8 @@ export class DeviceDropdown extends Component {
deviceStatus = "Used in other window";
} else if (!dev.connected) {
deviceStatus = "Disconnected";
} else if (!dev.available) {
deviceStatus = "Unavailable";
}
if (dev.features && dev.features.major_version > 1) {
@ -245,6 +304,7 @@ export class DeviceDropdown extends Component {
return (
<section>
{ currentDeviceMenu }
{ webUsbButton }
{ deviceList }
</section>

View File

@ -5,23 +5,25 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import Aside from '../components/wallet/aside/Aside';
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
import { toggleDeviceDropdown } from '../actions/AppActions';
function mapStateToProps(state, own) {
import * as TrezorConnectActions from '../../../actions/TrezorConnectActions';
import { toggleDeviceDropdown } from '../../../actions/WalletActions';
import Aside from './Aside';
const mapStateToProps = (state, own) => {
return {
connect: state.connect,
accounts: state.accounts,
router: state.router,
deviceDropdownOpened: state.DOM.deviceDropdownOpened,
deviceDropdownOpened: state.wallet.dropdownOpened,
fiat: state.fiat,
localStorage: state.localStorage,
discovery: state.discovery
};
}
function mapDispatchToProps(dispatch) {
const mapDispatchToProps = (dispatch) => {
return {
//onAccountSelect: bindActionCreators(AccountActions.onAccountSelect, dispatch),
toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch),
@ -29,6 +31,7 @@ function mapDispatchToProps(dispatch) {
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
gotoDeviceSettings: bindActionCreators(TrezorConnectActions.gotoDeviceSettings, dispatch),
onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
};
}

View File

@ -0,0 +1,58 @@
/* @flow */
'use strict';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom';
import Header from '../common/Header';
import Footer from '../common/Footer';
import AccountTabs from './account/AccountTabs';
import AsideContainer from './aside';
import ModalContainer from '../modal';
import Notifications from '../common/Notification';
import Log from '../common/Log';
const Content = (props) => {
return (
<article>
<nav>
<Route path="/device/:device/network/:network/address/:address" component={ AccountTabs } />
</nav>
<Notifications />
<Log />
{ props.children }
<Footer />
</article>
);
}
const Wallet = (props: any): any => {
return (
<div className="app">
<Header />
<main>
<AsideContainer />
<Content>
{ props.children }
</Content>
</main>
<ModalContainer />
</div>
);
}
const mapStateToProps = (state, own) => {
return {
};
}
const mapDispatchToProps = (dispatch) => {
return { };
}
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Wallet)
);

View File

@ -11,16 +11,16 @@ import AbstractAccount from '../account/AbstractAccount';
export default class Send extends AbstractAccount {
render() {
return super.render(this.props.sendForm) || _render(this.props);
return super.render(this.props.sendForm) || _render(this.props, this.device, this.discovery, this.account, this.deviceStatusNotification);
}
}
const _render = (props: any): any => {
const _render = (props: any, device, discovery, account, deviceStatusNotification): any => {
const device = props.devices.find(device => device.state === props.sendForm.deviceState);
const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === props.sendForm.network);
const account = props.accounts.find(a => a.deviceState === props.sendForm.deviceState && a.index === props.sendForm.accountIndex && a.network === props.sendForm.network);
// const device = props.devices.find(device => device.state === props.sendForm.deviceState);
// const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === props.sendForm.network);
// const account = props.accounts.find(a => a.deviceState === props.sendForm.deviceState && a.index === props.sendForm.accountIndex && a.network === props.sendForm.network);
const addressTokens = props.tokens.filter(t => t.ethAddress === account.address);
const {
@ -87,9 +87,16 @@ const _render = (props: any): any => {
buttonLabel += ` ${total} ${ selectedCoin.symbol }`;
}
if (device && !device.connected) {
buttonLabel = 'Device is not connected';
buttonDisabled = true;
if (device) {
if (!device.connected){
buttonLabel = 'Device is not connected';
buttonDisabled = true;
} else if (!device.available) {
buttonLabel = 'Device is unavailable';
buttonDisabled = true;
}
}
let notification = null;
@ -97,9 +104,7 @@ const _render = (props: any): any => {
return (
<section className="send-form">
{ !device.connected ? (
<Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />
) : null }
{ deviceStatusNotification }
<h2>Send Ethereum or tokens</h2>
<div className="row address-input">

View File

@ -5,10 +5,10 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import SendForm from '../components/wallet/send/SendForm';
import * as SendFormActions from '../actions/SendFormActions';
import * as SendFormActions from '../../../actions/SendFormActions';
import SendForm from './SendForm';
function mapStateToProps(state, own) {
const mapStateToProps = (state, own) => {
return {
location: state.router.location,
devices: state.connect.devices,
@ -22,7 +22,7 @@ function mapStateToProps(state, own) {
};
}
function mapDispatchToProps(dispatch) {
const mapDispatchToProps = (dispatch) => {
return {
sendFormActions: bindActionCreators(SendFormActions, dispatch),
initAccount: bindActionCreators(SendFormActions.init, dispatch),
@ -31,4 +31,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SendForm);
export default connect(mapStateToProps, mapDispatchToProps)(SendForm)

View File

@ -9,27 +9,28 @@ import AbstractAccount from '../account/AbstractAccount';
import { Notification } from '../../common/Notification';
import SummaryDetails from './SummaryDetails.js';
import SummaryTokens from './SummaryTokens.js';
import { findDevice } from '../../../utils/reducerUtils';
export default class Summary extends AbstractAccount {
render() {
return super.render(this.props.summary) || _render(this.props);
console.warn("RENDER SUMMARY!", this.device, this.discovery, this)
return super.render(this.props.summary) || _render(this.props, this.device, this.discovery, this.account, this.deviceStatusNotification);
}
}
const _render = (props: any): any => {
const _render = (props: any, device, discovery, account, deviceStatusNotification): any => {
const device = props.devices.find(d => d.state === props.summary.deviceState);
const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === props.summary.network);
const account = props.accounts.find(a => a.deviceState === props.summary.deviceState && a.index === props.summary.accountIndex && a.network === props.summary.network);
//const device = props.devices.find(d => d.state === props.summary.deviceState && d.features.device_id === props.summary.deviceId);
// const device = findDevice(props.devices, props.summary.deviceState, props.summary.deviceId);
// const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === props.summary.network);
//const account = props.accounts.find(a => a.deviceState === props.summary.deviceState && a.index === props.summary.accountIndex && a.network === props.summary.network);
const tokens = props.tokens.filter(t => t.ethAddress === account.address);
return (
<section className="summary">
{ !device.connected ? (
<Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />
) : null }
{ deviceStatusNotification }
<h2 className={ `summary-header ${props.summary.network}` }>Address #{ parseInt(props.match.params.address) + 1 }</h2>

View File

@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import Summary from './Summary';
import * as SummaryActions from '../../../actions/SummaryActions';
function mapStateToProps(state, own) {
const mapStateToProps = (state, own) => {
return {
location: state.router.location,
devices: state.connect.devices,
@ -21,7 +21,7 @@ function mapStateToProps(state, own) {
};
}
function mapDispatchToProps(dispatch) {
const mapDispatchToProps = (dispatch) => {
return {
summaryActions: bindActionCreators(SummaryActions, dispatch),
initAccount: bindActionCreators(SummaryActions.init, dispatch),

View File

@ -1,23 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Acquire from '../components/wallet/Acquire';
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
function mapStateToProps(state, own) {
return {
connect: state.connect
};
}
function mapDispatchToProps(dispatch) {
return {
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Acquire);

View File

@ -1,21 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Bootloader from '../components/wallet/Bootloader';
function mapStateToProps(state, own) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Bootloader);

View File

@ -1,46 +0,0 @@
/* @flow */
'use strict';
import React from 'react';
import { Route } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import Log from '../components/common/Log';
import Notifications from '../components/common/Notification';
import Footer from '../components/common/Footer';
import AccountTabs from '../components/wallet/account/AccountTabs';
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
import * as LogActions from '../actions/LogActions';
const Article = (props) => {
return (
<article>
<nav>
<Route path="/device/:device/network/:network/address/:address" component={ AccountTabs } />
</nav>
<Notifications />
<Log />
{ props.children }
<Footer />
</article>
);
}
function mapStateToProps(state, own) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Article)
);

View File

@ -1,21 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Dashboard from '../components/wallet/Dashboard';
function mapStateToProps(state, own) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);

View File

@ -1,23 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import History from '../components/wallet/History';
import * as SendFormActions from '../actions/SendFormActions';
function mapStateToProps(state, own) {
return {
web3: state.web3.web3,
};
}
function mapDispatchToProps(dispatch) {
return {
//sendFormActions: bindActionCreators(SendFormActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(History);

View File

@ -1,31 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Receive from '../components/wallet/Receive';
import * as ReceiveActions from '../actions/ReceiveActions';
import { getAddress } from '../actions/TrezorConnectActions';
function mapStateToProps(state, own) {
return {
location: state.router.location,
devices: state.connect.devices,
accounts: state.accounts,
discovery: state.discovery,
receive: state.receive
};
}
function mapDispatchToProps(dispatch) {
return {
initAccount: bindActionCreators(ReceiveActions.init, dispatch),
updateAccount: bindActionCreators(ReceiveActions.update, dispatch),
disposeAccount: bindActionCreators(ReceiveActions.dispose, dispatch),
showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Receive);

View File

@ -1,20 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Settings from '../components/wallet/Settings';
function mapStateToProps(state, own) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Settings);

View File

@ -1,20 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import SignVerify from '../components/wallet/SignVerify';
function mapStateToProps(state, own) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignVerify);

View File

@ -1,31 +0,0 @@
/* @flow */
'use strict';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import TopNavigation from '../components/TopNavigation';
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
import { resizeAppContainer, toggleDeviceDropdown } from '../actions/AppActions';
function mapStateToProps(state, own) {
return {
connect: state.connect
};
}
function mapDispatchToProps(dispatch) {
return {
toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch),
resizeAppContainer: bindActionCreators(resizeAppContainer, dispatch),
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
};
}
// export default connect(mapStateToProps, mapDispatchToProps)(TopNavigation);
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(TopNavigation)
);

View File

@ -1,40 +0,0 @@
/* @flow */
'use strict';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import Header from '../components/common/Header';
import AsideContainer from './AsideContainer';
import ContentContainer from './ContentContainer';
import ModalContainer from '../components/modal/ModalContainer';
const Wallet = (props: any): any => {
return (
<div className="app">
<Header />
<main>
<AsideContainer />
<ContentContainer>
{ props.children }
</ContentContainer>
</main>
<ModalContainer />
</div>
);
}
function mapStateToProps(state, own) {
return {
};
}
function mapDispatchToProps(dispatch) {
return { };
}
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Wallet)
);

View File

@ -1,19 +0,0 @@
/* @flow */
'use strict';
// wrapper layouts
export { default as LandingPageContainer } from './LandingPageContainer';
export { default as WalletContainer } from './WalletContainer';
// wallet sections
export { default as AcquireContainer } from './AcquireContainer';
export { default as BootloaderContainer } from './BootloaderContainer';
export { default as DashboardContainer } from './DashboardContainer';
export { default as HistoryContainer } from './HistoryContainer';
export { default as SendFormContainer } from './SendFormContainer';
export { default as ReceiveContainer } from './ReceiveContainer';
export { default as SignVerifyContainer } from './SignVerifyContainer';
export { default as SettingsContainer } from './SettingsContainer';

View File

@ -5,7 +5,7 @@ import React from 'react';
import { render } from 'react-dom';
import store from './store';
import router from './router';
import { onResize, onBeforeUnload } from './actions/AppActions';
import { onBeforeUnload } from './actions/WalletActions';
import Raven from 'raven-js';

View File

@ -1,49 +0,0 @@
/* @flow */
'use strict';
import { ON_RESIZE, TOGGLE_DEVICE_DROPDOWN, RESIZE_CONTAINER } from '../actions/AppActions';
import * as WEB3 from '../actions/constants/Web3';
const WIDTH: number = 1080;
const HEIGHT: number = 1920;
const initialState: Object = {
orginalWidth: WIDTH,
orginalHeight: HEIGHT,
width: window.innerWidth,
height: window.innerHeight,
scale: Math.min(window.innerWidth / WIDTH, window.innerHeight / HEIGHT),
coinDropdownOpened: false,
deviceDropdownOpened: false,
initialized: false,
landingPage: true,
};
export default function DOM(state: Object = initialState, action: Object): any {
switch(action.type) {
case ON_RESIZE :
return {
...state,
scale: Math.min(window.innerWidth / WIDTH, window.innerHeight / HEIGHT),
}
case RESIZE_CONTAINER :
return {
...state,
coinDropdownOpened: action.opened
}
case TOGGLE_DEVICE_DROPDOWN :
return {
...state,
deviceDropdownOpened: action.opened
}
case WEB3.READY :
return {
...state,
initialized: true
}
default:
return state;
}
}

View File

@ -2,7 +2,6 @@
'use strict';
import { UI, DEVICE } from 'trezor-connect';
import * as ACTIONS from '../actions';
import * as RECEIVE from '../actions/constants/receive';
import * as MODAL from '../actions/constants/Modal';
import * as CONNECT from '../actions/constants/TrezorConnect';
@ -10,12 +9,14 @@ import * as CONNECT from '../actions/constants/TrezorConnect';
type ModalState = {
opened: boolean;
device: any;
instances: Array<any>;
windowType: ?string;
}
const initialState: ModalState = {
opened: false,
device: null,
instances: null,
windowType: null
};
@ -26,6 +27,7 @@ export default function modal(state: ModalState = initialState, action: any): an
case RECEIVE.REQUEST_UNVERIFIED :
return {
...state,
device: action.device,
opened: true,
windowType: action.type
}
@ -36,6 +38,7 @@ export default function modal(state: ModalState = initialState, action: any): an
return {
...state,
device: action.device,
instances: action.instances,
opened: true,
windowType: action.type
};
@ -92,7 +95,7 @@ export default function modal(state: ModalState = initialState, action: any): an
}
case UI.CLOSE_UI_WINDOW :
case ACTIONS.CLOSE_MODAL :
case MODAL.CLOSE :
case CONNECT.FORGET :
case CONNECT.FORGET_SINGLE :

View File

@ -3,6 +3,7 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import * as NOTIFICATION from '../actions/constants/notification';
import { DEVICE } from 'trezor-connect';
type NotificationAction = {
label: string;
@ -11,6 +12,7 @@ type NotificationAction = {
type NotificationEntry = {
+id: ?string;
+devicePath: ?string;
+type: string;
+title: string;
+message: string;
@ -33,6 +35,7 @@ const addNotification = (state: Array<NotificationEntry>, payload: any): Array<N
const newState: Array<NotificationEntry> = state.filter(e => !e.cancelable);
newState.push({
id: payload.id,
devicePath: payload.devicePath,
type: payload.type,
title: payload.title.toString(),
message: payload.message.toString(),
@ -46,9 +49,11 @@ const addNotification = (state: Array<NotificationEntry>, payload: any): Array<N
const closeNotification = (state: Array<NotificationEntry>, payload: any): Array<NotificationEntry> => {
if (payload && typeof payload.id === 'string') {
return state.filter(e => e.id !== payload.id);
return state.filter(entry => entry.id !== payload.id);
} else if (payload && typeof payload.devicePath === 'string') {
return state.filter(entry => entry.devicePath !== payload.devicePath);
} else {
return state.filter(e => !e.cancelable);
return state.filter(entry => !entry.cancelable);
}
}
@ -62,6 +67,9 @@ export default function notification(state: Array<NotificationEntry> = initialSt
case NOTIFICATION.CLOSE :
return closeNotification(state, action.payload);
case DEVICE.DISCONNECT :
return state.filter(entry => entry.devicePath !== action.device.path);
default:
return state;
}

View File

@ -5,6 +5,8 @@ import * as RECEIVE from '../actions/constants/receive';
export type State = {
+deviceState: ?string;
+deviceId: ?string;
+deviceInstance: ?string;
+accountIndex: ?number;
+network: ?string;
location: string;
@ -14,6 +16,8 @@ export type State = {
export const initialState: State = {
deviceState: null,
deviceId: null,
deviceInstance: null,
accountIndex: null,
network: null,
location: '',

View File

@ -11,6 +11,8 @@ import { getFeeLevels } from '../actions/SendFormActions';
export type State = {
+deviceState: ?string;
+deviceId: ?string;
+deviceInstance: ?string;
+accountIndex: number;
+network: string;
+coinSymbol: string;
@ -51,6 +53,8 @@ export type FeeLevel = {
export const initialState: State = {
deviceState: null,
deviceId: null,
deviceInstance: null,
accountIndex: 0,
network: '',
coinSymbol: '',
@ -83,11 +87,11 @@ export const initialState: State = {
const onGasPriceUpdated = (state: State, action: any): State => {
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const newPrice = getRandomInt(10, 50).toString();
//const newPrice = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei');
// function getRandomInt(min, max) {
// return Math.floor(Math.random() * (max - min + 1)) + min;
// }
// const newPrice = getRandomInt(10, 50).toString();
const newPrice = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei');
if (action.network === state.network && newPrice !== state.recommendedGasPrice) {
const newState: State = { ...state };
if (!state.untouched) {

View File

@ -5,6 +5,8 @@ import * as SUMMARY from '../actions/constants/summary';
export type State = {
+deviceState: ?string;
+deviceId: ?string;
+deviceInstance: ?string;
+accountIndex: ?number;
+network: ?string;
location: string;
@ -15,6 +17,8 @@ export type State = {
export const initialState: State = {
deviceState: null,
deviceId: null,
deviceInstance: null,
accountIndex: null,
network: null,
location: '',

View File

@ -7,6 +7,7 @@ import * as CONNECT from '../actions/constants/TrezorConnect';
export type TrezorDevice = {
remember: boolean;
connected: boolean;
available: boolean; // device cannot be used because of features.passphrase_protection is different then expected (saved)
path: string;
+label: string;
+state: string;
@ -58,6 +59,8 @@ export const findSelectedDevice = (state: State): ?TrezorDevice => {
});
}
export const isSavedDevice = (state: State, device: any): ?Array<TrezorDevice> => {
const selected: ?SelectedDevice = state.selectedDevice;
if (!selected) return null;
@ -73,17 +76,36 @@ export const isSavedDevice = (state: State, device: any): ?Array<TrezorDevice> =
}
const mergeDevices = (current: TrezorDevice, upcoming: Object): TrezorDevice => {
// do not merge if passphrase protection was changed
// if (upcoming.features && current.features) {
// if (upcoming.features.passphrase_protection !== current.features.passphrase_protection) {
// // device settings has been changed, reset state
// // dev.state = null;
// // console.log("RESTETTTT STATE!");
// }
// }
let instanceLabel = current.instanceLabel;
if (upcoming.label !== current.label) {
instanceLabel = upcoming.label
if (typeof current.instance === 'number') {
instanceLabel += ` (${current.instance})`;
}
}
const dev: TrezorDevice = {
// ...current,
...upcoming,
// make sure that instance specific variables will not be overridden
connected: typeof upcoming.connected === 'boolean' ? upcoming.connected : current.connected,
available: typeof upcoming.available === 'boolean' ? upcoming.available : current.available,
remember: typeof upcoming.remember === 'boolean' ? upcoming.remember : current.remember,
instance: current.instance,
instanceLabel: current.instanceLabel,
instanceLabel,
state: current.state,
acquiring: typeof upcoming.acquiring === 'boolean' ? upcoming.acquiring : current.acquiring,
ts: new Date().getTime(),
ts: typeof upcoming.ts === 'number' ? upcoming.ts : current.ts,
}
if (upcoming.unacquired && current.state) {
@ -94,18 +116,11 @@ const mergeDevices = (current: TrezorDevice, upcoming: Object): TrezorDevice =>
} else if (!upcoming.unacquired && current.unacquired) {
dev.instanceLabel = upcoming.label;
if (typeof dev.instance === 'number') {
dev.instanceLabel = `${upcoming.label} #${dev.instance}`;
dev.instanceLabel = `${upcoming.label} TODO:(${dev.instance})`;
}
}
if (upcoming.features && current.features) {
console.log("CZEKIN PASS PROT");
if (upcoming.features.passphrase_protection !== current.features.passphrase_protection) {
// device settings has been changed, reset state
dev.state = null;
console.log("RESTETTTT STATE!");
}
}
return dev;
}
@ -132,7 +147,40 @@ const addDevice = (state: State, device: Object): State => {
if (affectedDevices.length > 0 ) {
// replace existing values
const changedDevices: Array<TrezorDevice> = affectedDevices.map(d => mergeDevices(d, { ...device, connected: true} ));
// const changedDevices: Array<TrezorDevice> = affectedDevices.map(d => mergeDevices(d, { ...device, connected: true} ));
let cloneInstance: number = 1;
const changedDevices: Array<TrezorDevice> = affectedDevices.map(d => {
if (d.features.passphrase_protection === device.features.passphrase_protection) {
cloneInstance = 0;
return mergeDevices(d, { ...device, connected: true, available: true } );
} else {
if (d.instance && cloneInstance > 0) {
cloneInstance = d.instance + 1;
}
return mergeDevices(d, { ...d, connected: true, available: false } );
// return d;
}
});
if (cloneInstance > 0) {
// TODO: instance should be calculated form affectedDevice
const instance = cloneInstance; //new Date().getTime();
const newDevice: TrezorDevice = {
...device,
acquiring: false,
remember: false,
connected: true,
available: true,
path: device.path,
label: device.label,
state: null,
instance,
instanceLabel: `${device.label} (${instance})`,
ts: new Date().getTime(),
}
changedDevices.push(newDevice);
}
newState.devices = otherDevices.concat(changedDevices);
} else {
@ -142,12 +190,13 @@ const addDevice = (state: State, device: Object): State => {
acquiring: false,
remember: false,
connected: true,
available: true,
path: device.path,
label: device.label,
state: null,
// instance: 0,
instanceLabel: device.label,
ts: 0,
ts: new Date().getTime(),
}
newState.devices.push(newDevice);
@ -184,11 +233,15 @@ const changeDevice = (state: State, device: Object): State => {
let affectedDevices: Array<TrezorDevice> = [];
let otherDevices: Array<TrezorDevice> = [];
if (device.features) {
affectedDevices = state.devices.filter(d => (d.features && d.features.device_id === device.features.device_id) || (d.path.length > 0 && d.path === device.path) );
affectedDevices = state.devices.filter(d =>
(d.features && d.features.device_id === device.features.device_id && d.features.passphrase_protection === device.features.passphrase_protection) ||
(d.path.length > 0 && d.path === device.path)
);
otherDevices = state.devices.filter(d => affectedDevices.indexOf(d) === -1);
} else {
affectedDevices = state.devices.filter(d => d.path === device.path);
otherDevices = state.devices.filter(d => d.path !== device.path);
affectedDevices = state.devices.filter(d => !d.features && d.path === device.path);
otherDevices = state.devices.filter(d => affectedDevices.indexOf(d) === -1);
// otherDevices = state.devices.filter(d => d.path !== device.path);
}
if (affectedDevices.length > 0) {
@ -226,13 +279,14 @@ const changeDevice = (state: State, device: Object): State => {
const disconnectDevice = (state: State, device: Object): State => {
const newState: State = { ...state };
const affectedDevices: Array<TrezorDevice> = state.devices.filter(d => d.path === device.path);
const affectedDevices: Array<TrezorDevice> = state.devices.filter(d => d.path === device.path || (d.features && device.features && d.features.device_id === device.features.device_id));
const otherDevices: Array<TrezorDevice> = state.devices.filter(d => affectedDevices.indexOf(d) === -1);
if (affectedDevices.length > 0) {
const acquiredDevices = affectedDevices.filter(d => !d.unacquired && d.state);
newState.devices = otherDevices.concat( acquiredDevices.map(d => {
d.connected = false;
d.available = false;
d.isUsedElsewhere = false;
d.featuresNeedsReload = false;
d.acquiring = false;
@ -261,7 +315,7 @@ const forgetDevice = (state: State, action: any): State => {
} else {
// remove all instances after disconnect (remember request declined)
//newState.devices = state.devices.filter(d => d.path !== action.device.path);
newState.devices = state.devices.filter(d => (d.features && d.features.device_id !== action.device.features.device_id) || (!d.features && d.path !== action.device.path));
newState.devices = state.devices.filter(d => d.remember || (d.features && d.features.device_id !== action.device.features.device_id) || (!d.features && d.path !== action.device.path));
}
return newState;
@ -272,6 +326,7 @@ const devicesFromLocalStorage = (devices: Array<any>): Array<TrezorDevice> => {
return {
...d,
connected: false,
available: false,
path: '',
acquiring: false,
featuresNeedsReload: false,
@ -282,24 +337,33 @@ const devicesFromLocalStorage = (devices: Array<any>): Array<TrezorDevice> => {
const duplicate = (state: State, device: any): State => {
const newState: State = { ...state };
const affectedDevices: Array<TrezorDevice> = state.devices.filter(d => d.path === device.path);
const affectedDevices: Array<TrezorDevice> = state.devices.filter(d => d.features.device_id === device.features.device_id);
const instance = affectedDevices.reduce((inst, dev) => {
console.warn("REDUC", inst, dev);
return dev.instance ? dev.instance + 1 : inst;
}, 1);
console.warn("NEEEW INST", instance);
// if (affectedDevices.length > 0) {
const newDevice: TrezorDevice = {
...device,
state: null,
remember: device.remember,
acquiring: false,
remember: false,
connected: device.connected,
available: device.available,
path: device.path,
label: device.label,
instance: new Date().getTime(),
instanceLabel: device.instanceLabel,
ts: 0,
state: null,
instance,
instanceLabel: `${device.label} (${instance})`,
ts: new Date().getTime(),
}
newState.devices.push(newDevice);
newState.selectedDevice = {
id: newDevice.features.device_id,
instance: newDevice.instance
instance
}
//}
@ -307,6 +371,19 @@ const duplicate = (state: State, device: any): State => {
}
const onSelectDevice = (state: State, action: any): State => {
const newState: State = { ...state };
newState.selectedDevice = action.payload;
const selected = findSelectedDevice(newState);
if (selected) {
selected.ts = new Date().getTime();
console.warn("APDEJT SELECTED!", selected.instanceLabel, selected.ts)
}
return newState;
}
export default function connect(state: State = initialState, action: any): any {
@ -323,10 +400,11 @@ export default function connect(state: State = initialState, action: any): any {
case CONNECT.SELECT_DEVICE :
return {
...state,
selectedDevice: action.payload
}
return onSelectDevice(state, action);
// return {
// ...state,
// selectedDevice: action.payload
// }
case CONNECT.INITIALIZATION_ERROR :
return {
@ -378,7 +456,7 @@ export default function connect(state: State = initialState, action: any): any {
return disconnectDevice(state, action.device);
case DEVICE.CHANGED :
return changeDevice(state, { ...action.device, connected: true });
return changeDevice(state, { ...action.device, connected: true, available: true });
default:
return state;

View File

@ -1,25 +1,43 @@
/* @flow */
'use strict';
import { ON_RESIZE, TOGGLE_DEVICE_DROPDOWN, RESIZE_CONTAINER } from '../actions/AppActions';
import * as WEB3 from '../actions/constants/Web3';
const WIDTH: number = 1080;
const HEIGHT: number = 1920;
import * as WALLET from '../actions/constants/wallet';
type State = {
network: string;
device: string;
ready: boolean;
dropdownOpened: boolean;
initialUrl: boolean;
}
const initialState: Object = {
ready: false,
dropdownOpened: false,
initialParams: null,
initialPathname: null,
};
export default function wallet(state: Object = initialState, action: Object): any {
switch(action.type) {
case WALLET.SET_INITIAL_URL :
return {
...state,
initialParams: action.params,
initialPathname: action.pathname
}
case WEB3.READY :
return {
...state,
ready: true
}
case WALLET.TOGGLE_DEVICE_DROPDOWN :
return {
...state,
dropdownOpened: action.opened
}
default:
return state;

View File

@ -4,7 +4,6 @@
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import DOM from './AppReducer.js';
import log from './LogReducer.js';
import localStorage from './LocalStorageReducer.js';
import connect from './TrezorConnectReducer.js';
@ -12,7 +11,7 @@ import notifications from './NotificationReducer.js';
import modal from './ModalReducer.js';
import web3 from './Web3Reducer.js';
import accounts from './AccountsReducer.js';
import accountDetail from './AccountDetailReducer.js';
import abstractAccount from './AbstractAccountReducer.js';
import sendForm from './SendFormReducer.js';
import receive from './ReceiveReducer.js';
import summary from './SummaryReducer.js';
@ -20,10 +19,10 @@ import tokens from './TokensReducer.js';
import discovery from './DiscoveryReducer.js';
import pending from './PendingTxReducer.js';
import fiat from './FiatRateReducer.js';
import wallet from './WalletReducer.js';
export default combineReducers({
router: routerReducer,
DOM,
log,
localStorage,
connect,
@ -31,12 +30,13 @@ export default combineReducers({
modal,
web3,
accounts,
accountDetail,
abstractAccount,
sendForm,
receive,
summary,
tokens,
discovery,
pending,
fiat
fiat,
wallet
});

View File

@ -7,23 +7,18 @@ import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import store, { history } from '../store';
import {
LandingPageContainer,
WalletContainer,
import LandingPageContainer from '../components/landing';
import WalletContainer from '../components/wallet';
import BootloaderContainer from '../components/wallet/Bootloader';
import AcquireContainer from '../components/wallet/Acquire';
AcquireContainer,
BootloaderContainer,
DashboardContainer,
HistoryContainer,
SendFormContainer,
ReceiveContainer,
SignVerifyContainer,
SettingsContainer,
} from '../containers';
import SummaryContainer from '../components/wallet/summary/SummaryContainer';
import DashboardContainer from '../components/wallet/Dashboard';
import SummaryContainer from '../components/wallet/summary';
import SendFormContainer from '../components/wallet/send';
import ReceiveContainer from '../components/wallet/Receive';
import SignVerifyContainer from '../components/wallet/SignVerify';
import DeviceSettingsContainer from '../components/wallet/DeviceSettings';
import WalletSettingsContainer from '../components/wallet/WalletSettings';
export default (
<Provider store={ store }>
@ -34,20 +29,19 @@ export default (
<Route exact path="/import" component={ LandingPageContainer } />
<Route>
<WalletContainer>
<Route exact path="/settings" component={ WalletSettingsContainer } />
<Route exact path="/device/:device/" component={ DashboardContainer } />
<Route exact path="/device/:device/network/:network" component={ DashboardContainer } />
<Route exact path="/device/:device/acquire" component={ AcquireContainer } />
<Route exact path="/device/:device/bootloader" component={ BootloaderContainer } />
<Route exact path="/device/:device/settings" component={ DeviceSettingsContainer } />
<Route exact path="/device/:device/network/:network/address/:address" component={ SummaryContainer } />
<Route path="/device/:device/network/:network/address/:address/send" component={ SendFormContainer } />
<Route path="/device/:device/network/:network/address/:address/send/override" component={ SendFormContainer } />
<Route path="/device/:device/network/:network/address/:address/receive" component={ ReceiveContainer } />
<Route path="/device/:device/network/:network/address/:address/signverify" component={ SignVerifyContainer } />
{/* <Route path="/device/:device/address/:address/history" component={ HistoryContainer } /> */}
</WalletContainer>
</Route>
<Route path="/settings" component={ SettingsContainer } />
</Switch>
</ConnectedRouter>
</Provider>

View File

@ -1,174 +0,0 @@
/* @flo */
'use strict';
//http://ropsten.etherscan.io/api?module=account&action=txlist&address=0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad&startblock=0&endblock=99999999&sort=asc&apikey=89IZG471H8ITVXY377I2QWJIT2D62DGG9Z
import { LOCATION_CHANGE, push } from 'react-router-redux';
import { httpRequest } from '../utils/networkUtils';
import * as ACTIONS from '../actions/index';
import { getBalance } from '../actions/Web3Actions';
const API_KEY: string = '89IZG471H8ITVXY377I2QWJIT2D62DGG9Z';
//const API: string = `http://ropsten.etherscan.io/api?module=account&action=txlist&startblock=0&endblock=99999999&sort=asc&apikey=89IZG471H8ITVXY377I2QWJIT2D62DGG9Z`;
const API: string = `http://ropsten.etherscan.io/api?module=account&action=txlist&startblock=0&endblock=99999999&sort=desc&apikey=${API_KEY}`;
export const getTransactionHistory = async (address: string): Promise<Array<any>> => {
const json = await httpRequest(`${API}&address=${address}`);
return json;
}
const getTransactionStatus = async (txid: string): Promise<Array<any>> => {
//https://ropsten.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1&apikey=YourApiKeyToken
const url: string = `https://ropsten.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&apikey=${API_KEY}`
//const url: string = `https://ropsten.etherscan.io/api?module=transaction&action=getstatus&apikey=${API_KEY}`
const json = await httpRequest(`${url}&txhash=${txid}`);
return json;
}
const getTokenHistory = async (tokenAddress, address) => {
// 0x58cda554935e4a1f2acbe15f8757400af275e084
// 0x000000000000000000000000 + 98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad
let url: string = 'https://ropsten.etherscan.io/api?module=logs&action=getLogs';
url += '&fromBlock=0&toBlock=latest';
url += `&address=${tokenAddress}`;
url += '&topic0=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
url += `&topic2=${ address }`;
// https://api.etherscan.io/api?module=logs&action=getLogs
// &fromBlock=0
// &toBlock=latest
// &address=[Token Contract Address]
// &topic0=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
// &topic1=[From Address, padded to 32 bytes - optional]
// &topic2=[To Address, padded to 32 bytes - optional]
console.log("TOKENURL", url);
const json = await httpRequest(url);
return json;
}
export const loadTokenHistory = (address): Promise<any> => {
// https://ropsten.etherscan.io/apis#logs
return async (dispatch, getState): Promise<any> => {
const tkn = '0x58cda554935e4a1f2acbe15f8757400af275e084';
const ad = '0x00000000000000000000000098ead4bd2fbbb0cf0b49459aa0510ef53faa6cad';
const incoming = await getTokenHistory(tkn, ad);
console.log("TOKEN HIST!", JSON.parse(incoming) );
}
}
export const loadTransactionStatus = (txid): Promise<any> => {
return async (dispatch, getState): Promise<any> => {
const json = await getTransactionStatus(txid);
const status = JSON.parse(json);
console.error("TXSTAT", status)
if (status.result.blockHash) {
// find address with pending tx
const { addresses } = getState().addresses;
for (let addr of addresses) {
if (addr.findPendingTx(txid)) {
dispatch( getBalance(addr) );
const txType = status.result.from === addr.address ? 'out' : 'in';
const txAddress = txType === 'out' ? status.result.to : status.result.from;
dispatch({
type: ACTIONS.ADDRESS_ADD_TO_HISTORY,
address: addr,
entry: {
txid: status.result.hash,
type: txType,
timestamp: '0',
address: txAddress,
value: status.result.value
}
});
//dispatch( loadHistory(addr, 3000) );
}
}
dispatch({
type: ACTIONS.TX_STATUS_OK,
txid
});
}
// if (status.status === "1" && status.message === "OK") {
// if (status.result.isError === "0") {
// dispatch({
// type: ACTIONS.TX_STATUS_OK,
// txid
// });
// } else {
// dispatch({
// type: ACTIONS.TX_STATUS_ERROR,
// txid,
// error: status.errDescription
// });
// }
// } else {
// dispatch({
// type: ACTIONS.TX_STATUS_UNKNOWN,
// txid,
// status
// });
// }
}
}
export const loadHistory = (address, delay): void => {
return async (dispatch, getState) => {
// const json = await getTransactionStatus('0x2113e578497f3486944566e2417b5ac3b31d7e76f71557ae0626e2a6fe191e58');
// console.log("JSON!", json)
/*
if (delay) {
console.warn("-----PRELOAD with delay", address)
await new Promise(resolve => {
setTimeout(resolve, delay);
})
}
const history = await getTransactionHistory(address.address);
dispatch({
type: ACTIONS.ADDRESS_SET_HISTORY,
address,
history
});
*/
}
}
const EtherscanService = store => next => action => {
next(action);
if (action.type === LOCATION_CHANGE) {
const { location } = store.getState().router;
const { addresses } = store.getState().addresses;
if (location) {
const parts = location.pathname.split("/");
if (parts.length === 3 && parts[1] === "address") {
const addressId = parseInt(parts[2]);
if (!isNaN(addressId) && addresses[addressId]) {
//store.dispatch( loadHistory( addresses[addressId] ) );
//store.dispatch( loadTokenHistory( addresses[addressId] ) );
}
//console.error("ETH", parts, "id", parts.length, parts.length === 3 && parts[1] === "address");
}
}
}
};
export default EtherscanService;

View File

@ -111,6 +111,7 @@ const LocalStorageService = (store: any) => (next: any) => (action: any) => {
case DEVICE.CHANGED :
case DEVICE.DISCONNECT :
case CONNECT.AUTH_DEVICE :
case CONNECT.SELECT_DEVICE :
save(store.dispatch, store.getState);
//store.dispatch( LocalStorageActions.save('devices', JSON.stringify( store.getState().connect.devices.filter(d => d.remember === true && !d.unacquired) ) ) );
// store.dispatch( LocalStorageActions.save('selectedDevice', JSON.stringify( store.getState().connect.selectedDevice ) ) );

View File

@ -4,8 +4,9 @@
import pathToRegexp from 'path-to-regexp';
import { DEVICE } from 'trezor-connect';
import { LOCATION_CHANGE, push, replace } from 'react-router-redux';
import { ON_BEFORE_UNLOAD } from '../actions/AppActions';
import * as CONNECT from '../actions/constants/TrezorConnect';
import * as WALLET from '../actions/constants/wallet';
import * as NotificationActions from '../actions/NotificationActions';
/**
* Middleware used for init application and managing router path.
@ -66,25 +67,25 @@ let __unloading: boolean = false;
const RouterService = (store: any) => (next: any) => (action: any) => {
if (action.type === ON_BEFORE_UNLOAD) {
if (action.type === WALLET.ON_BEFORE_UNLOAD) {
__unloading = true;
} else if (action.type === LOCATION_CHANGE && !__unloading) {
const { location } = store.getState().router;
const web3 = store.getState().web3;
const { devices, error } = store.getState().connect;
const { opened } = store.getState().modal;
const isModalOpened: boolean = store.getState().modal.opened;
let redirectPath: ?string;
// first (initial) event after app loads
// first event after application loads
if (!location) {
action.payload.state = {
initURL: action.payload.pathname,
initSearch: action.payload.search
}
store.dispatch({
type: WALLET.SET_INITIAL_URL,
pathname: action.payload.pathname,
params: pathToParams(action.payload.pathname)
});
// check if there are initial parameters in url (network/ device / account)
if (action.payload.search.length > 0) {
// save it in WalletReducer, after device detection will redirect to this request
redirectPath = '/';
@ -98,7 +99,7 @@ const RouterService = (store: any) => (next: any) => (action: any) => {
// if web3 wasn't initialized yet or there are no devices attached or initialization error occurs
const landingPage: boolean = web3.length < 1 || devices.length < 1 || error;
if (opened && action.payload.pathname !== location.pathname) {
if (isModalOpened && action.payload.pathname !== location.pathname) {
redirectPath = location.pathname;
console.warn("Modal still opened");
} else if (landingPage) {
@ -113,7 +114,7 @@ const RouterService = (store: any) => (next: any) => (action: any) => {
// TODO: switch to first device?
// redirectPath = `/device/${ devices[0].path }`;
redirectPath = location.pathname;
} else {
} else if (requestedParams.device) {
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
store.dispatch({
@ -147,6 +148,7 @@ const RouterService = (store: any) => (next: any) => (action: any) => {
action.payload.params = requestedParams;
}
store.dispatch( NotificationActions.clear(currentParams, requestedParams) );
}
// Pass all actions through by default

View File

@ -11,13 +11,14 @@ import * as WEB3 from '../actions/constants/Web3';
import * as STORAGE from '../actions/constants/LocalStorage';
import * as CONNECT from '../actions/constants/TrezorConnect';
import * as NOTIFICATION from '../actions/constants/notification';
import * as ACTIONS from '../actions';
import * as MODAL from '../actions/constants/Modal';
const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
const prevState = store.getState().connect;
const prevModalState = store.getState().modal;
const prevRouterState = store.getState().router;
next(action);
@ -25,8 +26,14 @@ const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
store.dispatch( TrezorConnectActions.init() );
} else if (action.type === TRANSPORT.ERROR) {
store.dispatch( push('/') );
} else if (action.type === TRANSPORT.START && store.getState().web3.length > 0 && prevState.devices.length > 0) {
// TODO: check if modal is open
// store.dispatch( push('/') );
// } else if (action.type === TRANSPORT.START && store.getState().web3.length > 0 && prevState.devices.length > 0) {
// store.dispatch( TrezorConnectActions.postInit() );
} else if (action.type === TRANSPORT.START && store.getState().web3.length < 1) {
//store.dispatch( TrezorConnectActions.postInit() );
store.dispatch( initWeb3() );
} else if (action.type === WEB3.READY) {
store.dispatch( TrezorConnectActions.postInit() );
} else if (action.type === TRANSPORT.UNREADABLE) {
store.dispatch({
@ -34,22 +41,16 @@ const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
payload: {
type: 'error',
title: 'Unreadable HID device',
message: 'What to do?',
message: '',
cancelable: true,
}
})
} else if (action.type === WEB3.READY) {
store.dispatch( TrezorConnectActions.postInit() );
});
} else if (action.type === DEVICE.DISCONNECT) {
store.dispatch( TrezorConnectActions.deviceDisconnect(action.device) );
} else if (action.type === CONNECT.REMEMBER_REQUEST) {
// TODO:
// TODO: 2 modals at once
if (prevModalState.opened && prevModalState.windowType === CONNECT.REMEMBER_REQUEST) {
store.dispatch({
type: CONNECT.FORGET,
device: store.getState().modal.device
@ -82,6 +83,7 @@ const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
// we need to change route
if (prevState.selectedDevice) {
if (!action.device.unacquired && action.device.path === prevState.selectedDevice.id) {
console.warn("TODO: here! better")
store.dispatch( TrezorConnectActions.onSelectDevice(action.device) );
}
}
@ -96,7 +98,7 @@ const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
if (modal.opened && modal.windowType === CONNECT.REMEMBER_REQUEST) {
if (action.device.features && modal.device.features.device_id === action.device.features.device_id) {
store.dispatch({
type: ACTIONS.CLOSE_MODAL,
type: MODAL.CLOSE,
});
} else {
store.dispatch({
@ -113,6 +115,7 @@ const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
store.dispatch( TrezorConnectActions.onDuplicateDevice() );
} else if (action.type === DEVICE.ACQUIRED || action.type === CONNECT.SELECT_DEVICE) {
console.warn("here!!!!!", action.type)
store.dispatch( TrezorConnectActions.getSelectedDeviceState() );
} else if (action.type === CONNECT.COIN_CHANGED) {

View File

@ -1,238 +0,0 @@
/* @flow */
'use strict';
import Web3 from 'web3';
import { LOCATION_CHANGE } from 'react-router-redux';
import * as ACTIONS from '../actions/index';
import { getBalance, getGasPrice, getTransactionReceipt } from '../actions/Web3Actions';
import { loadTransactionStatus } from './EtherscanService';
import BigNumber from 'bignumber.js';
let web3: Web3;
// export const getGasPrice = (): Promise<BigNumber> => {
// return (dispatch, getState) => {
// web3.eth.getGasPrice((error, gasPrice) => {
// if (!error) {
// dispatch({
// type: 'update_gas',
// gasPrice: web3.fromWei(gasPrice.toString(), 'gwei')
// })
// }
// });
// }
// }
const Web3Service = store => next => action => {
next(action);
switch (action.type) {
case 'WEB_2_START' :
if (web3) break;
//web3 = new Web3(window.web3.currentProvider);
//web3 = new Web3(new Web3.providers.HttpProvider("https://api.myetherapi.com/rop"));
web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io2/QGyVKozSUEh2YhL4s2G4"));
//web3 = new Web3( new Web3.providers.HttpProvider("ws://34.230.234.51:30303") );
/*store.dispatch( getGasPrice() );
const latestBlockFilter = web3.eth.filter('latest');
latestBlockFilter.watch((error, blockHash) => {
const { addresses, pendingTxs } = store.getState().addresses;
for (const addr of addresses) {
store.dispatch( getBalance(addr) );
}
store.dispatch( getGasPrice() );
if (pendingTxs.length > 0) {
for (const tx of pendingTxs) {
store.dispatch( getTransactionReceipt(tx) );
}
}
});*/
// store.dispatch({
// type: 'web3__init',
// web3
// });
// store.dispatch({
// type: 'update_gas',
// gasPrice: web3.fromWei(web3.eth.gasPrice, 'gwei')
// })
/*
{
"dd62ed3e": "allowance(address,address)",
"095ea7b3": "approve(address,uint256)",
"cae9ca51": "approveAndCall(address,uint256,bytes)",
"70a08231": "balanceOf(address)",
"313ce567": "decimals()",
"06fdde03": "name()",
"95d89b41": "symbol()",
"18160ddd": "totalSupply()",
"a9059cbb": "transfer(address,uint256)",
"23b872dd": "transferFrom(address,address,uint256)",
"54fd4d50": "version()"
}*/
// var balanceHex = "06fdde03"; // I believe this is the hex for balance
// var contractAddress = "0x58cda554935e4a1f2acbe15f8757400af275e084";
// var userAddress = "0x5DBB9793537515398A1176d365b636A5321D9e39";
// var balanceCall = getDataObj(contractAddress, balanceHex);
// var balance = web3.eth.call(balanceCall);
const abiArray = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":false,"stateMutability":"nonpayable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}];
//const contr = web3.eth.contract(abiArray, '0x58cda554935e4a1f2acbe15f8757400af275e084');
const contr = web3.eth.contract(abiArray).at('0x58cda554935e4a1f2acbe15f8757400af275e084');
console.log("contr", contr );
contr.name.call((e,r) => {
console.log("nameeeee", e, r)
})
contr.symbol.call((e,r) => {
console.log("symboll", e, r)
})
//console.log( const.name )
// contr.balanceOf('0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad', (e, r) => {
// console.warn('contrR', e, r.toString(10));
// });
let cntrData = contr.transfer.getData("0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad", 1, {
from: "0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad",
gasLimit: 36158,
gasPrice: "0x0ee6b28000"
})
console.log("contr", cntrData);
// const data = contr.transferFrom(
// '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
// '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
// 1
// );
// const data = contr.transferFrom(
// '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
// {
// from: '0x7314e0f1c0e28474bdb6be3e2c3e0453255188f8',
// value: 1
// }
// );
// const data = contr.transferFrom(
// '0x00000000000000000000000098ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
// '0x000000000000000000000000a738ea40b69d87f4f9ac94c9a0763f96248df23b',
// 2
// );
//console.log("contr", contr, data)
// var addr1 = '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad';
// var contractAddr = '0x58cda554935e4a1f2acbe15f8757400af275e084';
// var tknAddress = (addr1).substring(2);
// var contractData = ('0x70a08231000000000000000000000000' + tknAddress);
// console.warn("ADDDDDDDD", web3.toHex('0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad'));
// console.warn("ADDDDDDDD", web3.toHex('98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad'));
// web3.eth.call({
// to: contractAddr,
// data: contractData
// }, function(err, result) {
// if (result) {
// console.log("---------result", result, web3);
// //var tokens = web3.toBN(result).toString();
// //console.log('Tokens Owned: ' + web3.utils.fromWei(tokens, 'ether'));
// }
// else {
// console.log(err); // Dump errors here
// }
// });
/*
const pendingBlockFilter = web3.eth.filter('pending');
pendingBlockFilter.watch((error, txid) => {
//console.warn("Watch pending block", txid, error)
if (!error) {
if (pendingTx.length > 0) {
console.error("Watch pending block", pendingTx.indexOf(txid))
}
web3.eth.getTransactionReceipt(txid, async (error, tx) => {
if (!error && typeof tx === 'object') {
const { addresses } = store.getState().addresses;
const receiver = addresses.find(a => a.address === tx.to);
if (receiver) {
console.error("PendingTX RECV", txid, tx, receiver)
const balance = await getBalance(receiver.address);
store.dispatch({
type: ACTIONS.ADDRESS_SET_BALANCE,
txid,
address: receiver,
balance: web3.fromWei(balance.toString(), 'ether')
})
}
const sender = addresses.find(a => a.address === tx.from);
if (sender) {
const balance = await getBalance(sender.address);
store.dispatch({
type: ACTIONS.ADDRESS_SET_BALANCE,
txid,
address: sender,
balance: web3.fromWei(balance.toString(), 'ether')
})
}
//console.log("PendingTX", txid, tx, receiver, sender)
}
})
}
});
*/
//"web3": "^0.19.0"
break;
}
};
export default Web3Service;
export const estimateGas = (gasOptions): Promise<any> => {
return new Promise((resolve, reject) => {
web3.eth.estimateGas(gasOptions, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
})
}

View File

@ -5,14 +5,10 @@ import RouterService from './RouterService';
import LocalStorageService from './LocalStorageService';
import CoinmarketcapService from './CoinmarketcapService';
import TrezorConnectService from './TrezorConnectService';
import Web3Service from './Web3Service';
import EtherscanService from './EtherscanService';
export default [
RouterService,
LocalStorageService,
TrezorConnectService,
Web3Service,
CoinmarketcapService,
//EtherscanService,
];

View File

@ -10,3 +10,8 @@ export const getAccounts = (accounts: Array<any>, device: any, network: ?string)
}
}
// Public method used in components to find device by state and device_id
export const findDevice = (devices: Array<TrezorDevice>, state: ?string, deviceId: ?string, instance: ?string): ?TrezorDevice => {
return devices.find(d => d.state === state && d.features && d.features.device_id === deviceId && d.instance === instance);
}

View File

@ -95,6 +95,8 @@ aside {
margin: 12px 0px 12px 80px;
}
.device-select {
background: @color_white;
border-bottom: 1px solid @color_divider;
@ -246,6 +248,56 @@ aside {
}
.device-menu {
padding: 8px 0px;
// padding-left: 80px;
padding-left: 74px;
border-bottom: 1px solid @color_divider;
div {
display: flex;
align-items: center;
line-height: 24px;
cursor: pointer;
color: @color_text_secondary;
.hover();
&:before {
.icomoon-refresh;
// color: @color_text_secondary;
position: relative;
font-size: 24px;
margin-right: 12px;
.hover();
}
&:hover {
color: @color_text_primary;
// &:before {
// color: @color_text_primary;
// }
}
&.forget {
&:before {
.icomoon-eject;
}
}
&.clone {
&:before {
.icomoon-settings;
}
}
&.settings {
&:before {
.icomoon-settings;
}
}
}
}
a {
position: relative;
display: block;
@ -391,12 +443,12 @@ aside {
}
&.ropsten:before,
&.eth:before {
&.ethereum:before {
background-image: url('../images/eth-logo.png');
background-size: auto 20px;
}
&.rinkeby:before,
&.etc:before {
&.ethereum-classic:before {
background-image: url('../images/etc-logo.png');
background-size: auto 20px;
}

View File

@ -169,5 +169,6 @@
.icomoon-download {
.icomoon-base();
content: "\e91f";
content: "\e91b";
transform: rotate(180deg)
}

View File

@ -180,7 +180,7 @@
justify-content: space-between;
padding: 36px 0px;
margin: 0 auto;
width: 420px;
// width: 420px;
p {
// flex: 1;
@ -197,6 +197,18 @@
// color: @color_green_secondary;
// }
// }
.webusb-and,
.trezor-webusb-button {
display: none;
}
&.webusb {
width: 400px;
.webusb-and,
.trezor-webusb-button {
display: block;
}
}
}
p {

View File

@ -108,6 +108,7 @@
}
}
.duplicate,
.remember {
width: 360px;
padding: 24px 48px;
@ -138,6 +139,7 @@
}
}
.close-modal {
position: absolute;
top: 0;

View File

@ -190,20 +190,23 @@
.advanced-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 0px 48px;
padding-bottom: 24px;
button {
width: 50%;
min-width: 50%;
white-space: nowrap;
}
&.opened {
flex-direction: column;
padding: 0px;
button {
position: relative;
left: 50%;
width: 50%;
// left: 50%;
// width: 50%;
float: right; // TODO: better
}
.advanced {
display: inline-block;
@ -232,6 +235,12 @@
}
}
// @media screen and (max-width: 900px) {
// :not(.opened) {
// border: 1px solid red;
// }
// }
}
.gas-row {

View File

@ -13,12 +13,12 @@
}
&.ropsten:before,
&.eth:before {
&.ethereum:before {
background-image: url('../images/eth-logo.png');
background-size: auto 20px;
}
&.rinkeby:before,
&.etc:before {
&.ethereum-classic:before {
background-image: url('../images/etc-logo.png');
background-size: auto 20px;
}