PageSourceSearch

https://topofmmos.com/template/js/fingerprint2.js

js topofmmos.com collected 2026-10-07 03:25:36 UTC 64,365 bytes, 2,172 lines download raw bytes

vendor: 5,407 bytes, lines 1-164
1/*
2 * Fingerprintjs2 2.0.5 - Modern & flexible browser fingerprint library v2
3 * https://github.com/Valve/fingerprintjs2
4 * Copyright (c) 2015 Valentin Vasilyev ([email protected])
5 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6 *
7 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
8 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
9 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
10 * ARE DISCLAIMED. IN NO EVENT SHALL VALENTIN VASILYEV BE LIABLE FOR ANY
11 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
12 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
13 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
14 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
15 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
16 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17 */
18/* global define */
19(function(name, context, definition) {
20  "use strict";
21  if (
22    typeof window !== "undefined" &&
23    typeof define === "function" &&
24    define.amd
25  ) {
26    define(definition);
27  } else if (typeof module !== "undefined" && module.exports) {
28    module.exports = definition();
29  } else if (context.exports) {
30    context.exports = definition();
31  } else {
32    context[name] = definition();
33  }
34})("Fingerprint2", this, function() {
35  "use strict";
36
37  /// MurmurHash3 related functions
38
39  //
40  // Given two 64bit ints (as an array of two 32bit ints) returns the two
41  // added together as a 64bit int (as an array of two 32bit ints).
42  //
43  var x64Add = function(m, n) {
44    m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff];
45    n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff];
46    var o = [0, 0, 0, 0];
47    o[3] += m[3] + n[3];
48    o[2] += o[3] >>> 16;
49    o[3] &= 0xffff;
50    o[2] += m[2] + n[2];
51    o[1] += o[2] >>> 16;
52    o[2] &= 0xffff;
53    o[1] += m[1] + n[1];
54    o[0] += o[1] >>> 16;
55    o[1] &= 0xffff;
56    o[0] += m[0] + n[0];
57    o[0] &= 0xffff;
58    return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]];
59  };
60
61  //
62  // Given two 64bit ints (as an array of two 32bit ints) returns the two
63  // multiplied together as a 64bit int (as an array of two 32bit ints).
64  //
65  var x64Multiply = function(m, n) {
66    m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff];
67    n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff];
68    var o = [0, 0, 0, 0];
69    o[3] += m[3] * n[3];
70    o[2] += o[3] >>> 16;
71    o[3] &= 0xffff;
72    o[2] += m[2] * n[3];
73    o[1] += o[2] >>> 16;
74    o[2] &= 0xffff;
75    o[2] += m[3] * n[2];
76    o[1] += o[2] >>> 16;
77    o[2] &= 0xffff;
78    o[1] += m[1] * n[3];
79    o[0] += o[1] >>> 16;
80    o[1] &= 0xffff;
81    o[1] += m[2] * n[2];
82    o[0] += o[1] >>> 16;
83    o[1] &= 0xffff;
84    o[1] += m[3] * n[1];
85    o[0] += o[1] >>> 16;
86    o[1] &= 0xffff;
87    o[0] += m[0] * n[3] + m[1] * n[2] + m[2] * n[1] + m[3] * n[0];
88    o[0] &= 0xffff;
89    return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]];
90  };
91  //
92  // Given a 64bit int (as an array of two 32bit ints) and an int
93  // representing a number of bit positions, returns the 64bit int (as an
94  // array of two 32bit ints) rotated left by that number of positions.
95  //
96  var x64Rotl = function(m, n) {
97    n %= 64;
98    if (n === 32) {
99      return [m[1], m[0]];
100    } else if (n < 32) {
101      return [
102        (m[0] << n) | (m[1] >>> (32 - n)),
103        (m[1] << n) | (m[0] >>> (32 - n))
104      ];
105    } else {
106      n -= 32;
107      return [
108        (m[1] << n) | (m[0] >>> (32 - n)),
109        (m[0] << n) | (m[1] >>> (32 - n))
110      ];
111    }
112  };
113  //
114  // Given a 64bit int (as an array of two 32bit ints) and an int
115  // representing a number of bit positions, returns the 64bit int (as an
116  // array of two 32bit ints) shifted left by that number of positions.
117  //
118  var x64LeftShift = function(m, n) {
119    n %= 64;
120    if (n === 0) {
121      return m;
122    } else if (n < 32) {
123      return [(m[0] << n) | (m[1] >>> (32 - n)), m[1] << n];
124    } else {
125      return [m[1] << (n - 32), 0];
126    }
127  };
128  //
129  // Given two 64bit ints (as an array of two 32bit ints) returns the two
130  // xored together as a 64bit int (as an array of two 32bit ints).
131  //
132  var x64Xor = function(m, n) {
133    return [m[0] ^ n[0], m[1] ^ n[1]];
134  };
135  //
136  // Given a block, returns murmurHash3's final x64 mix of that block.
137  // (`[0, h[0] >>> 1]` is a 33 bit unsigned right shift. This is the
138  // only place where we need to right shift 64bit ints.)
139  //
140  var x64Fmix = function(h) {
141    h = x64Xor(h, [0, h[0] >>> 1]);
142    h = x64Multiply(h, [0xff51afd7, 0xed558ccd]);
143    h = x64Xor(h, [0, h[0] >>> 1]);
144    h = x64Multiply(h, [0xc4ceb9fe, 0x1a85ec53]);
145    h = x64Xor(h, [0, h[0] >>> 1]);
146    return h;
147  };
148
149  //
150  // Given a string and an optional seed as an int, returns a 128 bit
151  // hash using the x64 flavor of MurmurHash3, as an unsigned hex.
152  //
153  var x64hash128 = function(key, seed) {
154    key = key || "";
155    seed = seed || 0;
156    var remainder = key.length % 16;
157    var bytes = key.length - remainder;
158    var h1 = [0, seed];
159    var h2 = [0, seed];
160    var k1 = [0, 0];
161    var k2 = [0, 0];
162    var c1 = [0x87c37b91, 0x114253d5];
163    var c2 = [0x4cf5ad43, 0x2745937f];
164    for (var i = 0; i < bytes; i = i + 16) {
165      k1 = [
166        (key.charCodeAt(i + 4) & 0xff) |
167          ((key.charCodeAt(i + 5) & 0xff) << 8) |
168          ((key.charCodeAt(i + 6) & 0xff) << 16) |
169          ((key.charCodeAt(i + 7) & 0xff) << 24),
170        (key.charCodeAt(i) & 0xff) |
171          ((key.charCodeAt(i + 1) & 0xff) << 8) |
172          ((key.charCodeAt(i + 2) & 0xff) << 16) |
173          ((key.charCodeAt(i + 3) & 0xff) << 24)
174      ];
175      k2 = [
176        (key.charCodeAt(i + 12) & 0xff) |
177          ((key.charCodeAt(i + 13) & 0xff) << 8) |
178          ((key.charCodeAt(i + 14) & 0xff) << 16) |
179          ((key.charCodeAt(i + 15) & 0xff) << 24),
180        (key.charCodeAt(i + 8) & 0xff) |
181          ((key.charCodeAt(i + 9) & 0xff) << 8) |
182          ((key.charCodeAt(i + 10) & 0xff) << 16) |
183          ((key.charCodeAt(i + 11) & 0xff) << 24)
184      ];
185      k1 = x64Multiply(k1, c1);
186      k1 = x64Rotl(k1, 31);
187      k1 = x64Multiply(k1, c2);
188      h1 = x64Xor(h1, k1);
189      h1 = x64Rotl(h1, 27);
190      h1 = x64Add(h1, h2);
191      h1 = x64Add(x64Multiply(h1, [0, 5]), [0, 0x52dce729]);
192      k2 = x64Multiply(k2, c2);
193      k2 = x64Rotl(k2, 33);
194      k2 = x64Multiply(k2, c1);
195      h2 = x64Xor(h2, k2);
196      h2 = x64Rotl(h2, 31);
197      h2 = x64Add(h2, h1);
198      h2 = x64Add(x64Multiply(h2, [0, 5]), [0, 0x38495ab5]);
199    }
200    k1 = [0, 0];
201    k2 = [0, 0];
202    switch (remainder) {
203      case 15:
204        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 14)], 48));
205      // fallthrough
206      case 14:
207        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 13)], 40));
208      // fallthrough
209      case 13:
210        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 12)], 32));
211      // fallthrough
212      case 12:
213        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 11)], 24));
214      // fallthrough
215      case 11:
216        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 10)], 16));
217      // fallthrough
218      case 10:
219        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 9)], 8));
220      // fallthrough
221      case 9:
222        k2 = x64Xor(k2, [0, key.charCodeAt(i + 8)]);
223        k2 = x64Multiply(k2, c2);
224        k2 = x64Rotl(k2, 33);
225        k2 = x64Multiply(k2, c1);
226        h2 = x64Xor(h2, k2);
227      // fallthrough
228      case 8:
229        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 7)], 56));
230      // fallthrough
231      case 7:
232        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 6)], 48));
233      // fallthrough
234      case 6:
235        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 5)], 40));
236      // fallthrough
237      case 5:
238        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 4)], 32));
239      // fallthrough
240      case 4:
241        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 3)], 24));
242      // fallthrough
243      case 3:
244        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 2)], 16));
245      // fallthrough
246      case 2:
247        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 1)], 8));
248      // fallthrough
249      case 1:
250        k1 = x64Xor(k1, [0, key.charCodeAt(i)]);
251        k1 = x64Multiply(k1, c1);
252        k1 = x64Rotl(k1, 31);
253        k1 = x64Multiply(k1, c2);
254        h1 = x64Xor(h1, k1);
255      // fallthrough
256    }
257    h1 = x64Xor(h1, [0, key.length]);
258    h2 = x64Xor(h2, [0, key.length]);
259    h1 = x64Add(h1, h2);
260    h2 = x64Add(h2, h1);
261    h1 = x64Fmix(h1);
262    h2 = x64Fmix(h2);
263    h1 = x64Add(h1, h2);
264    h2 = x64Add(h2, h1);
265    return (
266      ("00000000" + (h1[0] >>> 0).toString(16)).slice(-8) +
267      ("00000000" + (h1[1] >>> 0).toString(16)).slice(-8) +
268      ("00000000" + (h2[0] >>> 0).toString(16)).slice(-8) +
269      ("00000000" + (h2[1] >>> 0).toString(16)).slice(-8)
270    );
271  };
272
273  var defaultOptions = {
274    preprocessor: null,
275    audio: {
276      timeout: 1000,
277      // On iOS 11, audio context can only be used in response to user interaction.
278      // We require users to explicitly enable audio fingerprinting on iOS 11.
279      // See https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088
280      excludeIOS11: true
281    },
282    fonts: {
283      swfContainerId: "fingerprintjs2",
284      swfPath: "flash/compiled/FontList.swf",
285      userDefinedFonts: [],
286      extendedJsFonts: false
287    },
288    screen: {
289      // To ensure consistent fingerprints when users rotate their mobile devices
290      detectScreenOrientation: true
291    },
292    plugins: {
293      sortPluginsFor: [/palemoon/i],
294      excludeIE: false
295    },
296    extraComponents: [],
297    excludes: {
298      // Unreliable on Windows, see https://github.com/Valve/fingerprintjs2/issues/375
299      enumerateDevices: true,
300      // devicePixelRatio depends on browser zoom, and it's impossible to detect browser zoom
301      pixelRatio: true,
302      // DNT depends on incognito mode for some browsers (Chrome) and it's impossible to detect incognito mode
303      doNotTrack: true,
304      // uses js fonts already
305      fontsFlash: true
306    },
307    NOT_AVAILABLE: "not available",
308    ERROR: "error",
309    EXCLUDED: "excluded"
310  };
311
312  var each = function(obj, iterator) {
313    if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
314      obj.forEach(iterator);
315    } else if (obj.length === +obj.length) {
316      for (var i = 0, l = obj.length; i < l; i++) {
317        iterator(obj[i], i, obj);
318      }
319    } else {
320      for (var key in obj) {
321        if (obj.hasOwnProperty(key)) {
322          iterator(obj[key], key, obj);
323        }
324      }
325    }
326  };
327
328  var map = function(obj, iterator) {
329    var results = [];
330    // Not using strict equality so that this acts as a
331    // shortcut to checking for `null` and `undefined`.
332    if (obj == null) {
333      return results;
334    }
335    if (Array.prototype.map && obj.map === Array.prototype.map) {
336      return obj.map(iterator);
337    }
338    each(obj, function(value, index, list) {
339      results.push(iterator(value, index, list));
340    });
341    return results;
342  };
343
344  var extendSoft = function(target, source) {
345    if (source == null) {
346      return target;
347    }
348    var value;
349    var key;
350    for (key in source) {
351      value = source[key];
352      if (value != null && !Object.prototype.hasOwnProperty.call(target, key)) {
353        target[key] = value;
354      }
355    }
356    return target;
357  };
358
359  // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
360  var enumerateDevicesKey = function(done, options) {
361    if (!isEnumerateDevicesSupported()) {
362      return done(options.NOT_AVAILABLE);
363    }
364    navigator.mediaDevices
365      .enumerateDevices()
366      .then(function(devices) {
367        done(
368          devices.map(function(device) {
369            return (
370              "id=" +
371              device.deviceId +
372              ";gid=" +
373              device.groupId +
374              ";" +
375              device.kind +
376              ";" +
377              device.label
378            );
379          })
380        );
381      })
382      .catch(function(error) {
383        done(error);
384      });
385  };
386
387  var isEnumerateDevicesSupported = function() {
388    return navigator.mediaDevices && navigator.mediaDevices.enumerateDevices;
389  };
390  // Inspired by and based on https://github.com/cozylife/audio-fingerprint
391  var audioKey = function(done, options) {
392    var audioOptions = options.audio;
393    if (
394      audioOptions.excludeIOS11 &&
395      navigator.userAgent.match(/OS 11.+Version\/11.+Safari/)
396    ) {
397      // See comment for excludeUserAgent and https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088
398      return done(options.EXCLUDED);
399    }
400
401    var AudioContext =
402      window.OfflineAudioContext || window.webkitOfflineAudioContext;
403
404    if (AudioContext == null) {
405      return done(options.NOT_AVAILABLE);
406    }
407
408    var context = new AudioContext(1, 44100, 44100);
409
410    var oscillator = context.createOscillator();
411    oscillator.type = "triangle";
412    oscillator.frequency.setValueAtTime(10000, context.currentTime);
413
414    var compressor = context.createDynamicsCompressor();
415    each(
416      [
417        ["threshold", -50],
418        ["knee", 40],
419        ["ratio", 12],
420        ["reduction", -20],
421        ["attack", 0],
422        ["release", 0.25]
423      ],
424      function(item) {
425        if (
426          compressor[item[0]] !== undefined &&
427          typeof compressor[item[0]].setValueAtTime === "function"
428        ) {
429          compressor[item[0]].setValueAtTime(item[1], context.currentTime);
430        }
431      }
432    );
433
434    oscillator.connect(compressor);
435    compressor.connect(context.destination);
436    oscillator.start(0);
437    context.startRendering();
438
439    var audioTimeoutId = setTimeout(function() {
440      console.warn(
441        'Audio fingerprint timed out. Please report bug at https://github.com/Valve/fingerprintjs2 with your user agent: "' +
442          navigator.userAgent +
443          '".'
444      );
445      context.oncomplete = function() {};
446      context = null;
447      return done("audioTimeout");
448    }, audioOptions.timeout);
449
450    context.oncomplete = function(event) {
451      var fingerprint;
452      try {
453        clearTimeout(audioTimeoutId);
454        fingerprint = event.renderedBuffer
455          .getChannelData(0)
456          .slice(4500, 5000)
457          .reduce(function(acc, val) {
458            return acc + Math.abs(val);
459          }, 0)
460          .toString();
461        oscillator.disconnect();
462        compressor.disconnect();
463      } catch (error) {
464        done(error);
465        return;
466      }
467      done(fingerprint);
468    };
469  };
470  var UserAgent = function(done) {
471    done(navigator.userAgent);
472  };
473  var languageKey = function(done, options) {
474    done(
475      navigator.language ||
476        navigator.userLanguage ||
477        navigator.browserLanguage ||
478        navigator.systemLanguage ||
479        options.NOT_AVAILABLE
480    );
481  };
482  var colorDepthKey = function(done, options) {
483    done(window.screen.colorDepth || options.NOT_AVAILABLE);
484  };
485  var deviceMemoryKey = function(done, options) {
486    done(navigator.deviceMemory || options.NOT_AVAILABLE);
487  };
488  var pixelRatioKey = function(done, options) {
489    done(window.devicePixelRatio || options.NOT_AVAILABLE);
490  };
491  var screenResolutionKey = function(done, options) {
492    done(getScreenResolution(options));
493  };
494  var getScreenResolution = function(options) {
495    var resolution = [window.screen.width, window.screen.height];
496    if (options.screen.detectScreenOrientation) {
497      resolution.sort().reverse();
498    }
499    return resolution;
500  };
501  var availableScreenResolutionKey = function(done, options) {
502    done(getAvailableScreenResolution(options));
503  };
504  var getAvailableScreenResolution = function(options) {
505    if (window.screen.availWidth && window.screen.availHeight) {
506      var available = [window.screen.availHeight, window.screen.availWidth];
507      if (options.screen.detectScreenOrientation) {
508        available.sort().reverse();
509      }
510      return available;
511    }
512    // headless browsers
513    return options.NOT_AVAILABLE;
514  };
515  var timezoneOffset = function(done) {
516    done(new Date().getTimezoneOffset());
517  };
518  var timezone = function(done, options) {
519    if (window.Intl && window.Intl.DateTimeFormat) {
520      done(new window.Intl.DateTimeFormat().resolvedOptions().timeZone);
521      return;
522    }
523    done(options.NOT_AVAILABLE);
524  };
525  var sessionStorageKey = function(done, options) {
526    done(hasSessionStorage(options));
527  };
528  var localStorageKey = function(done, options) {
529    done(hasLocalStorage(options));
530  };
531  var indexedDbKey = function(done, options) {
532    done(hasIndexedDB(options));
533  };
534  var addBehaviorKey = function(done) {
535    // body might not be defined at this point or removed programmatically
536    done(!!(document.body && document.body.addBehavior));
537  };
538  var openDatabaseKey = function(done) {
539    done(!!window.openDatabase);
540  };
541  var cpuClassKey = function(done, options) {
542    done(getNavigatorCpuClass(options));
543  };
544  var platformKey = function(done, options) {
545    done(getNavigatorPlatform(options));
546  };
547  var doNotTrackKey = function(done, options) {
548    done(getDoNotTrack(options));
549  };
550  var canvasKey = function(done, options) {
551    if (isCanvasSupported()) {
552      done(getCanvasFp(options));
553      return;
554    }
555    done(options.NOT_AVAILABLE);
556  };
557  var webglKey = function(done, options) {
558    if (isWebGlSupported()) {
559      done(getWebglFp());
560      return;
561    }
562    done(options.NOT_AVAILABLE);
563  };
564  var webglVendorAndRendererKey = function(done) {
565    if (isWebGlSupported()) {
566      done(getWebglVendorAndRenderer());
567      return;
568    }
569    done();
570  };
571  var adBlockKey = function(done) {
572    done(getAdBlock());
573  };
574  var hasLiedLanguagesKey = function(done) {
575    done(getHasLiedLanguages());
576  };
577  var hasLiedResolutionKey = function(done) {
578    done(getHasLiedResolution());
579  };
580  var hasLiedOsKey = function(done) {
581    done(getHasLiedOs());
582  };
583  var hasLiedBrowserKey = function(done) {
584    done(getHasLiedBrowser());
585  };
586  // flash fonts (will increase fingerprinting time 20X to ~ 130-150ms)
587  var flashFontsKey = function(done, options) {
588    // we do flash if swfobject is loaded
589    if (!hasSwfObjectLoaded()) {
590      return done("swf object not loaded");
591    }
592    if (!hasMinFlashInstalled()) {
593      return done("flash not installed");
594    }
595    if (!options.fonts.swfPath) {
596      return done("missing options.fonts.swfPath");
597    }
598    loadSwfAndDetectFonts(function(fonts) {
599      done(fonts);
600    }, options);
601  };
602  // kudos to http://www.lalit.org/lab/javascript-css-font-detect/
603  var jsFontsKey = function(done, options) {
604    // a font will be compared against all the three default fonts.
605    // and if it doesn't match all 3 then that font is not available.
606    var baseFonts = ["monospace", "sans-serif", "serif"];
607
608    var fontList = [
609      "Andale Mono",
610      "Arial",
611      "Arial Black",
612      "Arial Hebrew",
613      "Arial MT",
614      "Arial Narrow",
615      "Arial Rounded MT Bold",
616      "Arial Unicode MS",
617      "Bitstream Vera Sans Mono",
618      "Book Antiqua",
619      "Bookman Old Style",
620      "Calibri",
621      "Cambria",
622      "Cambria Math",
623      "Century",
624      "Century Gothic",
625      "Century Schoolbook",
626      "Comic Sans",
627      "Comic Sans MS",
628      "Consolas",
629      "Courier",
630      "Courier New",
631      "Geneva",
632      "Georgia",
633      "Helvetica",
634      "Helvetica Neue",
635      "Impact",
636      "Lucida Bright",
637      "Lucida Calligraphy",
638      "Lucida Console",
639      "Lucida Fax",
640      "LUCIDA GRANDE",
641      "Lucida Handwriting",
642      "Lucida Sans",
643      "Lucida Sans Typewriter",
644      "Lucida Sans Unicode",
645      "Microsoft Sans Serif",
646      "Monaco",
647      "Monotype Corsiva",
648      "MS Gothic",
649      "MS Outlook",
650      "MS PGothic",
651      "MS Reference Sans Serif",
652      "MS Sans Serif",
653      "MS Serif",
654      "MYRIAD",
655      "MYRIAD PRO",
656      "Palatino",
657      "Palatino Linotype",
658      "Segoe Print",
659      "Segoe Script",
660      "Segoe UI",
661      "Segoe UI Light",
662      "Segoe UI Semibold",
663      "Segoe UI Symbol",
664      "Tahoma",
665      "Times",
666      "Times New Roman",
667      "Times New Roman PS",
668      "Trebuchet MS",
669      "Verdana",
670      "Wingdings",
671      "Wingdings 2",
672      "Wingdings 3"
673    ];
674
675    if (options.fonts.extendedJsFonts) {
676      var extendedFontList = [
677        "Abadi MT Condensed Light",
678        "Academy Engraved LET",
679        "ADOBE CASLON PRO",
680        "Adobe Garamond",
681        "ADOBE GARAMOND PRO",
682        "Agency FB",
683        "Aharoni",
684        "Albertus Extra Bold",
685        "Albertus Medium",
686        "Algerian",
687        "Amazone BT",
688        "American Typewriter",
689        "American Typewriter Condensed",
690        "AmerType Md BT",
691        "Andalus",
692        "Angsana New",
693        "AngsanaUPC",
694        "Antique Olive",
695        "Aparajita",
696        "Apple Chancery",
697        "Apple Color Emoji",
698        "Apple SD Gothic Neo",
699        "Arabic Typesetting",
700        "ARCHER",
701        "ARNO PRO",
702        "Arrus BT",
703        "Aurora Cn BT",
704        "AvantGarde Bk BT",
705        "AvantGarde Md BT",
706        "AVENIR",
707        "Ayuthaya",
708        "Bandy",
709        "Bangla Sangam MN",
710        "Bank Gothic",
711        "BankGothic Md BT",
712        "Baskerville",
713        "Baskerville Old Face",
714        "Batang",
715        "BatangChe",
716        "Bauer Bodoni",
717        "Bauhaus 93",
718        "Bazooka",
719        "Bell MT",
720        "Bembo",
721        "Benguiat Bk BT",
722        "Berlin Sans FB",
723        "Berlin Sans FB Demi",
724        "Bernard MT Condensed",
725        "BernhardFashion BT",
726        "BernhardMod BT",
727        "Big Caslon",
728        "BinnerD",
729        "Blackadder ITC",
730        "BlairMdITC TT",
731        "Bodoni 72",
732        "Bodoni 72 Oldstyle",
733        "Bodoni 72 Smallcaps",
734        "Bodoni MT",
735        "Bodoni MT Black",
736        "Bodoni MT Condensed",
737        "Bodoni MT Poster Compressed",
738        "Bookshelf Symbol 7",
739        "Boulder",
740        "Bradley Hand",
741        "Bradley Hand ITC",
742        "Bremen Bd BT",
743        "Britannic Bold",
744        "Broadway",
745        "Browallia New",
746        "BrowalliaUPC",
747        "Brush Script MT",
748        "Californian FB",
749        "Calisto MT",
750        "Calligrapher",
751        "Candara",
752        "CaslonOpnface BT",
753        "Castellar",
754        "Centaur",
755        "Cezanne",
756        "CG Omega",
757        "CG Times",
758        "Chalkboard",
759        "Chalkboard SE",
760        "Chalkduster",
761        "Charlesworth",
762        "Charter Bd BT",
763        "Charter BT",
764        "Chaucer",
765        "ChelthmITC Bk BT",
766        "Chiller",
767        "Clarendon",
768        "Clarendon Condensed",
769        "CloisterBlack BT",
770        "Cochin",
771        "Colonna MT",
772        "Constantia",
773        "Cooper Black",
774        "Copperplate",
775        "Copperplate Gothic",
776        "Copperplate Gothic Bold",
777        "Copperplate Gothic Light",
778        "CopperplGoth Bd BT",
779        "Corbel",
780        "Cordia New",
781        "CordiaUPC",
782        "Cornerstone",
783        "Coronet",
784        "Cuckoo",
785        "Curlz MT",
786        "DaunPenh",
787        "Dauphin",
788        "David",
789        "DB LCD Temp",
790        "DELICIOUS",
791        "Denmark",
792        "DFKai-SB",
793        "Didot",
794        "DilleniaUPC",
795        "DIN",
796        "DokChampa",
797        "Dotum",
798        "DotumChe",
799        "Ebrima",
800        "Edwardian Script ITC",
801        "Elephant",
802        "English 111 Vivace BT",
803        "Engravers MT",
804        "EngraversGothic BT",
805        "Eras Bold ITC",
806        "Eras Demi ITC",
807        "Eras Light ITC",
808        "Eras Medium ITC",
809        "EucrosiaUPC",
810        "Euphemia",
811        "Euphemia UCAS",
812        "EUROSTILE",
813        "Exotc350 Bd BT",
814        "FangSong",
815        "Felix Titling",
816        "Fixedsys",
817        "FONTIN",
818        "Footlight MT Light",
819        "Forte",
820        "FrankRuehl",
821        "Fransiscan",
822        "Freefrm721 Blk BT",
823        "FreesiaUPC",
824        "Freestyle Script",
825        "French Script MT",
826        "FrnkGothITC Bk BT",
827        "Fruitger",
828        "FRUTIGER",
829        "Futura",
830        "Futura Bk BT",
831        "Futura Lt BT",
832        "Futura Md BT",
833        "Futura ZBlk BT",
834        "FuturaBlack BT",
835        "Gabriola",
836        "Galliard BT",
837        "Gautami",
838        "Geeza Pro",
839        "Geometr231 BT",
840        "Geometr231 Hv BT",
841        "Geometr231 Lt BT",
842        "GeoSlab 703 Lt BT",
843        "GeoSlab 703 XBd BT",
844        "Gigi",
845        "Gill Sans",
846        "Gill Sans MT",
847        "Gill Sans MT Condensed",
848        "Gill Sans MT Ext Condensed Bold",
849        "Gill Sans Ultra Bold",
850        "Gill Sans Ultra Bold Condensed",
851        "Gisha",
852        "Gloucester MT Extra Condensed",
853        "GOTHAM",
854        "GOTHAM BOLD",
855        "Goudy Old Style",
856        "Goudy Stout",
857        "GoudyHandtooled BT",
858        "GoudyOLSt BT",
859        "Gujarati Sangam MN",
860        "Gulim",
861        "GulimChe",
862        "Gungsuh",
863        "GungsuhChe",
864        "Gurmukhi MN",
865        "Haettenschweiler",
866        "Harlow Solid Italic",
867        "Harrington",
868        "Heather",
869        "Heiti SC",
870        "Heiti TC",
871        "HELV",
872        "Herald",
873        "High Tower Text",
874        "Hiragino Kaku Gothic ProN",
875        "Hiragino Mincho ProN",
876        "Hoefler Text",
877        "Humanst 521 Cn BT",
878        "Humanst521 BT",
879        "Humanst521 Lt BT",
880        "Imprint MT Shadow",
881        "Incised901 Bd BT",
882        "Incised901 BT",
883        "Incised901 Lt BT",
884        "INCONSOLATA",
885        "Informal Roman",
886        "Informal011 BT",
887        "INTERSTATE",
888        "IrisUPC",
889        "Iskoola Pota",
890        "JasmineUPC",
891        "Jazz LET",
892        "Jenson",
893        "Jester",
894        "Jokerman",
895        "Juice ITC",
896        "Kabel Bk BT",
897        "Kabel Ult BT",
898        "Kailasa",
899        "KaiTi",
900        "Kalinga",
901        "Kannada Sangam MN",
902        "Kartika",
903        "Kaufmann Bd BT",
904        "Kaufmann BT",
905        "Khmer UI",
906        "KodchiangUPC",
907        "Kokila",
908        "Korinna BT",
909        "Kristen ITC",
910        "Krungthep",
911        "Kunstler Script",
912        "Lao UI",
913        "Latha",
914        "Leelawadee",
915        "Letter Gothic",
916        "Levenim MT",
917        "LilyUPC",
918        "Lithograph",
919        "Lithograph Light",
920        "Long Island",
921        "Lydian BT",
922        "Magneto",
923        "Maiandra GD",
924        "Malayalam Sangam MN",
925        "Malgun Gothic",
926        "Mangal",
927        "Marigold",
928        "Marion",
929        "Marker Felt",
930        "Market",
931        "Marlett",
932        "Matisse ITC",
933        "Matura MT Script Capitals",
934        "Meiryo",
935        "Meiryo UI",
936        "Microsoft Himalaya",
937        "Microsoft JhengHei",
938        "Microsoft New Tai Lue",
939        "Microsoft PhagsPa",
940        "Microsoft Tai Le",
941        "Microsoft Uighur",
942        "Microsoft YaHei",
943        "Microsoft Yi Baiti",
944        "MingLiU",
945        "MingLiU_HKSCS",
946        "MingLiU_HKSCS-ExtB",
947        "MingLiU-ExtB",
948        "Minion",
949        "Minion Pro",
950        "Miriam",
951        "Miriam Fixed",
952        "Mistral",
953        "Modern",
954        "Modern No. 20",
955        "Mona Lisa Solid ITC TT",
956        "Mongolian Baiti",
957        "MONO",
958        "MoolBoran",
959        "Mrs Eaves",
960        "MS LineDraw",
961        "MS Mincho",
962        "MS PMincho",
963        "MS Reference Specialty",
964        "MS UI Gothic",
965        "MT Extra",
966        "MUSEO",
967        "MV Boli",
968        "Nadeem",
969        "Narkisim",
970        "NEVIS",
971        "News Gothic",
972        "News GothicMT",
973        "NewsGoth BT",
974        "Niagara Engraved",
975        "Niagara Solid",
976        "Noteworthy",
977        "NSimSun",
978        "Nyala",
979        "OCR A Extended",
980        "Old Century",
981        "Old English Text MT",
982        "Onyx",
983        "Onyx BT",
984        "OPTIMA",
985        "Oriya Sangam MN",
986        "OSAKA",
987        "OzHandicraft BT",
988        "Palace Script MT",
989        "Papyrus",
990        "Parchment",
991        "Party LET",
992        "Pegasus",
993        "Perpetua",
994        "Perpetua Titling MT",
995        "PetitaBold",
996        "Pickwick",
997        "Plantagenet Cherokee",
998        "Playbill",
999        "PMingLiU",
1000        "PMingLiU-ExtB",
1001        "Poor Richard",
1002        "Poster",
1003        "PosterBodoni BT",
1004        "PRINCETOWN LET",
1005        "Pristina",
1006        "PTBarnum BT",
1007        "Pythagoras",
1008        "Raavi",
1009        "Rage Italic",
1010        "Ravie",
1011        "Ribbon131 Bd BT",
1012        "Rockwell",
1013        "Rockwell Condensed",
1014        "Rockwell Extra Bold",
1015        "Rod",
1016        "Roman",
1017        "Sakkal Majalla",
1018        "Santa Fe LET",
1019        "Savoye LET",
1020        "Sceptre",
1021        "Script",
1022        "Script MT Bold",
1023        "SCRIPTINA",
1024        "Serifa",
1025        "Serifa BT",
1026        "Serifa Th BT",
1027        "ShelleyVolante BT",
1028        "Sherwood",
1029        "Shonar Bangla",
1030        "Showcard Gothic",
1031        "Shruti",
1032        "Signboard",
1033        "SILKSCREEN",
1034        "SimHei",
1035        "Simplified Arabic",
1036        "Simplified Arabic Fixed",
1037        "SimSun",
1038        "SimSun-ExtB",
1039        "Sinhala Sangam MN",
1040        "Sketch Rockwell",
1041        "Skia",
1042        "Small Fonts",
1043        "Snap ITC",
1044        "Snell Roundhand",
1045        "Socket",
1046        "Souvenir Lt BT",
1047        "Staccato222 BT",
1048        "Steamer",
1049        "Stencil",
1050        "Storybook",
1051        "Styllo",
1052        "Subway",
1053        "Swis721 BlkEx BT",
1054        "Swiss911 XCm BT",
1055        "Sylfaen",
1056        "Synchro LET",
1057        "System",
1058        "Tamil Sangam MN",
1059        "Technical",
1060        "Teletype",
1061        "Telugu Sangam MN",
1062        "Tempus Sans ITC",
1063        "Terminal",
1064        "Thonburi",
1065        "Traditional Arabic",
1066        "Trajan",
1067        "TRAJAN PRO",
1068        "Tristan",
1069        "Tubular",
1070        "Tunga",
1071        "Tw Cen MT",
1072        "Tw Cen MT Condensed",
1073        "Tw Cen MT Condensed Extra Bold",
1074        "TypoUpright BT",
1075        "Unicorn",
1076        "Univers",
1077        "Univers CE 55 Medium",
1078        "Univers Condensed",
1079        "Utsaah",
1080        "Vagabond",
1081        "Vani",
1082        "Vijaya",
1083        "Viner Hand ITC",
1084        "VisualUI",
1085        "Vivaldi",
1086        "Vladimir Script",
1087        "Vrinda",
1088        "Westminster",
1089        "WHITNEY",
1090        "Wide Latin",
1091        "ZapfEllipt BT",
1092        "ZapfHumnst BT",
1093        "ZapfHumnst Dm BT",
1094        "Zapfino",
1095        "Zurich BlkEx BT",
1096        "Zurich Ex BT",
1097        "ZWAdobeF"
1098      ];
1099      fontList = fontList.concat(extendedFontList);
1100    }
1101
1102    fontList = fontList.concat(options.fonts.userDefinedFonts);
1103
1104    // remove duplicate fonts
1105    fontList = fontList.filter(function(font, position) {
1106      return fontList.indexOf(font) === position;
1107    });
1108
1109    // we use m or w because these two characters take up the maximum width.
1110    // And we use a LLi so that the same matching fonts can get separated
1111    var testString = "mmmmmmmmmmlli";
1112
1113    // we test using 72px font size, we may use any size. I guess larger the better.
1114    var testSize = "72px";
1115
1116    var h = document.getElementsByTagName("body")[0];
1117
1118    // div to load spans for the base fonts
1119    var baseFontsDiv = document.createElement("div");
1120
1121    // div to load spans for the fonts to detect
1122    var fontsDiv = document.createElement("div");
1123
1124    var defaultWidth = {};
1125    var defaultHeight = {};
1126
1127    // creates a span where the fonts will be loaded
1128    var createSpan = function() {
1129      var s = document.createElement("span");
1130      /*
1131       * We need this css as in some weird browser this
1132       * span elements shows up for a microSec which creates a
1133       * bad user experience
1134       */
1135      s.style.position = "absolute";
1136      s.style.left = "-9999px";
1137      s.style.fontSize = testSize;
1138
1139      // css font reset to reset external styles
1140      s.style.fontStyle = "normal";
1141      s.style.fontWeight = "normal";
1142      s.style.letterSpacing = "normal";
1143      s.style.lineBreak = "auto";
1144      s.style.lineHeight = "normal";
1145      s.style.textTransform = "none";
1146      s.style.textAlign = "left";
1147      s.style.textDecoration = "none";
1148      s.style.textShadow = "none";
1149      s.style.whiteSpace = "normal";
1150      s.style.wordBreak = "normal";
1151      s.style.wordSpacing = "normal";
1152
1153      s.innerHTML = testString;
1154      return s;
1155    };
1156
1157    // creates a span and load the font to detect and a base font for fallback
1158    var createSpanWithFonts = function(fontToDetect, baseFont) {
1159      var s = createSpan();
1160      s.style.fontFamily = "'" + fontToDetect + "'," + baseFont;
1161      return s;
1162    };
1163
1164    // creates spans for the base fonts and adds them to baseFontsDiv
1165    var initializeBaseFontsSpans = function() {
1166      var spans = [];
1167      for (var index = 0, length = baseFonts.length; index < length; index++) {
1168        var s = createSpan();
1169        s.style.fontFamily = baseFonts[index];
1170        baseFontsDiv.appendChild(s);
1171        spans.push(s);
1172      }
1173      return spans;
1174    };
1175
1176    // creates spans for the fonts to detect and adds them to fontsDiv
1177    var initializeFontsSpans = function() {
1178      var spans = {};
1179      for (var i = 0, l = fontList.length; i < l; i++) {
1180        var fontSpans = [];
1181        for (
1182          var j = 0, numDefaultFonts = baseFonts.length;
1183          j < numDefaultFonts;
1184          j++
1185        ) {
1186          var s = createSpanWithFonts(fontList[i], baseFonts[j]);
1187          fontsDiv.appendChild(s);
1188          fontSpans.push(s);
1189        }
1190        spans[fontList[i]] = fontSpans; // Stores {fontName : [spans for that font]}
1191      }
1192      return spans;
1193    };
1194
1195    // checks if a font is available
1196    var isFontAvailable = function(fontSpans) {
1197      var detected = false;
1198      for (var i = 0; i < baseFonts.length; i++) {
1199        detected =
1200          fontSpans[i].offsetWidth !== defaultWidth[baseFonts[i]] ||
1201          fontSpans[i].offsetHeight !== defaultHeight[baseFonts[i]];
1202        if (detected) {
1203          return detected;
1204        }
1205      }
1206      return detected;
1207    };
1208
1209    // create spans for base fonts
1210    var baseFontsSpans = initializeBaseFontsSpans();
1211
1212    // add the spans to the DOM
1213    h.appendChild(baseFontsDiv);
1214
1215    // get the default width for the three base fonts
1216    for (var index = 0, length = baseFonts.length; index < length; index++) {
1217      defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth; // width for the default font
1218      defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight; // height for the default font
1219    }
1220
1221    // create spans for fonts to detect
1222    var fontsSpans = initializeFontsSpans();
1223
1224    // add all the spans to the DOM
1225    h.appendChild(fontsDiv);
1226
1227    // check available fonts
1228    var available = [];
1229    for (var i = 0, l = fontList.length; i < l; i++) {
1230      if (isFontAvailable(fontsSpans[fontList[i]])) {
1231        available.push(fontList[i]);
1232      }
1233    }
1234
1235    // remove spans from DOM
1236    h.removeChild(fontsDiv);
1237    h.removeChild(baseFontsDiv);
1238    done(available);
1239  };
1240  var pluginsComponent = function(done, options) {
1241    if (isIE()) {
1242      if (!options.plugins.excludeIE) {
1243        done(getIEPlugins(options));
1244      } else {
1245        done(options.EXCLUDED);
1246      }
1247    } else {
1248      done(getRegularPlugins(options));
1249    }
1250  };
1251  var getRegularPlugins = function(options) {
1252    if (navigator.plugins == null) {
1253      return options.NOT_AVAILABLE;
1254    }
1255
1256    var plugins = [];
1257    // plugins isn't defined in Node envs.
1258    for (var i = 0, l = navigator.plugins.length; i < l; i++) {
1259      if (navigator.plugins[i]) {
1260        plugins.push(navigator.plugins[i]);
1261      }
1262    }
1263
1264    // sorting plugins only for those user agents, that we know randomize the plugins
1265    // every time we try to enumerate them
1266    if (pluginsShouldBeSorted(options)) {
1267      plugins = plugins.sort(function(a, b) {
1268        if (a.name > b.name) {
1269          return 1;
1270        }
1271        if (a.name < b.name) {
1272          return -1;
1273        }
1274        return 0;
1275      });
1276    }
1277    return map(plugins, function(p) {
1278      var mimeTypes = map(p, function(mt) {
1279        return [mt.type, mt.suffixes];
1280      });
1281      return [p.name, p.description, mimeTypes];
1282    });
1283  };
1284  var getIEPlugins = function(options) {
1285    var result = [];
1286    if (
1287      (Object.getOwnPropertyDescriptor &&
1288        Object.getOwnPropertyDescriptor(window, "ActiveXObject")) ||
1289      "ActiveXObject" in window
1290    ) {
1291      var names = [
1292        "AcroPDF.PDF", // Adobe PDF reader 7+
1293        "Adodb.Stream",
1294        "AgControl.AgControl", // Silverlight
1295        "DevalVRXCtrl.DevalVRXCtrl.1",
1296        "MacromediaFlashPaper.MacromediaFlashPaper",
1297        "Msxml2.DOMDocument",
1298        "Msxml2.XMLHTTP",
1299        "PDF.PdfCtrl", // Adobe PDF reader 6 and earlier, brrr
1300        "QuickTime.QuickTime", // QuickTime
1301        "QuickTimeCheckObject.QuickTimeCheck.1",
1302        "RealPlayer",
1303        "RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)",
1304        "RealVideo.RealVideo(tm) ActiveX Control (32-bit)",
1305        "Scripting.Dictionary",
1306        "SWCtl.SWCtl", // ShockWave player
1307        "Shell.UIHelper",
1308        "ShockwaveFlash.ShockwaveFlash", // flash plugin
1309        "Skype.Detection",
1310        "TDCCtl.TDCCtl",
1311        "WMPlayer.OCX", // Windows media player
1312        "rmocx.RealPlayer G2 Control",
1313        "rmocx.RealPlayer G2 Control.1"
1314      ];
1315      // starting to detect plugins in IE
1316      result = map(names, function(name) {
1317        try {
1318          // eslint-disable-next-line no-new
1319          new window.ActiveXObject(name);
1320          return name;
1321        } catch (e) {
1322          return options.ERROR;
1323        }
1324      });
1325    } else {
1326      result.push(options.NOT_AVAILABLE);
1327    }
1328    if (navigator.plugins) {
1329      result = result.concat(getRegularPlugins(options));
1330    }
1331    return result;
1332  };
1333  var pluginsShouldBeSorted = function(options) {
1334    var should = false;
1335    for (var i = 0, l = options.plugins.sortPluginsFor.length; i < l; i++) {
1336      var re = options.plugins.sortPluginsFor[i];
1337      if (navigator.userAgent.match(re)) {
1338        should = true;
1339        break;
1340      }
1341    }
1342    return should;
1343  };
1344  var touchSupportKey = function(done) {
1345    done(getTouchSupport());
1346  };
1347  var hardwareConcurrencyKey = function(done, options) {
1348    done(getHardwareConcurrency(options));
1349  };
1350  var hasSessionStorage = function(options) {
1351    try {
1352      return !!window.sessionStorage;
1353    } catch (e) {
1354      return options.ERROR; // SecurityError when referencing it means it exists
1355    }
1356  };
1357
1358  // https://bugzilla.mozilla.org/show_bug.cgi?id=781447
1359  var hasLocalStorage = function(options) {
1360    try {
1361      return !!window.localStorage;
1362    } catch (e) {
1363      return options.ERROR; // SecurityError when referencing it means it exists
1364    }
1365  };
1366  var hasIndexedDB = function(options) {
1367    try {
1368      return !!window.indexedDB;
1369    } catch (e) {
1370      return options.ERROR; // SecurityError when referencing it means it exists
1371    }
1372  };
1373  var getHardwareConcurrency = function(options) {
1374    if (navigator.hardwareConcurrency) {
1375      return navigator.hardwareConcurrency;
1376    }
1377    return options.NOT_AVAILABLE;
1378  };
1379  var getNavigatorCpuClass = function(options) {
1380    return navigator.cpuClass || options.NOT_AVAILABLE;
1381  };
1382  var getNavigatorPlatform = function(options) {
1383    if (navigator.platform) {
1384      return navigator.platform;
1385    } else {
1386      return options.NOT_AVAILABLE;
1387    }
1388  };
1389  var getDoNotTrack = function(options) {
1390    if (navigator.doNotTrack) {
1391      return navigator.doNotTrack;
1392    } else if (navigator.msDoNotTrack) {
1393      return navigator.msDoNotTrack;
1394    } else if (window.doNotTrack) {
1395      return window.doNotTrack;
1396    } else {
1397      return options.NOT_AVAILABLE;
1398    }
1399  };
1400  // This is a crude and primitive touch screen detection.
1401  // It's not possible to currently reliably detect the  availability of a touch screen
1402  // with a JS, without actually subscribing to a touch event.
1403  // http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
1404  // https://github.com/Modernizr/Modernizr/issues/548
1405  // method returns an array of 3 values:
1406  // maxTouchPoints, the success or failure of creating a TouchEvent,
1407  // and the availability of the 'ontouchstart' property
1408
1409  var getTouchSupport = function() {
1410    var maxTouchPoints = 0;
1411    var touchEvent;
1412    if (typeof navigator.maxTouchPoints !== "undefined") {
1413      maxTouchPoints = navigator.maxTouchPoints;
1414    } else if (typeof navigator.msMaxTouchPoints !== "undefined") {
1415      maxTouchPoints = navigator.msMaxTouchPoints;
1416    }
1417    try {
1418      document.createEvent("TouchEvent");
1419      touchEvent = true;
1420    } catch (_) {
1421      touchEvent = false;
1422    }
1423    var touchStart = "ontouchstart" in window;
1424    return [maxTouchPoints, touchEvent, touchStart];
1425  };
1426  // https://www.browserleaks.com/canvas#how-does-it-work
1427
1428  var getCanvasFp = function(options) {
1429    var result = [];
1430    // Very simple now, need to make it more complex (geo shapes etc)
1431    var canvas = document.createElement("canvas");
1432    canvas.width = 2000;
1433    canvas.height = 200;
1434    canvas.style.display = "inline";
1435    var ctx = canvas.getContext("2d");
1436    // detect browser support of canvas winding
1437    // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
1438    // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/canvas/winding.js
1439    ctx.rect(0, 0, 10, 10);
1440    ctx.rect(2, 2, 6, 6);
1441    result.push(
1442      "canvas winding:" +
1443        (ctx.isPointInPath(5, 5, "evenodd") === false ? "yes" : "no")
1444    );
1445
1446    ctx.textBaseline = "alphabetic";
1447    ctx.fillStyle = "#f60";
1448    ctx.fillRect(125, 1, 62, 20);
1449    ctx.fillStyle = "#069";
1450    // https://github.com/Valve/fingerprintjs2/issues/66
1451    if (options.dontUseFakeFontInCanvas) {
1452      ctx.font = "11pt Arial";
1453    } else {
1454      ctx.font = "11pt no-real-font-123";
1455    }
1456    ctx.fillText("Cwm fjordbank glyphs vext quiz, \ud83d\ude03", 2, 15);
1457    ctx.fillStyle = "rgba(102, 204, 0, 0.2)";
1458    ctx.font = "18pt Arial";
1459    ctx.fillText("Cwm fjordbank glyphs vext quiz, \ud83d\ude03", 4, 45);
1460
1461    // canvas blending
1462    // http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
1463    // http://jsfiddle.net/NDYV8/16/
1464    ctx.globalCompositeOperation = "multiply";
1465    ctx.fillStyle = "rgb(255,0,255)";
1466    ctx.beginPath();
1467    ctx.arc(50, 50, 50, 0, Math.PI * 2, true);
1468    ctx.closePath();
1469    ctx.fill();
1470    ctx.fillStyle = "rgb(0,255,255)";
1471    ctx.beginPath();
1472    ctx.arc(100, 50, 50, 0, Math.PI * 2, true);
1473    ctx.closePath();
1474    ctx.fill();
1475    ctx.fillStyle = "rgb(255,255,0)";
1476    ctx.beginPath();
1477    ctx.arc(75, 100, 50, 0, Math.PI * 2, true);
1478    ctx.closePath();
1479    ctx.fill();
1480    ctx.fillStyle = "rgb(255,0,255)";
1481    // canvas winding
1482    // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
1483    // http://jsfiddle.net/NDYV8/19/
1484    ctx.arc(75, 75, 75, 0, Math.PI * 2, true);
1485    ctx.arc(75, 75, 25, 0, Math.PI * 2, true);
1486    ctx.fill("evenodd");
1487
1488    if (canvas.toDataURL) {
1489      result.push("canvas fp:" + canvas.toDataURL());
1490    }
1491    return result;
1492  };
1493  var getWebglFp = function() {
1494    var gl;
1495    var fa2s = function(fa) {
1496      gl.clearColor(0.0, 0.0, 0.0, 1.0);
1497      gl.enable(gl.DEPTH_TEST);
1498      gl.depthFunc(gl.LEQUAL);
1499      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
1500      return "[" + fa[0] + ", " + fa[1] + "]";
1501    };
1502    var maxAnisotropy = function(gl) {
1503      var ext =
1504        gl.getExtension("EXT_texture_filter_anisotropic") ||
1505        gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic") ||
1506        gl.getExtension("MOZ_EXT_texture_filter_anisotropic");
1507      if (ext) {
1508        var anisotropy = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
1509        if (anisotropy === 0) {
1510          anisotropy = 2;
1511        }
1512        return anisotropy;
1513      } else {
1514        return null;
1515      }
1516    };
1517
1518    gl = getWebglCanvas();
1519    if (!gl) {
1520      return null;
1521    }
1522    // WebGL fingerprinting is a combination of techniques, found in MaxMind antifraud 
1522script & Augur fingerprinting.
1523    // First it draws a gradient object with shaders and convers the image to the Base64 string.
1524    // Then it enumerates all WebGL extensions & capabilities and appends them to the Base64 string, resulting in a huge WebGL string, potentially very unique on each device
1525    // Since iOS supports webgl starting from version 8.1 and 8.1 runs on several graphics chips, the results may be different across ios devices, but we need to verify it.
1526    var result = [];
1527    var vShaderTemplate =
1528      "attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}";
1529    var fShaderTemplate =
1530      "precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}";
1531    var vertexPosBuffer = gl.createBuffer();
1532    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
1533    var vertices = new Float32Array([
1534      -0.2,
1535      -0.9,
1536      0,
1537      0.4,
1538      -0.26,
1539      0,
1540      0,
1541      0.732134444,
1542      0
1543    ]);
1544    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
1545    vertexPosBuffer.itemSize = 3;
1546    vertexPosBuffer.numItems = 3;
1547    var program = gl.createProgram();
1548    var vshader = gl.createShader(gl.VERTEX_SHADER);
1549    gl.shaderSource(vshader, vShaderTemplate);
1550    gl.compileShader(vshader);
1551    var fshader = gl.createShader(gl.FRAGMENT_SHADER);
1552    gl.shaderSource(fshader, fShaderTemplate);
1553    gl.compileShader(fshader);
1554    gl.attachShader(program, vshader);
1555    gl.attachShader(program, fshader);
1556    gl.linkProgram(program);
1557    gl.useProgram(program);
1558    program.vertexPosAttrib = gl.getAttribLocation(program, "attrVertex");
1559    program.offsetUniform = gl.getUniformLocation(program, "uniformOffset");
1560    gl.enableVertexAttribArray(program.vertexPosArray);
1561    gl.vertexAttribPointer(
1562      program.vertexPosAttrib,
1563      vertexPosBuffer.itemSize,
1564      gl.FLOAT,
1565      !1,
1566      0,
1567      0
1568    );
1569    gl.uniform2f(program.offsetUniform, 1, 1);
1570    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems);
1571    try {
1572      result.push(gl.canvas.toDataURL());
1573    } catch (e) {
1574      /* .toDataURL may be absent or broken (blocked by extension) */
1575    }
1576    result.push("extensions:" + (gl.getSupportedExtensions() || []).join(";"));
1577    result.push(
1578      "webgl aliased line width range:" +
1579        fa2s(gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE))
1580    );
1581    result.push(
1582      "webgl aliased point size range:" +
1583        fa2s(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE))
1584    );
1585    result.push("webgl alpha bits:" + gl.getParameter(gl.ALPHA_BITS));
1586    result.push(
1587      "webgl antialiasing:" +
1588        (gl.getContextAttributes().antialias ? "yes" : "no")
1589    );
1590    result.push("webgl blue bits:" + gl.getParameter(gl.BLUE_BITS));
1591    result.push("webgl depth bits:" + gl.getParameter(gl.DEPTH_BITS));
1592    result.push("webgl green bits:" + gl.getParameter(gl.GREEN_BITS));
1593    result.push("webgl max anisotropy:" + maxAnisotropy(gl));
1594    result.push(
1595      "webgl max combined texture image units:" +
1596        gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS)
1597    );
1598    result.push(
1599      "webgl max cube map texture size:" +
1600        gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)
1601    );
1602    result.push(
1603      "webgl max fragment uniform vectors:" +
1604        gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS)
1605    );
1606    result.push(
1607      "webgl max render buffer size:" +
1608        gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)
1609    );
1610    result.push(
1611      "webgl max texture image units:" +
1612        gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
1613    );
1614    result.push(
1615      "webgl max texture size:" + gl.getParameter(gl.MAX_TEXTURE_SIZE)
1616    );
1617    result.push(
1618      "webgl max varying vectors:" + gl.getParameter(gl.MAX_VARYING_VECTORS)
1619    );
1620    result.push(
1621      "webgl max vertex attribs:" + gl.getParameter(gl.MAX_VERTEX_ATTRIBS)
1622    );
1623    result.push(
1624      "webgl max vertex texture image units:" +
1625        gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS)
1626    );
1627    result.push(
1628      "webgl max vertex uniform vectors:" +
1629        gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS)
1630    );
1631    result.push(
1632      "webgl max viewport dims:" + fa2s(gl.getParameter(gl.MAX_VIEWPORT_DIMS))
1633    );
1634    result.push("webgl red bits:" + gl.getParameter(gl.RED_BITS));
1635    result.push("webgl renderer:" + gl.getParameter(gl.RENDERER));
1636    result.push(
1637      "webgl shading language version:" +
1638        gl.getParameter(gl.SHADING_LANGUAGE_VERSION)
1639    );
1640    result.push("webgl stencil bits:" + gl.getParameter(gl.STENCIL_BITS));
1641    result.push("webgl vendor:" + gl.getParameter(gl.VENDOR));
1642    result.push("webgl version:" + gl.getParameter(gl.VERSION));
1643
1644    try {
1645      // Add the unmasked vendor and unmasked renderer if the debug_renderer_info extension is available
1646      var extensionDebugRendererInfo = gl.getExtension(
1647        "WEBGL_debug_renderer_info"
1648      );
1649      if (extensionDebugRendererInfo) {
1650        result.push(
1651          "webgl unmasked vendor:" +
1652            gl.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL)
1653        );
1654        result.push(
1655          "webgl unmasked renderer:" +
1656            gl.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL)
1657        );
1658      }
1659    } catch (e) {
1660      /* squelch */
1661    }
1662
1663    if (!gl.getShaderPrecisionFormat) {
1664      return result;
1665    }
1666
1667    each(["FLOAT", "INT"], function(numType) {
1668      each(["VERTEX", "FRAGMENT"], function(shader) {
1669        each(["HIGH", "MEDIUM", "LOW"], function(numSize) {
1670          each(["precision", "rangeMin", "rangeMax"], function(key) {
1671            var format = gl.getShaderPrecisionFormat(
1672              gl[shader + "_SHADER"],
1673              gl[numSize + "_" + numType]
1674            )[key];
1675            if (key !== "precision") {
1676              key = "precision " + key;
1677            }
1678            var line = [
1679              "webgl ",
1680              shader.toLowerCase(),
1681              " shader ",
1682              numSize.toLowerCase(),
1683              " ",
1684              numType.toLowerCase(),
1685              " ",
1686              key,
1687              ":",
1688              format
1689            ].join("");
1690            result.push(line);
1691          });
1692        });
1693      });
1694    });
1695    return result;
1696  };
1697  var getWebglVendorAndRenderer = function() {
1698    /* This a subset of the WebGL fingerprint with a lot of entropy, while being reasonably browser-independent */
1699    try {
1700      var glContext = getWebglCanvas();
1701      var extensionDebugRendererInfo = glContext.getExtension(
1702        "WEBGL_debug_renderer_info"
1703      );
1704      return (
1705        glContext.getParameter(
1706          extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL
1707        ) +
1708        "~" +
1709        glContext.getParameter(
1710          extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL
1711        )
1712      );
1713    } catch (e) {
1714      return null;
1715    }
1716  };
1717  var getAdBlock = function() {
1718    var ads = document.createElement("div");
1719    ads.innerHTML = "&nbsp;";
1720    ads.className = "adsbox";
1721    var result = false;
1722    try {
1723      // body may not exist, that's why we need try/catch
1724      document.body.appendChild(ads);
1725      result = document.getElementsByClassName("adsbox")[0].offsetHeight === 0;
1726      document.body.removeChild(ads);
1727    } catch (e) {
1728      result = false;
1729    }
1730    return result;
1731  };
1732  var getHasLiedLanguages = function() {
1733    // We check if navigator.language is equal to the first language of navigator.languages
1734    if (typeof navigator.languages !== "undefined") {
1735      try {
1736        var firstLanguages = navigator.languages[0].substr(0, 2);
1737        if (firstLanguages !== navigator.language.substr(0, 2)) {
1738          return true;
1739        }
1740      } catch (err) {
1741        return true;
1742      }
1743    }
1744    return false;
1745  };
1746  var getHasLiedResolution = function() {
1747    return (
1748      window.screen.width < window.screen.availWidth ||
1749      window.screen.height < window.screen.availHeight
1750    );
1751  };
1752  var getHasLiedOs = function() {
1753    var userAgent = navigator.userAgent.toLowerCase();
1754    var oscpu = navigator.oscpu;
1755    var platform = navigator.platform.toLowerCase();
1756    var os;
1757    // We extract the OS from the user agent (respect the order of the if else if statement)
1758    if (userAgent.indexOf("windows phone") >= 0) {
1759      os = "Windows Phone";
1760    } else if (userAgent.indexOf("win") >= 0) {
1761      os = "Windows";
1762    } else if (userAgent.indexOf("android") >= 0) {
1763      os = "Android";
1764    } else if (userAgent.indexOf("linux") >= 0) {
1765      os = "Linux";
1766    } else if (
1767      userAgent.indexOf("iphone") >= 0 ||
1768      userAgent.indexOf("ipad") >= 0
1769    ) {
1770      os = "iOS";
1771    } else if (userAgent.indexOf("mac") >= 0) {
1772      os = "Mac";
1773    } else {
1774      os = "Other";
1775    }
1776    // We detect if the person uses a mobile device
1777    var mobileDevice =
1778      "ontouchstart" in window ||
1779      navigator.maxTouchPoints > 0 ||
1780      navigator.msMaxTouchPoints > 0;
1781
1782    if (
1783      mobileDevice &&
1784      os !== "Windows Phone" &&
1785      os !== "Android" &&
1786      os !== "iOS" &&
1787      os !== "Other"
1788    ) {
1789      return true;
1790    }
1791
1792    // We compare oscpu with the OS extracted from the UA
1793    if (typeof oscpu !== "undefined") {
1794      oscpu = oscpu.toLowerCase();
1795      if (
1796        oscpu.indexOf("win") >= 0 &&
1797        os !== "Windows" &&
1798        os !== "Windows Phone"
1799      ) {
1800        return true;
1801      } else if (
1802        oscpu.indexOf("linux") >= 0 &&
1803        os !== "Linux" &&
1804        os !== "Android"
1805      ) {
1806        return true;
1807      } else if (oscpu.indexOf("mac") >= 0 && os !== "Mac" && os !== "iOS") {
1808        return true;
1809      } else if (
1810        (oscpu.indexOf("win") === -1 &&
1811          oscpu.indexOf("linux") === -1 &&
1812          oscpu.indexOf("mac") === -1) !==
1813        (os === "Other")
1814      ) {
1815        return true;
1816      }
1817    }
1818
1819    // We compare platform with the OS extracted from the UA
1820    if (
1821      platform.indexOf("win") >= 0 &&
1822      os !== "Windows" &&
1823      os !== "Windows Phone"
1824    ) {
1825      return true;
1826    } else if (
1827      (platform.indexOf("linux") >= 0 ||
1828        platform.indexOf("android") >= 0 ||
1829        platform.indexOf("pike") >= 0) &&
1830      os !== "Linux" &&
1831      os !== "Android"
1832    ) {
1833      return true;
1834    } else if (
1835      (platform.indexOf("mac") >= 0 ||
1836        platform.indexOf("ipad") >= 0 ||
1837        platform.indexOf("ipod") >= 0 ||
1838        platform.indexOf("iphone") >= 0) &&
1839      os !== "Mac" &&
1840      os !== "iOS"
1841    ) {
1842      return true;
1843    } else if (
1844      (platform.indexOf("win") === -1 &&
1845        platform.indexOf("linux") === -1 &&
1846        platform.indexOf("mac") === -1) !==
1847      (os === "Other")
1848    ) {
1849      return true;
1850    }
1851
1852    return (
1853      typeof navigator.plugins === "undefined" &&
1854      os !== "Windows" &&
1855      os !== "Windows Phone"
1856    );
1857  };
1858  var getHasLiedBrowser = function() {
1859    var userAgent = navigator.userAgent.toLowerCase();
1860    var productSub = navigator.productSub;
1861
1862    // we extract the browser from the user agent (respect the order of the tests)
1863    var browser;
1864    if (userAgent.indexOf("firefox") >= 0) {
1865      browser = "Firefox";
1866    } else if (
1867      userAgent.indexOf("opera") >= 0 ||
1868      userAgent.indexOf("opr") >= 0
1869    ) {
1870      browser = "Opera";
1871    } else if (userAgent.indexOf("chrome") >= 0) {
1872      browser = "Chrome";
1873    } else if (userAgent.indexOf("safari") >= 0) {
1874      browser = "Safari";
1875    } else if (userAgent.indexOf("trident") >= 0) {
1876      browser = "Internet Explorer";
1877    } else {
1878      browser = "Other";
1879    }
1880
1881    if (
1882      (browser === "Chrome" || browser === "Safari" || browser === "Opera") &&
1883      productSub !== "20030107"
1884    ) {
1885      return true;
1886    }
1887
1888    // eslint-disable-next-line no-eval
1889    var tempRes = eval.toString().length;
1890    if (
1891      tempRes === 37 &&
1892      browser !== "Safari" &&
1893      browser !== "Firefox" &&
1894      browser !== "Other"
1895    ) {
1896      return true;
1897    } else if (
1898      tempRes === 39 &&
1899      browser !== "Internet Explorer" &&
1900      browser !== "Other"
1901    ) {
1902      return true;
1903    } else if (
1904      tempRes === 33 &&
1905      browser !== "Chrome" &&
1906      browser !== "Opera" &&
1907      browser !== "Other"
1908    ) {
1909      return true;
1910    }
1911
1912    // We create an error to see how it is handled
1913    var errFirefox;
1914    try {
1915      // eslint-disable-next-line no-throw-literal
1916      throw "a";
1917    } catch (err) {
1918      try {
1919        err.toSource();
1920        errFirefox = true;
1921      } catch (errOfErr) {
1922        errFirefox = false;
1923      }
1924    }
1925    return errFirefox && browser !== "Firefox" && browser !== "Other";
1926  };
1927  var isCanvasSupported = function() {
1928    var elem = document.createElement("canvas");
1929    return !!(elem.getContext && elem.getContext("2d"));
1930  };
1931  var isWebGlSupported = function() {
1932    // code taken from Modernizr
1933    if (!isCanvasSupported()) {
1934      return false;
1935    }
1936
1937    var glContext = getWebglCanvas();
1938    return !!window.WebGLRenderingContext && !!glContext;
1939  };
1940  var isIE = function() {
1941    if (navigator.appName === "Microsoft Internet Explorer") {
1942      return true;
1943    } else if (
1944      navigator.appName === "Netscape" &&
1945      /Trident/.test(navigator.userAgent)
1946    ) {
1947      // IE 11
1948      return true;
1949    }
1950    return false;
1951  };
1952  var hasSwfObjectLoaded = function() {
1953    return typeof window.swfobject !== "undefined";
1954  };
1955  var hasMinFlashInstalled = function() {
1956    return window.swfobject.hasFlashPlayerVersion("9.0.0");
1957  };
1958  var addFlashDivNode = function(options) {
1959    var node = document.createElement("div");
1960    node.setAttribute("id", options.fonts.swfContainerId);
1961    document.body.appendChild(node);
1962  };
1963  var loadSwfAndDetectFonts = function(done, options) {
1964    var hiddenCallback = "___fp_swf_loaded";
1965    window[hiddenCallback] = function(fonts) {
1966      done(fonts);
1967    };
1968    var id = options.fonts.swfContainerId;
1969    addFlashDivNode();
1970    var flashvars = { onReady: hiddenCallback };
1971    var flashparams = { allowScriptAccess: "always", menu: "false" };
1972    window.swfobject.embedSWF(
1973      options.fonts.swfPath,
1974      id,
1975      "1",
1976      "1",
1977      "9.0.0",
1978      false,
1979      flashvars,
1980      flashparams,
1981      {}
1982    );
1983  };
1984  var getWebglCanvas = function() {
1985    var canvas = document.createElement("canvas");
1986    var gl = null;
1987    try {
1988      gl =
1989        canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1990    } catch (e) {
1991      /* squelch */
1992    }
1993    if (!gl) {
1994      gl = null;
1995    }
1996    return gl;
1997  };
1998
1999  var components = [
2000    { key: "userAgent", getData: UserAgent },
2001    { key: "language", getData: languageKey },
2002    { key: "colorDepth", getData: colorDepthKey },
2003    { key: "deviceMemory", getData: deviceMemoryKey },
2004    { key: "pixelRatio", getData: pixelRatioKey },
2005    { key: "hardwareConcurrency", getData: hardwareConcurrencyKey },
2006    { key: "screenResolution", getData: screenResolutionKey },
2007    { key: "availableScreenResolution", getData: availableScreenResolutionKey },
2008    { key: "timezoneOffset", getData: timezoneOffset },
2009    { key: "timezone", getData: timezone },
2010    { key: "sessionStorage", getData: sessionStorageKey },
2011    { key: "localStorage", getData: localStorageKey },
2012    { key: "indexedDb", getData: indexedDbKey },
2013    { key: "addBehavior", getData: addBehaviorKey },
2014    { key: "openDatabase", getData: openDatabaseKey },
2015    { key: "cpuClass", getData: cpuClassKey },
2016    { key: "platform", getData: platformKey },
2017    { key: "doNotTrack", getData: doNotTrackKey },
2018    { key: "plugins", getData: pluginsComponent },
2019    //{ key: "canvas", getData: canvasKey },
2020    //{ key: "webgl", getData: webglKey },
2021    //{key: 'webglVendorAndRenderer', getData: webglVendorAndRendererKey},
2022    { key: "hasLiedLanguages", getData: hasLiedLanguagesKey },
2023    { key: "hasLiedResolution", getData: hasLiedResolutionKey },
2024    { key: "hasLiedOs", getData: hasLiedOsKey },
2025    { key: "hasLiedBrowser", getData: hasLiedBrowserKey },
2026    { key: "touchSupport", getData: touchSupportKey },
2027    { key: "fonts", getData: jsFontsKey, pauseBefore: true },
2028    { key: "fontsFlash", getData: flashFontsKey, pauseBefore: true },
2029    { key: "audio", getData: audioKey },
2030    { key: "enumerateDevices", getData: enumerateDevicesKey }
2031  ];
2032
2033  var Fingerprint2 = function(options) {
2034    throw new Error(
2035      "'new Fingerprint()' is deprecated, see https://github.com/Valve/fingerprintjs2#upgrade-guide-from-182-to-200"
2036    );
2037  };
2038
2039  Fingerprint2.get = function(options, callback) {
2040    if (!callback) {
2041      callback = options;
2042      options = {};
2043    } else if (!options) {
2044      options = {};
2045    }
2046    extendSoft(options, defaultOptions);
2047    options.components = options.extraComponents.concat(components);
2048
2049    var keys = {
2050      data: [],
2051      addPreprocessedComponent: function(key, value) {
2052        if (typeof options.preprocessor === "function") {
2053          value = options.preprocessor(key, value);
2054        }
2055        keys.data.push({ key: key, value: value });
2056      }
2057    };
2058
2059    var i = -1;
2060    var chainComponents = function(alreadyWaited) {
2061      i += 1;
2062      if (i >= options.components.length) {
2063        // on finish
2064        callback(keys.data);
2065        return;
2066      }
2067      var component = options.components[i];
2068
2069      if (options.excludes[component.key]) {
2070        chainComponents(false); // skip
2071        return;
2072      }
2073
2074      if (!alreadyWaited && component.pauseBefore) {
2075        i -= 1;
2076        setTimeout(function() {
2077          chainComponents(true);
2078        }, 1);
2079        return;
2080      }
2081
2082      try {
2083        component.getData(function(value) {
2084          keys.addPreprocessedComponent(component.key, value);
2085          chainComponents(false);
2086        }, options);
2087      } catch (error) {
2088        // main body error
2089        keys.addPreprocessedComponent(component.key, String(error));
2090        chainComponents(false);
2091      }
2092    };
2093
2094    chainComponents(false);
2095  };
2096
2097  Fingerprint2.getPromise = function(options) {
2098    return new Promise(function(resolve, reject) {
2099      Fingerprint2.get(options, resolve);
2100    });
2101  };
2102
2103  Fingerprint2.getV18 = function(options, callback) {
2104    if (callback == null) {
2105      callback = options;
2106      options = {};
2107    }
2108    return Fingerprint2.get(options, function(components) {
2109      var newComponents = [];
2110      for (var i = 0; i < components.length; i++) {
2111        var component = components[i];
2112        if (component.value === (options.NOT_AVAILABLE || "not available")) {
2113          newComponents.push({ key: component.key, value: "unknown" });
2114        } else if (component.key === "plugins") {
2115          newComponents.push({
2116            key: "plugins",
2117            value: map(component.value, function(p) {
2118              var mimeTypes = map(p[2], function(mt) {
2119                if (mt.join) {
2120                  return mt.join("~");
2121                }
2122                return mt;
2123              }).join(",");
2124              return [p[0], p[1], mimeTypes].join("::");
2125            })
2126          });
2127        } else if (["canvas", "webgl"].indexOf(component.key) !== -1) {
2128          newComponents.push({
2129            key: component.key,
2130            value: component.value.join("~")
2131          });
2132        } else if (
2133          [
2134            "sessionStorage",
2135            "localStorage",
2136            "indexedDb",
2137            "addBehavior",
2138            "openDatabase"
2139          ].indexOf(component.key) !== -1
2140        ) {
2141          if (component.value) {
2142            newComponents.push({ key: component.key, value: 1 });
2143          } else {
2144            // skip
2145            continue;
2146          }
2147        } else {
2148          if (component.value) {
2149            newComponents.push(
2150              component.value.join
2151                ? { key: component.key, value: component.value.join(";") }
2152                : component
2153            );
2154          } else {
2155            newComponents.push({ key: component.key, value: component.value });
2156          }
2157        }
2158      }
2159      var murmur = x64hash128(
2160        map(newComponents, function(component) {
2161          return component.value;
2162        }).join("~~~"),
2163        31
2164      );
2165      callback(murmur, newComponents);
2166    });
2167  };
2168
2169  Fingerprint2.x64hash128 = x64hash128;
2170  Fingerprint2.VERSION = "2.0.0";
2171  return Fingerprint2;
2172});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.