diff --git a/package.json b/package.json index 98faa808aa..c6d34a16bc 100644 --- a/package.json +++ b/package.json @@ -231,6 +231,6 @@ "follow-redirects": "^1.15.4" }, "optionalDependencies": { - "@temple-wallet/extension-ads": "8.1.1" + "@temple-wallet/extension-ads": "9.0.0-dev.2" } } diff --git a/src/content-scripts/replace-ads/ads-rules.ts b/src/content-scripts/replace-ads/ads-rules.ts index b95cc53ba5..d9b7977f63 100644 --- a/src/content-scripts/replace-ads/ads-rules.ts +++ b/src/content-scripts/replace-ads/ads-rules.ts @@ -4,15 +4,15 @@ import { importExtensionAdsModule } from 'lib/ads/import-extension-ads-module'; import { ALL_ADS_RULES_STORAGE_KEY, ADS_RULES_UPDATE_INTERVAL } from 'lib/constants'; import { fetchFromStorage } from 'lib/storage'; -export const getRulesFromContentScript = memoizee( - async (location: Location) => { +export const getRulesFromStorage = memoizee( + async (locationOrHref: Location | string) => { try { const { transformRawRules } = await importExtensionAdsModule(); const rulesStored = await fetchFromStorage(ALL_ADS_RULES_STORAGE_KEY); if (!rulesStored) throw new Error('No rules for ads found'); - return transformRawRules(location, rulesStored); + return transformRawRules(locationOrHref, rulesStored); } catch (error) { console.error(error); @@ -22,11 +22,16 @@ export const getRulesFromContentScript = memoizee( providersSelectors: [], providersNegativeSelectors: [], elementsToHideOrRemoveRules: [], + blacklistedHypelabCampaignsSlugs: [], timestamp: 0 }; } }, - { maxAge: ADS_RULES_UPDATE_INTERVAL, normalizer: ([location]) => location.href, promise: true } + { + maxAge: ADS_RULES_UPDATE_INTERVAL, + normalizer: ([locationOrHref]) => (typeof locationOrHref === 'string' ? locationOrHref : locationOrHref.href), + promise: true + } ); -export const clearRulesCache = () => getRulesFromContentScript.clear(); +export const clearRulesCache = () => getRulesFromStorage.clear(); diff --git a/src/content-scripts/replace-ads/ads-stack.iframe.ts b/src/content-scripts/replace-ads/ads-stack.iframe.ts index f7c6eb8cb4..9ab9ef8015 100644 --- a/src/content-scripts/replace-ads/ads-stack.iframe.ts +++ b/src/content-scripts/replace-ads/ads-stack.iframe.ts @@ -1,13 +1,28 @@ import { configureAds } from 'lib/ads/configure-ads'; import { importExtensionAdsModule } from 'lib/ads/import-extension-ads-module'; -import { ADS_META_SEARCH_PARAM_NAME, ORIGIN_SEARCH_PARAM_NAME } from 'lib/constants'; +import { ADS_META_SEARCH_PARAM_NAME, AD_CATEGORIES_PARAM_NAME, ORIGIN_SEARCH_PARAM_NAME } from 'lib/constants'; + +import { getRulesFromStorage } from './ads-rules'; const usp = new URLSearchParams(window.location.search); const id = usp.get('id'); const origin = usp.get(ORIGIN_SEARCH_PARAM_NAME) ?? window.location.href; const adsMetadataIds = usp.getAll(ADS_META_SEARCH_PARAM_NAME).map(value => JSON.parse(value)); +const adCategories = usp.getAll(AD_CATEGORIES_PARAM_NAME); configureAds() .then(() => importExtensionAdsModule()) - .then(({ renderAdsStack }) => renderAdsStack(id ?? '', adsMetadataIds, origin)) + .then(async ({ renderAdsStack }) => ({ renderAdsStack, rules: await getRulesFromStorage(origin) })) + .then(({ renderAdsStack, rules }) => { + const { blacklistedHypelabCampaignsSlugs, permanentAdPlacesRules, adPlacesRules } = rules; + + renderAdsStack( + id ?? '', + adsMetadataIds, + origin, + permanentAdPlacesRules.length > 0 || adPlacesRules.length > 0, + adCategories, + blacklistedHypelabCampaignsSlugs + ); + }) .catch(error => console.error(error)); diff --git a/src/content-scripts/replace-ads/index.ts b/src/content-scripts/replace-ads/index.ts index 6177cd706c..ca6d21ec6a 100644 --- a/src/content-scripts/replace-ads/index.ts +++ b/src/content-scripts/replace-ads/index.ts @@ -1 +1 @@ -export { getRulesFromContentScript, clearRulesCache } from './ads-rules'; +export { getRulesFromStorage as getRulesFromContentScript, clearRulesCache } from './ads-rules'; diff --git a/src/lib/ads/configure-ads.ts b/src/lib/ads/configure-ads.ts index 098572b362..667038c574 100644 --- a/src/lib/ads/configure-ads.ts +++ b/src/lib/ads/configure-ads.ts @@ -1,12 +1,41 @@ import browser from 'webextension-polyfill'; import { buildSwapPageUrlQuery } from 'app/pages/Swap/utils/build-url-query'; -import { ADS_META_SEARCH_PARAM_NAME, ContentScriptType, ORIGIN_SEARCH_PARAM_NAME } from 'lib/constants'; +import { + ADS_META_SEARCH_PARAM_NAME, + AD_CATEGORIES_PARAM_NAME, + ContentScriptType, + ORIGIN_SEARCH_PARAM_NAME +} from 'lib/constants'; import { APP_VERSION, EnvVars, IS_MISES_BROWSER } from 'lib/env'; import { isTruthy } from 'lib/utils'; import { importExtensionAdsModule } from './import-extension-ads-module'; +// Three interfaces below are copied from '@temple-wallet/extension-ads' to avoid importing it to ensure that a core +// build runs without errors. +interface AdSource { + shouldNotUseStrictContainerLimits?: boolean; + providerName: 'Temple' | 'Persona' | 'HypeLab' | 'SmartyAds'; + native?: boolean; + slug: string; + categories?: string[]; +} + +interface AdDimensions { + width: number; + height: number; + minContainerWidth: number; + minContainerHeight: number; + maxContainerWidth: number; + maxContainerHeight: number; +} + +interface AdMetadata { + source: AdSource; + dimensions: AdDimensions; +} + const smallTkeyInpageAdUrl = browser.runtime.getURL(`/misc/ad-banners/small-tkey-inpage-ad.png`); const tkeyInpageAdUrl = browser.runtime.getURL(`/misc/ad-banners/tkey-inpage-ad.png`); @@ -16,13 +45,14 @@ const swapTkeyUrl = `${browser.runtime.getURL('fullpage.html')}#/swap?${buildSwa true )}`; -const getAdsStackIframeURL = (id: string, adsMetadataIds: any[], origin: string) => { +const getAdsStackIframeURL = (id: string, adsMetadataIds: any[], origin: string, adCategories: string[]) => { const url = new URL(browser.runtime.getURL('iframes/ads-stack.html')); url.searchParams.set('id', id); adsMetadataIds.forEach(adMetadataId => url.searchParams.append(ADS_META_SEARCH_PARAM_NAME, JSON.stringify(adMetadataId)) ); url.searchParams.set(ORIGIN_SEARCH_PARAM_NAME, origin); + adCategories.forEach(category => url.searchParams.append(AD_CATEGORIES_PARAM_NAME, category)); return url.toString(); }; @@ -61,7 +91,7 @@ const buildNativeAdsMeta = (containerWidth: number, containerHeight: number) => } ].filter(isTruthy); -const bannerAdsMetaBase = [ +const bannerAdsMetaBase: (AdMetadata | false)[] = [ { source: { providerName: 'SmartyAds' as const, @@ -110,6 +140,52 @@ const bannerAdsMetaBase = [ maxContainerHeight: 300 } }, + { + source: { + providerName: 'HypeLab' as const, + native: false, + slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_WIDE_PLACEMENT_SLUG : EnvVars.HYPELAB_WIDE_PLACEMENT_SLUG + }, + dimensions: { + width: 728, + height: 90, + minContainerWidth: 727, + minContainerHeight: 89, + maxContainerWidth: Infinity, + maxContainerHeight: 300 + } + }, + { + source: { + providerName: 'Persona' as const, + native: false, + slug: IS_MISES_BROWSER ? EnvVars.PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID : EnvVars.PERSONA_ADS_WIDE_BANNER_UNIT_ID + }, + dimensions: { + width: 728, + height: 90, + minContainerWidth: 727, + minContainerHeight: 89, + maxContainerWidth: Infinity, + maxContainerHeight: 300 + } + }, + { + source: { + providerName: 'Persona' as const, + slug: IS_MISES_BROWSER + ? EnvVars.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID + : EnvVars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID + }, + dimensions: { + width: 600, + height: 160, + minContainerWidth: 599, + minContainerHeight: 159, + maxContainerWidth: 800, + maxContainerHeight: 300 + } + }, { source: { providerName: 'SmartyAds' as const, @@ -174,6 +250,37 @@ const bannerAdsMetaBase = [ maxContainerHeight: Infinity } }, + { + source: { + providerName: 'HypeLab' as const, + native: false, + slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_HIGH_PLACEMENT_SLUG : EnvVars.HYPELAB_HIGH_PLACEMENT_SLUG + }, + dimensions: { + width: 300, + height: 250, + minContainerWidth: 299, + minContainerHeight: 249, + maxContainerWidth: 700, + maxContainerHeight: Infinity + } + }, + { + source: { + providerName: 'Persona' as const, + slug: IS_MISES_BROWSER + ? EnvVars.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID + : EnvVars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID + }, + dimensions: { + width: 300, + height: 250, + minContainerWidth: 299, + minContainerHeight: 249, + maxContainerWidth: 700, + maxContainerHeight: Infinity + } + }, { source: { providerName: 'SmartyAds' as const, @@ -207,82 +314,6 @@ const bannerAdsMetaBase = [ maxContainerHeight: 130 } }, - { - source: { - providerName: 'HypeLab' as const, - native: false, - slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_WIDE_PLACEMENT_SLUG : EnvVars.HYPELAB_WIDE_PLACEMENT_SLUG - }, - dimensions: { - width: 728, - height: 90, - minContainerWidth: 727, - minContainerHeight: 89, - maxContainerWidth: Infinity, - maxContainerHeight: 300 - } - }, - { - source: { - providerName: 'Temple' as const, - slug: '' - }, - dimensions: { - width: 728, - height: 90, - minContainerWidth: 727, - minContainerHeight: 89, - maxContainerWidth: Infinity, - maxContainerHeight: 300 - } - }, - { - source: { - providerName: 'Persona' as const, - slug: IS_MISES_BROWSER - ? EnvVars.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID - : EnvVars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID - }, - dimensions: { - width: 600, - height: 160, - minContainerWidth: 599, - minContainerHeight: 159, - maxContainerWidth: 800, - maxContainerHeight: 300 - } - }, - { - source: { - providerName: 'HypeLab' as const, - native: false, - slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_HIGH_PLACEMENT_SLUG : EnvVars.HYPELAB_HIGH_PLACEMENT_SLUG - }, - dimensions: { - width: 300, - height: 250, - minContainerWidth: 299, - minContainerHeight: 249, - maxContainerWidth: 700, - maxContainerHeight: Infinity - } - }, - { - source: { - providerName: 'Persona' as const, - slug: IS_MISES_BROWSER - ? EnvVars.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID - : EnvVars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID - }, - dimensions: { - width: 300, - height: 250, - minContainerWidth: 299, - minContainerHeight: 249, - maxContainerWidth: 700, - maxContainerHeight: 500 - } - }, { source: { providerName: 'HypeLab' as const, @@ -302,7 +333,8 @@ const bannerAdsMetaBase = [ { source: { providerName: 'Persona' as const, - slug: IS_MISES_BROWSER ? EnvVars.PERSONA_ADS_MISES_BANNER_UNIT_ID : EnvVars.PERSONA_ADS_BANNER_UNIT_ID + slug: IS_MISES_BROWSER ? EnvVars.PERSONA_ADS_MISES_BANNER_UNIT_ID : EnvVars.PERSONA_ADS_BANNER_UNIT_ID, + shouldNotUseStrictContainerLimits: true }, dimensions: { width: 321, @@ -330,6 +362,32 @@ const bannerAdsMetaBase = [ } ]; +const bannerAdsMeta = bannerAdsMetaBase.filter(isTruthy); + +const pickNextAdMetadata = ( + allAdsMetadata: AdMetadata[], + currentAdMetadata: AdMetadata | undefined, + validAdsCounter: number, + pageHasPlacesRules: boolean, + adCategories: string[] +) => { + const currentAdMetadataIndex = currentAdMetadata ? allAdsMetadata.indexOf(currentAdMetadata) : -1; + const isPureCryptoAd = adCategories.length === 1 && adCategories[0] === 'crypto'; + + if ( + !currentAdMetadata || + (!pageHasPlacesRules && !isPureCryptoAd && validAdsCounter > 0 && currentAdMetadataIndex > 0) + ) { + return allAdsMetadata[0]; + } + + if (allAdsMetadata.length === 1) { + return undefined; + } + + return allAdsMetadata[(currentAdMetadataIndex + 1) % allAdsMetadata.length]; +}; + export const configureAds = async () => { const { configureAds: originalConfigureAds } = await importExtensionAdsModule(); originalConfigureAds({ @@ -341,9 +399,10 @@ export const configureAds = async () => { personaIframePath: browser.runtime.getURL('iframes/persona-ad.html'), getAdsStackIframeURL, buildNativeAdsMeta, - bannerAdsMeta: bannerAdsMetaBase.filter(isTruthy), + bannerAdsMeta, extVersion: APP_VERSION, templePassphrase: EnvVars.TEMPLE_ADS_ORIGIN_PASSPHRASE, - isMisesBrowser: IS_MISES_BROWSER + isMisesBrowser: IS_MISES_BROWSER, + pickNextAdMetadata }); }; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 7e6e351341..98b1a03c3d 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -7,6 +7,8 @@ export enum ContentScriptType { ReferralClick = 'ReferralClick' } +export const AD_CATEGORIES_PARAM_NAME = 'categories'; + export const ORIGIN_SEARCH_PARAM_NAME = 'o'; export const ADS_META_SEARCH_PARAM_NAME = 'ads-meta'; diff --git a/yarn.lock b/yarn.lock index bbcc9c93a4..228ef3e79d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3607,10 +3607,10 @@ dependencies: nanoid "^3.1.25" -"@temple-wallet/extension-ads@8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@temple-wallet/extension-ads/-/extension-ads-8.1.1.tgz#cfbca4e245096d39ed728133dd569d30253536f7" - integrity sha512-F05VRE7eg9lUnMu/3KvvRg5pOWAAwH7aI+yg61PnfgRA2XkWr7dM+ZdswTbJw+Bq27u3MnjdaSYMB3MIyPhk9g== +"@temple-wallet/extension-ads@9.0.0-dev.2": + version "9.0.0-dev.2" + resolved "https://registry.yarnpkg.com/@temple-wallet/extension-ads/-/extension-ads-9.0.0-dev.2.tgz#9ba67b3cb01368650f591e5893655a3d8bde95af" + integrity sha512-eUxOuIXqIL5OUYQrXGrZa3pgacNxbmrUzLaaGJofZI5wi0VAVJSvjRfPwvE7KwBGoYw7g3sE2/gIraU/qmzYhA== dependencies: axios "^1.7.4" crypto-js "^4.2.0"