diff --git a/src/drivers/webextension/_locales/ca/messages.json b/src/drivers/webextension/_locales/ca/messages.json index 825dcf3e4..721464c9d 100644 --- a/src/drivers/webextension/_locales/ca/messages.json +++ b/src/drivers/webextension/_locales/ca/messages.json @@ -15,6 +15,7 @@ "termsAccept": { "message": "Acceptar" }, "termsContent": { "message": "Aquesta extensió envia informació anònima sobre els llocs web que visiteu, inclosos el nom de domini i les tecnologies identificades a wappalyzer.com. Això pot desactivar-se a Opcions." }, "privacyPolicy": { "message": "Política de privadesa" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Taulers de missatgeria" }, "categoryName3": { "message": "Gestor de bases de dades" }, diff --git a/src/drivers/webextension/_locales/de/messages.json b/src/drivers/webextension/_locales/de/messages.json index f49ecc216..847dbf1fa 100644 --- a/src/drivers/webextension/_locales/de/messages.json +++ b/src/drivers/webextension/_locales/de/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Immer Icon anzeigen" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Nachrichten Board" }, "categoryName3": { "message": "Datenbankverwaltung" }, diff --git a/src/drivers/webextension/_locales/el/messages.json b/src/drivers/webextension/_locales/el/messages.json index 6f46ccd03..766f7f9b7 100644 --- a/src/drivers/webextension/_locales/el/messages.json +++ b/src/drivers/webextension/_locales/el/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Διαδικτυακό Φόρουμ" }, "categoryName3": { "message": "Διαχειριστής Βάσης Δεδομένων" }, diff --git a/src/drivers/webextension/_locales/en/messages.json b/src/drivers/webextension/_locales/en/messages.json index c2b6245c7..f65fc1a43 100644 --- a/src/drivers/webextension/_locales/en/messages.json +++ b/src/drivers/webextension/_locales/en/messages.json @@ -15,6 +15,7 @@ "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Message boards" }, "categoryName3": { "message": "Database managers" }, diff --git a/src/drivers/webextension/_locales/es/messages.json b/src/drivers/webextension/_locales/es/messages.json index ee0a480ee..14d102648 100644 --- a/src/drivers/webextension/_locales/es/messages.json +++ b/src/drivers/webextension/_locales/es/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "Gestor de Contenido" }, "categoryName2": { "message": "Foro" }, "categoryName3": { "message": "Gestor de Bases de Datos" }, diff --git a/src/drivers/webextension/_locales/fa/messages.json b/src/drivers/webextension/_locales/fa/messages.json index 1407f089b..adb30b530 100644 --- a/src/drivers/webextension/_locales/fa/messages.json +++ b/src/drivers/webextension/_locales/fa/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "همیشه نماد را نشان بده" }, "termsAccept": { "message": "قبول" }, "termsContent": { "message": "این افزونه اطلاعات وب‌سایت‌های بازدید شده توسط شما را به صورت ناشناس ارسال می‌کند، مانند آدرس سایت و تکنولوژی‌های استفاده شده در آن سایت را ارسال می‌کند. اطلاعات بیشتر در wappalyzer.com. شما می‌توانید این افزونه را غیرفعال کنید." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "سیستم مدیریت محتوا" }, "categoryName2": { "message": "انجمن پیام" }, "categoryName3": { "message": "مدیریت پایگاه داده" }, diff --git a/src/drivers/webextension/_locales/fr/messages.json b/src/drivers/webextension/_locales/fr/messages.json index 88a4ba138..37fd1c2ce 100644 --- a/src/drivers/webextension/_locales/fr/messages.json +++ b/src/drivers/webextension/_locales/fr/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": " Toujours afficher l'icône" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "Gestionnaire de base de données" }, diff --git a/src/drivers/webextension/_locales/gl_ES/messages.json b/src/drivers/webextension/_locales/gl_ES/messages.json index 6e54050a1..795060901 100644 --- a/src/drivers/webextension/_locales/gl_ES/messages.json +++ b/src/drivers/webextension/_locales/gl_ES/messages.json @@ -15,6 +15,7 @@ "termsAccept": { "message": "Aceptar" }, "termsContent": { "message": "Esta extensión envía anonimamente información acerca das webs que visitas, incluindo dominio e aplicativos identificados, a wappalyzer.com. Isto pode ser desactivado nas preferencias." }, "privacyPolicy": { "message": "Política de privacidade" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Taboleiro de mensaxes" }, "categoryName3": { "message": "Xestor de base de datos" }, diff --git a/src/drivers/webextension/_locales/gr/messages.json b/src/drivers/webextension/_locales/gr/messages.json index 6dceaf22c..b631044ee 100644 --- a/src/drivers/webextension/_locales/gr/messages.json +++ b/src/drivers/webextension/_locales/gr/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Διαδικτυακό Φόρουμ" }, "categoryName3": { "message": "Διαχειριστής Βάσης Δεδομένων" }, diff --git a/src/drivers/webextension/_locales/id/messages.json b/src/drivers/webextension/_locales/id/messages.json index 31f4b64f7..11d46d599 100644 --- a/src/drivers/webextension/_locales/id/messages.json +++ b/src/drivers/webextension/_locales/id/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "Sistem Pengelola Konten" }, "categoryName2": { "message": "Papan Pesan" }, "categoryName3": { "message": "Pengelola Basis Data" }, diff --git a/src/drivers/webextension/_locales/it/messages.json b/src/drivers/webextension/_locales/it/messages.json index 0f6f656d1..27882408a 100644 --- a/src/drivers/webextension/_locales/it/messages.json +++ b/src/drivers/webextension/_locales/it/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "Gestore di Database" }, diff --git a/src/drivers/webextension/_locales/ja/messages.json b/src/drivers/webextension/_locales/ja/messages.json index 362b88f5f..666d4d957 100644 --- a/src/drivers/webextension/_locales/ja/messages.json +++ b/src/drivers/webextension/_locales/ja/messages.json @@ -15,6 +15,7 @@ "termsAccept": { "message": "受諾する" }, "termsContent": { "message": "この拡張機能は、ドメイン名や特定された技術など、アクセスしたWebサイトに関する匿名情報をwappalyzer.comに送信します。これは設定で無効にできます。" }, "privacyPolicy": { "message": "プライバシーポリシー" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "メッセージボード" }, "categoryName3": { "message": "データベースマネージャー" }, diff --git a/src/drivers/webextension/_locales/pl/messages.json b/src/drivers/webextension/_locales/pl/messages.json index b04f2400e..90e83acfd 100644 --- a/src/drivers/webextension/_locales/pl/messages.json +++ b/src/drivers/webextension/_locales/pl/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Zawsze pokazuj tą ikonę" }, "termsAccept": { "message": "Akceptuj" }, "termsContent": { "message": "To rozszerzenie wysyła anonimowe informacje o stronach, które odwiedzasz, uwzględniając nazwy domen i zidentyfikowane technologie do wappalyzer.com. Opcja może zostać wyłączona w ustawieniach." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "System zarządzania treścią" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "Menedżer baz danych" }, diff --git a/src/drivers/webextension/_locales/pt/messages.json b/src/drivers/webextension/_locales/pt/messages.json index e358292b1..a7fd7a55a 100644 --- a/src/drivers/webextension/_locales/pt/messages.json +++ b/src/drivers/webextension/_locales/pt/messages.json @@ -15,6 +15,7 @@ "termsAccept": { "message": "Aceitar" }, "termsContent": { "message": "Esta extensão envia informações anónimas sobre os sites que visitas, incluindo o nome de domínio e as tecnologias identificadas, para o wappalyzer.com. Isso pode ser desativado nas configurações." }, "privacyPolicy": { "message": "Políticas de Privacidade" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Fórum" }, "categoryName3": { "message": "Gestor de Base de Dados" }, diff --git a/src/drivers/webextension/_locales/pt_BR/messages.json b/src/drivers/webextension/_locales/pt_BR/messages.json index 217d81b16..e32b108aa 100644 --- a/src/drivers/webextension/_locales/pt_BR/messages.json +++ b/src/drivers/webextension/_locales/pt_BR/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Sempre mostrar ícone" }, "termsAccept": { "message": "Aceitar" }, "termsContent": { "message": "Esta extensão envia informações anônimas sobre os sites que você visita, incluindo domínio e tecnologias identificadas para wappalyzer.com. Este comportamento pode ser desativado nas configurações." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Fórum" }, "categoryName3": { "message": "Gestão de Banco de Dados" }, diff --git a/src/drivers/webextension/_locales/ro/messages.json b/src/drivers/webextension/_locales/ro/messages.json index 68415aa06..a3036e74a 100644 --- a/src/drivers/webextension/_locales/ro/messages.json +++ b/src/drivers/webextension/_locales/ro/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Afișează icon tot timpul" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Forum de discuții" }, "categoryName3": { "message": "Manager baze de date" }, diff --git a/src/drivers/webextension/_locales/ru/messages.json b/src/drivers/webextension/_locales/ru/messages.json index 6d7a691fb..1c73c5907 100644 --- a/src/drivers/webextension/_locales/ru/messages.json +++ b/src/drivers/webextension/_locales/ru/messages.json @@ -67,6 +67,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName54": { "message": "SEO" }, "categoryName55": { "message": "Бухгалтерский учёт" }, "categoryName56": { "message": "Криптомайнер" }, diff --git a/src/drivers/webextension/_locales/sk/messages.json b/src/drivers/webextension/_locales/sk/messages.json index e07d4c928..b9ed2ce77 100644 --- a/src/drivers/webextension/_locales/sk/messages.json +++ b/src/drivers/webextension/_locales/sk/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Message Board" }, "categoryName3": { "message": "Správca databáz" }, diff --git a/src/drivers/webextension/_locales/tr/messages.json b/src/drivers/webextension/_locales/tr/messages.json index 5ded59dd2..63167726a 100644 --- a/src/drivers/webextension/_locales/tr/messages.json +++ b/src/drivers/webextension/_locales/tr/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Her zaman bu kategorinin ikonunu kullan" }, "termsAccept": { "message": "Kabul Ediyorum" }, "termsContent": { "message": "Bu eklenti, ziyaret ettiğiniz web site bilgilerini, alan adları ve tespit edilen teknolojiler ile beraber anonim olarak wappalyzer.com'a gönderir. Bunu, eklenti ayarlarından değiştirebilirsiniz." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "İçerik Yönetim Sistemi" }, "categoryName2": { "message": "Mesaj Tahtası" }, "categoryName3": { "message": "Veritabanı Yöneticisi" }, diff --git a/src/drivers/webextension/_locales/uk/messages.json b/src/drivers/webextension/_locales/uk/messages.json index 176a02601..c8c28983f 100644 --- a/src/drivers/webextension/_locales/uk/messages.json +++ b/src/drivers/webextension/_locales/uk/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Форум" }, "categoryName3": { "message": "Менеджер БД" }, diff --git a/src/drivers/webextension/_locales/uz/messages.json b/src/drivers/webextension/_locales/uz/messages.json index 6557c08d1..b0af1ba9f 100644 --- a/src/drivers/webextension/_locales/uz/messages.json +++ b/src/drivers/webextension/_locales/uz/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "Always show icon" }, "termsAccept": { "message": "Accept" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "CMS (KBT)" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "MB boshqaruvi" }, diff --git a/src/drivers/webextension/_locales/zh_CN/messages.json b/src/drivers/webextension/_locales/zh_CN/messages.json index 270801b1a..2aadbda4c 100644 --- a/src/drivers/webextension/_locales/zh_CN/messages.json +++ b/src/drivers/webextension/_locales/zh_CN/messages.json @@ -15,6 +15,7 @@ "termsAccept": { "message": "接受" }, "termsContent": { "message": "此扩展程序发送关于您访问的网站的匿名信息至 wappalyzer.com,包含域名和检测到的技术。这可以在设置中禁用。" }, "privacyPolicy": { "message": "隐私政策" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "内容管理系统(CMS)" }, "categoryName2": { "message": "消息板" }, "categoryName3": { "message": "数据库管理器" }, diff --git a/src/drivers/webextension/_locales/zh_TW/messages.json b/src/drivers/webextension/_locales/zh_TW/messages.json index ac32f3009..9ca2612f3 100644 --- a/src/drivers/webextension/_locales/zh_TW/messages.json +++ b/src/drivers/webextension/_locales/zh_TW/messages.json @@ -14,6 +14,8 @@ "categoryPin": { "message": "永遠顯示圖示" }, "termsAccept": { "message": "接受" }, "termsContent": { "message": "這個擴充功能將你所造訪網站的網域名稱和識別到的技術等資訊,匿名傳送至 wappalyzer.com。你可以在選項中停用。" }, + "privacyPolicy": { "message": "Privacy policy" }, + "createAlert": { "message": "Create an alert for this website" }, "categoryName1": { "message": "內容管理系統(CMS)" }, "categoryName2": { "message": "留言板/討論區" }, "categoryName3": { "message": "資料庫管理" }, diff --git a/src/drivers/webextension/css/popup.css b/src/drivers/webextension/css/popup.css index c8968157c..14ecfd695 100644 --- a/src/drivers/webextension/css/popup.css +++ b/src/drivers/webextension/css/popup.css @@ -31,6 +31,24 @@ body { display: none; } +.footer { + align-items: center; + border-top: 1px solid #dbdbdb; + height: 3rem; + display: flex; + padding: 0 1.5rem; +} + +.footer__link { + color: #4608ad; + text-decoration: none; +} + +.footer__link:hover, .footer__link:active { + color: #4608ad; + text-decoration: underline; +} + .container { min-height: 5rem; padding: 1rem 1.5rem 0rem 1.5rem; @@ -202,50 +220,68 @@ body { margin-top: 1rem; } -/* Add alternative color palette for Dark mode theme. */ -body.theme-mode-sync { - background: linear-gradient(160deg, #32067c, #150233); -} +@media (prefers-color-scheme: dark) { + /* Add alternative color palette for Dark mode theme. */ + body.theme-mode-sync { + background: linear-gradient(160deg, #32067c, #150233); + } -.theme-mode-sync .header { - border-bottom: 1px solid #000; -} + .theme-mode-sync .header { + border-bottom: 1px solid rgba(255, 255, 255, .2); + } -.theme-mode-sync .header__logo--dark { - display: inline-block; -} + .theme-mode-sync .header__logo--dark { + display: inline-block; + } -.theme-mode-sync .header__logo--light { - display: none; -} + .theme-mode-sync .header__logo--light { + display: none; + } -.theme-mode-sync .container { - color: white; -} + .theme-mode-sync .footer { + border-top: 1px solid rgba(255, 255, 255, .2); + } -.theme-mode-sync .detected__category-link, .theme-mode-sync .detected__app { - color: white; -} + .theme-mode-sync .footer__link { + color: rgba(255, 255, 255, .8); + } -.theme-mode-sync .detected__category-link:hover { - color: white; - border-bottom: 1px solid white; -} + .theme-mode-sync .footer__link:hover, .theme-mode-sync .footer__link:active { + color: rgba(255, 255, 255, .8); + } -.theme-mode-sync .detected__app-version, .theme-mode-sync .detected__app-confidence { - background-color: #4608ad; -} + .theme-mode-sync .container { + color: white; + } -.theme-mode-sync .detected__app:hover .detected__app-name { - border-bottom: 1px solid white; -} + .theme-mode-sync .detected__category-link { + color: #fff; + } -.theme-mode-sync .detected__app:hover .theme-mode-sync .detected__app-version, -.theme-mode-sync .detected__app:hover .theme-mode-sync .detected__app-confidence { - border-bottom: none; -} + .theme-mode-sync .detected__app { + color: rgba(255, 255, 255, .8); + } -.theme-mode-sync .terms__accept, -.theme-mode-sync .terms__privacy { - color: white; + .theme-mode-sync .detected__category-link:hover { + color: white; + border-bottom: 1px solid white; + } + + .theme-mode-sync .detected__app-version, .theme-mode-sync .detected__app-confidence { + background-color: #4608ad; + } + + .theme-mode-sync .detected__app:hover .detected__app-name { + border-bottom: 1px solid white; + } + + .theme-mode-sync .detected__app:hover .theme-mode-sync .detected__app-version, + .theme-mode-sync .detected__app:hover .theme-mode-sync .detected__app-confidence { + border-bottom: none; + } + + .theme-mode-sync .terms__accept, + .theme-mode-sync .terms__privacy { + color: white; + } } diff --git a/src/drivers/webextension/html/popup.html b/src/drivers/webextension/html/popup.html index 730a1f496..15fdc91dd 100644 --- a/src/drivers/webextension/html/popup.html +++ b/src/drivers/webextension/html/popup.html @@ -14,8 +14,8 @@
- - + +
@@ -24,11 +24,15 @@
- +
+ + diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js index 84e4eae35..5a264616e 100644 --- a/src/drivers/webextension/js/driver.js +++ b/src/drivers/webextension/js/driver.js @@ -10,29 +10,29 @@ /** global: fetch */ /** global: Wappalyzer */ -const wappalyzer = new Wappalyzer(); +const wappalyzer = new Wappalyzer() -const tabCache = {}; -const robotsTxtQueue = {}; +const tabCache = {} +const robotsTxtQueue = {} -let categoryOrder = []; +let categoryOrder = [] browser.tabs.onRemoved.addListener((tabId) => { - tabCache[tabId] = null; -}); + tabCache[tabId] = null +}) function userAgent() { - const url = chrome.extension.getURL('/'); + const url = chrome.extension.getURL('/') if (url.match(/^moz-/)) { - return 'firefox'; + return 'firefox' } if (url.match(/^ms-browser-/)) { - return 'edge'; + return 'edge' } - return 'chrome'; + return 'chrome' } /** @@ -40,22 +40,22 @@ function userAgent() { */ function getOption(name, defaultValue = null) { return new Promise(async (resolve, reject) => { - let value = defaultValue; + let value = defaultValue try { - const option = await browser.storage.local.get(name); + const option = await browser.storage.local.get(name) if (option[name] !== undefined) { - value = option[name]; + value = option[name] } } catch (error) { - wappalyzer.log(error.message, 'driver', 'error'); + wappalyzer.log(error.message, 'driver', 'error') - return reject(error.message); + return reject(error.message) } - return resolve(value); - }); + return resolve(value) + }) } /** @@ -64,15 +64,15 @@ function getOption(name, defaultValue = null) { function setOption(name, value) { return new Promise(async (resolve, reject) => { try { - await browser.storage.local.set({ [name]: value }); + await browser.storage.local.set({ [name]: value }) } catch (error) { - wappalyzer.log(error.message, 'driver', 'error'); + wappalyzer.log(error.message, 'driver', 'error') - return reject(error.message); + return reject(error.message) } - return resolve(); - }); + return resolve() + }) } /** @@ -81,8 +81,8 @@ function setOption(name, value) { function openTab(args) { browser.tabs.create({ url: args.url, - active: args.background === undefined || !args.background, - }); + active: args.background === undefined || !args.background + }) } /** @@ -92,321 +92,346 @@ async function post(url, body) { try { const response = await fetch(url, { method: 'POST', - body: JSON.stringify(body), - }); + body: JSON.stringify(body) + }) - wappalyzer.log(`POST ${url}: ${response.status}`, 'driver'); + wappalyzer.log(`POST ${url}: ${response.status}`, 'driver') } catch (error) { - wappalyzer.log(`POST ${url}: ${error}`, 'driver', 'error'); + wappalyzer.log(`POST ${url}: ${error}`, 'driver', 'error') } } // Capture response headers -browser.webRequest.onCompleted.addListener(async (request) => { - const headers = {}; +browser.webRequest.onCompleted.addListener( + async (request) => { + const headers = {} - if (request.responseHeaders) { - const url = wappalyzer.parseUrl(request.url); + if (request.responseHeaders) { + const url = wappalyzer.parseUrl(request.url) - let tab; + let tab - try { - [tab] = await browser.tabs.query({ url: [url.href] }); - } catch (error) { - wappalyzer.log(error, 'driver', 'error'); - } + try { + ;[tab] = await browser.tabs.query({ url: [url.href] }) + } catch (error) { + wappalyzer.log(error, 'driver', 'error') + } - if (tab) { - request.responseHeaders.forEach((header) => { - const name = header.name.toLowerCase(); + if (tab) { + request.responseHeaders.forEach((header) => { + const name = header.name.toLowerCase() - headers[name] = headers[name] || []; + headers[name] = headers[name] || [] - headers[name].push((header.value || header.binaryValue || '').toString()); - }); + headers[name].push( + (header.value || header.binaryValue || '').toString() + ) + }) - if (headers['content-type'] && /\/x?html/.test(headers['content-type'][0])) { - wappalyzer.analyze(url, { headers }, { tab }); + if ( + headers['content-type'] && + /\/x?html/.test(headers['content-type'][0]) + ) { + wappalyzer.analyze(url, { headers }, { tab }) + } } } - } -}, { urls: ['http://*/*', 'https://*/*'], types: ['main_frame'] }, ['responseHeaders']); + }, + { urls: ['http://*/*', 'https://*/*'], types: ['main_frame'] }, + ['responseHeaders'] +) browser.runtime.onConnect.addListener((port) => { port.onMessage.addListener(async (message) => { if (message.id === undefined) { - return; + return } if (message.id !== 'log') { - wappalyzer.log(`Message from ${port.name}: ${message.id}`, 'driver'); + wappalyzer.log(`Message from ${port.name}: ${message.id}`, 'driver') } - const pinnedCategory = await getOption('pinnedCategory'); + const pinnedCategory = await getOption('pinnedCategory') - const url = wappalyzer.parseUrl(port.sender.tab ? port.sender.tab.url : ''); + const url = wappalyzer.parseUrl(port.sender.tab ? port.sender.tab.url : '') - const cookies = await browser.cookies.getAll({ domain: `.${url.hostname}`, firstPartyDomain: null }); + const cookies = await browser.cookies.getAll({ + domain: `.${url.hostname}`, + firstPartyDomain: null + }) - let response; + let response switch (message.id) { case 'log': - wappalyzer.log(message.subject, message.source); + wappalyzer.log(message.subject, message.source) - break; + break case 'init': - wappalyzer.analyze(url, { cookies }, { tab: port.sender.tab }); + wappalyzer.analyze(url, { cookies }, { tab: port.sender.tab }) - break; + break case 'analyze': if (message.subject.html) { - browser.i18n.detectLanguage(message.subject.html) + browser.i18n + .detectLanguage(message.subject.html) .then(({ languages }) => { const language = languages .filter(({ percentage }) => percentage >= 75) - .map(({ language: lang }) => lang)[0]; + .map(({ language: lang }) => lang)[0] - message.subject.language = language; + message.subject.language = language - wappalyzer.analyze(url, message.subject, { tab: port.sender.tab }); - }); + wappalyzer.analyze(url, message.subject, { tab: port.sender.tab }) + }) } else { - wappalyzer.analyze(url, message.subject, { tab: port.sender.tab }); + wappalyzer.analyze(url, message.subject, { tab: port.sender.tab }) } - await setOption('hostnameCache', wappalyzer.hostnameCache); + await setOption('hostnameCache', wappalyzer.hostnameCache) - break; + break case 'ad_log': - wappalyzer.cacheDetectedAds(message.subject); + wappalyzer.cacheDetectedAds(message.subject) - break; + break case 'get_apps': response = { tabCache: tabCache[message.tab.id], apps: wappalyzer.apps, categories: wappalyzer.categories, pinnedCategory, - termsAccepted: userAgent() === 'chrome' || await getOption('termsAccepted', false), - }; + termsAccepted: + userAgent() === 'chrome' || + (await getOption('termsAccepted', false)) + } - break; + break case 'set_option': - await setOption(message.key, message.value); + await setOption(message.key, message.value) - break; + break case 'get_js_patterns': response = { - patterns: wappalyzer.jsPatterns, - }; + patterns: wappalyzer.jsPatterns + } - break; + break case 'update_theme_mode': // Sync theme mode to popup. response = { - themeMode: await getOption('themeMode', false), - }; + themeMode: await getOption('themeMode', false) + } - break; + break default: - // Do nothing + // Do nothing } if (response) { port.postMessage({ id: message.id, - response, - }); + response + }) } - }); -}); + }) +}) -wappalyzer.driver.document = document; +wappalyzer.driver.document = document /** * Log messages to console */ wappalyzer.driver.log = (message, source, type) => { - const log = ['warn', 'error'].indexOf(type) !== -1 ? type : 'log'; + const log = ['warn', 'error'].includes(type) ? type : 'log' - console[log](`[wappalyzer ${type}]`, `[${source}]`, message); // eslint-disable-line no-console -}; + console[log](`[wappalyzer ${type}]`, `[${source}]`, message) // eslint-disable-line no-console +} /** * Display apps */ wappalyzer.driver.displayApps = async (detected, meta, context) => { - const { tab } = context; + const { tab } = context if (tab === undefined) { - return; + return } tabCache[tab.id] = tabCache[tab.id] || { - detected: [], - }; + detected: [] + } - tabCache[tab.id].detected = detected; + tabCache[tab.id].detected = detected - const pinnedCategory = await getOption('pinnedCategory'); - const dynamicIcon = await getOption('dynamicIcon', true); + const pinnedCategory = await getOption('pinnedCategory') + const dynamicIcon = await getOption('dynamicIcon', true) - let found = false; + let found = false // Find the main application to display - [pinnedCategory].concat(categoryOrder).forEach((match) => { + ;[pinnedCategory].concat(categoryOrder).forEach((match) => { Object.keys(detected).forEach((appName) => { - const app = detected[appName]; + const app = detected[appName] app.props.cats.forEach((category) => { if (category === match && !found) { - let icon = app.props.icon && dynamicIcon ? app.props.icon : 'default.svg'; + let icon = + app.props.icon && dynamicIcon ? app.props.icon : 'default.svg' if (/\.svg$/i.test(icon)) { - icon = `converted/${icon.replace(/\.svg$/, '.png')}`; + icon = `converted/${icon.replace(/\.svg$/, '.png')}` } try { browser.pageAction.setIcon({ tabId: tab.id, - path: `../images/icons/${icon}`, - }); + path: `../images/icons/${icon}` + }) } catch (e) { // Firefox for Android does not support setIcon see https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 } - found = true; + found = true } - }); - }); - }); + }) + }) + }) - browser.pageAction.show(tab.id); -}; + browser.pageAction.show(tab.id) +} /** * Fetch and cache robots.txt for host */ wappalyzer.driver.getRobotsTxt = async (host, secure = false) => { if (robotsTxtQueue[host]) { - return robotsTxtQueue[host]; + return robotsTxtQueue[host] } - const tracking = await getOption('tracking', true); - const robotsTxtCache = await getOption('robotsTxtCache', {}); + const tracking = await getOption('tracking', true) + const robotsTxtCache = await getOption('robotsTxtCache', {}) robotsTxtQueue[host] = new Promise(async (resolve) => { if (!tracking) { - return resolve([]); + return resolve([]) } if (host in robotsTxtCache) { - return resolve(robotsTxtCache[host]); + return resolve(robotsTxtCache[host]) } - const timeout = setTimeout(() => resolve([]), 3000); + const timeout = setTimeout(() => resolve([]), 3000) - let response; + let response try { - response = await fetch(`http${secure ? 's' : ''}://${host}/robots.txt`, { redirect: 'follow', mode: 'no-cors' }); + response = await fetch(`http${secure ? 's' : ''}://${host}/robots.txt`, { + redirect: 'follow', + mode: 'no-cors' + }) } catch (error) { - wappalyzer.log(error, 'driver', 'error'); + wappalyzer.log(error, 'driver', 'error') - return resolve([]); + return resolve([]) } - clearTimeout(timeout); + clearTimeout(timeout) - const robotsTxt = response.ok ? await response.text() : ''; + const robotsTxt = response.ok ? await response.text() : '' - robotsTxtCache[host] = Wappalyzer.parseRobotsTxt(robotsTxt); + robotsTxtCache[host] = Wappalyzer.parseRobotsTxt(robotsTxt) - await setOption('robotsTxtCache', robotsTxtCache); + await setOption('robotsTxtCache', robotsTxtCache) - delete robotsTxtQueue[host]; + delete robotsTxtQueue[host] - return resolve(robotsTxtCache[host]); - }); + return resolve(robotsTxtCache[host]) + }) - return robotsTxtQueue[host]; -}; + return robotsTxtQueue[host] +} /** * Anonymously track detected applications for research purposes */ wappalyzer.driver.ping = async (hostnameCache = {}, adCache = []) => { - const tracking = await getOption('tracking', true); - const termsAccepted = userAgent() === 'chrome' || await getOption('termsAccepted', false); + const tracking = await getOption('tracking', true) + const termsAccepted = + userAgent() === 'chrome' || (await getOption('termsAccepted', false)) if (tracking && termsAccepted) { if (Object.keys(hostnameCache).length) { - post('https://api.wappalyzer.com/ping/v1/', hostnameCache); + post('https://api.wappalyzer.com/ping/v1/', hostnameCache) } if (adCache.length) { - post('https://ad.wappalyzer.com/log/wp/', adCache); + post('https://ad.wappalyzer.com/log/wp/', adCache) } - await setOption('robotsTxtCache', {}); + await setOption('robotsTxtCache', {}) } -}; +} // Init -(async () => { +;(async () => { // Technologies try { - const response = await fetch('../apps.json'); - const json = await response.json(); + const response = await fetch('../apps.json') + const json = await response.json() - wappalyzer.apps = json.apps; - wappalyzer.categories = json.categories; + wappalyzer.apps = json.apps + wappalyzer.categories = json.categories } catch (error) { - wappalyzer.log(`GET apps.json: ${error.message}`, 'driver', 'error'); + wappalyzer.log(`GET apps.json: ${error.message}`, 'driver', 'error') } - wappalyzer.parseJsPatterns(); + wappalyzer.parseJsPatterns() categoryOrder = Object.keys(wappalyzer.categories) - .map(categoryId => parseInt(categoryId, 10)) - .sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority); + .map((categoryId) => parseInt(categoryId, 10)) + .sort( + (a, b) => + wappalyzer.categories[a].priority - wappalyzer.categories[b].priority + ) // Version check - const { version } = browser.runtime.getManifest(); - const previousVersion = await getOption('version'); - const upgradeMessage = await getOption('upgradeMessage', true); + const { version } = browser.runtime.getManifest() + const previousVersion = await getOption('version') + const upgradeMessage = await getOption('upgradeMessage', true) if (previousVersion === null) { openTab({ - url: `${wappalyzer.config.websiteURL}installed`, - }); + url: `${wappalyzer.config.websiteURL}installed` + }) } else if (version !== previousVersion && upgradeMessage) { openTab({ url: `${wappalyzer.config.websiteURL}upgraded?v${version}`, - background: true, - }); + background: true + }) } - await setOption('version', version); + await setOption('version', version) // Hostname cache - wappalyzer.hostnameCache = await getOption('hostnameCache', {}); + wappalyzer.hostnameCache = await getOption('hostnameCache', {}) // Run content script on all tabs try { - const tabs = await browser.tabs.query({ url: ['http://*/*', 'https://*/*'] }); + const tabs = await browser.tabs.query({ + url: ['http://*/*', 'https://*/*'] + }) tabs.forEach(async (tab) => { try { await browser.tabs.executeScript(tab.id, { - file: '../js/content.js', - }); + file: '../js/content.js' + }) } catch (error) { // } - }); + }) } catch (error) { - wappalyzer.log(error, 'driver', 'error'); + wappalyzer.log(error, 'driver', 'error') } -})(); +})() diff --git a/src/drivers/webextension/js/options.js b/src/drivers/webextension/js/options.js index 03c6ae504..29d11d5d4 100644 --- a/src/drivers/webextension/js/options.js +++ b/src/drivers/webextension/js/options.js @@ -3,29 +3,29 @@ /* globals browser Wappalyzer */ /* eslint-env browser */ -const wappalyzer = new Wappalyzer(); +const wappalyzer = new Wappalyzer() /** * Get a value from localStorage */ function getOption(name, defaultValue = null) { return new Promise(async (resolve, reject) => { - let value = defaultValue; + let value = defaultValue try { - const option = await browser.storage.local.get(name); + const option = await browser.storage.local.get(name) if (option[name] !== undefined) { - value = option[name]; + value = option[name] } } catch (error) { - wappalyzer.log(error.message, 'driver', 'error'); + wappalyzer.log(error.message, 'driver', 'error') - return reject(error.message); + return reject(error.message) } - return resolve(value); - }); + return resolve(value) + }) } /** @@ -34,72 +34,76 @@ function getOption(name, defaultValue = null) { function setOption(name, value) { return new Promise(async (resolve, reject) => { try { - await browser.storage.local.set({ [name]: value }); + await browser.storage.local.set({ [name]: value }) } catch (error) { - wappalyzer.log(error.message, 'driver', 'error'); + wappalyzer.log(error.message, 'driver', 'error') - return reject(error.message); + return reject(error.message) } - return resolve(); - }); + return resolve() + }) } document.addEventListener('DOMContentLoaded', async () => { - const nodes = document.querySelectorAll('[data-i18n]'); + const nodes = document.querySelectorAll('[data-i18n]') Array.prototype.forEach.call(nodes, (node) => { - node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n); - }); + node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n) + }) document.querySelector('#github').addEventListener('click', () => { - window.open(wappalyzer.config.githubURL); - }); + window.open(wappalyzer.config.githubURL) + }) document.querySelector('#twitter').addEventListener('click', () => { - window.open(wappalyzer.config.twitterURL); - }); + window.open(wappalyzer.config.twitterURL) + }) document.querySelector('#wappalyzer').addEventListener('click', () => { - window.open(wappalyzer.config.websiteURL); - }); + window.open(wappalyzer.config.websiteURL) + }) - let el; - let value; + let el + let value // Upgrade message - value = await getOption('upgradeMessage', true); + value = await getOption('upgradeMessage', true) - el = document.querySelector('#option-upgrade-message'); + el = document.querySelector('#option-upgrade-message') - el.checked = value; + el.checked = value - el.addEventListener('change', e => setOption('upgradeMessage', e.target.checked)); + el.addEventListener('change', (e) => + setOption('upgradeMessage', e.target.checked) + ) // Dynamic icon - value = await getOption('dynamicIcon', true); + value = await getOption('dynamicIcon', true) - el = document.querySelector('#option-dynamic-icon'); + el = document.querySelector('#option-dynamic-icon') - el.checked = value; + el.checked = value - el.addEventListener('change', e => setOption('dynamicIcon', e.target.checked)); + el.addEventListener('change', (e) => + setOption('dynamicIcon', e.target.checked) + ) // Tracking - value = await getOption('tracking', true); + value = await getOption('tracking', true) - el = document.querySelector('#option-tracking'); + el = document.querySelector('#option-tracking') - el.checked = value; + el.checked = value - el.addEventListener('change', e => setOption('tracking', e.target.checked)); + el.addEventListener('change', (e) => setOption('tracking', e.target.checked)) // Theme Mode - value = await getOption('themeMode', false); + value = await getOption('themeMode', false) - el = document.querySelector('#option-theme-mode'); + el = document.querySelector('#option-theme-mode') - el.checked = value; + el.checked = value - el.addEventListener('change', e => setOption('themeMode', e.target.checked)); -}); + el.addEventListener('change', (e) => setOption('themeMode', e.target.checked)) +}) diff --git a/src/drivers/webextension/js/popup.js b/src/drivers/webextension/js/popup.js index cf5d2d95d..d04c65d9f 100644 --- a/src/drivers/webextension/js/popup.js +++ b/src/drivers/webextension/js/popup.js @@ -4,217 +4,267 @@ /** global: browser */ /** global: jsonToDOM */ -let pinnedCategory = null; -let termsAccepted = false; +let pinnedCategory = null +let termsAccepted = false const port = browser.runtime.connect({ - name: 'popup.js', -}); + name: 'popup.js' +}) function slugify(string) { - return string.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/--+/g, '-').replace(/(?:^-|-$)/, ''); + return string + .toLowerCase() + .replace(/[^a-z0-9-]/g, '-') + .replace(/--+/g, '-') + .replace(/(?:^-|-$)/, '') } function i18n() { - const nodes = document.querySelectorAll('[data-i18n]'); + const nodes = document.querySelectorAll('[data-i18n]') Array.prototype.forEach.call(nodes, (node) => { - node.innerHTML = browser.i18n.getMessage(node.dataset.i18n); - }); + node.innerHTML = browser.i18n.getMessage(node.dataset.i18n) + }) } function replaceDom(domTemplate) { - const container = document.getElementsByClassName('container')[0]; + const container = document.getElementsByClassName('container')[0] while (container.firstChild) { - container.removeChild(container.firstChild); + container.removeChild(container.firstChild) } - container.appendChild(jsonToDOM(domTemplate, document, {})); + container.appendChild(jsonToDOM(domTemplate, document, {})) - i18n(); + i18n() - Array.from(document.querySelectorAll('.detected__category-pin-wrapper')).forEach((pin) => { + Array.from( + document.querySelectorAll('.detected__category-pin-wrapper') + ).forEach((pin) => { pin.addEventListener('click', () => { - const categoryId = parseInt(pin.dataset.categoryId, 10); + const categoryId = parseInt(pin.dataset.categoryId, 10) if (categoryId === pinnedCategory) { - pin.className = 'detected__category-pin-wrapper'; + pin.className = 'detected__category-pin-wrapper' - pinnedCategory = null; + pinnedCategory = null } else { - const active = document.querySelector('.detected__category-pin-wrapper--active'); + const active = document.querySelector( + '.detected__category-pin-wrapper--active' + ) if (active) { - active.className = 'detected__category-pin-wrapper'; + active.className = 'detected__category-pin-wrapper' } - pin.className = 'detected__category-pin-wrapper detected__category-pin-wrapper--active'; + pin.className = + 'detected__category-pin-wrapper detected__category-pin-wrapper--active' - pinnedCategory = categoryId; + pinnedCategory = categoryId } port.postMessage({ id: 'set_option', key: 'pinnedCategory', - value: pinnedCategory, - }); - }); - }); + value: pinnedCategory + }) + }) + }) } function replaceDomWhenReady(dom) { if (/complete|interactive|loaded/.test(document.readyState)) { - replaceDom(dom); + replaceDom(dom) } else { document.addEventListener('DOMContentLoaded', () => { - replaceDom(dom); - }); + replaceDom(dom) + }) } } function appsToDomTemplate(response) { - let template = []; + let template = [] if (response.tabCache && Object.keys(response.tabCache.detected).length > 0) { - const categories = {}; + const categories = {} // Group apps by category for (const appName in response.tabCache.detected) { response.apps[appName].cats.forEach((cat) => { - categories[cat] = categories[cat] || { apps: [] }; + categories[cat] = categories[cat] || { apps: [] } - categories[cat].apps[appName] = appName; - }); + categories[cat].apps[appName] = appName + }) } for (const cat in categories) { - const apps = []; + const apps = [] for (const appName in categories[cat].apps) { - const { confidence, version } = response.tabCache.detected[appName]; - - apps.push( + const { confidence, version } = response.tabCache.detected[appName] + + apps.push([ + 'a', + { + class: 'detected__app', + target: '_blank', + href: `https://www.wappalyzer.com/technologies/${slugify(appName)}` + }, [ - 'a', { - class: 'detected__app', - target: '_blank', - href: `https://www.wappalyzer.com/technologies/${slugify(appName)}`, - }, [ - 'img', { - class: 'detected__app-icon', - src: `../images/icons/${response.apps[appName].icon || 'default.svg'}`, - }, - ], [ - 'span', { - class: 'detected__app-name', - }, - appName, - ], version ? [ - 'span', { - class: 'detected__app-version', - }, - version, - ] : null, confidence < 100 ? [ - 'span', { - class: 'detected__app-confidence', - }, - `${confidence}% sure`, - ] : null, + 'img', + { + class: 'detected__app-icon', + src: `../images/icons/${response.apps[appName].icon || + 'default.svg'}` + } + ], + [ + 'span', + { + class: 'detected__app-name' + }, + appName ], - ); + version + ? [ + 'span', + { + class: 'detected__app-version' + }, + version + ] + : null, + confidence < 100 + ? [ + 'span', + { + class: 'detected__app-confidence' + }, + `${confidence}% sure` + ] + : null + ]) } - template.push( + template.push([ + 'div', + { + class: 'detected__category' + }, [ - 'div', { - class: 'detected__category', - }, [ - 'div', { - class: 'detected__category-name', - }, [ - 'a', { - class: 'detected__category-link', - target: '_blank', - href: `https://www.wappalyzer.com/categories/${slugify(response.categories[cat].name)}`, - }, - browser.i18n.getMessage(`categoryName${cat}`), - ], [ - 'span', { - class: `detected__category-pin-wrapper${pinnedCategory == cat ? ' detected__category-pin-wrapper--active' : ''}`, - 'data-category-id': cat, - title: browser.i18n.getMessage('categoryPin'), - }, [ - 'img', { - class: 'detected__category-pin detected__category-pin--active', - src: '../images/pin-active.svg', - }, - ], [ - 'img', { - class: 'detected__category-pin detected__category-pin--inactive', - src: '../images/pin.svg', - }, - ], - ], - ], [ - 'div', { - class: 'detected__apps', + 'div', + { + class: 'detected__category-name' + }, + [ + 'a', + { + class: 'detected__category-link', + target: '_blank', + href: `https://www.wappalyzer.com/categories/${slugify( + response.categories[cat].name + )}` }, - apps, + browser.i18n.getMessage(`categoryName${cat}`) ], + [ + 'span', + { + class: `detected__category-pin-wrapper${ + pinnedCategory == cat + ? ' detected__category-pin-wrapper--active' + : '' + }`, + 'data-category-id': cat, + title: browser.i18n.getMessage('categoryPin') + }, + [ + 'img', + { + class: 'detected__category-pin detected__category-pin--active', + src: '../images/pin-active.svg' + } + ], + [ + 'img', + { + class: + 'detected__category-pin detected__category-pin--inactive', + src: '../images/pin.svg' + } + ] + ] ], - ); + [ + 'div', + { + class: 'detected__apps' + }, + apps + ] + ]) } template = [ - 'div', { - class: 'detected', + 'div', + { + class: 'detected' }, - template, - ]; + template + ] } else { template = [ - 'div', { - class: 'empty', + 'div', + { + class: 'empty' }, [ - 'span', { - class: 'empty__text', + 'span', + { + class: 'empty__text' }, - browser.i18n.getMessage('noAppsDetected'), - ], - ]; + browser.i18n.getMessage('noAppsDetected') + ] + ] } - return template; + return template } async function getApps() { try { const tabs = await browser.tabs.query({ active: true, - currentWindow: true, - }); + currentWindow: true + }) + + const url = new URL(tabs[0].url) + + document.querySelector( + '.footer__link' + ).href = `https://www.wappalyzer.com/alerts/manage?url=${encodeURIComponent( + `${url.protocol}//${url.hostname}` + )}` port.postMessage({ id: 'get_apps', - tab: tabs[0], - }); + tab: tabs[0] + }) } catch (error) { - console.error(error); // eslint-disable-line no-console + console.error(error) // eslint-disable-line no-console } } /** * Async function to update body class based on option. */ -async function getThemeMode() { +function getThemeMode() { try { port.postMessage({ - id: 'update_theme_mode', - }); + id: 'update_theme_mode' + }) } catch (error) { - console.error(error); // eslint-disable-line no-console + console.error(error) // eslint-disable-line no-console } } @@ -224,50 +274,51 @@ async function getThemeMode() { */ function updateThemeMode(res) { if (res.hasOwnProperty('themeMode') && res.themeMode !== false) { - document.body.classList.add('theme-mode-sync'); + document.body.classList.add('theme-mode-sync') } } + function displayApps(response) { - pinnedCategory = response.pinnedCategory; // eslint-disable-line prefer-destructuring - termsAccepted = response.termsAccepted; // eslint-disable-line prefer-destructuring + pinnedCategory = response.pinnedCategory // eslint-disable-line prefer-destructuring + termsAccepted = response.termsAccepted // eslint-disable-line prefer-destructuring if (termsAccepted) { - replaceDomWhenReady(appsToDomTemplate(response)); + replaceDomWhenReady(appsToDomTemplate(response)) } else { - i18n(); + i18n() - const wrapper = document.querySelector('.terms__wrapper'); + const wrapper = document.querySelector('.terms__wrapper') document.querySelector('.terms__accept').addEventListener('click', () => { port.postMessage({ id: 'set_option', key: 'termsAccepted', - value: true, - }); + value: true + }) - wrapper.classList.remove('terms__wrapper--active'); + wrapper.classList.remove('terms__wrapper--active') - getApps(); - }); + getApps() + }) - wrapper.classList.add('terms__wrapper--active'); + wrapper.classList.add('terms__wrapper--active') } } port.onMessage.addListener((message) => { switch (message.id) { case 'get_apps': - displayApps(message.response); + displayApps(message.response) - break; + break case 'update_theme_mode': - updateThemeMode(message.response); + updateThemeMode(message.response) - break; + break default: - // Do nothing + // Do nothing } -}); +}) -getThemeMode(); -getApps(); +getThemeMode() +getApps() diff --git a/src/wappalyzer.js b/src/wappalyzer.js index 771de8426..603ea119e 100644 --- a/src/wappalyzer.js +++ b/src/wappalyzer.js @@ -8,130 +8,137 @@ const validation = { hostname: /(www.)?((.+?)\.(([a-z]{2,3}\.)?[a-z]{2,6}))$/, - hostnameBlacklist: /((local|dev(elopment)?|stag(e|ing)?|test(ing)?|demo(shop)?|admin|google|cache)\.|\/admin|\.local)/, -}; + hostnameBlacklist: /((local|dev(elopment)?|stag(e|ing)?|test(ing)?|demo(shop)?|admin|google|cache)\.|\/admin|\.local)/ +} /** * Enclose string in array */ function asArray(value) { - return value instanceof Array ? value : [value]; + return Array.isArray(value) ? value : [value] } /** * */ function asyncForEach(iterable, iterator) { - return Promise.all((iterable || []) - .map(item => new Promise(resolve => setTimeout(() => resolve(iterator(item)), 1)))); + return Promise.all( + (iterable || []).map( + (item) => + new Promise((resolve) => setTimeout(() => resolve(iterator(item)), 1)) + ) + ) } /** * Mark application as detected, set confidence and version */ function addDetected(app, pattern, type, value, key) { - app.detected = true; + app.detected = true // Set confidence level - app.confidence[`${type} ${key ? `${key} ` : ''}${pattern.regex}`] = pattern.confidence === undefined ? 100 : parseInt(pattern.confidence, 10); + app.confidence[`${type} ${key ? `${key} ` : ''}${pattern.regex}`] = + pattern.confidence === undefined ? 100 : parseInt(pattern.confidence, 10) // Detect version number if (pattern.version) { - const versions = []; - const matches = pattern.regex.exec(value); + const versions = [] + const matches = pattern.regex.exec(value) - let { version } = pattern; + let { version } = pattern if (matches) { matches.forEach((match, i) => { // Parse ternary operator - const ternary = new RegExp(`\\\\${i}\\?([^:]+):(.*)$`).exec(version); + const ternary = new RegExp(`\\\\${i}\\?([^:]+):(.*)$`).exec(version) if (ternary && ternary.length === 3) { - version = version.replace(ternary[0], match ? ternary[1] : ternary[2]); + version = version.replace(ternary[0], match ? ternary[1] : ternary[2]) } // Replace back references - version = version.trim().replace(new RegExp(`\\\\${i}`, 'g'), match || ''); - }); + version = version + .trim() + .replace(new RegExp(`\\\\${i}`, 'g'), match || '') + }) - if (version && versions.indexOf(version) === -1) { - versions.push(version); + if (version && !versions.includes(version)) { + versions.push(version) } if (versions.length) { // Use the longest detected version number - app.version = versions.reduce((a, b) => (a.length > b.length ? a : b)); + app.version = versions.reduce((a, b) => (a.length > b.length ? a : b)) } } } } function resolveExcludes(apps, detected) { - const excludes = []; - const detectedApps = Object.assign({}, apps, detected); + const excludes = [] + const detectedApps = Object.assign({}, apps, detected) // Exclude app in detected apps only Object.keys(detectedApps).forEach((appName) => { - const app = detectedApps[appName]; + const app = detectedApps[appName] if (app.props.excludes) { asArray(app.props.excludes).forEach((excluded) => { - excludes.push(excluded); - }); + excludes.push(excluded) + }) } - }); + }) // Remove excluded applications Object.keys(apps).forEach((appName) => { - if (excludes.indexOf(appName) > -1) { - delete apps[appName]; + if (excludes.includes(appName)) { + delete apps[appName] } - }); + }) } class Application { constructor(name, props, detected) { - this.confidence = {}; - this.confidenceTotal = 0; - this.detected = Boolean(detected); - this.excludes = []; - this.name = name; - this.props = props; - this.version = ''; + this.confidence = {} + this.confidenceTotal = 0 + this.detected = Boolean(detected) + this.excludes = [] + this.name = name + this.props = props + this.version = '' } /** * Calculate confidence total */ getConfidence() { - let total = 0; + let total = 0 Object.keys(this.confidence).forEach((id) => { - total += this.confidence[id]; - }); + total += this.confidence[id] + }) - this.confidenceTotal = Math.min(total, 100); + this.confidenceTotal = Math.min(total, 100) - return this.confidenceTotal; + return this.confidenceTotal } } class Wappalyzer { constructor() { - this.apps = {}; - this.categories = {}; - this.driver = {}; - this.jsPatterns = {}; - this.detected = {}; - this.hostnameCache = {}; - this.adCache = []; + this.apps = {} + this.categories = {} + this.driver = {} + this.jsPatterns = {} + this.detected = {} + this.hostnameCache = {} + this.adCache = [] this.config = { websiteURL: 'https://www.wappalyzer.com/', twitterURL: 'https://twitter.com/Wappalyzer', - githubURL: 'https://github.com/AliasIO/Wappalyzer', - }; + githubURL: 'https://github.com/AliasIO/Wappalyzer' + } } /** @@ -139,124 +146,135 @@ class Wappalyzer { */ log(message, source, type) { if (this.driver.log) { - this.driver.log(message, source || '', type || 'debug'); + this.driver.log(message, source || '', type || 'debug') } } analyze(url, data, context) { - const apps = {}; - const promises = []; - const startTime = new Date(); - const { - scripts, - cookies, - headers, - js, - } = data; - - let { html } = data; + const apps = {} + const promises = [] + const startTime = new Date() + const { scripts, cookies, headers, js } = data + + let { html } = data if (this.detected[url.canonical] === undefined) { - this.detected[url.canonical] = {}; + this.detected[url.canonical] = {} } - const metaTags = []; + const metaTags = [] // Additional information - let language = null; + let language = null if (html) { if (typeof html !== 'string') { - html = ''; + html = '' } - let matches = data.html.match(new RegExp(']*[: ]lang="([a-z]{2}((-|_)[A-Z]{2})?)"', 'i')); + let matches = data.html.match( + new RegExp(']*[: ]lang="([a-z]{2}((-|_)[A-Z]{2})?)"', 'i') + ) - language = matches && matches.length ? matches[1] : data.language || null; + language = matches && matches.length ? matches[1] : data.language || null // Meta tags - const regex = /]+>/ig; + const regex = /]+>/gi do { - matches = regex.exec(html); + matches = regex.exec(html) if (!matches) { - break; + break } - metaTags.push(matches[0]); - } while (matches); + metaTags.push(matches[0]) + } while (matches) } Object.keys(this.apps).forEach((appName) => { - apps[appName] = this.detected[url.canonical] && this.detected[url.canonical][appName] - ? this.detected[url.canonical][appName] - : new Application(appName, this.apps[appName]); + apps[appName] = + this.detected[url.canonical] && this.detected[url.canonical][appName] + ? this.detected[url.canonical][appName] + : new Application(appName, this.apps[appName]) - const app = apps[appName]; + const app = apps[appName] - promises.push(this.analyzeUrl(app, url)); + promises.push(this.analyzeUrl(app, url)) if (html) { - promises.push(this.analyzeHtml(app, html)); - promises.push(this.analyzeMeta(app, metaTags)); + promises.push(this.analyzeHtml(app, html)) + promises.push(this.analyzeMeta(app, metaTags)) } if (scripts) { - promises.push(this.analyzeScripts(app, scripts)); + promises.push(this.analyzeScripts(app, scripts)) } if (cookies) { - promises.push(this.analyzeCookies(app, cookies)); + promises.push(this.analyzeCookies(app, cookies)) } if (headers) { - promises.push(this.analyzeHeaders(app, headers)); + promises.push(this.analyzeHeaders(app, headers)) } - }); + }) if (js) { Object.keys(js).forEach((appName) => { if (typeof js[appName] !== 'function') { - promises.push(this.analyzeJs(apps[appName], js[appName])); + promises.push(this.analyzeJs(apps[appName], js[appName])) } - }); + }) } return new Promise(async (resolve) => { - await Promise.all(promises); + await Promise.all(promises) Object.keys(apps).forEach((appName) => { - const app = apps[appName]; + const app = apps[appName] if (!app.detected || !app.getConfidence()) { - delete apps[app.name]; + delete apps[app.name] } - }); + }) - resolveExcludes(apps, this.detected[url]); - this.resolveImplies(apps, url.canonical); + resolveExcludes(apps, this.detected[url]) + this.resolveImplies(apps, url.canonical) - this.cacheDetectedApps(apps, url.canonical); - this.trackDetectedApps(apps, url, language); + this.cacheDetectedApps(apps, url.canonical) + this.trackDetectedApps(apps, url, language) - this.log(`Processing ${Object.keys(data).join(', ')} took ${((new Date() - startTime) / 1000).toFixed(2)}s (${url.hostname})`, 'core'); + this.log( + `Processing ${Object.keys(data).join(', ')} took ${( + (new Date() - startTime) / + 1000 + ).toFixed(2)}s (${url.hostname})`, + 'core' + ) if (Object.keys(apps).length) { - this.log(`Identified ${Object.keys(apps).join(', ')} (${url.hostname})`, 'core'); + this.log( + `Identified ${Object.keys(apps).join(', ')} (${url.hostname})`, + 'core' + ) } - this.driver.displayApps(this.detected[url.canonical], { language }, context); + this.driver.displayApps( + this.detected[url.canonical], + { language }, + context + ) - return resolve(); - }); + return resolve() + }) } /** * Cache detected ads */ cacheDetectedAds(ad) { - this.adCache.push(ad); + this.adCache.push(ad) } /** @@ -264,58 +282,65 @@ class Wappalyzer { */ robotsTxtAllows(url) { return new Promise(async (resolve, reject) => { - const parsed = this.parseUrl(url); + const parsed = this.parseUrl(url) if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { - return reject(); + return reject() } - const robotsTxt = await this.driver.getRobotsTxt(parsed.host, parsed.protocol === 'https:'); - - if (robotsTxt.some(disallowedPath => parsed.pathname.indexOf(disallowedPath) === 0)) { - return reject(); + const robotsTxt = await this.driver.getRobotsTxt( + parsed.host, + parsed.protocol === 'https:' + ) + + if ( + robotsTxt.some( + (disallowedPath) => parsed.pathname.indexOf(disallowedPath) === 0 + ) + ) { + return reject() } - return resolve(); - }); + return resolve() + }) } /** * Parse a URL */ parseUrl(url) { - const a = this.driver.document.createElement('a'); + const a = this.driver.document.createElement('a') - a.href = url; + a.href = url - a.canonical = `${a.protocol}//${a.host}${a.pathname}`; + a.canonical = `${a.protocol}//${a.host}${a.pathname}` - return a; + return a } /** * */ static parseRobotsTxt(robotsTxt) { - const disallow = []; + const disallow = [] - let userAgent; + let userAgent robotsTxt.split('\n').forEach((line) => { - let matches = /^User-agent:\s*(.+)$/i.exec(line.trim()); + let matches = /^User-agent:\s*(.+)$/i.exec(line.trim()) if (matches) { - userAgent = matches[1].toLowerCase(); + userAgent = matches[1].toLowerCase() } else if (userAgent === '*' || userAgent === 'wappalyzer') { - matches = /^Disallow:\s*(.+)$/i.exec(line.trim()); + matches = /^Disallow:\s*(.+)$/i.exec(line.trim()) if (matches) { - disallow.push(matches[1]); + disallow.push(matches[1]) } } - }); + }) - return disallow; + return disallow } /** @@ -323,15 +348,15 @@ class Wappalyzer { */ ping() { if (Object.keys(this.hostnameCache).length > 50) { - this.driver.ping(this.hostnameCache); + this.driver.ping(this.hostnameCache) - this.hostnameCache = {}; + this.hostnameCache = {} } if (this.adCache.length > 50) { - this.driver.ping({}, this.adCache); + this.driver.ping({}, this.adCache) - this.adCache = []; + this.adCache = [] } } @@ -340,55 +365,55 @@ class Wappalyzer { */ parsePatterns(patterns) { if (!patterns) { - return []; + return [] } - let parsed = {}; + let parsed = {} // Convert string to object containing array containing string - if (typeof patterns === 'string' || patterns instanceof Array) { + if (typeof patterns === 'string' || Array.isArray(patterns)) { patterns = { - main: asArray(patterns), - }; + main: asArray(patterns) + } } Object.keys(patterns).forEach((key) => { - parsed[key] = []; + parsed[key] = [] asArray(patterns[key]).forEach((pattern) => { - const attrs = {}; + const attrs = {} pattern.split('\\;').forEach((attr, i) => { if (i) { // Key value pairs - attr = attr.split(':'); + attr = attr.split(':') if (attr.length > 1) { - attrs[attr.shift()] = attr.join(':'); + attrs[attr.shift()] = attr.join(':') } } else { - attrs.string = attr; + attrs.string = attr try { - attrs.regex = new RegExp(attr.replace('/', '\/'), 'i'); // Escape slashes in regular expression + attrs.regex = new RegExp(attr.replace('/', '/'), 'i') // Escape slashes in regular expression } catch (error) { - attrs.regex = new RegExp(); + attrs.regex = new RegExp() - this.log(`${error.message}: ${attr}`, 'error', 'core'); + this.log(`${error.message}: ${attr}`, 'error', 'core') } } - }); + }) - parsed[key].push(attrs); - }); - }); + parsed[key].push(attrs) + }) + }) // Convert back to array if the original pattern list was an array (or string) if ('main' in parsed) { - parsed = parsed.main; + parsed = parsed.main } - return parsed; + return parsed } /** @@ -397,49 +422,60 @@ class Wappalyzer { parseJsPatterns() { Object.keys(this.apps).forEach((appName) => { if (this.apps[appName].js) { - this.jsPatterns[appName] = this.parsePatterns(this.apps[appName].js); + this.jsPatterns[appName] = this.parsePatterns(this.apps[appName].js) } - }); + }) } resolveImplies(apps, url) { - let checkImplies = true; + let checkImplies = true const resolve = (appName) => { - const app = apps[appName]; + const app = apps[appName] if (app && app.props.implies) { asArray(app.props.implies).forEach((implied) => { - [implied] = this.parsePatterns(implied); + ;[implied] = this.parsePatterns(implied) if (!this.apps[implied.string]) { - this.log(`Implied application ${implied.string} does not exist`, 'core', 'warn'); + this.log( + `Implied application ${implied.string} does not exist`, + 'core', + 'warn' + ) - return; + return } if (!(implied.string in apps)) { - apps[implied.string] = this.detected[url] && this.detected[url][implied.string] - ? this.detected[url][implied.string] - : new Application(implied.string, this.apps[implied.string], true); - - checkImplies = true; + apps[implied.string] = + this.detected[url] && this.detected[url][implied.string] + ? this.detected[url][implied.string] + : new Application( + implied.string, + this.apps[implied.string], + true + ) + + checkImplies = true } // Apply app confidence to implied app Object.keys(app.confidence).forEach((id) => { - apps[implied.string].confidence[`${id} implied by ${appName}`] = app.confidence[id] * (implied.confidence === undefined ? 1 : implied.confidence / 100); - }); - }); + apps[implied.string].confidence[`${id} implied by ${appName}`] = + app.confidence[id] * + (implied.confidence === undefined ? 1 : implied.confidence / 100) + }) + }) } - }; + } // Implied applications // Run several passes as implied apps may imply other apps while (checkImplies) { - checkImplies = false; + checkImplies = false - Object.keys(apps).forEach(resolve); + Object.keys(apps).forEach(resolve) } } @@ -448,19 +484,18 @@ class Wappalyzer { */ cacheDetectedApps(apps, url) { Object.keys(apps).forEach((appName) => { - const app = apps[appName]; + const app = apps[appName] // Per URL - this.detected[url][appName] = app; + this.detected[url][appName] = app - Object.keys(app.confidence) - .forEach((id) => { - this.detected[url][appName].confidence[id] = app.confidence[id]; - }); - }); + Object.keys(app.confidence).forEach((id) => { + this.detected[url][appName].confidence[id] = app.confidence[id] + }) + }) if (this.driver.ping instanceof Function) { - this.ping(); + this.ping() } } @@ -469,204 +504,219 @@ class Wappalyzer { */ trackDetectedApps(apps, url, language) { if (!(this.driver.ping instanceof Function)) { - return; + return } - const hostname = `${url.protocol}//${url.hostname}`; + const hostname = `${url.protocol}//${url.hostname}` Object.keys(apps).forEach((appName) => { - const app = apps[appName]; + const app = apps[appName] if (this.detected[url.canonical][appName].getConfidence() >= 100) { if ( - validation.hostname.test(url.hostname) - && !validation.hostnameBlacklist.test(url.hostname) + validation.hostname.test(url.hostname) && + !validation.hostnameBlacklist.test(url.hostname) ) { if (!(hostname in this.hostnameCache)) { this.hostnameCache[hostname] = { applications: {}, - meta: {}, - }; + meta: {} + } } if (!(appName in this.hostnameCache[hostname].applications)) { this.hostnameCache[hostname].applications[appName] = { - hits: 0, - }; + hits: 0 + } } - this.hostnameCache[hostname].applications[appName].hits += 1; + this.hostnameCache[hostname].applications[appName].hits += 1 if (apps[appName].version) { - this.hostnameCache[hostname].applications[appName].version = app.version; + this.hostnameCache[hostname].applications[appName].version = + app.version } } } - }); + }) if (hostname in this.hostnameCache) { - this.hostnameCache[hostname].meta.language = language; + this.hostnameCache[hostname].meta.language = language } - this.ping(); + this.ping() } /** * Analyze URL */ analyzeUrl(app, url) { - const patterns = this.parsePatterns(app.props.url); + const patterns = this.parsePatterns(app.props.url) if (!patterns.length) { - return Promise.resolve(); + return Promise.resolve() } return asyncForEach(patterns, (pattern) => { if (pattern.regex.test(url.canonical)) { - addDetected(app, pattern, 'url', url.canonical); + addDetected(app, pattern, 'url', url.canonical) } - }); + }) } /** * Analyze HTML */ analyzeHtml(app, html) { - const patterns = this.parsePatterns(app.props.html); + const patterns = this.parsePatterns(app.props.html) if (!patterns.length) { - return Promise.resolve(); + return Promise.resolve() } return asyncForEach(patterns, (pattern) => { if (pattern.regex.test(html)) { - addDetected(app, pattern, 'html', html); + addDetected(app, pattern, 'html', html) } - }); + }) } /** * Analyze script tag */ analyzeScripts(app, scripts) { - const patterns = this.parsePatterns(app.props.script); + const patterns = this.parsePatterns(app.props.script) if (!patterns.length) { - return Promise.resolve(); + return Promise.resolve() } return asyncForEach(patterns, (pattern) => { scripts.forEach((uri) => { if (pattern.regex.test(uri)) { - addDetected(app, pattern, 'script', uri); + addDetected(app, pattern, 'script', uri) } - }); - }); + }) + }) } /** * Analyze meta tag */ analyzeMeta(app, metaTags) { - const patterns = this.parsePatterns(app.props.meta); - const promises = []; + const patterns = this.parsePatterns(app.props.meta) + const promises = [] if (!app.props.meta) { - return Promise.resolve(); + return Promise.resolve() } metaTags.forEach((match) => { Object.keys(patterns).forEach((meta) => { - const r = new RegExp(`(?:name|property)=["']${meta}["']`, 'i'); + const r = new RegExp(`(?:name|property)=["']${meta}["']`, 'i') if (r.test(match)) { - const content = match.match(/content=("|')([^"']+)("|')/i); - - promises.push(asyncForEach(patterns[meta], (pattern) => { - if (content && content.length === 4 && pattern.regex.test(content[2])) { - addDetected(app, pattern, 'meta', content[2], meta); - } - })); + const content = match.match(/content=("|')([^"']+)("|')/i) + + promises.push( + asyncForEach(patterns[meta], (pattern) => { + if ( + content && + content.length === 4 && + pattern.regex.test(content[2]) + ) { + addDetected(app, pattern, 'meta', content[2], meta) + } + }) + ) } - }); - }); + }) + }) - return Promise.all(promises); + return Promise.all(promises) } /** * Analyze response headers */ analyzeHeaders(app, headers) { - const patterns = this.parsePatterns(app.props.headers); - const promises = []; + const patterns = this.parsePatterns(app.props.headers) + const promises = [] Object.keys(patterns).forEach((headerName) => { if (typeof patterns[headerName] !== 'function') { - promises.push(asyncForEach(patterns[headerName], (pattern) => { - headerName = headerName.toLowerCase(); - - if (headerName in headers) { - headers[headerName].forEach((headerValue) => { - if (pattern.regex.test(headerValue)) { - addDetected(app, pattern, 'headers', headerValue, headerName); - } - }); - } - })); + promises.push( + asyncForEach(patterns[headerName], (pattern) => { + headerName = headerName.toLowerCase() + + if (headerName in headers) { + headers[headerName].forEach((headerValue) => { + if (pattern.regex.test(headerValue)) { + addDetected(app, pattern, 'headers', headerValue, headerName) + } + }) + } + }) + ) } - }); + }) - return promises ? Promise.all(promises) : Promise.resolve(); + return promises ? Promise.all(promises) : Promise.resolve() } /** * Analyze cookies */ analyzeCookies(app, cookies) { - const patterns = this.parsePatterns(app.props.cookies); - const promises = []; + const patterns = this.parsePatterns(app.props.cookies) + const promises = [] Object.keys(patterns).forEach((cookieName) => { if (typeof patterns[cookieName] !== 'function') { - const cookieNameLower = cookieName.toLowerCase(); + const cookieNameLower = cookieName.toLowerCase() - promises.push(asyncForEach(patterns[cookieName], (pattern) => { - const cookie = cookies.find(_cookie => _cookie.name.toLowerCase() === cookieNameLower); + promises.push( + asyncForEach(patterns[cookieName], (pattern) => { + const cookie = cookies.find( + (_cookie) => _cookie.name.toLowerCase() === cookieNameLower + ) - if (cookie && pattern.regex.test(cookie.value)) { - addDetected(app, pattern, 'cookies', cookie.value, cookieName); - } - })); + if (cookie && pattern.regex.test(cookie.value)) { + addDetected(app, pattern, 'cookies', cookie.value, cookieName) + } + }) + ) } - }); + }) - return promises ? Promise.all(promises) : Promise.resolve(); + return promises ? Promise.all(promises) : Promise.resolve() } /** * Analyze JavaScript variables */ analyzeJs(app, results) { - const promises = []; + const promises = [] Object.keys(results).forEach((string) => { if (typeof results[string] !== 'function') { - promises.push(asyncForEach(Object.keys(results[string]), (index) => { - const pattern = this.jsPatterns[app.name][string][index]; - const value = results[string][index]; + promises.push( + asyncForEach(Object.keys(results[string]), (index) => { + const pattern = this.jsPatterns[app.name][string][index] + const value = results[string][index] - if (pattern && pattern.regex.test(value)) { - addDetected(app, pattern, 'js', value, string); - } - })); + if (pattern && pattern.regex.test(value)) { + addDetected(app, pattern, 'js', value, string) + } + }) + ) } - }); + }) - return promises ? Promise.all(promises) : Promise.resolve(); + return promises ? Promise.all(promises) : Promise.resolve() } } if (typeof module === 'object') { - module.exports = Wappalyzer; + module.exports = Wappalyzer }