Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

ledger backup and recovery #4396

Merged
merged 2 commits into from
Oct 11, 2016
Merged

ledger backup and recovery #4396

merged 2 commits into from
Oct 11, 2016

Conversation

jkup
Copy link
Contributor

@jkup jkup commented Sep 30, 2016

  • Submitted a ticket for my issue if one did not already exist.
  • Used Github auto-closing keywords in the commit message.
  • Added/updated tests for this change (for new code or code which already has tests).
  • Ran git rebase -i to squash commits (if needed).

Fixes #3350

Auditors @bbondy @bradleyrichter

Still remaining

  • Modals still need some custom CSS
  • Generate a new file with fs.writeStream for printing / saving
    • Either ignore generated file or delete after printing or saving
  • Re-implement printing and saving with the new file
  • Send data from recovery form to ledger.js
    • Send response back to UI and display message to user
  • Link through settings like minimum page time and minimum visits
  • At least write automated tests proving backup and recover flow

@jkup jkup added the design A design change, especially one which needs input from the design team. label Sep 30, 2016
@jkup jkup added this to the 0.12.4dev milestone Sep 30, 2016
@jkup
Copy link
Contributor Author

jkup commented Sep 30, 2016

Right now I'm working on the recovery flow. The thing I need help with is figuring out a good approach for printing / saving the keys!

@ayumi for recovering keys do I just have an aboutAction that ipc's to ledger.js and then a method in ledger.js that calls client.recover? I'm still trying to get my head around the ledger.js -> ledger-client -> ledger workflow

@bradleyrichter on the recover modal we have "Recover" and "Done" but I think it might cause some confusion where entering your keys and clicking "Done" seems like it should do a recover. Maybe we can remove "Done" or change it to "Cancel"?

Also, we're gonna need a better approach for organizing all these modals. I read this thread (reactjs/react-modal#75) which pointed to an interesting approach: https://github.com/react-rally/www/blob/gh-pages/2015/app/screens/Schedule/index.js#L152-L180

@bradleyrichter
Copy link
Contributor

@jkup good catch. This should fix the UX issue:

image

@ayumi
Copy link
Contributor

ayumi commented Sep 30, 2016

@jkup yes, the appAction IPC -> ledger thing sounds legit

@jkup jkup force-pushed the ledger-redesign branch 6 times, most recently from 5dac2e3 to 85e9db8 Compare October 4, 2016 03:02
@bbondy bbondy modified the milestones: 0.12.5dev, 0.12.4dev Oct 4, 2016
@jkup jkup force-pushed the ledger-redesign branch 3 times, most recently from fc02edc to a637fc7 Compare October 5, 2016 05:45
@jkup
Copy link
Contributor Author

jkup commented Oct 5, 2016

This one is getting close. Could I get some eyes on the communication from @bbondy or @bridiver.

Right now preferences.js triggers and aboutAction to appStore to ledger.js (which is all working but maybe not the best approach.

Then (the last part I'm struggling with is getting the data back to preferences.js from ledger.js. I tried (as you can see in this branch) just emitting an ipc event but that doesn't seem to be working.

I can see that the recover task is working correctly, I just need to get that info back to the UI.

@bridiver
Copy link
Collaborator

bridiver commented Oct 5, 2016

@jkup you communicate back to preferences by updating the state

@@ -583,6 +586,27 @@ const handleAppAction = (action) => {
case AppConstants.APP_SET_DICTIONARY:
appState = appState.setIn(['dictionary', 'locale'], action.locale)
break
case AppConstants.APP_BACKUP_KEYS:
const paymentId = appState.get('ledgerInfo').get('paymentId')
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you want to encapsulate this in a ledger.js method

})
break
case AppConstants.APP_RECOVER_WALLET:
ipcMain.emit(messages.LEDGER_RECOVER_WALLET, action.firstRecoveryKey, action.secondRecoveryKey)
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above. ledger.js runs in the same process as appStore.js so emitting an ipc event doesn't really make sense. Just call a method on ledger.js - see https://github.com/brave/browser-laptop/compare/tabs-api?expand=1#diff-23ca389e2bcb77191b5a9c10900eb3a3R717 for an example. The methods should always take the state and the action (reducer) as parameters and return the state (may or may not be modified). The flow should almost always be the same: call an action -> reducer functions update state in appStore -> ui updates to reflect the new state

@@ -88,6 +88,38 @@ const aboutActions = {
},

/**
* Generates a file with the users backup keys
*/
generateKeyFile: function (backupAction) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

it would probably be helpful to give these actions/constants names that include ledger. Eventually we'll probably break them out into ledger actions

@@ -722,7 +722,17 @@ class TabsTab extends ImmutableComponent {
class PaymentsTab extends ImmutableComponent {
constructor () {
super()
this.state = {
Copy link
Member

Choose a reason for hiding this comment

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

please put the state in AboutPreferences and pass it to PaymentTab as props.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you help me understand why? Will I also need to create and pass down actions for updating those keys as well? Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

yes. @bbondy can explain why

case AppConstants.APP_BACKUP_KEYS:
const paymentId = appState.get('ledgerInfo').get('paymentId')
const passphrase = appState.get('ledgerInfo').get('passphrase')
const message = 'Your ledger keys are ' + paymentId + ' and ' + passphrase
Copy link
Member

Choose a reason for hiding this comment

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

this should be localized

const message = 'Your ledger keys are ' + paymentId + ' and ' + passphrase
const filePath = app.getPath('userData') + '/backup_keys.html'

fs.writeFile(filePath, message)
Copy link
Member

Choose a reason for hiding this comment

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

i think this should be writeFileSync

Copy link
Collaborator

Choose a reason for hiding this comment

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

writeFileSync will block the entire browser process. Is there a particular reason for sync over a callback?

Copy link
Member

Choose a reason for hiding this comment

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

@bridiver sorry, i meant either writeFileSync or put the rest of the block in the callback

@@ -478,8 +478,12 @@ WindowStore
percentage: number, // i.e., 0, 1, ... 100
publisherURL: string, // publisher site, e.g., "https://wikipedia.org/"
faviconURL: string // i.e., "data:image/...;base64,..."
} ],
options: {
Copy link
Member

Choose a reason for hiding this comment

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

it would be useful to give this a more descriptive name, like synopsisOptions, because this property gets merged into ledgerData in js/components/frame.js

@@ -583,6 +586,27 @@ const handleAppAction = (action) => {
case AppConstants.APP_SET_DICTIONARY:
appState = appState.setIn(['dictionary', 'locale'], action.locale)
break
case AppConstants.APP_BACKUP_KEYS:
const paymentId = appState.get('ledgerInfo').get('paymentId')
const passphrase = appState.get('ledgerInfo').get('passphrase')
Copy link
Member

Choose a reason for hiding this comment

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

use getIn here and in the line above to avoid chaining gets

Copy link
Collaborator

Choose a reason for hiding this comment

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

fyi - getIn is helpful because you'll never get an undefined method on null when accessing nested props

@diracdeltas
Copy link
Member

please add a test or two for this in test/components/braveryPanelTest.js

@diracdeltas
Copy link
Member

pushing a fix for unicode thing now

diracdeltas added a commit that referenced this pull request Oct 11, 2016
address #4396 (comment)

Auditors: @aekeus

Test Plan:
1. go to ledger panel, save recovery keys
2. verify that opening the saved file in the browser does not show non-ascii characters
@@ -585,6 +587,21 @@ const handleAppAction = (action) => {
case AppConstants.APP_SET_DICTIONARY:
appState = appState.setIn(['dictionary', 'locale'], action.locale)
break
case AppConstants.APP_BACKUP_KEYS:
ledger.backupKeys(appState, action)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this should be appState = ledger.backupKeys(appState, action) to capture any state updates that this method might make. I know it doesn't directly make any now, but the pattern should always be the same to avoid confusion

ledger.backupKeys(appState, action)
break
case AppConstants.APP_RECOVER_WALLET:
ledger.recoverKeys(appState, action)
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above

*/

var recoverKeys = (appState, action) => {
client.recoverWallet(action.firstRecoveryKey, action.secondRecoveryKey, (err, body) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this callback may or may not happen asynchronously so the appActions should be called inside setImmediate to prevent any potential race conditions with state updates. The issue was discussed in slack, but this is how it happens:

For simplicity, all actions increment the current count by 1, but the same problem exists even if the actions change different parts of the state
1. action1 is called with the current appState = {count: 0}
2. action1 calls a method with a callback
3. callback returns synchronously (does not perform any io or other operations that would make it async)
4. action2 is called inside callback with the current appState = {count: 0}
5. action2 returns appState = {count: 1}
6. appState is emitted
7. action1 returns appState = {count: 1}
8 app state is emitted

@bridiver bridiver merged commit 43c0100 into master Oct 11, 2016
@bridiver
Copy link
Collaborator

++

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
design A design change, especially one which needs input from the design team. feature/printing feature/rewards QA/checked-macOS QA/checked-Win32 QA/checked-Win64
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants