diff --git a/src/drivers/webextension/_locales/en/messages.json b/src/drivers/webextension/_locales/en/messages.json
index 60cba2dd6..9f7b85cc0 100644
--- a/src/drivers/webextension/_locales/en/messages.json
+++ b/src/drivers/webextension/_locales/en/messages.json
@@ -8,7 +8,7 @@
"optionUpgradeMessage": { "message": "Tell me about upgrades" },
"optionDynamicIcon": { "message": "Use technology icon instead of Wappalyzer logo" },
"optionTracking": { "message": "Anonymously send identified technologies to wappalyzer.com" },
- "optionThemeMode": { "message": "Enable dark mode compatibility." },
+ "optionThemeMode": { "message": "Enable dark mode compatibility" },
"nothingToDo": { "message": "Nothing to do here." },
"noAppsDetected": { "message": "No technologies detected." },
"categoryPin": { "message": "Always show icon" },
diff --git a/src/drivers/webextension/css/options.css b/src/drivers/webextension/css/options.css
deleted file mode 100644
index b1eaa7df9..000000000
--- a/src/drivers/webextension/css/options.css
+++ /dev/null
@@ -1,108 +0,0 @@
-body {
- color: #303942;
- cursor: default;
- direction: __MSG_@@bidi_dir__;
- font-family: Helvetica, Arial, sans-serif;
- font-size: .8rem;
- line-height: 1.4rem;
- margin: 0;
-}
-
-p {
- margin: 0 0 1rem 0;
-}
-
-h1, h2, h3 {
- font-weight: normal;
- line-height: 1;
-}
-
-h1 {
- border-bottom: 1px solid #dbdbdb;
- font-size: 1.5rem;
- margin: 0 0 1.5rem 0;
- padding: 1rem 0 1.5rem 0;
-}
-
-h2 {
- font-size: 1.3em;
- margin-bottom: 0.4em;
-}
-
-h3 {
- color: black;
- font-size: 1.2em;
- margin-bottom: 0.5em;
-}
-
-a {
- color: rgb(17, 85, 204);
- text-decoration: underline;
-}
-
-label {
- display: block;
-}
-
-button {
- background: #4608ad;
- border: none;
- border-radius: .2rem;
- color: white;
- font-size: inherit;
- padding: 0 .6rem;
- line-height: 1.8rem;
-}
-
-a:active {
- color: rgb(5, 37, 119);
-}
-
-.hero {
- background: linear-gradient(160deg, #32067c, #150233);
- padding: 1.5rem 1.5rem 1rem 1.5rem;
-}
-
- .hero img {
- height: 3rem;
- }
-
-.container {
- margin: 0 auto;
- max-width: 800px;
-}
-
-.content {
- padding: 1.5rem;
-}
-
-#options-saved {
- display: none;
- margin-left: .5rem;
- -webkit-animation: fadeout 2s;
-}
-
-#about {
- border-top: 1px solid #dbdbdb;
- margin-top: 1.5rem;
- padding: 1.5rem 0 0 0;
-}
-
- #about img {
- margin-right: .2rem;
- vertical-align: middle;
- }
-
- #about button {
- background: white;
- border: 1px solid #dbdbdb;
- cursor: pointer;
- color: #303942;
- margin-bottom: .5rem;
- margin-inline-end: 1rem;
- }
-
-@-webkit-keyframes fadeout {
- from { opacity: 1; }
- to { opacity: 0; }
-}
diff --git a/src/drivers/webextension/css/popup.css b/src/drivers/webextension/css/styles.css
similarity index 90%
rename from src/drivers/webextension/css/popup.css
rename to src/drivers/webextension/css/styles.css
index a73551918..9ed0f55a3 100644
--- a/src/drivers/webextension/css/popup.css
+++ b/src/drivers/webextension/css/styles.css
@@ -10,6 +10,7 @@
body {
background: #fff;
+ color: var(--color-text);
direction: __MSG_@@bidi_dir__;
font-family: Helvetica, Arial, sans-serif;
font-size: .9rem;
@@ -36,7 +37,7 @@ a:hover {
align-items: center;
border-bottom: 1px solid var(--color-secondary);
display: flex;
- height: 4rem;
+ height: 4.5rem;
}
.header__logo {
@@ -79,6 +80,12 @@ a:hover {
padding: 1.5rem 1.5rem .5rem 1.5rem;
}
+.empty {
+ opacity: .3;
+ padding: 3rem 1.5rem .5rem 1.5rem;
+ text-align: center;
+}
+
.category {
page-break-inside: avoid;
break-inside: avoid-column;
@@ -189,9 +196,19 @@ a:hover {
margin-top: 1rem;
}
+.options {
+ padding: 1.5rem 1.5rem 1rem 1.5rem;
+}
+
+.options__label {
+ display: block;
+ margin-bottom: .5rem;
+}
+
@media (prefers-color-scheme: dark) {
body.theme-mode {
background: var(--color-primary-darken);
+ color: var(--color-text-dark);
}
.theme-mode a {
@@ -222,6 +239,10 @@ a:hover {
border-color: var(--color-secondary-dark)
}
+ .theme-mode .footer__settings {
+ color: var(--color-text-dark);
+ }
+
.theme-mode .alerts__icon {
color:var(--color-text-dark);
}
diff --git a/src/drivers/webextension/html/options.html b/src/drivers/webextension/html/options.html
index 23b49fb83..7cd30cee3 100644
--- a/src/drivers/webextension/html/options.html
+++ b/src/drivers/webextension/html/options.html
@@ -1,69 +1,41 @@
-
-
+
- Wappalyzer options
-
-
+
-
+
-
-
-
-
-
-
-
+
+
+
@@ -50,6 +54,9 @@
+
+
+
diff --git a/src/drivers/webextension/js/content.js b/src/drivers/webextension/js/content.js
index 3be7fa391..e637475c5 100644
--- a/src/drivers/webextension/js/content.js
+++ b/src/drivers/webextension/js/content.js
@@ -2,10 +2,10 @@
/* eslint-env browser */
/* globals chrome */
-const port = chrome.runtime.connect({ name: 'content.js' })
+const Content = {
+ port: chrome.runtime.connect({ name: 'content.js' }),
-;(async function() {
- if (typeof chrome !== 'undefined' && typeof document.body !== 'undefined') {
+ async init() {
await new Promise((resolve) => setTimeout(resolve, 1000))
try {
@@ -25,13 +25,13 @@ const port = chrome.runtime.connect({ name: 'content.js' })
html = chunks.join('\n')
- // Scripts
+ // Script tags
const scripts = Array.from(document.scripts)
.filter(({ src }) => src)
.map(({ src }) => src)
.filter((script) => script.indexOf('data:text/javascript;') !== 0)
- // Meta
+ // Meta tags
const meta = Array.from(document.querySelectorAll('meta'))
.map((meta) => ({
key: meta.getAttribute('name') || meta.getAttribute('property'),
@@ -39,61 +39,64 @@ const port = chrome.runtime.connect({ name: 'content.js' })
}))
.filter(({ value }) => value)
- port.postMessage({
+ Content.port.postMessage({
func: 'onContentLoad',
args: [location.href, { html, scripts, meta }]
})
- // JavaScript variables
- const script = document.createElement('script')
-
- script.onload = () => {
- const onMessage = (event) => {
- if (event.data.id !== 'js') {
- return
- }
-
- window.removeEventListener('message', onMessage)
+ Content.port.postMessage({ func: 'getTechnologies' })
+ } catch (error) {
+ Content.port.postMessage({ func: 'error', args: [error, 'content.js'] })
+ }
+ },
- port.postMessage({
- func: 'analyze',
- args: [new URL(location.href), { js: event.data.js }]
- })
+ onGetTechnologies(technologies) {
+ const script = document.createElement('script')
- script.remove()
+ script.onload = () => {
+ const onMessage = ({ data }) => {
+ if (!data.wappalyzer || !data.wappalyzer.js) {
+ return
}
- window.addEventListener('message', onMessage)
+ window.removeEventListener('message', onMessage)
+
+ Content.port.postMessage({
+ func: 'analyzeJs',
+ args: [location.href, data.wappalyzer.js]
+ })
- port.postMessage({ id: 'get_js_patterns' })
+ script.remove()
}
- script.setAttribute('src', chrome.extension.getURL('js/inject.js'))
+ window.addEventListener('message', onMessage)
- document.body.appendChild(script)
- } catch (error) {
- port.postMessage({ func: 'error', args: [error, 'content.js'] })
+ window.postMessage({
+ wappalyzer: {
+ technologies: technologies
+ .filter(({ js }) => Object.keys(js).length)
+ .filter(({ name }) => name === 'jQuery')
+ .map(({ name, js }) => ({ name, chains: Object.keys(js) }))
+ }
+ })
}
+
+ script.setAttribute('src', chrome.extension.getURL('js/inject.js'))
+
+ document.body.appendChild(script)
}
-})()
-
-port.onMessage.addListener((message) => {
- switch (message.id) {
- case 'get_js_patterns':
- postMessage(
- {
- id: 'patterns',
- patterns: message.response.patterns
- },
- window.location.href
- )
-
- break
- default:
- // Do nothing
+}
+
+Content.port.onMessage.addListener(({ func, args }) => {
+ const onFunc = `on${func.charAt(0).toUpperCase() + func.slice(1)}`
+
+ if (Content[onFunc]) {
+ Content[onFunc](args)
}
})
-// https://stackoverflow.com/a/44774834
-// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/executeScript#Return_value
-undefined // eslint-disable-line no-unused-expressions
+if (/complete|interactive|loaded/.test(document.readyState)) {
+ Content.init()
+} else {
+ document.addEventListener('DOMContentLoaded', Content.init)
+}
diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js
index 5671c66ef..0dd587cc3 100644
--- a/src/drivers/webextension/js/driver.js
+++ b/src/drivers/webextension/js/driver.js
@@ -2,7 +2,14 @@
/* eslint-env browser */
/* globals chrome, Wappalyzer, Utils */
-const { setTechnologies, setCategories, analyze, resolve, unique } = Wappalyzer
+const {
+ setTechnologies,
+ setCategories,
+ analyze,
+ analyzeManyToMany,
+ resolve,
+ unique
+} = Wappalyzer
const { promisify, getOption } = Utils
const Driver = {
@@ -52,6 +59,26 @@ const Driver = {
}
},
+ async analyzeJs(href, js) {
+ const url = new URL(href)
+
+ await Driver.onDetect(
+ url,
+ Array.prototype.concat.apply(
+ [],
+ await Promise.all(
+ js.map(({ name, chain, value }) =>
+ analyzeManyToMany(
+ Wappalyzer.technologies.find(({ name: _name }) => name === _name),
+ 'js',
+ { [chain]: [value] }
+ )
+ )
+ )
+ )
+ )
+ },
+
onRuntimeConnect(port) {
port.onMessage.addListener(async ({ func, args }) => {
if (!func) {
@@ -60,6 +87,12 @@ const Driver = {
Driver.log({ port: port.name, func, args })
+ if (!Driver[func]) {
+ Driver.error(new Error(`Method does not exist: Driver.${func}`))
+
+ return
+ }
+
port.postMessage({
func,
args: await Driver[func].call(port.sender, ...(args || []))
@@ -175,7 +208,10 @@ const Driver = {
headers['content-type'] &&
/\/x?html/.test(headers['content-type'][0])
) {
- await Driver.onDetect(url, await analyze(url, { headers }, { tab }))
+ await Driver.onDetect(
+ url,
+ await analyze(url.href, { headers }, { tab })
+ )
}
}
} catch (error) {
@@ -192,12 +228,16 @@ const Driver = {
domain: `.${url.hostname}`
})
- await Driver.onDetect(url, await analyze(url, items))
+ await Driver.onDetect(url, await analyze(href, items))
} catch (error) {
Driver.error(error)
}
},
+ getTechnologies() {
+ return Wappalyzer.technologies
+ },
+
async onDetect(url, detections = []) {
Driver.cache.hostnames[url.hostname] = unique([
...(Driver.cache.hostnames[url.hostname] || []),
diff --git a/src/drivers/webextension/js/inject.js b/src/drivers/webextension/js/inject.js
index e23cb48b9..b0807124e 100644
--- a/src/drivers/webextension/js/inject.js
+++ b/src/drivers/webextension/js/inject.js
@@ -1,62 +1,44 @@
/* eslint-env browser */
-/* eslint-disable no-restricted-globals, no-prototype-builtins */
-;(() => {
+;(function() {
try {
- const detectJs = (chain) => {
- const properties = chain.split('.')
-
- let value = properties.length ? window : null
-
- for (let i = 0; i < properties.length; i += 1) {
- const property = properties[i]
-
- if (value && value.hasOwnProperty(property)) {
- value = value[property]
- } else {
- value = null
-
- break
- }
- }
-
- return typeof value === 'string' || typeof value === 'number'
- ? value
- : !!value
- }
-
- const onMessage = (event) => {
- if (event.data.id !== 'patterns') {
+ const onMessage = ({ data }) => {
+ if (!data.wappalyzer) {
return
}
- removeEventListener('message', onMessage)
-
- const patterns = event.data.patterns || {}
-
- const js = {}
-
- for (const appName in patterns) {
- if (patterns.hasOwnProperty(appName)) {
- js[appName] = {}
+ const { technologies } = data.wappalyzer || {}
- for (const chain in patterns[appName]) {
- if (patterns[appName].hasOwnProperty(chain)) {
- js[appName][chain] = {}
-
- for (const index in patterns[appName][chain]) {
- const value = detectJs(chain)
+ removeEventListener('message', onMessage)
- if (value && patterns[appName][chain].hasOwnProperty(index)) {
- js[appName][chain][index] = value
- }
- }
- }
- }
+ postMessage({
+ wappalyzer: {
+ js: technologies.reduce((results, { name, chains }) => {
+ chains.forEach((chain) => {
+ const value = chain
+ .split('.')
+ .reduce(
+ (value, method) =>
+ value && value.hasOwnProperty(method)
+ ? value[method]
+ : undefined,
+ window
+ )
+
+ technologies.push({
+ name,
+ chain,
+ value:
+ typeof value === 'string' || typeof value === 'number'
+ ? value
+ : !!value
+ })
+ })
+
+ return technologies
+ }, [])
}
- }
-
- postMessage({ id: 'js', js }, window.location.href)
+ })
}
addEventListener('message', onMessage)
diff --git a/src/drivers/webextension/js/options.js b/src/drivers/webextension/js/options.js
index 29d11d5d4..505117b52 100644
--- a/src/drivers/webextension/js/options.js
+++ b/src/drivers/webextension/js/options.js
@@ -1,109 +1,44 @@
-/** global: browser */
-/** global: Wappalyzer */
-/* globals browser Wappalyzer */
+'use strict'
/* eslint-env browser */
+/* globals Utils */
-const wappalyzer = new Wappalyzer()
+const { i18n, getOption, setOption } = Utils
-/**
- * Get a value from localStorage
- */
-function getOption(name, defaultValue = null) {
- return new Promise(async (resolve, reject) => {
- let value = defaultValue
+const Options = {
+ async init() {
+ // Theme mode
+ const themeMode = await getOption('themeMode', false)
- try {
- const option = await browser.storage.local.get(name)
-
- if (option[name] !== undefined) {
- value = option[name]
- }
- } catch (error) {
- wappalyzer.log(error.message, 'driver', 'error')
-
- return reject(error.message)
+ if (themeMode) {
+ document.querySelector('body').classList.add('theme-mode')
}
- return resolve(value)
- })
+ ;[
+ ['upgradeMessage', true],
+ ['dynamicIcon', true],
+ ['tracking', true],
+ ['themeMode', false]
+ ].map(async ([option, defaultValue]) => {
+ const el = document
+ .querySelector(
+ `[data-i18n="option${option.charAt(0).toUpperCase() +
+ option.slice(1)}"]`
+ )
+ .parentNode.querySelector('input')
+
+ el.checked = !!(await getOption(option, defaultValue))
+
+ el.addEventListener('click', async () => {
+ await setOption(option, !!el.checked)
+ })
+ })
+
+ i18n()
+ }
}
-/**
- * Set a value in localStorage
- */
-function setOption(name, value) {
- return new Promise(async (resolve, reject) => {
- try {
- await browser.storage.local.set({ [name]: value })
- } catch (error) {
- wappalyzer.log(error.message, 'driver', 'error')
-
- return reject(error.message)
- }
-
- return resolve()
- })
+if (/complete|interactive|loaded/.test(document.readyState)) {
+ Options.init()
+} else {
+ document.addEventListener('DOMContentLoaded', Options.init)
}
-
-document.addEventListener('DOMContentLoaded', async () => {
- const nodes = document.querySelectorAll('[data-i18n]')
-
- Array.prototype.forEach.call(nodes, (node) => {
- node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n)
- })
-
- document.querySelector('#github').addEventListener('click', () => {
- window.open(wappalyzer.config.githubURL)
- })
-
- document.querySelector('#twitter').addEventListener('click', () => {
- window.open(wappalyzer.config.twitterURL)
- })
-
- document.querySelector('#wappalyzer').addEventListener('click', () => {
- window.open(wappalyzer.config.websiteURL)
- })
-
- let el
- let value
-
- // Upgrade message
- value = await getOption('upgradeMessage', true)
-
- el = document.querySelector('#option-upgrade-message')
-
- el.checked = value
-
- el.addEventListener('change', (e) =>
- setOption('upgradeMessage', e.target.checked)
- )
-
- // Dynamic icon
- value = await getOption('dynamicIcon', true)
-
- el = document.querySelector('#option-dynamic-icon')
-
- el.checked = value
-
- el.addEventListener('change', (e) =>
- setOption('dynamicIcon', e.target.checked)
- )
-
- // Tracking
- value = await getOption('tracking', true)
-
- el = document.querySelector('#option-tracking')
-
- el.checked = value
-
- el.addEventListener('change', (e) => setOption('tracking', e.target.checked))
-
- // Theme Mode
- value = await getOption('themeMode', false)
-
- el = document.querySelector('#option-theme-mode')
-
- el.checked = value
-
- 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 8170aa98c..a58e1cb79 100644
--- a/src/drivers/webextension/js/popup.js
+++ b/src/drivers/webextension/js/popup.js
@@ -2,7 +2,7 @@
/* eslint-env browser */
/* globals chrome, Utils */
-const { agent, getOption, setOption, promisify } = Utils
+const { agent, i18n, getOption, setOption, promisify } = Utils
const Popup = {
port: chrome.runtime.connect({ name: 'popup.js' }),
@@ -37,7 +37,7 @@ const Popup = {
} else {
document.querySelector('.detections').style.display = 'none'
- Popup.i18n()
+ i18n()
}
// Alert
@@ -65,12 +65,6 @@ const Popup = {
Popup.driver('log', message, 'popup.js')
},
- i18n() {
- Array.from(document.querySelectorAll('[data-i18n]')).forEach(
- (node) => (node.innerHTML = chrome.i18n.getMessage(node.dataset.i18n))
- )
- },
-
categorise(technologies) {
return Object.values(
technologies.reduce((categories, technology) => {
@@ -91,6 +85,10 @@ const Popup = {
async onGetDetections(detections) {
const pinnedCategory = await getOption('pinnedCategory')
+ if (detections.length) {
+ document.querySelector('.empty').remove()
+ }
+
Popup.categorise(detections).forEach(
({ id, name, slug: categorySlug, technologies }) => {
const categoryNode = Popup.templates.category.cloneNode(true)
@@ -149,7 +147,7 @@ const Popup = {
a.addEventListener('click', () => Popup.driver('open', a.href))
)
- Popup.i18n()
+ i18n()
}
}
diff --git a/src/drivers/webextension/js/utils.js b/src/drivers/webextension/js/utils.js
index b35190ff3..3f83449d1 100644
--- a/src/drivers/webextension/js/utils.js
+++ b/src/drivers/webextension/js/utils.js
@@ -43,5 +43,11 @@ const Utils = {
} catch (error) {
throw new Error(error.message || error.toString())
}
+ },
+
+ i18n() {
+ Array.from(document.querySelectorAll('[data-i18n]')).forEach(
+ (node) => (node.innerHTML = chrome.i18n.getMessage(node.dataset.i18n))
+ )
}
}