From efacb7b1706d38b6b962cfe9ebdce2c37fe2d246 Mon Sep 17 00:00:00 2001 From: NejcZdovc Date: Thu, 9 Nov 2017 12:27:10 +0100 Subject: [PATCH 1/3] Adds UGP support Resolves #11100 Auditors: Test Plan: --- app/browser/api/ledger.js | 167 +++++-- app/browser/api/ledgerNotifications.js | 144 +++++- app/browser/reducers/ledgerReducer.js | 32 ++ app/browser/tabs.js | 1 + app/common/lib/ledgerUtil.js | 12 +- app/common/state/ledgerState.js | 147 +++++- .../locales/en-US/preferences.properties | 7 +- .../components/main/notificationItem.js | 10 +- .../preferences/payment/disabledContent.js | 103 ++-- .../preferences/payment/enabledContent.js | 121 ++++- .../components/preferences/paymentsTab.js | 4 +- app/sessionStore.js | 3 +- docs/state.md | 77 +++ js/actions/appActions.js | 37 ++ js/constants/appConstants.js | 6 + less/about/preferences.less | 34 ++ package-lock.json | 452 +++++++++++------- package.json | 4 +- test/unit/about/preferencesTest.js | 1 + .../browser/api/ledgerNotificationsTest.js | 342 ++++++++++++- test/unit/app/browser/api/ledgerTest.js | 103 +++- .../components/preferences/paymentsTabTest.js | 1 + 22 files changed, 1497 insertions(+), 311 deletions(-) diff --git a/app/browser/api/ledger.js b/app/browser/api/ledger.js index f62b4015c7f..bd18d8802ce 100644 --- a/app/browser/api/ledger.js +++ b/app/browser/api/ledger.js @@ -71,6 +71,8 @@ let synopsis // Timers let balanceTimeoutId = false let runTimeoutId +let promotionTimeoutId +let togglePromotionTimeoutId // Database let v2RulesetDB @@ -79,14 +81,6 @@ const statePath = 'ledger-state.json' const newClientPath = 'ledger-newstate.json' // Definitions -const miliseconds = { - year: 365 * 24 * 60 * 60 * 1000, - week: 7 * 24 * 60 * 60 * 1000, - day: 24 * 60 * 60 * 1000, - hour: 60 * 60 * 1000, - minute: 60 * 1000, - second: 1000 -} const clientOptions = { debugP: process.env.LEDGER_DEBUG, loggingP: process.env.LEDGER_LOGGING, @@ -94,7 +88,8 @@ const clientOptions = { verboseP: process.env.LEDGER_VERBOSE, server: process.env.LEDGER_SERVER_URL, createWorker: electron.app.createWorker, - version: 'v2' + version: 'v2', + environment: 'staging' // TODO remove before merge } const fileTypes = { bmp: Buffer.from([0x42, 0x4d]), @@ -152,7 +147,7 @@ const paymentPresent = (state, tabId, present) => { if (Object.keys(ledgerPaymentsPresent).length > 0 && getSetting(settings.PAYMENTS_ENABLED)) { if (!balanceTimeoutId) { - getBalance(state) + module.exports.getBalance(state) } } else if (balanceTimeoutId) { clearTimeout(balanceTimeoutId) @@ -164,8 +159,8 @@ const addFoundClosed = (state) => { if (balanceTimeoutId) { clearTimeout(balanceTimeoutId) } - const balanceFn = getBalance.bind(null, state) - balanceTimeoutId = setTimeout(balanceFn, 5 * miliseconds.second) + const balanceFn = module.exports.getBalance.bind(null, state) + balanceTimeoutId = setTimeout(balanceFn, 5 * ledgerUtil.miliseconds.second) } const boot = () => { @@ -205,10 +200,10 @@ const onBootStateFile = (state) => { } if (client.sync(callback) === true) { - run(random.randomInt({min: miliseconds.minute, max: 10 * miliseconds.minute})) + run(random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute})) } - getBalance(state) + module.exports.getBalance(state) bootP = false @@ -306,16 +301,16 @@ const getPublisherData = (result, scorekeeper) => { }) } - if (duration >= miliseconds.day) { - data.daysSpent = Math.max(Math.round(duration / miliseconds.day), 1) - } else if (duration >= miliseconds.hour) { - data.hoursSpent = Math.max(Math.floor(duration / miliseconds.hour), 1) - data.minutesSpent = Math.round((duration % miliseconds.hour) / miliseconds.minute) - } else if (duration >= miliseconds.minute) { - data.minutesSpent = Math.max(Math.floor(duration / miliseconds.minute), 1) - data.secondsSpent = Math.round((duration % miliseconds.minute) / miliseconds.second) + if (duration >= ledgerUtil.miliseconds.day) { + data.daysSpent = Math.max(Math.round(duration / ledgerUtil.miliseconds.day), 1) + } else if (duration >= ledgerUtil.miliseconds.hour) { + data.hoursSpent = Math.max(Math.floor(duration / ledgerUtil.miliseconds.hour), 1) + data.minutesSpent = Math.round((duration % ledgerUtil.miliseconds.hour) / ledgerUtil.miliseconds.minute) + } else if (duration >= ledgerUtil.miliseconds.minute) { + data.minutesSpent = Math.max(Math.floor(duration / ledgerUtil.miliseconds.minute), 1) + data.secondsSpent = Math.round((duration % ledgerUtil.miliseconds.minute) / ledgerUtil.miliseconds.second) } else { - data.secondsSpent = Math.max(Math.round(duration / miliseconds.second), 1) + data.secondsSpent = Math.max(Math.round(duration / ledgerUtil.miliseconds.second), 1) } if (_internal.verboseP) { @@ -584,7 +579,7 @@ const excludeP = (publisherKey, callback) => { } if (!v2RulesetDB) { - return setTimeout(() => excludeP(publisherKey, callback), 5 * miliseconds.second) + return setTimeout(() => excludeP(publisherKey, callback), 5 * ledgerUtil.miliseconds.second) } inspectP(v2RulesetDB, v2RulesetPath, publisherKey, 'exclude', 'domain:' + publisherKey, (err, result) => { @@ -1016,7 +1011,7 @@ const onWalletRecovery = (state, error, result) => { if (balanceTimeoutId) { clearTimeout(balanceTimeoutId) } - getBalance(state) + module.exports.getBalance(state) state = ledgerState.setRecoveryStatus(state, true) } @@ -1102,6 +1097,28 @@ const enable = (state, paymentsEnabled) => { } } + if (paymentsEnabled === getSetting(settings.PAYMENTS_ENABLED)) { + // on start + if (!promotionTimeoutId) { + clearInterval(promotionTimeoutId) + } + promotionTimeoutId = setInterval(getPromotion, 24 * ledgerUtil.miliseconds.hour) + + if (!togglePromotionTimeoutId) { + clearTimeout(togglePromotionTimeoutId) + } + togglePromotionTimeoutId = setTimeout(getPromotion, 15 * ledgerUtil.miliseconds.second) + } else if (paymentsEnabled) { + // on toggle + const promotion = ledgerState.getPromotionNotification(state) + if (!promotion.isEmpty()) { + appActions.hideNotification(promotion.get('message')) + } + + state = ledgerState.setActivePromotion(state, paymentsEnabled) + getPromotion() + } + if (synopsis) { return updatePublisherInfo(state, null, true) } @@ -1332,7 +1349,7 @@ const observeTransactions = (state, transactions) => { // TODO convert this function and related ones to immutable const getStateInfo = (state, parsedData) => { const info = parsedData.paymentInfo - const then = new Date().getTime() - miliseconds.year + const then = new Date().getTime() - ledgerUtil.miliseconds.year if (!parsedData.properties.wallet) { return state @@ -1551,7 +1568,7 @@ const setPaymentInfo = (amount) => { // wallet being created... return setTimeout(function () { setPaymentInfo(amount) - }, 2 * miliseconds.second) + }, 2 * ledgerUtil.miliseconds.second) } amount = parseInt(amount, 10) @@ -1595,8 +1612,8 @@ const onBraveryProperties = (state, error, result) => { const getBalance = (state) => { if (!client) return - const balanceFn = getBalance.bind(null, state) - balanceTimeoutId = setTimeout(balanceFn, 1 * miliseconds.minute) + const balanceFn = module.exports.getBalance.bind(null, state) + balanceTimeoutId = setTimeout(balanceFn, 1 * ledgerUtil.miliseconds.minute) return getPaymentInfo(state) } @@ -1611,7 +1628,7 @@ const callback = (err, result, delayTime) => { if (!client) return if (typeof delayTime === 'undefined') { - delayTime = random.randomInt({min: miliseconds.minute, max: 10 * miliseconds.minute}) + delayTime = random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute}) } } @@ -1704,7 +1721,7 @@ const initialize = (state, paymentsEnabled) => { if (!v2RulesetDB) v2RulesetDB = levelUp(pathName(v2RulesetPath)) state = enable(state, paymentsEnabled) - ledgerNotifications.init(state) + ledgerNotifications.init() if (!paymentsEnabled) { client = null @@ -1808,7 +1825,7 @@ const onInitRead = (state, parsedData) => { // enables it again -> reconcileStamp is in the past. // In this case reset reconcileStamp to the future. try { - timeUntilReconcile = client.timeUntilReconcile() + timeUntilReconcile = client.timeUntilReconcile(synopsis) } catch (ex) {} let ledgerWindow = (ledgerState.getSynopsisOption(state, 'numFrames') - 1) * ledgerState.getSynopsisOption(state, 'frameSize') @@ -1835,7 +1852,7 @@ const onInitRead = (state, parsedData) => { } appActions.onLedgerFirstSync(parsedData) - }, 3 * miliseconds.second) + }, 3 * ledgerUtil.miliseconds.second) // Make sure bravery props are up-to-date with user settings const address = ledgerState.getInfoProp(state, 'address') @@ -1845,7 +1862,7 @@ const onInitRead = (state, parsedData) => { const contributionAmount = getContributionAmount() module.exports.setPaymentInfo(contributionAmount) - getBalance(state) + module.exports.getBalance(state) // Show relevant browser notifications on launch state = ledgerNotifications.onLaunch(state) @@ -1862,7 +1879,7 @@ const onTimeUntilReconcile = (state, stateResult) => { const onLedgerFirstSync = (state, parsedData) => { if (client.sync(callback) === true) { - run(state, random.randomInt({min: miliseconds.minute, max: 10 * miliseconds.minute})) + run(state, random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute})) } return cacheRuleSet(state, parsedData.ruleset) @@ -1947,12 +1964,12 @@ const run = (state, delayTime) => { if (delayTime === 0) { try { - delayTime = client.timeUntilReconcile() + delayTime = client.timeUntilReconcile(synopsis) } catch (ex) { delayTime = false } if (delayTime === false) { - delayTime = random.randomInt({min: miliseconds.minute, max: 10 * miliseconds.minute}) + delayTime = random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute}) } } @@ -1960,8 +1977,8 @@ const run = (state, delayTime) => { if (runTimeoutId) return const active = client - if (delayTime > (1 * miliseconds.hour)) { - delayTime = random.randomInt({min: 3 * miliseconds.minute, max: miliseconds.hour}) + if (delayTime > (1 * ledgerUtil.miliseconds.hour)) { + delayTime = random.randomInt({min: 3 * ledgerUtil.miliseconds.minute, max: ledgerUtil.miliseconds.hour}) } runTimeoutId = setTimeout(() => { @@ -1979,7 +1996,7 @@ const run = (state, delayTime) => { return } - if (client.isReadyToReconcile()) { + if (client.isReadyToReconcile(synopsis)) { client.reconcile(uuid.v4().toLowerCase(), callback) } } @@ -1989,7 +2006,7 @@ const networkConnected = () => { if (!client) return appActions.onNetworkConnected() - }, 1 * miliseconds.minute, true) + }, 1 * ledgerUtil.miliseconds.minute, true) } const onNetworkConnected = (state) => { @@ -1999,13 +2016,13 @@ const onNetworkConnected = (state) => { } if (client.sync(callback) === true) { - const delayTime = random.randomInt({min: miliseconds.minute, max: 10 * miliseconds.minute}) + const delayTime = random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute}) run(state, delayTime) } if (balanceTimeoutId) clearTimeout(balanceTimeoutId) - const newBalance = getBalance.bind(null, state) - balanceTimeoutId = setTimeout(newBalance, 5 * miliseconds.second) + const newBalance = module.exports.getBalance.bind(null, state) + balanceTimeoutId = setTimeout(newBalance, 5 * ledgerUtil.miliseconds.second) } const muonWriter = (fileName, payload) => { @@ -2197,7 +2214,7 @@ const transitionWalletToBat = () => { console.log('ledger client is currently busy; transition will be retried on next launch') return } - const delayTime = random.randomInt({ min: miliseconds.minute, max: 10 * miliseconds.minute }) + const delayTime = random.randomInt({ min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute }) console.log('ledger client is currently busy; transition will be retried shortly (this was attempt ' + busyRetryCount + ')') setTimeout(() => transitionWalletToBat(), delayTime) return @@ -2214,7 +2231,7 @@ const transitionWalletToBat = () => { client = newClient newClient = true // NOTE: onLedgerCallback will save latest client to disk as ledger-state.json - appActions.onLedgerCallback(result, random.randomInt({ min: miliseconds.minute, max: 10 * miliseconds.minute })) + appActions.onLedgerCallback(result, random.randomInt({ min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute })) appActions.onBitcoinToBatTransitioned() ledgerNotifications.showBraveWalletUpdated() client.publisherTimestamp((err, result) => { @@ -2366,6 +2383,56 @@ const onMediaPublisher = (state, mediaKey, response, duration, revisited) => { return state } +const getPromotion = () => { + let tempClient = client + if (!tempClient) { + clientprep() + tempClient = ledgerClient(null, underscore.extend({roundtrip: roundtrip}, clientOptions), null) + } + + tempClient.getPromotion((err, result) => { + if (err) { + console.error('Error retrieving promotion', err.toString()) + return + } + + appActions.saveLedgerPromotion(result) + }) +} + +const claimPromotion = (state) => { + if (!client) { + return + } + + const promotion = ledgerState.getPromotion(state) + if (promotion.isEmpty()) { + return + } + + client.setPromotion(promotion.get('promotionId'), (err) => { + if (err) { + console.error(`Problem claiming promotion ${err.toString()}`) + return + } + + appActions.onPromotionResponse() + }) +} + +const onPromotionResponse = (state) => { + ledgerNotifications.removePromotionNotification(state) + state = ledgerState.setPromotionProp(state, 'claimedTimestamp', new Date().getTime()) + + if (togglePromotionTimeoutId) { + clearTimeout(togglePromotionTimeoutId) + } + + module.exports.getBalance(state) + + return state +} + const getMethods = () => { const publicMethods = { backupKeys, @@ -2404,7 +2471,10 @@ const getMethods = () => { onMediaRequest, onMediaPublisher, saveVisit, - generatePaymentData + generatePaymentData, + claimPromotion, + onPromotionResponse, + getBalance } let privateMethods = {} @@ -2440,7 +2510,8 @@ const getMethods = () => { synopsisNormalizer, checkVerifiedStatus, roundtrip, - observeTransactions + observeTransactions, + getPromotion } } diff --git a/app/browser/api/ledgerNotifications.js b/app/browser/api/ledgerNotifications.js index 5f05d3d5273..a74d6b9001e 100644 --- a/app/browser/api/ledgerNotifications.js +++ b/app/browser/api/ledgerNotifications.js @@ -19,17 +19,9 @@ const appActions = require('../../../js/actions/appActions') // Utils const locale = require('../../locale') +const ledgerUtil = require('../../common/lib/ledgerUtil') const getSetting = require('../../../js/settings').getSetting -const miliseconds = { - year: 365 * 24 * 60 * 60 * 1000, - week: 7 * 24 * 60 * 60 * 1000, - day: 24 * 60 * 60 * 1000, - hour: 60 * 60 * 1000, - minute: 60 * 1000, - second: 1000 -} - const text = { hello: locale.translation('updateHello'), paymentDone: undefined, @@ -39,13 +31,13 @@ const text = { walletConvertedToBat: locale.translation('walletConvertedToBat') } -const pollingInterval = 15 * miliseconds.minute // 15 * minutes +const pollingInterval = 15 * ledgerUtil.miliseconds.minute // 15 * minutes let intervalTimeout const displayOptions = { style: 'greetingStyle', persist: false } -const nextAddFundsTime = 3 * miliseconds.day +const nextAddFundsTime = 3 * ledgerUtil.miliseconds.day const sufficientBalanceToReconcile = (state) => { const balance = Number(ledgerState.getInfoProp(state, 'balance') || 0) @@ -68,14 +60,14 @@ const shouldShowNotificationAddFunds = () => { return !nextTime || (new Date().getTime() > nextTime) } -const init = (state) => { +const init = () => { // Check if relevant browser notifications should be shown every 15 minutes if (intervalTimeout) { clearInterval(intervalTimeout) } - intervalTimeout = setInterval((state) => { - module.exports.onInterval(state) - }, pollingInterval, state) + intervalTimeout = setInterval(() => { + appActions.onLedgerNotificationInterval() + }, pollingInterval) } const onLaunch = (state) => { @@ -114,11 +106,34 @@ const onLaunch = (state) => { const onInterval = (state) => { if (getSetting(settings.PAYMENTS_ENABLED)) { if (getSetting(settings.PAYMENTS_NOTIFICATIONS)) { - showEnabledNotifications(state) + module.exports.showEnabledNotifications(state) } } else { - showDisabledNotifications(state) + module.exports.showDisabledNotifications(state) + } + + if (getSetting(settings.PAYMENTS_NOTIFICATIONS)) { + state = module.exports.onIntervalDynamic(state) } + + return state +} + +const onIntervalDynamic = (state) => { + const promotion = ledgerState.getPromotion(state) + const time = new Date().getTime() + + if (promotion.isEmpty()) { + return state + } + + const timestamp = promotion.get('remindTimestamp') + if (timestamp && timestamp !== -1 && time > timestamp) { + state = ledgerState.setPromotionProp(state, 'remindTimestamp', -1) + module.exports.showPromotionNotification(state) + } + + return state } const onResponse = (message, buttonIndex, activeWindow) => { @@ -139,7 +154,7 @@ const onResponse = (message, buttonIndex, activeWindow) => { break case text.reconciliation: -// buttonIndex === 1 is Dismiss + // buttonIndex === 1 is Dismiss if (buttonIndex === 0) { appActions.changeSetting(settings.PAYMENTS_NOTIFICATIONS, false) } else if (buttonIndex === 2 && activeWindow) { @@ -183,6 +198,32 @@ const onResponse = (message, buttonIndex, activeWindow) => { appActions.hideNotification(message) } +const onDynamicResponse = (message, actionId, activeWindow) => { + if (!message) { + return + } + + switch (actionId) { + case 'optInPromotion': + { + if (activeWindow) { + appActions.createTabRequested({ + url: 'about:preferences#payments', + windowId: activeWindow.id + }) + } + break + } + case 'remindLater': + { + appActions.onPromotionRemind() + break + } + } + + appActions.hideNotification(message) +} + /** * Show message that it's time to add funds if reconciliation is less than * a day in the future and balance is too low. @@ -195,18 +236,18 @@ const showEnabledNotifications = (state) => { return } - if (reconcileStamp - new Date().getTime() < miliseconds.day) { + if (reconcileStamp - new Date().getTime() < ledgerUtil.miliseconds.day) { if (sufficientBalanceToReconcile(state)) { if (shouldShowNotificationReviewPublishers()) { const reconcileFrequency = ledgerState.getInfoProp(state, 'reconcileFrequency') - showReviewPublishers(reconcileStamp + ((reconcileFrequency - 2) * miliseconds.day)) + showReviewPublishers(reconcileStamp + ((reconcileFrequency - 2) * ledgerUtil.miliseconds.day)) } } else if (shouldShowNotificationAddFunds()) { showAddFunds() } - } else if (reconcileStamp - new Date().getTime() < 2 * miliseconds.day) { + } else if (reconcileStamp - new Date().getTime() < 2 * ledgerUtil.miliseconds.day) { if (sufficientBalanceToReconcile(state) && (shouldShowNotificationReviewPublishers())) { - showReviewPublishers(new Date().getTime() + miliseconds.day) + showReviewPublishers(new Date().getTime() + ledgerUtil.miliseconds.day) } } } @@ -307,8 +348,54 @@ const showBraveWalletUpdated = () => { }) } +const onPromotionReceived = (state) => { + const promotion = ledgerState.getPromotionNotification(state) + + if (!promotion.isEmpty() && !promotion.has('firstShowTimestamp')) { + state = ledgerState.setPromotionNotificationProp(state, 'firstShowTimestamp', new Date().getTime()) + showPromotionNotification(state) + } + + return state +} + +const showPromotionNotification = (state) => { + const notification = ledgerState.getPromotionNotification(state) + + if ( + notification.isEmpty() || + ( + getSetting(settings.PAYMENTS_ENABLED) && + !getSetting(settings.PAYMENTS_NOTIFICATIONS) + ) + ) { + return + } + + appActions.showNotification(notification.toJS()) +} + +const removePromotionNotification = (state) => { + const notification = ledgerState.getPromotionNotification(state) + + if (notification.isEmpty()) { + return + } + + appActions.hideNotification(notification.get('message')) +} + if (ipc) { - ipc.on(messages.NOTIFICATION_RESPONSE, (e, message, buttonIndex) => { + ipc.on(messages.NOTIFICATION_RESPONSE, (e, message, buttonIndex, checkbox, index, buttonActionId) => { + if (buttonActionId) { + onDynamicResponse( + message, + buttonActionId, + electron.BrowserWindow.getActiveWindow() + ) + return + } + onResponse( message, buttonIndex, @@ -323,7 +410,13 @@ const getMethods = () => { init, onLaunch, showBraveWalletUpdated, - onInterval + onInterval, + onPromotionReceived, + removePromotionNotification, + showDisabledNotifications, + showEnabledNotifications, + onIntervalDynamic, + showPromotionNotification } let privateMethods = {} @@ -338,7 +431,8 @@ const getMethods = () => { }, getPollingInterval: () => { return pollingInterval - } + }, + onDynamicResponse } } diff --git a/app/browser/reducers/ledgerReducer.js b/app/browser/reducers/ledgerReducer.js index 9ea63224888..9065e78deb3 100644 --- a/app/browser/reducers/ledgerReducer.js +++ b/app/browser/reducers/ledgerReducer.js @@ -17,6 +17,7 @@ const migrationState = require('../../common/state/migrationState') // Utils const ledgerApi = require('../../browser/api/ledger') +const ledgerNotifications = require('../../browser/api/ledgerNotifications') const {makeImmutable} = require('../../common/state/immutableUtil') const getSetting = require('../../../js/settings').getSetting @@ -404,6 +405,22 @@ const ledgerReducer = (state, action, immutableAction) => { state = ledgerState.setLedgerValue(state, 'publisherTimestamp', action.get('timestamp')) break } + case appConstants.APP_SAVE_LEDGER_PROMOTION: + { + state = ledgerState.savePromotion(state, action.get('promotion')) + state = ledgerNotifications.onPromotionReceived(state) + break + } + case appConstants.APP_ON_PROMOTION_CLAIM: + { + ledgerApi.claimPromotion(state) + break + } + case appConstants.APP_ON_PROMOTION_REMIND: + { + state = ledgerState.remindMeLater(state) + break + } case appConstants.APP_ON_LEDGER_MEDIA_DATA: { state = ledgerApi.onMediaRequest(state, action.get('url'), action.get('type'), action.get('tabId')) @@ -420,6 +437,21 @@ const ledgerReducer = (state, action, immutableAction) => { state = ledgerState.saveSynopsis(state, publishers) break } + case appConstants.APP_ON_PROMOTION_RESPONSE: + { + state = ledgerApi.onPromotionResponse(state) + break + } + case appConstants.APP_ON_PROMOTION_REMOVAL: + { + state = ledgerState.removePromotion(state) + break + } + case appConstants.APP_ON_LEDGER_NOTIFICATION_INTERVAL: + { + state = ledgerNotifications.onInterval(state) + break + } case appConstants.APP_ON_LEDGER_MEDIA_PUBLISHER: { state = ledgerApi.onMediaPublisher( diff --git a/app/browser/tabs.js b/app/browser/tabs.js index f39b8d673cd..4c4b7f4eade 100644 --- a/app/browser/tabs.js +++ b/app/browser/tabs.js @@ -241,6 +241,7 @@ const updateAboutDetails = (tabId) => { .merge(preferencesData) .set('wizardData', wizardData) .set('migration', migration) + .set('promotion', ledgerState.getAboutPromotion(appState)) sendAboutDetails(tabId, messages.LEDGER_UPDATED, ledgerData) } else if (url === 'about:preferences#sync' || location === 'about:contributions' || onPaymentsPage) { const sync = appState.get('sync', Immutable.Map()) diff --git a/app/common/lib/ledgerUtil.js b/app/common/lib/ledgerUtil.js index c7b473c541b..b34d5174747 100644 --- a/app/common/lib/ledgerUtil.js +++ b/app/common/lib/ledgerUtil.js @@ -292,6 +292,15 @@ const getMediaProvider = (url) => { return provider } +const miliseconds = { + year: 365 * 24 * 60 * 60 * 1000, + week: 7 * 24 * 60 * 60 * 1000, + day: 24 * 60 * 60 * 1000, + hour: 60 * 60 * 1000, + minute: 60 * 1000, + second: 1000 +} + const getMethods = () => { const publicMethods = { shouldTrackView, @@ -309,7 +318,8 @@ const getMethods = () => { getMediaDuration, getMediaProvider, getMediaData, - getMediaKey + getMediaKey, + miliseconds } let privateMethods = {} diff --git a/app/common/state/ledgerState.js b/app/common/state/ledgerState.js index 9e505817af6..fbce1685cde 100644 --- a/app/common/state/ledgerState.js +++ b/app/common/state/ledgerState.js @@ -5,10 +5,17 @@ const Immutable = require('immutable') const assert = require('assert') +// Actions +const appActions = require('../../../js/actions/appActions') + // State const pageDataState = require('./pageDataState') +// Constants +const settings = require('../../../js/constants/settings') + // Utils +const getSetting = require('../../../js/settings').getSetting const siteSettings = require('../../../js/state/siteSettings') const urlUtil = require('../../../js/lib/urlutil') const {makeImmutable, isMap} = require('../../common/state/immutableUtil') @@ -326,7 +333,135 @@ const ledgerState = { return state }, - // About page + /** + * PROMOTIONS + */ + savePromotion: (state, promotion) => { + state = validateState(state) + + if (promotion == null) { + return state + } + + promotion = makeImmutable(promotion) + + const oldPromotion = ledgerState.getPromotion(state) + + if (promotion.get('promotionId') === oldPromotion.get('promotionId')) { + promotion = oldPromotion.mergeDeep(promotion) + } else { + if (!oldPromotion.isEmpty()) { + const notification = ledgerState.getPromotionNotification(state) + appActions.hideNotification(notification.get('message')) + } + + promotion = promotion.set('remindTimestamp', -1) + } + + state = state.setIn(['ledger', 'promotion'], promotion) + return ledgerState.setActivePromotion(state) + }, + + getPromotion: (state) => { + state = validateState(state) + + return state.getIn(['ledger', 'promotion']) || Immutable.Map() + }, + + setActivePromotion: (state, paymentsEnabled = null) => { + state = validateState(state) + const promotion = ledgerState.getPromotion(state) + + if (promotion.isEmpty()) { + return state + } + + if (paymentsEnabled === null) { + paymentsEnabled = getSetting(settings.PAYMENTS_ENABLED) + } + + let active = 'disabledWallet' + if (paymentsEnabled) { + const balance = ledgerState.getInfoProp(state, 'balance') || 0 + + if (balance > 0) { + active = 'fundedWallet' + } else { + active = 'emptyWallet' + } + } + + return state.setIn(['ledger', 'promotion', 'activeState'], active) + }, + + getActivePromotion: (state) => { + state = validateState(state) + const active = state.getIn(['ledger', 'promotion', 'activeState']) + + if (!active) { + return Immutable.Map() + } + + return state.getIn(['ledger', 'promotion', 'stateWallet', active]) || Immutable.Map() + }, + + setPromotionProp: (state, prop, value) => { + state = validateState(state) + + if (prop == null) { + return state + } + + return state.setIn(['ledger', 'promotion', prop], value) + }, + + removePromotion: (state) => { + state = validateState(state) + + let promotion = Immutable.fromJS({}) + + return state.setIn(['ledger', 'promotion'], promotion) + }, + + remindMeLater: (state, time) => { + const ledgerUtil = require('../lib/ledgerUtil') + if (time == null) { + time = 24 * ledgerUtil.miliseconds.hour + } + + state = validateState(state) + + return ledgerState.setPromotionProp(state, 'remindTimestamp', new Date().getTime() + time) + }, + + /** + * PROMOTIONS / NOTIFICATION + */ + + getPromotionNotification: (state) => { + state = validateState(state) + + const promotion = ledgerState.getActivePromotion(state) + + return promotion.get('notification') || Immutable.Map() + }, + + setPromotionNotificationProp: (state, prop, value) => { + state = validateState(state) + + if (prop == null) { + return state + } + + const active = state.getIn(['ledger', 'promotion', 'activeState']) + const path = ['ledger', 'promotion', 'stateWallet', active, 'notification', prop] + + return state.setIn(path, value) + }, + + /** + * ABOUT PAGE + */ // TODO (optimization) don't have two almost identical object in state (synopsi->publishers and about->synopsis) saveAboutSynopsis: (state, publishers) => { state = validateState(state) @@ -356,6 +491,16 @@ const ledgerState = { geWizardData: (state) => { state = validateState(state) return state.getIn(['ledger', 'wizardData']) || Immutable.Map() + }, + + getAboutPromotion: (state) => { + const promotion = ledgerState.getActivePromotion(state) + const claim = state.getIn(['ledger', 'promotion', 'claimedTimestamp']) || null + if (claim) { + return promotion.set('claimedTimestamp', claim) + } + + return promotion } } diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties index e56e0537818..d3ab3229a41 100644 --- a/app/extensions/brave/locales/en-US/preferences.properties +++ b/app/extensions/brave/locales/en-US/preferences.properties @@ -228,14 +228,14 @@ paymentsSidebarText1=Our Partners paymentsSidebarText2=All transaction IP addresses are anonymized with technology from: paymentsSidebarText3=Brave BAT Wallets are provided through a partnership with: paymentsWelcomeLink=View the FAQ -paymentsWelcomeText1=Brave has created a simple way for you to contribute to the sites you use most. -paymentsWelcomeText2=To begin using Brave Payments, simply flip the switch at the top of this window. The rest is easy. +paymentsWelcomeText1=To start using Brave Payments, simply flip the switch at the top of this window. +paymentsWelcomeText2=The rest is easy. paymentsWelcomeText3=Brave Payments allows publishers to stay in business even though you may have blocked their advertisements with Brave. All of this works while keeping your browsing history private. Your funds are transferred to the site owner through an anonymous ledger system, which makes it impossible for you ever to be identified based on the sites you visit and support. paymentsWelcomeText4=Brave Payments is currently in Beta. With your help and feedback, we can fine tune the system through the beta period. paymentsWelcomeText5=Note: Brave Payments uses a country-lookup service in order to provide better user wallet funding options based on your location. This service is completely anonymous. paymentsWelcomeText6=Need more info? paymentsWelcomeText7=for Brave Payments… -paymentsWelcomeTitle=Welcome to Brave Payments! +paymentsWelcomeTitle=Brave has created a simple way for you to contribute to the sites that you use most. pendingFundsStatus=Pending funds: {{funds}}. Newly-added funds may take 30+ minutes to appear. percentage=% percentPaid=Percentage @@ -247,6 +247,7 @@ printKeys=Print key privacy=Privacy privateData=Private Data privateDataMessage=Clear the following data types when I close Brave +promotionClaimed=Congrats! {{token}} tokens have been added to your wallet. protocolRegistrationPermission=Protocol registration publisher=Site publisherMediaName={{publisherName}} on {{provider}} diff --git a/app/renderer/components/main/notificationItem.js b/app/renderer/components/main/notificationItem.js index 97eeb94b10e..f824486a239 100644 --- a/app/renderer/components/main/notificationItem.js +++ b/app/renderer/components/main/notificationItem.js @@ -30,7 +30,7 @@ class NotificationItem extends React.Component { this.toggleCheckbox = this.toggleCheckbox.bind(this) } - clickHandler (buttonIndex) { + clickHandler (buttonIndex, buttonActionId) { if (this.props.nonce) { // This needs to be changed into an app action but it is // currently ipc.emit on purpose so the message goes to the @@ -42,7 +42,8 @@ class NotificationItem extends React.Component { this.props.message, buttonIndex, this.checkbox ? this.checkbox.checked : false, - this.props.index + this.props.index, + buttonActionId ) } else { // This needs to be changed into an app action but it is @@ -53,7 +54,8 @@ class NotificationItem extends React.Component { this.props.message, buttonIndex, this.checkbox ? this.checkbox.checked : false, - this.props.index + this.props.index, + buttonActionId ) } } @@ -128,7 +130,7 @@ class NotificationItem extends React.Component { iconClass={button.get('className')} testId='notificationButton' label={button.get('text')} - onClick={this.clickHandler.bind(this, i)} + onClick={this.clickHandler.bind(this, i, button.get('buttonActionId'))} />) : null } diff --git a/app/renderer/components/preferences/payment/disabledContent.js b/app/renderer/components/preferences/payment/disabledContent.js index 9fb772f49e9..f7515b47ab6 100644 --- a/app/renderer/components/preferences/payment/disabledContent.js +++ b/app/renderer/components/preferences/payment/disabledContent.js @@ -5,9 +5,12 @@ const React = require('react') const {StyleSheet, css} = require('aphrodite/no-important') -// components +// Components const ImmutableComponent = require('../../immutableComponent') +// Utils +const cx = require('../../../../../js/lib/classSet') + // style const globalStyles = require('../../styles/global') const commonStyles = require('../../styles/commonStyles') @@ -17,27 +20,63 @@ const uphold = require('../../../../extensions/brave/img/ledger/uphold_logo_smal const uphold2 = require('../../../../extensions/brave/img/ledger/uphold_logo_medium.png') class DisabledContent extends ImmutableComponent { + constructor (props) { + super(props) + this.text = this.getAlternativeText() + } + + getAlternativeText () { + return
+

+

+

+

+ } + + getText () { + if (this.props.ledgerData == null) { + return + } + + const text = this.props.ledgerData.getIn(['promotion', 'panel', 'optInMarkup']) + const claimed = this.props.ledgerData.has('claimedTimestamp') + + if (!text || !claimed) { + return + } + + this.text =
+ } + render () { + this.getText() + return
-
-

-
-
-
-
-
-
-   -   - +
+
+ {this.text} +
+
+
+

@@ -58,14 +97,25 @@ const styles = StyleSheet.create({ marginTop: globalStyles.spacing.panelMargin }, + disabledContent__commonText: { + padding: '0.5em 0' + }, + + disabledContent__commonText_bold: { + fontWeight: 'bold' + }, + + disabledContent__wrapper: { + fontSize: globalStyles.payments.fontSize.regular, + color: globalStyles.color.mediumGray + }, + disabledContent__message: { backgroundColor: globalStyles.color.lightGray, borderRadius: globalStyles.radius.borderRadiusUIbox, boxSizing: 'border-box', padding: '40px', - fontSize: globalStyles.payments.fontSize.regular, - lineHeight: '1.8em', - color: globalStyles.color.mediumGray + lineHeight: '1.8em' }, disabledContent__message__header: { @@ -73,14 +123,6 @@ const styles = StyleSheet.create({ paddingBottom: '0.5em' }, - disabledContent__message__text: { - padding: '0.5em 0' - }, - - disabledContent__message__text_bold: { - fontWeight: 'bold' - }, - disabledContent__message__toc: { display: 'flex', flex: 1, @@ -122,6 +164,11 @@ const styles = StyleSheet.create({ backgroundSize: 'contain', backgroundRepeat: 'no-repeat', height: '50px' + }, + + disabledContent__footer: { + lineHeight: '1.2em', + padding: '20px' } }) diff --git a/app/renderer/components/preferences/payment/enabledContent.js b/app/renderer/components/preferences/payment/enabledContent.js index c3cfba58c9b..0e7a158b3d7 100644 --- a/app/renderer/components/preferences/payment/enabledContent.js +++ b/app/renderer/components/preferences/payment/enabledContent.js @@ -5,6 +5,7 @@ const React = require('react') const {StyleSheet, css} = require('aphrodite/no-important') const moment = require('moment') +const Immutable = require('immutable') // util const {batToCurrencyString, formatCurrentBalance, formattedDateFromTimestamp, walletStatus} = require('../../../../common/lib/ledgerUtil') @@ -24,6 +25,7 @@ const LedgerTable = require('./ledgerTable') const globalStyles = require('../../styles/global') const {paymentStylesVariables} = require('../../styles/payment') const {loaderAnimation} = require('../../styles/animations') +const closeButton = require('../../../../../img/toolbar/stoploading_btn.svg') const cx = require('../../../../../js/lib/classSet') // Actions @@ -32,6 +34,12 @@ const appActions = require('../../../../../js/actions/appActions') // TODO: report when funds are too low // TODO: support non-USD currency class EnabledContent extends ImmutableComponent { + constructor (props) { + super(props) + this.claimButton = this.claimButton.bind(this) + this.onClaimClick = this.onClaimClick.bind(this) + } + walletButton () { const ledgerData = this.props.ledgerData const buttonText = ledgerData.get('created') @@ -61,6 +69,33 @@ class EnabledContent extends ImmutableComponent {

} + onClaimClick () { + appActions.onPromotionClaim() + } + + claimButton () { + const ledgerData = this.props.ledgerData || Immutable.Map() + const promotion = ledgerData.get('promotion') + + if (promotion == null || promotion.isEmpty() || promotion.has('claimedTimestamp') || !ledgerData.get('created')) { + return null + } + + return
+ +
+ } + ledgerDataErrorText () { const ledgerData = this.props.ledgerData const ledgerError = ledgerData.get('error') @@ -162,6 +197,36 @@ class EnabledContent extends ImmutableComponent {

} + closeClick () { + appActions.onPromotionRemoval() + } + + successMessage () { + const promo = this.props.ledgerData.get('promotion') || Immutable.Map() + const successText = promo.getIn(['panel', 'successText']) + + if (!successText || !promo.has('claimedTimestamp')) { + return + } + + return
+
+

+

+ {promo.getIn(['panel', 'disclaimer'])} +

+ +
+ } + render () { const ledgerData = this.props.ledgerData const walletStatusText = walletStatus(ledgerData) @@ -184,7 +249,9 @@ class EnabledContent extends ImmutableComponent {
-
+
+ {this.claimButton()} +
+ {this.successMessage()}
- : + : }
} diff --git a/app/sessionStore.js b/app/sessionStore.js index 6782a3ddab7..24e885115d0 100644 --- a/app/sessionStore.js +++ b/app/sessionStore.js @@ -1005,7 +1005,8 @@ module.exports.defaultAppState = () => { synopsis: { options: {}, publishers: {} - } + }, + promotion: {} }, migrations: { batMercuryTimestamp: now, diff --git a/docs/state.md b/docs/state.md index 8684ce461ad..692daa7cfc8 100644 --- a/docs/state.md +++ b/docs/state.md @@ -238,6 +238,83 @@ AppStore publisher: string // url of the publisher in question } }, + promotion: { + activeState: string, + claimedTimestamp: number, + promotionId: number, + remindTimestamp: number, + stateWallet: { + disabledWallet: { + notification: { + buttons: [{ + buttonActionId: string, + className: string, + text: string + }], + firstShowTimestamp: number, + greeting: string, + message: string, + messageAction: string, + options: { + persist: boolean, + style: string + } + }, + panel: { + disclaimer: string, + optInMarkup: string, + optedInButton: string, + successText: string + } + }, + emptyWallet: { + notification: { + buttons: [{ + buttonActionId: string, + className: string, + text: string + }], + firstShowTimestamp: number, + greeting: string, + message: string, + messageAction: string, + options: { + persist: boolean, + style: string + } + }, + panel: { + disclaimer: string, + optInMarkup: string, + optedInButton: string, + successText: string + } + }, + fundedWallet: { + notification: { + buttons: [{ + buttonActionId: string, + className: string, + text: string + }], + firstShowTimestamp: number, + greeting: string, + message: string, + messageAction: string, + options: { + persist: boolean, + style: string + } + }, + panel: { + disclaimer: string, + optInMarkup: string, + optedInButton: string, + successText: string + } + } + } + } publisherTimestamp: number, // timestamp of last publisher update in the database synopsis: { options: { diff --git a/js/actions/appActions.js b/js/actions/appActions.js index f30e6b54244..e1209daf52e 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -1824,6 +1824,43 @@ const appActions = { }) }, + saveLedgerPromotion: function (promotion) { + dispatch({ + actionType: appConstants.APP_SAVE_LEDGER_PROMOTION, + promotion + }) + }, + + onPromotionClaim: function () { + dispatch({ + actionType: appConstants.APP_ON_PROMOTION_CLAIM + }) + }, + + onPromotionResponse: function () { + dispatch({ + actionType: appConstants.APP_ON_PROMOTION_RESPONSE + }) + }, + + onPromotionRemind: function () { + dispatch({ + actionType: appConstants.APP_ON_PROMOTION_REMIND + }) + }, + + onPromotionRemoval: function () { + dispatch({ + actionType: appConstants.APP_ON_PROMOTION_REMOVAL + }) + }, + + onLedgerNotificationInterval: function () { + dispatch({ + actionType: appConstants.APP_ON_LEDGER_NOTIFICATION_INTERVAL + }) + }, + onLedgerMediaData: function (url, type, tabId) { dispatch({ actionType: appConstants.APP_ON_LEDGER_MEDIA_DATA, diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 6fec46fee43..1620db0f1fb 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -181,6 +181,12 @@ const appConstants = { APP_ON_LEDGER_QR_GENERATED: _, APP_ON_BTC_TO_BAT_BEGIN_TRANSITION: _, APP_ON_PUBLISHER_TIMESTAMP: _, + APP_SAVE_LEDGER_PROMOTION: _, + APP_ON_PROMOTION_CLAIM: _, + APP_ON_PROMOTION_RESPONSE: _, + APP_ON_PROMOTION_REMIND: _, + APP_ON_PROMOTION_REMOVAL: _, + APP_ON_LEDGER_NOTIFICATION_INTERVAL: _, APP_ON_LEDGER_MEDIA_DATA: _, APP_ON_PRUNE_SYNOPSIS: _, APP_ON_LEDGER_MEDIA_PUBLISHER: _ diff --git a/less/about/preferences.less b/less/about/preferences.less index 750332ba4d4..2ba7f8eb2d0 100644 --- a/less/about/preferences.less +++ b/less/about/preferences.less @@ -206,3 +206,37 @@ table.sortableTable { cursor: pointer; } } + +.disabledLedgerContent { + h3 { + font-size: 18px; + padding-bottom: 0.5em + } + + p { + padding: 0.5em 0; + } + + a { + display: inline-block; + text-decoration: underline; + color: rgb(255, 80, 0); + cursor: pointer; + margin: 0; + + &:hover { + color: #000; + } + } + + b { + font-weight: bold; + } +} + +.enabledContent__grant { + b { + font-weight: normal; + color: #ff5500; + } +} diff --git a/package-lock.json b/package-lock.json index 61bcb7c22b1..40b75b0c77a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,16 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@ambassify/backoff-strategies": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@ambassify/backoff-strategies/-/backoff-strategies-1.0.0.tgz", - "integrity": "sha1-6salmVHxSXdAY2wGRi2QbXKfQ6U=" - }, - "@types/node": { - "version": "6.0.88", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", - "integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==" - }, "7zip-bin": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-2.2.4.tgz", @@ -30,6 +20,16 @@ "dev": true, "optional": true }, + "@ambassify/backoff-strategies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ambassify/backoff-strategies/-/backoff-strategies-1.0.0.tgz", + "integrity": "sha1-6salmVHxSXdAY2wGRi2QbXKfQ6U=" + }, + "@types/node": { + "version": "6.0.88", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", + "integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==" + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -1413,9 +1413,12 @@ "dev": true }, "basic-auth": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", - "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "requires": { + "safe-buffer": "5.1.1" + } }, "bat-balance": { "version": "1.0.3", @@ -1441,14 +1444,14 @@ } }, "bat-client": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/bat-client/-/bat-client-1.4.2.tgz", - "integrity": "sha1-TKVEDArvOJVlmLiLUTDmGvd6VvY=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bat-client/-/bat-client-2.0.1.tgz", + "integrity": "sha512-d10bfnqM18QnlUBzFygCAJCfVWFocyfNSpG+BBPidVvAog9wqGucF7VLhXziEBXMR6Ys5iy5CTjpzSA4LHeWRQ==", "requires": { "@ambassify/backoff-strategies": "1.0.0", "bat-balance": "1.0.4", - "bat-publisher": "1.3.0", - "bitgo": "4.10.0", + "bat-publisher": "2.0.0", + "bitgo": "4.15.0", "brave-crypto": "0.0.1", "http-request-signature": "0.0.2", "joi": "11.4.0", @@ -1484,9 +1487,9 @@ } }, "bat-publisher": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bat-publisher/-/bat-publisher-1.3.0.tgz", - "integrity": "sha1-u+raH957YCEUZEYFFjGoFq2Eq5k=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bat-publisher/-/bat-publisher-2.0.0.tgz", + "integrity": "sha512-H3Yz5/qB+9HoRo0XWMyKwA0bA6FSBQQNIu4zoril4syedcBOCPRpsETMzOtWznaJw+V/rqHeKBaSjwEqXsCHbw==", "requires": { "@ambassify/backoff-strategies": "1.0.0", "async": "2.5.0", @@ -1544,45 +1547,6 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "bcashjs-lib": { - "version": "git+https://github.com/BitGo/bcashjs-lib.git#a49702b12f06729ab80756d1c6dba880f0c64295", - "requires": { - "bech32": "0.0.3", - "bigi": "1.4.0", - "bip66": "1.1.5", - "bitcoin-ops": "1.3.0", - "bs58check": "2.0.2", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ecurve": "1.0.5", - "merkle-lib": "2.0.10", - "pushdata-bitcoin": "1.0.1", - "randombytes": "2.0.5", - "safe-buffer": "5.1.1", - "typeforce": "1.11.5", - "varuint-bitcoin": "1.0.4", - "wif": "2.0.6" - }, - "dependencies": { - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "requires": { - "base-x": "3.0.2" - } - }, - "bs58check": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.0.2.tgz", - "integrity": "sha1-BvY7AcL6YXMDPJDrh/H+PS4T2Jo=", - "requires": { - "bs58": "4.0.1", - "create-hash": "1.1.3" - } - } - } - }, "bcrypt-pbkdf": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", @@ -1649,8 +1613,7 @@ "bindings": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", - "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==", - "optional": true + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" }, "bip66": { "version": "1.1.5", @@ -1666,51 +1629,71 @@ "integrity": "sha1-axJrWFU3vGebAu1JnxRFDP/DfhM=" }, "bitcoinjs-lib": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-2.1.4.tgz", - "integrity": "sha1-1PwjWwZaoZxI4R2RNu6rB7OMFTQ=", + "version": "git+https://github.com/dabura667/bitcoinjs-lib.git#92c602567bd62dd1b41b717cc7d977c015104f1c", "requires": { + "bech32": "0.0.3", "bigi": "1.4.0", "bip66": "1.1.5", - "bs58check": "1.3.4", - "buffer-equals": "1.0.4", - "buffer-reverse": "1.0.1", + "bitcoin-ops": "1.3.0", + "bs58check": "2.1.0", "create-hash": "1.1.3", "create-hmac": "1.1.6", "ecurve": "1.0.5", + "merkle-lib": "2.0.10", + "pushdata-bitcoin": "1.0.1", "randombytes": "2.0.5", + "safe-buffer": "5.1.1", "typeforce": "1.11.5", - "wif": "1.2.1" + "varuint-bitcoin": "1.0.4", + "wif": "2.0.6" }, "dependencies": { - "base-x": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-1.1.0.tgz", - "integrity": "sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w=" - }, "bs58": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-3.1.0.tgz", - "integrity": "sha1-1MJjiL9IBMrHFBQbGUWqR+XrJI4=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", "requires": { - "base-x": "1.1.0" + "base-x": "3.0.2" } }, "bs58check": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-1.3.4.tgz", - "integrity": "sha1-xSVABzdJEXcU+gQsMEfrj5FRy/g=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.0.tgz", + "integrity": "sha512-FWT30tP+89OiXjWLHlAElxdPrbYo05T8XSmgKhh2GHq1JCBFUG0fujt5n7dadkEL3l4DFdfEi5KG5RcXW7UDEA==", "requires": { - "bs58": "3.1.0", + "bs58": "4.0.1", "create-hash": "1.1.3" } + } + } + }, + "bitcoinjs-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz", + "integrity": "sha512-H5pJC7/eSqVjREiEOZ4jifX+7zXYP3Y28GIOIqg9hrgE7Vj8Eva9+HnVqnxwA1rJPOwZKuw0vo6k0UxgVc6q1A==", + "requires": { + "bs58check": "2.1.0", + "buffer-equals": "1.0.4", + "create-hash": "1.1.3", + "secp256k1": "3.2.5", + "varuint-bitcoin": "1.0.4" + }, + "dependencies": { + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "3.0.2" + } }, - "wif": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/wif/-/wif-1.2.1.tgz", - "integrity": "sha1-qoGlfcH+yGtIT7509KtfZftR4EM=", + "bs58check": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.0.tgz", + "integrity": "sha512-FWT30tP+89OiXjWLHlAElxdPrbYo05T8XSmgKhh2GHq1JCBFUG0fujt5n7dadkEL3l4DFdfEi5KG5RcXW7UDEA==", "requires": { - "bs58check": "1.3.4" + "bs58": "4.0.1", + "create-hash": "1.1.3" } } } @@ -1721,19 +1704,19 @@ "integrity": "sha1-pUd/AOM/Knbtwgmq8mvwk5SjeM8=" }, "bitgo": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/bitgo/-/bitgo-4.10.0.tgz", - "integrity": "sha512-JWu/KA0aI9vQeUyJx1HU92FY+0A8Cz47IX6QjTfB/4i4gTDEYQMyIU8OevUmiCl6LE1imSXrBELi3ZGL9yVpFw==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/bitgo/-/bitgo-4.15.0.tgz", + "integrity": "sha512-3jd2FxpcciMNbetX+Ryj5nkwl8wb+bFYYlMXj7MuYv+1n+DuXtTl9Jy4e24w96pYNJIfvfqIFkRxGoQZ0EQ6ow==", "requires": { "argparse": "0.1.16", "assert": "0.4.9", - "bcashjs-lib": "git+https://github.com/BitGo/bcashjs-lib.git#a49702b12f06729ab80756d1c6dba880f0c64295", "big.js": "3.1.3", "bigi": "1.4.0", "bignumber.js": "4.0.4", - "bitcoinjs-lib": "2.1.4", + "bitcoinjs-lib": "git+https://github.com/dabura667/bitcoinjs-lib.git#92c602567bd62dd1b41b717cc7d977c015104f1c", + "bitcoinjs-message": "2.0.0", "bluebird": "3.5.0", - "body-parser": "1.17.2", + "body-parser": "1.18.2", "bs58": "2.0.1", "bs58check": "1.0.4", "create-hmac": "1.1.6", @@ -1741,16 +1724,17 @@ "eol": "0.5.0", "ethereumjs-abi": "0.6.4", "ethereumjs-util": "4.4.1", - "express": "4.15.5", + "express": "4.16.2", "http-proxy": "1.11.1", "lodash": "4.13.1", "minimist": "0.2.0", "moment": "2.18.1", - "morgan": "1.5.3", + "morgan": "1.9.0", "prova-lib": "0.2.9", "ripple-lib": "0.17.7", "sanitize-html": "1.13.0", "secp256k1": "3.2.5", + "secrets.js-grempe": "1.1.0", "superagent": "3.5.2", "superagent-proxy": "1.0.2", "underscore.string": "2.4.0" @@ -1761,6 +1745,57 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=" }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.1", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.0", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.2", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.1", + "vary": "1.1.2" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + } + }, "form-data": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", @@ -1776,6 +1811,11 @@ "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" }, + "ipaddr.js": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1786,11 +1826,25 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.13.1.tgz", "integrity": "sha1-g+SxCRP0hJbU0W/sSlYK8u50S2g=" }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, "minimist": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=" }, + "proxy-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.5.2" + } + }, "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", @@ -1810,6 +1864,42 @@ "util-deprecate": "1.0.2" } }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "requires": { + "debug": "2.6.9", + "depd": "1.1.1", + "destroy": "1.0.4", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "requires": { + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.1" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", @@ -1830,10 +1920,15 @@ "form-data": "2.3.1", "formidable": "1.1.1", "methods": "1.1.2", - "mime": "1.3.4", + "mime": "1.4.1", "qs": "6.5.1", "readable-stream": "2.3.3" } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" } } }, @@ -2093,34 +2188,47 @@ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "body-parser": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", - "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", "requires": { - "bytes": "2.4.0", + "bytes": "3.0.0", "content-type": "1.0.4", - "debug": "2.6.7", + "debug": "2.6.9", "depd": "1.1.1", "http-errors": "1.6.2", - "iconv-lite": "0.4.15", + "iconv-lite": "0.4.19", "on-finished": "2.3.0", - "qs": "6.4.0", - "raw-body": "2.2.0", + "qs": "6.5.1", + "raw-body": "2.3.2", "type-is": "1.6.15" }, "dependencies": { - "debug": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", - "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", - "requires": { - "ms": "2.0.0" - } + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } } } }, @@ -2446,11 +2554,6 @@ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", "dev": true }, - "buffer-reverse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", - "integrity": "sha1-SSg8jvpvkBvAH6MwTQYCeXGuL2A=" - }, "buffer-to-vinyl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz", @@ -4843,7 +4946,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", - "optional": true, "requires": { "browserify-aes": "1.0.8", "create-hash": "1.1.3", @@ -6177,6 +6279,7 @@ "version": "4.15.5", "resolved": "https://registry.npmjs.org/express/-/express-4.15.5.tgz", "integrity": "sha1-ZwI1ypWYiQpa6BcLg9tyK4Qu2Sc=", + "dev": true, "requires": { "accepts": "1.3.4", "array-flatten": "1.1.1", @@ -6211,7 +6314,8 @@ "qs": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", - "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" + "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==", + "dev": true } } }, @@ -6484,6 +6588,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", + "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "1.0.1", @@ -7682,14 +7787,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -7700,6 +7797,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -11589,47 +11694,15 @@ "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" }, "morgan": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.5.3.tgz", - "integrity": "sha1-ittOcvnlxUNuXZP0KRCDX3nan98=", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", + "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", "requires": { - "basic-auth": "1.0.4", - "debug": "2.2.0", - "depd": "1.0.1", - "on-finished": "2.2.1" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "depd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", - "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" - }, - "ee-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz", - "integrity": "sha1-ag18YiHkkP7v2S7D9EHJzozQl/Q=" - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - }, - "on-finished": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.1.tgz", - "integrity": "sha1-XIXBzDYpn3gCllP2Z/J7a5nrwCk=", - "requires": { - "ee-first": "1.1.0" - } - } + "basic-auth": "2.0.0", + "debug": "2.6.9", + "depd": "1.1.1", + "on-finished": "2.3.0", + "on-headers": "1.0.1" } }, "move-concurrently": { @@ -14350,14 +14423,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -14368,6 +14433,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -14824,8 +14897,7 @@ "on-headers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" }, "once": { "version": "1.4.0", @@ -16264,6 +16336,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", + "dev": true, "requires": { "forwarded": "0.1.2", "ipaddr.js": "1.4.0" @@ -17427,7 +17500,6 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.2.5.tgz", "integrity": "sha1-Dd5bJ+UCFmX23/ynssPgEMbBPJM=", - "optional": true, "requires": { "bindings": "1.3.0", "bip66": "1.1.5", @@ -17439,6 +17511,11 @@ "prebuild-install": "2.2.2" } }, + "secrets.js-grempe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/secrets.js-grempe/-/secrets.js-grempe-1.1.0.tgz", + "integrity": "sha1-uztgbdaGN8okRoGhD97mxRIEkpQ=" + }, "seek-bzip": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", @@ -17500,6 +17577,7 @@ "version": "0.15.6", "resolved": "https://registry.npmjs.org/send/-/send-0.15.6.tgz", "integrity": "sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ=", + "dev": true, "requires": { "debug": "2.6.9", "depd": "1.1.1", @@ -17552,6 +17630,7 @@ "version": "1.12.6", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.6.tgz", "integrity": "sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc=", + "dev": true, "requires": { "encodeurl": "1.0.1", "escape-html": "1.0.3", @@ -18638,11 +18717,6 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -18672,6 +18746,11 @@ "ipaddr.js": "1.4.0" } }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -19761,7 +19840,8 @@ "utils-merge": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", + "dev": true }, "uuid": { "version": "3.1.0", diff --git a/package.json b/package.json index bd8ff389691..fb4f9056307 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,8 @@ "aphrodite": "1.1.0", "async": "^2.0.1", "bat-balance": "^1.0.3", - "bat-client": "^1.4.2", - "bat-publisher": "^1.3.0", + "bat-client": "^2.0.1", + "bat-publisher": "^2.0.0", "bignumber.js": "^4.0.4", "bloodhound-js": "brave/bloodhound", "clipboard-copy": "^1.0.0", diff --git a/test/unit/about/preferencesTest.js b/test/unit/about/preferencesTest.js index 4f90acfd848..1c66576736a 100644 --- a/test/unit/about/preferencesTest.js +++ b/test/unit/about/preferencesTest.js @@ -57,6 +57,7 @@ describe('Preferences component unittest', function () { mockery.registerMock('../../../extensions/brave/img/preferences/browser_prefs_payments_off.svg') // Mocks the icon used in payments tab mockery.registerMock('../../../extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg') + mockery.registerMock('../../../../../img/toolbar/stoploading_btn.svg') // Mocks the icons used in addFundsDialog and its steps mockery.registerMock('../../../../../../extensions/brave/img/ledger/wallet_icon.svg') mockery.registerMock('../../../../../../extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg') diff --git a/test/unit/app/browser/api/ledgerNotificationsTest.js b/test/unit/app/browser/api/ledgerNotificationsTest.js index 06157293f2d..a440a3e27c5 100644 --- a/test/unit/app/browser/api/ledgerNotificationsTest.js +++ b/test/unit/app/browser/api/ledgerNotificationsTest.js @@ -13,6 +13,7 @@ describe('ledgerNotifications unit test', function () { let fakeClock let ledgerApi let ledgerNotificationsApi + let appAction let paymentsEnabled let paymentsNotifications @@ -53,6 +54,7 @@ describe('ledgerNotifications unit test', function () { fakeClock = sinon.useFakeTimers() ledgerApi = require('../../../../../app/browser/api/ledger') ledgerNotificationsApi = require('../../../../../app/browser/api/ledgerNotifications') + appAction = require('../../../../../js/actions/appActions') }) after(function () { @@ -62,26 +64,26 @@ describe('ledgerNotifications unit test', function () { }) describe('init', function () { - let onIntervalSpy + let notificationAction beforeEach(function () { - onIntervalSpy = sinon.spy(ledgerNotificationsApi, 'onInterval') + notificationAction = sinon.spy(appAction, 'onLedgerNotificationInterval') }) afterEach(function () { - onIntervalSpy.restore() + notificationAction.restore() }) it('does not immediately call notifications.onInterval', function () { - ledgerNotificationsApi.init(defaultAppState) - assert(onIntervalSpy.notCalled) + ledgerNotificationsApi.init() + assert(notificationAction.notCalled) }) it('calls notifications.onInterval after interval', function () { fakeClock.tick(0) - ledgerNotificationsApi.init(defaultAppState) + ledgerNotificationsApi.init() fakeClock.tick(ledgerNotificationsApi.getPollingInterval()) - assert(onIntervalSpy.calledOnce) + assert(notificationAction.calledOnce) }) it('assigns a value to timeout', function () { ledgerNotificationsApi.setTimeOut(0) - ledgerNotificationsApi.init(defaultAppState) + ledgerNotificationsApi.init() assert(ledgerNotificationsApi.getTimeOut(0)) }) }) @@ -303,4 +305,328 @@ describe('ledgerNotifications unit test', function () { }) }) }) + + describe('onInterval', function () { + let showEnabledNotificationsSpy, showDisabledNotificationsSpy, onIntervalDynamicSpy + + before(function () { + showEnabledNotificationsSpy = sinon.spy(ledgerNotificationsApi, 'showEnabledNotifications') + showDisabledNotificationsSpy = sinon.spy(ledgerNotificationsApi, 'showDisabledNotifications') + onIntervalDynamicSpy = sinon.spy(ledgerNotificationsApi, 'onIntervalDynamic') + }) + + afterEach(function () { + showDisabledNotificationsSpy.reset() + showEnabledNotificationsSpy.reset() + onIntervalDynamicSpy.reset() + }) + + after(function () { + showDisabledNotificationsSpy.restore() + showEnabledNotificationsSpy.restore() + onIntervalDynamicSpy.restore() + }) + + it('payments disabled', function () { + paymentsEnabled = false + ledgerNotificationsApi.onInterval(defaultAppState) + assert(showEnabledNotificationsSpy.notCalled) + assert(showDisabledNotificationsSpy.calledOnce) + assert(onIntervalDynamicSpy.calledOnce) + paymentsEnabled = true + }) + + it('payments enabled, but notifications disabled', function () { + paymentsNotifications = false + paymentsEnabled = true + ledgerNotificationsApi.onInterval(defaultAppState) + assert(showEnabledNotificationsSpy.notCalled) + assert(showDisabledNotificationsSpy.notCalled) + assert(onIntervalDynamicSpy.notCalled) + paymentsNotifications = true + }) + + it('payments enabled and notifications enabled', function () { + paymentsNotifications = true + ledgerNotificationsApi.onInterval(defaultAppState) + assert(showDisabledNotificationsSpy.notCalled) + assert(showEnabledNotificationsSpy.calledOnce) + assert(onIntervalDynamicSpy.calledOnce) + }) + }) + + describe('onIntervalDynamic', function () { + let fakeClock, showPromotionNotificationSpy + + before(function () { + fakeClock = sinon.useFakeTimers() + showPromotionNotificationSpy = sinon.spy(ledgerNotificationsApi, 'showPromotionNotification') + }) + + afterEach(function () { + showPromotionNotificationSpy.reset() + }) + + after(function () { + fakeClock.restore() + showPromotionNotificationSpy.restore() + }) + + it('empty promotions', function () { + const result = ledgerNotificationsApi.onIntervalDynamic(defaultAppState) + assert.deepEqual(result.toJS(), defaultAppState.toJS()) + assert(showPromotionNotificationSpy.notCalled) + }) + + it('promotion was not shown yet', function () { + const state = defaultAppState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1' + })) + const result = ledgerNotificationsApi.onIntervalDynamic(state) + assert.deepEqual(result.toJS(), state.toJS()) + assert(showPromotionNotificationSpy.notCalled) + }) + + it('promotion was shown, but it is not time yet', function () { + const state = defaultAppState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1', + remindTimestamp: 100 + })) + const result = ledgerNotificationsApi.onIntervalDynamic(state) + assert.deepEqual(result.toJS(), state.toJS()) + assert(showPromotionNotificationSpy.notCalled) + }) + + it('promotion was shown, but it is not time to re-show it yet', function () { + fakeClock.tick(0) + const state = defaultAppState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1', + remindTimestamp: 100 + })) + const result = ledgerNotificationsApi.onIntervalDynamic(state) + assert.deepEqual(result.toJS(), state.toJS()) + assert(showPromotionNotificationSpy.notCalled) + }) + + it('promotion was re-shown', function () { + fakeClock.tick(800) + const state = defaultAppState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1', + remindTimestamp: 700 + })) + const result = ledgerNotificationsApi.onIntervalDynamic(state) + const expectedState = state + .setIn(['ledger', 'promotion', 'remindTimestamp'], -1) + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(showPromotionNotificationSpy.calledOnce) + }) + }) + + describe('onDynamicResponse', function () { + let hideNotificationSpy + before(function () { + hideNotificationSpy = sinon.spy(appAction, 'hideNotification') + }) + + afterEach(function () { + hideNotificationSpy.reset() + }) + + after(function () { + hideNotificationSpy.restore() + }) + + it('null case', function () { + ledgerNotificationsApi.onDynamicResponse() + assert(hideNotificationSpy.notCalled) + }) + + describe('optInPromotion', function () { + let createTabRequestedSpy + + before(function () { + createTabRequestedSpy = sinon.spy(appAction, 'createTabRequested') + }) + + afterEach(function () { + createTabRequestedSpy.reset() + }) + + it('activeWindow is missing', function () { + ledgerNotificationsApi.onDynamicResponse('msg', 'optInPromotion') + assert(hideNotificationSpy.calledOnce) + assert(createTabRequestedSpy.notCalled) + }) + + it('called', function () { + ledgerNotificationsApi.onDynamicResponse('msg', 'optInPromotion', {id: 1}) + assert(hideNotificationSpy.calledOnce) + assert(createTabRequestedSpy.calledOnce) + }) + }) + + describe('remindLater', function () { + let onPromotionRemindSpy + + before(function () { + onPromotionRemindSpy = sinon.spy(appAction, 'onPromotionRemind') + }) + + afterEach(function () { + onPromotionRemindSpy.reset() + }) + + after(function () { + onPromotionRemindSpy.restore() + }) + + it('called', function () { + ledgerNotificationsApi.onDynamicResponse('msg', 'remindLater') + assert(hideNotificationSpy.calledOnce) + assert(onPromotionRemindSpy.calledOnce) + }) + }) + }) + + describe('onPromotionReceived', function () { + let showPromotionNotificationSpy, fakeClock + + before(function () { + showPromotionNotificationSpy = sinon.spy(ledgerNotificationsApi, 'showPromotionNotification') + fakeClock = sinon.useFakeTimers() + }) + + afterEach(function () { + showPromotionNotificationSpy.reset() + }) + + after(function () { + showPromotionNotificationSpy.restore() + fakeClock.restore() + }) + + it('there is no promotion', function () { + ledgerNotificationsApi.onPromotionReceived(defaultAppState) + assert(showPromotionNotificationSpy.notCalled) + }) + + it('promotion was already shown', function () { + const state = defaultAppState + .setIn(['ledger', 'promotion', 'activeState'], 'disabledWallet') + .setIn(['ledger', 'promotion', 'stateWallet'], Immutable.fromJS({ + disabledWallet: { + firstShowTimestamp: 1 + } + })) + ledgerNotificationsApi.onPromotionReceived(state) + assert(showPromotionNotificationSpy.notCalled) + }) + + it('show promotion', function () { + fakeClock.tick(6000) + const state = defaultAppState + .setIn(['ledger', 'promotion', 'activeState'], 'disabledWallet') + .setIn(['ledger', 'promotion', 'stateWallet'], Immutable.fromJS({ + disabledWallet: { + notification: { + message: 'Hello' + } + } + })) + const result = ledgerNotificationsApi.onPromotionReceived(state) + const expectedState = state + .setIn(['ledger', 'promotion', 'stateWallet', 'disabledWallet', 'notification', 'firstShowTimestamp'], 6000) + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(showPromotionNotificationSpy.notCalled) + }) + }) + + describe('showPromotionNotification', function () { + let showNotificationSpy + + const state = defaultAppState + .setIn(['ledger', 'promotion', 'activeState'], 'disabledWallet') + .setIn(['ledger', 'promotion', 'stateWallet'], Immutable.fromJS({ + disabledWallet: { + notification: { + message: 'Hello' + } + } + })) + + before(function () { + showNotificationSpy = sinon.spy(appAction, 'showNotification') + }) + + afterEach(function () { + showNotificationSpy.reset() + }) + + after(function () { + showNotificationSpy.restore() + }) + + it('no promotion', function () { + ledgerNotificationsApi.showPromotionNotification(defaultAppState) + assert(showNotificationSpy.notCalled) + }) + + it('notifications disabled while payments are enabled', function () { + paymentsEnabled = true + paymentsNotifications = false + ledgerNotificationsApi.showPromotionNotification(state) + assert(showNotificationSpy.notCalled) + paymentsNotifications = true + }) + + it('payments disabled, notification is shown', function () { + ledgerNotificationsApi.showPromotionNotification(state) + assert(showNotificationSpy.calledOnce) + }) + + it('notification is shown', function () { + ledgerNotificationsApi.showPromotionNotification(state) + assert(showNotificationSpy.calledOnce) + }) + }) + + describe('removePromotionNotification', function () { + let hideNotificationSpy + + const state = defaultAppState + .setIn(['ledger', 'promotion', 'activeState'], 'disabledWallet') + .setIn(['ledger', 'promotion', 'stateWallet'], Immutable.fromJS({ + disabledWallet: { + notification: { + message: 'Hello' + } + } + })) + + before(function () { + hideNotificationSpy = sinon.spy(appAction, 'hideNotification') + }) + + afterEach(function () { + hideNotificationSpy.reset() + }) + + after(function () { + hideNotificationSpy.restore() + }) + + it('no promotion', function () { + ledgerNotificationsApi.removePromotionNotification(defaultAppState) + assert(hideNotificationSpy.notCalled) + }) + + it('notification is shown', function () { + ledgerNotificationsApi.removePromotionNotification(state) + assert(hideNotificationSpy.calledOnce) + }) + }) }) diff --git a/test/unit/app/browser/api/ledgerTest.js b/test/unit/app/browser/api/ledgerTest.js index 3b24a485c6b..6f6a415257f 100644 --- a/test/unit/app/browser/api/ledgerTest.js +++ b/test/unit/app/browser/api/ledgerTest.js @@ -41,6 +41,8 @@ describe('ledger api unit tests', function () { let onLedgerCallbackSpy let onBitcoinToBatBeginTransitionSpy let onChangeSettingSpy + let ledgersetPromotionSpy + let ledgergetPromotionSpy const defaultAppState = Immutable.fromJS({ cache: { @@ -142,12 +144,16 @@ describe('ledger api unit tests', function () { }, publisherTimestamp: function () { return 0 - } + }, + getPromotion: () => {}, + setPromotion: () => {} } ledgerClient.prototype.boolion = function (value) { return false } ledgerClient.prototype.getWalletPassphrase = function (state) {} ledgerTransitionSpy = sinon.spy(lc, 'transition') ledgerTransitionedSpy = sinon.spy(lc, 'transitioned') + ledgersetPromotionSpy = sinon.spy(lc, 'setPromotion') + ledgergetPromotionSpy = sinon.spy(lc, 'getPromotion') ledgerClient.returns(lc) mockery.registerMock('bat-client', ledgerClient) @@ -493,6 +499,7 @@ describe('ledger api unit tests', function () { after(function () { verifiedPSpy.restore() + ledgerApi.setClient(undefined) }) it('null case', function () { @@ -1028,6 +1035,10 @@ describe('ledger api unit tests', function () { showPaymentDoneSpy.reset() }) + after(function () { + showPaymentDoneSpy.restore() + }) + it('null case', function () { ledgerApi.observeTransactions(defaultAppState) assert(showPaymentDoneSpy.notCalled) @@ -1081,10 +1092,14 @@ describe('ledger api unit tests', function () { generatePaymentDataSpy = sinon.spy(ledgerApi, 'generatePaymentData') }) - after(function () { + afterEach(function () { generatePaymentDataSpy.reset() }) + after(function () { + generatePaymentDataSpy.restore() + }) + it('null case', function () { ledgerApi.onWalletProperties() assert(generatePaymentDataSpy.notCalled) @@ -1305,4 +1320,88 @@ describe('ledger api unit tests', function () { }) }) }) + + describe('claimPromotion', function () { + const state = defaultAppState + .setIn(['ledger', 'promotion', 'promotionId'], '1') + + before(function () { + ledgersetPromotionSpy.reset() + }) + + afterEach(function () { + ledgersetPromotionSpy.reset() + }) + + it('null case', function () { + ledgerApi.claimPromotion(defaultAppState) + assert(ledgersetPromotionSpy.notCalled) + }) + + it('empty client', function () { + const oldClient = ledgerApi.getClient() + ledgerApi.setClient(undefined) + ledgerApi.claimPromotion(state) + assert(ledgersetPromotionSpy.notCalled) + ledgerApi.setClient(oldClient) + }) + + it('execute', function () { + ledgerApi.claimPromotion(state) + assert(ledgersetPromotionSpy.calledOnce) + }) + }) + + describe('getPromotion', function () { + before(function () { + ledgergetPromotionSpy.reset() + ledgerClient.reset() + }) + + afterEach(function () { + ledgergetPromotionSpy.reset() + ledgerClient.reset() + }) + + it('empty client', function () { + const oldClient = ledgerApi.getClient() + ledgerApi.setClient(undefined) + ledgerApi.getPromotion() + assert(ledgergetPromotionSpy.calledOnce) + assert(ledgerClient.calledOnce) + ledgerApi.setClient(oldClient) + }) + + it('existing client', function () { + ledgerApi.getPromotion() + assert(ledgerClient.notCalled) + assert(ledgergetPromotionSpy.calledOnce) + }) + }) + + describe('onPromotionResponse', function () { + let removeNotificationSpy, fakeClock, getBalanceSpy + + before(function () { + removeNotificationSpy = sinon.spy(ledgerNotificationsApi, 'removePromotionNotification') + getBalanceSpy = sinon.spy(ledgerApi, 'getBalance') + fakeClock = sinon.useFakeTimers() + }) + + after(function () { + removeNotificationSpy.restore() + getBalanceSpy.restore() + fakeClock.restore() + }) + + it('execute', function () { + fakeClock.tick(6000) + const result = ledgerApi.onPromotionResponse(defaultAppState) + const expectedSate = defaultAppState + .setIn(['ledger', 'promotion', 'claimedTimestamp'], 6000) + assert(removeNotificationSpy.calledOnce) + assert(getBalanceSpy.calledOnce) + assert.deepEqual(result.toJS(), expectedSate.toJS()) + }) + }) }) diff --git a/test/unit/app/renderer/components/preferences/paymentsTabTest.js b/test/unit/app/renderer/components/preferences/paymentsTabTest.js index 8159464d82f..6828554870a 100644 --- a/test/unit/app/renderer/components/preferences/paymentsTabTest.js +++ b/test/unit/app/renderer/components/preferences/paymentsTabTest.js @@ -54,6 +54,7 @@ describe('PaymentsTab component', function () { mockery.registerMock('../../../../extensions/brave/img/ios_download.svg') // Mocks the icon used in payments tab mockery.registerMock('../../../extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg') + mockery.registerMock('../../../../../img/toolbar/stoploading_btn.svg') // Mocks the icons used in addFundsDialog and its steps mockery.registerMock('../../../../../../extensions/brave/img/ledger/wallet_icon.svg') mockery.registerMock('../../../../../../extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg') From c8032f08a62c4db33fd9991636416814653b722a Mon Sep 17 00:00:00 2001 From: Brian Clifton Date: Wed, 22 Nov 2017 14:24:27 -0700 Subject: [PATCH 2/3] Fix for new bat-publisher library Fixes https://github.com/brave/browser-laptop/issues/12068 Auditors: @mrose17 --- app/browser/api/ledger.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/browser/api/ledger.js b/app/browser/api/ledger.js index bd18d8802ce..f7fcb93a111 100644 --- a/app/browser/api/ledger.js +++ b/app/browser/api/ledger.js @@ -1735,6 +1735,7 @@ const initialize = (state, paymentsEnabled) => { if (!ledgerPublisher) ledgerPublisher = require('bat-publisher') let ruleset = [] + if (typeof ledgerPublisher.ruleset === 'function') ledgerPublisher.ruleset = ledgerPublisher.ruleset() ledgerPublisher.ruleset.forEach(rule => { if (rule.consequent) ruleset.push(rule) }) From 7f6103b61e1ef973ad4dc56efa675569b9028c9e Mon Sep 17 00:00:00 2001 From: NejcZdovc Date: Thu, 23 Nov 2017 09:42:36 +0100 Subject: [PATCH 3/3] Updates for UGP - More tests - Added paymentId and language to getPromotions - Fixes claim button after changes - Fixes notification to global - Adds min reconcile timestamp - Fixes get media, that was broken with publishers 2.x.x - Prevents unnecessary calls for promotion - Updated to the latest response - Fixes clear timers --- app/browser/api/ledger.js | 109 ++-- app/browser/api/ledgerNotifications.js | 17 +- app/browser/reducers/ledgerReducer.js | 5 + app/common/lib/ledgerUtil.js | 4 +- app/common/state/ledgerState.js | 35 +- .../preferences/payment/disabledContent.js | 20 +- .../preferences/payment/enabledContent.js | 35 +- docs/state.md | 80 +-- js/actions/appActions.js | 6 + js/constants/appConstants.js | 1 + less/about/preferences.less | 34 -- package-lock.json | 163 ++---- package.json | 2 +- .../browser/api/ledgerNotificationsTest.js | 8 + test/unit/app/browser/api/ledgerTest.js | 64 ++- .../app/browser/reducers/ledgerReducerTest.js | 155 +++++- test/unit/app/common/lib/ledgerUtilTest.js | 34 +- test/unit/app/common/state/ledgerStateTest.js | 507 +++++++++++++++++- 18 files changed, 996 insertions(+), 283 deletions(-) diff --git a/app/browser/api/ledger.js b/app/browser/api/ledger.js index f7fcb93a111..7b24fe29d17 100644 --- a/app/browser/api/ledger.js +++ b/app/browser/api/ledger.js @@ -160,7 +160,7 @@ const addFoundClosed = (state) => { clearTimeout(balanceTimeoutId) } const balanceFn = module.exports.getBalance.bind(null, state) - balanceTimeoutId = setTimeout(balanceFn, 5 * ledgerUtil.miliseconds.second) + balanceTimeoutId = setTimeout(balanceFn, 5 * ledgerUtil.milliseconds.second) } const boot = () => { @@ -200,7 +200,7 @@ const onBootStateFile = (state) => { } if (client.sync(callback) === true) { - run(random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute})) + run(random.randomInt({min: ledgerUtil.milliseconds.minute, max: 10 * ledgerUtil.milliseconds.minute})) } module.exports.getBalance(state) @@ -301,16 +301,16 @@ const getPublisherData = (result, scorekeeper) => { }) } - if (duration >= ledgerUtil.miliseconds.day) { - data.daysSpent = Math.max(Math.round(duration / ledgerUtil.miliseconds.day), 1) - } else if (duration >= ledgerUtil.miliseconds.hour) { - data.hoursSpent = Math.max(Math.floor(duration / ledgerUtil.miliseconds.hour), 1) - data.minutesSpent = Math.round((duration % ledgerUtil.miliseconds.hour) / ledgerUtil.miliseconds.minute) - } else if (duration >= ledgerUtil.miliseconds.minute) { - data.minutesSpent = Math.max(Math.floor(duration / ledgerUtil.miliseconds.minute), 1) - data.secondsSpent = Math.round((duration % ledgerUtil.miliseconds.minute) / ledgerUtil.miliseconds.second) + if (duration >= ledgerUtil.milliseconds.day) { + data.daysSpent = Math.max(Math.round(duration / ledgerUtil.milliseconds.day), 1) + } else if (duration >= ledgerUtil.milliseconds.hour) { + data.hoursSpent = Math.max(Math.floor(duration / ledgerUtil.milliseconds.hour), 1) + data.minutesSpent = Math.round((duration % ledgerUtil.milliseconds.hour) / ledgerUtil.milliseconds.minute) + } else if (duration >= ledgerUtil.milliseconds.minute) { + data.minutesSpent = Math.max(Math.floor(duration / ledgerUtil.milliseconds.minute), 1) + data.secondsSpent = Math.round((duration % ledgerUtil.milliseconds.minute) / ledgerUtil.milliseconds.second) } else { - data.secondsSpent = Math.max(Math.round(duration / ledgerUtil.miliseconds.second), 1) + data.secondsSpent = Math.max(Math.round(duration / ledgerUtil.milliseconds.second), 1) } if (_internal.verboseP) { @@ -579,7 +579,7 @@ const excludeP = (publisherKey, callback) => { } if (!v2RulesetDB) { - return setTimeout(() => excludeP(publisherKey, callback), 5 * ledgerUtil.miliseconds.second) + return setTimeout(() => excludeP(publisherKey, callback), 5 * ledgerUtil.milliseconds.second) } inspectP(v2RulesetDB, v2RulesetPath, publisherKey, 'exclude', 'domain:' + publisherKey, (err, result) => { @@ -1099,24 +1099,32 @@ const enable = (state, paymentsEnabled) => { if (paymentsEnabled === getSetting(settings.PAYMENTS_ENABLED)) { // on start - if (!promotionTimeoutId) { + if (promotionTimeoutId) { clearInterval(promotionTimeoutId) } - promotionTimeoutId = setInterval(getPromotion, 24 * ledgerUtil.miliseconds.hour) + promotionTimeoutId = setInterval(() => { + appActions.onPromotionGet() + }, 24 * ledgerUtil.milliseconds.hour) - if (!togglePromotionTimeoutId) { + if (togglePromotionTimeoutId) { clearTimeout(togglePromotionTimeoutId) } - togglePromotionTimeoutId = setTimeout(getPromotion, 15 * ledgerUtil.miliseconds.second) + togglePromotionTimeoutId = setTimeout(() => { + appActions.onPromotionGet() + }, 15 * ledgerUtil.milliseconds.second) } else if (paymentsEnabled) { // on toggle + if (togglePromotionTimeoutId) { + clearTimeout(togglePromotionTimeoutId) + } + const promotion = ledgerState.getPromotionNotification(state) if (!promotion.isEmpty()) { appActions.hideNotification(promotion.get('message')) } state = ledgerState.setActivePromotion(state, paymentsEnabled) - getPromotion() + getPromotion(state) } if (synopsis) { @@ -1349,7 +1357,7 @@ const observeTransactions = (state, transactions) => { // TODO convert this function and related ones to immutable const getStateInfo = (state, parsedData) => { const info = parsedData.paymentInfo - const then = new Date().getTime() - ledgerUtil.miliseconds.year + const then = new Date().getTime() - ledgerUtil.milliseconds.year if (!parsedData.properties.wallet) { return state @@ -1568,7 +1576,7 @@ const setPaymentInfo = (amount) => { // wallet being created... return setTimeout(function () { setPaymentInfo(amount) - }, 2 * ledgerUtil.miliseconds.second) + }, 2 * ledgerUtil.milliseconds.second) } amount = parseInt(amount, 10) @@ -1613,7 +1621,7 @@ const getBalance = (state) => { if (!client) return const balanceFn = module.exports.getBalance.bind(null, state) - balanceTimeoutId = setTimeout(balanceFn, 1 * ledgerUtil.miliseconds.minute) + balanceTimeoutId = setTimeout(balanceFn, 1 * ledgerUtil.milliseconds.minute) return getPaymentInfo(state) } @@ -1628,7 +1636,7 @@ const callback = (err, result, delayTime) => { if (!client) return if (typeof delayTime === 'undefined') { - delayTime = random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute}) + delayTime = random.randomInt({min: ledgerUtil.milliseconds.minute, max: 10 * ledgerUtil.milliseconds.minute}) } } @@ -1726,7 +1734,7 @@ const initialize = (state, paymentsEnabled) => { if (!paymentsEnabled) { client = null newClient = false - return ledgerState.resetInfo(state) + return ledgerState.resetInfo(state, true) } if (client) { @@ -1853,7 +1861,7 @@ const onInitRead = (state, parsedData) => { } appActions.onLedgerFirstSync(parsedData) - }, 3 * ledgerUtil.miliseconds.second) + }, 3 * ledgerUtil.milliseconds.second) // Make sure bravery props are up-to-date with user settings const address = ledgerState.getInfoProp(state, 'address') @@ -1880,7 +1888,7 @@ const onTimeUntilReconcile = (state, stateResult) => { const onLedgerFirstSync = (state, parsedData) => { if (client.sync(callback) === true) { - run(state, random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute})) + run(state, random.randomInt({min: ledgerUtil.milliseconds.minute, max: 10 * ledgerUtil.milliseconds.minute})) } return cacheRuleSet(state, parsedData.ruleset) @@ -1970,7 +1978,7 @@ const run = (state, delayTime) => { delayTime = false } if (delayTime === false) { - delayTime = random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute}) + delayTime = random.randomInt({min: ledgerUtil.milliseconds.minute, max: 10 * ledgerUtil.milliseconds.minute}) } } @@ -1978,8 +1986,8 @@ const run = (state, delayTime) => { if (runTimeoutId) return const active = client - if (delayTime > (1 * ledgerUtil.miliseconds.hour)) { - delayTime = random.randomInt({min: 3 * ledgerUtil.miliseconds.minute, max: ledgerUtil.miliseconds.hour}) + if (delayTime > (1 * ledgerUtil.milliseconds.hour)) { + delayTime = random.randomInt({min: 3 * ledgerUtil.milliseconds.minute, max: ledgerUtil.milliseconds.hour}) } runTimeoutId = setTimeout(() => { @@ -2007,7 +2015,7 @@ const networkConnected = () => { if (!client) return appActions.onNetworkConnected() - }, 1 * ledgerUtil.miliseconds.minute, true) + }, 1 * ledgerUtil.milliseconds.minute, true) } const onNetworkConnected = (state) => { @@ -2017,13 +2025,13 @@ const onNetworkConnected = (state) => { } if (client.sync(callback) === true) { - const delayTime = random.randomInt({min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute}) + const delayTime = random.randomInt({min: ledgerUtil.milliseconds.minute, max: 10 * ledgerUtil.milliseconds.minute}) run(state, delayTime) } if (balanceTimeoutId) clearTimeout(balanceTimeoutId) const newBalance = module.exports.getBalance.bind(null, state) - balanceTimeoutId = setTimeout(newBalance, 5 * ledgerUtil.miliseconds.second) + balanceTimeoutId = setTimeout(newBalance, 5 * ledgerUtil.milliseconds.second) } const muonWriter = (fileName, payload) => { @@ -2215,7 +2223,10 @@ const transitionWalletToBat = () => { console.log('ledger client is currently busy; transition will be retried on next launch') return } - const delayTime = random.randomInt({ min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute }) + const delayTime = random.randomInt({ + min: ledgerUtil.milliseconds.minute, + max: 10 * ledgerUtil.milliseconds.minute + }) console.log('ledger client is currently busy; transition will be retried shortly (this was attempt ' + busyRetryCount + ')') setTimeout(() => transitionWalletToBat(), delayTime) return @@ -2232,7 +2243,10 @@ const transitionWalletToBat = () => { client = newClient newClient = true // NOTE: onLedgerCallback will save latest client to disk as ledger-state.json - appActions.onLedgerCallback(result, random.randomInt({ min: ledgerUtil.miliseconds.minute, max: 10 * ledgerUtil.miliseconds.minute })) + appActions.onLedgerCallback(result, random.randomInt({ + min: ledgerUtil.milliseconds.minute, + max: 10 * ledgerUtil.milliseconds.minute + })) appActions.onBitcoinToBatTransitioned() ledgerNotifications.showBraveWalletUpdated() client.publisherTimestamp((err, result) => { @@ -2300,7 +2314,7 @@ const onMediaRequest = (state, xhr, type, tabId) => { providerName: type } - ledgerPublisher.getMedia.getPublisherFromMediaProps(mediaProps, options, (error, response) => { + ledgerPublisher.getMedia().getPublisherFromMediaProps(mediaProps, options, (error, response) => { if (error) { console.error('Error while getting publisher from media', error.toString()) return @@ -2384,14 +2398,18 @@ const onMediaPublisher = (state, mediaKey, response, duration, revisited) => { return state } -const getPromotion = () => { +const getPromotion = (state) => { let tempClient = client + let paymentId = null if (!tempClient) { clientprep() tempClient = ledgerClient(null, underscore.extend({roundtrip: roundtrip}, clientOptions), null) + paymentId = ledgerState.getInfoProp(state, 'paymentId') } - tempClient.getPromotion((err, result) => { + const lang = getSetting(settings.LANGUAGE) + + tempClient.getPromotion(lang, paymentId, (err, result) => { if (err) { console.error('Error retrieving promotion', err.toString()) return @@ -2425,6 +2443,21 @@ const onPromotionResponse = (state) => { ledgerNotifications.removePromotionNotification(state) state = ledgerState.setPromotionProp(state, 'claimedTimestamp', new Date().getTime()) + const currentTimestamp = ledgerState.getInfoProp(state, 'reconcileStamp') + const minTimestamp = ledgerState.getPromotionProp(state, 'minimumReconcileTimestamp') + + if (minTimestamp > currentTimestamp) { + client.setTimeUntilReconcile(minTimestamp, (err, stateResult) => { + if (err) return console.error('ledger setTimeUntilReconcile error: ' + err.toString()) + + if (!stateResult) { + return + } + + appActions.onTimeUntilReconcile(stateResult) + }) + } + if (togglePromotionTimeoutId) { clearTimeout(togglePromotionTimeoutId) } @@ -2475,7 +2508,8 @@ const getMethods = () => { generatePaymentData, claimPromotion, onPromotionResponse, - getBalance + getBalance, + getPromotion } let privateMethods = {} @@ -2511,8 +2545,7 @@ const getMethods = () => { synopsisNormalizer, checkVerifiedStatus, roundtrip, - observeTransactions, - getPromotion + observeTransactions } } diff --git a/app/browser/api/ledgerNotifications.js b/app/browser/api/ledgerNotifications.js index a74d6b9001e..50dfd60ccac 100644 --- a/app/browser/api/ledgerNotifications.js +++ b/app/browser/api/ledgerNotifications.js @@ -31,13 +31,13 @@ const text = { walletConvertedToBat: locale.translation('walletConvertedToBat') } -const pollingInterval = 15 * ledgerUtil.miliseconds.minute // 15 * minutes +const pollingInterval = 15 * ledgerUtil.milliseconds.minute // 15 * minutes let intervalTimeout const displayOptions = { style: 'greetingStyle', persist: false } -const nextAddFundsTime = 3 * ledgerUtil.miliseconds.day +const nextAddFundsTime = 3 * ledgerUtil.milliseconds.day const sufficientBalanceToReconcile = (state) => { const balance = Number(ledgerState.getInfoProp(state, 'balance') || 0) @@ -236,18 +236,18 @@ const showEnabledNotifications = (state) => { return } - if (reconcileStamp - new Date().getTime() < ledgerUtil.miliseconds.day) { + if (reconcileStamp - new Date().getTime() < ledgerUtil.milliseconds.day) { if (sufficientBalanceToReconcile(state)) { if (shouldShowNotificationReviewPublishers()) { const reconcileFrequency = ledgerState.getInfoProp(state, 'reconcileFrequency') - showReviewPublishers(reconcileStamp + ((reconcileFrequency - 2) * ledgerUtil.miliseconds.day)) + showReviewPublishers(reconcileStamp + ((reconcileFrequency - 2) * ledgerUtil.milliseconds.day)) } } else if (shouldShowNotificationAddFunds()) { showAddFunds() } - } else if (reconcileStamp - new Date().getTime() < 2 * ledgerUtil.miliseconds.day) { + } else if (reconcileStamp - new Date().getTime() < 2 * ledgerUtil.milliseconds.day) { if (sufficientBalanceToReconcile(state) && (shouldShowNotificationReviewPublishers())) { - showReviewPublishers(new Date().getTime() + ledgerUtil.miliseconds.day) + showReviewPublishers(new Date().getTime() + ledgerUtil.milliseconds.day) } } } @@ -372,7 +372,10 @@ const showPromotionNotification = (state) => { return } - appActions.showNotification(notification.toJS()) + const data = notification.toJS() + data.from = 'ledger' + + appActions.showNotification(data) } const removePromotionNotification = (state) => { diff --git a/app/browser/reducers/ledgerReducer.js b/app/browser/reducers/ledgerReducer.js index 9065e78deb3..5aa96f6c761 100644 --- a/app/browser/reducers/ledgerReducer.js +++ b/app/browser/reducers/ledgerReducer.js @@ -452,6 +452,11 @@ const ledgerReducer = (state, action, immutableAction) => { state = ledgerNotifications.onInterval(state) break } + case appConstants.APP_ON_PROMOTION_GET: + { + ledgerApi.getPromotion(state) + break + } case appConstants.APP_ON_LEDGER_MEDIA_PUBLISHER: { state = ledgerApi.onMediaPublisher( diff --git a/app/common/lib/ledgerUtil.js b/app/common/lib/ledgerUtil.js index b34d5174747..783f297635b 100644 --- a/app/common/lib/ledgerUtil.js +++ b/app/common/lib/ledgerUtil.js @@ -292,7 +292,7 @@ const getMediaProvider = (url) => { return provider } -const miliseconds = { +const milliseconds = { year: 365 * 24 * 60 * 60 * 1000, week: 7 * 24 * 60 * 60 * 1000, day: 24 * 60 * 60 * 1000, @@ -319,7 +319,7 @@ const getMethods = () => { getMediaProvider, getMediaData, getMediaKey, - miliseconds + milliseconds } let privateMethods = {} diff --git a/app/common/state/ledgerState.js b/app/common/state/ledgerState.js index fbce1685cde..57e93f0728a 100644 --- a/app/common/state/ledgerState.js +++ b/app/common/state/ledgerState.js @@ -279,9 +279,18 @@ const ledgerState = { return state.setIn(['ledger', 'info'], oldData.merge(data)) }, - resetInfo: (state) => { + resetInfo: (state, keep) => { state = validateState(state) - return state.setIn(['ledger', 'info'], Immutable.Map()) + let newData = Immutable.Map() + + if (keep) { + const paymentId = ledgerState.getInfoProp(state, 'paymentId') + if (paymentId) { + newData = newData.set('paymentId', paymentId) + } + } + + return state.setIn(['ledger', 'info'], newData) }, saveQRCode: (state, currency, image) => { @@ -391,7 +400,7 @@ const ledgerState = { } } - return state.setIn(['ledger', 'promotion', 'activeState'], active) + return ledgerState.setPromotionProp(state, 'activeState', active) }, getActivePromotion: (state) => { @@ -415,18 +424,25 @@ const ledgerState = { return state.setIn(['ledger', 'promotion', prop], value) }, - removePromotion: (state) => { + getPromotionProp: (state, prop) => { state = validateState(state) - let promotion = Immutable.fromJS({}) + if (prop == null) { + return null + } + + return state.getIn(['ledger', 'promotion', prop]) + }, - return state.setIn(['ledger', 'promotion'], promotion) + removePromotion: (state) => { + state = validateState(state) + return state.setIn(['ledger', 'promotion'], Immutable.Map()) }, remindMeLater: (state, time) => { const ledgerUtil = require('../lib/ledgerUtil') if (time == null) { - time = 24 * ledgerUtil.miliseconds.hour + time = 24 * ledgerUtil.milliseconds.hour } state = validateState(state) @@ -454,6 +470,11 @@ const ledgerState = { } const active = state.getIn(['ledger', 'promotion', 'activeState']) + + if (active == null) { + return state + } + const path = ['ledger', 'promotion', 'stateWallet', active, 'notification', prop] return state.setIn(path, value) diff --git a/app/renderer/components/preferences/payment/disabledContent.js b/app/renderer/components/preferences/payment/disabledContent.js index f7515b47ab6..21d81b2ea30 100644 --- a/app/renderer/components/preferences/payment/disabledContent.js +++ b/app/renderer/components/preferences/payment/disabledContent.js @@ -27,9 +27,9 @@ class DisabledContent extends ImmutableComponent { getAlternativeText () { return
-

-

-

+

+

+

} @@ -38,14 +38,22 @@ class DisabledContent extends ImmutableComponent { return } - const text = this.props.ledgerData.getIn(['promotion', 'panel', 'optInMarkup']) + const markup = this.props.ledgerData.getIn(['promotion', 'panel', 'optInMarkup']) const claimed = this.props.ledgerData.has('claimedTimestamp') - if (!text || !claimed) { + if (!markup || claimed) { return } - this.text =
+ const text = markup.get('title') + let message = markup.get('message') + + this.text =
+

{ text }

+ { + message.map(item =>

{ item }

) + } +
} render () { diff --git a/app/renderer/components/preferences/payment/enabledContent.js b/app/renderer/components/preferences/payment/enabledContent.js index 0e7a158b3d7..dbaa701f07f 100644 --- a/app/renderer/components/preferences/payment/enabledContent.js +++ b/app/renderer/components/preferences/payment/enabledContent.js @@ -81,19 +81,17 @@ class EnabledContent extends ImmutableComponent { return null } - return
- -
+ return } ledgerDataErrorText () { @@ -214,7 +212,9 @@ class EnabledContent extends ImmutableComponent { className={css(styles.enabledContent__grant_close)} onClick={this.closeClick} /> -

+

+ { successText.get('title') } { successText.get('message') } +

{promo.getIn(['panel', 'disclaimer'])}

@@ -487,7 +487,7 @@ const styles = StyleSheet.create({ }, claimButton: { - marginTop: '-10px' + marginTop: '10px' }, enabledContent__grant: { @@ -496,6 +496,7 @@ const styles = StyleSheet.create({ top: 0, left: 0, width: '100%', + minHeight: '159px', background: '#f3f3f3', borderRadius: '8px', padding: '30px 50px 20px', @@ -526,6 +527,10 @@ const styles = StyleSheet.create({ marginBottom: '10px' }, + enabledContent__grant_bold: { + color: '#ff5500' + }, + enabledContent__grant_text: { fontSize: '16px', color: '#9b9b9b', diff --git a/docs/state.md b/docs/state.md index 692daa7cfc8..a9f2b408278 100644 --- a/docs/state.md +++ b/docs/state.md @@ -181,37 +181,38 @@ AppStore }, ledger: { info: { - address: string, // the BTC wallet address (in base58) - amount: number, // fiat amount to contribute per reconciliation period - balance: string, // confirmed balance in BTC.toFixed(4) + addresses: { + BAT: string, + BTC: string, + CARD_ID: string, + ETH: string, + LTC: string + }, + balance: number, // confirmed balance in BAT.toFixed(2) bravery: { + days: number, fee: { - amount: number, // set from `amount` above - currency: string // set from `currency` above - } - }, // values round-tripped through the ledger-client - btc: string, // BTC to contribute per reconciliation period - buyURL: string, // URL to buy bitcoin using debit/credit card - countryCode: string, // ISO3166 2-letter code for country of browser's location - created: boolean, // wallet is created + amount: number, + currency: string + }, + setting: string + }, + converted: string, + created, bolean, // wallet is created creating: boolean, // wallet is being created - currency: string, // fiat currency denominating the amount - error: { - caller: string, // function in which error was handled - error: object // error object returned - }, // non-null if the last updateLedgerInfo happened concurrently with an error - exchangeInfo: { - exchangeName: string, // the name of the BTC exchange - exchangeURL: string // the URL of the BTC exchange - }, // information about the corresponding "friendliest" BTC exchange (suggestions welcome!) + currentRate: number, hasBitcoinHandler: boolean, // brave browser has a `bitcoin:` URI handler - passphrase: string, // the BTC wallet passphrase - paymentIMG: string, // the QR code equivalent of `paymentURL` expressed as "data:image/...;base64,..." - paymentURL: string, // bitcoin:...?amount={btc}&label=Brave%20Software - reconcileFrequency: number, // duration between each reconciliation in days - reconcileStamp: number, // timestamp for the next reconcilation - recoverySucceeded: boolean, // the status of an attempted recovery - probi: number, // confirmed balance as an integer number of probi + passphrase: string, // the BAT wallet passphrase + paymentId: string, + probi: number, + rates:{ + BTC: string, + ETH: number, + EUR: number, + USD: number + }, + reconcileFrequency: number // duration between each reconciliation in days + reconcileStamp: number, // timestamp for the next reconcilation transactions: [{ ballots: { [publisher]: number // e.g., "wikipedia.org": 3 @@ -230,8 +231,8 @@ AppStore count: number, // total number of ballots allowed to be cast submissionStamp: number, // timestamp for this contribution viewingId: string, // UUIDv4 for this contribution - }], // contributions reconciling/reconciled - unconfirmed: string // unconfirmed balance in BTC.toFixed(4) + }], + unconfirmed: string // unconfirmed balance in BAT.toFixed(2) }, locations: { [url]: { @@ -241,6 +242,7 @@ AppStore promotion: { activeState: string, claimedTimestamp: number, + minimumReconcileTimestamp: number, promotionId: number, remindTimestamp: number, stateWallet: { @@ -261,10 +263,10 @@ AppStore } }, panel: { - disclaimer: string, - optInMarkup: string, - optedInButton: string, - successText: string + optInMarkup: { + message: Array, + title: string + } } }, emptyWallet: { @@ -285,9 +287,11 @@ AppStore }, panel: { disclaimer: string, - optInMarkup: string, optedInButton: string, - successText: string + successText: { + message: string, + title: string + } } }, fundedWallet: { @@ -308,9 +312,11 @@ AppStore }, panel: { disclaimer: string, - optInMarkup: string, optedInButton: string, - successText: string + successText: { + message: string, + title: string + } } } } diff --git a/js/actions/appActions.js b/js/actions/appActions.js index e1209daf52e..a3a98f0b85b 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -1855,6 +1855,12 @@ const appActions = { }) }, + onPromotionGet: function () { + dispatch({ + actionType: appConstants.APP_ON_PROMOTION_GET + }) + }, + onLedgerNotificationInterval: function () { dispatch({ actionType: appConstants.APP_ON_LEDGER_NOTIFICATION_INTERVAL diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 1620db0f1fb..dce35562b19 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -186,6 +186,7 @@ const appConstants = { APP_ON_PROMOTION_RESPONSE: _, APP_ON_PROMOTION_REMIND: _, APP_ON_PROMOTION_REMOVAL: _, + APP_ON_PROMOTION_GET: _, APP_ON_LEDGER_NOTIFICATION_INTERVAL: _, APP_ON_LEDGER_MEDIA_DATA: _, APP_ON_PRUNE_SYNOPSIS: _, diff --git a/less/about/preferences.less b/less/about/preferences.less index 2ba7f8eb2d0..750332ba4d4 100644 --- a/less/about/preferences.less +++ b/less/about/preferences.less @@ -206,37 +206,3 @@ table.sortableTable { cursor: pointer; } } - -.disabledLedgerContent { - h3 { - font-size: 18px; - padding-bottom: 0.5em - } - - p { - padding: 0.5em 0; - } - - a { - display: inline-block; - text-decoration: underline; - color: rgb(255, 80, 0); - cursor: pointer; - margin: 0; - - &:hover { - color: #000; - } - } - - b { - font-weight: bold; - } -} - -.enabledContent__grant { - b { - font-weight: normal; - color: #ff5500; - } -} diff --git a/package-lock.json b/package-lock.json index 40b75b0c77a..13976c4e347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1444,9 +1444,9 @@ } }, "bat-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bat-client/-/bat-client-2.0.1.tgz", - "integrity": "sha512-d10bfnqM18QnlUBzFygCAJCfVWFocyfNSpG+BBPidVvAog9wqGucF7VLhXziEBXMR6Ys5iy5CTjpzSA4LHeWRQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bat-client/-/bat-client-2.0.2.tgz", + "integrity": "sha512-BDthH12iuhA0CGqmHazhAG0Tfq5rCwZN9XxaZON4wFM7KpaZ3329tpi85zXFrTEKNgQz0ZxNEI5EALkeYCU0Bw==", "requires": { "@ambassify/backoff-strategies": "1.0.0", "bat-balance": "1.0.4", @@ -5335,6 +5335,61 @@ } } }, + "electron-download": { + "version": "github:brave/electron-download#409b65caff14edeef1daa36a7445ba6334658d7c", + "dev": true, + "requires": { + "debug": "2.6.9", + "home-path": "1.0.5", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "mv": "2.1.1", + "nugget": "1.6.2", + "path-exists": "1.0.0", + "rc": "1.2.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "nugget": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz", + "integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=", + "dev": true, + "requires": { + "debug": "2.6.9", + "minimist": "1.2.0", + "pretty-bytes": "1.0.4", + "progress-stream": "1.2.0", + "request": "2.82.0", + "single-line-log": "0.4.1", + "throttleit": "0.0.2" + } + }, + "path-exists": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", + "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", + "dev": true + }, + "single-line-log": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", + "integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=", + "dev": true + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + } + } + }, "electron-download-tf": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/electron-download-tf/-/electron-download-tf-4.3.1.tgz", @@ -5520,20 +5575,6 @@ "integrity": "sha1-HUixB9ghJqLz4hHC6iX4A7pVGyE=", "dev": true }, - "electron-download": { - "version": "github:brave/electron-download#409b65caff14edeef1daa36a7445ba6334658d7c", - "dev": true, - "requires": { - "debug": "2.6.9", - "home-path": "1.0.5", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "mv": "2.1.1", - "nugget": "1.6.2", - "path-exists": "1.0.0", - "rc": "1.2.1" - } - }, "electron-osx-sign": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.3.2.tgz", @@ -5583,27 +5624,6 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, - "nugget": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz", - "integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=", - "dev": true, - "requires": { - "debug": "2.6.9", - "minimist": "1.2.0", - "pretty-bytes": "1.0.4", - "progress-stream": "1.2.0", - "request": "2.82.0", - "single-line-log": "0.4.1", - "throttleit": "0.0.2" - } - }, - "path-exists": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", - "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", - "dev": true - }, "plist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", @@ -5616,18 +5636,6 @@ "xmldom": "0.1.27" } }, - "single-line-log": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", - "integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=", - "dev": true - }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", - "dev": true - }, "xmlbuilder": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz", @@ -5645,61 +5653,6 @@ "requires": { "electron-download": "github:brave/electron-download#409b65caff14edeef1daa36a7445ba6334658d7c", "extract-zip": "1.6.5" - }, - "dependencies": { - "electron-download": { - "version": "github:brave/electron-download#409b65caff14edeef1daa36a7445ba6334658d7c", - "dev": true, - "requires": { - "debug": "2.6.9", - "home-path": "1.0.5", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "mv": "2.1.1", - "nugget": "1.6.2", - "path-exists": "1.0.0", - "rc": "1.2.1" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "nugget": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz", - "integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=", - "dev": true, - "requires": { - "debug": "2.6.9", - "minimist": "1.2.0", - "pretty-bytes": "1.0.4", - "progress-stream": "1.2.0", - "request": "2.82.0", - "single-line-log": "0.4.1", - "throttleit": "0.0.2" - } - }, - "path-exists": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", - "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", - "dev": true - }, - "single-line-log": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", - "integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=", - "dev": true - }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", - "dev": true - } } }, "electron-publish": { diff --git a/package.json b/package.json index fb4f9056307..9219475d4da 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "aphrodite": "1.1.0", "async": "^2.0.1", "bat-balance": "^1.0.3", - "bat-client": "^2.0.1", + "bat-client": "^2.0.2", "bat-publisher": "^2.0.0", "bignumber.js": "^4.0.4", "bloodhound-js": "brave/bloodhound", diff --git a/test/unit/app/browser/api/ledgerNotificationsTest.js b/test/unit/app/browser/api/ledgerNotificationsTest.js index a440a3e27c5..93d4c4cf1fd 100644 --- a/test/unit/app/browser/api/ledgerNotificationsTest.js +++ b/test/unit/app/browser/api/ledgerNotificationsTest.js @@ -592,6 +592,14 @@ describe('ledgerNotifications unit test', function () { ledgerNotificationsApi.showPromotionNotification(state) assert(showNotificationSpy.calledOnce) }) + + it('we set global notification', function () { + const notification = state + .getIn(['ledger', 'promotion', 'stateWallet', 'disabledWallet', 'notification']) + .set('from', 'ledger') + ledgerNotificationsApi.showPromotionNotification(state) + assert(showNotificationSpy.withArgs(notification.toJS()).calledOnce) + }) }) describe('removePromotionNotification', function () { diff --git a/test/unit/app/browser/api/ledgerTest.js b/test/unit/app/browser/api/ledgerTest.js index 6f6a415257f..9a203321dba 100644 --- a/test/unit/app/browser/api/ledgerTest.js +++ b/test/unit/app/browser/api/ledgerTest.js @@ -43,6 +43,7 @@ describe('ledger api unit tests', function () { let onChangeSettingSpy let ledgersetPromotionSpy let ledgergetPromotionSpy + let ledgerSetTimeUntilReconcile const defaultAppState = Immutable.fromJS({ cache: { @@ -146,7 +147,8 @@ describe('ledger api unit tests', function () { return 0 }, getPromotion: () => {}, - setPromotion: () => {} + setPromotion: () => {}, + setTimeUntilReconcile: () => {} } ledgerClient.prototype.boolion = function (value) { return false } ledgerClient.prototype.getWalletPassphrase = function (state) {} @@ -154,6 +156,7 @@ describe('ledger api unit tests', function () { ledgerTransitionedSpy = sinon.spy(lc, 'transitioned') ledgersetPromotionSpy = sinon.spy(lc, 'setPromotion') ledgergetPromotionSpy = sinon.spy(lc, 'getPromotion') + ledgerSetTimeUntilReconcile = sinon.spy(lc, 'setTimeUntilReconcile') ledgerClient.returns(lc) mockery.registerMock('bat-client', ledgerClient) @@ -164,8 +167,10 @@ describe('ledger api unit tests', function () { return null }, Synopsis: batPublisher.Synopsis, - getMedia: { - getPublisherFromMediaProps: () => {} + getMedia: () => { + return { + getPublisherFromMediaProps: () => {} + } } } mockery.registerMock('bat-publisher', ledgerPublisher) @@ -531,7 +536,7 @@ describe('ledger api unit tests', function () { }) describe('onMediaRequest', function () { - let publisherFromMediaPropsSpy, saveVisitSpy + let mediaSpy, saveVisitSpy const cacheAppState = defaultAppState .setIn(['cache', 'ledgerVideos', videoId], Immutable.fromJS({ @@ -543,12 +548,12 @@ describe('ledger api unit tests', function () { })) beforeEach(function () { - publisherFromMediaPropsSpy = sinon.spy(ledgerPublisher.getMedia, 'getPublisherFromMediaProps') + mediaSpy = sinon.spy(ledgerPublisher, 'getMedia') saveVisitSpy = sinon.spy(ledgerApi, 'saveVisit') }) afterEach(function () { - publisherFromMediaPropsSpy.restore() + mediaSpy.restore() saveVisitSpy.restore() ledgerApi.setCurrentMediaKey(null) }) @@ -560,7 +565,7 @@ describe('ledger api unit tests', function () { it('does nothing if input is null', function () { const result = ledgerApi.onMediaRequest(defaultAppState) assert.deepEqual(result.toJS(), defaultAppState.toJS()) - assert(publisherFromMediaPropsSpy.notCalled) + assert(mediaSpy.notCalled) assert(saveVisitSpy.notCalled) }) @@ -584,7 +589,7 @@ describe('ledger api unit tests', function () { it('does nothing if tab is private', function () { const xhr2 = 'https://www.youtube.com/api/stats/watchtime?docid=kLiLOkzLetE&st=20.338&et=21.339' ledgerApi.onMediaRequest(cacheAppState, xhr2, ledgerMediaProviders.YOUTUBE, 1) - assert(publisherFromMediaPropsSpy.notCalled) + assert(mediaSpy.notCalled) assert(saveVisitSpy.notCalled) }) }) @@ -592,13 +597,13 @@ describe('ledger api unit tests', function () { it('set currentMediaKey when it is different than saved', function () { ledgerApi.onMediaRequest(defaultAppState, xhr, ledgerMediaProviders.YOUTUBE, 1) assert.equal(ledgerApi.getCurrentMediaKey(), videoId) - assert(publisherFromMediaPropsSpy.calledOnce) + assert(mediaSpy.calledOnce) assert(saveVisitSpy.notCalled) }) it('get data from cache, if we have publisher in synopsis', function () { ledgerApi.onMediaRequest(cacheAppState, xhr, ledgerMediaProviders.YOUTUBE, 1) - assert(publisherFromMediaPropsSpy.notCalled) + assert(mediaSpy.notCalled) assert(saveVisitSpy.withArgs(cacheAppState, publisherKey, { duration: 10001, revisited: false, @@ -611,7 +616,7 @@ describe('ledger api unit tests', function () { publisher: publisherKey })) ledgerApi.onMediaRequest(state, xhr, ledgerMediaProviders.YOUTUBE, 1) - assert(publisherFromMediaPropsSpy.calledOnce) + assert(mediaSpy.calledOnce) assert(saveVisitSpy.notCalled) }) @@ -627,7 +632,7 @@ describe('ledger api unit tests', function () { // second call, revisit true ledgerApi.onMediaRequest(cacheAppState, xhr, ledgerMediaProviders.YOUTUBE, 1) - assert(publisherFromMediaPropsSpy.notCalled) + assert(mediaSpy.notCalled) assert(saveVisitSpy.withArgs(cacheAppState, publisherKey, { duration: 10001, revisited: false, @@ -1366,14 +1371,24 @@ describe('ledger api unit tests', function () { it('empty client', function () { const oldClient = ledgerApi.getClient() ledgerApi.setClient(undefined) - ledgerApi.getPromotion() + ledgerApi.getPromotion(defaultAppState) assert(ledgergetPromotionSpy.calledOnce) assert(ledgerClient.calledOnce) ledgerApi.setClient(oldClient) }) + it('empty client with existing wallet', function () { + const state = defaultAppState.setIn(['ledger', 'info', 'paymentId'], 'a-1-a') + const oldClient = ledgerApi.getClient() + ledgerApi.setClient(undefined) + ledgerApi.getPromotion(state) + assert(ledgergetPromotionSpy.withArgs(sinon.match.any, 'a-1-a', sinon.match.any).calledOnce) + assert(ledgerClient.calledOnce) + ledgerApi.setClient(oldClient) + }) + it('existing client', function () { - ledgerApi.getPromotion() + ledgerApi.getPromotion(defaultAppState) assert(ledgerClient.notCalled) assert(ledgergetPromotionSpy.calledOnce) }) @@ -1384,10 +1399,15 @@ describe('ledger api unit tests', function () { before(function () { removeNotificationSpy = sinon.spy(ledgerNotificationsApi, 'removePromotionNotification') + ledgerSetTimeUntilReconcile.reset() getBalanceSpy = sinon.spy(ledgerApi, 'getBalance') fakeClock = sinon.useFakeTimers() }) + afterEach(function () { + ledgerSetTimeUntilReconcile.reset() + }) + after(function () { removeNotificationSpy.restore() getBalanceSpy.restore() @@ -1403,5 +1423,21 @@ describe('ledger api unit tests', function () { assert(getBalanceSpy.calledOnce) assert.deepEqual(result.toJS(), expectedSate.toJS()) }) + + it('set minReconcile timestamp if higher then current reconcileStamp', function () { + const state = defaultAppState + .setIn(['ledger', 'promotion', 'minimumReconcileTimestamp'], 10000) + .setIn(['ledger', 'info', 'reconcileStamp'], 100) + ledgerApi.onPromotionResponse(state) + assert(ledgerSetTimeUntilReconcile.calledOnce) + }) + + it('do not set minReconcile timestamp if lower then current reconcileStamp', function () { + const state = defaultAppState + .setIn(['ledger', 'promotion', 'minimumReconcileTimestamp'], 10000) + .setIn(['ledger', 'info', 'reconcileStamp'], 10001) + ledgerApi.onPromotionResponse(state) + assert(ledgerSetTimeUntilReconcile.notCalled) + }) }) }) diff --git a/test/unit/app/browser/reducers/ledgerReducerTest.js b/test/unit/app/browser/reducers/ledgerReducerTest.js index 84f310e0c07..f377a8d49db 100644 --- a/test/unit/app/browser/reducers/ledgerReducerTest.js +++ b/test/unit/app/browser/reducers/ledgerReducerTest.js @@ -11,6 +11,7 @@ describe('ledgerReducer unit tests', function () { let ledgerReducer let fakeLedgerApi let fakeLedgerState + let fakeLedgerNotifications let appState let paymentsEnabled let returnedState @@ -52,16 +53,27 @@ describe('ledgerReducer unit tests', function () { onTimeUntilReconcile: dummyModifyState, run: () => {}, onNetworkConnected: dummyModifyState, - getNewClient: () => {} + getNewClient: () => {}, + claimPromotion: () => {}, + onPromotionResponse: dummyModifyState, + getPromotion: () => {} } fakeLedgerState = { resetSynopsis: dummyModifyState, setRecoveryStatus: dummyModifyState, setInfoProp: dummyModifyState, - saveSynopsis: dummyModifyState + saveSynopsis: dummyModifyState, + savePromotion: dummyModifyState, + remindMeLater: dummyModifyState, + removePromotion: dummyModifyState + } + fakeLedgerNotifications = { + onPromotionReceived: dummyModifyState, + onInterval: dummyModifyState } mockery.registerMock('../../browser/api/ledger', fakeLedgerApi) mockery.registerMock('../../common/state/ledgerState', fakeLedgerState) + mockery.registerMock('../../browser/api/ledgerNotifications', fakeLedgerNotifications) mockery.registerMock('../../../js/settings', { getSetting: (settingKey, settingsCollection, value) => { if (settingKey === settings.PAYMENTS_ENABLED) { @@ -586,4 +598,143 @@ describe('ledgerReducer unit tests', function () { assert(ledgerStateSpy.calledOnce) }) }) + + describe('APP_SAVE_LEDGER_PROMOTION', function () { + let savePromotionSpy, onPromotionReceivedSpy + + before(function () { + savePromotionSpy = sinon.spy(fakeLedgerState, 'savePromotion') + onPromotionReceivedSpy = sinon.spy(fakeLedgerNotifications, 'onPromotionReceived') + }) + + after(function () { + savePromotionSpy.restore() + }) + + it('execute', function () { + const result = ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_SAVE_LEDGER_PROMOTION, + promotion: { + promotionId: '1' + } + })) + assert.notDeepEqual(result, appState) + assert(savePromotionSpy.calledOnce) + assert(onPromotionReceivedSpy.calledOnce) + }) + }) + + describe('APP_ON_PROMOTION_CLAIM', function () { + let claimPromotionSpy + + before(function () { + claimPromotionSpy = sinon.spy(fakeLedgerApi, 'claimPromotion') + }) + + after(function () { + claimPromotionSpy.restore() + }) + + it('execute', function () { + ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_ON_PROMOTION_CLAIM + })) + assert(claimPromotionSpy.calledOnce) + }) + }) + + describe('APP_ON_PROMOTION_REMIND', function () { + let remindMeLaterSpy + + before(function () { + remindMeLaterSpy = sinon.spy(fakeLedgerState, 'remindMeLater') + }) + + after(function () { + remindMeLaterSpy.restore() + }) + + it('execute', function () { + ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_ON_PROMOTION_REMIND + })) + assert(remindMeLaterSpy.calledOnce) + }) + }) + + describe('APP_ON_PROMOTION_RESPONSE', function () { + let onPromotionResponseSpy + + before(function () { + onPromotionResponseSpy = sinon.spy(fakeLedgerApi, 'onPromotionResponse') + }) + + after(function () { + onPromotionResponseSpy.restore() + }) + + it('execute', function () { + ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_ON_PROMOTION_RESPONSE + })) + assert(onPromotionResponseSpy.calledOnce) + }) + }) + + describe('APP_ON_PROMOTION_REMOVAL', function () { + let removePromotionSpy + + before(function () { + removePromotionSpy = sinon.spy(fakeLedgerState, 'removePromotion') + }) + + after(function () { + removePromotionSpy.restore() + }) + + it('execute', function () { + ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_ON_PROMOTION_REMOVAL + })) + assert(removePromotionSpy.calledOnce) + }) + }) + + describe('APP_ON_LEDGER_NOTIFICATION_INTERVAL', function () { + let onIntervalSpy + + before(function () { + onIntervalSpy = sinon.spy(fakeLedgerNotifications, 'onInterval') + }) + + after(function () { + onIntervalSpy.restore() + }) + + it('execute', function () { + ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_ON_LEDGER_NOTIFICATION_INTERVAL + })) + assert(onIntervalSpy.calledOnce) + }) + }) + + describe('APP_ON_PROMOTION_GET', function () { + let getPromotionSpy + + before(function () { + getPromotionSpy = sinon.spy(fakeLedgerApi, 'getPromotion') + }) + + after(function () { + getPromotionSpy.restore() + }) + + it('execute', function () { + ledgerReducer(appState, Immutable.fromJS({ + actionType: appConstants.APP_ON_PROMOTION_GET + })) + assert(getPromotionSpy.calledOnce) + }) + }) }) diff --git a/test/unit/app/common/lib/ledgerUtilTest.js b/test/unit/app/common/lib/ledgerUtilTest.js index 96939a6fa39..f58953e49c3 100644 --- a/test/unit/app/common/lib/ledgerUtilTest.js +++ b/test/unit/app/common/lib/ledgerUtilTest.js @@ -5,7 +5,7 @@ const Immutable = require('immutable') require('../../../braveUnit') const ledgerMediaProviders = require('../../../../../app/common/constants/ledgerMediaProviders') -describe('ledgerUtil test', function () { +describe('ledgerUtil unit test', function () { let ledgerUtil let fakeLevel const fakeElectron = require('../../../lib/fakeElectron') @@ -331,4 +331,36 @@ describe('ledgerUtil test', function () { assert.equal(result, ledgerMediaProviders.YOUTUBE) }) }) + + describe('milliseconds', function () { + it('seconds', function () { + const result = ledgerUtil.milliseconds.second + assert.equal(result, 1000) + }) + + it('minute', function () { + const result = ledgerUtil.milliseconds.minute + assert.equal(result, 60000) + }) + + it('hour', function () { + const result = ledgerUtil.milliseconds.hour + assert.equal(result, 3600000) + }) + + it('day', function () { + const result = ledgerUtil.milliseconds.day + assert.equal(result, 86400000) + }) + + it('week', function () { + const result = ledgerUtil.milliseconds.week + assert.equal(result, 604800000) + }) + + it('year', function () { + const result = ledgerUtil.milliseconds.year + assert.equal(result, 31536000000) + }) + }) }) diff --git a/test/unit/app/common/state/ledgerStateTest.js b/test/unit/app/common/state/ledgerStateTest.js index c5e58db3afc..04ef86ab37e 100644 --- a/test/unit/app/common/state/ledgerStateTest.js +++ b/test/unit/app/common/state/ledgerStateTest.js @@ -2,39 +2,65 @@ * 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/. */ -/* global describe, it */ +/* global describe, it, before, after, afterEach */ const assert = require('assert') const Immutable = require('immutable') +const sinon = require('sinon') +const mockery = require('mockery') require('../../../braveUnit') const ledgerState = require('../../../../../app/common/state/ledgerState') +const appActions = require('../../../../../js/actions/appActions') +const settings = require('../../../../../js/constants/settings') -const blankState = Immutable.fromJS({ - ledger: {} -}) +describe('ledgerState unit test', function () { + // State + const defaultState = Immutable.fromJS({ + ledger: {} + }) -const stateWithData = Immutable.fromJS({ - ledger: { - publisherTime: 1 - } -}) + const stateWithData = Immutable.fromJS({ + ledger: { + publisherTime: 1 + } + }) + + // settings + let paymentsEnabled = true + + before(function () { + mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false, + useCleanCache: true + }) + + mockery.registerMock('../../../js/settings', { + getSetting: (settingKey) => { + switch (settingKey) { + case settings.PAYMENTS_ENABLED: + return paymentsEnabled + } + return false + } + }) + }) -describe('ledgerState unit test', function () { describe('setLedgerValue', function () { it('null case', function () { - const result = ledgerState.setLedgerValue(blankState) - assert.deepEqual(result.toJS(), blankState.toJS()) + const result = ledgerState.setLedgerValue(defaultState) + assert.deepEqual(result.toJS(), defaultState.toJS()) }) it('key is provided', function () { - const result = ledgerState.setLedgerValue(blankState, 'publisherTime', 1) + const result = ledgerState.setLedgerValue(defaultState, 'publisherTime', 1) assert.deepEqual(result.toJS(), stateWithData.toJS()) }) }) describe('getLedgerValue', function () { it('null case', function () { - const result = ledgerState.getLedgerValue(blankState) + const result = ledgerState.getLedgerValue(defaultState) assert.deepEqual(result, null) }) @@ -43,4 +69,457 @@ describe('ledgerState unit test', function () { assert.deepEqual(result, 1) }) }) + + describe('savePromotion', function () { + let setActivePromotionSpy + + before(function () { + setActivePromotionSpy = sinon.spy(ledgerState, 'setActivePromotion') + }) + + afterEach(function () { + setActivePromotionSpy.reset() + }) + + after(function () { + setActivePromotionSpy.restore() + }) + + it('null case', function () { + const result = ledgerState.savePromotion(defaultState) + assert.deepEqual(result.toJS(), defaultState.toJS()) + assert(setActivePromotionSpy.notCalled) + }) + + it('promotion is regular object', function () { + const result = ledgerState.savePromotion(defaultState, { + promotionId: '1' + }) + const expectedState = defaultState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + activeState: 'disabledWallet', + promotionId: '1', + remindTimestamp: -1 + })) + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setActivePromotionSpy.calledOnce) + }) + + it('we already have the same promotion', function () { + const state = defaultState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1', + remindTimestamp: 10, + stateWallet: { + disabledWallet: { + notification: { + message: 'Hello' + } + } + } + })) + const result = ledgerState.savePromotion(state, Immutable.fromJS({ + promotionId: '1', + stateWallet: { + disabledWallet: { + notification: { + message: 'New Hello' + } + } + } + })) + const expectedState = defaultState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + activeState: 'disabledWallet', + promotionId: '1', + remindTimestamp: 10, + stateWallet: { + disabledWallet: { + notification: { + message: 'New Hello' + } + } + } + })) + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setActivePromotionSpy.calledOnce) + }) + + describe('existing promotion', function () { + let hideNotificationSpy + before(function () { + hideNotificationSpy = sinon.spy(appActions, 'hideNotification') + }) + + it('we have existing promotion, but is empty', function () { + const state = defaultState + .setIn(['ledger', 'promotion'], Immutable.Map()) + + const result = ledgerState.savePromotion(state, Immutable.fromJS({ + promotionId: '1' + })) + const expectedState = state + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + activeState: 'disabledWallet', + promotionId: '1', + remindTimestamp: -1 + })) + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setActivePromotionSpy.calledOnce) + assert(hideNotificationSpy.notCalled) + }) + + it('we have existing promotion', function () { + const state = defaultState + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '2', + activeState: 'disabledWallet', + remindTimestamp: 10, + stateWallet: { + disabledWallet: { + notification: { + message: 'Hello' + } + } + } + })) + const result = ledgerState.savePromotion(state, Immutable.fromJS({ + promotionId: '1', + stateWallet: { + disabledWallet: { + notification: { + message: 'New Hello' + } + } + } + })) + const expectedState = state + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + activeState: 'disabledWallet', + promotionId: '1', + remindTimestamp: -1, + stateWallet: { + disabledWallet: { + notification: { + message: 'New Hello' + } + } + } + })) + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setActivePromotionSpy.calledOnce) + assert(hideNotificationSpy.withArgs('Hello').calledOnce) + }) + }) + }) + + describe('getPromotion', function () { + it('no promotion', function () { + const result = ledgerState.getPromotion(defaultState) + assert.deepEqual(result.toJS(), {}) + }) + + it('promotion exists', function () { + const promotion = { + promotionId: '2', + activeState: 'disabledWallet', + remindTimestamp: 10, + stateWallet: { + disabledWallet: { + notification: { + message: 'Hello' + } + } + } + } + const state = defaultState.setIn(['ledger', 'promotion'], Immutable.fromJS(promotion)) + const result = ledgerState.getPromotion(state) + assert.deepEqual(result.toJS(), promotion) + }) + }) + + describe('setActivePromotion', function () { + let setPromotionPropSpy + + before(function () { + setPromotionPropSpy = sinon.spy(ledgerState, 'setPromotionProp') + }) + + afterEach(function () { + setPromotionPropSpy.reset() + }) + + after(function () { + setPromotionPropSpy.restore() + }) + + it('promotion is missing', function () { + const result = ledgerState.setActivePromotion(defaultState) + assert.deepEqual(result.toJS(), defaultState.toJS()) + assert(setPromotionPropSpy.notCalled) + }) + + it('payment status is not provided', function () { + paymentsEnabled = false + const state = defaultState.setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1' + })) + const result = ledgerState.setActivePromotion(state) + const expectedState = state + .setIn(['ledger', 'promotion', 'activeState'], 'disabledWallet') + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setPromotionPropSpy.calledOnce) + paymentsEnabled = true + }) + + it('payment is disabled', function () { + const state = defaultState.setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1' + })) + const result = ledgerState.setActivePromotion(state, false) + const expectedState = state + .setIn(['ledger', 'promotion', 'activeState'], 'disabledWallet') + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setPromotionPropSpy.calledOnce) + }) + + it('payment is enabled, but wallet is empty', function () { + const state = defaultState.setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1' + })) + const result = ledgerState.setActivePromotion(state, true) + const expectedState = state + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setPromotionPropSpy.calledOnce) + }) + + it('payment is enabled and wallet is founded', function () { + const state = defaultState + .setIn(['ledger', 'info', 'balance'], 10) + .setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1' + })) + const result = ledgerState.setActivePromotion(state, true) + const expectedState = state + .setIn(['ledger', 'promotion', 'activeState'], 'fundedWallet') + assert.deepEqual(result.toJS(), expectedState.toJS()) + assert(setPromotionPropSpy.calledOnce) + }) + }) + + describe('getActivePromotion', function () { + it('active state is missing', function () { + const result = ledgerState.getActivePromotion(defaultState) + assert.deepEqual(result.toJS(), {}) + }) + + it('active state is provided, but promotion do not have this promotion', function () { + const state = defaultState.setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + const result = ledgerState.getActivePromotion(state) + assert.deepEqual(result.toJS(), {}) + }) + + it('promotion is found', function () { + const notification = { + notification: { + message: 'Hi' + } + } + const state = defaultState + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + .setIn(['ledger', 'promotion', 'stateWallet', 'emptyWallet'], Immutable.fromJS(notification)) + const result = ledgerState.getActivePromotion(state) + assert.deepEqual(result.toJS(), notification) + }) + }) + + describe('setPromotionProp', function () { + it('null case', function () { + const result = ledgerState.setPromotionProp(defaultState) + assert.deepEqual(result.toJS(), defaultState.toJS()) + }) + + it('prop is set', function () { + const result = ledgerState.setPromotionProp(defaultState, 'promotionId', '1') + const expectedState = defaultState.setIn(['ledger', 'promotion', 'promotionId'], '1') + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + }) + + describe('removePromotion', function () { + it('null case', function () { + const result = ledgerState.removePromotion(defaultState) + const expectedState = defaultState.setIn(['ledger', 'promotion'], Immutable.Map()) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + + it('remove promotion', function () { + const state = defaultState.setIn(['ledger', 'promotion'], Immutable.fromJS({ + promotionId: '1' + })) + const result = ledgerState.removePromotion(state) + const expectedState = state.setIn(['ledger', 'promotion'], Immutable.Map()) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + }) + + describe('remindMeLater', function () { + let fakeClock + + before(function () { + fakeClock = sinon.useFakeTimers() + fakeClock.tick(6000) + }) + + after(function () { + fakeClock.restore() + }) + + it('null case', function () { + const result = ledgerState.remindMeLater(defaultState) + const expectedState = defaultState.setIn(['ledger', 'promotion', 'remindTimestamp'], 86406000) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + + it('custom time', function () { + const result = ledgerState.remindMeLater(defaultState, 50) + const expectedState = defaultState.setIn(['ledger', 'promotion', 'remindTimestamp'], 6050) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + }) + + describe('getPromotionNotification', function () { + it('promotion is missing', function () { + const result = ledgerState.getPromotionNotification(defaultState) + assert.deepEqual(result.toJS(), {}) + }) + + it('we do not have active promotion', function () { + const state = defaultState.setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + const result = ledgerState.getPromotionNotification(state) + assert.deepEqual(result.toJS(), {}) + }) + + it('notification is missing', function () { + const state = defaultState + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + .setIn(['ledger', 'promotion', 'stateWallet', 'emptyWallet'], Immutable.Map()) + const result = ledgerState.getPromotionNotification(state) + assert.deepEqual(result.toJS(), {}) + }) + + it('notification is returned', function () { + const notification = { + message: 'Hello' + } + const state = defaultState + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + .setIn(['ledger', 'promotion', 'stateWallet', 'emptyWallet', 'notification'], Immutable.fromJS(notification)) + const result = ledgerState.getPromotionNotification(state) + assert.deepEqual(result.toJS(), notification) + }) + }) + + describe('setPromotionNotificationProp', function () { + it('null case', function () { + const result = ledgerState.setPromotionNotificationProp(defaultState) + assert.deepEqual(result.toJS(), defaultState.toJS()) + }) + + it('active state is missing', function () { + const result = ledgerState.setPromotionNotificationProp(defaultState, 'message') + assert.deepEqual(result.toJS(), defaultState.toJS()) + }) + + it('prop is set', function () { + const state = defaultState + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + const result = ledgerState.setPromotionNotificationProp(state, 'message', 'Hello') + const expectedState = state + .setIn(['ledger', 'promotion', 'stateWallet', 'emptyWallet', 'notification', 'message'], 'Hello') + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + }) + + describe('getAboutPromotion', function () { + it('no active promotion', function () { + const result = ledgerState.getAboutPromotion(defaultState) + assert.deepEqual(result.toJS(), {}) + }) + + it('active promotion (no claim)', function () { + const promo = Immutable.fromJS({ + notification: { + message: 'Hello' + } + }) + const state = defaultState + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + .setIn(['ledger', 'promotion', 'stateWallet', 'emptyWallet'], promo) + const result = ledgerState.getAboutPromotion(state) + assert.deepEqual(result.toJS(), promo.toJS()) + }) + + it('promotion was claimed', function () { + const promo = Immutable.fromJS({ + notification: { + message: 'Hello' + } + }) + const state = defaultState + .setIn(['ledger', 'promotion', 'activeState'], 'emptyWallet') + .setIn(['ledger', 'promotion', 'claimedTimestamp'], 10000) + .setIn(['ledger', 'promotion', 'stateWallet', 'emptyWallet'], promo) + const result = ledgerState.getAboutPromotion(state) + const expectedPromo = promo.set('claimedTimestamp', 10000) + assert.deepEqual(result.toJS(), expectedPromo.toJS()) + }) + }) + + describe('resetInfo', function () { + it('null case', function () { + const state = defaultState.setIn(['ledger', 'info'], Immutable.fromJS({ + paymentId: 'a-1-a', + balance: 10.00 + })) + const result = ledgerState.resetInfo(state) + const expectedState = defaultState.setIn(['ledger', 'info'], Immutable.Map()) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + + it('keep is on, but paymentId is not there', function () { + const state = defaultState.setIn(['ledger', 'info'], Immutable.fromJS({ + balance: 10.00 + })) + const result = ledgerState.resetInfo(state, true) + const expectedState = defaultState.setIn(['ledger', 'info'], Immutable.Map()) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + + it('keep it', function () { + const state = defaultState.setIn(['ledger', 'info'], Immutable.fromJS({ + paymentId: 'a-1-a', + balance: 10.00 + })) + const result = ledgerState.resetInfo(state, true) + const expectedState = defaultState.setIn(['ledger', 'info'], Immutable.fromJS({ + paymentId: 'a-1-a' + })) + assert.deepEqual(result.toJS(), expectedState.toJS()) + }) + }) + + describe('getPromotionProp', function () { + it('null case', function () { + const result = ledgerState.getPromotionProp(defaultState) + assert.deepEqual(result, null) + }) + + it('prop is set', function () { + const state = defaultState.setIn(['ledger', 'promotion', 'promotionId'], '1') + const result = ledgerState.getPromotionProp(state, 'promotionId') + assert.deepEqual(result, '1') + }) + }) })