Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add signers #32

Merged
merged 11 commits into from
Sep 13, 2019
Merged

WIP: Add signers #32

merged 11 commits into from
Sep 13, 2019

Conversation

justinmoon
Copy link
Owner

@justinmoon justinmoon commented Sep 13, 2019

  • Wallet page nicely displays current and potential signers in tables
  • Can add signers
  • Modal for entering Trezor PIN
  • Some backend improvements

One big problem: activeWallet goes stale after updates to the wallet list. Perhaps it would be better to just keep track of the activeWalletName and have a selector that uses it to looks up activeWallet ...

Copy link
Owner Author

@justinmoon justinmoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some notes

pinEntryDevice: null,
}

toggle() {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method and state.modal needs to be renamed to like toggleDeviceInstructionsModal or something.

Originally I just had 1 modal and the generic names haven't been updated yet ...

@@ -14,6 +14,15 @@
logger = logging.getLogger(__name__)
CLIENT = None

def kill_client():
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same instance of trezor.TrezorClient() must prompt and send the pin. Therefore, we implement it as a global variable. But we need to close it when we're done using or we get "device is busy" errors when creating competing instances. Therefore, I close it wherever possible.

# FIXME: or path???
'fingerprint': { 'type': 'string' }, # FIXME: regex
# device_id is fingerprint or path
'device_id': { 'type': 'string' }, # FIXME: regex
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wbobeirne went with your idea here to accept fingerprint or path


class AddDevice extends React.Component<AddDeviceProps> {
handleUnlock(device: Device) {
api.promptPin({ path: device.path }).then(this.props.displayPinEntry)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modal is weird. We want to have HWI prompt pin every time the modal is displayed, but because the modal component is always rendered and just pops in and out of view, we can't prompt pin entry on componentWillMount or something like that ...

I'm sure there's a better way than this ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two ways to go about this. One is to make a sub-component that only renders when the modal is open, and use that component's componentDidMount. Alternatively, you can use componentDidUpdate like so:

componentDidUpdate(prevProps: Props) {
  if (this.props.isOpen && !prevProps.isOpen) {
    // Do thing on modal open
  }
}

modal: boolean;
pinModalActive: boolean;
// FIXME: keeping track of the device outside the modal sucks
pinEntryDevice: Device | null;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sucks. Since the modal is rendered before a user chooses which device to unlock, we have to keep track of the chosen device in the parent component and pass to the pin entry component. But it works.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty reasonable to me, IMO. However, maybe the "cooler" option would be to have a global component rendered way further up in the stack, and to trigger pin entries via redux. There could be a state prop called pinDevice or something like that, and you just call this.props.promptPin(device), it sets it in redux state, and causes that modal to render and take it from there.

}));
}

openModal(device: Device) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Janky code to communicate the device to modal

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could just consolidate the two props into one be informing modal openness from whether or not you have a pin entry device set, i.e. isOpen={!!this.state.pinEntryDevice}

isOpen: boolean;
}

export default class DeviceInstructionsModal extends React.Component<Props> {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone is a VSCode noob who haven't figured out how to do indentation properly ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can get prettier setup on this sucker to avoid code style issues 💪

}

handleMouseDown(digit: number) {
if (!this.state.submitting) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good way to dispable buttons when they shouldn't be clicked?

Or should I use a disabled=bool HTML attribute?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disabled works pretty nicely on Reactstrap buttons, so I'd go with that. But doing it in both places doesn't hurt either.

// This is hard to use
export function selectActiveWallet(state: AppState) {
const { activeWallet } = state.wallet;
// if (activeWallet === null) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this selector that throws if there is no active wallet. I found it hard to use. React is always trying to render ... this approach needs some way to tell react not to render until wallets are loaded. Also, some way to redirect user through onboarding steps (#1 update settings, #2 create wallet) if wallets are loaded and no active wallet exists.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you'd want a wrapper or higher order component for that, sorry for the my earlier red herring solution (Which does work in some cases, just not the one here.) Something like:

<RequireWallet component={MyComponent} />

where RequireWallet's render looks like

if (this.props.activeWallet) {
  return <MyComponent activeWallet={this.props.activeWallet} />
} else {
  return 'Oh no you need a wallet fam';
}

}

// FIXME: some of these are null at times
// but I don't really want typescript yelling at me all the time ...
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird. I should be saying that some of these properties can be be null. But then typescript relentlessly yells at me in places where I know the properties will exist.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need union types for those. What are the cases where you know it will be null, and know it won't?

Copy link
Collaborator

@wbobeirne wbobeirne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't run yet, but here are some notes!


class AddDevice extends React.Component<AddDeviceProps> {
handleUnlock(device: Device) {
api.promptPin({ path: device.path }).then(this.props.displayPinEntry)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two ways to go about this. One is to make a sub-component that only renders when the modal is open, and use that component's componentDidMount. Alternatively, you can use componentDidUpdate like so:

componentDidUpdate(prevProps: Props) {
  if (this.props.isOpen && !prevProps.isOpen) {
    // Do thing on modal open
  }
}


deletePrompt() {
return this.request<any>('DELETE', '/prompt',)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should type all of these responses at some point, <any> was only meant as a placeholder.

rightComponent = <Button onClick={() => this.handleUnlock(device)}>Unlock</Button>
// TODO
// } else if (device.needs_pin_sent) {
// rightComponent = <Button onClick={enterPassphrase}>Unlock</Button>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this conditional right? Or is it mean for needs_password_somethingorother?

modal: boolean;
pinModalActive: boolean;
// FIXME: keeping track of the device outside the modal sucks
pinEntryDevice: Device | null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty reasonable to me, IMO. However, maybe the "cooler" option would be to have a global component rendered way further up in the stack, and to trigger pin entries via redux. There could be a state prop called pinDevice or something like that, and you just call this.props.promptPin(device), it sets it in redux state, and causes that modal to render and take it from there.

}));
}

openModal(device: Device) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could just consolidate the two props into one be informing modal openness from whether or not you have a pin entry device set, i.e. isOpen={!!this.state.pinEntryDevice}

{activeWallet.balances.unconfirmed > 0 &&
<div>Unconfirmed Balance: {activeWallet.balances.unconfirmed}</div>}
{signersComponent}
{addSigners}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to stick with one style of conditional rendering for consistency's sake. Here both variable assignment in conditional, and in-jsx checking is done. addSigners and signersComponent could both be done with in-jsx conditionals.

import { Signer, Device } from '../../types'

export function selectCandidateDevicesForActiveWallet(state: AppState) {
// FIXME this check sucks
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable to me 🤷‍♂

candidates.push(device);
}
return candidates;
}, [])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filter is probably a better method than reduce for this.

// This is hard to use
export function selectActiveWallet(state: AppState) {
const { activeWallet } = state.wallet;
// if (activeWallet === null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you'd want a wrapper or higher order component for that, sorry for the my earlier red herring solution (Which does work in some cases, just not the one here.) Something like:

<RequireWallet component={MyComponent} />

where RequireWallet's render looks like

if (this.props.activeWallet) {
  return <MyComponent activeWallet={this.props.activeWallet} />
} else {
  return 'Oh no you need a wallet fam';
}

}

// FIXME: some of these are null at times
// but I don't really want typescript yelling at me all the time ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need union types for those. What are the cases where you know it will be null, and know it won't?

@justinmoon justinmoon merged commit 03653de into develop Sep 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants