Add ability to pin a category

main
Elbert Alias 8 years ago
parent 0dcb78a8bd
commit 8a5bc035ce

@ -16,7 +16,7 @@
"required": true,
"properties": {
"priority": {
"type": "string"
"type": "number"
},
"name": {
"type": "string"
@ -34,7 +34,7 @@
"cats": {
"type": "array",
"items": {
"type": "string"
"type": "number"
},
"required": true
},

File diff suppressed because it is too large Load Diff

@ -1,313 +0,0 @@
'use strict';
const Wappalyzer = require('./wappalyzer');
const request = require('request');
const url = require('url');
const fs = require('fs');
const Browser = require('zombie');
const json = JSON.parse(fs.readFileSync(__dirname + '/apps.json'));
const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/;
class Driver {
constructor(pageUrl, options) {
this.options = Object.assign({}, {
chunkSize: 5,
debug: false,
delay: 500,
maxDepth: 3,
maxUrls: 10,
maxWait: 5000,
recursive: false,
userAgent: 'Mozilla/5.0 (compatible; Wappalyzer)',
}, options || {});
this.options.debug = Boolean(this.options.debug);
this.options.delay = this.options.recursive ? parseInt(this.options.delay, 10) : 0;
this.options.maxDepth = parseInt(this.options.maxDepth, 10);
this.options.maxUrls = parseInt(this.options.maxUrls, 10);
this.options.maxWait = parseInt(this.options.maxWait, 10);
this.options.recursive = Boolean(this.options.recursive);
this.origPageUrl = url.parse(pageUrl);
this.analyzedPageUrls = [];
this.apps = [];
this.meta = {};
this.wappalyzer = new Wappalyzer();
this.wappalyzer.apps = json.apps;
this.wappalyzer.categories = json.categories;
this.wappalyzer.parseJsPatterns();
this.wappalyzer.driver.log = (message, source, type) => this.log(message, source, type);
this.wappalyzer.driver.displayApps = (detected, meta, context) => this.displayApps(detected, meta, context);
}
analyze() {
this.time = {
start: new Date().getTime(),
last: new Date().getTime(),
}
return this.crawl(this.origPageUrl);
}
log(message, source, type) {
this.options.debug && console.log('[wappalyzer ' + type + ']', '[' + source + ']', message);
}
displayApps(detected, meta) {
this.meta = meta;
Object.keys(detected).forEach(appName => {
const app = detected[appName];
var categories = [];
app.props.cats.forEach(id => {
var category = {};
category[id] = json.categories[id].name;
categories.push(category)
});
if ( !this.apps.some(detectedApp => detectedApp.name === app.name) ) {
this.apps.push({
name: app.name,
confidence: app.confidenceTotal.toString(),
version: app.version,
icon: app.props.icon || 'default.svg',
website: app.props.website,
categories
});
}
});
}
fetch(pageUrl, index, depth) {
// Return when the URL is a duplicate or maxUrls has been reached
if ( this.analyzedPageUrls.indexOf(pageUrl.href) !== -1 || this.analyzedPageUrls.length >= this.options.maxUrls ) {
return Promise.resolve();
}
this.analyzedPageUrls.push(pageUrl.href);
const timerScope = {
last: new Date().getTime()
};
this.timer('fetch; url: ' + pageUrl.href + '; depth: ' + depth + '; delay: ' + ( this.options.delay * index ) + 'ms', timerScope);
return new Promise(resolve => this.sleep(this.options.delay * index).then(() => this.visit(pageUrl, timerScope, resolve)));
}
visit(pageUrl, timerScope, resolve) {
const browser = new Browser({
silent: true,
userAgent: this.options.userAgent,
waitDuration: this.options.maxWait,
});
this.timer('browser.visit start; url: ' + pageUrl.href, timerScope);
browser.visit(pageUrl.href, () => {
this.timer('browser.visit end; url: ' + pageUrl.href, timerScope);
if ( !this.responseOk(browser, pageUrl) ) {
return resolve();
}
const headers = this.getHeaders(browser);
const html = this.getHtml(browser);
const scripts = this.getScripts(browser);
const js = this.getJs(browser);
this.wappalyzer.analyze(pageUrl, {
headers,
html,
scripts,
js
});
const links = Array.from(browser.document.getElementsByTagName('a'))
.filter(link => link.protocol === 'http:' || link.protocol === 'https:')
.filter(link => link.hostname === this.origPageUrl.hostname)
.filter(link => extensions.test(link.pathname))
.map(link => { link.hash = ''; return url.parse(link.href) });
return resolve(links);
});
}
responseOk(browser, pageUrl) {
// Validate response
const resource = browser.resources.length ? browser.resources.filter(resource => resource.response).shift() : null;
if ( !resource ) {
this.wappalyzer.log('No response from server; url: ' + pageUrl.href, 'driver', 'error');
return false;
}
if ( resource.response.status !== 200 ) {
this.wappalyzer.log('Response was not OK; status: ' + resource.response.status + ' ' + resource.response.statusText + '; url: ' + pageUrl.href, 'driver', 'error');
return false;
}
const headers = this.getHeaders(browser);
// Validate content type
const contentType = headers.hasOwnProperty('content-type') ? headers['content-type'].shift() : null;
if ( !contentType || !/\btext\/html\b/.test(contentType) ) {
this.wappalyzer.log('Skipping; url: ' + pageUrl.href + '; content type: ' + contentType, 'driver');
this.analyzedPageUrls.splice(this.analyzedPageUrls.indexOf(pageUrl.href), 1);
return false;
}
// Validate document
if ( !browser.document || !browser.document.documentElement ) {
this.wappalyzer.log('No HTML document; url: ' + pageUrl.href, 'driver', 'error');
return false;
}
return true;
}
getHeaders(browser) {
const headers = {};
const resource = browser.resources.length ? browser.resources.filter(resource => resource.response).shift() : null;
if ( resource ) {
resource.response.headers._headers.forEach(header => {
if ( !headers[header[0]] ){
headers[header[0]] = [];
}
headers[header[0]].push(header[1]);
});
}
return headers;
}
getHtml(browser) {
let html = '';
try {
html = browser.html();
if ( html.length > 50000 ) {
html = html.substring(0, 25000) + html.substring(html.length - 25000, html.length);
}
} catch ( error ) {
this.wappalyzer.log(error.message, 'browser', 'error');
}
return html;
}
getScripts(browser) {
if ( !browser.document || !browser.document.scripts ) {
return [];
}
const scripts = Array.prototype.slice
.apply(browser.document.scripts)
.filter(script => script.src)
.map(script => script.src);
return scripts;
}
getJs(browser) {
const patterns = this.wappalyzer.jsPatterns;
const js = {};
Object.keys(patterns).forEach(appName => {
js[appName] = {};
Object.keys(patterns[appName]).forEach(chain => {
js[appName][chain] = {};
patterns[appName][chain].forEach((pattern, index) => {
const properties = chain.split('.');
let value = properties.reduce((parent, property) => {
return parent && parent.hasOwnProperty(property) ? parent[property] : null;
}, browser.window);
value = typeof value === 'string' ? value : !!value;
if ( value ) {
js[appName][chain][index] = value;
}
});
});
});
return js;
}
crawl(pageUrl, index, depth = 1) {
pageUrl.canonical = pageUrl.protocol + '//' + pageUrl.host + pageUrl.pathname;
return new Promise(resolve => {
this.fetch(pageUrl, index, depth)
.catch(() => {})
.then(links => {
if ( links && Boolean(this.options.recursive) && depth < this.options.maxDepth ) {
return this.chunk(links.slice(0, this.options.maxUrls), depth + 1);
} else {
return Promise.resolve();
}
})
.then(() => {
resolve({
urls: this.analyzedPageUrls,
applications: this.apps,
meta: this.meta
});
});
});
}
chunk(links, depth, chunk = 0) {
if ( links.length === 0 ) {
return Promise.resolve();
}
const chunked = links.splice(0, this.options.chunkSize);
return new Promise(resolve => {
Promise.all(chunked.map((link, index) => this.crawl(link, index, depth)))
.then(() => this.chunk(links, depth, chunk + 1))
.then(() => resolve());
});
}
sleep(ms) {
return ms ? new Promise(resolve => setTimeout(resolve, ms)) : Promise.resolve();
}
timer(message, scope) {
const time = new Date().getTime();
const sinceStart = ( Math.round(( time - this.time.start ) / 10) / 100) + 's';
const sinceLast = ( Math.round(( time - scope.last ) / 10) / 100) + 's';
this.wappalyzer.log('[timer] ' + message + '; lapsed: ' + sinceLast + ' / ' + sinceStart, 'driver');
scope.last = time;
}
};
module.exports = Driver;

@ -10,6 +10,7 @@
"optionTracking": { "message": "Anonyme Statistiken an wappalyzer.com übermitteln" },
"nothingToDo": { "message": "Nichts zu tun." },
"noAppsDetected": { "message": "Keine Applikation entdeckt." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Nachrichten Board" },
"categoryName3": { "message": "Datenbankverwaltung" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Ανώνυμη αποστολή αναφορών για εντοπισμένες εφαρμογές στο wappalyzer.com για έρευνα" },
"nothingToDo": { "message": "Καμία ενέργεια." },
"noAppsDetected": { "message": "Δεν ανιχνεύθηκαν εφαρμογές." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Διαδικτυακό Φόρουμ" },
"categoryName3": { "message": "Διαχειριστής Βάσης Δεδομένων" },

@ -6,10 +6,11 @@
"optionsSave": { "message": "Save options" },
"optionsSaved": { "message": "Saved" },
"optionUpgradeMessage": { "message": "Tell me about upgrades" },
"optionDynamicIcon": { "message": "Use application icon instead of Wappalyzer logo" },
"optionTracking": { "message": "Anonymously send reports on detected applications to wappalyzer.com for research" },
"optionDynamicIcon": { "message": "Use technology icon instead of Wappalyzer logo" },
"optionTracking": { "message": "Anonymously send identified technologies to wappalyzer.com" },
"nothingToDo": { "message": "Nothing to do here." },
"noAppsDetected": { "message": "No applications detected." },
"noAppsDetected": { "message": "No technologies detected." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Message Board" },
"categoryName3": { "message": "Database Manager" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Enviar informes anónimos sobre las aplicaciones detectadas a wappalyzer.com para análisis" },
"nothingToDo": { "message": "Nada que hacer aquí." },
"noAppsDetected": { "message": "Aplicaciones no detectadas." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "Gestor de Contenido" },
"categoryName2": { "message": "Foro" },
"categoryName3": { "message": "Gestor de Bases de Datos" },

@ -10,6 +10,7 @@
"optionsSaved": { "message": "Sauvegardé" },
"twitter": { "message": "Suivre Wappalyzer sur Twitter" },
"website": { "message": "Aller sur wappalyzer.com" },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Forum" },
"categoryName3": { "message": "Gestionnaire de base de données" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Ανώνυμη αποστολή αναφορών για εντοπισμένες εφαρμογές στο wappalyzer.com για έρευνα" },
"nothingToDo": { "message": "Καμία ενέργεια." },
"noAppsDetected": { "message": "Δεν ανιχνεύθηκαν εφαρμογές." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Διαδικτυακό Φόρουμ" },
"categoryName3": { "message": "Διαχειριστής Βάσης Δεδομένων" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Secara anonim kirimkan laporan tentang aplikasi yang terdeteksi ke wappalyzer.com untuk penelitian" },
"nothingToDo": { "message": "Tak ada yang dilakukan disini." },
"noAppsDetected": { "message": "Tidak ada aplikasi yang terdeteksi." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "Sistem Pengelola Konten" },
"categoryName2": { "message": "Papan Pesan" },
"categoryName3": { "message": "Pengelola Basis Data" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Inviare anonimamente un report sulle applicazioni rilevate a wappalyzer.com per l'analisi" },
"nothingToDo": { "message": "Niente da fare qui." },
"noAppsDetected": { "message": "Nessuna applicazione rilevata." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Forum" },
"categoryName3": { "message": "Gestore di Database" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Przesyłaj anonimowe statystyki aplikacji wykrytych przez Wappalyzer do twórców" },
"nothingToDo": { "message": "Nic tu nie ma." },
"noAppsDetected": { "message": "Nie wykryto żadnych aplikacji." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "System zarządzania treścią" },
"categoryName2": { "message": "Forum" },
"categoryName3": { "message": "Menedżer baz danych" },

@ -10,6 +10,7 @@
"optionsSaved": { "message": "Guardado" },
"twitter": { "message": "Seguir o Wappalyzer no Twitter" },
"website": { "message": "Ir para wappalyzer.com" },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "Sistema de gerenciamento de conteudo(CMS)" },
"categoryName2": { "message": "Forum" },
"categoryName3": { "message": "Gestor de base de dados" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Trimite rapoarte anonime despre aplicațiile detectate către wappalyzer.com pentru cercetare" },
"nothingToDo": { "message": "Nimic de făcut pe pagina curentă." },
"noAppsDetected": { "message": "Nici o aplicație detectată." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Forum de discuții" },
"categoryName3": { "message": "Manager baze de date" },

@ -63,5 +63,6 @@
"optionsSaved" : { "message" : "Успешно сохранено!" },
"twitter" : { "message" : "Следите за новостями в Твиттере" },
"website" : { "message" : "Перейти на Wappalyzer.com" },
"categoryPin": { "message": "Always show icon" },
"categoryName54": { "message": "SEO" }
}

@ -10,6 +10,7 @@
"optionTracking": { "message": "Anonymne posielať správy o zistených aplikáciách na wappalyzer.com pre výskum" },
"nothingToDo": { "message": "Nie je tu čo robiť." },
"noAppsDetected": { "message": "Žiadne aplikácie neboli zistené." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Message Board" },
"categoryName3": { "message": "Správca databáz" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Anonim olarak tespit edilen uygulamalar hakkında wappalyzer.com'a araştırma raporları gönderin" },
"nothingToDo": { "message": "Burada yapacak birşey yok." },
"noAppsDetected": { "message": "Uygulamalar tespit edilemedi." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Mesaj Tahtası" },
"categoryName3": { "message": "Veritabanı Yöneticisi" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Анонімно надсилати статистику розпізнавань на сервер для досліджень" },
"nothingToDo": { "message": "Тут нічого робити." },
"noAppsDetected": { "message": "Нічого не знайдено." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS" },
"categoryName2": { "message": "Форум" },
"categoryName3": { "message": "Менеджер БД" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "Wappalyzer takomillashtirish uchun hisobotlarni maxfiy ravishda serverga jo'natish" },
"nothingToDo": { "message": "Bu yerda tekshirib bolmaydi." },
"noAppsDetected": { "message": "Hech qanday dastur aniqlanmadi." },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "CMS (KBT)" },
"categoryName2": { "message": "Forum" },
"categoryName3": { "message": "MB boshqaruvi" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "发送匿名的应用报告到 wappalyzer.com 供研究使用" },
"nothingToDo": { "message": "这儿啥也没有。" },
"noAppsDetected": { "message": "未检测到任何应用。" },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "内容管理系统CMS" },
"categoryName2": { "message": "消息板" },
"categoryName3": { "message": "数据库管理器" },

@ -10,6 +10,7 @@
"optionTracking": { "message": "匿名傳送應用程式偵測報告至 wappalyzer.com 作為研究用途" },
"nothingToDo": { "message": "這裡什麼也沒有。" },
"noAppsDetected": { "message": "未偵測到應用程式。" },
"categoryPin": { "message": "Always show icon" },
"categoryName1": { "message": "內容管理系統CMS" },
"categoryName2": { "message": "留言板/討論區" },
"categoryName3": { "message": "資料庫管理" },

@ -46,24 +46,50 @@ body {
padding-bottom: 1rem;
}
.detected__category-link {
.detected__category-name {
border-bottom: 1px solid #dbdbdb;
display: block;
margin-bottom: .5rem;
text-decoration: none;
}
.detected__category-name {
.detected__category-link {
color: #4608ad;
display: block;
font-weight: bold;
line-height: 2rem;
text-decoration: none;
}
.detected__category-link:hover .detected__category-name {
.detected__category-link:hover {
color: #4a4a4a;
}
.detected__category-pin-wrapper {
margin-left: .2rem;
}
.detected__category-pin {
cursor: pointer;
display: none;
height: 16px;
margin-left: .2rem;
width: 16px;
vertical-align: middle;
}
.detected__category:hover .detected__category-pin--inactive {
display: inline-block;
}
.detected__category-pin-wrapper--active .detected__category-pin--inactive,
.detected__category-pin-wrapper:hover .detected__category-pin--inactive {
display: none !important;
}
.detected__category-pin-wrapper--active .detected__category-pin--active,
.detected__category-pin-wrapper:hover .detected__category-pin--active {
display: inline-block;
}
.detected__app {
display: block;
line-height: 1.7rem;

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 193.826 193.826" style="enable-background:new 0 0 193.826 193.826;" xml:space="preserve">
<path fill="#4608ad" d="M191.495,55.511L137.449,1.465c-1.951-1.953-5.119-1.953-7.07,0l-0.229,0.229c-3.314,3.313-5.14,7.72-5.14,12.406
c0,3.019,0.767,5.916,2.192,8.485l-56.55,48.533c-4.328-3.868-9.852-5.985-15.703-5.985c-6.305,0-12.232,2.455-16.689,6.913
l-0.339,0.339c-1.953,1.952-1.953,5.118,0,7.07l32.378,32.378l-31.534,31.533c-0.631,0.649-15.557,16.03-25.37,28.27
c-9.345,11.653-11.193,13.788-11.289,13.898c-1.735,1.976-1.639,4.956,0.218,6.822c0.973,0.977,2.256,1.471,3.543,1.471
c1.173,0,2.349-0.41,3.295-1.237c0.083-0.072,2.169-1.885,13.898-11.289c12.238-9.813,27.619-24.74,28.318-25.421l31.483-31.483
l30.644,30.644c0.976,0.977,2.256,1.465,3.535,1.465s2.56-0.488,3.535-1.465l0.339-0.339c4.458-4.457,6.913-10.385,6.913-16.689
c0-5.851-2.118-11.375-5.985-15.703l48.533-56.55c2.569,1.425,5.466,2.192,8.485,2.192c4.687,0,9.093-1.825,12.406-5.14l0.229-0.229
C193.448,60.629,193.448,57.463,191.495,55.511z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 193.826 193.826" style="enable-background:new 0 0 193.826 193.826;" xml:space="preserve">
<path fill="#dbdbdb" d="M191.495,55.511L137.449,1.465c-1.951-1.953-5.119-1.953-7.07,0l-0.229,0.229c-3.314,3.313-5.14,7.72-5.14,12.406
c0,3.019,0.767,5.916,2.192,8.485l-56.55,48.533c-4.328-3.868-9.852-5.985-15.703-5.985c-6.305,0-12.232,2.455-16.689,6.913
l-0.339,0.339c-1.953,1.952-1.953,5.118,0,7.07l32.378,32.378l-31.534,31.533c-0.631,0.649-15.557,16.03-25.37,28.27
c-9.345,11.653-11.193,13.788-11.289,13.898c-1.735,1.976-1.639,4.956,0.218,6.822c0.973,0.977,2.256,1.471,3.543,1.471
c1.173,0,2.349-0.41,3.295-1.237c0.083-0.072,2.169-1.885,13.898-11.289c12.238-9.813,27.619-24.74,28.318-25.421l31.483-31.483
l30.644,30.644c0.976,0.977,2.256,1.465,3.535,1.465s2.56-0.488,3.535-1.465l0.339-0.339c4.458-4.457,6.913-10.385,6.913-16.689
c0-5.851-2.118-11.375-5.985-15.703l48.533-56.55c2.569,1.425,5.466,2.192,8.485,2.192c4.687,0,9.093-1.825,12.406-5.14l0.229-0.229
C193.448,60.629,193.448,57.463,191.495,55.511z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -10,6 +10,7 @@ const wappalyzer = new Wappalyzer();
var tabCache = {};
var headersCache = {};
var categoryOrder = [];
var options = {};
browser.tabs.onRemoved.addListener(tabId => {
tabCache[tabId] = null;
@ -21,7 +22,9 @@ browser.tabs.onRemoved.addListener(tabId => {
function getOption(name, defaultValue) {
return new Promise((resolve, reject) => {
const callback = item => {
resolve(item.hasOwnProperty(name) ? item[name] : defaultValue);
options[name] = item.hasOwnProperty(name) ? item[name] : defaultValue;
resolve(options[name]);
};
browser.storage.local.get(name)
@ -39,6 +42,8 @@ function setOption(name, value) {
option[name] = value;
browser.storage.local.set(option);
options[name] = value;
}
/**
@ -75,9 +80,9 @@ fetch('../apps.json')
wappalyzer.apps = json.apps;
wappalyzer.categories = json.categories;
categoryOrder = Object.keys(wappalyzer.categories).sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority);
wappalyzer.parseJsPatterns();
categoryOrder = Object.keys(wappalyzer.categories)
.map(categoryId => parseInt(categoryId, 10))
.sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority);
})
.catch(error => {
wappalyzer.log('GET apps.json: ' + error, 'driver', 'error');
@ -107,6 +112,9 @@ getOption('version')
setOption('version', version);
});
getOption('dynamicIcon', true);
getOption('pinnedCategory');
// Run content script
var callback = tabs => {
tabs.forEach(tab => {
@ -186,9 +194,14 @@ browser.webRequest.onCompleted.addListener(request => {
response = {
tabCache: tabCache[message.tab.id],
apps: wappalyzer.apps,
categories: wappalyzer.categories
categories: wappalyzer.categories,
pinnedCategory: options.pinnedCategory,
};
break;
case 'set_option':
setOption(message.key, message.value);
break;
case 'init_js':
response = {
@ -223,12 +236,10 @@ wappalyzer.driver.displayApps = (detected, meta, context) => {
tabCache[tab.id].detected = detected;
if ( Object.keys(detected).length ) {
getOption('dynamicIcon', true)
.then(dynamicIcon => {
var appName, found = false;
// Find the main application to display
categoryOrder.forEach(match => {
[ options.pinnedCategory ].concat(categoryOrder).forEach(match => {
Object.keys(detected).forEach(appName => {
var app = detected[appName];
@ -236,7 +247,7 @@ wappalyzer.driver.displayApps = (detected, meta, context) => {
if ( category === match && !found ) {
var icon = app.props.icon || 'default.svg';
if ( !dynamicIcon ) {
if ( !options.dynamicIcon ) {
icon = 'default.svg';
}
@ -265,7 +276,6 @@ wappalyzer.driver.displayApps = (detected, meta, context) => {
} else {
browser.pageAction.show(tab.id);
}
});
}
};

@ -1,12 +1,16 @@
/** global: chrome */
/** global: browser */
var pinnedCategory = null;
var func = tabs => {
( chrome || browser ).runtime.sendMessage({
id: 'get_apps',
tab: tabs[0],
source: 'popup.js'
}, response => {
pinnedCategory = response.pinnedCategory;
replaceDomWhenReady(appsToDomTemplate(response));
});
};
@ -39,6 +43,34 @@ function replaceDom(domTemplate) {
Array.prototype.forEach.call(nodes, node => {
node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n);
});
Array.from(document.querySelectorAll('.detected__category-pin-wrapper')).forEach(pin => {
pin.addEventListener('click', event => {
const categoryId = parseInt(pin.dataset.categoryId, 10);
if ( categoryId === pinnedCategory ) {
pin.className = 'detected__category-pin-wrapper';
pinnedCategory = null;
} else {
const active = document.querySelector('.detected__category-pin-wrapper--active');
if ( active ) {
active.className = 'detected__category-pin-wrapper';
}
pin.className = 'detected__category-pin-wrapper detected__category-pin-wrapper--active';
pinnedCategory = categoryId;
}
( chrome || browser ).runtime.sendMessage({
id: 'set_option',
key: 'pinnedCategory',
value: pinnedCategory,
});
});
});
}
function appsToDomTemplate(response) {
@ -90,16 +122,32 @@ function appsToDomTemplate(response) {
[
'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-name'
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'
},
browser.i18n.getMessage('categoryName' + cat)
], [
'img', {
class: 'detected__category-pin detected__category-pin--inactive',
src: '../images/pin.svg'
}
]
]
], [
'div', {

@ -2,6 +2,6 @@
# yarn lockfile v1
webextension-polyfill@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.1.1.tgz#1d172e59b9ee8706d5ce2c55eebfe0cf23972d70"
webextension-polyfill@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.2.1.tgz#cdfc9126033039f1713553157d35beff1d4d6f4a"

@ -2,26 +2,31 @@
# yarn lockfile v1
file-type@3.8.*:
version "3.8.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.8.0.tgz#bcadf6a8f624ebe4a10e5ad26727b6b93f16d78d"
file-type@7.4.*:
version "7.4.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.4.0.tgz#2a7c94f62a0030150bb7d9b6c70cfa1d3e759c86"
html-comment-regex@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
is-svg@2.0.*:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.0.1.tgz#f93ab3bf1d6bbca30e9753cd3485b1300eebc013"
is-svg@2.1.*:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
dependencies:
html-comment-regex "^1.1.0"
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
read-chunk@2.0.*:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.0.0.tgz#3246e877829116cec059674c4d5f300f7a9261f3"
read-chunk@2.1.*:
version "2.1.0"
resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655"
dependencies:
pify "^2.3.0"
pify "^3.0.0"
safe-buffer "^5.1.1"
safe-buffer@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"