diff --git a/.eslintrc b/.eslintrc index aa612ab4d..3be0aafb5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,7 @@ } } }, + "ignorePatterns": "src/**/*.embed.js", "rules": { "prettier/prettier": "error", "prefer-const": "error", diff --git a/package.json b/package.json index 9766341e6..239bac25f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@redux-devtools/remote": "^0.8.0", "@reduxjs/toolkit": "^1.8.5", "@rnw-community/shared": "^0.48.0", + "@slise/embed-react": "^2.2.0", "@svgr/webpack": "6.4.0", "@taquito/ledger-signer": "17.0.0", "@taquito/local-forging": "17.0.0", diff --git a/src/contentScript.ts b/src/contentScript.ts index 65b7e8ac4..fdc5fe588 100644 --- a/src/contentScript.ts +++ b/src/contentScript.ts @@ -1,13 +1,11 @@ import { TemplePageMessage, TemplePageMessageType } from '@temple-wallet/dapp/dist/types'; import browser from 'webextension-polyfill'; +import { ContentScriptType, WEBSITES_ANALYTICS_ENABLED } from 'lib/constants'; import { IntercomClient } from 'lib/intercom/client'; import { serealizeError } from 'lib/intercom/helpers'; import { TempleMessageType, TempleResponse } from 'lib/temple/types'; -import { ContentScriptType } from './lib/constants'; - -const WEBSITES_ANALYTICS_ENABLED = 'WEBSITES_ANALYTICS_ENABLED'; const TRACK_URL_CHANGE_INTERVAL = 5000; enum BeaconMessageTarget { diff --git a/src/lib/slise/get-ads-containers.ts b/src/lib/slise/get-ads-containers.ts new file mode 100644 index 000000000..3f9b0aa1a --- /dev/null +++ b/src/lib/slise/get-ads-containers.ts @@ -0,0 +1,42 @@ +interface AdContainerProps { + element: HTMLElement; + width: number; +} + +const getFinalWidth = (element: Element) => { + const elementStyle = getComputedStyle(element); + const rawWidthFromStyle = elementStyle.width; + const rawWidthFromAttribute = element.getAttribute('width'); + + return Number((rawWidthFromAttribute || rawWidthFromStyle).replace('px', '') || element.clientWidth); +}; + +export const getAdsContainers = () => { + const builtInAdsImages = [...document.querySelectorAll('span + img')].filter(element => { + const { width, height } = element.getBoundingClientRect(); + const label = element.previousElementSibling?.innerHTML ?? ''; + + return (width > 0 || height > 0) && ['Featured', 'Ad'].includes(label); + }); + const coinzillaBanners = [...document.querySelectorAll('.coinzilla')]; + const bitmediaBanners = [...document.querySelectorAll('iframe[src*="media.bmcdn"], iframe[src*="cdn.bmcdn"]')]; + + return builtInAdsImages + .map((image): AdContainerProps | null => { + const element = image.closest('div'); + + return element && { element, width: getFinalWidth(image) }; + }) + .concat( + [...bitmediaBanners, ...coinzillaBanners].map(banner => { + const parentElement = banner.parentElement; + const closestDiv = parentElement?.closest('div') ?? null; + const element = bitmediaBanners.includes(banner) ? closestDiv : parentElement; + const widthDefinedElement = element?.parentElement ?? parentElement; + const bannerFrame = banner.tagName === 'iframe' ? banner : banner.querySelector('iframe'); + + return element && { element, width: getFinalWidth(bannerFrame || widthDefinedElement!) }; + }) + ) + .filter((element): element is AdContainerProps => Boolean(element)); +}; diff --git a/src/lib/slise/get-slot-id.ts b/src/lib/slise/get-slot-id.ts new file mode 100644 index 000000000..6f0e67880 --- /dev/null +++ b/src/lib/slise/get-slot-id.ts @@ -0,0 +1,7 @@ +export const getSlotId = () => { + const hostnameParts = window.location.hostname.split('.').filter(part => part !== 'www'); + const serviceId = hostnameParts[0]; + const pathnameParts = window.location.pathname.split('/').filter(part => part !== '' && !/0x[0-9a-f]+/i.test(part)); + + return [serviceId, ...pathnameParts].join('-'); +}; diff --git a/src/lib/slise/slise-ad.embed.js b/src/lib/slise/slise-ad.embed.js new file mode 100644 index 000000000..5cddf329f --- /dev/null +++ b/src/lib/slise/slise-ad.embed.js @@ -0,0 +1 @@ +(()=>{"use strict";var t={965:(t,e)=>{function n(){return{origin:window.location.origin,path:window.location.pathname}}Object.defineProperty(e,"__esModule",{value:!0}),e.Analytics=void 0;const i=()=>Math.floor(Date.now()/1e3);e.Analytics=class{constructor(t){this._sendEvent=t,this.QUEUE=[{event:"#slise.internal.page_load",time:i()},{event:"#slise.internal.analytics_inited"}],this.initAdvIdFromScriptTag()}initAdvIdFromScriptTag(){if(this.ADVERTISER_ID)return;let t=function(){try{const t="data-slise-adv-id",e="slise-pix3l",n="https://v1.slise.xyz/scripts/pix3l.js";let i=document.getElementById(e);if(i||(i=document.querySelector(`script[src='${n}']`)),!i)return;let o=i.getAttribute(t);if(!o)return;return o}catch(t){return}}();t&&this.setAdvertiserId(t)}setAdvertiserId(t){"string"==typeof t&&(t.startsWith("adv-")&&(t=t.slice(4)),t=parseInt(t)),this.ADVERTISER_ID=t,this.flushQueue()}setClientId(t){this.CLIENT_ID=t,this.flushQueue()}isReady(){return!!this.ADVERTISER_ID&&!!this.CLIENT_ID&&{adv:this.ADVERTISER_ID,client:this.CLIENT_ID}}sendEvent(t){this.QUEUE.push(Object.assign(Object.assign(Object.assign({},t),n()),{time:i()})),this.flushQueue()}flushQueue(){let t=this.isReady();if(!t)return;let e=this.QUEUE;this.QUEUE=[];for(let o of e)this._sendEvent(Object.assign(Object.assign(Object.assign({time:i()},n()),o),t))}}},249:function(t,e,n){var i=this&&this.__awaiter||function(t,e,n,i){return new(n||(n=Promise))((function(o,s){function r(t){try{l(i.next(t))}catch(t){s(t)}}function a(t){try{l(i.throw(t))}catch(t){s(t)}}function l(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(r,a)}l((i=i.apply(t,e||[])).next())}))};Object.defineProperty(e,"__esModule",{value:!0}),e.SliseApi=void 0;const o=n(737);e.SliseApi=class{constructor(){this.apiURL=(0,o.getSliseAPIUrl)()}getAdServeURL(t,e,n,i){"string"!=typeof e&&(e=e.join(",")),n=encodeURIComponent(n),i=encodeURIComponent(i);let o=encodeURIComponent(window.location.pathname);return`${this.apiURL}/serve?pub=${t}&size=${e}&slot=${n}&path=${o}&rnd=${i}`}post(t,e){return i(this,void 0,void 0,(function*(){return fetch(`${this.apiURL}${t}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}))}sendTrackingEvent(t){return i(this,void 0,void 0,(function*(){yield this.post("/analytics",t)}))}sendTrackingData(t){return i(this,void 0,void 0,(function*(){const e=yield this.post("/track",t);return yield e.json()}))}sendThirdPartyEvent(t){return i(this,void 0,void 0,(function*(){yield this.post("/analytics/tpe",t)}))}}},737:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.getSliseAPIUrl=void 0,e.getSliseAPIUrl=()=>"https://v1.slise.xyz"},356:(t,e)=>{function n(...t){return function(e){e.apply(null,t)}}Object.defineProperty(e,"__esModule",{value:!0}),e.EventClass=e.callWithArgsInForEach=void 0,e.callWithArgsInForEach=n,e.EventClass=class{constructor(){this.listeners=[],this.count=0}raise(...t){this.count++,this.listeners.forEach(n(...t))}on(t){return this.listeners.push(t),()=>this.off(t)}once(t){var e=this,n=function(...i){e.off(n),t(...i)};this.listeners.push(n)}off(t){this.listeners=this.listeners.filter((function(e){return e!=t}))}removeAllListeners(){this.listeners=[]}}},64:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.subscribeToPeraWalletChange=e.getPeraInfo=e.getPeraAccounts=void 0;const i=n(315);function o(){try{let t=localStorage.getItem("PeraWallet.Wallet");if(!t)return[];let e=JSON.parse(t),n=null==e?void 0:e.accounts;return n&&Array.isArray(n)&&n.length?e.accounts:[]}catch(t){return[]}}e.getPeraAccounts=o,e.getPeraInfo=function(){let t=o();return{pera_accounts:t,pera_connected:t.length>0}},e.subscribeToPeraWalletChange=function(){return(0,i.pollForChange)(o,1e3,i.compareArrays,1.1).event}},375:function(t,e,n){var i=this&&this.__awaiter||function(t,e,n,i){return new(n||(n=Promise))((function(o,s){function r(t){try{l(i.next(t))}catch(t){s(t)}}function a(t){try{l(i.throw(t))}catch(t){s(t)}}function l(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(r,a)}l((i=i.apply(t,e||[])).next())}))};Object.defineProperty(e,"__esModule",{value:!0}),e.Pix3lData=void 0;const o=n(965),s=n(64),r=n(630),a="slise:client:id";function l(){return i(this,void 0,void 0,(function*(){return localStorage.getItem(a)}))}function c(){var t;return i(this,void 0,void 0,(function*(){let e=window.ethereum;if(void 0===window.ethereum||!e)return Promise.resolve({metamask_installed:!1,phantom_installed:!1});const n=void 0!==window.phantom;n&&(null===(t=window.phantom)||void 0===t?void 0:t.ethereum)&&(e=window.phantom.ethereum);let i=yield e.request({method:"eth_accounts"});return{metamask_installed:!0,metamask_connected:i.length>0,phantom_installed:n,accounts:i}}))}e.Pix3lData=class{constructor(t){this.sliseAPI=t,this.collectData=()=>i(this,void 0,void 0,(function*(){const t=navigator.languages&&navigator.languages[0]||navigator.language,e=(0,r.getSolanaInfo)(),n=(0,s.getPeraInfo)(),i=function(){const t=/(\w+)\/([\d.]+)/g.exec(navigator.userAgent),e=t?t[1]:"Unknown",n=t?t[2]:"Unknown";return{screen_resolution_height:window.screen.height,screen_resolution_width:window.screen.width,window_size_width:window.innerWidth,window_size_height:window.innerHeight,language:navigator.language,platform:navigator.platform,timezone_offset:(new Date).getTimezoneOffset(),cookies_enabled:navigator.cookieEnabled,browser_name:e,browser_version:n,device_pixel_ratio:window.devicePixelRatio,touchSupport:"ontouchstart"in window||navigator.maxTouchPoints>0}}(),[o,a]=yield Promise.all([l(),c()]);return this.sendData(Object.assign(Object.assign({locale:t,lstorage:o,clientBrowserData:i},a),{algorand:n,solana:e}))})),this.sendData=t=>i(this,void 0,void 0,(function*(){const{clientId:e}=yield this.sliseAPI.sendTrackingData(t);e&&(function(t){i(this,void 0,void 0,(function*(){localStorage.setItem(a,t)}))}(e),this.analytics.setClientId(e))})),this.thirdPartyEvent=(t,e)=>i(this,void 0,void 0,(function*(){return this.analytics.initAdvIdFromScriptTag(),this.analytics.sendEvent({event:t,args:e})})),this.analytics=new o.Analytics((e=>t.sendThirdPartyEvent(e)))}init(){return i(this,void 0,void 0,(function*(){l().then((t=>t&&this.analytics.setClientId(t))),this.subscribeToWalletChange();try{(0,s.subscribeToPeraWalletChange)().on(this.collectData),(0,r.subscribeToSolanaWalletChange)().on(this.collectData)}catch(t){}return this.collectData()}))}subscribeToWalletChange(){var t;void 0!==window.ethereum&&(null===(t=window.ethereum)||void 0===t||t.on("accountsChanged",this.collectData))}INIT_GLOBALS(t=!1){var e,n;let i=function(t){let e={event:t};return function(t,...n){let i=e[t];if("function"==typeof i)return i(...n)}}(this.thirdPartyEvent);if(!(null===(e=window.slq)||void 0===e?void 0:e.exe)||t){let t=null===(n=window.slq)||void 0===n?void 0:n.queue;if(window.slq=i,window.slq.exe=i,t)for(let e of t)i(...e)}return this}}},630:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.subscribeToSolanaWalletChange=e.getSolanaInfo=void 0;const i=n(315),o=()=>{var t;const e=null===(t=window.solana)||void 0===t?void 0:t.publicKey;return e?[e.toString()]:[]};e.getSolanaInfo=()=>{const t=o();return{solana_connected:t.length>0,solana_accounts:t}},e.subscribeToSolanaWalletChange=function(){return(0,i.pollForChange)(o,1e3,i.compareArrays,1.1).event}},315:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.compareArrays=e.pollForChange=void 0;const i=n(356);e.pollForChange=function(t,e,n,o=1){let s=new i.EventClass,r=t(),a=e,l=function(){let i=t();(n?n(r,i):r==i)||(a=e,r=i,s.raise(i)),1!=o&&(a*=o,clearInterval(c),c=setInterval(l,a))},c=setInterval(l,a);return{event:s,stop:()=>clearInterval(c)}},e.compareArrays=function(t,e){if(t.length!=e.length)return!1;for(let n=0;n{const t=n(249),e=n(356),i=n(375);!function(){const n=new t.SliseApi;function o(t,e){const n=t.style.width,i=t.style.height,o=document.createElement("iframe");o.src=e,o.width=n,o.height=i,o.style.backgroundColor="transparent",o.style.border="none",o.style.colorScheme="normal",t.innerHTML="",t.appendChild(o)}function s(t,e){t.innerHTML=e}function r(){return Math.random().toString(36).substring(2,15)}function a(){return`${r()}${r()}${r()}${r()}`}new i.Pix3lData(n).init(),window.adsbyslisernd||(window.adsbyslisernd=a()),function(){let t=new e.EventClass,n=window.location.href;return setInterval((function(){window.location.href!=n&&(n=window.location.href,t.raise())}),1e3),t}().on((()=>window.adsbyslisernd=a())),window.adsbyslisesync=window.adsbyslisesync||function(){const t=window.adsbyslise;t&&t.length&&(window.adsbyslise=[],function(t){var e,i,r;for(let a=0;a { + width: number; + height: number; +} + +export const SliseAd = memo(({ width, height, ...restProps }: CunningSliseAdProps) => { + useDidMount(() => require('./slise-ad.embed')); + + return ; +}); diff --git a/src/replaceAds.tsx b/src/replaceAds.tsx new file mode 100644 index 000000000..eb5a89a4e --- /dev/null +++ b/src/replaceAds.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import debounce from 'debounce'; +import { createRoot } from 'react-dom/client'; +import browser from 'webextension-polyfill'; + +import { WEBSITES_ANALYTICS_ENABLED } from 'lib/constants'; +import { getAdsContainers } from 'lib/slise/get-ads-containers'; +import { getSlotId } from 'lib/slise/get-slot-id'; +import { SliseAd } from 'lib/slise/slise-ad'; + +const availableAdsResolutions = [ + { width: 270, height: 90 }, + { width: 728, height: 90 } +]; + +const replaceAds = debounce( + () => { + try { + const adsContainers = getAdsContainers(); + + adsContainers.forEach(({ element: adContainer, width: containerWidth }) => { + let adsResolution = availableAdsResolutions[0]; + for (let i = 1; i < availableAdsResolutions.length; i++) { + const candidate = availableAdsResolutions[i]; + if (candidate.width <= containerWidth && candidate.width > adsResolution.width) { + adsResolution = candidate; + } + } + + const adRoot = createRoot(adContainer); + adRoot.render( + + ); + }); + } catch {} + }, + 100, + true +); + +// Prevents the script from running in an Iframe +if (window.frameElement === null) { + browser.storage.local.get(WEBSITES_ANALYTICS_ENABLED).then(storage => { + if (storage[WEBSITES_ANALYTICS_ENABLED]) { + // Replace ads with those from Slise + window.addEventListener('load', () => replaceAds()); + window.addEventListener('ready', () => replaceAds()); + setInterval(() => replaceAds(), 1000); + replaceAds(); + } + }); +} diff --git a/webpack.config.ts b/webpack.config.ts index fadd7b31c..e71aa8003 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -42,7 +42,7 @@ const HTML_TEMPLATES = PAGES_NAMES.map(name => { return { name, filename, path }; }); -const CONTENT_SCRIPTS = ['contentScript']; +const CONTENT_SCRIPTS = ['contentScript', 'replaceAds']; if (BACKGROUND_IS_WORKER) CONTENT_SCRIPTS.push('keepBackgroundWorkerAlive'); const mainConfig = (() => { @@ -159,7 +159,8 @@ const scriptsConfig = (() => { const config = buildBaseConfig(); config.entry = { - contentScript: Path.join(PATHS.SOURCE, 'contentScript.ts') + contentScript: Path.join(PATHS.SOURCE, 'contentScript.ts'), + replaceAds: Path.join(PATHS.SOURCE, 'replaceAds.tsx') }; if (BACKGROUND_IS_WORKER) diff --git a/webpack/manifest.ts b/webpack/manifest.ts index 20ce4849b..fa985076d 100644 --- a/webpack/manifest.ts +++ b/webpack/manifest.ts @@ -152,6 +152,12 @@ const buildManifestCommons = (vendor: string): Omit