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

Commit

Permalink
Merge pull request #13504 from jasonrsadler/gen-backup-remind
Browse files Browse the repository at this point in the history
Implemented notification for user to backup Brave wallet
  • Loading branch information
NejcZdovc authored Apr 26, 2018
2 parents 31b5ebb + cf5bea5 commit a2256bc
Show file tree
Hide file tree
Showing 16 changed files with 387 additions and 15 deletions.
10 changes: 9 additions & 1 deletion app/browser/api/ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const BigNumber = require('bignumber.js')
const appActions = require('../../../js/actions/appActions')

// State
const aboutPreferencesState = require('../../common/state/aboutPreferencesState')
const ledgerState = require('../../common/state/ledgerState')
const pageDataState = require('../../common/state/pageDataState')
const updateState = require('../../common/state/updateState')
Expand Down Expand Up @@ -1101,7 +1102,9 @@ const backupKeys = (state, backupAction) => {

if (backupAction === 'print') {
tabs.create({url: appUrlUtil.aboutUrls.get('about:printkeys')})
return

// we do not check whether the user actually printed the backup word list
return aboutPreferencesState.setBackupStatus(state, true)
}

const dialog = electron.dialog
Expand All @@ -1117,11 +1120,13 @@ const backupKeys = (state, backupAction) => {
if (file) {
try {
fs.writeFileSync(file, message)
appActions.onLedgerBackupSuccess()
} catch (e) {
console.error('Problem saving backup keys')
}
}
})
return state
}

const fileRecoveryKeys = (state, recoveryKeyFile) => {
Expand Down Expand Up @@ -1841,6 +1846,9 @@ const onWalletProperties = (state, body) => {
const balance = parseFloat(body.get('balance'))
if (balance >= 0) {
state = ledgerState.setInfoProp(state, 'balance', balance)
if (balance > 0) {
state = ledgerState.setInfoProp(state, 'userHasFunded', true)
}
lockInContributionAmount(state, balance)
}

Expand Down
97 changes: 88 additions & 9 deletions app/browser/api/ledgerNotifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const messages = require('../../../js/constants/messages')
const settings = require('../../../js/constants/settings')

// State
const aboutPreferencesState = require('../../common/state/aboutPreferencesState')
const ledgerState = require('../../common/state/ledgerState')

// Actions
Expand All @@ -26,23 +27,31 @@ const text = {
paymentDone: undefined,
addFunds: locale.translation('addFundsNotification'),
tryPayments: locale.translation('notificationTryPayments'),
reconciliation: locale.translation('reconciliationNotification')
reconciliation: locale.translation('reconciliationNotification'),
backupKeys: locale.translation('backupKeys')
}

const pollingInterval = 15 * ledgerUtil.milliseconds.minute // 15 * minutes
const pollingInterval = process.env.LEDGER_NOTIFICATION ? ledgerUtil.milliseconds.second * 5 : 15 * ledgerUtil.milliseconds.minute // 15 * minute default production
let intervalTimeout
const displayOptions = {
style: 'greetingStyle',
persist: false
}
const nextAddFundsTime = 3 * ledgerUtil.milliseconds.day
let backupNotifyInterval = process.env.LEDGER_NOTIFICATION ? [2 * ledgerUtil.milliseconds.minute, 5 * ledgerUtil.milliseconds.minute] : [7 * ledgerUtil.milliseconds.day, 14 * ledgerUtil.milliseconds.day]

const sufficientBalanceToReconcile = (state) => {
const balance = Number(ledgerState.getInfoProp(state, 'balance') || 0)
const unconfirmed = Number(ledgerState.getInfoProp(state, 'unconfirmed') || 0)
const budget = ledgerState.getContributionAmount(state)
return balance + unconfirmed >= budget
}

const hasFunded = (state) => {
return getSetting(settings.PAYMENTS_ENABLED)
? ledgerState.getInfoProp(state, 'userHasFunded') || false
: false
}
const shouldShowNotificationReviewPublishers = () => {
const nextTime = getSetting(settings.PAYMENTS_NOTIFICATION_RECONCILE_SOON_TIMESTAMP)
return !nextTime || (new Date().getTime() > nextTime)
Expand All @@ -63,9 +72,12 @@ const init = () => {
}

const onInterval = (state) => {
if (process.env.LEDGER_NOTIFICATION) {
runDebugCounter()
}
if (getSetting(settings.PAYMENTS_ENABLED)) {
if (getSetting(settings.PAYMENTS_NOTIFICATIONS)) {
module.exports.showEnabledNotifications(state)
state = module.exports.showEnabledNotifications(state)
}
} else {
module.exports.showDisabledNotifications(state)
Expand Down Expand Up @@ -140,6 +152,16 @@ const onResponse = (message, buttonIndex, activeWindow) => {
appActions.changeSetting(settings.PAYMENTS_NOTIFICATION_TRY_PAYMENTS_DISMISSED, true)
break

case text.backupKeys:
if (buttonIndex === 0) {
appActions.changeSetting(settings.PAYMENTS_NOTIFICATIONS, false)
} else if (buttonIndex === 2 && activeWindow) {
appActions.createTabRequested({
url: 'about:preferences#payments?ledgerBackupOverlayVisible',
windowId: activeWindow.id
})
}
break
default:
return
}
Expand Down Expand Up @@ -183,14 +205,17 @@ const onDynamicResponse = (message, actionId, activeWindow) => {
* a day in the future and balance is too low.
* 24 hours prior to reconciliation, show message asking user to review
* their votes.
*
* If not time to reconcile, check to show backup notification ()
*/
const showEnabledNotifications = (state) => {
const now = new Date().getTime()
let bootStamp = ledgerState.getInfoProp(state, 'bootStamp')
const reconcileStamp = ledgerState.getInfoProp(state, 'reconcileStamp')
if (!reconcileStamp) {
return
return state
}

if (reconcileStamp - new Date().getTime() < ledgerUtil.milliseconds.day) {
if (reconcileStamp - now < ledgerUtil.milliseconds.day) {
if (sufficientBalanceToReconcile(state)) {
if (shouldShowNotificationReviewPublishers()) {
const reconcileFrequency = ledgerState.getInfoProp(state, 'reconcileFrequency')
Expand All @@ -199,11 +224,38 @@ const showEnabledNotifications = (state) => {
} else if (shouldShowNotificationAddFunds()) {
showAddFunds()
}
} else if (reconcileStamp - new Date().getTime() < 2 * ledgerUtil.milliseconds.day) {
} else if (reconcileStamp - now < 2 * ledgerUtil.milliseconds.day) {
if (sufficientBalanceToReconcile(state) && (shouldShowNotificationReviewPublishers())) {
showReviewPublishers(new Date().getTime() + ledgerUtil.milliseconds.day)
showReviewPublishers(now + ledgerUtil.milliseconds.day)
}
} else if (hasFunded(state) && !aboutPreferencesState.hasBeenBackedUp(state)) {
const backupNotifyCount = aboutPreferencesState.getPreferencesProp(state, 'backupNotifyCount') || 0
const backupNotifyTimestamp = aboutPreferencesState.getPreferencesProp(state, 'backupNotifyTimestamp') || backupNotifyInterval[0]
if (!bootStamp) {
bootStamp = now
state = ledgerState.setInfoProp(state, 'bootStamp', bootStamp)
}
if (now - bootStamp > backupNotifyTimestamp) {
const nextTime = backupNotifyTimestamp + getNextBackupNotification(state, backupNotifyCount + 1, backupNotifyInterval) // set next time to notify case for remind later
state = aboutPreferencesState.setPreferencesProp(state, 'backupNotifyCount', (backupNotifyCount + 1))
state = aboutPreferencesState.setPreferencesProp(state, 'backupNotifyTimestamp', nextTime)
module.exports.showBackupKeys(nextTime)
}
if (process.env.LEDGER_NOTIFICATION) {
watchNotificationTimers(now, bootStamp, backupNotifyCount, backupNotifyTimestamp)
}
}
return state
}

const getNextBackupNotification = (state, count, interval) => {
if (count >= (interval && interval.constructor === Array ? interval.length : 0)) {
if (process.env.LEDGER_NOTIFICATION) {
return ledgerUtil.milliseconds.minute
}
return ledgerUtil.milliseconds.month
}
return interval[count]
}

const showDisabledNotifications = (state) => {
Expand All @@ -226,6 +278,20 @@ const showDisabledNotifications = (state) => {
}
}

const showBackupKeys = (nextTime) => {
appActions.showNotification({
position: 'global',
greeting: text.hello,
message: text.backupKeys,
buttons: [
{text: locale.translation('turnOffNotifications')},
{text: locale.translation('updateLater')},
{text: locale.translation('backupKeysNow'), className: 'primaryButton'}
],
options: displayOptions
})
}

const showReviewPublishers = (nextTime) => {
appActions.changeSetting(settings.PAYMENTS_NOTIFICATION_RECONCILE_SOON_TIMESTAMP, nextTime)

Expand Down Expand Up @@ -326,6 +392,16 @@ const removePromotionNotification = (state) => {
appActions.hideNotification(notification.get('message'))
}

const watchNotificationTimers = (now, bootStamp, backupNotifyCount, backupNotifyTimestamp) => { // for testing
console.log('now - bootstamp: ' + (now - bootStamp))
console.log('count: ' + backupNotifyCount)
console.log('backupNotifyTimestamp: ' + backupNotifyTimestamp)
}

const runDebugCounter = () => {
console.log(new Date().getTime() / ledgerUtil.milliseconds.second)
}

if (ipc) {
ipc.on(messages.NOTIFICATION_RESPONSE, (e, message, buttonIndex, checkbox, index, buttonActionId) => {
if (buttonActionId) {
Expand Down Expand Up @@ -355,7 +431,10 @@ const getMethods = () => {
showDisabledNotifications,
showEnabledNotifications,
onIntervalDynamic,
showPromotionNotification
showPromotionNotification,
showBackupKeys,
hasFunded,
getNextBackupNotification
}

let privateMethods = {}
Expand Down
8 changes: 7 additions & 1 deletion app/browser/reducers/ledgerReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const tabActionConstants = require('../../common/constants/tabAction')
const ledgerState = require('../../common/state/ledgerState')
const pageDataState = require('../../common/state/pageDataState')
const updateState = require('../../common/state/updateState')
const aboutPreferencesState = require('../../common/state/aboutPreferencesState')
const tabState = require('../../common/state/tabState')

// Utils
Expand Down Expand Up @@ -46,7 +47,7 @@ const ledgerReducer = (state, action, immutableAction) => {
}
case appConstants.APP_BACKUP_KEYS:
{
ledgerApi.backupKeys(state, action.get('backupAction'))
state = ledgerApi.backupKeys(state, action.get('backupAction'))
break
}
case appConstants.APP_RECOVER_WALLET:
Expand Down Expand Up @@ -513,6 +514,11 @@ const ledgerReducer = (state, action, immutableAction) => {
)
break
}
case appConstants.APP_ON_LEDGER_BACKUP_SUCCESS:
{
state = aboutPreferencesState.setBackupStatus(state, true)
break
}
}
return state
}
Expand Down
1 change: 1 addition & 0 deletions app/common/lib/ledgerUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ const defaultMonthlyAmounts = Immutable.List([5.0, 7.5, 10.0, 17.5, 25.0, 50.0,

const milliseconds = {
year: 365 * 24 * 60 * 60 * 1000,
month: (365 * 24 * 60 * 60 * 1000) / 12,
week: 7 * 24 * 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
Expand Down
48 changes: 48 additions & 0 deletions app/common/state/aboutPreferencesState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

const assert = require('assert')

// utils
const {makeImmutable, isMap} = require('../../common/state/immutableUtil')

const validateState = (state) => {
state = makeImmutable(state)
assert.ok(isMap(state), 'state must be an Immutable.Map')
assert.ok(isMap(state.getIn(['about', 'preferences'])), 'state must contain an Immutable.Map of \'about\' \'preferences\'')
return state
}
const aboutPreferencesState = {
setBackupStatus: (state, status) => {
state = validateState(state)
if (status == null) {
return state
}
const date = new Date().getTime()
state = aboutPreferencesState.setPreferencesProp(state, 'backupSucceeded', status)
return aboutPreferencesState.setPreferencesProp(state, 'updatedStamp', date)
},

hasBeenBackedUp: (state) => {
state = validateState(state)
return (aboutPreferencesState.getPreferencesProp(state, 'backupSucceeded') || aboutPreferencesState.getPreferencesProp(state, 'updatedStamp') != null) || false
},

getPreferencesProp: (state, key) => {
state = validateState(state)
if (key == null) {
return null
}
return state.getIn(['about', 'preferences', key])
},

setPreferencesProp: (state, key, value) => {
state = validateState(state)
if (key == null) {
return state
}
return state.setIn(['about', 'preferences', key], value)
}
}
module.exports = aboutPreferencesState
2 changes: 2 additions & 0 deletions app/extensions/brave/locales/en-US/app.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ autocompleteData=Autocomplete data
autofillData=Autofill data
back=Back
backButton.title=Go back
backupKeys=Let's backup your Brave wallet. It only takes a second...
backupKeysNow=Backup Now
basicAuthMessage={{host}} requires a username and password.
basicAuthPasswordLabel=Password
basicAuthRequired=Authentication Required
Expand Down
2 changes: 2 additions & 0 deletions app/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ var rendererIdentifiers = function () {
'ledgerBackupText3',
'ledgerBackupText4',
'ledgerBackupText5',
'backupKeys',
'backupKeysNow',
'editFolder',
'editBookmark',
'unmuteTabs',
Expand Down
2 changes: 2 additions & 0 deletions app/renderer/components/preferences/payment/ledgerBackup.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ const globalStyles = require('../../styles/global')

// other
const aboutActions = require('../../../../../js/about/aboutActions')
const appActions = require('../../../../../js/actions/appActions')

class LedgerBackupContent extends ImmutableComponent {
copyToClipboard (text) {
aboutActions.setClipboard(text)
appActions.onLedgerBackupSuccess()
}

render () {
Expand Down
1 change: 1 addition & 0 deletions app/sessionStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,7 @@ module.exports.defaultAppState = () => {
ignoredTopSites: [],
pinnedTopSites: []
},
preferences: {},
welcome: {
showOnLoad: !['test', 'development'].includes(process.env.NODE_ENV)
}
Expand Down
6 changes: 5 additions & 1 deletion docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ AppStore
updatedStamp: number // timestamp for when the data was last updated
},
preferences: {
backupNotifyCount: number, // number of times user has been reminded to backup wallet
backupNotifyTimestamp: number, // number of milliseconds from the last reminder until the next
backupSucceeded: (boolean|undefined), // was last backup successful?
recoverySucceeded: (boolean|undefined),
updatedStamp: number
}
Expand Down Expand Up @@ -240,7 +243,8 @@ AppStore
submissionStamp: number, // timestamp for this contribution
viewingId: string, // UUIDv4 for this contribution
}],
unconfirmed: string // unconfirmed balance in BAT.toFixed(2)
unconfirmed: string, // unconfirmed balance in BAT.toFixed(2)
userHasFunded: boolean // permanently true once user funds wallet
},
locations: {
[url]: {
Expand Down
6 changes: 6 additions & 0 deletions js/actions/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,12 @@ const appActions = {
duration,
revisited
})
},

onLedgerBackupSuccess: function () {
dispatch({
actionType: appConstants.APP_ON_LEDGER_BACKUP_SUCCESS
})
}
}

Expand Down
1 change: 1 addition & 0 deletions js/constants/appConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ const appConstants = {
APP_CHECK_REFERRAL_ACTIVITY: _,
APP_ON_REFERRAL_ACTIVITY: _,
APP_ON_LEDGER_MEDIA_PUBLISHER: _,
APP_ON_LEDGER_BACKUP_SUCCESS: _,
APP_ADD_PUBLISHER_TO_LEDGER: _
}

Expand Down
Loading

0 comments on commit a2256bc

Please sign in to comment.