Skip to content

Commit

Permalink
Merge pull request #40 from brave/rewards-twitter-fix-excessive-api-c…
Browse files Browse the repository at this point in the history
…alls

Greaselion makes excessive calls to Twitter API
  • Loading branch information
emerick committed Jan 18, 2021
2 parents b89b4da + 8113e27 commit c21c10f
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 29 deletions.
38 changes: 38 additions & 0 deletions scripts/brave_rewards/publisher/common/lruCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* 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/. */

export class LruCache<T> {

private values: Map<string, T> = new Map<string, T>()
private maxEntries: number

constructor (maxEntries: number) {
this.maxEntries = maxEntries
}

get (key: string): T | null {
if (!key) {
return null
}

const entry = this.values.get(key)
if (!entry) {
return null
}

this.values.delete(key)
this.values.set(key, entry)

return entry
}

put (key: string, value: T) {
if (this.values.size >= this.maxEntries) {
const key = this.values.keys().next().value
this.values.delete(key)
}

this.values.set(key, value)
}
}
15 changes: 12 additions & 3 deletions scripts/brave_rewards/publisher/twitter/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */

import { getPort } from '../common/messaging'
import { getAuthHeaders } from './auth'
import { getAuthHeaders, hasRequiredAuthHeaders } from './auth'

import * as types from './types'

let lastRequestTime = 0

const sendAPIRequest = (name: string, url: string) => {
return new Promise((resolve, reject) => {
if (!name || !url) {
reject(new Error('Invalid parameters'))
return
}

const authHeaders = getAuthHeaders()
if (Object.keys(authHeaders).length === 0) {
if (!hasRequiredAuthHeaders()) {
reject(new Error('Missing auth headers'))
return
}
Expand All @@ -26,6 +27,14 @@ const sendAPIRequest = (name: string, url: string) => {
return
}

if ((lastRequestTime !== 0) && (Date.now() - lastRequestTime < 3000)) {
reject(new Error('Ignoring API request due to network throttle'))
return
}

lastRequestTime = Date.now()

const authHeaders = getAuthHeaders()
port.postMessage({
type: 'OnAPIRequest',
mediaType: types.mediaType,
Expand Down
10 changes: 10 additions & 0 deletions scripts/brave_rewards/publisher/twitter/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const getAuthHeaders = () => {
return authHeaders
}

export const hasRequiredAuthHeaders = () => {
return authHeaders['authorization'] &&
(authHeaders['x-csrf-token'] || authHeaders['x-guest-token'])
}

export const processRequestHeaders = (requestHeaders: any[]) => {
if (!requestHeaders) {
return false
Expand All @@ -58,6 +63,11 @@ export const processRequestHeaders = (requestHeaders: any[]) => {
}
}

// For our purposes (authentication), we want this always to be 'yes'
if (headers['x-twitter-active-user'] !== 'yes') {
headers['x-twitter-active-user'] = 'yes'
}

if (commonUtils.areObjectsEqualShallow(authHeaders, headers)) {
return false
}
Expand Down
70 changes: 44 additions & 26 deletions scripts/brave_rewards/publisher/twitter/publisherInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* 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/. */

import { LruCache } from '../common/lruCache'
import { getPort } from '../common/messaging'

import * as commonUtils from '../common/utils'
Expand All @@ -10,6 +11,41 @@ import * as api from './api'
import * as types from './types'
import * as utils from './utils'

const userCache = new LruCache<any>(128)

const savePublisherVisit = (screenName: string, userDetails: any) => {
if (!screenName || !userDetails) {
return
}

const userId = userDetails.id_str
const publisherKey =
commonUtils.buildPublisherKey(types.mediaType, userId)
const publisherName = screenName
const mediaKey = commonUtils.buildMediaKey(types.mediaType, screenName)
const favIconUrl =
userDetails.profile_image_url_https.replace('_normal', '')

const profileUrl = utils.buildProfileUrl(screenName, userId)

const port = getPort()
if (!port) {
return
}

port.postMessage({
type: 'SavePublisherVisit',
mediaType: types.mediaType,
data: {
url: profileUrl,
publisherKey,
publisherName,
mediaKey,
favIconUrl
}
})
}

const sendForExcludedPage = () => {
const url = `https://${types.mediaDomain}`
const publisherKey = types.mediaDomain
Expand Down Expand Up @@ -41,34 +77,16 @@ const sendForStandardPage = (url: URL) => {
return
}

const userDetails = userCache.get(screenName)
if (userDetails) {
savePublisherVisit(screenName, userDetails)
return
}

api.getUserDetails(screenName)
.then((userDetails: any) => {
const userId = userDetails.id_str
const publisherKey =
commonUtils.buildPublisherKey(types.mediaType, userId)
const publisherName = screenName
const mediaKey = commonUtils.buildMediaKey(types.mediaType, screenName)
const favIconUrl =
userDetails.profile_image_url_https.replace('_normal', '')

const profileUrl = utils.buildProfileUrl(screenName, userId)

const port = getPort()
if (!port) {
return
}

port.postMessage({
type: 'SavePublisherVisit',
mediaType: types.mediaType,
data: {
url: profileUrl,
publisherKey,
publisherName,
mediaKey,
favIconUrl
}
})
userCache.put(screenName, userDetails)
savePublisherVisit(screenName, userDetails)
})
.catch(error => {
console.error(`Failed to fetch user details for ${screenName}: ${error.message}`)
Expand Down

0 comments on commit c21c10f

Please sign in to comment.