diff --git a/src/js/components/modal/ConfirmAddress.js b/src/js/components/modal/ConfirmAddress.js index 15b98b73..4ad9e89e 100644 --- a/src/js/components/modal/ConfirmAddress.js +++ b/src/js/components/modal/ConfirmAddress.js @@ -56,7 +56,7 @@ export const ConfirmUnverifiedAddress = (props: Props) => { return (
-

{ device.instanceLabel } is not connected

+

{ device.label } is not connected

To prevent phishing attacks, you should verify the address on your TREZOR first. Please reconnect your device to continue with the verification process.

-

{ device.instanceLabel } is unavailable

+

{ device.label } is unavailable

To prevent phishing attacks, you should verify the address on your TREZOR first. { enable } passphrase settings to continue with the verification process.

- -
- ); +type State = { + defaultName: string; + instanceName: ?string; } -export default RememberDevice; \ No newline at end of file +export default class DuplicateDevice extends Component { + + state: State; + + constructor(props: Props) { + super(props); + + const device = props.modal.opened ? props.modal.device : null; + if (!device) return; + + const instance = getNewInstance(props.connect.devices, device); + + this.state = { + defaultName: `${device.label} (${instance.toString()})`, + instanceName: null + } + } + + onNameChange = (value: string): void => { + this.setState({ + instanceName: value.length > 0 ? value : null + }); + } + + render() { + + if (!this.props.modal.opened) return null; + + const { device } = this.props.modal; + const { onCancel, onDuplicateDevice } = this.props.modalActions; + + return ( +
+

Clone { device.label }?

+

This will create new instance of device which can be used with different passphrase

+ + this.onNameChange(event.currentTarget.value) } + defaultValue={ this.state.instanceName } /> + + +
+ ); + } +} \ No newline at end of file diff --git a/src/js/components/modal/InvalidPin.js b/src/js/components/modal/InvalidPin.js index bd313be5..ae8b1005 100644 --- a/src/js/components/modal/InvalidPin.js +++ b/src/js/components/modal/InvalidPin.js @@ -10,7 +10,7 @@ const InvalidPin = (props: Props) => { const { device } = props.modal; return (
-

Entered PIN for { device.label } is not correct.

+

Entered PIN for { device.label } is not correct

Retrying...

); diff --git a/src/js/components/modal/PassphraseType.js b/src/js/components/modal/PassphraseType.js index 0d2a0f1d..0cd2dbf8 100644 --- a/src/js/components/modal/PassphraseType.js +++ b/src/js/components/modal/PassphraseType.js @@ -2,12 +2,17 @@ 'use strict'; import React from 'react'; +import type { Props } from './index'; + +const Confirmation = (props: Props) => { + + if (!props.modal.opened) return null; + const { device } = props.modal; -const Confirmation = () => { return (
-

Complete the action on your TREZOR device

+

Complete the action on { device.label } device

); diff --git a/src/js/components/modal/RememberDevice.js b/src/js/components/modal/RememberDevice.js index 4762fa95..ca55af92 100644 --- a/src/js/components/modal/RememberDevice.js +++ b/src/js/components/modal/RememberDevice.js @@ -67,16 +67,15 @@ export default class RememberDevice extends Component { const { onForgetDevice, onRememberDevice } = this.props.modalActions; let label = device.label; - let devicePlural: string = "device or to remember it"; - if (instances && instances.length > 1) { + const devicePlural: string = instances && instances.length > 1 ? "devices or to remember them" : "device or to remember it"; + if (instances && instances.length > 0) { label = instances.map((instance, index) => { let comma: string = ''; if (index > 0) comma = ', '; return ( { comma }{ instance.instanceLabel } ); - }) - devicePlural = "devices or to remember them"; + }); } return (
diff --git a/src/js/flowtype/index.js b/src/js/flowtype/index.js index a285e417..4210c5b9 100644 --- a/src/js/flowtype/index.js +++ b/src/js/flowtype/index.js @@ -51,6 +51,7 @@ export type TrezorDevice = { state: ?string; instance?: number; instanceLabel: string; + instanceName: ?string; features?: Features; unacquired?: boolean; acquiring: boolean; diff --git a/src/js/reducers/TrezorConnectReducer.js b/src/js/reducers/TrezorConnectReducer.js index 657e0aed..e7426b8f 100644 --- a/src/js/reducers/TrezorConnectReducer.js +++ b/src/js/reducers/TrezorConnectReducer.js @@ -81,6 +81,24 @@ export const isSavedDevice = (state: State, device: any): ?Array = }); } +export const getNewInstance = (devices: Array, device: Device | TrezorDevice): number => { + + const affectedDevices: Array = devices.filter(d => d.features && device.features && d.features.device_id === device.features.device_id) + .sort((a, b) => { + if (!a.instance) { + return -1; + } else { + return !b.instance || a.instance > b.instance ? 1 : -1; + } + }); + + const instance: number = affectedDevices.reduce((inst, dev) => { + return dev.instance ? dev.instance + 1 : inst + 1; + }, 0); + + return instance; +} + const mergeDevices = (current: TrezorDevice, upcoming: Object): TrezorDevice => { // do not merge if passphrase protection was changed @@ -95,7 +113,7 @@ const mergeDevices = (current: TrezorDevice, upcoming: Object): TrezorDevice => if (upcoming.label !== current.label) { instanceLabel = upcoming.label if (typeof current.instance === 'number') { - instanceLabel += ` (${current.instance})`; + instanceLabel += ` (${current.instanceName || current.instance})`; } } @@ -112,21 +130,14 @@ const mergeDevices = (current: TrezorDevice, upcoming: Object): TrezorDevice => acquiring: typeof upcoming.acquiring === 'boolean' ? upcoming.acquiring : current.acquiring, ts: typeof upcoming.ts === 'number' ? upcoming.ts : current.ts, } - + // corner-case: trying to merge unacquired device with acquired + // make sure that sensitive fields will not be changed and device will remain acquired if (upcoming.unacquired && current.state) { - dev.instanceLabel = current.instanceLabel; + dev.unacquired = false; dev.features = current.features; dev.label = current.label; - dev.unacquired = false; - } else if (!upcoming.unacquired && current.unacquired) { - // dev.instanceLabel = upcoming.label; - // if (typeof dev.instance === 'number') { - // dev.instanceLabel = `${upcoming.label} TODO:(${dev.instance})`; - // } } - - return dev; } @@ -151,14 +162,17 @@ const addDevice = (state: State, device: Device): State => { } if (affectedDevices.length > 0 ) { - // replace existing values - // const changedDevices: Array = affectedDevices.map(d => mergeDevices(d, { ...device, connected: true} )); + // check if freshly added device has different "passphrase_protection" settings let cloneInstance: number = 1; + let hasDifferentPassphraseSettings: boolean = false; + let hasInstancesWithPassphraseSettings: boolean = false; const changedDevices: Array = affectedDevices.map(d => { if (d.features && d.features.passphrase_protection === device.features.passphrase_protection) { cloneInstance = 0; + hasInstancesWithPassphraseSettings = true; return mergeDevices(d, { ...device, connected: true, available: true } ); } else { + hasDifferentPassphraseSettings = true; if (d.instance && cloneInstance > 0) { cloneInstance = d.instance + 1; } @@ -167,9 +181,12 @@ const addDevice = (state: State, device: Device): State => { } }); - if (cloneInstance > 0) { + // edge case: freshly connected device has different "passphrase_protection" than saved instances + // need to automatically create another instance with default instance name + if (hasDifferentPassphraseSettings && !hasInstancesWithPassphraseSettings) { // TODO: instance should be calculated form affectedDevice - const instance = cloneInstance; //new Date().getTime(); + // const instance = cloneInstance; //new Date().getTime(); + const instance = getNewInstance(affectedDevices, device); const newDevice: TrezorDevice = { ...device, @@ -182,14 +199,15 @@ const addDevice = (state: State, device: Device): State => { state: null, instance, instanceLabel: `${device.label} (${instance})`, + instanceName: null, ts: new Date().getTime(), } changedDevices.push(newDevice); } + newState.devices = otherDevices.concat(changedDevices); } else { - const newDevice: TrezorDevice = { ...device, acquiring: false, @@ -199,14 +217,11 @@ const addDevice = (state: State, device: Device): State => { path: device.path, label: device.label, state: null, - // instance: 0, instanceLabel: device.label, + instanceName: null, ts: new Date().getTime(), } newState.devices.push(newDevice); - - // const clone = { ...newDevice, instance: 1, instanceLabel: device.label + '#1' }; - // newState.devices.push(clone); } return newState; @@ -251,9 +266,15 @@ const changeDevice = (state: State, device: Object): State => { if (affectedDevices.length > 0) { const isAffectedUnacquired: number = affectedDevices.findIndex(d => d.unacquired); - if (isAffectedUnacquired >= 0 && affectedDevices.length > 1){ - affectedDevices.splice(isAffectedUnacquired, 1); - } + // if (isAffectedUnacquired >= 0 && affectedDevices.length > 1){ + if (isAffectedUnacquired >= 0){ + // TODO: should unacquired device be removed? or merged? + //affectedDevices.splice(isAffectedUnacquired, 1); + } else { + // replace existing values + const changedDevices: Array = affectedDevices.map(d => mergeDevices(d, device)); + newState.devices = otherDevices.concat(changedDevices); + } // else if (isAffectedUnacquired >= 0 && !device.unacquired && affectedDevices.length > 1) { // affectedDevices.splice(isAffectedUnacquired, 1); @@ -263,12 +284,6 @@ const changeDevice = (state: State, device: Object): State => { if (state.selectedDevice && device.path === state.selectedDevice.id && affectedDevices.length > 1) { // affectedDevices = affectedDevices.filter(d => d.path !== state.selectedDevice.id && d.features); } - - - - // replace existing values - const changedDevices: Array = affectedDevices.map(d => mergeDevices(d, device)); - newState.devices = otherDevices.concat(changedDevices); } return newState; @@ -336,30 +351,33 @@ const devicesFromLocalStorage = (devices: Array): Array => { const duplicate = (state: State, device: TrezorDevice): State => { const newState: State = { ...state }; - const affectedDevices: Array = state.devices.filter(d => d.features && device.features && d.features.device_id === device.features.device_id) - .sort((a, b) => { - if (!a.instance) { - return -1; - } else { - return !b.instance || a.instance > b.instance ? 1 : -1; - } - }); + // const affectedDevices: Array = state.devices.filter(d => d.features && device.features && d.features.device_id === device.features.device_id) + // .sort((a, b) => { + // if (!a.instance) { + // return -1; + // } else { + // return !b.instance || a.instance > b.instance ? 1 : -1; + // } + // }); - const instance: number = affectedDevices.reduce((inst, dev) => { - return dev.instance ? dev.instance + 1 : inst + 1; - }, 0); + // const instance: number = affectedDevices.reduce((inst, dev) => { + // return dev.instance ? dev.instance + 1 : inst + 1; + // }, 0); + + const instance: number = getNewInstance(state.devices, device); const newDevice: TrezorDevice = { ...device, - acquiring: false, + // acquiring: false, remember: false, - connected: device.connected, - available: device.available, - path: device.path, - label: device.label, + // connected: device.connected, + // available: device.available, + // path: device.path, + // label: device.label, state: null, instance, - instanceLabel: `${device.label} (${instance})`, + // instanceLabel: `${device.label} (${instance})`, + instanceLabel: `${device.label} (${ device.instanceName || instance })`, ts: new Date().getTime(), } newState.devices.push(newDevice); @@ -450,7 +468,7 @@ export default function connect(state: State = initialState, action: Action): St case DEVICE.CONNECT_UNACQUIRED : return addDevice(state, action.device); case DEVICE.CHANGED : - return changeDevice(state, { ...action.device, connected: true, available: true }); + return changeDevice(state, { ...action.device, connected: true, available: true }); // TODO: check if available will propagate to unavailable case DEVICE.DISCONNECT : case DEVICE.DISCONNECT_UNACQUIRED : return disconnectDevice(state, action.device);