Completed
Push — master ( bf4769...c9b0ab )
by Elbert
33s
created

driver.js ➔ ... ➔ ???   A

Complexity

Conditions 1
Paths 4

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 7
rs 9.4285
1
/**
2
 * WebExtension driver
3
 */
4
5
/** global: browser */
6
/** global: Wappalyzer */
7
8
const wappalyzer = new Wappalyzer();
9
10
var tabCache = {};
11
var categoryOrder = [];
12
var options = {};
13
var robotsTxtQueue = {};
14
15
browser.tabs.onRemoved.addListener(tabId => {
16
  tabCache[tabId] = null;
17
});
18
19
/**
20
 * Get a value from localStorage
21
 */
22
function getOption(name, defaultValue = null) {
23
  return new Promise((resolve, reject) => {
0 ignored issues
show
Unused Code introduced by
The parameter reject is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
24
    const callback = item => {
25
      options[name] = item.hasOwnProperty(name) ? item[name] : defaultValue;
26
27
      resolve(options[name]);
28
    };
29
30
    browser.storage.local.get(name)
31
      .then(callback)
32
      .catch(error => wappalyzer.log(error, 'driver', 'error'));
33
  });
34
}
35
36
/**
37
 * Set a value in localStorage
38
 */
39
function setOption(name, value) {
40
  var option = {};
41
42
  option[name] = value;
43
44
  browser.storage.local.set(option);
45
46
  options[name] = value;
47
}
48
49
/**
50
 * Open a tab
51
 */
52
function openTab(args) {
53
  browser.tabs.create({
54
    url: args.url,
55
    active: args.background === undefined || !args.background
56
  });
57
}
58
59
/**
60
 * Make a POST request
61
 */
62
function post(url, body) {
63
  fetch(url, {
64
    method: 'POST',
65
    body: JSON.stringify(body)
66
  })
67
    .then(response => {
68
      wappalyzer.log('POST ' + url + ': ' + response.status, 'driver');
69
    })
70
    .catch(error => {
71
      wappalyzer.log('POST ' + url + ': ' + error, 'driver', 'error');
72
    });
73
}
74
75
fetch('../apps.json')
76
  .then(response => {
77
    return response.json();
78
  })
79
  .then(json => {
80
    wappalyzer.apps = json.apps;
81
    wappalyzer.categories = json.categories;
82
83
    wappalyzer.parseJsPatterns();
84
85
    categoryOrder = Object.keys(wappalyzer.categories)
86
      .map(categoryId => parseInt(categoryId, 10))
87
      .sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority);
88
  })
89
  .catch(error => {
90
    wappalyzer.log('GET apps.json: ' + error, 'driver', 'error');
91
  });
92
93
// Version check
94
var version = browser.runtime.getManifest().version;
95
96
getOption('version')
97
  .then(previousVersion => {
98
    if ( previousVersion === null ) {
99
      openTab({
100
        url: wappalyzer.config.websiteURL + 'installed'
101
      });
102
    } else if ( version !== previousVersion ) {
103
      getOption('upgradeMessage', true)
104
        .then(upgradeMessage => {
105
          if ( upgradeMessage ) {
106
            openTab({
107
              url: wappalyzer.config.websiteURL + 'upgraded?v' + version,
108
              background: true
109
            });
110
          }
111
        });
112
    }
113
114
    setOption('version', version);
115
  });
116
117
getOption('dynamicIcon', true);
118
getOption('pinnedCategory');
119
120
// Run content script on all tabs
121
browser.tabs.query({ url: [ 'http://*/*', 'https://*/*' ] })
122
  .then(tabs => {
123
    tabs.forEach(tab => {
124
      browser.tabs.executeScript(tab.id, {
125
        file: '../js/content.js'
126
      });
127
    })
128
  })
129
  .catch(error => wappalyzer.log(error, 'driver', 'error'));
130
131
// Capture response headers
132
browser.webRequest.onCompleted.addListener(request => {
133
  const headers = {};
134
135
  if ( request.responseHeaders ) {
136
    const url = wappalyzer.parseUrl(request.url);
137
138
    browser.tabs.query({ url: [ url.canonical ] })
139
      .then(tabs => {
140
        const tab = tabs[0] || null;
141
142
        if ( tab ) {
143
          request.responseHeaders.forEach(header => {
144
            const name = header.name.toLowerCase();
145
146
            headers[name] = headers[name] || [];
147
148
            headers[name].push(header.value || header.binaryValue.toString());
149
          });
150
151
          if ( headers['content-type'] && /\/x?html/.test(headers['content-type'][0]) ) {
152
            wappalyzer.analyze(url, { headers }, { tab });
153
          }
154
        }
155
      })
156
      .catch(error => wappalyzer.log(error, 'driver', 'error'));
157
  }
158
}, { urls: [ 'http://*/*', 'https://*/*' ], types: [ 'main_frame' ] }, [ 'responseHeaders' ]);
159
160
// Listen for messages
161
( chrome || browser ).runtime.onMessage.addListener((message, sender, sendResponse) => {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable chrome is declared in the current environment, consider using typeof chrome === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
162
  if ( typeof message.id != 'undefined' ) {
163
    if ( message.id !== 'log' ) {
164
      wappalyzer.log('Message' + ( message.source ? ' from ' + message.source : '' ) + ': ' + message.id, 'driver');
165
    }
166
167
    var url = wappalyzer.parseUrl(sender.tab ? sender.tab.url : '');
168
    var response;
169
170
    switch ( message.id ) {
171
      case 'log':
172
        wappalyzer.log(message.subject, message.source);
173
174
        break;
175
      case 'init':
176
        browser.cookies.getAll({ domain: '.' + url.hostname })
177
          .then(cookies => wappalyzer.analyze(url, { cookies }, { tab: sender.tab }));
178
179
        break;
180
      case 'analyze':
181
        wappalyzer.analyze(url, message.subject, { tab: sender.tab });
182
183
        break;
184
      case 'ad_log':
185
        wappalyzer.cacheDetectedAds(message.subject);
186
187
        break;
188
      case 'get_apps':
189
        response = {
190
          tabCache: tabCache[message.tab.id],
191
          apps: wappalyzer.apps,
192
          categories: wappalyzer.categories,
193
          pinnedCategory: options.pinnedCategory,
194
        };
195
196
        break;
197
      case 'set_option':
198
        setOption(message.key, message.value);
199
200
        break;
201
      case 'get_js_patterns':
202
        response = {
203
          patterns: wappalyzer.jsPatterns
204
        };
205
206
        break;
207
      default:
208
    }
209
210
    sendResponse(response);
211
  }
212
213
  return true;
214
});
215
216
wappalyzer.driver.document = document;
217
218
/**
219
 * Log messages to console
220
 */
221
wappalyzer.driver.log = (message, source, type) => {
222
  console.log('[wappalyzer ' + type + ']', '[' + source + ']', message);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
223
};
224
225
/**
226
 * Display apps
227
 */
228
wappalyzer.driver.displayApps = (detected, meta, context) => {
229
  var tab = context.tab;
230
231
  if ( tab === undefined ) {
232
    return;
233
  }
234
235
  tabCache[tab.id] = tabCache[tab.id] || {
236
    detected: []
237
  };
238
239
  tabCache[tab.id].detected = detected;
240
241
  if ( Object.keys(detected).length ) {
242
    var appName, found = false;
0 ignored issues
show
Unused Code introduced by
The variable appName seems to be never used. Consider removing it.
Loading history...
243
244
    // Find the main application to display
245
    [ options.pinnedCategory ].concat(categoryOrder).forEach(match => {
246
      Object.keys(detected).forEach(appName => {
247
        var app = detected[appName];
248
249
        app.props.cats.forEach(category => {
250
          if ( category === match && !found ) {
251
            var icon = app.props.icon || 'default.svg';
252
253
            if ( !options.dynamicIcon ) {
254
              icon = 'default.svg';
255
            }
256
257
            if ( /\.svg$/i.test(icon) ) {
258
              icon = 'converted/' + icon.replace(/\.svg$/, '.png');
259
            }
260
261
            try {
262
              browser.pageAction.setIcon({
263
                tabId: tab.id,
264
                path: '../images/icons/' + icon
265
              });
266
            } catch(e) {
267
              // Firefox for Android does not support setIcon see https://bugzilla.mozilla.org/show_bug.cgi?id=1331746
268
            }
269
270
            found = true;
271
          }
272
        });
273
      });
274
    });
275
276
    if ( typeof chrome !== 'undefined' ) {
0 ignored issues
show
Bug introduced by
The variable chrome seems to not be initialized for all possible execution paths.
Loading history...
277
      // Browser polyfill doesn't seem to work here
278
      chrome.pageAction.show(tab.id);
279
    } else {
280
      browser.pageAction.show(tab.id);
281
    }
282
  }
283
};
284
285
/**
286
 * Fetch and cache robots.txt for host
287
 */
288
wappalyzer.driver.getRobotsTxt = (host, secure = false) => {
289
  if ( robotsTxtQueue.hasOwnProperty(host) ) {
290
    return robotsTxtQueue[host];
291
  }
292
293
  robotsTxtQueue[host] = new Promise((resolve, reject) => {
0 ignored issues
show
Unused Code introduced by
The parameter reject is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
294
    getOption('tracking', true)
295
      .then(tracking => {
296
        if ( !tracking ) {
297
          return resolve([]);
298
        }
299
300
        getOption('robotsTxtCache')
301
          .then(robotsTxtCache => {
302
            robotsTxtCache = robotsTxtCache || {};
303
304
            if ( host in robotsTxtCache ) {
305
              return resolve(robotsTxtCache[host]);
306
            }
307
308
            const timeout = setTimeout(() => resolve([]), 3000);
309
310
            fetch('http' + ( secure ? 's' : '' ) + '://' + host + '/robots.txt', { redirect: 'follow' })
311
              .then(response => {
312
                clearTimeout(timeout);
313
314
                return response.ok ? response.text() : '';
315
              })
316
              .then(robotsTxt => {
317
                robotsTxtCache[host] = wappalyzer.parseRobotsTxt(robotsTxt);
318
319
                setOption('robotsTxtCache', robotsTxtCache);
320
321
                resolve(robotsTxtCache[host]);
322
              })
323
              .catch(err => resolve([]));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
Unused Code introduced by
The parameter err is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
324
          });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
325
      });
326
  })
327
  .finally(() => delete robotsTxtQueue[host]);
328
329
  return robotsTxtQueue[host];
330
};
331
332
/**
333
 * Anonymously track detected applications for research purposes
334
 */
335
wappalyzer.driver.ping = (hostnameCache, adCache) => {
336
  getOption('tracking', true)
337
    .then(tracking => {
338
      if ( tracking ) {
339
        if ( Object.keys(hostnameCache).length ) {
340
          post('https://api.wappalyzer.com/ping/v1/', hostnameCache);
341
        }
342
343
        if ( adCache.length ) {
344
          post('https://ad.wappalyzer.com/log/wp/', adCache);
345
        }
346
347
        setOption('robotsTxtCache', {});
348
      }
349
    });
350
};
351