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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions app/browser/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const {app, BrowserWindow, session, webContents} = require('electron')
const extensions = process.atomBinding('extension')
const { getIndexHTML } = require('../../js/lib/appUrlUtil')

let currentWebContents = {}
let activeTab = null

const cleanupWebContents = (tabId) => {
delete currentWebContents[tabId]
}

const tabs = {
init: () => {
app.on('web-contents-created', function (event, tab) {
// TODO(bridiver) - also exclude extension action windows??
if (extensions.isBackgroundPage(tab) || tab.getURL() === getIndexHTML()) {
return
}
let tabId = tab.getId()
tab.on('destroyed', cleanupWebContents.bind(null, tabId))
tab.on('crashed', cleanupWebContents.bind(null, tabId))
tab.on('close', cleanupWebContents.bind(null, tabId))
tab.on('set-active', function (evt, active) {
if (active) {
activeTab = tab
}
})
currentWebContents[tabId] = tab
})
},

getWebContents: (tabId) => {
return currentWebContents[tabId]
},

create: (createProperties) => {
return new Promise((resolve, reject) => {
// TODO(bridiver) - make this available from electron
var payload = {}
process.emit('ELECTRON_GUEST_VIEW_MANAGER_NEXT_INSTANCE_ID', payload)
var guestInstanceId = payload.returnValue

let win = BrowserWindow.getFocusedWindow()
let windowId = createProperties.windowId
if (windowId && windowId !== -2) {
win = BrowserWindow.fromId(windowId) || win
}
if (!win) {
reject('Could not find a window for new tab')
return
}
let opener = null
let newSession = session.defaultSession
let openerTabId = createProperties.openerTabId
if (openerTabId) {
opener = tabs.getWebContents(openerTabId)
if (!opener) {
reject('Opener does not exist')
return
}
// only use the opener if it is in the same window
if (opener.webContents.hostWebContents !== win.webContents) {
reject('Opener must be in the same window as new tab')
return
}
}

opener = opener || activeTab
if (opener) {
newSession = opener.session
} else {
reject('Could not find an opener for new tab')
return
}

let webPreferences = {
isGuest: true,
embedder: win.webContents,
session: newSession,
guestInstanceId,
delayedLoadUrl: createProperties.url || 'about:newtab'
}
webPreferences = Object.assign({}, opener.getWebPreferences(), webPreferences)
let guest = webContents.create(webPreferences)
process.emit('ELECTRON_GUEST_VIEW_MANAGER_REGISTER_GUEST', { sender: opener }, guest, guestInstanceId)

guest.once('did-finish-load', () => {
resolve(guest)
})
let active = createProperties.active !== false
if (!active) {
active = createProperties.selected !== false
}
let disposition = active ? 'foreground-tab' : 'background-tab'

process.emit('ELECTRON_GUEST_VIEW_MANAGER_TAB_OPEN',
{ sender: opener }, // event
'about:blank',
'',
disposition,
{ webPreferences: guest.getWebPreferences() })
})
}
}

module.exports = tabs
2 changes: 1 addition & 1 deletion app/extensions/brave/locales/en-US/app.properties
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ flashInstalled=Flash is already installed and can be enabled in Preferences > Se
goToPrefs=Open Preferences
goToAdobe=Reinstall Flash
allowFlashPlayer=Allow {{origin}} to run Flash Player?

ledgerBackupText=Your ledger keys are {{paymentId}} and {{passphrase}}
error=Error
caseSensitivity=Match case
nameField=Title:
Expand Down
24 changes: 23 additions & 1 deletion app/extensions/brave/locales/en-US/preferences.properties
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ bitcoinVisitAccount=Transfer BTC
bitcoinBalance=Please transfer: 
bitcoinWalletNotAvailable=Wallet information not available. :(
usd=$
cancel=Cancel
done=Done
off=off
on=on
notifications=notifications
ok=Ok
notifications=Show payment notifications
moneyAdd=Use your debit/credit card
moneyAddSubTitle=No Bitcoin needed!
outsideUSAPayment=Need to buy Bitcoin outside of the USA?
Expand All @@ -71,6 +73,11 @@ add=Fund with debit/credit
transferTime=Transfer may take up to 40 minutes
addFundsTitle=Add funds…
addFunds=Three ways to add funds to your Brave Wallet
copy=Copy
firstKey=Key 1
secondKey=Key 2
firstRecoveryKey=Recovery Key 1
secondRecoveryKey=Recovery Key 2
copyToClipboard=Copy to clipboard
smartphoneTitle=Use your smartphone app to transfer Bitcoin
displayQRCode=Display QR code
Expand Down Expand Up @@ -110,6 +117,21 @@ offerSearchSuggestions=Autocomplete search term as you type
doNotTrackTitle=Do Not Track
doNotTrack=Send a 'Do Not Track' header with browsing requests (requires browser restart)
blockCanvasFingerprinting=Fingerprinting Protection (may break some websites)
advancedSettings=Advanced Settings...
advancedSettingsTitle=Advanced Settings for Brave Payments
ledgerRecoveryTitle=Recover your Brave wallet
ledgerRecoverySubtitle=Enter your recovery keys below
ledgerRecoveryContent=The balance of the recovered wallet will be transferred to your new Brave wallet. The old wallet will still exist as an empty wallet.
ledgerBackupTitle=Backup your Brave wallet
ledgerBackupContent=Below, you will find the anonymized recovery keys that are required if you ever lose access to this computer.
minimumPageTimeSetting=Minimum page time before logging a visit
minimumVisitsSetting=Minimum visits for publisher relevancy
backupLedger=Backup your wallet
balanceRecovered={{balance}} BTC was recovered and transferred to your Brave wallet.
recoverLedger=Recover your wallet
recover=Recover
printKeys=Print keys
saveRecoveryFile=Save recovery file...
advancedPrivacySettings=Advanced Privacy Settings:
braveryDefaults=Bravery Defaults
blockAttackSites=Block reported attack sites (not available yet)
Expand Down
2 changes: 2 additions & 0 deletions app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const contentSettings = require('../js/state/contentSettings')
const privacy = require('../js/state/privacy')
const basicAuth = require('./browser/basicAuth')
const async = require('async')
const tabs = require('./browser/tabs')

// temporary fix for #4517, #4518 and #4472
app.commandLine.appendSwitch('enable-use-zoom-for-dsf', 'false')
Expand Down Expand Up @@ -415,6 +416,7 @@ app.on('ready', () => {
Menu.init(initialState, null)
return loadedPerWindowState
}).then((loadedPerWindowState) => {
tabs.init()
basicAuth.init()
contentSettings.init()
privacy.init()
Expand Down
86 changes: 84 additions & 2 deletions app/ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const appStore = require('../js/stores/appStore')
const eventStore = require('../js/stores/eventStore')
const rulesolver = require('./extensions/brave/content/scripts/pageInformation')
const ledgerUtil = require('./common/lib/ledgerUtil')
const Tabs = require('./browser/tabs')
const {fileUrl} = require('../js/lib/appUrlUtil')

// TBD: remove these post beta [MTR]
const logPath = 'ledger-log.json'
Expand Down Expand Up @@ -133,9 +135,25 @@ const doAction = (action) => {
case settings.PAYMENTS_ENABLED:
initialize(action.value)
break

case settings.PAYMENTS_CONTRIBUTION_AMOUNT:
setPaymentInfo(action.value)
break

case settings.MINIMUM_VISIT_TIME:
if (action.value <= 0) break

synopsis.options.minDuration = action.value
updatePublisherInfo()
break

case settings.MINIMUM_VISTS:
if (action.value <= 0) break

synopsis.options.minPublisherVisits = action.value
updatePublisherInfo()
break

default:
break
}
Expand Down Expand Up @@ -215,6 +233,51 @@ var boot = () => {
})
}

/*
* Print or Save Recovery Keys
*/

var backupKeys = (appState, action) => {
const paymentId = appState.getIn(['ledgerInfo', 'paymentId'])
const passphrase = appState.getIn(['ledgerInfo', 'passphrase'])
const message = locale.translation('ledgerBackupText', {paymentId, passphrase})
const filePath = path.join(app.getPath('userData'), '/brave_wallet_recovery.txt')

fs.writeFile(filePath, message, (err) => {
if (err) {
console.log(err)
} else {
Tabs.create({url: fileUrl(filePath)}).then((webContents) => {
if (action.backupAction === 'print') {
webContents.print({silent: false, printBackground: false})
} else {
webContents.downloadURL(fileUrl(filePath))
}
}).catch((err) => {
console.error(err)
})
}
})

return appState
}

/*
* Recover Ledger Keys
*/

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

if (err) {
setImmediate(() => appActions.ledgerRecoveryFailed())
} else {
setImmediate(() => appActions.ledgerRecoverySucceeded())
}
})

return appState
}

/*
* IPC entry point
*/
Expand Down Expand Up @@ -571,6 +634,8 @@ var enable = (paymentsEnabled) => {
*/

var publisherInfo = {
options: undefined,

synopsis: undefined,

_internal: {
Expand Down Expand Up @@ -601,13 +666,15 @@ var updatePublisherInfo = () => {
syncWriter(pathName(synopsisPath), synopsis, () => {})
publisherInfo.synopsis = synopsisNormalizer()

publisherInfo.synopsisOptions = synopsis.options

if (publisherInfo._internal.debugP) {
data = []
publisherInfo.synopsis.forEach((entry) => {
data.push(underscore.extend(underscore.omit(entry, [ 'faviconURL' ]), { faviconURL: entry.faviconURL && '...' }))
})

console.log('\nupdatePublisherInfo: ' + JSON.stringify(data, null, 2))
console.log('\nupdatePublisherInfo: ' + JSON.stringify({ options: publisherInfo.synopsisOptions, synopsis: data }, null, 2))
}

appActions.updatePublisherInfo(underscore.omit(publisherInfo, [ '_internal' ]))
Expand Down Expand Up @@ -871,6 +938,14 @@ var ledgerInfo = {
buyURL: undefined,
bravery: undefined,

// wallet credentials
paymentId: undefined,
passphrase: undefined,

// advanced ledger settings
minDuration: undefined,
minPublisherVisits: undefined,

hasBitcoinHandler: false,

// geoIP/exchange information
Expand Down Expand Up @@ -1109,6 +1184,12 @@ var getStateInfo = (state) => {
var info = state.paymentInfo
var then = underscore.now() - msecs.year

ledgerInfo.paymentId = state.properties.wallet.paymentId
ledgerInfo.passphrase = state.properties.wallet.keychains.passphrase

ledgerInfo.minDuration = synopsis.options.minDuration
ledgerInfo.minPublisherVisits = synopsis.options.minPublisherVisits

ledgerInfo.created = !!state.properties.wallet
ledgerInfo.creating = !ledgerInfo.created

Expand Down Expand Up @@ -1230,7 +1311,6 @@ var getPaymentInfo = () => {

info = underscore.extend(info, underscore.pick(body, [ 'buyURL', 'buyURLExpires', 'balance', 'unconfirmed', 'satoshis' ]))
info.address = client.getWalletAddress()
info.passphrase = client.getWalletPassphrase()
if ((amount) && (currency)) {
info = underscore.extend(info, { amount: amount, currency: currency })
if ((body.rates) && (body.rates[currency])) {
Expand Down Expand Up @@ -1452,6 +1532,8 @@ const showNotificationPaymentDone = (transactionContributionFiat) => {

module.exports = {
init: init,
recoverKeys: recoverKeys,
backupKeys: backupKeys,
quit: quit,
boot: boot
}
9 changes: 8 additions & 1 deletion app/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var rendererIdentifiers = function () {
'deleteBookmark',
'deleteHistoryEntry',
'deleteLedgerEntry',
'ledgerBackupText',
'editFolder',
'editBookmark',
'unmuteTabs',
Expand Down Expand Up @@ -229,13 +230,19 @@ var ctx = null
var translations = {}
var lang = 'en-US'

// todo: FSI/PDI stripping can probably be replaced once
// https://github.com/l20n/l20n.js/commit/2fea50bf43c43a8e930a519a37f0f64f3626e885
// is released
const FSI = '\u2068'
const PDI = '\u2069'

// Return a translate token from cache or a placeholder
// indicating that no translation is available
exports.translation = function (token, replacements = {}) {
if (translations[token]) {
let returnVal = translations[token]
for (var key in replacements) {
returnVal = returnVal.replace(new RegExp('{{\\s*' + key + '\\s*}}'), replacements[key])
returnVal = returnVal.replace(new RegExp(FSI + '{{\\s*' + key + '\\s*}}' + PDI), replacements[key])
}
return returnVal
} else {
Expand Down
4 changes: 4 additions & 0 deletions app/sessionStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ module.exports.cleanAppData = (data, isShutdown) => {
data.temporarySiteSettings = {}
// Delete Flash state since this is checked on startup
delete data.flashInitialized
// Delete Recovery status on shut down
try {
delete data.ui.about.preferences.recoverySucceeded
} catch (e) {}
// We used to store a huge list of IDs but we didn't use them.
// Get rid of them here.
delete data.windows
Expand Down
12 changes: 12 additions & 0 deletions docs/appActions.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ Dispatches a message to clear all completed downloads



### ledgerRecoverySucceeded()

Dispatches a message to clear all completed downloads



### ledgerRecoveryFailed()

Dispatches a message to clear all completed downloads



### setDefaultWindowSize(size)

Sets the default window size
Expand Down
Loading