diff --git a/src/drivers/webextension/html/background.html b/src/drivers/webextension/html/background.html index ef82f1b2c..0bb1bdf41 100644 --- a/src/drivers/webextension/html/background.html +++ b/src/drivers/webextension/html/background.html @@ -6,7 +6,6 @@ -
diff --git a/src/drivers/webextension/images/icons/ActBlue.svg b/src/drivers/webextension/images/icons/ActBlue.svg new file mode 100644 index 000000000..f38bd2503 --- /dev/null +++ b/src/drivers/webextension/images/icons/ActBlue.svg @@ -0,0 +1,15 @@ + diff --git a/src/drivers/webextension/images/icons/Alliance Auth.svg b/src/drivers/webextension/images/icons/Alliance Auth.svg new file mode 100644 index 000000000..25dcce48e --- /dev/null +++ b/src/drivers/webextension/images/icons/Alliance Auth.svg @@ -0,0 +1,4 @@ + diff --git a/src/drivers/webextension/images/icons/AlvandCMS.png b/src/drivers/webextension/images/icons/AlvandCMS.png new file mode 100644 index 000000000..e9e82afb0 Binary files /dev/null and b/src/drivers/webextension/images/icons/AlvandCMS.png differ diff --git a/src/drivers/webextension/images/icons/ApexChat.svg b/src/drivers/webextension/images/icons/ApexChat.svg new file mode 100644 index 000000000..d34b3feb3 --- /dev/null +++ b/src/drivers/webextension/images/icons/ApexChat.svg @@ -0,0 +1,3 @@ + diff --git a/src/drivers/webextension/images/icons/Beehiiv.svg b/src/drivers/webextension/images/icons/Beehiiv.svg new file mode 100644 index 000000000..98b4f2a1e --- /dev/null +++ b/src/drivers/webextension/images/icons/Beehiiv.svg @@ -0,0 +1,29 @@ + diff --git a/src/drivers/webextension/images/icons/Better Stack.svg b/src/drivers/webextension/images/icons/Better Stack.svg new file mode 100644 index 000000000..c8f77db7c --- /dev/null +++ b/src/drivers/webextension/images/icons/Better Stack.svg @@ -0,0 +1,5 @@ + diff --git a/src/drivers/webextension/images/icons/Better Uptime.svg b/src/drivers/webextension/images/icons/Better Uptime.svg deleted file mode 100644 index a136b6056..000000000 --- a/src/drivers/webextension/images/icons/Better Uptime.svg +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/src/drivers/webextension/images/icons/Creatium.svg b/src/drivers/webextension/images/icons/Creatium.svg new file mode 100644 index 000000000..fd85e6d12 --- /dev/null +++ b/src/drivers/webextension/images/icons/Creatium.svg @@ -0,0 +1,4 @@ + diff --git a/src/drivers/webextension/images/icons/DataDome.svg b/src/drivers/webextension/images/icons/DataDome.svg new file mode 100644 index 000000000..1f47aa337 --- /dev/null +++ b/src/drivers/webextension/images/icons/DataDome.svg @@ -0,0 +1,14 @@ + diff --git a/src/drivers/webextension/images/icons/Datatrics.svg b/src/drivers/webextension/images/icons/Datatrics.svg new file mode 100644 index 000000000..05be407e4 --- /dev/null +++ b/src/drivers/webextension/images/icons/Datatrics.svg @@ -0,0 +1,4 @@ + diff --git a/src/drivers/webextension/images/icons/Drubbit.svg b/src/drivers/webextension/images/icons/Drubbit.svg new file mode 100644 index 000000000..61eba44ef --- /dev/null +++ b/src/drivers/webextension/images/icons/Drubbit.svg @@ -0,0 +1,10 @@ + diff --git a/src/drivers/webextension/images/icons/Forethought Solve.svg b/src/drivers/webextension/images/icons/Forethought Solve.svg new file mode 100644 index 000000000..08142234f --- /dev/null +++ b/src/drivers/webextension/images/icons/Forethought Solve.svg @@ -0,0 +1,44 @@ + diff --git a/src/drivers/webextension/images/icons/Fortinet.svg b/src/drivers/webextension/images/icons/Fortinet.svg new file mode 100644 index 000000000..fe403bdf7 --- /dev/null +++ b/src/drivers/webextension/images/icons/Fortinet.svg @@ -0,0 +1,3 @@ + diff --git a/src/drivers/webextension/images/icons/GreatPages.svg b/src/drivers/webextension/images/icons/GreatPages.svg new file mode 100644 index 000000000..80a623b5c --- /dev/null +++ b/src/drivers/webextension/images/icons/GreatPages.svg @@ -0,0 +1,5 @@ + diff --git a/src/drivers/webextension/images/icons/Handlebars.png b/src/drivers/webextension/images/icons/Handlebars.png deleted file mode 100644 index 691e0b9bc..000000000 Binary files a/src/drivers/webextension/images/icons/Handlebars.png and /dev/null differ diff --git a/src/drivers/webextension/images/icons/Handlebars.svg b/src/drivers/webextension/images/icons/Handlebars.svg new file mode 100644 index 000000000..7eb09a000 --- /dev/null +++ b/src/drivers/webextension/images/icons/Handlebars.svg @@ -0,0 +1,37 @@ + diff --git a/src/drivers/webextension/images/icons/HighLevel.svg b/src/drivers/webextension/images/icons/HighLevel.svg new file mode 100644 index 000000000..8515996f7 --- /dev/null +++ b/src/drivers/webextension/images/icons/HighLevel.svg @@ -0,0 +1,5 @@ + diff --git a/src/drivers/webextension/images/icons/ID5.svg b/src/drivers/webextension/images/icons/ID5.svg new file mode 100644 index 000000000..a5b59bed4 --- /dev/null +++ b/src/drivers/webextension/images/icons/ID5.svg @@ -0,0 +1,27 @@ + diff --git a/src/drivers/webextension/images/icons/LlamaLink.png b/src/drivers/webextension/images/icons/LlamaLink.png new file mode 100644 index 000000000..23ecaafb0 Binary files /dev/null and b/src/drivers/webextension/images/icons/LlamaLink.png differ diff --git a/src/drivers/webextension/images/icons/Magixite.png b/src/drivers/webextension/images/icons/Magixite.png new file mode 100644 index 000000000..dacd49d28 Binary files /dev/null and b/src/drivers/webextension/images/icons/Magixite.png differ diff --git a/src/drivers/webextension/images/icons/MantisBT.png b/src/drivers/webextension/images/icons/MantisBT.png index 548219842..3eae13dfd 100644 Binary files a/src/drivers/webextension/images/icons/MantisBT.png and b/src/drivers/webextension/images/icons/MantisBT.png differ diff --git a/src/drivers/webextension/images/icons/Mixpanel.svg b/src/drivers/webextension/images/icons/Mixpanel.svg index 1f82faa24..162bcb061 100644 --- a/src/drivers/webextension/images/icons/Mixpanel.svg +++ b/src/drivers/webextension/images/icons/Mixpanel.svg @@ -1,18 +1,3 @@ diff --git a/src/drivers/webextension/images/icons/Moat.svg b/src/drivers/webextension/images/icons/Moat.svg deleted file mode 100644 index 8e4b2ce77..000000000 --- a/src/drivers/webextension/images/icons/Moat.svg +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/src/drivers/webextension/images/icons/Navidium.svg b/src/drivers/webextension/images/icons/Navidium.svg new file mode 100644 index 000000000..526121305 --- /dev/null +++ b/src/drivers/webextension/images/icons/Navidium.svg @@ -0,0 +1,5 @@ + diff --git a/src/drivers/webextension/images/icons/OrderCast.svg b/src/drivers/webextension/images/icons/OrderCast.svg new file mode 100644 index 000000000..01074327c --- /dev/null +++ b/src/drivers/webextension/images/icons/OrderCast.svg @@ -0,0 +1,11 @@ + diff --git a/src/drivers/webextension/images/icons/Progress.svg b/src/drivers/webextension/images/icons/Progress.svg new file mode 100644 index 000000000..1ada67cb1 --- /dev/null +++ b/src/drivers/webextension/images/icons/Progress.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/drivers/webextension/images/icons/Salla.svg b/src/drivers/webextension/images/icons/Salla.svg index 89a54c19b..e362bf3ed 100644 --- a/src/drivers/webextension/images/icons/Salla.svg +++ b/src/drivers/webextension/images/icons/Salla.svg @@ -1,19 +1,4 @@ diff --git a/src/drivers/webextension/images/icons/SellersCommerce.png b/src/drivers/webextension/images/icons/SellersCommerce.png new file mode 100644 index 000000000..ac0ec3463 Binary files /dev/null and b/src/drivers/webextension/images/icons/SellersCommerce.png differ diff --git a/src/drivers/webextension/images/icons/Selless.svg b/src/drivers/webextension/images/icons/Selless.svg new file mode 100644 index 000000000..bdd882006 --- /dev/null +++ b/src/drivers/webextension/images/icons/Selless.svg @@ -0,0 +1,15 @@ + diff --git a/src/drivers/webextension/images/icons/Shoppub.svg b/src/drivers/webextension/images/icons/Shoppub.svg new file mode 100644 index 000000000..448244baf --- /dev/null +++ b/src/drivers/webextension/images/icons/Shoppub.svg @@ -0,0 +1,5 @@ + diff --git a/src/drivers/webextension/images/icons/Skyflow.svg b/src/drivers/webextension/images/icons/Skyflow.svg new file mode 100644 index 000000000..1298bf4d6 --- /dev/null +++ b/src/drivers/webextension/images/icons/Skyflow.svg @@ -0,0 +1,4 @@ + diff --git a/src/drivers/webextension/images/icons/Spatie.png b/src/drivers/webextension/images/icons/Spatie.png deleted file mode 100644 index 7da240289..000000000 Binary files a/src/drivers/webextension/images/icons/Spatie.png and /dev/null differ diff --git a/src/drivers/webextension/images/icons/Spatie.svg b/src/drivers/webextension/images/icons/Spatie.svg new file mode 100644 index 000000000..63f947819 --- /dev/null +++ b/src/drivers/webextension/images/icons/Spatie.svg @@ -0,0 +1 @@ + diff --git a/src/drivers/webextension/images/icons/Tiiny Host.png b/src/drivers/webextension/images/icons/Tiiny Host.png new file mode 100644 index 000000000..3591c72df Binary files /dev/null and b/src/drivers/webextension/images/icons/Tiiny Host.png differ diff --git a/src/drivers/webextension/images/icons/Wask.svg b/src/drivers/webextension/images/icons/Wask.svg new file mode 100644 index 000000000..9e0934037 --- /dev/null +++ b/src/drivers/webextension/images/icons/Wask.svg @@ -0,0 +1,4 @@ + diff --git a/src/drivers/webextension/images/icons/WayForPay.svg b/src/drivers/webextension/images/icons/WayForPay.svg new file mode 100644 index 000000000..fd0a65ccf --- /dev/null +++ b/src/drivers/webextension/images/icons/WayForPay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/drivers/webextension/images/icons/WeWeb.svg b/src/drivers/webextension/images/icons/WeWeb.svg new file mode 100644 index 000000000..db6f5f439 --- /dev/null +++ b/src/drivers/webextension/images/icons/WeWeb.svg @@ -0,0 +1,14 @@ + diff --git a/src/drivers/webextension/images/icons/authorize.net.svg b/src/drivers/webextension/images/icons/authorize.net.svg new file mode 100644 index 000000000..f2824846e --- /dev/null +++ b/src/drivers/webextension/images/icons/authorize.net.svg @@ -0,0 +1,12 @@ + diff --git a/src/drivers/webextension/images/icons/datadome.png b/src/drivers/webextension/images/icons/datadome.png deleted file mode 100644 index 76ec66a29..000000000 Binary files a/src/drivers/webextension/images/icons/datadome.png and /dev/null differ diff --git a/src/drivers/webextension/js/background.js b/src/drivers/webextension/js/background.js index 1743427fc..c762b7cb0 100644 --- a/src/drivers/webextension/js/background.js +++ b/src/drivers/webextension/js/background.js @@ -3,4 +3,3 @@ importScripts(chrome.runtime.getURL('js/wappalyzer.js')) importScripts(chrome.runtime.getURL('js/utils.js')) importScripts(chrome.runtime.getURL('js/driver.js')) -importScripts(chrome.runtime.getURL('js/lib/network.js')) diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js index c5fa8b67f..a22fe5d96 100644 --- a/src/drivers/webextension/js/driver.js +++ b/src/drivers/webextension/js/driver.js @@ -17,7 +17,7 @@ const expiry = 1000 * 60 * 60 * 48 const maxHostnames = 100 const hostnameIgnoreList = - /\b((local|dev(elop(ment)?)?|sandbox|stag(e|ing)?|preprod|production|preview|test(ing)?|[^a-z]demo(shop)?|cache)[.-]|dev\d|localhost|((wappalyzer|google|bing|baidu|microsoft|duckduckgo|facebook|adobe|twitter|reddit|yahoo|wikipedia|amazon|amazonaws|youtube|stackoverflow|github|stackexchange|w3schools|twitch)\.)|(live|office|herokuapp|shopifypreview)\.com|\.local|\.test|\.netlify\.app|web\.archive\.org|zoom\.us|^([0-9.]+|[\d.]+)$|^([a-f0-9:]+:+)+[a-f0-9]+$)/ + /\b((local|dev(elop(ment)?)?|sandbox|stag(e|ing)?|preprod|production|preview|test(ing)?|[^a-z]demo(shop)?|cache)[.-]|dev\d|localhost|((wappalyzer|google|bing|baidu|microsoft|duckduckgo|facebook|adobe|twitter|reddit|yahoo|wikipedia|amazon|amazonaws|youtube|stackoverflow|github|stackexchange|w3schools|twitch)\.)|(live|office|herokuapp|shopifypreview)\.com|\.local|\.test|\.netlify\.app|ngrok|web\.archive\.org|zoom\.us|^([0-9.]+|[\d.]+)$|^([a-f0-9:]+:+)+[a-f0-9]+$)/ const xhrDebounce = [] @@ -79,7 +79,6 @@ const Driver = { {} ), robots: await getOption('robots', {}), - ads: [], } const { version } = chrome.runtime.getManifest() @@ -711,14 +710,6 @@ const Driver = { Driver.log({ hostname, technologies: resolved }) }, - /** - * Callback for onAd listener - * @param {Object} ad - */ - onAd(ad) { - Driver.cache.ads.push(ad) - }, - /** * Update the extension icon * @param {String} url @@ -1009,20 +1000,6 @@ const Driver = { Driver.cache.hostnames[hostname].hits = 0 }) } - - if (Driver.cache.ads.length > 25) { - try { - await Driver.post( - 'https://ad.wappalyzer.com/log/wp/', - Driver.cache.ads - ) - } catch (error) { - // eslint-disable-next-line no-console - console.error(error) - } - - Driver.cache.ads = [] - } } }, } diff --git a/src/drivers/webextension/js/lib/iframe.js b/src/drivers/webextension/js/lib/iframe.js deleted file mode 100644 index c883e4ffb..000000000 --- a/src/drivers/webextension/js/lib/iframe.js +++ /dev/null @@ -1,1546 +0,0 @@ -'use strict' -/* eslint-env browser */ -/* globals chrome */ -;(function (win) { - const exports = {} - - ;(function (exports) { - const utils = { - /** - * Normalize URL - * @param {String} url - */ - normalizeUrl(url) { - return this.hashUrl(url) || null - }, - - /** - * Get referrer. - */ - getReferrer() { - return this.normalizeUrl(document.referrer) - }, - - /** - * Get current page URL. - */ - getPageUrl() { - return this.normalizeUrl(window.location.href) - }, - - /** - * Generated hashed URL. - * @param {String} url - */ - hashUrl(url) { - let a, result - - if (!url || url.indexOf('http') !== 0) { - return null - } - - a = document.createElement('a') - a.href = url - - result = a.protocol + '//' + a.hostname + '/' - - if (a.pathname && a.pathname !== '/') { - result += this.hashCode(a.pathname) - } - - if (a.search) { - result += '?' + this.hashCode(a.search) - } - - if (a.hash) { - result += '#' + this.hashCode(a.hash) - } - - return result - }, - - /** - * Generate random hash. - * @param {String} str - */ - hashCode(str) { - let hash = 0 - let kar - let i - - if (str.length === 0) { - return hash - } - - for (i = 0; i < str.length; i++) { - kar = str.charCodeAt(i) - hash = (hash << 5) - hash + kar - hash = hash & hash - } - - return hash + Math.pow(2, 32) - }, - - /** - * Apply array function to non-array. - * @param {Object} a - */ - realArray(a) { - return Array.prototype.slice.apply(a) - }, - - /** - * Listener callback for onDocLoaded. - * @param {Object} doc - * @param {Function} callback - */ - onDocLoaded(doc, callback) { - if (doc.readyState === 'loading') { - doc.addEventListener('DOMContentLoaded', callback) - } else { - callback() - } - }, - - SCRIPT_IN_WINDOW_TOP: window === window.top, - - /** - * Check for href Window object. - * @param {Object} win - */ - isFriendlyWindow(win) { - let href - try { - href = win.location.href - } catch (e) { - return false - } - return true - }, - - /** - * Get default view from element. - * @param {Object} el - */ - elementWindow(el) { - return el.ownerDocument.defaultView - }, - - /** - * Get viewport size. - * @param {Object} win - */ - viewport(win) { - return { width: win.innerWidth, height: win.innerHeight } - }, - - /** - * Parse query string parameters. - * @param {String} qs - */ - parseQS(qs) { - if (qs.indexOf('http') === 0) { - qs = qs.split('?')[1] - } - let i, kvs, key, val - const dict = {} - qs = qs.split('&') - for (i = 0; i < qs.length; i++) { - kvs = qs[i].split('=') - key = kvs[0] - val = kvs.slice(1).join('=') - try { - dict[key] = window.decodeURIComponent(val) - } catch (e) { - continue - } - } - return dict - }, - - /** - * Send PostMessage response. - * @param {Object} message - * @param {String} event - * @param {String} responseMessage - */ - sendToBackground(message, event, responseMessage) { - try { - chrome.runtime.sendMessage(message, (message) => { - if (message && typeof message.tracking_enabled !== 'undefined') { - if (message.tracking_enabled) { - utilCallback() - } else { - utilElseCallback() - } - } - }) - } catch (error) { - // Continue - } - }, - - /** - * Check if anonymous tracking is enabled. - * @param {Function} callback - * @param {Function} elseCallback - * @todo validate if utilCallback or utilElseCallback are being used. - */ - askIfTrackingEnabled(callback, elseCallback) { - utilCallback = callback - utilElseCallback = elseCallback - - this.sendToBackground( - 'is_tracking_enabled', - '', - 'tracking_enabled_response' - ) - }, - } - - utils.SCRIPT_IN_FRIENDLY_IFRAME = - !utils.SCRIPT_IN_WINDOW_TOP && utils.isFriendlyWindow(window.parent) - utils.SCRIPT_IN_HOSTILE_IFRAME = - !utils.SCRIPT_IN_WINDOW_TOP && !utils.SCRIPT_IN_FRIENDLY_IFRAME - - /** - * Generate new Logging object. - */ - function LogGenerator() { - this.msgNum = 0 - this.pageMeta = { - url: utils.getPageUrl(), - isHP: window.location.pathname === '/', - referrer: utils.getReferrer(), - rand: Math.floor(Math.random() * 10e12), - startTime: new Date().getTime(), - } - } - - LogGenerator.prototype = { - /** - * Log data. - * @param {String} event - * @param {Array} opt_assets - * @param {Array} opt_pageTags - */ - log(event, opt_assets, opt_pageTags) { - let opt_video_assets - if (event === 'video' || event === 'invalid-video') { - opt_video_assets = opt_assets || [] - opt_assets = [] - } else { - opt_video_assets = [] - opt_assets = opt_assets || [] - } - const result = { - doc: this.pageMeta, - event, - video_assets: opt_video_assets, - assets: opt_assets, - version: '3', - mrev: '15a9f21-d', - msgNum: this.msgNum, - timestamp: new Date().getTime(), - pageVis: document.visibilityState, - pageFoc: document.hasFocus(), - pageTags: opt_pageTags || [], - } - this.msgNum++ - return result - }, - } - - utils.LogGenerator = LogGenerator - - let utilCallback, utilElseCallback - - exports.utils = utils - })(exports) - ;(function (exports) { - const SizeMatcher = { - VALID_AD_SIZES: [ - [300, 50], - [320, 50], - [160, 600], - [300, 250], - [300, 600], - [300, 1050], - [336, 280], - [336, 850], - [468, 60], - [728, 90], - [728, 250], - [728, 270], - [970, 66], - [970, 90], - [970, 125], - [970, 250], - [970, 400], - [970, 415], - [1280, 100], - ], - - PX_SIZE_TOL: 10, - - /** - * Get ad size. - * @param {Int} width - * @param {Int} height - */ - getMatchedAdSize(width, height) { - if (!this.set) { - this.set = this._makeSizeSet() - } - - return this.set[Math.round(width) + 'x' + Math.round(height)] - }, - - /** - * Check element size. - * @param {HTMLElement} el - */ - elementIsAdShaped(el) { - return !!this.getMatchedAdSizeForElement(el) - }, - - /** - * Get ad size. - * @param {HTMLElement} el - * @todo better description - */ - getMatchedAdSizeForElement(el) { - const rect = el.getBoundingClientRect() - return this.getMatchedAdSize(rect.width, rect.height) - }, - - /** - * Generate ad sizes. - */ - _makeSizeSet() { - const set = {} - let i - let xfuz - let yfuz - let size - let width - let height - - for (i = 0; i < this.VALID_AD_SIZES.length; i++) { - for (xfuz = -this.PX_SIZE_TOL; xfuz <= this.PX_SIZE_TOL; xfuz++) { - for (yfuz = -this.PX_SIZE_TOL; yfuz <= this.PX_SIZE_TOL; yfuz++) { - size = this.VALID_AD_SIZES[i] - width = size[0] + xfuz - height = size[1] + yfuz - set[width + 'x' + height] = size - } - } - } - return set - }, - } - - const Throttler = { - MAX_SEARCHES_PER_WINDOW: 10, - MAX_SEARCHES_PER_ELEMENT: 2, - - /** - * Count number of elements. - * @param {HTMLElement} el - */ - countSearch: (el) => { - if (typeof el.searches !== 'number') { - el.searches = 0 - } - - el.searches += 1 - }, - - /** - * - * @param {*} el - * @param {*} max - * - * @todo add description - */ - throttle(el, max) { - if (typeof el.searches === 'number' && el.searches >= max) { - return true - } - return false - }, - - /** - * - * @param {*} el - * - * @todo add description - */ - throttleElement(el) { - return this.throttle(el, this.MAX_SEARCHES_PER_ELEMENT) - }, - - /** - * - * @param {*} win - * - * @todo add description - */ - throttleWin(win) { - return this.throttle(win, this.MAX_SEARCHES_PER_WINDOW) - }, - - /** - * - * @param {*} el - * - * @todo add description - */ - getCount(el) { - return el.searches || 0 - }, - } - - /** - * Initialize window and document elements. - * @param {*} win - */ - function TopSearcher(win) { - this.win = win - this.doc = win.document - } - - /** - * Add search function. - */ - TopSearcher.prototype.search = function () { - const candidates = exports.utils.realArray( - this.doc.querySelectorAll('img, object, embed') - ) - let html5Ad - let ads = [] - - ads = ads.concat( - candidates.filter(function (el) { - if (!el.mpAdFound && !Throttler.throttleElement(el)) { - Throttler.countSearch(el) - if ( - (el.tagName !== 'IMG' || isStandardImage(el)) && - SizeMatcher.elementIsAdShaped(el) - ) { - el.mpAdFound = true - return true - } - } - return false - }) - ) - - html5Ad = this._mainGetHTMLAd() - if (html5Ad) { - html5Ad.html5 = true - html5Ad.mpAdFound = true - ads.push(html5Ad) - } - - return ads - } - - /** - * @todo add description - */ - TopSearcher.prototype._mainGetHTMLAd = function () { - const styles = this.doc.querySelectorAll( - 'div > style, div > link[rel="stylesheet"]' - ) - let i - let div - for (i = 0; i < styles.length; i++) { - div = styles[i].parentNode - if ( - !div.mpAdFound && - SizeMatcher.elementIsAdShaped(div) && - this._jumpedOut(div) - ) { - return div - } - } - } - - /** - * @todo add description - */ - TopSearcher.prototype._jumpedOut = function (el) { - let siblings, ifrs - siblings = exports.utils.realArray(el.parentNode.children) - ifrs = siblings.filter(function (el) { - return ( - el.tagName === 'IFRAME' && - el.offsetWidth === 0 && - el.offsetHeight === 0 - ) - }) - return ifrs.length > 0 - } - - /** - * - * @param {*} win - * - * @todo add description - */ - function IframeSearcher(win) { - this.MIN_AD_AREA = 14000 - this.MIN_WINDOW_PX = 10 - - this.win = win - this.doc = win.document - this.body = win.document.body - this.winClickTag = win.clickTag - this.adSizeMeta = this._getAdSizeMeta() - this.numElementsInBody = - (this.body && this.body.querySelectorAll('*').length) || 0 - - this.shouldSearchWindow = false - if ( - !this.win.mpAdFound && - this.body && - !Throttler.throttleWin(this.win) - ) { - this.winWidth = this.win.innerWidth - this.winHeight = this.win.innerHeight - if ( - this._meetsMinAdSize(this.winWidth, this.winHeight) && - !this._containsLargeIframes() - ) { - this.shouldSearchWindow = true - } - } - } - - /** - * @todo add description - */ - IframeSearcher.prototype.search = function () { - let ad - - if (this.shouldSearchWindow) { - ad = this._search() - if (ad) { - ad.mpAdFound = true - win.mpAdFound = true - return ad - } - Throttler.countSearch(this.win) - } - - return null - } - - /** - * @todo add description - */ - IframeSearcher.prototype._search = function () { - const _this = this - let stdCandidates - let html5Candidates - let stdEl - let html5El - - stdCandidates = this.body.querySelectorAll('img, object, embed') - - stdEl = getFirst(stdCandidates, function (el) { - if ( - !el.mpAdFound && - !Throttler.throttleElement(el) && - (el.tagName !== 'IMG' || isStandardImage(el)) && - _this._elementIsAtLeastAsBigAsWindow(el) - ) { - return true - } - Throttler.countSearch(el) - return false - }) - - if (stdEl) { - return stdEl - } - - if (this._isHTML5Iframe()) { - html5Candidates = this.doc.querySelectorAll( - 'body, canvas, button, video, svg, div' - ) - html5El = getFirst(html5Candidates, function (el) { - if (_this._elementIsAtLeastAsBigAsWindow(el)) { - return true - } - Throttler.countSearch(el) - return false - }) - } - - if (html5El) { - html5El.html5 = true - html5El.winClickTag = this.winClickTag - html5El.adSizeMeta = this.adSizeMeta - return html5El - } - - return null - } - - /** - * @todo add description - */ - IframeSearcher.prototype._isHTML5Iframe = function () { - if (this.winClickTag || this.adSizeMeta) { - return true - } - - if ( - this.doc.querySelectorAll('canvas', 'button', 'video', 'svg').length > 0 - ) { - return true - } - - if ( - this.numElementsInBody >= 5 && - Throttler.getCount(this.win) > 0 && - this.doc.querySelectorAll('div').length > 0 - ) { - return true - } - - return false - } - - /** - * @todo add description - */ - IframeSearcher.prototype._elementIsAtLeastAsBigAsWindow = function (el) { - const rect = el.getBoundingClientRect() - const tol = 0.95 - - return ( - rect.width >= tol * this.winWidth && rect.height >= tol * this.winHeight - ) - } - - /** - * @todo add description - */ - IframeSearcher.prototype._meetsMinAdSize = function (width, height) { - return width * height >= this.MIN_AD_AREA - } - - /** - * @todo add description - */ - IframeSearcher.prototype._containsLargeIframes = function () { - const iframes = this.doc.querySelectorAll('iframe') - let rect - let i - for (i = 0; i < iframes.length; i++) { - rect = iframes[i].getBoundingClientRect() - if ( - rect.width > this.MIN_WINDOW_PX || - rect.height > this.MIN_WINDOW_PX - ) { - return true - } - } - return false - } - - /** - * @todo add description - */ - IframeSearcher.prototype._getAdSizeMeta = function () { - const adSizeMeta = this.doc.querySelectorAll('meta[name="ad.size"]') - if (adSizeMeta.length > 0) { - return adSizeMeta[0].content - } else { - return null - } - } - - /** - * - * @param {*} arr - * @param {*} testFn - * - * @todo add description - */ - function getFirst(arr, testFn) { - let i, el - for (i = 0; i < arr.length; i++) { - el = arr[i] - if (testFn(el)) { - return el - } - } - return null - } - - /** - * Check for image attributes. - * @param {HTMLElement} img - */ - function isStandardImage(img) { - return ( - img.src && - (img.parentNode.tagName === 'A' || img.getAttribute('onclick')) - ) - } - - /** - * Extract iFrames from page. - * @param {Object} win - */ - function getFriendlyIframes(win) { - let iframes = win.document.querySelectorAll('iframe') - iframes = exports.utils.realArray(iframes) - const friendlyIframes = iframes.filter(function (ifr) { - return exports.utils.isFriendlyWindow(ifr.contentWindow) - }) - return friendlyIframes - } - - /** - * - * @param {*} win - */ - function findAds(win) { - let i - let iframes - let searcher - let ad - let ads = [] - - if (win === win.top) { - searcher = new TopSearcher(win) - ads = ads.concat(searcher.search()) - } else { - searcher = new IframeSearcher(win) - ad = searcher.search() - if (ad) { - ads.push(ad) - } - } - - iframes = getFriendlyIframes(win) - for (i = 0; i < iframes.length; i++) { - ads = ads.concat(findAds(iframes[i].contentWindow)) - } - - return ads - } - - exports.adfinder = { - getMatchedAdSize: SizeMatcher.getMatchedAdSize.bind(SizeMatcher), - findAds, - } - })(exports) - ;(function (exports) { - const parser = { - TAGS_WITH_SRC_ATTR: { - IMG: true, - SCRIPT: true, - IFRAME: true, - EMBED: true, - }, - - MAX_ATTR_LEN: 100, - - /** - * - * @param {*} el - * @param {*} params - * - * @todo add description - */ - getUrl(el, params) { - let url - - if (this.TAGS_WITH_SRC_ATTR.hasOwnProperty(el.tagName)) { - url = el.src - } else if (el.tagName === 'OBJECT') { - url = el.data || (params && params.movie) || null - } else if (el.tagName === 'A') { - url = el.href - } - - if (url && url.indexOf('http') === 0) { - return url - } else { - return null - } - }, - - /** - * - * @param {*} el - * - * @todo add description - */ - getParams(el) { - if (el.tagName !== 'OBJECT') { - return null - } - - let i, child - const params = {} - const children = el.children - for (i = 0; i < children.length; i++) { - child = children[i] - if (child.tagName === 'PARAM' && child.name) { - params[child.name.toLowerCase()] = child.value - } - } - return params - }, - - /** - * Get element position. - * @param {HTMLElement} el - */ - getPosition(el) { - const rect = el.getBoundingClientRect() - const win = exports.utils.elementWindow(el) - - return { - width: Math.round(rect.width), - height: Math.round(rect.height), - left: Math.round(rect.left + win.pageXOffset), - top: Math.round(rect.top + win.pageYOffset), - } - }, - - /** - * - * @param {*} el - * @param {*} params - * @param {*} url - * - * @todo add description - */ - getFlashvars(el, params, url) { - let flashvars - const urlQS = url && url.split('?')[1] - - if (el.tagName === 'EMBED') { - flashvars = el.getAttribute('flashvars') || urlQS - } else if (el.tagName === 'OBJECT') { - flashvars = params.flashvars || el.getAttribute('flashvars') || urlQS - } - - return (flashvars && exports.utils.parseQS(flashvars)) || null - }, - - /** - * - * @param {*} el - * @param {*} flashvars - * - * @todo add description - */ - findClickThru(el, flashvars) { - let key - if (el.tagName === 'IMG' && el.parentElement.tagName === 'A') { - return el.parentElement.href - } else if (flashvars) { - for (key in flashvars) { - if (flashvars.hasOwnProperty(key)) { - if (key.toLowerCase().indexOf('clicktag') === 0) { - return flashvars[key] - } - } - } - } - return null - }, - - /** - * Get element attribute. - * @param {HTMLElement} el - * @param {String} name - */ - getAttr(el, name) { - const val = el.getAttribute(name) - - if (val && val.slice && val.toString) { - return val.slice(0, this.MAX_ATTR_LEN).toString() - } else { - return null - } - }, - - /** - * - * @param {*} obj - * @param {*} name - * @param {*} val - * - * @todo add description - */ - putPropIfExists(obj, name, val) { - if (val) { - obj[name] = val - } - }, - - /** - * - * @param {*} obj - * @param {*} el - * @param {*} name - * - * @todo add description - */ - putAttrIfExists(obj, el, name) { - const val = this.getAttr(el, name) - this.putPropIfExists(obj, name, val) - }, - - /** - * Convert Element to JSON - * @param {HTMLElement} el - * @param {Boolean} opt_findClickThru - */ - elementToJSON(el, opt_findClickThru) { - const pos = this.getPosition(el) - const params = this.getParams(el) - const url = this.getUrl(el, params) - const flashvars = this.getFlashvars(el, params, url) - const clickThru = opt_findClickThru && this.findClickThru(el, flashvars) - const json = { - tagName: el.tagName, - width: pos.width, - height: pos.height, - left: pos.left, - top: pos.top, - children: [], - } - - if (params) { - delete params.flashvars - } - - this.putAttrIfExists(json, el, 'id') - this.putAttrIfExists(json, el, 'class') - this.putAttrIfExists(json, el, 'name') - - this.putPropIfExists(json, 'flashvars', flashvars) - this.putPropIfExists(json, 'url', url) - this.putPropIfExists(json, 'params', params) - this.putPropIfExists(json, 'clickThru', clickThru) - - return json - }, - } - - exports.parser = { elementToJSON: parser.elementToJSON.bind(parser) } - })(exports) - - // Anonymous invocation. - ;(function (exports) { - /** - * Setter for ad data. - * @param {*} adData - */ - const ContextManager = function (adData) { - this.adData = adData - } - - ContextManager.prototype = { - CONTAINER_SIZE_TOL: 0.4, - ASPECT_RATIO_FOR_LEADERBOARDS: 2, - - /** - * Check if iframe is valid. - * @param {HTMLElement} el - * @param {HTMLElement} opt_curWin - */ - isValidContainer(el, opt_curWin) { - const cWidth = el.clientWidth - const cHeight = el.clientHeight - - const adWidth = this.adData.width - const adHeight = this.adData.height - - const winWidth = opt_curWin && opt_curWin.innerWidth - const winHeight = opt_curWin && opt_curWin.innerHeight - const similarWin = - opt_curWin && - this.withinTol(adWidth, winWidth) && - this.withinTol(adHeight, winHeight) - - const similarSizeX = this.withinTol(adWidth, cWidth) - const similarSizeY = this.withinTol(adHeight, cHeight) - const adAspect = adWidth / adHeight - - return ( - similarWin || - el.tagName === 'A' || - (adAspect >= this.ASPECT_RATIO_FOR_LEADERBOARDS && similarSizeY) || - (similarSizeX && similarSizeY) - ) - }, - - /** - * Check tolerance. - * @param {Int} adlen - * @param {Int} conlen - */ - withinTol(adlen, conlen) { - const pct = (conlen - adlen) / adlen - - return pct <= this.CONTAINER_SIZE_TOL - }, - - /** - * Serialize elements. - * @param {*} el - * @todo define parameter type. - */ - serializeElements(el) { - if (!el) { - return - } - let i - let ifrWin - const adId = this.adData.adId - let elIsAd = false - - if (adId && el[adId] && el[adId].isAd === true) { - elIsAd = true - } - - const json = exports.parser.elementToJSON(el, elIsAd) - let childJSON - - if (elIsAd) { - json.adId = adId - this.adData.element = {} - - const keys = Object.keys(json) - for (i = 0; i < keys.length; i++) { - const key = keys[i] - if (key !== 'children' && key !== 'contents') { - this.adData.element[key] = json[key] - } - } - } - - const children = exports.utils - .realArray(el.children) - .filter(function (el) { - const param = el.tagName === 'PARAM' - const inlineScript = - el.tagName === 'SCRIPT' && !(el.src && el.src.includes('http')) - const noScript = el.tagName === 'NOSCRIPT' - return !(param || inlineScript || noScript) - }) - - for (i = 0; i < children.length; i++) { - childJSON = this.serializeElements(children[i]) - if (childJSON) { - json.children.push(childJSON) - } - } - - if (el.tagName === 'IFRAME') { - ifrWin = el.contentWindow - - if (adId && el[adId] && el[adId].needsWindow) { - json.contents = this.adData.serializedIframeContents - el[adId].needsWindow = false - delete this.adData.serializedIframeContents - } else if (exports.utils.isFriendlyWindow(ifrWin)) { - childJSON = this.serializeElements(ifrWin.document.documentElement) - if (childJSON) { - json.contents = childJSON - } - } - } - - if ( - json.children.length > 0 || - json.adId || - json.tagName === 'IFRAME' || - json.url - ) { - return json - } else { - return null - } - }, - - /** - * Get element containers. - * @param {*} containerEl - */ - captureHTML(containerEl) { - this.adData.context = this.serializeElements(containerEl) - }, - - /** - * Get number of Nodes. - * @param {HTMLElement} el - */ - nodeCount(el) { - return el.getElementsByTagName('*').length + 1 - }, - - /** - * - * @param {*} curWin - * @param {*} referenceElement - * - * @todo add description - */ - highestContainer(curWin, referenceElement) { - let curContainer = referenceElement - const docEl = curWin.document.documentElement - let parentContainer - - if (curWin !== curWin.top && this.isValidContainer(docEl, curWin)) { - return docEl - } - - while (true) { - parentContainer = curContainer.parentElement - if (parentContainer && this.isValidContainer(parentContainer)) { - curContainer = parentContainer - } else { - return curContainer - } - } - }, - } - - const tagfinder = { - /** - * - * @param {*} adData - * @param {*} opt_el - * @param {*} opt_winPos - * - * @todo add description - */ - setPositions(adData, opt_el, opt_winPos) { - const el = opt_el || adData.context - const winPos = opt_winPos || { left: 0, top: 0 } - let ifrPos - - el.left += winPos.left - el.top += winPos.top - - if (el.children) { - el.children.forEach(function (child) { - this.setPositions(adData, child, winPos) - }, this) - } - - if (el.contents) { - ifrPos = { left: el.left, top: el.top } - this.setPositions(adData, el.contents, ifrPos) - } - - if (el.adId === adData.adId) { - adData.element.left = el.left - adData.element.top = el.top - } - }, - - /** - * - * @param {*} adData - * @param {*} referenceElement - * - * @todo add description - */ - appendTags: (adData, referenceElement) => { - const mgr = new ContextManager(adData) - let curWin = exports.utils.elementWindow(referenceElement) - let highestContainer - - while (true) { - highestContainer = mgr.highestContainer(curWin, referenceElement) - mgr.captureHTML(highestContainer) - if (curWin === curWin.top) { - break - } else { - curWin.mpAdFound = true - - mgr.adData.serializedIframeContents = mgr.adData.context - - if (exports.utils.isFriendlyWindow(curWin.parent)) { - referenceElement = curWin.frameElement - referenceElement[mgr.adData.adId] = { needsWindow: true } - curWin = curWin.parent - } else { - break - } - } - } - return { - referenceElement, - highestContainer, - } - }, - } - - exports.tagfinder = tagfinder - })(exports) - ;(function (exports) { - let _onAdFound - const _logGen = new exports.utils.LogGenerator() - let _pageTags - const INIT_MS_BW_SEARCHES = 2000 - const PAGE_TAG_RE = new RegExp('gpt|oascentral') - const POST_MSG_ID = '1554456894-8541-12665-19466-15909' - const AD_SERVER_RE = new RegExp('^(google_ads_iframe|oas_frame|atwAdFrame)') - - /** - * Get script tags from document. - * @param {Object} doc - */ - function getPageTags(doc) { - let scripts = doc.getElementsByTagName('script') - const pageTags = [] - scripts = exports.utils.realArray(scripts) - scripts.forEach(function (script) { - if (PAGE_TAG_RE.exec(script.src)) { - pageTags.push({ tagName: 'SCRIPT', url: script.src }) - } - }) - return pageTags - } - - /** - * Send message to parent iFrames. - * @param {String} adData - */ - function messageAllParentFrames(adData) { - adData.postMessageId = POST_MSG_ID - - adData = JSON.stringify(adData) - - let win = window - while (win !== win.top) { - win = win.parent - win.postMessage(adData, '*') - } - } - - /** - * - * @param {String} adData - * @param {HTMLElement} referenceElement - * - * @todo update description - */ - function appendTagsAndSendToParent(adData, referenceElement) { - const results = exports.tagfinder.appendTags(adData, referenceElement) - if (exports.utils.SCRIPT_IN_HOSTILE_IFRAME) { - messageAllParentFrames(adData) - } else if (exports.utils.SCRIPT_IN_WINDOW_TOP) { - exports.tagfinder.setPositions(adData) - - adData.matchedSize = exports.adfinder.getMatchedAdSize( - adData.width, - adData.height - ) - if (!adData.matchedSize) { - if (AD_SERVER_RE.exec(results.referenceElement.id)) { - adData.matchedSize = [adData.width, adData.height] - adData.oddSize = true - } else { - return - } - } - delete adData.width - delete adData.height - adData.curPageUrl = exports.utils.getPageUrl() - _pageTags = _pageTags || getPageTags(document) - const log = _logGen.log('ad', [adData], _pageTags) - - if (_onAdFound) { - _onAdFound(log, results.referenceElement) - } - } - } - - /** - * SetTimeout wrapper for extracting ads. - */ - function extractAdsWrapper() { - if ( - exports.utils.SCRIPT_IN_WINDOW_TOP || - document.readyState === 'complete' - ) { - extractAds() - } - setTimeout(function () { - extractAdsWrapper() - }, INIT_MS_BW_SEARCHES) - } - - /** - * Main function for extracting ads after loaded. - */ - function extractAds() { - const ads = exports.adfinder.findAds(window) - ads.forEach(function (ad) { - const startTime = new Date().getTime() - const adId = startTime + '-' + Math.floor(Math.random() * 10e12) - - const adData = { - width: Math.round(ad.offsetWidth), - height: Math.round(ad.offsetHeight), - startTime, - adId, - html5: ad.html5 || false, - } - - if (ad.html5) { - adData.adSizeMeta = ad.adSizeMeta || null - adData.winClickTag = ad.winClickTag || null - } - - ad[adId] = { isAd: true } - - appendTagsAndSendToParent(adData, ad) - }) - } - - /** - * Check if window is child of parent. - * @param {Object} myWin - * @param {Object} otherWin - */ - function isChildWin(myWin, otherWin) { - let parentWin = otherWin.parent - while (parentWin !== otherWin) { - if (parentWin === myWin) { - return true - } - otherWin = parentWin - parentWin = parentWin.parent - } - return false - } - - /** - * - * @param {*} win - * @param {*} winToMatch - * - * @todo update description - */ - function iframeFromWindow(win, winToMatch) { - let i - let ifr - let ifrWin - const iframes = win.document.querySelectorAll('iframe') - - for (i = 0; i < iframes.length; i++) { - ifr = iframes[i] - if (ifr.contentWindow === winToMatch) { - return ifr - } - } - - for (i = 0; i < iframes.length; i++) { - ifrWin = iframes[i].contentWindow - if (exports.utils.isFriendlyWindow(ifrWin)) { - ifr = iframeFromWindow(ifrWin, winToMatch) - if (ifr) { - return ifr - } - } - } - } - - /** - * - * @param {*} event - * - * @todo update description - */ - function onPostMessage(event) { - let adData - const ifrWin = event.source - const myWin = window.document.defaultView - let ifrTag - - if (typeof event.data === 'string' && event.data.includes(POST_MSG_ID)) { - try { - adData = JSON.parse(event.data) - } catch (e) { - return - } - } else return - - if (adData.postMessageId === POST_MSG_ID) { - delete adData.postMessageId - - event.stopImmediatePropagation() - - if (isChildWin(myWin, ifrWin)) { - if (exports.utils.isFriendlyWindow(ifrWin)) { - ifrTag = ifrWin.frameElement - } else { - ifrTag = iframeFromWindow(myWin, ifrWin) - } - - if (ifrTag) { - ifrTag[adData.adId] = { needsWindow: true } - appendTagsAndSendToParent(adData, ifrTag) - } - } - } - } - - /** - * - * @param {*} msg - * @param {*} sender - * @param {*} callback - * - * @todo update description - */ - function onVideoMessage(msg, sender, callback) { - let log - if (msg.event === 'new-video-ad') { - msg.assets.forEach(function (asset) {}) - log = _logGen.log('video', msg.assets) - } else { - log = _logGen.log('invalid-video', msg.assets) - } - - msg.assets.forEach(function (a) { - delete a.isVideo - }) - log.displayAdFound = msg.displayAdFound - log.requests = msg.requests - log.data = msg.event_data - - log.doc.finalPageUrl = log.doc.url - log.doc.url = exports.utils.normalizeUrl(msg.origUrl) - - _onAdFound(log) - } - - /** - * Add background listener. - * @param {String} event - * @param {Function} callback - */ - function addBackgroundListener(event, callback) { - chrome.runtime.onMessage.addListener(function (msg) { - if (msg.event === event) { - callback(msg) - } - }) - } - - exports.coordinator = { - /** - * @todo update description - */ - addPostMessageListener() { - if (!exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { - window.addEventListener('message', onPostMessage, false) - } - }, - - /** - * - * @param {*} sendFcn - * @param {*} origUrl - * - * @todo update description - */ - blockedRobotsMsgGen(sendFcn, origUrl) { - if (!origUrl.includes('google.com/_/chrome/newtab')) { - const onBlockedRobotsMessage = function () { - let log - log = _logGen.log('invalid-robotstxt', []) - log.doc.finalPageUrl = log.doc.url - log.doc.url = exports.utils.normalizeUrl(origUrl) - - sendFcn(log) - } - return onBlockedRobotsMessage - } else { - return function () {} - } - }, - - /** - * - * @param {*} onAdFound - */ - init(onAdFound) { - if (exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { - return false - } - - _onAdFound = onAdFound - if (exports.utils.SCRIPT_IN_WINDOW_TOP) { - const log = _logGen.log('page') - onAdFound(log) - - window.addEventListener('beforeunload', function (event) { - const log = _logGen.log('unload') - log.timing = window.performance.timing - onAdFound(log) - }) - - addBackgroundListener('new-video-ad', onVideoMessage) - addBackgroundListener('new-invalid-video-ad', onVideoMessage) - } - - exports.utils.onDocLoaded(document, extractAdsWrapper) - }, - } - })(exports) - - if (exports.utils.SCRIPT_IN_WINDOW_TOP) { - window.adparser = { - init: exports.coordinator.init, - addPostMessageListener: exports.coordinator.addPostMessageListener, - askIfTrackingEnabled: exports.utils.askIfTrackingEnabled, - blockedRobotsMsgGen: exports.coordinator.blockedRobotsMsgGen, - inWindowTop: exports.utils.SCRIPT_IN_WINDOW_TOP, - sendToBackground: exports.utils.sendToBackground, - } - } else { - exports.coordinator.addPostMessageListener() - exports.utils.askIfTrackingEnabled( - function () { - exports.coordinator.init(function () {}) - }, - function () {} - ) - } -})(window) -;(function (adparser, pageUrl) { - function onAdFound(log) { - adparser.sendToBackground( - { source: 'iframe.js', func: 'onAd', args: [log] }, - 'onAd', - '', - function () {} - ) - } - - if (adparser && adparser.inWindowTop) { - adparser.addPostMessageListener() - adparser.askIfTrackingEnabled(function () { - adparser.init(onAdFound) - }, adparser.blockedRobotsMsgGen(onAdFound, pageUrl)) - } -})(window.adparser, window.location.href) diff --git a/src/drivers/webextension/js/lib/network.js b/src/drivers/webextension/js/lib/network.js deleted file mode 100644 index 258d821ef..000000000 --- a/src/drivers/webextension/js/lib/network.js +++ /dev/null @@ -1,864 +0,0 @@ -'use strict' -;(function () { - const MIN_FF_MAJOR_VERSION = 51 - - let areListenersRegistered = false - const secBefore = 2000 - const secAfter = 5000 - const secBetweenDupAssets = 10e3 - const minVidSize = 500e3 - const maxVidSize = 25e6 - const maxContentRange = 25e6 - const videoExtensions = [ - 'af', - '3gp', - 'asf', - 'avchd', - 'avi', - 'cam', - 'dsh', - 'flv', - 'm1v', - 'm2v', - 'fla', - 'flr', - 'sol', - 'm4v', - 'mkv', - 'wrap', - 'mng', - 'mov', - 'mpeg', - 'mpg', - 'mpe', - 'mp4', - 'mxf', - 'nsv', - 'ogg', - 'rm', - 'svi', - 'smi', - 'wmv', - 'webm', - ] - const extensionsReg = new RegExp('\\.' + videoExtensions.join('$|\\.') + '$') - const videoContentTypesPrefixes = [ - 'binary/octet-stream', - 'video/', - 'flv-application/', - 'media', - ] - - const bannedContentTypes = ['video/mp2t', 'video/f4m', 'video/f4f'] - const bannedFiletypes = ['ts'] - const bannedFiletypesReg = new RegExp( - '\\.' + bannedFiletypes.join('$|\\.') + '$' - ) - const whitelistReqTypes = ['object', 'xmlhttprequest', 'other'] - - const topVideoAssetDomains = [ - '2mdn.net', - 'adap.tv', - 'adnxs.com', - 'adsrvr.org', - 'btrll.com', - 'celtra.com', - 'flashtalking.com', - 'flite.com', - 'innovid.com', - 'jivox.com', - 'mixpo.com', - 'nytimes.com', - 'playwire.com', - 'selectmedia.asia', - 'serving-sys.com', - 'solvemedia.com', - 'spotible.com', - 'teads.tv', - 'tribalfusion.com', - 'tubemogul.com', - 'videologygroup.com', - 'washingtonpost.com', - ] - - const robotsTxtAllows = Driver.checkRobots - if (!String.prototype.endsWith) { - String.prototype.endsWith = function (searchString, position) { - const subjectString = this.toString() - if ( - typeof position !== 'number' || - !isFinite(position) || - Math.floor(position) !== position || - position > subjectString.length - ) { - position = subjectString.length - } - position -= searchString.length - const lastIndex = subjectString.indexOf(searchString, position) - return lastIndex !== -1 && lastIndex === position - } - } - - function getFrame(getFrameDetails, callback) { - chrome.webNavigation.getFrame(getFrameDetails, callback) - } - - function ifTrackingEnabled(details, ifCallback, elseCallback) { - const fullIfCallback = function () { - allowedByRobotsTxt(details, ifCallback, elseCallback) - } - - Utils.getOption('tracking', true).then(function (tracking) { - if (tracking) { - fullIfCallback() - } else { - elseCallback() - } - }) - } - - function allowedByRobotsTxt(details, ifCallback, elseCallback) { - if (details.url && !details.url.startsWith('chrome://')) { - Driver.checkRobots(details.url, details.url.startsWith('https:')) - .then(ifCallback) - .catch(elseCallback) - } else { - elseCallback() - } - } - - function isPixelRequest(request) { - return ( - (request.type === 'image' || request.responseStatus === 204) && - request.size <= 1000 - ) - } - - function isVpaidOrVastRequest(request) { - const lowerCaseUrl = request.url.toLowerCase() - return lowerCaseUrl.includes('vpaid') || lowerCaseUrl.includes('vast') - } - - function hasValidRequestType(request) { - return whitelistReqTypes.includes(request.type) - } - - function stripQueryParams(url) { - return url.split('?', 1)[0] - } - - function parseHostnameFromUrl(url) { - try { - const { hostname } = new URL(url) - - return hostname - } catch { - return '' - } - } - - function hasDomain(url, domain) { - return parseHostnameFromUrl(url).endsWith(domain) - } - - function findHeader(headers, key) { - let header - for (let i = 0; i < headers.length; i += 1) { - header = headers[i] - if (header.name.toLowerCase() === key) { - return header - } - } - return null - } - - function validVideoType(vtype) { - const goodType = videoContentTypesPrefixes.some(function (prefix) { - return vtype.indexOf(prefix) === 0 - }) - return goodType - } - - function assetMsgKey(assetReq) { - const url = stripQueryParams(assetReq.url) - const key = assetReq.frameId + '-' + url - return key - } - - const PageNetworkTrafficCollector = function (tabId) { - this.tabId = tabId - this.displayAdFound = false - this.requests = {} - this.msgsBeingSent = {} - this.assetsSeen = {} - this.allRedirects = {} - } - - var globalPageContainer = { - collectors: {}, - dyingCollectors: {}, - - cleanupCollector(tabId) { - if (tabId in this.collectors) { - delete globalPageContainer.collectors[tabId] - } - }, - - onNewNavigation(details) { - const tabId = details.tabId - this.cleanupCollector(tabId) - - ifTrackingEnabled( - details, - function () { - if (!areListenersRegistered) { - registerListeners() - } - this.collectors[tabId] = new PageNetworkTrafficCollector(tabId) - }.bind(this), - function () { - if (areListenersRegistered) { - unregisterListeners() - } - } - ) - }, - - onNavigationCommitted(details) {}, - - onNavigationCompleted(details) {}, - - onTabClose(tabId, closeInfo) { - this.cleanupCollector(tabId) - delete this.collectors[tabId] - }, - - onDisplayAdFound(tabId) { - this.collectors[tabId].displayAdFound = true - }, - - getRandId() { - return String(Math.floor(Math.random() * 1e9)) - }, - - getCollector(tabId) { - if (this.collectors.hasOwnProperty(tabId)) { - return this.collectors[tabId] - } - return null - }, - - forwardCall(details, collectorMemberFunction) { - const collector = this.getCollector(details.tabId) - if (collector !== null) { - collectorMemberFunction.apply(collector, [details]) - } - }, - } - - PageNetworkTrafficCollector.prototype.sendLogMessageToTabConsole = function () { - const logMessage = Array.from(arguments).join(' ') - const message = { message: logMessage, event: 'console-log-message' } - chrome.tabs.sendMessage(this.tabId, message) - } - - PageNetworkTrafficCollector.prototype.sendToTab = function ( - assetReq, - reqs, - curPageUrl, - adTrackingEvent - ) { - const msg = {} - msg.assets = [] - msg.requests = [] - msg.event_data = {} - msg.event = adTrackingEvent - if (adTrackingEvent === 'new-video-ad') { - msg.requests = reqs - msg.requests.sort(function (reqA, reqB) { - return reqA.requestTimestamp - reqB.requestTimestamp - }) - if (assetReq) { - msg.assets = [assetReq] - } - } else if (adTrackingEvent === 'new-invalid-video-ad') { - msg.requests = reqs.map(function (request) { - return parseHostnameFromUrl(request.url) - }) - msg.assets = [ - { - url: parseHostnameFromUrl(assetReq.url), - - contentType: assetReq.contentType, - size: assetReq.size, - }, - ] - } - msg.origUrl = curPageUrl - msg.displayAdFound = this.displayAdFound - - chrome.tabs.sendMessage(this.tabId, msg) - } - - PageNetworkTrafficCollector.prototype.getRedirKey = function (url, frameId) { - return url + ':' + frameId - } - - PageNetworkTrafficCollector.prototype.seenBefore = function (request) { - const oldTime = this.assetsSeen[assetMsgKey(request)] - if (oldTime && request.requestTimestamp - oldTime < secBetweenDupAssets) { - return true - } - return false - } - - PageNetworkTrafficCollector.prototype.recordSeenAsset = function (request) { - this.assetsSeen[assetMsgKey(request)] = request.requestTimestamp - } - - PageNetworkTrafficCollector.prototype.onBeforeRequest = function (details) { - const req = { - url: details.url, - type: details.type, - httpMethod: details.method, - frameId: details.frameId, - parentFrameId: details.parentFrameId, - requestTimestamp: details.timeStamp, - } - this.requests[details.requestId] = req - } - - PageNetworkTrafficCollector.prototype.onSendHeaders = function (details) { - let request, header - request = this.requests[details.requestId] - header = request && findHeader(details.requestHeaders, 'x-requested-with') - if (header && header.value.toLowerCase().includes('flash')) { - request.from_flash = true - } - } - - PageNetworkTrafficCollector.prototype.onHeadersReceived = function (details) { - const getFrameDetails = { - tabId: details.tabId, - processId: null, - frameId: details.frameId, - } - const pageNetworkTrafficController = this - getFrame(getFrameDetails, function (frameDetails) { - if (frameDetails && frameDetails.url) { - pageNetworkTrafficController._onHeadersReceived(details, frameDetails) - } - }) - } - - PageNetworkTrafficCollector.prototype._onHeadersReceived = function ( - details, - frameDetails - ) { - let contentSize, contentRange - - const request = this.requests[details.requestId] - if (request) { - const redirParent = this.allRedirects[ - this.getRedirKey(details.url, details.frameId) - ] - let header = - request && findHeader(details.responseHeaders, 'content-type') - const contentType = header && header.value.toLowerCase() - - if (contentType) { - request.contentType = contentType - } - header = request && findHeader(details.responseHeaders, 'content-length') - contentSize = header && header.value - if (contentSize) { - request.size = request.size || 0 - request.size += parseInt(contentSize) - } - header = request && findHeader(details.responseHeaders, 'content-range') - contentRange = header && header.value - if (contentRange) { - request.contentRange = parseInt(contentRange.split('/')[1]) - } - - let frameUrl = null - if (frameDetails && frameDetails.url) { - frameUrl = frameDetails.url - } - if ( - !this.bannedRequest(request) && - (this.isVideoReq(frameUrl, request) || - (redirParent && redirParent.isVideo)) - ) { - request.isVideo = true - } - } - } - - PageNetworkTrafficCollector.prototype.onBeforeRedirect = function (details) { - const request = this.requests[details.requestId] - if (request) { - if (request.redirects) { - request.redirects.push(details.redirectUrl) - } else { - request.redirects = [details.redirectUrl] - } - this.allRedirects[ - this.getRedirKey(details.redirectUrl, details.frameId) - ] = request - } - } - - PageNetworkTrafficCollector.prototype.isYoutubeMastheadRequest = function ( - url - ) { - const re = /video_masthead/ - return this.hasYoutubeDomain(url) && re.test(url) - } - PageNetworkTrafficCollector.prototype.isYoutubeVideoRequest = function ( - srcUrl, - destUrl - ) { - if (!this.hasYoutubeDomain(srcUrl)) { - return false - } - - const re = /https?:\/\/r.*?\.googlevideo\.com\/videoplayback\?/ - return re.test(destUrl) - } - PageNetworkTrafficCollector.prototype.processResponse = function ( - requestDetails, - frameDetails - ) { - let request - if (requestDetails) { - request = this.requests[requestDetails.requestId] - if (request) { - request.responseStatus = requestDetails.statusCode - request.responseTimestamp = requestDetails.timeStamp - - let frameUrl = null - if (frameDetails && frameDetails.url) { - frameUrl = frameDetails.url - } - - let requestUrl = null - if (request.url) { - requestUrl = request.url - } - - if (this.isYoutubeAdReq(frameUrl, requestUrl)) { - const destVideoId = this.parseYoutubeVideoIdFromUrl(requestUrl) - const srcVideoId = this.parseYoutubeVideoIdFromUrl(frameUrl) - if (srcVideoId && destVideoId) { - request.isYoutubeAd = true - request.isVideo = true - request.rawSrcUrl = frameUrl - request.rawDestUrl = requestUrl - request.url = - 'https://www.youtube.com/watch?v=' + - this.parseYoutubeVideoIdFromUrl(requestUrl) - } - } else if ( - !this.bannedRequest(request) && - (this.isVideo || this.isVideoReq(frameUrl, request)) - ) { - request.isVideo = true - } - - if (request.isVideo) { - const msgKey = assetMsgKey(request) - this.msgsBeingSent[msgKey] = request - if (!this.seenBefore(request)) { - this.sendMsgWhenQuiet(msgKey) - } - this.recordSeenAsset(request) - } - } - } - } - - PageNetworkTrafficCollector.prototype.onResponseStarted = function ( - responseDetails - ) { - if (responseDetails.frameId < 0) { - responseDetails.frameId = 99999 - } - const getFrameDetails = { - tabId: responseDetails.tabId, - processId: null, - frameId: responseDetails.frameId, - } - const pageNetworkTrafficController = this - getFrame(getFrameDetails, function (frameDetails) { - if (frameDetails && frameDetails.url) { - pageNetworkTrafficController.processResponse( - responseDetails, - frameDetails - ) - } - }) - } - - PageNetworkTrafficCollector.prototype.hasBannedFiletype = function (request) { - const url = stripQueryParams(request.url) - if (bannedFiletypesReg.exec(url)) { - return true - } else { - return false - } - } - - PageNetworkTrafficCollector.prototype.checkContentHeaders = function ( - request - ) { - if (request.contentType && validVideoType(request.contentType)) { - return true - } - return false - } - - PageNetworkTrafficCollector.prototype.checkUrlExtension = function (request) { - const url = stripQueryParams(request.url) - if (extensionsReg.exec(url)) { - return true - } else { - return false - } - } - - PageNetworkTrafficCollector.prototype.isVideoReq = function ( - srcUrl, - request - ) { - if (this.isYoutubeVideoRequest(srcUrl, request.url)) { - return false - } - return this.checkUrlExtension(request) || this.checkContentHeaders(request) - } - PageNetworkTrafficCollector.prototype.hasYoutubeDomain = function (url) { - const hostname = parseHostnameFromUrl(url) - if (hostname === 'www.youtube.com') { - return true - } - return false - } - PageNetworkTrafficCollector.prototype.parseYoutubeVideoIdFromUrl = function ( - url - ) { - let re = /^https?:\/\/www\.youtube\.com\/get_video_info.*(?:\?|&)video_id=(.*?)(?:$|&)/ - let match = re.exec(url) - if (match && match.length > 1) { - return match[1] - } - - re = /^https?:\/\/www\.youtube\.com\/embed\/(.*?)(?:$|\?)/ - match = re.exec(url) - if (match && match.length > 1) { - return match[1] - } - - re = /^https?:\/\/www\.youtube\.com\/watch.*(\?|&)v=([^&]*)/ - match = re.exec(url) - if (match && match.length > 1) { - return match[1] - } - return null - } - - PageNetworkTrafficCollector.prototype.isYoutubeGetVideoInfoReq = function ( - url - ) { - const re = /^https?:\/\/www\.youtube\.com\/get_video_info\?/ - return re.test(url) - } - PageNetworkTrafficCollector.prototype.isYoutubeAdReq = function ( - srcUrl, - destUrl - ) { - if ( - !this.hasYoutubeDomain(srcUrl) || - !this.isYoutubeGetVideoInfoReq(destUrl) - ) { - return false - } - if ( - this.parseYoutubeVideoIdFromUrl(srcUrl) === - this.parseYoutubeVideoIdFromUrl(destUrl) && - !this.isYoutubeMastheadRequest(destUrl) - ) { - return false - } - return true - } - - PageNetworkTrafficCollector.prototype.bannedRequest = function (request) { - return ( - this.bannedVideoType(request) || - this.hasBannedFiletype(request) || - this.bannedVideoSize(request) - ) - } - - PageNetworkTrafficCollector.prototype.bannedVideoType = function (request) { - let badType = false - if (request.contentType) { - badType = bannedContentTypes.some(function (prefix) { - return request.contentType.includes(prefix) - }) - } - return badType - } - - PageNetworkTrafficCollector.prototype.bannedVideoSize = function (request) { - if (request.size !== null) { - if ( - request.size < minVidSize || - request.size > maxVidSize || - request.contentRange > maxContentRange - ) { - return true - } - } - return false - } - - PageNetworkTrafficCollector.prototype.grabTagReqs = function ( - tabRequests, - assetRequest - ) { - let minTimestamp, maxTimestamp - minTimestamp = assetRequest.requestTimestamp - secBefore - maxTimestamp = assetRequest.requestTimestamp + secAfter - - const filteredRequests = tabRequests.filter(function (request) { - return ( - request.requestTimestamp > minTimestamp && - request.requestTimestamp < maxTimestamp && - request.frameId === assetRequest.frameId && - request.url !== assetRequest.url && - (hasValidRequestType(request) || isPixelRequest(request)) - ) - }) - - return filteredRequests - } - - PageNetworkTrafficCollector.prototype.isValidVideoAd = function ( - assetRequest, - tagRequests - ) { - const hasVpaidOrVastRequest = tagRequests.some(function (tagRequest) { - return isVpaidOrVastRequest(tagRequest) - }) - - if (assetRequest.isYoutubeAd) { - return true - } - if (hasVpaidOrVastRequest) { - return true - } - const hasTopVideoAssetDomain = topVideoAssetDomains.some(function ( - assetDomain - ) { - return hasDomain(assetRequest.url, assetDomain) - }) - - return hasTopVideoAssetDomain - } - - PageNetworkTrafficCollector.prototype.sendMsgWhenQuiet = function (msgKey) { - const _this = this - let origPageUrl - let msgAssetReq - msgAssetReq = this.msgsBeingSent[msgKey] - chrome.tabs.get(this.tabId, function (tab) { - origPageUrl = tab.url - }) - - setTimeout(function () { - const rawRequests = [] - if (globalPageContainer.collectors[_this.tabId] === _this) { - for (const reqId in _this.requests) { - rawRequests.push(_this.requests[reqId]) - } - const tagReqs = _this.grabTagReqs(rawRequests, msgAssetReq) - - if (_this.isValidVideoAd(msgAssetReq, tagReqs)) { - _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-video-ad') - } else { - _this.sendToTab( - msgAssetReq, - tagReqs, - origPageUrl, - 'new-invalid-video-ad' - ) - } - } else { - } - delete _this.msgsBeingSent[msgKey] - }, secAfter + secBefore) - } - - PageNetworkTrafficCollector.prototype.existingMessage = function ( - candidateRequest - ) { - const frameMsg = this.msgsBeingSent[candidateRequest.frameId] - if (frameMsg) { - return frameMsg - } else { - return null - } - } - - function onBeforeRequestListener(details) { - globalPageContainer.forwardCall( - details, - PageNetworkTrafficCollector.prototype.onBeforeRequest - ) - } - - function onSendHeadersListener(details) { - globalPageContainer.forwardCall( - details, - PageNetworkTrafficCollector.prototype.onSendHeaders - ) - } - - function onHeadersReceivedListener(details) { - globalPageContainer.forwardCall( - details, - PageNetworkTrafficCollector.prototype.onHeadersReceived - ) - } - - function onBeforeRedirectListener(details) { - globalPageContainer.forwardCall( - details, - PageNetworkTrafficCollector.prototype.onBeforeRedirect - ) - } - - function onResponseStartedListener(details) { - globalPageContainer.forwardCall( - details, - PageNetworkTrafficCollector.prototype.onResponseStarted - ) - } - - function onCommittedListener(details) { - if (details.frameId === 0) { - globalPageContainer.onNavigationCommitted(details) - } - } - - function onCompletedListener(details) { - if (details.frameId === 0) { - globalPageContainer.onNavigationCompleted(details) - } - } - - function onRemovedListener(tabId, closeInfo) { - globalPageContainer.onTabClose(tabId, closeInfo) - } - - function onMessageListener(message, sender, sendResponse) { - if (message.event === 'new-ad' && message.data.event === 'ad') { - const tabId = sender.tab.id - if (tabId) { - globalPageContainer.onDisplayAdFound(tabId) - } - } - } - - function registerListeners() { - chrome.webRequest.onBeforeRequest.addListener( - onBeforeRequestListener, - { urls: ['http://*/*', 'https://*/*'] }, - [] - ) - - chrome.webRequest.onSendHeaders.addListener( - onSendHeadersListener, - { urls: ['http://*/*', 'https://*/*'] }, - ['requestHeaders'] - ) - - chrome.webRequest.onHeadersReceived.addListener( - onHeadersReceivedListener, - { urls: ['http://*/*', 'https://*/*'] }, - ['responseHeaders'] - ) - - chrome.webRequest.onBeforeRedirect.addListener( - onBeforeRedirectListener, - { urls: ['http://*/*', 'https://*/*'] }, - [] - ) - - chrome.webRequest.onResponseStarted.addListener( - onResponseStartedListener, - { urls: ['http://*/*', 'https://*/*'] }, - ['responseHeaders'] - ) - - chrome.webNavigation.onCommitted.addListener(onCommittedListener) - chrome.webNavigation.onCompleted.addListener(onCompletedListener) - chrome.tabs.onRemoved.addListener(onRemovedListener) - chrome.runtime.onMessage.addListener(onMessageListener) - - areListenersRegistered = true - } - - function unregisterListeners() { - chrome.webRequest.onBeforeRequest.removeListener(onBeforeRequestListener) - - chrome.webRequest.onSendHeaders.removeListener(onSendHeadersListener) - - chrome.webRequest.onHeadersReceived.removeListener( - onHeadersReceivedListener - ) - - chrome.webRequest.onBeforeRedirect.removeListener(onBeforeRedirectListener) - - chrome.webRequest.onResponseStarted.removeListener( - onResponseStartedListener - ) - - chrome.webNavigation.onCommitted.removeListener(onCommittedListener) - chrome.webNavigation.onCompleted.removeListener(onCompletedListener) - chrome.tabs.onRemoved.removeListener(onRemovedListener) - chrome.runtime.onMessage.removeListener(onMessageListener) - areListenersRegistered = false - } - - chrome.webNavigation.onBeforeNavigate.addListener( - function (details) { - if (details.frameId === 0) { - globalPageContainer.onNewNavigation(details) - } - }, - { - url: [{ urlMatches: 'http://*/*' }, { urlMatches: 'https://*/*' }], - } - ) - - chrome.runtime.onMessage.addListener((message, sender, callback) => { - if (message === 'is_tracking_enabled') { - ifTrackingEnabled( - sender.tab, - function () { - try { - callback({ tracking_enabled: true }) - } catch (err) {} - }, - function () { - try { - callback({ tracking_enabled: false }) - } catch (err) {} - } - ) - } - return true - }) -})() diff --git a/src/drivers/webextension/js/popup.js b/src/drivers/webextension/js/popup.js index bea241e7e..73f9bf5da 100644 --- a/src/drivers/webextension/js/popup.js +++ b/src/drivers/webextension/js/popup.js @@ -585,8 +585,12 @@ const Popup = { el.issue.classList.remove('issue--hidden') el.plusDownload.classList.remove('plus-download--hidden') - while (el.detections.firstChild) { - el.detections.removeChild(detections.firstChild) + let firstChild + + while ((firstChild = el.detections.firstChild)) { + if (firstChild instanceof Node) { + el.detections.removeChild(firstChild) + } } const pinnedCategory = await getOption('pinnedCategory') @@ -722,8 +726,12 @@ const Popup = { el.crawl.classList.add('plus-crawl--hidden') el.error.classList.add('plus-error--hidden') - while (el.panels.lastElementChild) { - el.panels.removeChild(el.panels.lastElementChild) + let lastChild + + while ((lastChild = el.panels.lastElementChild)) { + if (lastChild instanceof Node) { + el.panels.removeChild(lastChild) + } } try { diff --git a/src/drivers/webextension/manifest-v2.json b/src/drivers/webextension/manifest-v2.json index d4bdc9e79..055605b99 100644 --- a/src/drivers/webextension/manifest-v2.json +++ b/src/drivers/webextension/manifest-v2.json @@ -46,17 +46,6 @@ "js/content.js" ], "run_at": "document_idle" - }, - { - "matches": [ - "http://*/*", - "https://*/*" - ], - "js": [ - "js/lib/iframe.js" - ], - "run_at": "document_start", - "all_frames": true } ], "web_accessible_resources": [ diff --git a/src/drivers/webextension/manifest-v3.json b/src/drivers/webextension/manifest-v3.json index 39ca6ea92..94d48fbef 100644 --- a/src/drivers/webextension/manifest-v3.json +++ b/src/drivers/webextension/manifest-v3.json @@ -46,17 +46,6 @@ "js/content.js" ], "run_at": "document_idle" - }, - { - "matches": [ - "http://*/*", - "https://*/*" - ], - "js": [ - "js/lib/iframe.js" - ], - "run_at": "document_start", - "all_frames": true } ], "web_accessible_resources": [ diff --git a/src/technologies/a.json b/src/technologies/a.json index 27acf0ec1..7db21a41d 100644 --- a/src/technologies/a.json +++ b/src/technologies/a.json @@ -791,6 +791,22 @@ "scriptSrc": "\\.acquire\\.io/(?!cobrowse)", "website": "https://acquire.io" }, + "ActBlue": { + "cats": [ + 111 + ], + "description": "ActBlue is an online fundraising platform that facilitates secure donations to Democratic candidates and progressive causes, streamlining the process of processing and distributing campaign contributions.", + "icon": "ActBlue.svg", + "dom": "a[href*='//secure.actblue.com/donate/']", + "js": { + "actblue.__configuration": "" + }, + "pricing": [ + "payg" + ], + "saas": true, + "website": "https://secure.actblue.com" + }, "Act-On": { "cats": [ 32 @@ -2446,6 +2462,23 @@ "scriptSrc": "\\.alliai\\.com/", "website": "https://www.alliai.com" }, + "Alliance Auth": { + "cats": [ + 69 + ], + "description": "Alliance Auth is an access management platform designed for Eve Online groups. It controls access to applications and services based on in-game memberships.", + "icon": "Alliance Auth.svg", + "dom": "link[rel='stylesheet'][href*='/static/allianceauth/css']", + "implies": [ + "Django", + "Python", + "Font Awesome", + "Bootstrap", + "jQuery" + ], + "oss": true, + "website": "https://gitlab.com/allianceauth/allianceauth" + }, "AlloyUI": { "cats": [ 12 @@ -2542,6 +2575,24 @@ ], "website": "https://www.alumniq.com/platform/" }, + "AlvandCMS": { + "cats": [ + 1 + ], + "description": "AlvandCMS is a PHP-based content management system that is commonly used in Iran.", + "icon": "AlvandCMS.png", + "implies": [ + "PHP", + "MySQL" + ], + "meta": { + "generator": "AlvandCMS\\s([\\d\\.]+)\\;version:\\1" + }, + "pricing": [ + "poa" + ], + "website": "https://alvandcms.ir" + }, "Amaya": { "cats": [ 20 @@ -3455,6 +3506,22 @@ "oss": true, "website": "https://apexcharts.com" }, + "ApexChat": { + "cats": [ + 52 + ], + "description": "ApexChat is a company that provides businesses with live chat software and services to facilitate real-time customer engagement, support, lead generation, and enhanced online interactions.", + "icon": "ApexChat.svg", + "js": { + "ApexChat": "", + "apexchat_dompopup_chatwindow_client": "" + }, + "pricing": [ + "poa" + ], + "saas": true, + "website": "https://www.apexchat.com" + }, "ApexPages": { "cats": [ 51 @@ -3489,10 +3556,7 @@ "icon": "Apisearch.png", "oss": true, "pricing": [ - "freemium", - "low", - "mid", - "recurring" + "payg" ], "saas": true, "scriptSrc": "static\\.apisearch\\.cloud", @@ -4514,6 +4578,26 @@ ], "website": "https://auth0.github.io/auth0.js/index.html" }, + "authorize.net": { + "cats": [ + 41 + ], + "description": "Authorize.net is a secure online payment gateway service that enables businesses to accept payments through various channels, such as ecommerce websites, mobile devices, and retail stores, providing a trusted platform for processing credit card and electronic cheque payments.", + "icon": "authorize.net.svg", + "headers": { + "Content-Security-Policy": "\\.authorize\\.net\\s" + }, + "js": { + "config.authorizenet_public_client_key": "" + }, + "scriptSrc": "\\.authorize\\.net/", + "saas": true, + "pricing": [ + "recurring", + "payg" + ], + "website": "https://www.authorize.net" + }, "Auth0 Lock": { "cats": [ 69 diff --git a/src/technologies/b.json b/src/technologies/b.json index 89ee643ea..0457b0162 100644 --- a/src/technologies/b.json +++ b/src/technologies/b.json @@ -465,6 +465,36 @@ "scriptSrc": "cdn\\.trybeans\\.com", "website": "https://www.trybeans.com/" }, + "Beehiiv": { + "cats": [ + 11 + ], + "description": "Beehiiv is a relatively young, hosted newsletter platform built for businesses and creators.", + "icon": "Beehiiv.svg", + "dom": "link[href*='media.beehiiv.com/']", + "pricing": [ + "freemium", + "low", + "recurring" + ], + "saas": true, + "website": "https://www.beehiiv.com" + }, + "Beehiiv RSS feed": { + "cats": [ + 49 + ], + "description": "Beehiiv RSS feed is a feature of the Beehiiv. Beehiiv is a relatively young, hosted newsletter platform built for businesses and creators.", + "icon": "Beehiiv.svg", + "dom": "iframe[src*='embeds.beehiiv.com/']", + "pricing": [ + "freemium", + "low", + "recurring" + ], + "saas": true, + "website": "https://www.beehiiv.com" + }, "Beeketing": { "cats": [ 32 @@ -528,20 +558,20 @@ "scriptSrc": "/shopify-apps//js/betterprice/betterprice\\.js", "website": "https://apps.shopify.com/better-price" }, - "Better Uptime": { + "Better Stack": { "cats": [ 13 ], - "description": "Better Uptime is the all-in-one infrastructure monitoring platform for your incident management, uptime monitoring, and status pages.", - "icon": "Better Uptime.svg", + "description": "Better Stack is the all-in-one infrastructure monitoring platform for your incident management, uptime monitoring, and status pages.", + "icon": "Better Stack.svg", "pricing": [ "freemium", "low", "recurring" ], "saas": true, - "scriptSrc": "//betteruptime\\.com/", - "website": "https://betterstack.com/better-uptime" + "scriptSrc": "//(?:uptime\\.)?(?:betteruptime|betterstack)\\.com/", + "website": "https://betterstack.com/uptime" }, "BetterDocs": { "cats": [ diff --git a/src/technologies/c.json b/src/technologies/c.json index 2b341c788..4484b3e76 100644 --- a/src/technologies/c.json +++ b/src/technologies/c.json @@ -634,7 +634,7 @@ "meta": { "cargo_title": "" }, - "scriptSrc": "/cargo\\.", + "scriptSrc": "(?]*type=[^>]text\\/x-handlebars-template", - "icon": "Handlebars.png", + "dom": "script[type='text/x-handlebars-template']", + "icon": "Handlebars.svg", "js": { "Handlebars": "", "Handlebars.VERSION": "^(.+)$\\;version:\\1" }, "scriptSrc": "handlebars(?:\\.runtime)?(?:-v([\\d.]+?))?(?:\\.min)?\\.js\\;version:\\1", + "oss": true, "website": "https://handlebarsjs.com" }, "Handtalk": { @@ -831,6 +832,24 @@ "scriptSrc": "highcharts.*\\.js", "website": "https://www.highcharts.com" }, + "HighLevel": { + "cats": [ + 32, + 53 + ], + "description": "HighLevel is an all-in-one marketing and automation platform designed for marketing agencies and small businesses to manage CRM, marketing campaigns, sales funnels, appointment scheduling, and more.", + "icon": "HighLevel.svg", + "js": { + "leadConnector.chatWidget": "" + }, + "scriptSrc": "\\.leadconnectorhq\\.com/", + "saas": true, + "pricing": [ + "mid", + "recurring" + ], + "website": "https://www.gohighlevel.com" + }, "Highlight.js": { "cats": [ 59 diff --git a/src/technologies/i.json b/src/technologies/i.json index 3399fdfdb..592542080 100644 --- a/src/technologies/i.json +++ b/src/technologies/i.json @@ -31,6 +31,26 @@ "icon": "IBM.svg", "website": "https://ibm.com/software/webservers/httpservers" }, + "ID5": { + "cats": [ + 36 + ], + "description": "ID5 is a company that offers an identity solution for digital advertising, providing a Universal ID that enables privacy-compliant user recognition and tracking across websites without relying on personal information or third-party cookies.", + "saas": true, + "icon": "ID5.svg", + "website": "https://id5.io/", + "scriptSrc": [ + "^https://(?:cdn\\.)?id5-sync\\.com/" + ], + "xhr": ".*id5-sync\\.com", + "cookies": { + "id5": "" + }, + "js": { + "ID5._version": "^(.+)$\\;version:\\1", + "__id5_instances": "" + } + }, "IIS": { "cats": [ 22 @@ -392,10 +412,13 @@ }, "Imperva": { "cats": [ - 16 + 16, + 31 ], + "description": "Imperva is a cyber security software and services company for networking, data, and application security.", "headers": { - "X-Iinfo": "" + "X-Iinfo": "", + "x-cdn": "^Imperva" }, "icon": "Imperva.svg", "scriptSrc": [ @@ -1637,4 +1660,4 @@ "saas": true, "website": "https://www.iyzico.com" } -} \ No newline at end of file +} diff --git a/src/technologies/j.json b/src/technologies/j.json index e7e916906..0f0cedba4 100644 --- a/src/technologies/j.json +++ b/src/technologies/j.json @@ -851,7 +851,7 @@ "cats": [ 26 ], - "description": "jQTouch is an open-source Zepto/ JQuery plugin with native animations, automatic navigation, and themes for mobile WebKit browsers like iPhone, G1 (Android), and Palm Pre.", + "description": "jQTouch is an open-source Zepto/ jQuery plugin with native animations, automatic navigation, and themes for mobile WebKit browsers like iPhone, G1 (Android), and Palm Pre.", "icon": "jQTouch.png", "js": { "jQT": "" @@ -936,7 +936,7 @@ "cats": [ 59 ], - "description": "JQuery Modal is an overlay dialog box or in other words, a popup window that is made to display on the top or 'overlayed' on the current page.", + "description": "jQuery Modal is an overlay dialog box or in other words, a popup window that is made to display on the top or 'overlayed' on the current page.", "dom": "link[href*='jquery.modal.min.css']", "icon": "jQuery Modal.png", "implies": "jQuery", diff --git a/src/technologies/k.json b/src/technologies/k.json index 6d2429c2a..78539d8d6 100644 --- a/src/technologies/k.json +++ b/src/technologies/k.json @@ -1244,4 +1244,4 @@ ], "website": "https://www.keep.pt/en/produts/archeevo-archival-management-software" } -} \ No newline at end of file +} diff --git a/src/technologies/l.json b/src/technologies/l.json index a1efe51ad..a2dbe108d 100644 --- a/src/technologies/l.json +++ b/src/technologies/l.json @@ -54,6 +54,22 @@ "website": "https://wiki.lkqd.com", "xhr": "\\.lkqd\\.net" }, + "LlamaLink Cloud Server": { + "cats": [ + 22 + ], + "description": "LlamaLink Cloud Server is a custom dynamic web server based on Nginx Web server that load balance user traffic between hosting nodes and allows fast and cached expiriance to website users.", + "icon": "LlamaLink.png", + "implies": "Nginx", + "headers": { + "Server": "^LlamaLink\\sCloud$" + }, + "pricing": [ + "payg", + "recurring" + ], + "website": "https://llamalink.net" + }, "LOU": { "cats": [ 58 @@ -1717,12 +1733,10 @@ }, "Loox": { "cats": [ - 90, - 100 + 90 ], "description": "Loox is a reviews app for Shopify that helps you gather reviews and user-generated photos from your customers.", "icon": "Loox.svg", - "implies": "Shopify", "js": { "loox_global_hash": "" }, diff --git a/src/technologies/m.json b/src/technologies/m.json index f560daaf1..32ae5d675 100644 --- a/src/technologies/m.json +++ b/src/technologies/m.json @@ -398,6 +398,21 @@ "scriptSrc": "\\.magisto\\.com/", "website": "https://www.magisto.com" }, + "Magixite": { + "cats": [ + 68 + ], + "description": "Magixite offers the Web Content Accessibility Guidelines (WCAG), a set of guidelines and requirements designed to help designers and developers improve the accessibility of web content, ensuring it can be effectively used by individuals with disabilities.", + "icon": "Magixite.png", + "saas": true, + "scriptSrc": "acc\\.magixite\\.com/", + "pricing": [ + "freemium", + "low", + "recurring" + ], + "website": "https://acc.magixite.com" + }, "MailChimp": { "cats": [ 32, @@ -683,9 +698,12 @@ 13 ], "cpe": "cpe:2.3:a:mantisbt:mantisbt:*:*:*:*:*:*:*:*", - "html": "]+ alt=\"Powered by Mantis Bugtracker", "icon": "MantisBT.png", - "implies": "PHP", + "description": "MantisBT is an open-source, web-based issue tracking system written in PHP with a MySQL database backend, designed to facilitate bug tracking and project management for software development teams.", + "dom": "link[rel='stylesheet'][href*='/css/ace-mantis.css']", + "requires": "PHP", + "implies": "MySQL", + "oss": true, "website": "https://www.mantisbt.org" }, "ManyChat": { @@ -2231,17 +2249,6 @@ "scriptSrc": "cdn\\.moengage\\.\\w+", "website": "https://www.moengage.com" }, - "Moat": { - "cats": [ - 10 - ], - "description": "Moat is a digital ad analytics tool.", - "icon": "Moat.svg", - "scriptSrc": [ - "moatads\\.com" - ], - "website": "https://moat.com/" - }, "MobX": { "cats": [ 59 @@ -3397,4 +3404,4 @@ ], "website": "https://code.google.com/p/modwsgi" } -} \ No newline at end of file +} diff --git a/src/technologies/n.json b/src/technologies/n.json index a839ad0c3..9f9b93bba 100644 --- a/src/technologies/n.json +++ b/src/technologies/n.json @@ -294,6 +294,23 @@ "scriptSrc": "rua-api\\.ncloud\\.com/", "website": "https://analytics.naver.com" }, + "Navidium Shipping Protection": { + "cats": [ + 5 + ], + "description": "Navidium is a shipping insurance company that covers packages that are lost, stolen, or damaged in transit. Navidium Shipping Protection is an optional shipping protection service where, in the event of an incident (the order is damaged, lost, or stolen during transit), we will immediately re-ship any damaged, lost, or stolen products.", + "icon": "Navidium.svg", + "js": { + "nvdShop": "\\.myshopify\\.com" + }, + "pricing": [ + "low", + "recurring" + ], + "saas": true, + "scriptSrc": "^.*/navidium-extension-checker\\.js$", + "website": "https://navidiumapp.com" + }, "Neat A/B testing": { "cats": [ 74, diff --git a/src/technologies/o.json b/src/technologies/o.json index 4451089f8..4edbb66d2 100644 --- a/src/technologies/o.json +++ b/src/technologies/o.json @@ -436,6 +436,7 @@ "cpe": "cpe:2.3:a:omeka:omeka:*:*:*:*:*:*:*:*:*", "description": "Omeka is a free Content Management System (CMS) used by archives, historical societies, libraries, museums, and individual researchers for publishing digital collections.", "icon": "Omeka.png", + "dom": "link[rel*='stylesheet'][href*='css/myomeka.css'], link[rel*='stylesheet'][href*='/omeka/plugins/'], footer > p > a[href*='//omeka.org']", "js": { "Omeka": "" }, @@ -1315,11 +1316,15 @@ }, "cpe": "cpe:2.3:a:episerver:episerver:*:*:*:*:*:*:*:*", "description": "Optimizely Content Management (formerly EPiServer) is digital content, ecommerce, and marketing management solution designed for editors and marketers.", + "dom": "link[href*='/EPiServer.Forms/']", "headers": { "content-security-policy": "\\.episerver\\.net" }, "icon": "Optimizely.svg", "implies": "Microsoft ASP.NET", + "js": { + "epi.EPiServer": "" + }, "meta": { "generator": "EPiServer" }, @@ -1501,6 +1506,17 @@ "scriptSrc": "service\\.maxymiser\\.net", "website": "https://www.oracle.com/uk/cx/marketing/personalization-testing" }, + "Oracle Moat Measurement": { + "cats": [ + 10 + ], + "description": "Oracle Moat delivers solutions that are critical to measuring advertising effectiveness, including verification and attention, reach, and frequency as well as sales lift measurement.", + "icon": "Oracle.svg", + "scriptSrc": [ + "moatads\\.com" + ], + "website": "https://www.oracle.com/cx/advertising/measurement/" + }, "Oracle Recommendations On Demand": { "cats": [ 10 @@ -1606,6 +1622,24 @@ "saas": true, "website": "https://www.orckestra.com" }, + "OrderCast": { + "cats": [ + 6 + ], + "description": "OrderCast is a B2B ecommerce platform focused on streamlining wholesale operations, offering SKU management, order handling, and customisable online store features for improved customer experience.", + "icon": "OrderCast.svg", + "implies": [ + "Python", + "React" + ], + "pricing": [ + "poa", + "recurring" + ], + "saas": true, + "url": "\\.(?:eu|us1)\\.ordercast\\.io/", + "website": "https://www.ordercast.io" + }, "Order Deadline": { "cats": [ 100 @@ -1954,4 +1988,4 @@ }, "website": "https://owncloud.org" } -} \ No newline at end of file +} diff --git a/src/technologies/p.json b/src/technologies/p.json index 7b1be6b50..62808b0ed 100644 --- a/src/technologies/p.json +++ b/src/technologies/p.json @@ -2051,12 +2051,14 @@ ], "cpe": "cpe:2.3:a:plone:plone:*:*:*:*:*:*:*:*", "description": "Plone is a free and open source content management system (CMS) built on top of the Zope application server.", + "dom": "link[href^='/++resource++']", "icon": "Plone.svg", "implies": "Python", "meta": { "generator": "Plone" }, "oss": true, + "scriptSrc": "^/\\+\\+resource\\+\\+", "website": "https://plone.org/" }, "Plotly": { @@ -2739,10 +2741,10 @@ "MySQL" ], "js": { - "freeProductTranslation": "\\;confidence:25", + "freeProductTranslation": "\\;confidence:40", "prestashop": "", - "priceDisplayMethod": "\\;confidence:25", - "priceDisplayPrecision": "\\;confidence:25", + "priceDisplayMethod": "\\;confidence:40", + "priceDisplayPrecision": "\\;confidence:40", "rcAnalyticsEvents.eventPrestashopCheckout": "" }, "meta": { @@ -3025,6 +3027,53 @@ ], "website": "https://www.profitwell.com/" }, + "Progress Sitefinity": { + "cats": [ + 1 + ], + "cpe": "cpe:2.3:a:progress:sitefinity:*:*:*:*:*:*:*:*", + "description": "Progress Sitefinity is a content management system (CMS) that you use to create, store, manage, and present content on your website.", + "icon": "Sitefinity.svg", + "implies": "Microsoft ASP.NET", + "js": { + "sfDataIntell": "" + }, + "meta": { + "generator": "^Sitefinity\\s([\\S]{3,9})\\;version:\\1" + }, + "pricing": [ + "poa" + ], + "saas": true, + "website": "https://www.sitefinity.com" + }, + "Progress WS_FTP": { + "cats": [ + 19 + ], + "cpe":"cpe:2.3:a:progress:ws_ftp_server:*:*:*:*:*:*:*:*", + "description": "Progress WS_FTP is a file transfer client software developed by Progress Software Corporation, supporting FTP, FTPS, SFTP, and HTTPS protocols with features like drag-and-drop support, file synchronization, and encrypted data transmission.", + "dom": "form[name='formLogin'][action='login.aspx' i][id='formLogin']\\;confidence:40", + "scriptSrc": "/ThinClient/(?:WTM|WebResource)(?:\\.axd|/public)\\;confidence:60", + "requires": "Microsoft ASP.NET", + "icon": "Progress.svg", + "website": "https://www.progress.com/ws_ftp" + }, + "Progress MOVEit": { + "cats": [ + 19 + ], + "description": "Progress MOVEit is a managed file transfer solution that enables secure and compliant transfer of sensitive files while providing automation, central management, and auditing capabilities.", + "headers": { + "X-Moveitisapi-Version": "^(.+)$\\;version:\\1" + }, + "meta": { + "apple-itunes-app": "app-id=1500056420", + "google-play-app": "app-id=com\\.progress\\.moveit\\.transfer\\.dev\\.appid" + }, + "icon": "Progress.svg", + "website": "https://www.progress.com/moveit" + }, "Project Wonderful": { "cats": [ 36 diff --git a/src/technologies/r.json b/src/technologies/r.json index 7696c3347..affa3532d 100644 --- a/src/technologies/r.json +++ b/src/technologies/r.json @@ -2310,6 +2310,22 @@ ], "website": "https://www.royalmail.com" }, + "Rubedo": { + "cats": [ + 1 + ], + "description": "Rubedo is an open-source PHP CMS powered by the Zend Framework, NoSQL MongoDB, Elasticsearch, and AngularJS, offering advanced features for content management and development.", + "implies": [ + "MongoDB", + "Elasticsearch", + "PHP" + ], + "js": { + "rubedoConfig": "" + }, + "oss": true, + "website": "https://github.com/WebTales/rubedo" + }, "Rubicon Project": { "cats": [ 36 @@ -2379,7 +2395,8 @@ "implies": "Ruby", "js": { "ReactOnRails": "", - "__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__": "" + "__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__": "", + "_rails_loaded": "" }, "meta": { "csrf-param": "^authenticity_token$\\;confidence:50" @@ -2481,4 +2498,4 @@ "scripts": "/recaptcha/api\\.js", "website": "https://www.google.com/recaptcha/" } -} \ No newline at end of file +} diff --git a/src/technologies/s.json b/src/technologies/s.json index acd3ae7dc..3e500b1e7 100644 --- a/src/technologies/s.json +++ b/src/technologies/s.json @@ -793,12 +793,21 @@ "cats": [ 6 ], - "description": "Salla is an ecommerce platform.", + "description": "Salla is an ecommerce platform specifically tailored to serve businesses and customers in Saudi Arabia.", "headers": { "x-powered-by": "^Salla$" }, "icon": "Salla.svg", - "scriptSrc": "cdn\\.salla\\.sa/", + "js": { + "Salla.shop": "", + "SallaApplePay": "" + }, + "pricing": [ + "freemium", + "low", + "recurring" + ], + "saas": true, "website": "https://salla.sa" }, "Salonist": { @@ -1531,6 +1540,36 @@ "saas": true, "website": "https://selldone.com" }, + "Selless": { + "cats": [ + 6 + ], + "description": "Selless is an all-in-one, ready-to-use ecommerce platform.", + "icon": "Selless.svg", + "js": { + "__NEXT_DATA__.assetPrefix": "\\.selless\\.us/" + }, + "pricing": [ + "poa", + "recurring" + ], + "saas": true, + "website": "https://selless.com" + }, + "SellersCommerce": { + "cats": [ + 6 + ], + "description": "SellersCommerce is a medium ecommerce software company that provides b2b ecommerce platform to retail companies.", + "icon": "SellersCommerce.png", + "pricing": [ + "poa" + ], + "saas": true, + "scriptSrc": "\\.sellerscommerce\\.com/", + "requires": "Microsoft ASP.NET", + "website": "https://www.sellerscommerce.com" + }, "Sellfy": { "cats": [ 6 @@ -2752,6 +2791,25 @@ "scriptSrc": "cdn\\.shoppinggives\\.com/", "website": "https://shoppinggives.com" }, + "Shoppub": { + "cats": [ + 6 + ], + "description": "Shoppub is an ecommerce platform that focuses on implementing advanced business rules.", + "icon": "Shoppub.svg", + "meta": { + "author": "^Shoppub$" + }, + "js": { + "Shoppub.store": "" + }, + "pricing": [ + "mid", + "recurring" + ], + "saas": true, + "website": "https://www.shoppub.com.br" + }, "Shoppy": { "cats": [ 6 @@ -3584,26 +3642,6 @@ "saas": true, "website": "https://www.sitecore.com/" }, - "Sitefinity": { - "cats": [ - 1 - ], - "cpe": "cpe:2.3:a:progress:sitefinity:*:*:*:*:*:*:*:*", - "description": "Sitefinity is a content management system (CMS) that you use to create, store, manage, and present content on your website.", - "icon": "Sitefinity.svg", - "implies": "Microsoft ASP.NET", - "js": { - "sfDataIntell": "" - }, - "meta": { - "generator": "^Sitefinity\\s([\\S]{3,9})\\;version:\\1" - }, - "pricing": [ - "poa" - ], - "saas": true, - "website": "https://www.sitefinity.com" - }, "Siteglide": { "cats": [ 1, @@ -3846,6 +3884,23 @@ "saas": true, "website": "https://www.skolengo.com" }, + "Skyflow": { + "cats": [ + 16 + ], + "description": "Skyflow is a company that provides a secure data privacy platform and API.", + "icon": "Skyflow.svg", + "js": { + "Skyflow.ElementType.CARD_NUMBER": "", + "Skyflow.ValidationRuleType": "" + }, + "pricing": [ + "high", + "recurring" + ], + "saas": true, + "website": "https://www.skyflow.com" + }, "Sky-Shop": { "cats": [ 6 @@ -4651,7 +4706,7 @@ "SolidJS" ], "js": { - "_$HY": "" + "_$HY.init": "" }, "oss": true, "website": "https://start.solidjs.com" @@ -4949,7 +5004,7 @@ 67 ], "description": "Spatie Laravel Cookie Consent is a banner that is displayed on websites to ask visitors for consent for the use of cookies.", - "icon": "Spatie.png", + "icon": "Spatie.svg", "implies": "Laravel", "js": { "laravelCookieConsent": "" @@ -4979,7 +5034,7 @@ ], "description": "Spatie Support Bubble is a non-intrusive support form.", "dom": "div.spatie-support-bubble", - "icon": "Spatie.png", + "icon": "Spatie.svg", "implies": [ "Laravel", "Tailwind CSS" @@ -6247,7 +6302,7 @@ 1 ], "description": "Storyblok is a headless CMS with a visual editor for developers, marketers and content editors. Storyblok helps your team to manage content and digital experiences for every use-case from corporate websites, ecommerce, helpdesks, mobile apps, screen displays, and more.", - "dom": "img[src*='//a.storyblok.com/']", + "dom": "img[src*='//a.storyblok.com/'], img[srcset*='a.storyblok.com']", "headers": { "content-security-policy": "app\\.storyblok\\.com", "x-frame-options": "app\\.storyblok\\.com" @@ -7230,4 +7285,4 @@ }, "website": "https://styled-components.com" } -} \ No newline at end of file +} diff --git a/src/technologies/t.json b/src/technologies/t.json index 0d973b9ec..c3f9e7bb0 100644 --- a/src/technologies/t.json +++ b/src/technologies/t.json @@ -224,17 +224,16 @@ ], "cpe": "cpe:2.3:a:typo3:typo3:*:*:*:*:*:*:*:*", "description": "TYPO3 is a free and open-source Web content management system written in PHP.", - "html": [ - "]+ href=\"/?typo3(?:conf|temp)/", - "]+ src=\"/?typo3(?:conf|temp)/", - "