1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-24 01:08:27 +00:00

Merge pull request #134 from trezor/feature/change-passphrase

Feature/change passphrase
This commit is contained in:
Vladimir Volek 2018-10-09 12:58:37 +02:00 committed by GitHub
commit f258f1b125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 369 additions and 80 deletions

View File

@ -142,7 +142,8 @@ const begin = (device: TrezorDevice, network: string): AsyncAction => async (dis
},
path: coinToDiscover.bip44,
keepSession: true, // acquire and hold session
useEmptyPassphrase: !device.instance,
//useEmptyPassphrase: !device.instance,
useEmptyPassphrase: device.useEmptyPassphrase,
});
// handle TREZOR response error
@ -264,7 +265,8 @@ const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction
state: device.state,
},
keepSession: false,
useEmptyPassphrase: !device.instance,
// useEmptyPassphrase: !device.instance,
useEmptyPassphrase: device.useEmptyPassphrase,
});
await dispatch(BlockchainActions.subscribe(discoveryProcess.network));

View File

@ -112,6 +112,17 @@ export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispa
}
};
export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean): ThunkAction => (dispatch: Dispatch): void => {
dispatch({
type: MODAL.CLOSE,
});
dispatch({
type: CONNECT.RECEIVE_WALLET_TYPE,
device,
hidden,
});
};
export default {
onPinSubmit,
onPassphraseSubmit,
@ -121,4 +132,5 @@ export default {
onForgetSingleDevice,
onCancel,
onDuplicateDevice,
onWalletTypeRequest,
};

View File

@ -3,12 +3,8 @@
import * as PENDING from 'actions/constants/pendingTx';
import type {
Action, ThunkAction, GetState, Dispatch,
} from 'flowtype';
import type { State, PendingTx } from 'reducers/PendingTxReducer';
export type PendingTxAction = {
type: typeof PENDING.FROM_STORAGE,
payload: State
@ -25,29 +21,4 @@ export type PendingTxAction = {
} | {
type: typeof PENDING.TX_TOKEN_ERROR,
tx: PendingTx,
}
export const reject = (tx: PendingTx): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
/*
dispatch({
type: NOTIFICATION.ADD,
payload: {
type: 'warning',
title: 'Pending transaction rejected',
message: `Transaction with id: ${tx.id} not found.`,
cancelable: true,
actions: [
{
label: 'OK',
callback: () => {
dispatch({
type: PENDING.TX_RESOLVED,
tx,
});
},
},
],
},
});
*/
};
}

View File

@ -67,7 +67,8 @@ export const showAddress = (path: Array<number>): AsyncAction => async (dispatch
state: selected.state,
},
path,
useEmptyPassphrase: !selected.instance,
// useEmptyPassphrase: !selected.instance,
useEmptyPassphrase: selected.useEmptyPassphrase,
});
if (response && response.success) {

View File

@ -258,12 +258,12 @@ export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dis
/*
* Redirect to first device or landing page
*/
export const selectFirstAvailableDevice = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const selectFirstAvailableDevice = (gotoRoot: boolean = false): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const url = dispatch(getFirstAvailableDeviceUrl());
if (url) {
const currentParams = getState().router.location.state;
const requestedParams = dispatch(pathToParams(url));
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
if (gotoRoot || currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
dispatch(goto(url));
}
} else {

View File

@ -458,7 +458,8 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
instance: selected.instance,
state: selected.state,
},
useEmptyPassphrase: !selected.instance,
// useEmptyPassphrase: !selected.instance,
useEmptyPassphrase: selected.useEmptyPassphrase,
path: account.addressPath,
transaction: txData,
});

View File

@ -6,6 +6,7 @@ import * as CONNECT from 'actions/constants/TrezorConnect';
import * as NOTIFICATION from 'actions/constants/notification';
import { getDuplicateInstanceNumber } from 'reducers/utils';
import * as RouterActions from 'actions/RouterActions';
import * as deviceUtils from 'utils/device';
import type {
DeviceMessage,
@ -58,7 +59,7 @@ export type TrezorConnectAction = {
type: typeof CONNECT.FORGET,
device: TrezorDevice
} | {
type: typeof CONNECT.FORGET_SINGLE,
type: typeof CONNECT.FORGET_SINGLE | typeof CONNECT.FORGET_SILENT,
device: TrezorDevice
} | {
type: typeof CONNECT.REMEMBER,
@ -71,6 +72,13 @@ export type TrezorConnectAction = {
payload: Array<TrezorDevice>
} | {
type: typeof CONNECT.START_ACQUIRING | typeof CONNECT.STOP_ACQUIRING,
} | {
type: typeof CONNECT.REQUEST_WALLET_TYPE,
device: TrezorDevice
} | {
type: typeof CONNECT.RECEIVE_WALLET_TYPE,
device: TrezorDevice,
hidden: boolean,
};
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
@ -152,7 +160,19 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
}
};
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const requestWalletType = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
if (!isDeviceReady) return;
dispatch({
type: CONNECT.REQUEST_WALLET_TYPE,
device: selected,
});
};
export const authorizeDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
@ -164,7 +184,7 @@ export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispat
instance: selected.instance,
state: selected.state,
},
useEmptyPassphrase: !selected.instance,
useEmptyPassphrase: selected.useEmptyPassphrase,
});
if (response && response.success) {
@ -190,25 +210,32 @@ export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispat
type: NOTIFICATION.CLOSE,
payload: { devicePath: selected.path },
});
dispatch(getSelectedDeviceState());
dispatch(authorizeDevice());
},
},
],
},
});
}
};
export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
if (device.features) {
const instances = getState().devices.filter(d => d.features && device.features && d.state && !d.remember && d.features.device_id === device.features.device_id);
if (instances.length > 0) {
dispatch({
type: CONNECT.REMEMBER_REQUEST,
device: instances[0],
instances,
});
const isSelected = deviceUtils.isSelectedDevice(getState().wallet.selectedDevice, instances[0]);
if (!isSelected && getState().modal.opened) {
dispatch({
type: CONNECT.FORGET_SILENT,
device: instances[0],
});
} else {
dispatch({
type: CONNECT.REMEMBER_REQUEST,
device: instances[0],
instances,
});
}
} else {
dispatch(RouterActions.selectFirstAvailableDevice());
}
@ -231,11 +258,14 @@ export function acquire(): AsyncAction {
type: CONNECT.START_ACQUIRING,
});
// this is the only place where useEmptyPassphrase should be used every time
// the goal here is to acquire device and get his features
// authentication (passphrase) is not needed here yet
const response = await TrezorConnect.getFeatures({
device: {
path: selected.path,
},
useEmptyPassphrase: !selected.instance,
useEmptyPassphrase: true,
});
if (!response.success) {
@ -270,12 +300,7 @@ export const forget = (device: TrezorDevice): Action => ({
device,
});
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
// dispatch({
// type: CONNECT.TRY_TO_DUPLICATE,
// device,
// });
export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const instance: number = getDuplicateInstanceNumber(getState().devices, device);
const extended: Object = { instance };
dispatch({
@ -283,3 +308,10 @@ export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dis
device: { ...device, ...extended },
});
};
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
dispatch({
type: CONNECT.REQUEST_WALLET_TYPE,
device,
});
};

View File

@ -87,6 +87,7 @@ export const clearUnavailableDevicesData = (prevState: State, device: Device): T
const actions = [
LOCATION_CHANGE,
CONNECT.AUTH_DEVICE,
CONNECT.RECEIVE_WALLET_TYPE,
...Object.values(DEVICE).filter(v => typeof v === 'string'),
];

View File

@ -16,6 +16,7 @@ export const REMEMBER_REQUEST: 'connect__remember_request' = 'connect__remember_
export const FORGET_REQUEST: 'connect__forget_request' = 'connect__forget_request';
export const FORGET: 'connect__forget' = 'connect__forget';
export const FORGET_SINGLE: 'connect__forget_single' = 'connect__forget_single';
export const FORGET_SILENT: 'connect__forget_silent' = 'connect__forget_silent';
export const DISCONNECT_REQUEST: 'connect__disconnect_request' = 'connect__disconnect_request';
export const REMEMBER: 'connect__remember' = 'connect__remember';
@ -25,4 +26,7 @@ export const DUPLICATE: 'connect__duplicate' = 'connect__duplicate';
export const DEVICE_STATE_EXCEPTION: 'connect__device_state_exception' = 'connect__device_state_exception';
export const START_ACQUIRING: 'connect__start_acquiring' = 'connect__start_acquiring';
export const STOP_ACQUIRING: 'connect__stop_acquiring' = 'connect__stop_acquiring';
export const STOP_ACQUIRING: 'connect__stop_acquiring' = 'connect__stop_acquiring';
export const REQUEST_WALLET_TYPE: 'connect__request_wallet_type' = 'connect__request_wallet_type';
export const RECEIVE_WALLET_TYPE: 'connect__receive_wallet_type' = 'connect__receive_wallet_type';

View File

@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import colors from 'config/colors';
import styled from 'styled-components';
const SvgWrapper = styled.svg`
:hover {
path {
fill: ${props => props.hoverColor}
}
}
`;
const Path = styled.path`
fill: ${props => props.color};
`;
export const HIDDEN = 'M23.9,13.6l-4-11C19.8,2.3,19.4,2,19,2h-3c-0.6,0-1,0.4-1,1s0.4,1,1,1h2.3l3.3,9H14h-4H2.4l3.3-9H8 c0.6,0,1-0.4,1-1S8.6,2,8,2H5C4.6,2,4.2,2.3,4.1,2.7l-4,11C0,13.7,0,13.9,0,14c0,0,0,0,0,0v0c0,0,0,0,0,0v5c0,1.7,1.3,3,3,3h5 c1.7,0,3-1.3,3-3v-4h2v4c0,1.7,1.3,3,3,3h5c1.7,0,3-1.3,3-3v-5c0,0,0,0,0,0v0c0,0,0,0,0,0C24,13.9,24,13.7,23.9,13.6z';
export const STANDARD = 'M23,4H4H3C2.449,4,2,3.551,2,3s0.449-1,1-1h15v1h2V1c0-0.552-0.448-1-1-1H3C1.343,0,0,1.343,0,3v17 c0,2.209,1.791,4,4,4h19c0.552,0,1-0.448,1-1V5C24,4.448,23.552,4,23,4z M18,16c-1.105,0-2-0.895-2-2c0-1.105,0.895-2,2-2 s2,0.895,2,2C20,15.105,19.105,16,18,16z';
const Icon = ({
type = 'standard',
size = 24,
color = colors.TEXT_SECONDARY,
hoverColor,
onClick,
}) => (
<SvgWrapper
hoverColor={hoverColor}
width={`${size}`}
height={`${size}`}
viewBox="-12 -12 48 48"
onClick={onClick}
>
<Path
key={type}
color={color}
d={type === 'hidden' ? HIDDEN : STANDARD}
/>
</SvgWrapper>
);
Icon.propTypes = {
type: PropTypes.string,
size: PropTypes.number,
color: PropTypes.string,
hoverColor: PropTypes.string,
onClick: PropTypes.func,
};
export default Icon;

View File

@ -0,0 +1,139 @@
/* @flow */
import React, { Component } from 'react';
import styled from 'styled-components';
import { H3 } from 'components/Heading';
import P from 'components/Paragraph';
import Button from 'components/Button';
import Tooltip from 'components/Tooltip';
import Icon from 'components/Icon';
import Link from 'components/Link';
import colors from 'config/colors';
import icons from 'config/icons';
import WalletTypeIcon from 'components/images/WalletType';
import type { Props } from 'components/modals/index';
const Wrapper = styled.div`
width: 360px;
padding: 24px 48px;
`;
const StyledLink = styled(Link)`
position: absolute;
right: 15px;
top: 15px;
`;
const StyledButton = styled(Button)`
margin: 0 0 10px 0;
`;
const StyledTooltip = styled(Tooltip)`
position: absolute;
right: 0px;
top: 1px;
`;
const StyledIcon = styled(Icon)`
position: relative;
top: -1px;
&:hover {
cursor: pointer;
}
`;
const Row = styled.div`
display: flex;
flex-direction: column;
padding: 10px 0;
`;
const Span = styled.div`
position: relative;
display: flex;
align-items: center;
flex-direction: row;
justify-content: center;
`;
const Divider = styled.div`
margin: 20px 0;
border-top: 1px solid ${colors.DIVIDER};
`;
class WalletType extends Component<Props> {
constructor(props: Props) {
super(props);
this.keyboardHandler = this.keyboardHandler.bind(this);
}
componentDidMount() {
window.addEventListener('keydown', this.keyboardHandler, false);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.keyboardHandler, false);
}
keyboardHandler(event: KeyboardEvent): void {
if (event.keyCode === 13) {
event.preventDefault();
this.changeType(false);
}
}
keyboardHandler: (event: KeyboardEvent) => void;
changeType(hidden: boolean) {
const { modal } = this.props;
if (!modal.opened) return;
this.props.modalActions.onWalletTypeRequest(modal.device, hidden);
}
render() {
if (!this.props.modal.opened) return null;
const { device } = this.props.modal;
const { onCancel } = this.props.modalActions;
return (
<Wrapper>
{ device.state && (
<StyledLink onClick={onCancel}>
<Icon size={20} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
</StyledLink>
)}
<H3>RequestWalletType for { device.instanceLabel }?</H3>
<Row>
<Span>
<WalletTypeIcon type="standard" size={24} color={colors.TEXT_SECONDARY} />
Standard Wallet
</Span>
<P isSmaller>Continue to access your standard wallet.</P>
<StyledButton onClick={() => this.changeType(false)}>Go to your standard wallet</StyledButton>
<Divider />
<Span>
<WalletTypeIcon type="hidden" size={24} color={colors.TEXT_SECONDARY} />
Hidden Wallet
<StyledTooltip
maxWidth={285}
placement="top"
content="Passphrase is an optional feature of the Trezor device that is recommended for advanced users only. It is a word or a sentence of your choice. Its main purpose is to access a hidden wallet."
readMoreLink="https://wiki.trezor.io/Passphrase"
>
<StyledIcon
icon={icons.HELP}
color={colors.TEXT_SECONDARY}
size={24}
/>
</StyledTooltip>
</Span>
<P isSmaller>You will be asked to enter your passphrase to unlock your hidden wallet.</P>
<StyledButton isWhite onClick={() => this.changeType(true)}>Go to your hidden wallet</StyledButton>
</Row>
</Wrapper>
);
}
}
export default WalletType;

View File

@ -1,5 +1,5 @@
/* @flow */
import React, { Component } from 'react';
import * as React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
@ -29,6 +29,7 @@ import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddres
import ForgetDevice from 'components/modals/device/Forget';
import RememberDevice from 'components/modals/device/Remember';
import DuplicateDevice from 'components/modals/device/Duplicate';
import WalletType from 'components/modals/device/WalletType';
type OwnProps = { }
@ -51,13 +52,13 @@ type DispatchProps = {
export type Props = StateProps & DispatchProps;
const Fade = ({ children, ...props }) => (
const Fade = (props: { children: React.Node}) => (
<CSSTransition
{...props}
timeout={1000}
classNames="fade"
>
{ children }
{ props.children }
</CSSTransition>
);
@ -84,7 +85,7 @@ const ModalWindow = styled.div`
text-align: center;
`;
class Modal extends Component<Props> {
class Modal extends React.Component<Props> {
render() {
if (!this.props.modal.opened) return null;
@ -123,6 +124,10 @@ class Modal extends Component<Props> {
component = (<DuplicateDevice {...this.props} />);
break;
case CONNECT.REQUEST_WALLET_TYPE:
component = (<WalletType {...this.props} />);
break;
default:
component = null;
}

View File

@ -212,7 +212,7 @@ class Passphrase extends Component<Props, State> {
return (
<Wrapper>
<H2>Enter {this.state.deviceLabel} passphrase</H2>
<P isSmaller>Note that passphrase is case-sensitive.</P>
<P isSmaller>Note that passphrase is case-sensitive. If you enter a wrong passphrase, you will not unlock the desired hidden wallet.</P>
<Row>
<Label>Passphrase</Label>
<Input
@ -228,7 +228,7 @@ class Passphrase extends Component<Props, State> {
</Row>
{!this.state.shouldShowSingleInput && (
<Row>
<Label>Re-enter passphrase</Label>
<Label>Confirm passphrase</Label>
<Input
name="passphraseCheckInputValue"
type={this.state.isPassphraseHidden ? 'password' : 'text'}
@ -263,12 +263,12 @@ class Passphrase extends Component<Props, State> {
</Row>
<Footer>
<P isSmaller>If you want to access your default account</P>
<P isSmaller>
Changed your mind? &nbsp;
<LinkButton
isGreen
onClick={() => this.submitPassphrase(true)}
>Leave passphrase blank
>Go to your standard wallet
</LinkButton>
</P>
</Footer>

View File

@ -1,9 +1,14 @@
/* @flow */
import React from 'react';
import Icon from 'components/Icon';
import colors from 'config/colors';
import icons from 'config/icons';
import styled from 'styled-components';
import { H3 } from 'components/Heading';
import P from 'components/Paragraph';
import type { Props } from 'components/modals/index';
const Wrapper = styled.div`
width: 360px;
@ -12,7 +17,7 @@ const Wrapper = styled.div`
const Header = styled.div``;
const Confirmation = (props) => {
const Confirmation = (props: Props) => {
if (!props.modal.opened) return null;
const { device } = props.modal;
@ -21,6 +26,7 @@ const Confirmation = (props) => {
<Header>
<Icon icon={icons.T1} size={60} color={colors.TEXT_SECONDARY} />
<H3>Complete the action on { device.label } device</H3>
<P isSmaller>If you enter a wrong passphrase, you will not unlock the desired hidden wallet.</P>
</Header>
</Wrapper>
);

View File

@ -56,6 +56,7 @@ export type AcquiredDevice = $Exact<{
status: DeviceStatus,
+mode: DeviceMode,
state: ?string,
useEmptyPassphrase: boolean,
remember: boolean; // device should be remembered
connected: boolean; // device is connected
@ -73,6 +74,7 @@ export type UnknownDevice = $Exact<{
+label: string,
+features: null,
state: ?string,
useEmptyPassphrase: boolean,
remember: boolean; // device should be remembered
connected: boolean; // device is connected

View File

@ -92,6 +92,8 @@ export default (state: State = initialState, action: Action): State => {
case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE:
case CONNECT.FORGET_SILENT:
case CONNECT.RECEIVE_WALLET_TYPE:
return removeAccounts(state, action.device);
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:

View File

@ -39,6 +39,7 @@ const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): T
instanceName: typeof upcoming.instanceName === 'string' ? upcoming.instanceName : current.instanceName,
state: current.state,
ts: typeof upcoming.ts === 'number' ? upcoming.ts : current.ts,
useEmptyPassphrase: typeof upcoming.useEmptyPassphrase === 'boolean' ? upcoming.useEmptyPassphrase : current.useEmptyPassphrase,
};
if (upcoming.type === 'acquired') {
@ -89,6 +90,7 @@ const addDevice = (state: State, device: Device): State => {
instanceLabel: device.label,
instanceName: null,
ts: new Date().getTime(),
useEmptyPassphrase: true,
};
@ -260,6 +262,24 @@ const onSelectedDevice = (state: State, device: ?TrezorDevice): State => {
return otherDevices.concat([extended]);
};
const onChangeWalletType = (state: State, device: TrezorDevice, hidden: boolean): State => {
const affectedDevices: State = state.filter(d => d.path === device.path || (d.features && device.features && d.features.device_id === device.features.device_id));
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
if (affectedDevices.length > 0) {
const changedDevices = affectedDevices.map((d) => { // eslint-disable-line arrow-body-style
return d.type === 'acquired' ? {
...d,
remember: false,
state: null,
useEmptyPassphrase: !hidden,
ts: new Date().getTime(),
} : d;
});
return otherDevices.concat(changedDevices);
}
return state;
};
export default function devices(state: State = initialState, action: Action): State {
switch (action.type) {
case CONNECT.DEVICE_FROM_STORAGE:
@ -278,6 +298,7 @@ export default function devices(state: State = initialState, action: Action): St
case CONNECT.FORGET:
return forgetDevice(state, action.device);
case CONNECT.FORGET_SINGLE:
case CONNECT.FORGET_SILENT:
return forgetSingleDevice(state, action.device);
case DEVICE.CONNECT:
@ -296,6 +317,9 @@ export default function devices(state: State = initialState, action: Action): St
case WALLET.SET_SELECTED_DEVICE:
return onSelectedDevice(state, action.device);
case CONNECT.RECEIVE_WALLET_TYPE:
return onChangeWalletType(state, action.device, action.hidden);
default:
return state;
}

View File

@ -185,6 +185,8 @@ export default function discovery(state: State = initialState, action: Action):
});
case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE:
case CONNECT.FORGET_SILENT:
case CONNECT.RECEIVE_WALLET_TYPE:
return forgetDiscovery(state, action.device);
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:
return clear(state, action.devices);

View File

@ -32,6 +32,13 @@ export default function modal(state: State = initialState, action: Action): Stat
windowType: action.type,
};
case CONNECT.REQUEST_WALLET_TYPE:
return {
opened: true,
device: action.device,
windowType: action.type,
};
case CONNECT.REMEMBER_REQUEST:
return {
opened: true,

View File

@ -1,20 +1,22 @@
/* @flow */
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as PENDING from 'actions/constants/pendingTx';
import * as SEND from 'actions/constants/send';
import type { Action } from 'flowtype';
import type { TrezorDevice, Action } from 'flowtype';
import type { SendTxAction } from 'actions/SendFormActions';
export type PendingTx = {
+type: 'send' | 'recv';
+type: 'send' | 'receive';
+id: string;
+network: string;
+address: string;
+deviceState: string;
+currency: string;
+amount: string;
+total: string;
+tx: any;
+nonce: number;
+address: string;
rejected: boolean;
}
@ -28,12 +30,14 @@ const add = (state: State, action: SendTxAction): State => {
type: 'send',
id: action.txid,
network: action.account.network,
address: action.account.address,
deviceState: action.account.deviceState,
currency: action.selectedCurrency,
amount: action.amount,
total: action.total,
tx: action.tx,
nonce: action.nonce,
address: action.account.address,
rejected: false,
});
return newState;
@ -47,6 +51,8 @@ const addFromBloockbokNotifiaction = (state: State, payload: any): State => {
};
*/
const clear = (state: State, device: TrezorDevice): State => state.filter(tx => tx.deviceState !== device.state);
const remove = (state: State, id: string): State => state.filter(tx => tx.id !== id);
const reject = (state: State, id: string): State => state.map((tx) => {
@ -61,6 +67,12 @@ export default function pending(state: State = initialState, action: Action): St
case SEND.TX_COMPLETE:
return add(state, action);
case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE:
case CONNECT.FORGET_SILENT:
case CONNECT.RECEIVE_WALLET_TYPE:
return clear(state, action.device);
// case PENDING.ADD:
// return add(state, action.payload);
case PENDING.TX_RESOLVED:

View File

@ -71,6 +71,8 @@ export default (state: State = initialState, action: Action): State => {
case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE:
case CONNECT.FORGET_SILENT:
case CONNECT.RECEIVE_WALLET_TYPE:
return forget(state, action.device);
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:

View File

@ -102,6 +102,8 @@ const LocalStorageService: Middleware = (api: MiddlewareAPI) => (next: Middlewar
case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE:
case CONNECT.FORGET_SILENT:
case CONNECT.RECEIVE_WALLET_TYPE:
case DEVICE.CHANGED:
case DEVICE.DISCONNECT:
case CONNECT.AUTH_DEVICE:

View File

@ -11,6 +11,7 @@ import * as TrezorConnectActions from 'actions/TrezorConnectActions';
import * as SelectedAccountActions from 'actions/SelectedAccountActions';
import * as SendFormActionActions from 'actions/SendFormActions';
import * as DiscoveryActions from 'actions/DiscoveryActions';
import * as RouterActions from 'actions/RouterActions';
import type {
Middleware,
@ -49,10 +50,9 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
api.dispatch(LocalStorageActions.loadData());
break;
case WALLET.SET_SELECTED_DEVICE:
if (action.device) {
// try to authorize device
api.dispatch(TrezorConnectActions.getSelectedDeviceState());
}
// try to authorize device
// api.dispatch(TrezorConnectActions.authorizeDevice());
api.dispatch(TrezorConnectActions.requestWalletType());
break;
case DEVICE.CONNECT:
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.device));
@ -102,10 +102,19 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
// if "selectedAccount" didn't change observe send form props changes
api.dispatch(SendFormActionActions.observe(prevState, action));
}
} else if (action.type === CONNECT.AUTH_DEVICE) {
// selected device did changed
// try to restore discovery after device authentication
api.dispatch(DiscoveryActions.restore());
} else {
switch (action.type) {
case CONNECT.AUTH_DEVICE:
// selected device did changed
// try to restore discovery after device authentication
api.dispatch(DiscoveryActions.restore());
break;
case CONNECT.RECEIVE_WALLET_TYPE:
api.dispatch(RouterActions.selectFirstAvailableDevice(true));
api.dispatch(TrezorConnectActions.authorizeDevice());
break;
default: break;
}
}
// even if "selectedDevice" didn't change because it was updated on DEVICE.CHANGED before DEVICE.CONNECT action

View File

@ -4,7 +4,7 @@ import colors from 'config/colors';
const tooltipStyles = css`
.rc-tooltip {
position: absolute;
z-index: 1070;
z-index: 10070;
visibility: visible;
border: none;
border-radius: 3px;

View File

@ -58,14 +58,14 @@ class MenuItems extends Component {
if (!this.showDeviceMenu()) return null;
return (
<Wrapper>
<Item onClick={() => this.onDeviceMenuClick('settings', this.props.device)}>
{/* <Item onClick={() => this.onDeviceMenuClick('settings', this.props.device)}>
<Icon
icon={icons.COG}
size={25}
color={colors.TEXT_SECONDARY}
/>
<Label>Device settings</Label>
</Item>
</Item> */}
<Item onClick={() => this.onDeviceMenuClick('forget', this.props.device)}>
<Icon
icon={icons.EJECT}
@ -81,7 +81,7 @@ class MenuItems extends Component {
size={25}
color={colors.TEXT_SECONDARY}
/>
<Label>Create hidden wallet</Label>
<Label>Change wallet type</Label>
</Item>
)}
{this.showRenewSession() && (

View File

@ -4,6 +4,7 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import colors from 'config/colors';
import Icon from 'components/Icon';
import WalletTypeIcon from 'components/images/WalletType';
import icons from 'config/icons';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import styled from 'styled-components';
@ -197,11 +198,12 @@ class LeftNavigation extends React.PureComponent<Props, State> {
this.handleOpen();
}
}}
device={this.props.wallet.selectedDevice}
device={selectedDevice}
disabled={!isDeviceAccessible && this.props.devices.length === 1}
isOpen={this.props.wallet.dropdownOpened}
icon={(
<React.Fragment>
<WalletTypeIcon type={selectedDevice && !selectedDevice.useEmptyPassphrase ? 'hidden' : 'standard'} size={25} color={colors.TEXT_SECONDARY} />
{this.props.devices.length > 1 && (
<Counter>{this.props.devices.length}</Counter>
)}