Refactoring

main
Elbert Alias 5 years ago
parent 6b77f4cadf
commit 2a54f1cd4f

@ -8,7 +8,7 @@
"optionUpgradeMessage": { "message": "Tell me about upgrades" }, "optionUpgradeMessage": { "message": "Tell me about upgrades" },
"optionDynamicIcon": { "message": "Use technology icon instead of Wappalyzer logo" }, "optionDynamicIcon": { "message": "Use technology icon instead of Wappalyzer logo" },
"optionTracking": { "message": "Anonymously send identified technologies to wappalyzer.com" }, "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." }, "nothingToDo": { "message": "Nothing to do here." },
"noAppsDetected": { "message": "No technologies detected." }, "noAppsDetected": { "message": "No technologies detected." },
"categoryPin": { "message": "Always show icon" }, "categoryPin": { "message": "Always show icon" },

@ -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; }
}

@ -10,6 +10,7 @@
body { body {
background: #fff; background: #fff;
color: var(--color-text);
direction: __MSG_@@bidi_dir__; direction: __MSG_@@bidi_dir__;
font-family: Helvetica, Arial, sans-serif; font-family: Helvetica, Arial, sans-serif;
font-size: .9rem; font-size: .9rem;
@ -36,7 +37,7 @@ a:hover {
align-items: center; align-items: center;
border-bottom: 1px solid var(--color-secondary); border-bottom: 1px solid var(--color-secondary);
display: flex; display: flex;
height: 4rem; height: 4.5rem;
} }
.header__logo { .header__logo {
@ -79,6 +80,12 @@ a:hover {
padding: 1.5rem 1.5rem .5rem 1.5rem; padding: 1.5rem 1.5rem .5rem 1.5rem;
} }
.empty {
opacity: .3;
padding: 3rem 1.5rem .5rem 1.5rem;
text-align: center;
}
.category { .category {
page-break-inside: avoid; page-break-inside: avoid;
break-inside: avoid-column; break-inside: avoid-column;
@ -189,9 +196,19 @@ a:hover {
margin-top: 1rem; margin-top: 1rem;
} }
.options {
padding: 1.5rem 1.5rem 1rem 1.5rem;
}
.options__label {
display: block;
margin-bottom: .5rem;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body.theme-mode { body.theme-mode {
background: var(--color-primary-darken); background: var(--color-primary-darken);
color: var(--color-text-dark);
} }
.theme-mode a { .theme-mode a {
@ -222,6 +239,10 @@ a:hover {
border-color: var(--color-secondary-dark) border-color: var(--color-secondary-dark)
} }
.theme-mode .footer__settings {
color: var(--color-text-dark);
}
.theme-mode .alerts__icon { .theme-mode .alerts__icon {
color:var(--color-text-dark); color:var(--color-text-dark);
} }

@ -1,69 +1,41 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title data-i18n="options">Wappalyzer options</title> <title data-i18n="options"></title>
<link rel="icon" href="../images/icon_32.png">
<link rel="stylesheet" href="../css/options.css"> <link rel="stylesheet" href="../css/styles.css">
<script src="../node_modules/webextension-polyfill/dist/browser-polyfill.js"></script>
<script src="../js/utils.js"></script> <script src="../js/utils.js"></script>
<script src="../js/options.js"></script> <script src="../js/options.js"></script>
</head> </head>
<body> <body>
<div class="hero"> <div class="options">
<div class="container"> <label class="options__label">
<img src="../images/logo-white.svg"> <input class="options__checkbox" type="checkbox">
</div>
</div>
<div class="content">
<div class="container">
<h1 data-i18n="options">Options</h1>
<p> <span data-i18n="optionUpgradeMessage">&nbsp;</span>
<label for="option-upgrade-message">
<input id="option-upgrade-message" type="checkbox">
<span data-i18n="optionUpgradeMessage">Tell me about upgrades</span>
</label> </label>
<label for="option-dynamic-icon">
<input id="option-dynamic-icon" type="checkbox"> <label class="options__label">
<span data-i18n="optionDynamicIcon">Use application icon instead of Wappalyzer logo</span> <input class="options__checkbox" type="checkbox">
</label>
<label for="option-tracking"> <span data-i18n="optionDynamicIcon">&nbsp;</span>
<input id="option-tracking" type="checkbox">
<span data-i18n="optionTracking">Anonymously send reports on detected applications to wappalyzer.com for research</span>
</label> </label>
<label for="option-theme-mode">
<input id="option-theme-mode" type="checkbox"> <label class="options__label">
<span data-i18n="optionThemeMode">Enable dark mode compatibility</span> <input class="options__checkbox" type="checkbox">
<span data-i18n="optionTracking">&nbsp;</span>
</label> </label>
</p>
<div id="about"> <label class="options__label">
<p> <input class="options__checkbox" type="checkbox">
<button id="github">
<img src="../images/github.png" width="16" height="16" alt="GitHub icon" /> <span data-i18n="optionThemeMode">&nbsp;</span>
<span data-i18n="github">Fork Wappalyzer on GitHub!</span> </label>
</button>
<button id="twitter">
<img src="../images/twitter.png" width="16" height="16" alt="Twitter icon" />
<span data-i18n="twitter">Follow Wappalyzer on Twitter</span>
</button>
<button id="wappalyzer">
<img src="../images/icon_16.png" width="16" height="16" alt="Wappalyzer icon" />
<span data-i18n="website">Go to wappalyzer.com</span>
</button>
</p>
</div>
</div>
</div> </div>
</body> </body>
</html> </html>

@ -4,7 +4,9 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../css/popup.css"> <title data-i18n="options"></title>
<link rel="stylesheet" href="../css/styles.css">
<script src="../js/utils.js"></script> <script src="../js/utils.js"></script>
<script src="../js/popup.js"></script> <script src="../js/popup.js"></script>
@ -25,6 +27,8 @@
<a class="terms__privacy" href="https://www.wappalyzer.com/privacy" data-i18n="privacyPolicy"></a> <a class="terms__privacy" href="https://www.wappalyzer.com/privacy" data-i18n="privacyPolicy"></a>
</div> </div>
<div class="empty" data-i18n="noAppsDetected"></div>
<div class="detections"></div> <div class="detections"></div>
<div data-template="category" class="category"> <div data-template="category" class="category">
@ -50,6 +54,9 @@
<img class="technology__icon" alt="" src="../images/icons/default.svg" /> <img class="technology__icon" alt="" src="../images/icons/default.svg" />
<a class="technology__link" href="#"></a> <a class="technology__link" href="#"></a>
<span class="technology__confidence">&nbsp;</span>
<span class="technology__version">&nbsp;</span>
</div> </div>
</div> </div>

@ -2,10 +2,10 @@
/* eslint-env browser */ /* eslint-env browser */
/* globals chrome */ /* globals chrome */
const port = chrome.runtime.connect({ name: 'content.js' }) const Content = {
port: chrome.runtime.connect({ name: 'content.js' }),
;(async function() { async init() {
if (typeof chrome !== 'undefined' && typeof document.body !== 'undefined') {
await new Promise((resolve) => setTimeout(resolve, 1000)) await new Promise((resolve) => setTimeout(resolve, 1000))
try { try {
@ -25,13 +25,13 @@ const port = chrome.runtime.connect({ name: 'content.js' })
html = chunks.join('\n') html = chunks.join('\n')
// Scripts // Script tags
const scripts = Array.from(document.scripts) const scripts = Array.from(document.scripts)
.filter(({ src }) => src) .filter(({ src }) => src)
.map(({ src }) => src) .map(({ src }) => src)
.filter((script) => script.indexOf('data:text/javascript;') !== 0) .filter((script) => script.indexOf('data:text/javascript;') !== 0)
// Meta // Meta tags
const meta = Array.from(document.querySelectorAll('meta')) const meta = Array.from(document.querySelectorAll('meta'))
.map((meta) => ({ .map((meta) => ({
key: meta.getAttribute('name') || meta.getAttribute('property'), key: meta.getAttribute('name') || meta.getAttribute('property'),
@ -39,25 +39,31 @@ const port = chrome.runtime.connect({ name: 'content.js' })
})) }))
.filter(({ value }) => value) .filter(({ value }) => value)
port.postMessage({ Content.port.postMessage({
func: 'onContentLoad', func: 'onContentLoad',
args: [location.href, { html, scripts, meta }] args: [location.href, { html, scripts, meta }]
}) })
// JavaScript variables Content.port.postMessage({ func: 'getTechnologies' })
} catch (error) {
Content.port.postMessage({ func: 'error', args: [error, 'content.js'] })
}
},
onGetTechnologies(technologies) {
const script = document.createElement('script') const script = document.createElement('script')
script.onload = () => { script.onload = () => {
const onMessage = (event) => { const onMessage = ({ data }) => {
if (event.data.id !== 'js') { if (!data.wappalyzer || !data.wappalyzer.js) {
return return
} }
window.removeEventListener('message', onMessage) window.removeEventListener('message', onMessage)
port.postMessage({ Content.port.postMessage({
func: 'analyze', func: 'analyzeJs',
args: [new URL(location.href), { js: event.data.js }] args: [location.href, data.wappalyzer.js]
}) })
script.remove() script.remove()
@ -65,35 +71,32 @@ const port = chrome.runtime.connect({ name: 'content.js' })
window.addEventListener('message', onMessage) window.addEventListener('message', onMessage)
port.postMessage({ id: 'get_js_patterns' }) 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')) script.setAttribute('src', chrome.extension.getURL('js/inject.js'))
document.body.appendChild(script) document.body.appendChild(script)
} catch (error) {
port.postMessage({ func: 'error', args: [error, 'content.js'] })
} }
} }
})()
port.onMessage.addListener((message) => {
switch (message.id) {
case 'get_js_patterns':
postMessage(
{
id: 'patterns',
patterns: message.response.patterns
},
window.location.href
)
break Content.port.onMessage.addListener(({ func, args }) => {
default: const onFunc = `on${func.charAt(0).toUpperCase() + func.slice(1)}`
// Do nothing
if (Content[onFunc]) {
Content[onFunc](args)
} }
}) })
// https://stackoverflow.com/a/44774834 if (/complete|interactive|loaded/.test(document.readyState)) {
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/executeScript#Return_value Content.init()
undefined // eslint-disable-line no-unused-expressions } else {
document.addEventListener('DOMContentLoaded', Content.init)
}

@ -2,7 +2,14 @@
/* eslint-env browser */ /* eslint-env browser */
/* globals chrome, Wappalyzer, Utils */ /* globals chrome, Wappalyzer, Utils */
const { setTechnologies, setCategories, analyze, resolve, unique } = Wappalyzer const {
setTechnologies,
setCategories,
analyze,
analyzeManyToMany,
resolve,
unique
} = Wappalyzer
const { promisify, getOption } = Utils const { promisify, getOption } = Utils
const Driver = { 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) { onRuntimeConnect(port) {
port.onMessage.addListener(async ({ func, args }) => { port.onMessage.addListener(async ({ func, args }) => {
if (!func) { if (!func) {
@ -60,6 +87,12 @@ const Driver = {
Driver.log({ port: port.name, func, args }) Driver.log({ port: port.name, func, args })
if (!Driver[func]) {
Driver.error(new Error(`Method does not exist: Driver.${func}`))
return
}
port.postMessage({ port.postMessage({
func, func,
args: await Driver[func].call(port.sender, ...(args || [])) args: await Driver[func].call(port.sender, ...(args || []))
@ -175,7 +208,10 @@ const Driver = {
headers['content-type'] && headers['content-type'] &&
/\/x?html/.test(headers['content-type'][0]) /\/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) { } catch (error) {
@ -192,12 +228,16 @@ const Driver = {
domain: `.${url.hostname}` domain: `.${url.hostname}`
}) })
await Driver.onDetect(url, await analyze(url, items)) await Driver.onDetect(url, await analyze(href, items))
} catch (error) { } catch (error) {
Driver.error(error) Driver.error(error)
} }
}, },
getTechnologies() {
return Wappalyzer.technologies
},
async onDetect(url, detections = []) { async onDetect(url, detections = []) {
Driver.cache.hostnames[url.hostname] = unique([ Driver.cache.hostnames[url.hostname] = unique([
...(Driver.cache.hostnames[url.hostname] || []), ...(Driver.cache.hostnames[url.hostname] || []),

@ -1,62 +1,44 @@
/* eslint-env browser */ /* eslint-env browser */
/* eslint-disable no-restricted-globals, no-prototype-builtins */
;(() => { ;(function() {
try { try {
const detectJs = (chain) => { const onMessage = ({ data }) => {
const properties = chain.split('.') if (!data.wappalyzer) {
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') {
return return
} }
removeEventListener('message', onMessage) const { technologies } = data.wappalyzer || {}
const patterns = event.data.patterns || {}
const js = {}
for (const appName in patterns) {
if (patterns.hasOwnProperty(appName)) {
js[appName] = {}
for (const chain in patterns[appName]) { removeEventListener('message', onMessage)
if (patterns[appName].hasOwnProperty(chain)) {
js[appName][chain] = {}
for (const index in patterns[appName][chain]) { postMessage({
const value = detectJs(chain) 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
})
})
if (value && patterns[appName][chain].hasOwnProperty(index)) { return technologies
js[appName][chain][index] = value }, [])
} }
} })
}
}
}
}
postMessage({ id: 'js', js }, window.location.href)
} }
addEventListener('message', onMessage) addEventListener('message', onMessage)

@ -1,109 +1,44 @@
/** global: browser */ 'use strict'
/** global: Wappalyzer */
/* globals browser Wappalyzer */
/* eslint-env browser */ /* eslint-env browser */
/* globals Utils */
const wappalyzer = new Wappalyzer() const { i18n, getOption, setOption } = Utils
/** const Options = {
* Get a value from localStorage async init() {
*/ // Theme mode
function getOption(name, defaultValue = null) { const themeMode = await getOption('themeMode', false)
return new Promise(async (resolve, reject) => {
let value = defaultValue
try { if (themeMode) {
const option = await browser.storage.local.get(name) document.querySelector('body').classList.add('theme-mode')
if (option[name] !== undefined) {
value = option[name]
}
} catch (error) {
wappalyzer.log(error.message, 'driver', 'error')
return reject(error.message)
}
return resolve(value)
})
} }
/** ;[
* Set a value in localStorage ['upgradeMessage', true],
*/ ['dynamicIcon', true],
function setOption(name, value) { ['tracking', true],
return new Promise(async (resolve, reject) => { ['themeMode', false]
try { ].map(async ([option, defaultValue]) => {
await browser.storage.local.set({ [name]: value }) const el = document
} catch (error) { .querySelector(
wappalyzer.log(error.message, 'driver', 'error') `[data-i18n="option${option.charAt(0).toUpperCase() +
option.slice(1)}"]`
return reject(error.message) )
} .parentNode.querySelector('input')
return resolve()
})
}
document.addEventListener('DOMContentLoaded', async () => { el.checked = !!(await getOption(option, defaultValue))
const nodes = document.querySelectorAll('[data-i18n]')
Array.prototype.forEach.call(nodes, (node) => { el.addEventListener('click', async () => {
node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n) await setOption(option, !!el.checked)
}) })
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', () => { i18n()
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)) if (/complete|interactive|loaded/.test(document.readyState)) {
}) Options.init()
} else {
document.addEventListener('DOMContentLoaded', Options.init)
}

@ -2,7 +2,7 @@
/* eslint-env browser */ /* eslint-env browser */
/* globals chrome, Utils */ /* globals chrome, Utils */
const { agent, getOption, setOption, promisify } = Utils const { agent, i18n, getOption, setOption, promisify } = Utils
const Popup = { const Popup = {
port: chrome.runtime.connect({ name: 'popup.js' }), port: chrome.runtime.connect({ name: 'popup.js' }),
@ -37,7 +37,7 @@ const Popup = {
} else { } else {
document.querySelector('.detections').style.display = 'none' document.querySelector('.detections').style.display = 'none'
Popup.i18n() i18n()
} }
// Alert // Alert
@ -65,12 +65,6 @@ const Popup = {
Popup.driver('log', message, 'popup.js') 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) { categorise(technologies) {
return Object.values( return Object.values(
technologies.reduce((categories, technology) => { technologies.reduce((categories, technology) => {
@ -91,6 +85,10 @@ const Popup = {
async onGetDetections(detections) { async onGetDetections(detections) {
const pinnedCategory = await getOption('pinnedCategory') const pinnedCategory = await getOption('pinnedCategory')
if (detections.length) {
document.querySelector('.empty').remove()
}
Popup.categorise(detections).forEach( Popup.categorise(detections).forEach(
({ id, name, slug: categorySlug, technologies }) => { ({ id, name, slug: categorySlug, technologies }) => {
const categoryNode = Popup.templates.category.cloneNode(true) const categoryNode = Popup.templates.category.cloneNode(true)
@ -149,7 +147,7 @@ const Popup = {
a.addEventListener('click', () => Popup.driver('open', a.href)) a.addEventListener('click', () => Popup.driver('open', a.href))
) )
Popup.i18n() i18n()
} }
} }

@ -43,5 +43,11 @@ const Utils = {
} catch (error) { } catch (error) {
throw new Error(error.message || error.toString()) throw new Error(error.message || error.toString())
} }
},
i18n() {
Array.from(document.querySelectorAll('[data-i18n]')).forEach(
(node) => (node.innerHTML = chrome.i18n.getMessage(node.dataset.i18n))
)
} }
} }

Loading…
Cancel
Save