From c9597ce76e2749f9650942d345b535df2c592266 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 25 Jul 2024 01:58:04 +0300 Subject: [PATCH 1/6] TW-1503: [research] Referral links replacement --- .env.dist | 2 + .../replace-ads/referrals/index.tsx | 126 ++++++++++++++++ .../replace-ads/referrals/takeads/index.ts | 134 ++++++++++++++++++ .../replace-ads/referrals/takeads/types.ts | 70 +++++++++ .../replace-ads/referrals/takeads/user-id.ts | 30 ++++ src/lib/env.ts | 3 +- src/replaceAds.ts | 3 + 7 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 src/content-scripts/replace-ads/referrals/index.tsx create mode 100644 src/content-scripts/replace-ads/referrals/takeads/index.ts create mode 100644 src/content-scripts/replace-ads/referrals/takeads/types.ts create mode 100644 src/content-scripts/replace-ads/referrals/takeads/user-id.ts diff --git a/.env.dist b/.env.dist index 971e8b82d..5eaad72ef 100644 --- a/.env.dist +++ b/.env.dist @@ -30,3 +30,5 @@ PERSONA_ADS_MEDIUM_BANNER_UNIT_ID= PERSONA_ADS_SQUARISH_BANNER_UNIT_ID= TEMPLE_ADS_ORIGIN_PASSPHRASE= + +TAKE_ADS_TOKEN= diff --git a/src/content-scripts/replace-ads/referrals/index.tsx b/src/content-scripts/replace-ads/referrals/index.tsx new file mode 100644 index 000000000..9232b07eb --- /dev/null +++ b/src/content-scripts/replace-ads/referrals/index.tsx @@ -0,0 +1,126 @@ +import React, { FC, useRef } from 'react'; + +import { createRoot } from 'react-dom/client'; +import browser from 'webextension-polyfill'; + +import { EnvVars } from 'lib/env'; + +import { TakeAds } from './takeads'; +import { Daum } from './takeads/types'; + +export function replaceGoogleAds(localAds: Daum[]) { + if (localAds.find(ad => ad.hostname === window.location.hostname)) { + console.warn('HOST IS IN ADS LIST'); + return; + } + + const links = document.querySelectorAll('a'); + const anchorsElements = Array.from(links); + console.log('Found anchors:', anchorsElements); + + // if href of tag is not empty, replace it with our ad + for (const aElem of anchorsElements) { + const ad = localAds.find(ad => compareDomains(ad.websiteUrl, aElem.href)); + + if (ad) processAnchorElement(aElem, ad); + } +} + +const takeads = new TakeAds(EnvVars.TAKE_ADS_TOKEN); + +async function processAnchorElement(aElem: HTMLAnchorElement, adData: Daum) { + const dirtyLink = new URL(aElem.href); + const cleanLink = dirtyLink.origin + dirtyLink.pathname; + + console.log('LINK', cleanLink); + + // const newLinkData = await sendRequestToBackground(cleanLink); + const newLinkData = await takeads.affiliateLinks([cleanLink]); + console.log('NEW LINK DATA', newLinkData); + + const newLink = newLinkData.data[0]!.deeplink; + const showHref = newLinkData.data[0]!.iri; + + console.log('ADS CHANGED', aElem, newLink); + + console.log('OLD_LINK', aElem); + + const parent = createRoot(aElem.parentElement as Element); + console.log('PARENT', parent); + parent.render(); + + await browser.runtime.sendMessage({ + type: 'AD_VIEWED', + payload: { + pricing_model: adData.pricingModel + } + }); +} + +const skeepSubdomain = (hostname: string, subdomain: string) => { + if (hostname.includes(`${subdomain}.`)) { + return hostname.slice(subdomain.length + 1); + } + + return hostname; +}; + +const compareDomains = (url1: string, url2: string) => { + try { + const URL1 = new URL(url1); + const URL2 = new URL(url2); + const hostname1 = skeepSubdomain(URL1.hostname, 'www'); + const hostname2 = skeepSubdomain(URL2.hostname, 'www'); + return hostname1 === hostname2; + } catch (e) { + return false; + } +}; + +interface ReactLinkProps { + html: string; + showHref: string; + href: string; +} + +const ReactLink: FC = ({ html, href, showHref }) => { + const linkRef = useRef(null); + + const handleClick = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + await browser.runtime.sendMessage({ + type: 'AD_CLICKED', + payload: { + host: window.location.host, + url: showHref + } + }); + + window.open(href, '_self'); + }; + + const onRightClick: React.MouseEventHandler = event => { + // linkRef.current!.href = href; + event.currentTarget.href = href; + + browser.runtime.sendMessage({ + type: 'AD_CONTEXT_MENU_CLICKED', + payload: { + host: window.location.host, + url: showHref + } + }); + }; + + return ( + + ); +}; diff --git a/src/content-scripts/replace-ads/referrals/takeads/index.ts b/src/content-scripts/replace-ads/referrals/takeads/index.ts new file mode 100644 index 000000000..f26667325 --- /dev/null +++ b/src/content-scripts/replace-ads/referrals/takeads/index.ts @@ -0,0 +1,134 @@ +import { Advertisement, AffiliateLink, AffiliateResponse, Daum, IpApi, TakeAdsResponse } from './types'; +import { userIdService } from './user-id'; + +enum ProgramStatus { + ACTIVE = 'ACTIVE', + INACTIVE = 'INACTIVE' +} + +export class TakeAds { + monetizeApiRoute = '/v1/product/monetize-api'; + authHeaders: Headers; + url: URL; + + constructor(private publicKey: string, baseUrl: string = 'https://api.takeads.com') { + this.authHeaders = new Headers(); + this.authHeaders.append('Authorization', `Bearer ${this.publicKey}`); + + this.url = new URL(baseUrl); + } + + getPrograms({ + next, + limit, + updatedAtFrom, + updatedAtTo, + programStatus = ProgramStatus.ACTIVE + }: { + next?: string; + limit?: number; + updatedAtFrom?: Date; + updatedAtTo?: Date; + programStatus?: ProgramStatus; + } = {}) { + const route = `${this.monetizeApiRoute}/v2/program`; + + const queryObj = Object.entries({ + next: next, + limit: limit?.toString(), + updatedAtFrom: updatedAtFrom?.toISOString(), + updatedAtTo: updatedAtTo?.toISOString(), + programStatus + }).filter(([, value]) => value !== undefined) as string[][]; + + const query = new URLSearchParams(queryObj); + + const url = new URL(`${route}?${query}`, this.url); + + return this.fetch(url.href, { + headers: this.authHeaders + }); + } + + async getUserCountryCode() { + // Make a request to the ipapi.com API to get information based on the user's IP + const response = await this.fetch('https://ipapi.co/json/'); + + // Extract the country code from the response + const countryCode = response.country; + + return countryCode; + } + + async getLocalPrograms() { + const countryCode = await this.getUserCountryCode(); + + const programs = await this.getPrograms({ + programStatus: ProgramStatus.ACTIVE + }); + + console.log(programs); + + const localPrograms = programs.data.filter(program => program.countryCodes.includes(countryCode)); + + return localPrograms; + } + + async affiliateLinks(websiteUrls: string[]) { + const route = `${this.monetizeApiRoute}/v1/resolve`; + + const body = { + iris: websiteUrls, + subId: await this.getUserUniqueId(), + withImages: true + }; + + console.log({ body }); + + console.log(JSON.stringify(body)); + + const url = new URL(route, this.url); + + const headers = new Headers(); + headers.append('Authorization', `Bearer ${this.publicKey}`); + headers.append('Content-Type', 'application/json'); + + return this.fetch(url.href, { + method: 'PUT', + headers, + body: JSON.stringify(body) + }); + } + + async getLocalAdVariants(data: Daum[]): Promise> { + const websiteUrls = data.map(program => program.websiteUrl); + + const affiliateLinks = await this.affiliateLinks(websiteUrls); + + console.log({ affiliateLinks }); + + const affiliateLinksMap = affiliateLinks.data.reduce((acc, curr) => { + acc[curr.iri] = curr; + return acc; + }, {} as Record); + + return data.map(program => { + const affiliateLink = affiliateLinksMap[program.websiteUrl]; + + return { + name: program.name, + originalLink: program.websiteUrl, + link: affiliateLink?.deeplink, + image: affiliateLink?.imageUrl + }; + }); + } + + async getUserUniqueId() { + return await userIdService.getUserId(); + } + + private fetch(...args: Parameters): Promise { + return fetch(...args).then(res => res.json()) as Promise; + } +} diff --git a/src/content-scripts/replace-ads/referrals/takeads/types.ts b/src/content-scripts/replace-ads/referrals/takeads/types.ts new file mode 100644 index 000000000..9aecbb400 --- /dev/null +++ b/src/content-scripts/replace-ads/referrals/takeads/types.ts @@ -0,0 +1,70 @@ +export interface IpApi { + ip: string; + network: string; + version: string; + city: string; + region: string; + region_code: string; + country: string; + country_name: string; + country_code: string; + country_code_iso3: string; + country_capital: string; + country_tld: string; + continent_code: string; + in_eu: boolean; + postal: string; + latitude: number; + longitude: number; + timezone: string; + utc_offset: string; + country_calling_code: string; + currency: string; + currency_name: string; + languages: string; + country_area: number; + country_population: number; + asn: string; + org: string; +} +export interface TakeAdsResponse { + meta: Meta; + data: Daum[]; +} + +export interface Meta { + limit: number; + next: string; +} + +export interface Daum { + id: string; + name: string; + websiteUrl: string; + imageUrl: string; + countryCodes: string[]; + languageCodes: never[]; + avgCommission?: number; + updatedAt: string; + hostname: string; + programStatus: string; + merchantId: number; + pricingModel: string; +} + +export interface AffiliateResponse { + data: AffiliateLink[]; +} + +export interface AffiliateLink { + iri: string; + deeplink: string; + imageUrl: string; +} + +export interface Advertisement { + name: string; + originalLink: string; + link: string; + image: string; +} diff --git a/src/content-scripts/replace-ads/referrals/takeads/user-id.ts b/src/content-scripts/replace-ads/referrals/takeads/user-id.ts new file mode 100644 index 000000000..84b90e8f6 --- /dev/null +++ b/src/content-scripts/replace-ads/referrals/takeads/user-id.ts @@ -0,0 +1,30 @@ +import browser from 'webextension-polyfill'; + +class UserId { + private generateUserId() { + return 'user_' + Math.random().toString(36).substring(2, 15); + } + + private getUserIdCb(callback: (userId: string) => void) { + browser.storage.local.get(['user_id']).then(result => { + let userId = result.user_id; + + if (!userId) { + userId = this.generateUserId(); + browser.storage.local.set({ user_id: userId }); + } + + callback(userId); + }); + } + + async getUserId(): Promise { + return new Promise(resolve => { + this.getUserIdCb(userId => { + resolve(userId); + }); + }); + } +} + +export const userIdService = new UserId(); diff --git a/src/lib/env.ts b/src/lib/env.ts index a64e630e4..9c7cdfb71 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -37,5 +37,6 @@ export const EnvVars = { PERSONA_ADS_MEDIUM_BANNER_UNIT_ID: process.env.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID!, PERSONA_ADS_SQUARISH_BANNER_UNIT_ID: process.env.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID!, TEMPLE_ADS_ORIGIN_PASSPHRASE: process.env.TEMPLE_ADS_ORIGIN_PASSPHRASE!, - CONVERSION_VERIFICATION_URL: process.env.CONVERSION_VERIFICATION_URL! + CONVERSION_VERIFICATION_URL: process.env.CONVERSION_VERIFICATION_URL!, + TAKE_ADS_TOKEN: process.env.TAKE_ADS_TOKEN! } as const; diff --git a/src/replaceAds.ts b/src/replaceAds.ts index 180e6a24f..3d0775881 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -1,5 +1,6 @@ import browser from 'webextension-polyfill'; +import { replaceGoogleAds } from 'content-scripts/replace-ads/referrals'; import { configureAds } from 'lib/ads/configure-ads'; import { importExtensionAdsModule } from 'lib/ads/import-extension-ads-module'; import { ContentScriptType, ADS_RULES_UPDATE_INTERVAL, WEBSITES_ANALYTICS_ENABLED } from 'lib/constants'; @@ -45,6 +46,8 @@ if (window.frameElement === null) { await configureAds(); // Replace ads with ours setInterval(() => replaceAds(), 1000); + + replaceGoogleAds([]); }) .catch(console.error); } From caec82d4239121973900ce87df8689393ceeb1cb Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 25 Jul 2024 23:42:54 +0300 Subject: [PATCH 2/6] TW-1503: [research] Referral links replacement. TakeAds. v2 endpoint --- .../replace-ads/referrals/index.tsx | 71 +++++++++---------- .../replace-ads/referrals/takeads/index.ts | 10 +-- .../replace-ads/referrals/takeads/types.ts | 2 +- src/replaceAds.ts | 6 +- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/content-scripts/replace-ads/referrals/index.tsx b/src/content-scripts/replace-ads/referrals/index.tsx index 9232b07eb..c6577c5ac 100644 --- a/src/content-scripts/replace-ads/referrals/index.tsx +++ b/src/content-scripts/replace-ads/referrals/index.tsx @@ -1,12 +1,11 @@ import React, { FC, useRef } from 'react'; import { createRoot } from 'react-dom/client'; -import browser from 'webextension-polyfill'; import { EnvVars } from 'lib/env'; import { TakeAds } from './takeads'; -import { Daum } from './takeads/types'; +import { AffiliateLink, Daum } from './takeads/types'; export function replaceGoogleAds(localAds: Daum[]) { if (localAds.find(ad => ad.hostname === window.location.hostname)) { @@ -14,47 +13,56 @@ export function replaceGoogleAds(localAds: Daum[]) { return; } - const links = document.querySelectorAll('a'); - const anchorsElements = Array.from(links); + const anchorsElements = Array.from(document.querySelectorAll('a')); console.log('Found anchors:', anchorsElements); // if href of tag is not empty, replace it with our ad for (const aElem of anchorsElements) { const ad = localAds.find(ad => compareDomains(ad.websiteUrl, aElem.href)); - if (ad) processAnchorElement(aElem, ad); + if (ad) + processAnchorElement(aElem, ad).catch(error => { + console.error('Error while replacing referral link:', error); + }); } } const takeads = new TakeAds(EnvVars.TAKE_ADS_TOKEN); -async function processAnchorElement(aElem: HTMLAnchorElement, adData: Daum) { +async function processAnchorElement(aElem: HTMLAnchorElement, adData?: Daum) { + console.log('Processing referrals for:', adData, aElem); + const dirtyLink = new URL(aElem.href); const cleanLink = dirtyLink.origin + dirtyLink.pathname; - console.log('LINK', cleanLink); + console.log('Link:', dirtyLink, '->', cleanLink); + + const takeadsItems = await takeads.affiliateLinks([cleanLink]); + console.log('TakeAds data:', takeadsItems); - // const newLinkData = await sendRequestToBackground(cleanLink); - const newLinkData = await takeads.affiliateLinks([cleanLink]); - console.log('NEW LINK DATA', newLinkData); + const takeadAd = takeadsItems.data[0] as AffiliateLink | undefined; - const newLink = newLinkData.data[0]!.deeplink; - const showHref = newLinkData.data[0]!.iri; + if (!takeadAd) return console.warn('No affiliate link for', dirtyLink.href, '@', window.location.href); - console.log('ADS CHANGED', aElem, newLink); + const newLink = takeadAd.trackingLink; + const showHref = takeadAd.iri; - console.log('OLD_LINK', aElem); + console.info( + 'Replacing referral:', + dirtyLink.href, + 'to show', + showHref, + 'and link to', + newLink, + '@', + window.location.href, + 'with pricing model', + adData?.pricingModel + ); const parent = createRoot(aElem.parentElement as Element); - console.log('PARENT', parent); - parent.render(); - await browser.runtime.sendMessage({ - type: 'AD_VIEWED', - payload: { - pricing_model: adData.pricingModel - } - }); + parent.render(); } const skeepSubdomain = (hostname: string, subdomain: string) => { @@ -71,6 +79,7 @@ const compareDomains = (url1: string, url2: string) => { const URL2 = new URL(url2); const hostname1 = skeepSubdomain(URL1.hostname, 'www'); const hostname2 = skeepSubdomain(URL2.hostname, 'www'); + return hostname1 === hostname2; } catch (e) { return false; @@ -86,17 +95,11 @@ interface ReactLinkProps { const ReactLink: FC = ({ html, href, showHref }) => { const linkRef = useRef(null); - const handleClick = async (e: React.MouseEvent) => { + const handleClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - await browser.runtime.sendMessage({ - type: 'AD_CLICKED', - payload: { - host: window.location.host, - url: showHref - } - }); + console.log('Takead ad clicked:', showHref, '@', window.location.href); window.open(href, '_self'); }; @@ -105,13 +108,7 @@ const ReactLink: FC = ({ html, href, showHref }) => { // linkRef.current!.href = href; event.currentTarget.href = href; - browser.runtime.sendMessage({ - type: 'AD_CONTEXT_MENU_CLICKED', - payload: { - host: window.location.host, - url: showHref - } - }); + console.log('Takead ad context menu:', showHref, '@', window.location.href); }; return ( diff --git a/src/content-scripts/replace-ads/referrals/takeads/index.ts b/src/content-scripts/replace-ads/referrals/takeads/index.ts index f26667325..601b83071 100644 --- a/src/content-scripts/replace-ads/referrals/takeads/index.ts +++ b/src/content-scripts/replace-ads/referrals/takeads/index.ts @@ -6,6 +6,7 @@ enum ProgramStatus { INACTIVE = 'INACTIVE' } +/** Only used for referrals in this research. Although, presented fully. */ export class TakeAds { monetizeApiRoute = '/v1/product/monetize-api'; authHeaders: Headers; @@ -75,11 +76,11 @@ export class TakeAds { } async affiliateLinks(websiteUrls: string[]) { - const route = `${this.monetizeApiRoute}/v1/resolve`; + const route = `${this.monetizeApiRoute}/v2/resolve`; const body = { iris: websiteUrls, - subId: await this.getUserUniqueId(), + subId: await this.getUserUniqueId(), // TODO: Use mock? withImages: true }; @@ -89,8 +90,7 @@ export class TakeAds { const url = new URL(route, this.url); - const headers = new Headers(); - headers.append('Authorization', `Bearer ${this.publicKey}`); + const headers = new Headers(this.authHeaders); headers.append('Content-Type', 'application/json'); return this.fetch(url.href, { @@ -118,7 +118,7 @@ export class TakeAds { return { name: program.name, originalLink: program.websiteUrl, - link: affiliateLink?.deeplink, + link: affiliateLink?.trackingLink, image: affiliateLink?.imageUrl }; }); diff --git a/src/content-scripts/replace-ads/referrals/takeads/types.ts b/src/content-scripts/replace-ads/referrals/takeads/types.ts index 9aecbb400..98487ad06 100644 --- a/src/content-scripts/replace-ads/referrals/takeads/types.ts +++ b/src/content-scripts/replace-ads/referrals/takeads/types.ts @@ -58,7 +58,7 @@ export interface AffiliateResponse { export interface AffiliateLink { iri: string; - deeplink: string; + trackingLink: string; imageUrl: string; } diff --git a/src/replaceAds.ts b/src/replaceAds.ts index 3d0775881..273c242ca 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -46,8 +46,10 @@ if (window.frameElement === null) { await configureAds(); // Replace ads with ours setInterval(() => replaceAds(), 1000); - - replaceGoogleAds([]); }) .catch(console.error); } + +setTimeout(() => { + replaceGoogleAds([]); +}, 5_000); From a901b1c9d5213d53f6706f13c27e6dde6c3070c9 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 25 Jul 2024 23:59:28 +0300 Subject: [PATCH 3/6] TW-1503: [research] Referral links replacement. Fix CORS --- .../replace-ads/referrals/index.tsx | 17 +++++++++-------- .../replace-ads/referrals/takeads/index.ts | 4 ---- src/lib/constants.ts | 3 ++- src/lib/temple/back/main.ts | 9 ++++++++- src/replaceAds.ts | 6 +++++- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/content-scripts/replace-ads/referrals/index.tsx b/src/content-scripts/replace-ads/referrals/index.tsx index c6577c5ac..168dc8c88 100644 --- a/src/content-scripts/replace-ads/referrals/index.tsx +++ b/src/content-scripts/replace-ads/referrals/index.tsx @@ -1,11 +1,11 @@ import React, { FC, useRef } from 'react'; import { createRoot } from 'react-dom/client'; +import browser from 'webextension-polyfill'; -import { EnvVars } from 'lib/env'; +import { ContentScriptType } from 'lib/constants'; -import { TakeAds } from './takeads'; -import { AffiliateLink, Daum } from './takeads/types'; +import { AffiliateLink, AffiliateResponse, Daum } from './takeads/types'; export function replaceGoogleAds(localAds: Daum[]) { if (localAds.find(ad => ad.hostname === window.location.hostname)) { @@ -16,7 +16,6 @@ export function replaceGoogleAds(localAds: Daum[]) { const anchorsElements = Array.from(document.querySelectorAll('a')); console.log('Found anchors:', anchorsElements); - // if href of tag is not empty, replace it with our ad for (const aElem of anchorsElements) { const ad = localAds.find(ad => compareDomains(ad.websiteUrl, aElem.href)); @@ -27,8 +26,6 @@ export function replaceGoogleAds(localAds: Daum[]) { } } -const takeads = new TakeAds(EnvVars.TAKE_ADS_TOKEN); - async function processAnchorElement(aElem: HTMLAnchorElement, adData?: Daum) { console.log('Processing referrals for:', adData, aElem); @@ -37,7 +34,11 @@ async function processAnchorElement(aElem: HTMLAnchorElement, adData?: Daum) { console.log('Link:', dirtyLink, '->', cleanLink); - const takeadsItems = await takeads.affiliateLinks([cleanLink]); + // Not requesting directly because of CORS. + const takeadsItems: AffiliateResponse = await browser.runtime.sendMessage({ + type: ContentScriptType.FetchReferrals, + linkUrl: cleanLink + }); console.log('TakeAds data:', takeadsItems); const takeadAd = takeadsItems.data[0] as AffiliateLink | undefined; @@ -60,7 +61,7 @@ async function processAnchorElement(aElem: HTMLAnchorElement, adData?: Daum) { adData?.pricingModel ); - const parent = createRoot(aElem.parentElement as Element); + const parent = createRoot(aElem.parentElement!); parent.render(); } diff --git a/src/content-scripts/replace-ads/referrals/takeads/index.ts b/src/content-scripts/replace-ads/referrals/takeads/index.ts index 601b83071..18668d7dc 100644 --- a/src/content-scripts/replace-ads/referrals/takeads/index.ts +++ b/src/content-scripts/replace-ads/referrals/takeads/index.ts @@ -84,10 +84,6 @@ export class TakeAds { withImages: true }; - console.log({ body }); - - console.log(JSON.stringify(body)); - const url = new URL(route, this.url); const headers = new Headers(this.authHeaders); diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 1731d9fd8..e198fb598 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,7 +1,8 @@ export enum ContentScriptType { ExternalLinksActivity = 'ExternalLinksActivity', ExternalAdsActivity = 'ExternalAdsActivity', - UpdateAdsRules = 'UpdateAdsRules' + UpdateAdsRules = 'UpdateAdsRules', + FetchReferrals = 'FetchReferrals' } export const ORIGIN_SEARCH_PARAM_NAME = 'o'; diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index cd28aace5..f3d54a9c6 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -1,9 +1,10 @@ import browser, { Runtime } from 'webextension-polyfill'; +import { TakeAds } from 'content-scripts/replace-ads/referrals/takeads'; import { updateRulesStorage } from 'lib/ads/update-rules-storage'; import { ADS_VIEWER_ADDRESS_STORAGE_KEY, ANALYTICS_USER_ID_STORAGE_KEY, ContentScriptType } from 'lib/constants'; import { E2eMessageType } from 'lib/e2e/types'; -import { BACKGROUND_IS_WORKER } from 'lib/env'; +import { BACKGROUND_IS_WORKER, EnvVars } from 'lib/env'; import { fetchFromStorage } from 'lib/storage'; import { encodeMessage, encryptMessage, getSenderId, MessageType, Response } from 'lib/temple/beacon'; import { clearAsyncStorages } from 'lib/temple/reset'; @@ -303,6 +304,10 @@ browser.runtime.onMessage.addListener(async msg => { rpc: undefined }); break; + + case ContentScriptType.FetchReferrals: { + return await takeads.affiliateLinks([msg.linkUrl]); + } } } catch (e) { console.error(e); @@ -310,3 +315,5 @@ browser.runtime.onMessage.addListener(async msg => { return; }); + +const takeads = new TakeAds(EnvVars.TAKE_ADS_TOKEN); diff --git a/src/replaceAds.ts b/src/replaceAds.ts index 273c242ca..090624d82 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -51,5 +51,9 @@ if (window.frameElement === null) { } setTimeout(() => { - replaceGoogleAds([]); + replaceGoogleAds([ + // { + // // https://cointraffic.io/?utm_source=display&utm_medium=banner&utm_campaign=ad_here_v2&ref=UuqWGTVUWf-ad-here-v2 + // } + ]); }, 5_000); From ffe0d9e2b3a2c96a54e96dd0965f5566c37e36a7 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Jul 2024 02:29:44 +0300 Subject: [PATCH 4/6] TW-1503: [research] Referral links replacement. Working example for Aliexpress.com links --- .../{referrals/index.tsx => referrals.tsx} | 3 +-- .../referrals => lib}/takeads/index.ts | 17 +++++++++++++++-- .../referrals => lib}/takeads/types.ts | 18 +++++++++--------- .../referrals => lib}/takeads/user-id.ts | 0 src/lib/temple/back/main.ts | 2 +- src/replaceAds.ts | 11 +++++++---- 6 files changed, 33 insertions(+), 18 deletions(-) rename src/content-scripts/replace-ads/{referrals/index.tsx => referrals.tsx} (97%) rename src/{content-scripts/replace-ads/referrals => lib}/takeads/index.ts (92%) rename src/{content-scripts/replace-ads/referrals => lib}/takeads/types.ts (83%) rename src/{content-scripts/replace-ads/referrals => lib}/takeads/user-id.ts (100%) diff --git a/src/content-scripts/replace-ads/referrals/index.tsx b/src/content-scripts/replace-ads/referrals.tsx similarity index 97% rename from src/content-scripts/replace-ads/referrals/index.tsx rename to src/content-scripts/replace-ads/referrals.tsx index 168dc8c88..483b35f28 100644 --- a/src/content-scripts/replace-ads/referrals/index.tsx +++ b/src/content-scripts/replace-ads/referrals.tsx @@ -4,8 +4,7 @@ import { createRoot } from 'react-dom/client'; import browser from 'webextension-polyfill'; import { ContentScriptType } from 'lib/constants'; - -import { AffiliateLink, AffiliateResponse, Daum } from './takeads/types'; +import { AffiliateLink, AffiliateResponse, Daum } from 'lib/takeads/types'; export function replaceGoogleAds(localAds: Daum[]) { if (localAds.find(ad => ad.hostname === window.location.hostname)) { diff --git a/src/content-scripts/replace-ads/referrals/takeads/index.ts b/src/lib/takeads/index.ts similarity index 92% rename from src/content-scripts/replace-ads/referrals/takeads/index.ts rename to src/lib/takeads/index.ts index 18668d7dc..386be5498 100644 --- a/src/content-scripts/replace-ads/referrals/takeads/index.ts +++ b/src/lib/takeads/index.ts @@ -6,7 +6,11 @@ enum ProgramStatus { INACTIVE = 'INACTIVE' } -/** Only used for referrals in this research. Although, presented fully. */ +/** + * Only used for referrals in this research. Although, presented fully from source. + * + * See API docs: https://docs.takeads.com/ + */ export class TakeAds { monetizeApiRoute = '/v1/product/monetize-api'; authHeaders: Headers; @@ -19,6 +23,7 @@ export class TakeAds { this.url = new URL(baseUrl); } + /* getPrograms({ next, limit, @@ -50,7 +55,9 @@ export class TakeAds { headers: this.authHeaders }); } + */ + /* async getUserCountryCode() { // Make a request to the ipapi.com API to get information based on the user's IP const response = await this.fetch('https://ipapi.co/json/'); @@ -60,7 +67,9 @@ export class TakeAds { return countryCode; } + */ + /* async getLocalPrograms() { const countryCode = await this.getUserCountryCode(); @@ -74,13 +83,15 @@ export class TakeAds { return localPrograms; } + */ async affiliateLinks(websiteUrls: string[]) { const route = `${this.monetizeApiRoute}/v2/resolve`; const body = { iris: websiteUrls, - subId: await this.getUserUniqueId(), // TODO: Use mock? + // subId: await this.getUserUniqueId(), + subId: 'product_page', // Taken from example in docs withImages: true }; @@ -96,6 +107,7 @@ export class TakeAds { }); } + /* async getLocalAdVariants(data: Daum[]): Promise> { const websiteUrls = data.map(program => program.websiteUrl); @@ -119,6 +131,7 @@ export class TakeAds { }; }); } + */ async getUserUniqueId() { return await userIdService.getUserId(); diff --git a/src/content-scripts/replace-ads/referrals/takeads/types.ts b/src/lib/takeads/types.ts similarity index 83% rename from src/content-scripts/replace-ads/referrals/takeads/types.ts rename to src/lib/takeads/types.ts index 98487ad06..a3775f918 100644 --- a/src/content-scripts/replace-ads/referrals/takeads/types.ts +++ b/src/lib/takeads/types.ts @@ -38,17 +38,17 @@ export interface Meta { } export interface Daum { - id: string; - name: string; + // id: string; + // name: string; websiteUrl: string; - imageUrl: string; - countryCodes: string[]; - languageCodes: never[]; - avgCommission?: number; - updatedAt: string; + // imageUrl: string; + // countryCodes: string[]; + // languageCodes: never[]; + // avgCommission?: number; + // updatedAt: string; hostname: string; - programStatus: string; - merchantId: number; + // programStatus: string; + // merchantId: number; pricingModel: string; } diff --git a/src/content-scripts/replace-ads/referrals/takeads/user-id.ts b/src/lib/takeads/user-id.ts similarity index 100% rename from src/content-scripts/replace-ads/referrals/takeads/user-id.ts rename to src/lib/takeads/user-id.ts diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index f3d54a9c6..129d6926c 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -1,11 +1,11 @@ import browser, { Runtime } from 'webextension-polyfill'; -import { TakeAds } from 'content-scripts/replace-ads/referrals/takeads'; import { updateRulesStorage } from 'lib/ads/update-rules-storage'; import { ADS_VIEWER_ADDRESS_STORAGE_KEY, ANALYTICS_USER_ID_STORAGE_KEY, ContentScriptType } from 'lib/constants'; import { E2eMessageType } from 'lib/e2e/types'; import { BACKGROUND_IS_WORKER, EnvVars } from 'lib/env'; import { fetchFromStorage } from 'lib/storage'; +import { TakeAds } from 'lib/takeads'; import { encodeMessage, encryptMessage, getSenderId, MessageType, Response } from 'lib/temple/beacon'; import { clearAsyncStorages } from 'lib/temple/reset'; import { TempleMessageType, TempleRequest, TempleResponse } from 'lib/temple/types'; diff --git a/src/replaceAds.ts b/src/replaceAds.ts index 090624d82..a90ab1cfa 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -1,12 +1,12 @@ import browser from 'webextension-polyfill'; -import { replaceGoogleAds } from 'content-scripts/replace-ads/referrals'; import { configureAds } from 'lib/ads/configure-ads'; import { importExtensionAdsModule } from 'lib/ads/import-extension-ads-module'; import { ContentScriptType, ADS_RULES_UPDATE_INTERVAL, WEBSITES_ANALYTICS_ENABLED } from 'lib/constants'; import { fetchFromStorage } from 'lib/storage'; import { getRulesFromContentScript, clearRulesCache } from './content-scripts/replace-ads'; +import { replaceGoogleAds } from './content-scripts/replace-ads/referrals'; let processing = false; @@ -52,8 +52,11 @@ if (window.frameElement === null) { setTimeout(() => { replaceGoogleAds([ - // { - // // https://cointraffic.io/?utm_source=display&utm_medium=banner&utm_campaign=ad_here_v2&ref=UuqWGTVUWf-ad-here-v2 - // } + { + // See it working on this page: https://news.ycombinator.com/item?id=38872234 + hostname: 'aliexpress.com', + websiteUrl: 'https://aliexpress.com', + pricingModel: 'some pricing model' + } ]); }, 5_000); From cc4bf1985675b5bb309fc4b8c07643d11d52ce3a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Jul 2024 13:22:07 +0300 Subject: [PATCH 5/6] TW-1503: [research] Referral links replacement. -- user-id.ts --- src/lib/takeads/index.ts | 10 ++-------- src/lib/takeads/user-id.ts | 30 ------------------------------ src/lib/temple/back/main.ts | 5 ++++- 3 files changed, 6 insertions(+), 39 deletions(-) delete mode 100644 src/lib/takeads/user-id.ts diff --git a/src/lib/takeads/index.ts b/src/lib/takeads/index.ts index 386be5498..899d8fd1f 100644 --- a/src/lib/takeads/index.ts +++ b/src/lib/takeads/index.ts @@ -1,5 +1,4 @@ import { Advertisement, AffiliateLink, AffiliateResponse, Daum, IpApi, TakeAdsResponse } from './types'; -import { userIdService } from './user-id'; enum ProgramStatus { ACTIVE = 'ACTIVE', @@ -16,7 +15,7 @@ export class TakeAds { authHeaders: Headers; url: URL; - constructor(private publicKey: string, baseUrl: string = 'https://api.takeads.com') { + constructor(private publicKey: string, private subId: string, baseUrl: string = 'https://api.takeads.com') { this.authHeaders = new Headers(); this.authHeaders.append('Authorization', `Bearer ${this.publicKey}`); @@ -90,8 +89,7 @@ export class TakeAds { const body = { iris: websiteUrls, - // subId: await this.getUserUniqueId(), - subId: 'product_page', // Taken from example in docs + subId: this.subId, withImages: true }; @@ -133,10 +131,6 @@ export class TakeAds { } */ - async getUserUniqueId() { - return await userIdService.getUserId(); - } - private fetch(...args: Parameters): Promise { return fetch(...args).then(res => res.json()) as Promise; } diff --git a/src/lib/takeads/user-id.ts b/src/lib/takeads/user-id.ts deleted file mode 100644 index 84b90e8f6..000000000 --- a/src/lib/takeads/user-id.ts +++ /dev/null @@ -1,30 +0,0 @@ -import browser from 'webextension-polyfill'; - -class UserId { - private generateUserId() { - return 'user_' + Math.random().toString(36).substring(2, 15); - } - - private getUserIdCb(callback: (userId: string) => void) { - browser.storage.local.get(['user_id']).then(result => { - let userId = result.user_id; - - if (!userId) { - userId = this.generateUserId(); - browser.storage.local.set({ user_id: userId }); - } - - callback(userId); - }); - } - - async getUserId(): Promise { - return new Promise(resolve => { - this.getUserIdCb(userId => { - resolve(userId); - }); - }); - } -} - -export const userIdService = new UserId(); diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index 129d6926c..6b56e28c8 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -316,4 +316,7 @@ browser.runtime.onMessage.addListener(async msg => { return; }); -const takeads = new TakeAds(EnvVars.TAKE_ADS_TOKEN); +const takeads = new TakeAds( + EnvVars.TAKE_ADS_TOKEN, + 'product_page' // Taken from example in API Swagger +); From eccb593d83536b6875d0de2c2413ec1c45253286 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Jul 2024 18:56:55 +0300 Subject: [PATCH 6/6] TW-1503: [research] Referral links replacement. Working example. Refactor --- src/content-scripts/replace-ads/referrals.tsx | 8 ++++---- src/replaceAds.ts | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/content-scripts/replace-ads/referrals.tsx b/src/content-scripts/replace-ads/referrals.tsx index 483b35f28..9b57807a8 100644 --- a/src/content-scripts/replace-ads/referrals.tsx +++ b/src/content-scripts/replace-ads/referrals.tsx @@ -6,7 +6,7 @@ import browser from 'webextension-polyfill'; import { ContentScriptType } from 'lib/constants'; import { AffiliateLink, AffiliateResponse, Daum } from 'lib/takeads/types'; -export function replaceGoogleAds(localAds: Daum[]) { +export function replaceReferrals(localAds: Daum[]) { if (localAds.find(ad => ad.hostname === window.location.hostname)) { console.warn('HOST IS IN ADS LIST'); return; @@ -101,12 +101,12 @@ const ReactLink: FC = ({ html, href, showHref }) => { console.log('Takead ad clicked:', showHref, '@', window.location.href); - window.open(href, '_self'); + window.open(href, '_self'); // Make sure if it works in Firefox + // Make sure, users can open links in new tab (Ctl/Cmd + Click) }; const onRightClick: React.MouseEventHandler = event => { - // linkRef.current!.href = href; - event.currentTarget.href = href; + event.currentTarget.href = href; // Needed to preserve copiable original link in context menu console.log('Takead ad context menu:', showHref, '@', window.location.href); }; diff --git a/src/replaceAds.ts b/src/replaceAds.ts index a90ab1cfa..02aa25b26 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -6,7 +6,7 @@ import { ContentScriptType, ADS_RULES_UPDATE_INTERVAL, WEBSITES_ANALYTICS_ENABLE import { fetchFromStorage } from 'lib/storage'; import { getRulesFromContentScript, clearRulesCache } from './content-scripts/replace-ads'; -import { replaceGoogleAds } from './content-scripts/replace-ads/referrals'; +import { replaceReferrals } from './content-scripts/replace-ads/referrals'; let processing = false; @@ -51,12 +51,17 @@ if (window.frameElement === null) { } setTimeout(() => { - replaceGoogleAds([ + replaceReferrals([ { // See it working on this page: https://news.ycombinator.com/item?id=38872234 hostname: 'aliexpress.com', websiteUrl: 'https://aliexpress.com', pricingModel: 'some pricing model' + }, + { + hostname: 'agoda.com', + websiteUrl: 'https://agoda.com', + pricingModel: 'some pricing model' } ]); }, 5_000);