diff --git a/app/browser/api/ledger.js b/app/browser/api/ledger.js
index b770e693948..c813b5f3964 100644
--- a/app/browser/api/ledger.js
+++ b/app/browser/api/ledger.js
@@ -8,6 +8,7 @@ const acorn = require('acorn')
const moment = require('moment')
const Immutable = require('immutable')
const electron = require('electron')
+const ipc = electron.ipcMain
const path = require('path')
const os = require('os')
const qr = require('qr-image')
@@ -29,6 +30,7 @@ const migrationState = require('../../common/state/migrationState')
// Constants
const settings = require('../../../js/constants/settings')
+const messages = require('../../../js/constants/messages')
// Utils
const tabs = require('../../browser/tabs')
@@ -42,6 +44,7 @@ const ledgerUtil = require('../../common/lib/ledgerUtil')
const tabState = require('../../common/state/tabState')
const pageDataUtil = require('../../common/lib/pageDataUtil')
const ledgerNotifications = require('./ledgerNotifications')
+const ledgerVideoCache = require('../../common/cache/ledgerVideoCache')
// Caching
let locationDefault = 'NOOP'
@@ -107,6 +110,38 @@ underscore.keys(fileTypes).forEach((fileType) => {
})
signatureMax = Math.ceil(signatureMax * 1.5)
+if (ipc) {
+ ipc.on(messages.LEDGER_PUBLISHER, (event, location) => {
+ if (!synopsis || event.sender.session === electron.session.fromPartition('default') || !tldjs.isValid(location)) {
+ event.returnValue = {}
+ return
+ }
+
+ let ctx = urlParse(location, true)
+ ctx.TLD = tldjs.getPublicSuffix(ctx.host)
+ if (!ctx.TLD) {
+ if (_internal.verboseP) console.log('\nno TLD for:' + ctx.host)
+ event.returnValue = {}
+ return
+ }
+
+ ctx = underscore.mapObject(ctx, function (value) {
+ if (!underscore.isFunction(value)) return value
+ })
+ ctx.URL = location
+ ctx.SLD = tldjs.getDomain(ctx.host)
+ ctx.RLD = tldjs.getSubdomain(ctx.host)
+ ctx.QLD = ctx.RLD ? underscore.last(ctx.RLD.split('.')) : ''
+
+ if (!event.sender.isDestroyed()) {
+ event.sender.send(messages.LEDGER_PUBLISHER_RESPONSE + '-' + location, {
+ context: ctx,
+ rules: _internal.ruleset.cooked
+ })
+ }
+ })
+}
+
let ledgerPaymentsPresent = {}
const paymentPresent = (state, tabId, present) => {
if (present) {
@@ -247,7 +282,8 @@ const getPublisherData = (result, scorekeeper) => {
let data = {
verified: result.options.verified || false,
exclude: result.options.exclude || false,
- site: result.publisherKey,
+ publisherKey: result.publisherKey,
+ siteName: result.publisherKey,
views: result.visits,
duration: duration,
daysSpent: 0,
@@ -259,9 +295,16 @@ const getPublisherData = (result, scorekeeper) => {
pinPercentage: result.pinPercentage,
weight: result.pinPercentage
}
- // HACK: Protocol is sometimes blank here, so default to http:// so we can
- // still generate publisherURL.
- data.publisherURL = (result.protocol || 'http:') + '//' + result.publisherKey
+
+ data.publisherURL = result.publisherURL || ((result.protocol || 'https:') + '//' + result.publisherKey)
+
+ // media publisher
+ if (result.faviconName) {
+ data.siteName = locale.translation('publisherMediaName', {
+ publisherName: result.faviconName,
+ provider: result.providerName
+ })
+ }
if (duration >= miliseconds.day) {
data.daysSpent = Math.max(Math.round(duration / miliseconds.day), 1)
@@ -543,7 +586,7 @@ const excludeP = (publisherKey, callback) => {
return done(err, result)
}
- let props = ledgerPublisher.getPublisherProps('https://' + publisherKey)
+ let props = ledgerPublisher.getPublisherProps(publisherKey)
if (!props) return done()
v2RulesetDB.createReadStream({lt: 'domain:'}).on('data', (data) => {
@@ -584,30 +627,26 @@ const excludeP = (publisherKey, callback) => {
})
}
-const addVisit = (state, startTimestamp, location, tabId) => {
+const addSiteVisit = (state, timestamp, location, tabId) => {
if (!synopsis) {
return state
}
location = pageDataUtil.getInfoKey(location)
const locationData = ledgerState.getLocation(state, location)
- const timestamp = new Date().getTime()
+ const duration = new Date().getTime() - timestamp
if (_internal.verboseP) {
console.log(
`locations[${location}]=${JSON.stringify(locationData, null, 2)} ` +
- `duration=${(timestamp - startTimestamp)} msec tabId= ${tabId}`
+ `duration=${(duration)} msec tabId= ${tabId}`
)
}
- if (locationData.isEmpty() || !tabId) {
- return state
- }
- let publisherKey = locationData.get('publisher')
- if (!publisherKey) {
+ if (locationData.isEmpty()) {
return state
}
- let duration = timestamp - startTimestamp
+ let publisherKey = locationData.get('publisher')
let revisitP = false
if (duration >= getSetting(settings.PAYMENTS_MINIMUM_VISIT_TIME)) {
@@ -627,13 +666,19 @@ const addVisit = (state, startTimestamp, location, tabId) => {
}
}
+ return saveVisit(state, publisherKey, duration, revisitP)
+}
+
+const saveVisit = (state, publisherKey, duration, revisited) => {
+ if (!synopsis || !publisherKey) {
+ return state
+ }
+
if (_internal.verboseP) {
- console.log('\nadd publisher ' + publisherKey + ': ' + (duration / 1000) + ' sec' + ' revisitP=' + revisitP + ' state=' +
- JSON.stringify(underscore.extend({location: location}, visitsByPublisher[publisherKey][location]),
- null, 2))
+ console.log('\nadd publisher ' + publisherKey + ': ' + (duration / 1000) + ' sec' + ' revisitP=' + revisited)
}
- synopsis.addPublisher(publisherKey, {duration: duration, revisitP: revisitP})
+ synopsis.addPublisher(publisherKey, {duration: duration, revisitP: revisited})
state = ledgerState.setPublisher(state, publisherKey, synopsis.publishers[publisherKey])
state = updatePublisherInfo(state)
state = checkVerifiedStatus(state, publisherKey)
@@ -684,7 +729,7 @@ const addNewLocation = (state, location, tabId = tabState.TAB_ID_NONE, keepInfo
// Add visit to the ledger when we are not in a private tab
if (!isPrivate && !tabFromState.isEmpty() && ledgerUtil.shouldTrackView(tabFromState)) {
- state = addVisit(state, currentTimestamp, currentUrl, currentTabId)
+ state = addSiteVisit(state, currentTimestamp, currentUrl, currentTabId)
}
}
@@ -1130,21 +1175,12 @@ const cacheRuleSet = (state, ruleset) => {
for (let item of publishers) {
const publisherKey = item[0]
const publisher = item[1]
- const location = (publisher.get('protocol') || 'http:') + '//' + publisherKey
- let ctx = urlParse(location)
+ const ctx = ledgerPublisher.getPublisherProps(publisherKey)
- ctx.TLD = tldjs.getPublicSuffix(ctx.host)
- if (!ctx.TLD) {
- return state
- }
+ if (!ctx.TLD) continue
- ctx = underscore.mapObject(ctx, function (value) {
- if (!underscore.isFunction(value)) return value
- })
- ctx.URL = location
- ctx.SLD = tldjs.getDomain(ctx.host)
- ctx.RLD = tldjs.getSubdomain(ctx.host)
- ctx.QLD = ctx.RLD ? underscore.last(ctx.RLD.split('.')) : ''
+ if (publisher.publisherURL) ctx.URL = publisher.publisherURL
+ if (!ctx.URL) ctx.URL = (publisher.get('protocol') || 'https:') + '//' + publisherKey
stewed.forEach((rule) => {
if (rule.consequent !== null || rule.dom) return
@@ -1178,7 +1214,8 @@ const roundtrip = (params, options, callback) => {
let parts = typeof params.server === 'string' ? urlParse(params.server)
: typeof params.server !== 'undefined' ? params.server
: typeof options.server === 'string' ? urlParse(options.server) : options.server
- const rawP = options.rawP
+ const binaryP = options.binaryP
+ const rawP = binaryP || options.rawP
if (!params.method) params.method = 'GET'
parts = underscore.extend(underscore.pick(parts, ['protocol', 'hostname', 'port']),
@@ -1205,7 +1242,7 @@ const roundtrip = (params, options, callback) => {
url: urlFormat(parts),
method: params.method,
payload: params.payload,
- responseType: 'text',
+ responseType: binaryP ? 'binary' : 'text',
headers: underscore.defaults(params.headers || {}, {'content-type': 'application/json; charset=utf-8'}),
verboseP: options.verboseP
}
@@ -1220,7 +1257,7 @@ const roundtrip = (params, options, callback) => {
console.log('>>> ' + header + ': ' + response.headers[header])
})
console.log('>>>')
- console.log('>>> ' + (body || '').split('\n').join('\n>>> '))
+ console.log('>>> ' + (rawP ? '...' : (body || '').split('\n').join('\n>>> ')))
}
if (err) return callback(err, response)
@@ -2155,6 +2192,117 @@ const transitionWalletToBat = () => {
}
}
+let currentMediaKey = null
+const onMediaRequest = (state, xhr, type, tabId) => {
+ if (!xhr || type == null) {
+ return state
+ }
+
+ const parsed = ledgerUtil.getMediaData(xhr, type)
+ const mediaId = ledgerUtil.getMediaId(parsed, type)
+ const mediaKey = ledgerUtil.getMediaKey(mediaId, type)
+ let duration = ledgerUtil.getMediaDuration(parsed, type)
+
+ if (mediaId == null || duration == null || mediaKey == null) {
+ return state
+ }
+
+ const minDuration = getSetting(settings.PAYMENTS_MINIMUM_VISIT_TIME)
+ duration = parseInt(duration)
+ if (duration > 0 && duration < minDuration) {
+ duration = minDuration
+ }
+
+ if (!ledgerPublisher) {
+ ledgerPublisher = require('bat-publisher')
+ }
+
+ let revisited = true
+ const activeTabId = tabState.getActiveTabId(state)
+ if (activeTabId === tabId && mediaKey !== currentMediaKey) {
+ revisited = false
+ currentMediaKey = mediaKey
+ }
+
+ const cache = ledgerVideoCache.getDataByVideoId(state, mediaKey)
+
+ if (!cache.isEmpty()) {
+ return module.exports.saveVisit(state, cache.get('publisher'), duration, revisited)
+ }
+
+ const options = underscore.extend({roundtrip: roundtrip}, clientOptions)
+ const mediaProps = {
+ mediaId,
+ providerName: type
+ }
+
+ ledgerPublisher.getMedia.getPublisherFromMediaProps(mediaProps, options, (error, response) => {
+ if (error) {
+ console.error('Error while getting publisher from media', error.toString())
+ return
+ }
+
+ // publisher not found
+ if (!response) {
+ return
+ }
+
+ appActions.onLedgerMediaPublisher(mediaKey, response, duration, revisited)
+ })
+
+ return state
+}
+
+const onMediaPublisher = (state, mediaKey, response, duration, revisited) => {
+ const publisherKey = response ? response.get('publisher') : null
+ if (publisherKey == null) {
+ return state
+ }
+
+ let publisher = ledgerState.getPublisher(state, publisherKey)
+ const faviconName = response.get('faviconName')
+ const faviconURL = response.get('faviconURL')
+ const publisherURL = response.get('publisherURL')
+
+ if (publisher.isEmpty()) {
+ revisited = false
+ synopsis.initPublisher(publisherKey)
+
+ synopsis.publishers[publisherKey].faviconName = faviconName
+ synopsis.publishers[publisherKey].faviconURL = faviconURL
+ synopsis.publishers[publisherKey].publisherURL = publisherURL
+ synopsis.publishers[publisherKey].providerName = response.get('providerName')
+
+ if (synopsis.publishers[publisherKey]) {
+ state = ledgerState.setPublisher(state, publisherKey, synopsis.publishers[publisherKey])
+ }
+
+ if (!getSetting(settings.PAYMENTS_SITES_AUTO_SUGGEST)) {
+ appActions.onPublisherOptionUpdate(publisherKey, 'exclude', true)
+ savePublisherOption(publisherKey, 'exclude', true)
+ } else {
+ excludeP(publisherKey, (unused, exclude) => {
+ appActions.onPublisherOptionUpdate(publisherKey, 'exclude', exclude)
+ savePublisherOption(publisherKey, 'exclude', exclude)
+ })
+ }
+ } else {
+ synopsis.publishers[publisherKey].faviconName = faviconName
+ synopsis.publishers[publisherKey].faviconURL = faviconURL
+ synopsis.publishers[publisherKey].publisherURL = publisherURL
+ state = ledgerState.setPublishersProp(state, publisherKey, 'faviconName', faviconName)
+ state = ledgerState.setPublishersProp(state, publisherKey, 'faviconURL', faviconURL)
+ state = ledgerState.setPublishersProp(state, publisherKey, 'publisherURL', publisherURL)
+ }
+
+ // Add to cache
+ state = ledgerVideoCache.setCacheByVideoId(state, mediaKey, response)
+
+ state = module.exports.saveVisit(state, publisherKey, duration, revisited)
+
+ return state
+}
+
const getMethods = () => {
const publicMethods = {
backupKeys,
@@ -2189,7 +2337,10 @@ const getMethods = () => {
getNewClient,
savePublisherData,
pruneSynopsis,
- checkBtcBatMigrated
+ checkBtcBatMigrated,
+ onMediaRequest,
+ onMediaPublisher,
+ saveVisit
}
let privateMethods = {}
@@ -2197,7 +2348,8 @@ const getMethods = () => {
if (process.env.NODE_ENV === 'test') {
privateMethods = {
enable,
- addVisit,
+ addSiteVisit,
+ checkBtcBatMigrated,
clearVisitsByPublisher: function () {
visitsByPublisher = {}
},
@@ -2217,6 +2369,10 @@ const getMethods = () => {
setClient: (data) => {
client = data
},
+ setCurrentMediaKey: (key) => {
+ currentMediaKey = key
+ },
+ getCurrentMediaKey: (key) => currentMediaKey,
synopsisNormalizer,
checkVerifiedStatus
}
diff --git a/app/browser/reducers/ledgerReducer.js b/app/browser/reducers/ledgerReducer.js
index fd6a066607d..f2c5e777d2e 100644
--- a/app/browser/reducers/ledgerReducer.js
+++ b/app/browser/reducers/ledgerReducer.js
@@ -404,6 +404,22 @@ const ledgerReducer = (state, action, immutableAction) => {
state = ledgerState.setLedgerValue(state, 'publisherTimestamp', action.get('timestamp'))
break
}
+ case appConstants.APP_ON_LEDGER_MEDIA_DATA:
+ {
+ state = ledgerApi.onMediaRequest(state, action.get('url'), action.get('type'), action.get('tabId'))
+ break
+ }
+ case appConstants.APP_ON_LEDGER_MEDIA_PUBLISHER:
+ {
+ state = ledgerApi.onMediaPublisher(
+ state,
+ action.get('mediaKey'),
+ action.get('response'),
+ action.get('duration'),
+ action.get('revisited')
+ )
+ break
+ }
}
return state
}
diff --git a/app/common/cache/ledgerVideoCache.js b/app/common/cache/ledgerVideoCache.js
new file mode 100644
index 00000000000..c7f0ae3fcb1
--- /dev/null
+++ b/app/common/cache/ledgerVideoCache.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Immutable = require('immutable')
+const assert = require('assert')
+
+const { makeImmutable, isMap } = require('../state/immutableUtil')
+
+const validateState = function (state) {
+ state = makeImmutable(state)
+ assert.ok(isMap(state), 'state must be an Immutable.Map')
+ assert.ok(isMap(state.getIn(['cache', 'ledgerVideos'])), 'state must contain ledgerVideos as Immutable.Map')
+ return state
+}
+
+const getDataByVideoId = (state, key) => {
+ state = validateState(state)
+ if (key == null) {
+ return Immutable.Map()
+ }
+
+ return state.getIn(['cache', 'ledgerVideos', key]) || Immutable.Map()
+}
+
+const setCacheByVideoId = (state, key, data) => {
+ state = validateState(state)
+ if (key == null) {
+ return state
+ }
+
+ data = makeImmutable(data)
+
+ return state.setIn(['cache', 'ledgerVideos', key], data)
+}
+
+module.exports = {
+ getDataByVideoId,
+ setCacheByVideoId
+}
diff --git a/app/common/constants/ledgerMediaProviders.js b/app/common/constants/ledgerMediaProviders.js
new file mode 100644
index 00000000000..84ef2d1af80
--- /dev/null
+++ b/app/common/constants/ledgerMediaProviders.js
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const providers = {
+ YOUTUBE: 'youtube'
+}
+
+module.exports = providers
diff --git a/app/common/lib/ledgerUtil.js b/app/common/lib/ledgerUtil.js
index 207141aee72..c7b473c541b 100644
--- a/app/common/lib/ledgerUtil.js
+++ b/app/common/lib/ledgerUtil.js
@@ -7,6 +7,7 @@
const Immutable = require('immutable')
const moment = require('moment')
const BigNumber = require('bignumber.js')
+const queryString = require('querystring')
// State
const siteSettingsState = require('../state/siteSettingsState')
@@ -14,11 +15,13 @@ const ledgerState = require('../state/ledgerState')
// Constants
const settings = require('../../../js/constants/settings')
+const ledgerMediaProviders = require('../constants/ledgerMediaProviders')
// Utils
const {responseHasContent} = require('./httpUtil')
const urlUtil = require('../../../js/lib/urlutil')
const getSetting = require('../../../js/settings').getSetting
+const urlParse = require('../urlParse')
/**
* Is page an actual page being viewed by the user? (not an error page, etc)
@@ -188,16 +191,136 @@ const stickyP = (state, publisherKey) => {
return (result === undefined || result)
}
-module.exports = {
- shouldTrackView,
- batToCurrencyString,
- formattedTimeFromNow,
- formattedDateFromTimestamp,
- walletStatus,
- blockedP,
- contributeP,
- visibleP,
- eligibleP,
- stickyP,
- formatCurrentBalance
+const getMediaId = (data, type) => {
+ let id = null
+
+ if (type == null || data == null) {
+ return id
+ }
+
+ switch (type) {
+ case ledgerMediaProviders.YOUTUBE:
+ {
+ id = data.docid
+ break
+ }
+ }
+
+ return id
+}
+
+const getMediaKey = (id, type) => {
+ if (id == null || type == null) {
+ return null
+ }
+
+ return `${type.toLowerCase()}_${id}`
+}
+
+const getMediaData = (xhr, type) => {
+ let result = null
+
+ if (xhr == null || type == null) {
+ return result
+ }
+
+ switch (type) {
+ case ledgerMediaProviders.YOUTUBE:
+ {
+ const parsedUrl = urlParse(xhr)
+ let query = null
+
+ if (parsedUrl && parsedUrl.query) {
+ query = queryString.parse(parsedUrl.query)
+ }
+ result = query
+ break
+ }
+ }
+
+ return result
+}
+
+const getMediaDuration = (data, type) => {
+ let duration = 0
+ switch (type) {
+ case ledgerMediaProviders.YOUTUBE: {
+ duration = getYouTubeDuration(data)
+ break
+ }
+ }
+
+ return duration
+}
+
+const getYouTubeDuration = (data) => {
+ let time = 0
+
+ if (data == null || data.st == null || data.et == null) {
+ return time
+ }
+
+ const startTime = data.st.split(',')
+ const endTime = data.et.split(',')
+
+ if (startTime.length !== endTime.length) {
+ return time
+ }
+
+ for (let i = 0; i < startTime.length; i++) {
+ time += parseFloat(endTime[i]) - parseFloat(startTime[i])
+ }
+
+ // we get seconds back, so we need to convert it into ms
+ time = time * 1000
+
+ return parseInt(time)
+}
+
+const getMediaProvider = (url) => {
+ let provider = null
+
+ if (url == null) {
+ return provider
+ }
+
+ // Youtube
+ if (url.startsWith('https://www.youtube.com/api/stats/watchtime?')) {
+ provider = ledgerMediaProviders.YOUTUBE
+ }
+
+ return provider
+}
+
+const getMethods = () => {
+ const publicMethods = {
+ shouldTrackView,
+ batToCurrencyString,
+ formattedTimeFromNow,
+ formattedDateFromTimestamp,
+ walletStatus,
+ blockedP,
+ contributeP,
+ visibleP,
+ eligibleP,
+ stickyP,
+ formatCurrentBalance,
+ getMediaId,
+ getMediaDuration,
+ getMediaProvider,
+ getMediaData,
+ getMediaKey
+ }
+
+ let privateMethods = {}
+
+ if (process.env.NODE_ENV === 'test') {
+ privateMethods = {
+ getYouTubeDuration
+ }
+ }
+
+ return Object.assign({}, publicMethods, privateMethods)
}
+
+module.exports = getMethods()
diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties
index d651df23a69..c8bfe82910d 100644
--- a/app/extensions/brave/locales/en-US/preferences.properties
+++ b/app/extensions/brave/locales/en-US/preferences.properties
@@ -4,6 +4,7 @@ accountBalanceConnectionError=Please check your Internet connection.
actions=Actions
add=Fund with debit/credit
addFundsHeader=Add funds to your Brave Wallet
+allowMediaPublishers=Allow contributions to videos
upholdFooterText1=All transactions are processed by Uphold.
upholdFooterText2=You can also use Uphold to easily fund your Brave wallet.
learnMore=Learn more...
@@ -248,6 +249,7 @@ privateData=Private Data
privateDataMessage=Clear the following data types when I close Brave
protocolRegistrationPermission=Protocol registration
publisher=Site
+publisherMediaName={{publisherName}} on {{provider}}
publishers=Publishers
rank=Rank
receiptLink=Receipt Link
diff --git a/app/filtering.js b/app/filtering.js
index eb8a75d80c4..4fd57ae5bb5 100644
--- a/app/filtering.js
+++ b/app/filtering.js
@@ -31,6 +31,7 @@ const {updateElectronDownloadItem} = require('./browser/electronDownloadItem')
const {fullscreenOption} = require('./common/constants/settingsEnums')
const isThirdPartyHost = require('./browser/isThirdPartyHost')
const extensionState = require('./common/state/extensionState')
+const ledgerUtil = require('./common/lib/ledgerUtil')
const {cookieExceptions, refererExceptions} = require('../js/data/siteHacks')
const {getBraverySettingsCache, updateBraverySettingsCache} = require('./common/cache/braverySettingsCache')
@@ -200,15 +201,22 @@ function registerForBeforeRequest (session, partition) {
}
}
// Redirect to non-script version of DDG when it's blocked
- let url = details.url
+ const url = details.url
if (details.resourceType === 'mainFrame' &&
url.startsWith('https://duckduckgo.com/?q') &&
module.exports.isResourceEnabled('noScript', url, isPrivate)) {
- url = url.replace('?q=', 'html?q=')
- muonCb({redirectURL: url})
+ muonCb({redirectURL: url.replace('?q=', 'html?q=')})
} else {
muonCb({})
}
+
+ if (module.exports.isResourceEnabled('ledger') && module.exports.isResourceEnabled('ledgerMedia')) {
+ // Ledger media
+ const provider = ledgerUtil.getMediaProvider(url)
+ if (provider) {
+ appActions.onLedgerMediaData(url, provider, details.tabId)
+ }
+ }
})
}
@@ -761,6 +769,14 @@ module.exports.isResourceEnabled = (resourceName, url, isPrivate) => {
return extension !== undefined ? extension.get('enabled') : false
}
+ if (resourceName === 'ledger') {
+ return getSetting(settings.PAYMENTS_ENABLED, settingsState)
+ }
+
+ if (resourceName === 'ledgerMedia') {
+ return getSetting(settings.PAYMENTS_ALLOW_MEDIA_PUBLISHERS, settingsState)
+ }
+
const braverySettings = getBraverySettingsForUrl(url, appState, isPrivate)
// If full shields are down never enable extra protection
diff --git a/app/locale.js b/app/locale.js
index a1b574c0de9..003ecc034a4 100644
--- a/app/locale.js
+++ b/app/locale.js
@@ -182,6 +182,7 @@ var rendererIdentifiers = function () {
'learnSpelling',
'forgetLearnedSpelling',
'lookupSelection',
+ 'publisherMediaName',
// Other identifiers
'aboutBlankTitle',
'urlCopied',
diff --git a/app/renderer/components/preferences/payment/advancedSettings.js b/app/renderer/components/preferences/payment/advancedSettings.js
index e5f508d2a70..dad7d591f88 100644
--- a/app/renderer/components/preferences/payment/advancedSettings.js
+++ b/app/renderer/components/preferences/payment/advancedSettings.js
@@ -73,6 +73,15 @@ class AdvancedSettingsContent extends ImmutableComponent {
prefKey={settings.PAYMENTS_ALLOW_NON_VERIFIED}
settings={this.props.settings}
onChangeSetting={this.props.onChangeSetting}
+ className={css(styles.advancedSettings__switches__listItem_first)}
+ switchClassName={css(styles.advancedSettings__switches__listItem__checkboxSwitch)}
+ />
+
diff --git a/app/renderer/components/preferences/payment/ledgerTable.js b/app/renderer/components/preferences/payment/ledgerTable.js
index 9f8b95f4806..72905e88bde 100644
--- a/app/renderer/components/preferences/payment/ledgerTable.js
+++ b/app/renderer/components/preferences/payment/ledgerTable.js
@@ -52,7 +52,7 @@ class LedgerTable extends ImmutableComponent {
}
getHostPattern (synopsis) {
- return urlUtil.getHostPattern(synopsis.get('site'))
+ return urlUtil.getHostPattern(synopsis.get('publisherKey'))
}
getVerifiedIcon (synopsis) {
@@ -151,10 +151,11 @@ class LedgerTable extends ImmutableComponent {
const duration = synopsis.get('duration')
const publisherURL = synopsis.get('publisherURL')
const percentage = pinned ? this.pinPercentageValue(synopsis) : synopsis.get('percentage')
- const site = synopsis.get('site')
+ const publisherKey = synopsis.get('publisherKey')
+ const siteName = synopsis.get('siteName')
const defaultAutoInclude = this.enabledForSite(synopsis)
- const rowRefName = 'rowPercentage_' + site
+ const rowRefName = 'rowPercentage_' + publisherKey
if (this.refs[rowRefName]) {
this.refs[rowRefName].value = percentage
}
@@ -169,13 +170,13 @@ class LedgerTable extends ImmutableComponent {
{
faviconURL
- ?
+ ?
:
}
- {site}
+ {siteName}
,
- value: site
+ value: publisherKey
},
{
html: pinned
diff --git a/app/sessionStore.js b/app/sessionStore.js
index 1f0afa9a156..04030735f81 100644
--- a/app/sessionStore.js
+++ b/app/sessionStore.js
@@ -926,7 +926,8 @@ module.exports.defaultAppState = () => {
},
cache: {
bookmarkLocation: undefined,
- bookmarkOrder: {}
+ bookmarkOrder: {},
+ ledgerVideos: {}
},
pinnedSites: {},
bookmarks: {},
diff --git a/docs/state.md b/docs/state.md
index 307b747cbd9..c072f747a48 100644
--- a/docs/state.md
+++ b/docs/state.md
@@ -89,6 +89,9 @@ AppStore
order: number,
type: string // siteTags.BOOKMARK or siteTags.BOOKMARK_FOLDER
}]
+ },
+ ledgerVideos: {
+ [mediaKey]: string // publisher key
}
}
clearBrowsingDataDefaults: {
@@ -356,6 +359,7 @@ AppStore
'general.startup-mode': string, // one of: lastTime, homePage, newTabPage
'notification-add-funds-timestamp': number, // timestamp on which we decide if we will show notification Add founds
'notification-reconcile-soon-timestamp': number, // timestamp
+ 'payments.allow-media-publishers': boolean,
'payments.allow-non-verified-publishers': boolean,
'payments.contribution-amount': number, // in USD
'payments.enabled': boolean, // true if the Payments pane is active
diff --git a/js/actions/appActions.js b/js/actions/appActions.js
index 714c44e7c54..402eeaf61fd 100644
--- a/js/actions/appActions.js
+++ b/js/actions/appActions.js
@@ -1812,6 +1812,25 @@ const appActions = {
actionType: appConstants.APP_ON_PUBLISHER_TIMESTAMP,
timestamp
})
+ },
+
+ onLedgerMediaData: function (url, type, tabId) {
+ dispatch({
+ actionType: appConstants.APP_ON_LEDGER_MEDIA_DATA,
+ url,
+ type,
+ tabId
+ })
+ },
+
+ onLedgerMediaPublisher: function (mediaKey, response, duration, revisited) {
+ dispatch({
+ actionType: appConstants.APP_ON_LEDGER_MEDIA_PUBLISHER,
+ mediaKey,
+ response,
+ duration,
+ revisited
+ })
}
}
diff --git a/js/constants/appConfig.js b/js/constants/appConfig.js
index 8f34e336cc6..27c3636f350 100644
--- a/js/constants/appConfig.js
+++ b/js/constants/appConfig.js
@@ -183,6 +183,7 @@ module.exports = {
// Tracking issue for that and to re-enable title mode on Windows is at #9900.
'general.disable-title-mode': process.platform === 'linux' || process.platform === 'win32',
// payments
+ 'payments.allow-media-publishers': true,
'payments.allow-non-verified-publishers': true,
'payments.contribution-amount': 25, // BAT
'payments.enabled': false,
diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js
index ac5c63c3cfd..94f11ee08cd 100644
--- a/js/constants/appConstants.js
+++ b/js/constants/appConstants.js
@@ -179,7 +179,9 @@ const appConstants = {
APP_ON_BTC_TO_BAT_TRANSITIONED: _,
APP_ON_LEDGER_QR_GENERATED: _,
APP_ON_BTC_TO_BAT_BEGIN_TRANSITION: _,
- APP_ON_PUBLISHER_TIMESTAMP: _
+ APP_ON_PUBLISHER_TIMESTAMP: _,
+ APP_ON_LEDGER_MEDIA_DATA: _,
+ APP_ON_LEDGER_MEDIA_PUBLISHER: _
}
module.exports = mapValuesByKeys(appConstants)
diff --git a/js/constants/settings.js b/js/constants/settings.js
index 518511b85ce..d327bcecbac 100644
--- a/js/constants/settings.js
+++ b/js/constants/settings.js
@@ -53,6 +53,7 @@ const settings = {
// Autofill
AUTOFILL_ENABLED: 'privacy.autofill-enabled',
// Payments Tab
+ PAYMENTS_ALLOW_MEDIA_PUBLISHERS: 'payments.allow-media-publishers',
PAYMENTS_ALLOW_NON_VERIFIED: 'payments.allow-non-verified-publishers',
PAYMENTS_CONTRIBUTION_AMOUNT: 'payments.contribution-amount',
PAYMENTS_ENABLED: 'payments.enabled',
diff --git a/package-lock.json b/package-lock.json
index 742735c453d..a01fe8e94a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "brave",
- "version": "0.21.0",
+ "version": "0.22.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1431,13 +1431,13 @@
}
},
"bat-client": {
- "version": "1.0.15",
- "resolved": "https://registry.npmjs.org/bat-client/-/bat-client-1.0.15.tgz",
- "integrity": "sha1-ayPKg6QsaSzl0M/hietJ2J5xnCA=",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/bat-client/-/bat-client-1.2.0.tgz",
+ "integrity": "sha1-OXAH7txSSSBq0ZMg6HEZsK3CRdM=",
"requires": {
"@ambassify/backoff-strategies": "1.0.0",
"bat-balance": "1.0.3",
- "bat-publisher": "1.1.5",
+ "bat-publisher": "1.2.0",
"bitgo": "4.10.0",
"brave-crypto": "0.0.1",
"http-request-signature": "0.0.2",
@@ -1450,27 +1450,6 @@
"uuid": "3.1.0"
},
"dependencies": {
- "bat-publisher": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/bat-publisher/-/bat-publisher-1.1.5.tgz",
- "integrity": "sha1-xadplXDIsIWYf+whwhiQv+w5axw=",
- "requires": {
- "@ambassify/backoff-strategies": "1.0.0",
- "async": "2.5.0",
- "data-expression": "1.0.0",
- "glob": "7.1.2",
- "jimp": "0.2.28",
- "joi": "11.4.0",
- "jsdom": "11.2.0",
- "metascraper": "1.0.7",
- "node-cache": "4.1.1",
- "parse-cache-control": "1.0.1",
- "random-lib": "2.1.0",
- "tldjs": "2.2.0",
- "underscore": "1.8.3",
- "underscore.string": "3.3.4"
- }
- },
"joi": {
"version": "11.4.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-11.4.0.tgz",
@@ -1480,41 +1459,24 @@
"isemail": "3.0.0",
"topo": "2.0.2"
}
- },
- "punycode": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
- },
- "tldjs": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.2.0.tgz",
- "integrity": "sha512-5b5t+HKprfccAFRAsH/fzDR4O+UgO6vStvbaJo10jvMcUavlwxR3Jrn2WmXfjG3k22T7b4pqqfput38nr1RpJQ==",
- "requires": {
- "punycode": "1.4.1"
- }
- },
- "underscore.string": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz",
- "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=",
- "requires": {
- "sprintf-js": "1.1.1",
- "util-deprecate": "1.0.2"
- }
}
}
},
"bat-publisher": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/bat-publisher/-/bat-publisher-1.0.3.tgz",
- "integrity": "sha512-Y4YfTrADz8LltdJw8oaOr6fTgFfkWQLftj2N/2WZ6dwvJWhzrzxcZzEy1fOWNvxdc5/c9/nw5C9ayZ6Xct8rsQ==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/bat-publisher/-/bat-publisher-1.2.0.tgz",
+ "integrity": "sha1-rf4wRIHvspjQci615UdTI9+C5Vc=",
"requires": {
+ "@ambassify/backoff-strategies": "1.0.0",
"async": "2.5.0",
"data-expression": "1.0.0",
"glob": "7.1.2",
+ "jimp": "0.2.28",
"joi": "11.4.0",
"jsdom": "11.2.0",
+ "metascraper": "1.0.7",
+ "node-cache": "4.1.1",
+ "parse-cache-control": "1.0.1",
"random-lib": "2.1.0",
"tldjs": "2.2.0",
"underscore": "1.8.3",
@@ -5240,6 +5202,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",
@@ -5425,20 +5442,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",
@@ -5488,27 +5491,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",
@@ -5521,18 +5503,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",
@@ -5550,61 +5520,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": {
@@ -5630,7 +5545,7 @@
}
},
"electron-squirrel-startup": {
- "version": "github:brave/electron-squirrel-startup#5575c01deca9aac963b6888a5f02bd9f3b7bd5c1",
+ "version": "github:brave/electron-squirrel-startup#88d78fee0079d7bfce7e5238658e54e2e75550ef",
"requires": {
"debug": "2.6.9"
}
diff --git a/package.json b/package.json
index e336769838b..60759a3f47a 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.0.15",
- "bat-publisher": "^1.0.3",
+ "bat-client": "^1.2.0",
+ "bat-publisher": "^1.2.0",
"bignumber.js": "^4.0.4",
"bloodhound-js": "brave/bloodhound",
"clipboard-copy": "^1.0.0",
diff --git a/test/unit/app/browser/api/ledgerTest.js b/test/unit/app/browser/api/ledgerTest.js
index 1cba09bd6a6..0c36a48f930 100644
--- a/test/unit/app/browser/api/ledgerTest.js
+++ b/test/unit/app/browser/api/ledgerTest.js
@@ -11,12 +11,19 @@ const settings = require('../../../../../js/constants/settings')
const appActions = require('../../../../../js/actions/appActions')
const migrationState = require('../../../../../app/common/state/migrationState')
const batPublisher = require('bat-publisher')
+const ledgerMediaProviders = require('../../../../../app/common/constants/ledgerMediaProviders')
describe('ledger api unit tests', function () {
let ledgerApi
let ledgerNotificationsApi
let isBusy = false
let ledgerClient
+ let ledgerPublisher
+
+ // constants
+ const xhr = 'https://www.youtube.com/api/stats/watchtime?docid=kLiLOkzLetE&st=11.338&et=21.339'
+ const videoId = 'youtube_kLiLOkzLetE'
+ const publisherKey = 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg'
// settings
let contributionAmount = 25
@@ -31,6 +38,9 @@ describe('ledger api unit tests', function () {
let onChangeSettingSpy
const defaultAppState = Immutable.fromJS({
+ cache: {
+ ledgerVideos: {}
+ },
ledger: {},
migrations: {}
})
@@ -128,14 +138,21 @@ describe('ledger api unit tests', function () {
mockery.registerMock('bat-client', ledgerClient)
// ledger publisher stubbing
- const lp = {
+ ledgerPublisher = {
ruleset: [],
- getPublisherProps: function (publisher) {
+ getPublisherProps: function () {
return null
},
- Synopsis: batPublisher.Synopsis
+ Synopsis: batPublisher.Synopsis,
+ getMedia: {
+ getPublisherFromMediaProps: () => {}
+ }
}
- mockery.registerMock('bat-publisher', lp)
+ mockery.registerMock('bat-publisher', ledgerPublisher)
+ mockery.registerMock('../../common/state/tabState', {
+ TAB_ID_NONE: -1,
+ getActiveTabId: () => 1
+ })
ledgerNotificationsApi = require('../../../../../app/browser/api/ledgerNotifications')
@@ -156,9 +173,15 @@ describe('ledger api unit tests', function () {
beforeEach(function () {
notificationsInitStub = sinon.stub(ledgerNotificationsApi, 'init')
})
+
afterEach(function () {
notificationsInitStub.restore()
})
+
+ after(function () {
+ ledgerApi.setSynopsis(undefined)
+ })
+
it('calls notifications.init', function () {
ledgerApi.initialize(defaultAppState, true)
assert(notificationsInitStub.calledOnce)
@@ -264,7 +287,7 @@ describe('ledger api unit tests', function () {
})
})
- describe('addVisit', function () {
+ describe('addSiteVisit', function () {
const fakeTabId = 7
let stateWithLocation
let fakeClock
@@ -289,7 +312,7 @@ describe('ledger api unit tests', function () {
fakeClock.tick(6000)
- const result = ledgerApi.addVisit(state, 0, 'https://clifton.io', fakeTabId)
+ const result = ledgerApi.addSiteVisit(state, 0, 'https://clifton.io', fakeTabId)
const visitsByPublisher = ledgerApi.getVisitsByPublisher()
// Assert state WAS modified AND publisher was recorded
@@ -301,7 +324,7 @@ describe('ledger api unit tests', function () {
fakeClock.tick(0)
- const result = ledgerApi.addVisit(state, 0, 'https://clifton.io', fakeTabId)
+ const result = ledgerApi.addSiteVisit(state, 0, 'https://clifton.io', fakeTabId)
const visitsByPublisher = ledgerApi.getVisitsByPublisher()
// Assert state WAS modified but publisher wasn NOT recorded
@@ -312,10 +335,10 @@ describe('ledger api unit tests', function () {
const state = ledgerApi.initialize(stateWithLocation, true)
fakeClock.tick(2000)
- const result1 = ledgerApi.addVisit(state, 0, 'https://clifton.io', fakeTabId)
+ const result1 = ledgerApi.addSiteVisit(state, 0, 'https://clifton.io', fakeTabId)
fakeClock.tick(15000)
- const result2 = ledgerApi.addVisit(result1, 0, 'https://clifton.io', fakeTabId)
+ const result2 = ledgerApi.addSiteVisit(result1, 0, 'https://clifton.io', fakeTabId)
const visitsByPublisher = ledgerApi.getVisitsByPublisher()
@@ -398,6 +421,10 @@ describe('ledger api unit tests', function () {
})
describe('transitionWalletToBat', function () {
+ after(function () {
+ ledgerApi.setSynopsis(undefined)
+ })
+
describe('when client is not busy', function () {
before(function () {
ledgerApi.onBootStateFile(defaultAppState)
@@ -487,6 +514,10 @@ describe('ledger api unit tests', function () {
})
describe('synopsisNormalizer', function () {
+ after(function () {
+ ledgerApi.setSynopsis(undefined)
+ })
+
describe('prune synopsis', function () {
let pruneSynopsisSpy
@@ -511,6 +542,10 @@ describe('ledger api unit tests', function () {
})
describe('pruneSynopsis', function () {
+ after(function () {
+ ledgerApi.setSynopsis(undefined)
+ })
+
it('null case', function () {
const result = ledgerApi.pruneSynopsis(defaultAppState)
assert.deepEqual(result.toJS(), defaultAppState.toJS())
@@ -538,6 +573,9 @@ describe('ledger api unit tests', function () {
})
const expectedResult = {
+ cache: {
+ ledgerVideos: {}
+ },
ledger: {
synopsis: {
publishers: {
@@ -598,4 +636,210 @@ describe('ledger api unit tests', function () {
assert(verifiedPSpy.calledOnce)
})
})
+
+ describe('onMediaRequest', function () {
+ let publisherFromMediaPropsSpy, saveVisitSpy
+
+ beforeEach(function () {
+ publisherFromMediaPropsSpy = sinon.spy(ledgerPublisher.getMedia, 'getPublisherFromMediaProps')
+ saveVisitSpy = sinon.spy(ledgerApi, 'saveVisit')
+ })
+
+ afterEach(function () {
+ publisherFromMediaPropsSpy.restore()
+ saveVisitSpy.restore()
+ ledgerApi.setCurrentMediaKey(null)
+ })
+
+ after(function () {
+ ledgerApi.setSynopsis(undefined)
+ })
+
+ it('null case', function () {
+ const result = ledgerApi.onMediaRequest(defaultAppState)
+ assert.deepEqual(result.toJS(), defaultAppState.toJS())
+ assert(publisherFromMediaPropsSpy.notCalled)
+ assert(saveVisitSpy.notCalled)
+ })
+
+ 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(saveVisitSpy.notCalled)
+ })
+
+ it('get data from cache', function () {
+ const state = defaultAppState.setIn(['cache', 'ledgerVideos', videoId], Immutable.fromJS({
+ publisher: publisherKey
+ }))
+ ledgerApi.onMediaRequest(state, xhr, ledgerMediaProviders.YOUTUBE, 1)
+ assert(publisherFromMediaPropsSpy.notCalled)
+ assert(saveVisitSpy.withArgs(state, publisherKey, 10001, false).calledOnce)
+ })
+
+ it('min duration is set to minimum visit time if below that threshold', function () {
+ const state = defaultAppState.setIn(['cache', 'ledgerVideos', videoId], Immutable.fromJS({
+ publisher: publisherKey
+ }))
+ const xhr2 = 'https://www.youtube.com/api/stats/watchtime?docid=kLiLOkzLetE&st=20.338&et=21.339'
+ ledgerApi.onMediaRequest(state, xhr2, ledgerMediaProviders.YOUTUBE, 1)
+ assert(publisherFromMediaPropsSpy.notCalled)
+ assert(saveVisitSpy.withArgs(state, publisherKey, paymentsMinVisitTime, false).calledOnce)
+ })
+
+ it('revisited if visiting the same media in the same tab', function () {
+ const state = defaultAppState.setIn(['cache', 'ledgerVideos', videoId], Immutable.fromJS({
+ publisher: publisherKey
+ }))
+ // first call, revisit false
+ ledgerApi.onMediaRequest(state, xhr, ledgerMediaProviders.YOUTUBE, 1)
+ assert.equal(ledgerApi.getCurrentMediaKey(), videoId)
+ assert(saveVisitSpy.withArgs(state, publisherKey, 10001, false).calledOnce)
+
+ // second call, revisit true
+ ledgerApi.onMediaRequest(state, xhr, ledgerMediaProviders.YOUTUBE, 1)
+ assert(publisherFromMediaPropsSpy.notCalled)
+ assert(saveVisitSpy.withArgs(state, publisherKey, 10001, true).calledOnce)
+ })
+
+ it('revisited if visiting media in the background tab', function () {
+ const state = defaultAppState.setIn(['cache', 'ledgerVideos', videoId], Immutable.fromJS({
+ publisher: publisherKey
+ }))
+ // first call, revisit false
+ ledgerApi.setCurrentMediaKey('11')
+ ledgerApi.onMediaRequest(state, xhr, ledgerMediaProviders.YOUTUBE, 10)
+ assert.equal(ledgerApi.getCurrentMediaKey(), '11')
+ assert(saveVisitSpy.withArgs(state, publisherKey, 10001, true).calledOnce)
+ })
+ })
+
+ describe('onMediaPublisher', function () {
+ let saveVisitSpy, verifiedPStub
+
+ const expectedState = Immutable.fromJS({
+ cache: {
+ ledgerVideos: {
+ 'youtube_kLiLOkzLetE': {
+ publisher: 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg',
+ faviconName: 'Brave',
+ providerName: 'Youtube',
+ faviconURL: 'data:image/jpeg;base64,...',
+ publisherURL: 'https://brave.com'
+ }
+ }
+ },
+ ledger: {
+ synopsis: {
+ publishers: {
+ 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg': {
+ exclude: false,
+ options: {
+ exclude: true
+ },
+ providerName: 'Youtube',
+ faviconName: 'Brave',
+ faviconURL: 'data:image/jpeg;base64,...',
+ publisherURL: 'https://brave.com'
+ }
+ }
+ }
+ },
+ migrations: {}
+ })
+
+ before(function () {
+ verifiedPStub = sinon.stub(ledgerApi, 'verifiedP', (state, publisherKey, fn) => state)
+ })
+
+ after(function () {
+ verifiedPStub.restore()
+ })
+
+ beforeEach(function () {
+ ledgerApi.setSynopsis({
+ initPublisher: () => {},
+ addPublisher: () => {},
+ publishers: {
+ [publisherKey]: {
+ exclude: false,
+ options: {
+ exclude: true
+ },
+ providerName: 'Youtube'
+ }
+ }
+ })
+ saveVisitSpy = sinon.spy(ledgerApi, 'saveVisit')
+ })
+
+ afterEach(function () {
+ ledgerApi.setSynopsis(undefined)
+ saveVisitSpy.restore()
+ })
+
+ it('null case', function () {
+ const result = ledgerApi.onMediaPublisher(defaultAppState)
+ assert.deepEqual(result.toJS(), defaultAppState.toJS())
+ })
+
+ it('create publisher if new and add cache', function () {
+ const response = Immutable.fromJS({
+ publisher: publisherKey,
+ faviconName: 'Brave',
+ faviconURL: 'data:image/jpeg;base64,...',
+ publisherURL: 'https://brave.com',
+ providerName: 'Youtube'
+ })
+
+ const state = ledgerApi.onMediaPublisher(defaultAppState, videoId, response, 1000, false)
+ assert(saveVisitSpy.calledOnce)
+ assert.deepEqual(state.toJS(), expectedState.toJS())
+ })
+
+ it('update publisher if exists', function () {
+ const newState = Immutable.fromJS({
+ cache: {
+ ledgerVideos: {
+ 'youtube_kLiLOkzLetE': {
+ publisher: 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg',
+ faviconName: 'Brave',
+ providerName: 'Youtube',
+ faviconURL: 'data:image/jpeg;base64,...',
+ publisherURL: 'https://brave.com'
+ }
+ }
+ },
+ ledger: {
+ synopsis: {
+ publishers: {
+ 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg': {
+ options: {
+ exclude: true
+ },
+ faviconName: 'old Brave',
+ faviconURL: 'data:image/jpeg;base64,...',
+ publisherURL: 'https://brave.io',
+ providerName: 'Youtube'
+ }
+ }
+ }
+ },
+ migrations: {}
+ })
+
+ const response = Immutable.fromJS({
+ publisher: publisherKey,
+ faviconName: 'Brave',
+ faviconURL: 'data:image/jpeg;base64,...',
+ publisherURL: 'https://brave.com',
+ providerName: 'Youtube'
+ })
+
+ const state = ledgerApi.onMediaPublisher(newState, videoId, response, 1000, false)
+ assert(saveVisitSpy.calledOnce)
+ assert.deepEqual(state.toJS(), expectedState.toJS())
+ })
+ })
})
diff --git a/test/unit/app/common/cache/ledgerVideoCacheTest.js b/test/unit/app/common/cache/ledgerVideoCacheTest.js
new file mode 100644
index 00000000000..feadd27153c
--- /dev/null
+++ b/test/unit/app/common/cache/ledgerVideoCacheTest.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* global describe, it */
+
+const Immutable = require('immutable')
+const assert = require('assert')
+const ledgerVideoCache = require('../../../../../app/common/cache/ledgerVideoCache')
+
+const baseState = Immutable.fromJS({
+ cache: {
+ ledgerVideos: {}
+ }
+})
+const stateWithData = Immutable.fromJS({
+ cache: {
+ ledgerVideos: {
+ 'youtube_kLiLOkzLetE': {
+ publisher: 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg'
+ }
+ }
+ }
+})
+
+describe('ledgerVideoCache unit test', function () {
+ describe('getDataByVideoId', function () {
+ it('key is not provided', function () {
+ const result = ledgerVideoCache.getDataByVideoId(baseState)
+ assert.deepEqual(result.toJS(), {})
+ })
+
+ it('key does not exist in the cache', function () {
+ const result = ledgerVideoCache.getDataByVideoId(baseState, 'key')
+ assert.deepEqual(result.toJS(), {})
+ })
+
+ it('data is ok', function () {
+ const result = ledgerVideoCache.getDataByVideoId(stateWithData, 'youtube_kLiLOkzLetE')
+ assert.deepEqual(result.toJS(), {
+ publisher: 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg'
+ })
+ })
+ })
+
+ describe('setCacheByVideoId', function () {
+ it('key is not provided', function () {
+ const state = ledgerVideoCache.setCacheByVideoId(baseState)
+ assert.deepEqual(state.toJS(), baseState.toJS())
+ })
+
+ it('data is ok', function () {
+ const state = ledgerVideoCache.setCacheByVideoId(baseState, 'youtube_kLiLOkzLetE', Immutable.fromJS({
+ publisher: 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg'
+ }))
+ const expectedState = state.setIn(['cache', 'ledgerVideos', 'youtube_kLiLOkzLetE'], Immutable.fromJS({
+ publisher: 'youtube#channel:UCFNTTISby1c_H-rm5Ww5rZg'
+ }))
+ assert.deepEqual(state.toJS(), expectedState.toJS())
+ })
+ })
+})
diff --git a/test/unit/app/common/lib/ledgerUtilTest.js b/test/unit/app/common/lib/ledgerUtilTest.js
index e54df4a155a..96939a6fa39 100644
--- a/test/unit/app/common/lib/ledgerUtilTest.js
+++ b/test/unit/app/common/lib/ledgerUtilTest.js
@@ -3,6 +3,7 @@ const mockery = require('mockery')
const assert = require('assert')
const Immutable = require('immutable')
require('../../../braveUnit')
+const ledgerMediaProviders = require('../../../../../app/common/constants/ledgerMediaProviders')
describe('ledgerUtil test', function () {
let ledgerUtil
@@ -211,4 +212,123 @@ describe('ledgerUtil test', function () {
describe('walletStatus', function () {
})
+
+ describe('getMediaId', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getMediaId()
+ assert.equal(result, null)
+ })
+
+ it('unknown type', function () {
+ const result = ledgerUtil.getMediaData({}, 'test')
+ assert.equal(result, null)
+ })
+
+ describe('Youtube', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getMediaId(null, ledgerMediaProviders.YOUTUBE)
+ assert.equal(result, null)
+ })
+
+ it('id is provided', function () {
+ const result = ledgerUtil.getMediaId({docid: 'kLiLOkzLetE'}, ledgerMediaProviders.YOUTUBE)
+ assert.equal(result, 'kLiLOkzLetE')
+ })
+ })
+ })
+
+ describe('getMediaKey', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getMediaKey()
+ assert.equal(result, null)
+ })
+
+ it('type is missing', function () {
+ const result = ledgerUtil.getMediaKey('kLiLOkzLetE')
+ assert.equal(result, null)
+ })
+
+ it('id is null', function () {
+ const result = ledgerUtil.getMediaKey(null, ledgerMediaProviders.YOUTUBE)
+ assert.equal(result, null)
+ })
+
+ it('data is ok', function () {
+ const result = ledgerUtil.getMediaKey('kLiLOkzLetE', ledgerMediaProviders.YOUTUBE)
+ assert.equal(result, 'youtube_kLiLOkzLetE')
+ })
+ })
+
+ describe('getMediaData', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getMediaData()
+ assert.equal(result, null)
+ })
+
+ it('unknown type', function () {
+ const result = ledgerUtil.getMediaData('https://youtube.com', 'test')
+ assert.equal(result, null)
+ })
+
+ describe('Youtube', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getMediaData(null, ledgerMediaProviders.YOUTUBE)
+ assert.equal(result, null)
+ })
+
+ it('query is not present', function () {
+ const result = ledgerUtil.getMediaData('https://youtube.com', ledgerMediaProviders.YOUTUBE)
+ assert.equal(result, null)
+ })
+
+ it('query is present', function () {
+ const result = ledgerUtil.getMediaData('https://www.youtube.com/api/stats/watchtime?docid=kLiLOkzLetE&st=11.338&et=21.339', ledgerMediaProviders.YOUTUBE)
+ assert.deepEqual(result, {
+ docid: 'kLiLOkzLetE',
+ st: '11.338',
+ et: '21.339'
+ })
+ })
+ })
+ })
+
+ describe('getYouTubeDuration', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getYouTubeDuration()
+ assert.equal(result, 0)
+ })
+
+ it('multiple times', function () {
+ const result = ledgerUtil.getYouTubeDuration({
+ st: '11.338,21.339,25.000',
+ et: '21.339,25.000,26.100'
+ })
+ assert.equal(result, 14762)
+ })
+
+ it('single time', function () {
+ const result = ledgerUtil.getYouTubeDuration({
+ st: '11.338',
+ et: '21.339'
+ })
+ assert.equal(result, 10001)
+ })
+ })
+
+ describe('getMediaProvider', function () {
+ it('null case', function () {
+ const result = ledgerUtil.getMediaProvider()
+ assert.equal(result, null)
+ })
+
+ it('unknown provider', function () {
+ const result = ledgerUtil.getMediaProvider('https://www.brave.com')
+ assert.equal(result, null)
+ })
+
+ it('youtube', function () {
+ const result = ledgerUtil.getMediaProvider('https://www.youtube.com/api/stats/watchtime?docid=kLiLOkzLetE&st=11.338&et=21.339')
+ assert.equal(result, ledgerMediaProviders.YOUTUBE)
+ })
+ })
})
diff --git a/test/unit/app/renderer/components/preferences/payments/ledgerTableTest.js b/test/unit/app/renderer/components/preferences/payments/ledgerTableTest.js
index 65bead1131a..167a651f07f 100644
--- a/test/unit/app/renderer/components/preferences/payments/ledgerTableTest.js
+++ b/test/unit/app/renderer/components/preferences/payments/ledgerTableTest.js
@@ -51,7 +51,8 @@ const fivePublishers = {
]),
synopsis: Immutable.List([
Immutable.Map({
- site: 'times.com',
+ publisherKey: 'times.com',
+ siteName: 'times.com',
verified: false,
views: 2,
pinPercentage: 10,
@@ -66,7 +67,8 @@ const fivePublishers = {
faviconURL: ''
}),
Immutable.Map({
- site: 'cnn.com',
+ publisherKey: 'cnn.com',
+ siteName: 'cnn.com',
verified: false,
views: 1,
pinPercentage: 15,
@@ -81,7 +83,8 @@ const fivePublishers = {
faviconURL: ''
}),
Immutable.Map({
- site: 'brianbondy.com',
+ publisherKey: 'brianbondy.com',
+ siteName: 'brianbondy.com',
verified: true,
views: 1,
pinPercentage: 0,
@@ -96,7 +99,8 @@ const fivePublishers = {
faviconURL: ''
}),
Immutable.Map({
- site: 'github.com',
+ publisherKey: 'github.com',
+ siteName: 'github.com',
verified: false,
views: 1,
pinPercentage: 0,
@@ -111,7 +115,8 @@ const fivePublishers = {
faviconURL: ''
}),
Immutable.Map({
- site: 'clifton.io',
+ publisherKey: 'clifton.io',
+ siteName: 'clifton.io',
verified: false,
views: 1,
pinPercentage: 0,
@@ -164,7 +169,8 @@ describe('LedgerTable component', function () {
const synopsis = Immutable.List([
Immutable.Map({
- site: 'times.com',
+ publisherKey: 'times.com',
+ siteName: 'times.com',
verified: false,
views: 2,
pinPercentage: 0,
@@ -218,7 +224,8 @@ describe('LedgerTable component', function () {
const synopsis = Immutable.List([
Immutable.Map({
- site: 'times.com',
+ publisherKey: 'times.com',
+ siteName: 'times.com',
verified: false,
views: 2,
pinPercentage: 10,
@@ -233,7 +240,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'cnn.com',
+ publisherKey: 'cnn.com',
+ siteName: 'cnn.com',
verified: false,
views: 1,
pinPercentage: 15,
@@ -248,7 +256,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'brianbondy.com',
+ publisherKey: 'brianbondy.com',
+ siteName: 'brianbondy.com',
verified: true,
views: 1,
pinPercentage: 0,
@@ -297,7 +306,8 @@ describe('LedgerTable component', function () {
const synopsis = Immutable.List([
Immutable.Map({
- site: 'times.com',
+ publisherKey: 'times.com',
+ siteName: 'times.com',
verified: false,
views: 2,
pinPercentage: 10,
@@ -312,7 +322,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'cnn.com',
+ publisherKey: 'cnn.com',
+ siteName: 'cnn.com',
verified: false,
views: 1,
pinPercentage: 15,
@@ -354,7 +365,8 @@ describe('LedgerTable component', function () {
const synopsis = Immutable.List([
Immutable.Map({
- site: 'times.com',
+ publisherKey: 'times.com',
+ siteName: 'times.com',
verified: false,
views: 2,
pinPercentage: 42,
@@ -424,7 +436,8 @@ describe('LedgerTable component', function () {
const synopsis = Immutable.List([
Immutable.Map({
- site: 'times.com',
+ siteName: 'times.com',
+ publisherKey: 'times.com',
verified: false,
views: 2,
pinPercentage: 10,
@@ -439,7 +452,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'cnn.com',
+ siteName: 'cnn.com',
+ publisherKey: 'cnn.com',
verified: false,
views: 1,
pinPercentage: 15,
@@ -454,7 +468,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'brianbondy.com',
+ siteName: 'brianbondy.com',
+ publisherKey: 'brianbondy.com',
verified: true,
views: 1,
pinPercentage: 0,
@@ -469,7 +484,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'github.com',
+ siteName: 'github.com',
+ publisherKey: 'github.com',
verified: false,
views: 1,
pinPercentage: 0,
@@ -484,7 +500,8 @@ describe('LedgerTable component', function () {
faviconURL: ''
}),
Immutable.Map({
- site: 'clifton.io',
+ siteName: 'clifton.io',
+ publisherKey: 'clifton.io',
verified: false,
views: 1,
pinPercentage: 0,