PageSourceSearch

https://redesurftank.com.br/_cdn/fingerprint2.js

js redesurftank.com.br collected 2026-10-02 01:18:26 UTC 60,617 bytes, 1,466 lines download raw bytes

1/*
2* Fingerprintjs2 2.1.0 - Modern & flexible browser fingerprint library v2
3* https://github.com/Valve/fingerprintjs2
vendor: 9,786 bytes, lines 3-270
3
4* Copyright (c) 2020 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/*
19* This software contains code from open-source projects:
20* MurmurHash3 by Karan Lyons (https://github.com/karanlyons/murmurHash3.js)
21*/
22
23/* global define */
24(function (name, context, definition) {
25  'use strict'
26  if (typeof window !== 'undefined' && typeof define === 'function' && define.amd) { define(definition) } else if (typeof module !== 'undefined' && module.exports) { module.exports = definition() } else if (context.exports) { context.exports = definition() } else { context[name] = definition() }
27})('Fingerprint2', this, function () {
28  'use strict'
29
30  // detect if object is array
31  // only implement if no native implementation is available
32  if (typeof Array.isArray === 'undefined') {
33    Array.isArray = function (obj) {
34      return Object.prototype.toString.call(obj) === '[object Array]'
35    }
36  };
37
38  /// MurmurHash3 related functions
39
40  //
41  // Given two 64bit ints (as an array of two 32bit ints) returns the two
42  // added together as a 64bit int (as an array of two 32bit ints).
43  //
44  var x64Add = function (m, n) {
45    m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff]
46    n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff]
47    var o = [0, 0, 0, 0]
48    o[3] += m[3] + n[3]
49    o[2] += o[3] >>> 16
50    o[3] &= 0xffff
51    o[2] += m[2] + n[2]
52    o[1] += o[2] >>> 16
53    o[2] &= 0xffff
54    o[1] += m[1] + n[1]
55    o[0] += o[1] >>> 16
56    o[1] &= 0xffff
57    o[0] += m[0] + n[0]
58    o[0] &= 0xffff
59    return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]]
60  }
61
62  //
63  // Given two 64bit ints (as an array of two 32bit ints) returns the two
64  // multiplied together as a 64bit int (as an array of two 32bit ints).
65  //
66  var x64Multiply = function (m, n) {
67    m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff]
68    n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff]
69    var o = [0, 0, 0, 0]
70    o[3] += m[3] * n[3]
71    o[2] += o[3] >>> 16
72    o[3] &= 0xffff
73    o[2] += m[2] * n[3]
74    o[1] += o[2] >>> 16
75    o[2] &= 0xffff
76    o[2] += m[3] * n[2]
77    o[1] += o[2] >>> 16
78    o[2] &= 0xffff
79    o[1] += m[1] * n[3]
80    o[0] += o[1] >>> 16
81    o[1] &= 0xffff
82    o[1] += m[2] * n[2]
83    o[0] += o[1] >>> 16
84    o[1] &= 0xffff
85    o[1] += m[3] * n[1]
86    o[0] += o[1] >>> 16
87    o[1] &= 0xffff
88    o[0] += (m[0] * n[3]) + (m[1] * n[2]) + (m[2] * n[1]) + (m[3] * n[0])
89    o[0] &= 0xffff
90    return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]]
91  }
92  //
93  // Given a 64bit int (as an array of two 32bit ints) and an int
94  // representing a number of bit positions, returns the 64bit int (as an
95  // array of two 32bit ints) rotated left by that number of positions.
96  //
97  var x64Rotl = function (m, n) {
98    n %= 64
99    if (n === 32) {
100      return [m[1], m[0]]
101    } else if (n < 32) {
102      return [(m[0] << n) | (m[1] >>> (32 - n)), (m[1] << n) | (m[0] >>> (32 - n))]
103    } else {
104      n -= 32
105      return [(m[1] << n) | (m[0] >>> (32 - n)), (m[0] << n) | (m[1] >>> (32 - n))]
106    }
107  }
108  //
109  // Given a 64bit int (as an array of two 32bit ints) and an int
110  // representing a number of bit positions, returns the 64bit int (as an
111  // array of two 32bit ints) shifted left by that number of positions.
112  //
113  var x64LeftShift = function (m, n) {
114    n %= 64
115    if (n === 0) {
116      return m
117    } else if (n < 32) {
118      return [(m[0] << n) | (m[1] >>> (32 - n)), m[1] << n]
119    } else {
120      return [m[1] << (n - 32), 0]
121    }
122  }
123  //
124  // Given two 64bit ints (as an array of two 32bit ints) returns the two
125  // xored together as a 64bit int (as an array of two 32bit ints).
126  //
127  var x64Xor = function (m, n) {
128    return [m[0] ^ n[0], m[1] ^ n[1]]
129  }
130  //
131  // Given a block, returns murmurHash3's final x64 mix of that block.
132  // (`[0, h[0] >>> 1]` is a 33 bit unsigned right shift. This is the
133  // only place where we need to right shift 64bit ints.)
134  //
135  var x64Fmix = function (h) {
136    h = x64Xor(h, [0, h[0] >>> 1])
137    h = x64Multiply(h, [0xff51afd7, 0xed558ccd])
138    h = x64Xor(h, [0, h[0] >>> 1])
139    h = x64Multiply(h, [0xc4ceb9fe, 0x1a85ec53])
140    h = x64Xor(h, [0, h[0] >>> 1])
141    return h
142  }
143
144  //
145  // Given a string and an optional seed as an int, returns a 128 bit
146  // hash using the x64 flavor of MurmurHash3, as an unsigned hex.
147  //
148  var x64hash128 = function (key, seed) {
149    key = key || ''
150    seed = seed || 0
151    var remainder = key.length % 16
152    var bytes = key.length - remainder
153    var h1 = [0, seed]
154    var h2 = [0, seed]
155    var k1 = [0, 0]
156    var k2 = [0, 0]
157    var c1 = [0x87c37b91, 0x114253d5]
158    var c2 = [0x4cf5ad43, 0x2745937f]
159    for (var i = 0; i < bytes; i = i + 16) {
160      k1 = [((key.charCodeAt(i + 4) & 0xff)) | ((key.charCodeAt(i + 5) & 0xff) << 8) | ((key.charCodeAt(i + 6) & 0xff) << 16) | ((key.charCodeAt(i + 7) & 0xff) << 24), ((key.charCodeAt(i) & 0xff)) | ((key.charCodeAt(i + 1) & 0xff) << 8) | ((key.charCodeAt(i + 2) & 0xff) << 16) | ((key.charCodeAt(i + 3) & 0xff) << 24)]
161      k2 = [((key.charCodeAt(i + 12) & 0xff)) | ((key.charCodeAt(i + 13) & 0xff) << 8) | ((key.charCodeAt(i + 14) & 0xff) << 16) | ((key.charCodeAt(i + 15) & 0xff) << 24), ((key.charCodeAt(i + 8) & 0xff)) | ((key.charCodeAt(i + 9) & 0xff) << 8) | ((key.charCodeAt(i + 10) & 0xff) << 16) | ((key.charCodeAt(i + 11) & 0xff) << 24)]
162      k1 = x64Multiply(k1, c1)
163      k1 = x64Rotl(k1, 31)
164      k1 = x64Multiply(k1, c2)
165      h1 = x64Xor(h1, k1)
166      h1 = x64Rotl(h1, 27)
167      h1 = x64Add(h1, h2)
168      h1 = x64Add(x64Multiply(h1, [0, 5]), [0, 0x52dce729])
169      k2 = x64Multiply(k2, c2)
170      k2 = x64Rotl(k2, 33)
171      k2 = x64Multiply(k2, c1)
172      h2 = x64Xor(h2, k2)
173      h2 = x64Rotl(h2, 31)
174      h2 = x64Add(h2, h1)
175      h2 = x64Add(x64Multiply(h2, [0, 5]), [0, 0x38495ab5])
176    }
177    k1 = [0, 0]
178    k2 = [0, 0]
179    switch (remainder) {
180      case 15:
181        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 14)], 48))
182      // fallthrough
183      case 14:
184        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 13)], 40))
185      // fallthrough
186      case 13:
187        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 12)], 32))
188      // fallthrough
189      case 12:
190        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 11)], 24))
191      // fallthrough
192      case 11:
193        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 10)], 16))
194      // fallthrough
195      case 10:
196        k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 9)], 8))
197      // fallthrough
198      case 9:
199        k2 = x64Xor(k2, [0, key.charCodeAt(i + 8)])
200        k2 = x64Multiply(k2, c2)
201        k2 = x64Rotl(k2, 33)
202        k2 = x64Multiply(k2, c1)
203        h2 = x64Xor(h2, k2)
204      // fallthrough
205      case 8:
206        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 7)], 56))
207      // fallthrough
208      case 7:
209        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 6)], 48))
210      // fallthrough
211      case 6:
212        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 5)], 40))
213      // fallthrough
214      case 5:
215        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 4)], 32))
216      // fallthrough
217      case 4:
218        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 3)], 24))
219      // fallthrough
220      case 3:
221        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 2)], 16))
222      // fallthrough
223      case 2:
224        k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 1)], 8))
225      // fallthrough
226      case 1:
227        k1 = x64Xor(k1, [0, key.charCodeAt(i)])
228        k1 = x64Multiply(k1, c1)
229        k1 = x64Rotl(k1, 31)
230        k1 = x64Multiply(k1, c2)
231        h1 = x64Xor(h1, k1)
232      // fallthrough
233    }
234    h1 = x64Xor(h1, [0, key.length])
235    h2 = x64Xor(h2, [0, key.length])
236    h1 = x64Add(h1, h2)
237    h2 = x64Add(h2, h1)
238    h1 = x64Fmix(h1)
239    h2 = x64Fmix(h2)
240    h1 = x64Add(h1, h2)
241    h2 = x64Add(h2, h1)
242    return ('00000000' + (h1[0] >>> 0).toString(16)).slice(-8) + ('00000000' + (h1[1] >>> 0).toString(16)).slice(-8) + ('00000000' + (h2[0] >>> 0).toString(16)).slice(-8) + ('00000000' + (h2[1] >>> 0).toString(16)).slice(-8)
243  }
244
245  var defaultOptions = {
246    preprocessor: null,
247    audio: {
248      timeout: 1000,
249      // On iOS 11, audio context can only be used in response to user interaction.
250      // We require users to explicitly enable audio fingerprinting on iOS 11.
251      // See https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088
252      excludeIOS11: true
253    },
254    fonts: {
255      swfContainerId: 'fingerprintjs2',
256      swfPath: 'flash/compiled/FontList.swf',
257      userDefinedFonts: [],
258      extendedJsFonts: false
259    },
260    screen: {
261      // To ensure consistent fingerprints when users rotate their mobile devices
262      detectScreenOrientation: true
263    },
264    plugins: {
265      sortPluginsFor: [/palemoon/i],
266      excludeIE: false
267    },
268    extraComponents: [],
269    excludes: {
270      // Unreliable on Windows, see https://github.com/
vendor: 6,792 bytes, lines 270-470
270Valve/fingerprintjs2/issues/375
271      'enumerateDevices': true,
272      // devicePixelRatio depends on browser zoom, and it's impossible to detect browser zoom
273      'pixelRatio': true,
274      // DNT depends on incognito mode for some browsers (Chrome) and it's impossible to detect incognito mode
275      'doNotTrack': true,
276      // uses js fonts already
277      'fontsFlash': true
278    },
279    NOT_AVAILABLE: 'not available',
280    ERROR: 'error',
281    EXCLUDED: 'excluded'
282  }
283
284  var each = function (obj, iterator) {
285    if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
286      obj.forEach(iterator)
287    } else if (obj.length === +obj.length) {
288      for (var i = 0, l = obj.length; i < l; i++) {
289        iterator(obj[i], i, obj)
290      }
291    } else {
292      for (var key in obj) {
293        if (obj.hasOwnProperty(key)) {
294          iterator(obj[key], key, obj)
295        }
296      }
297    }
298  }
299
300  var map = function (obj, iterator) {
301    var results = []
302    // Not using strict equality so that this acts as a
303    // shortcut to checking for `null` and `undefined`.
304    if (obj == null) {
305      return results
306    }
307    if (Array.prototype.map && obj.map === Array.prototype.map) { return obj.map(iterator) }
308    each(obj, function (value, index, list) {
309      results.push(iterator(value, index, list))
310    })
311    return results
312  }
313
314  var extendSoft = function (target, source) {
315    if (source == null) { return target }
316    var value
317    var key
318    for (key in source) {
319      value = source[key]
320      if (value != null && !(Object.prototype.hasOwnProperty.call(target, key))) {
321        target[key] = value
322      }
323    }
324    return target
325  }
326
327  // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
328  var enumerateDevicesKey = function (done, options) {
329    if (!isEnumerateDevicesSupported()) {
330      return done(options.NOT_AVAILABLE)
331    }
332    navigator.mediaDevices.enumerateDevices().then(function (devices) {
333      done(devices.map(function (device) {
334        return 'id=' + device.deviceId + ';gid=' + device.groupId + ';' + device.kind + ';' + device.label
335      }))
336    })['catch'](function (error) {
337      done(error)
338    })
339  }
340
341  var isEnumerateDevicesSupported = function () {
342    return (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices)
343  }
344  // Inspired by and based on https://github.com/cozylife/audio-fingerprint
345  var audioKey = function (done, options) {
346    var audioOptions = options.audio
347    if (audioOptions.excludeIOS11 && navigator.userAgent.match(/OS 11.+Version\/11.+Safari/)) {
348      // See comment for excludeUserAgent and https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088
349      return done(options.EXCLUDED)
350    }
351
352    var AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext
353
354    if (AudioContext == null) {
355      return done(options.NOT_AVAILABLE)
356    }
357
358    var context = new AudioContext(1, 44100, 44100)
359
360    var oscillator = context.createOscillator()
361    oscillator.type = 'triangle'
362    oscillator.frequency.setValueAtTime(10000, context.currentTime)
363
364    var compressor = context.createDynamicsCompressor()
365    each([
366      ['threshold', -50],
367      ['knee', 40],
368      ['ratio', 12],
369      ['reduction', -20],
370      ['attack', 0],
371      ['release', 0.25]
372    ], function (item) {
373      if (compressor[item[0]] !== undefined && typeof compressor[item[0]].setValueAtTime === 'function') {
374        compressor[item[0]].setValueAtTime(item[1], context.currentTime)
375      }
376    })
377
378    oscillator.connect(compressor)
379    compressor.connect(context.destination)
380    oscillator.start(0)
381    context.startRendering()
382
383    var audioTimeoutId = setTimeout(function () {
384      console.warn('Audio fingerprint timed out. Please report bug at https://github.com/Valve/fingerprintjs2 with your user agent: "' + navigator.userAgent + '".')
385      context.oncomplete = function () { }
386      context = null
387      return done('audioTimeout')
388    }, audioOptions.timeout)
389
390    context.oncomplete = function (event) {
391      var fingerprint
392      try {
393        clearTimeout(audioTimeoutId)
394        fingerprint = event.renderedBuffer.getChannelData(0)
395          .slice(4500, 5000)
396          .reduce(function (acc, val) { return acc + Math.abs(val) }, 0)
397          .toString()
398        oscillator.disconnect()
399        compressor.disconnect()
400      } catch (error) {
401        done(error)
402        return
403      }
404      done(fingerprint)
405    }
406  }
407  var UserAgent = function (done) {
408    done(navigator.userAgent)
409  }
410  var webdriver = function (done, options) {
411    done(navigator.webdriver == null ? options.NOT_AVAILABLE : navigator.webdriver)
412  }
413  var languageKey = function (done, options) {
414    done(navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage || options.NOT_AVAILABLE)
415  }
416  var colorDepthKey = function (done, options) {
417    done(window.screen.colorDepth || options.NOT_AVAILABLE)
418  }
419  var deviceMemoryKey = function (done, options) {
420    done(navigator.deviceMemory || options.NOT_AVAILABLE)
421  }
422  var pixelRatioKey = function (done, options) {
423    done(window.devicePixelRatio || options.NOT_AVAILABLE)
424  }
425  var screenResolutionKey = function (done, options) {
426    done(getScreenResolution(options))
427  }
428  var getScreenResolution = function (options) {
429    var resolution = [window.screen.width, window.screen.height]
430    if (options.screen.detectScreenOrientation) {
431      resolution.sort().reverse()
432    }
433    return resolution
434  }
435  var availableScreenResolutionKey = function (done, options) {
436    done(getAvailableScreenResolution(options))
437  }
438  var getAvailableScreenResolution = function (options) {
439    if (window.screen.availWidth && window.screen.availHeight) {
440      var available = [window.screen.availHeight, window.screen.availWidth]
441      if (options.screen.detectScreenOrientation) {
442        available.sort().reverse()
443      }
444      return available
445    }
446    // headless browsers
447    return options.NOT_AVAILABLE
448  }
449  var timezoneOffset = function (done) {
450    done(new Date().getTimezoneOffset())
451  }
452  var timezone = function (done, options) {
453    if (window.Intl && window.Intl.DateTimeFormat) {
454      done(new window.Intl.DateTimeFormat().resolvedOptions().timeZone)
455      return
456    }
457    done(options.NOT_AVAILABLE)
458  }
459  var sessionStorageKey = function (done, options) {
460    done(hasSessionStorage(options))
461  }
462  var localStorageKey = function (done, options) {
463    done(hasLocalStorage(options))
464  }
465  var indexedDbKey = function (done, options) {
466    done(hasIndexedDB(options))
467  }
468  var addBehaviorKey = function (done) {
469    // body might not be defined at this point or removed programmatically
470    done(!!(document.body && document.body.addBehavior)
vendor: 18,093 bytes, lines 470-842
470)
471  }
472  var openDatabaseKey = function (done) {
473    done(!!window.openDatabase)
474  }
475  var cpuClassKey = function (done, options) {
476    done(getNavigatorCpuClass(options))
477  }
478  var platformKey = function (done, options) {
479    done(getNavigatorPlatform(options))
480  }
481  var doNotTrackKey = function (done, options) {
482    done(getDoNotTrack(options))
483  }
484  var canvasKey = function (done, options) {
485    if (isCanvasSupported()) {
486      done(getCanvasFp(options))
487      return
488    }
489    done(options.NOT_AVAILABLE)
490  }
491  var webglKey = function (done, options) {
492    if (isWebGlSupported()) {
493      done(getWebglFp())
494      return
495    }
496    done(options.NOT_AVAILABLE)
497  }
498  var webglVendorAndRendererKey = function (done) {
499    if (isWebGlSupported()) {
500      done(getWebglVendorAndRenderer())
501      return
502    }
503    done()
504  }
505  var adBlockKey = function (done) {
506    done(getAdBlock())
507  }
508  var hasLiedLanguagesKey = function (done) {
509    done(getHasLiedLanguages())
510  }
511  var hasLiedResolutionKey = function (done) {
512    done(getHasLiedResolution())
513  }
514  var hasLiedOsKey = function (done) {
515    done(getHasLiedOs())
516  }
517  var hasLiedBrowserKey = function (done) {
518    done(getHasLiedBrowser())
519  }
520  // flash fonts (will increase fingerprinting time 20X to ~ 130-150ms)
521  var flashFontsKey = function (done, options) {
522    // we do flash if swfobject is loaded
523    if (!hasSwfObjectLoaded()) {
524      return done('swf object not loaded')
525    }
526    if (!hasMinFlashInstalled()) {
527      return done('flash not installed')
528    }
529    if (!options.fonts.swfPath) {
530      return done('missing options.fonts.swfPath')
531    }
532    loadSwfAndDetectFonts(function (fonts) {
533      done(fonts)
534    }, options)
535  }
536  // kudos to http://www.lalit.org/lab/javascript-css-font-detect/
537  var jsFontsKey = function (done, options) {
538    // a font will be compared against all the three default fonts.
539    // and if it doesn't match all 3 then that font is not available.
540    var baseFonts = ['monospace', 'sans-serif', 'serif']
541
542    var fontList = [
543      'Andale Mono', 'Arial', 'Arial Black', 'Arial Hebrew', 'Arial MT', 'Arial Narrow', 'Arial Rounded MT Bold', 'Arial Unicode MS',
544      'Bitstream Vera Sans Mono', 'Book Antiqua', 'Bookman Old Style',
545      'Calibri', 'Cambria', 'Cambria Math', 'Century', 'Century Gothic', 'Century Schoolbook', 'Comic Sans', 'Comic Sans MS', 'Consolas', 'Courier', 'Courier New',
546      'Geneva', 'Georgia',
547      'Helvetica', 'Helvetica Neue',
548      'Impact',
549      'Lucida Bright', 'Lucida Calligraphy', 'Lucida Console', 'Lucida Fax', 'LUCIDA GRANDE', 'Lucida Handwriting', 'Lucida Sans', 'Lucida Sans Typewriter', 'Lucida Sans Unicode',
550      'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS Outlook', 'MS PGothic', 'MS Reference Sans Serif', 'MS Sans Serif', 'MS Serif', 'MYRIAD', 'MYRIAD PRO',
551      'Palatino', 'Palatino Linotype',
552      'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Light', 'Segoe UI Semibold', 'Segoe UI Symbol',
553      'Tahoma', 'Times', 'Times New Roman', 'Times New Roman PS', 'Trebuchet MS',
554      'Verdana', 'Wingdings', 'Wingdings 2', 'Wingdings 3'
555    ]
556
557    if (options.fonts.extendedJsFonts) {
558      var extendedFontList = [
559        'Abadi MT Condensed Light', 'Academy Engraved LET', 'ADOBE CASLON PRO', 'Adobe Garamond', 'ADOBE GARAMOND PRO', 'Agency FB', 'Aharoni', 'Albertus Extra Bold', 'Albertus Medium', 'Algerian', 'Amazone BT', 'American Typewriter',
560        'American Typewriter Condensed', 'AmerType Md BT', 'Andalus', 'Angsana New', 'AngsanaUPC', 'Antique Olive', 'Aparajita', 'Apple Chancery', 'Apple Color Emoji', 'Apple SD Gothic Neo', 'Arabic Typesetting', 'ARCHER',
561        'ARNO PRO', 'Arrus BT', 'Aurora Cn BT', 'AvantGarde Bk BT', 'AvantGarde Md BT', 'AVENIR', 'Ayuthaya', 'Bandy', 'Bangla Sangam MN', 'Bank Gothic', 'BankGothic Md BT', 'Baskerville',
562        'Baskerville Old Face', 'Batang', 'BatangChe', 'Bauer Bodoni', 'Bauhaus 93', 'Bazooka', 'Bell MT', 'Bembo', 'Benguiat Bk BT', 'Berlin Sans FB', 'Berlin Sans FB Demi', 'Bernard MT Condensed', 'BernhardFashion BT', 'BernhardMod BT', 'Big Caslon', 'BinnerD',
563        'Blackadder ITC', 'BlairMdITC TT', 'Bodoni 72', 'Bodoni 72 Oldstyle', 'Bodoni 72 Smallcaps', 'Bodoni MT', 'Bodoni MT Black', 'Bodoni MT Condensed', 'Bodoni MT Poster Compressed',
564        'Bookshelf Symbol 7', 'Boulder', 'Bradley Hand', 'Bradley Hand ITC', 'Bremen Bd BT', 'Britannic Bold', 'Broadway', 'Browallia New', 'BrowalliaUPC', 'Brush Script MT', 'Californian FB', 'Calisto MT', 'Calligrapher', 'Candara',
565        'CaslonOpnface BT', 'Castellar', 'Centaur', 'Cezanne', 'CG Omega', 'CG Times', 'Chalkboard', 'Chalkboard SE', 'Chalkduster', 'Charlesworth', 'Charter Bd BT', 'Charter BT', 'Chaucer',
566        'ChelthmITC Bk BT', 'Chiller', 'Clarendon', 'Clarendon Condensed', 'CloisterBlack BT', 'Cochin', 'Colonna MT', 'Constantia', 'Cooper Black', 'Copperplate', 'Copperplate Gothic', 'Copperplate Gothic Bold',
567        'Copperplate Gothic Light', 'CopperplGoth Bd BT', 'Corbel', 'Cordia New', 'CordiaUPC', 'Cornerstone', 'Coronet', 'Cuckoo', 'Curlz MT', 'DaunPenh', 'Dauphin', 'David', 'DB LCD Temp', 'DELICIOUS', 'Denmark',
568        'DFKai-SB', 'Didot', 'DilleniaUPC', 'DIN', 'DokChampa', 'Dotum', 'DotumChe', 'Ebrima', 'Edwardian Script ITC', 'Elephant', 'English 111 Vivace BT', 'Engravers MT', 'EngraversGothic BT', 'Eras Bold ITC', 'Eras Demi ITC', 'Eras Light ITC', 'Eras Medium ITC',
569        'EucrosiaUPC', 'Euphemia', 'Euphemia UCAS', 'EUROSTILE', 'Exotc350 Bd BT', 'FangSong', 'Felix Titling', 'Fixedsys', 'FONTIN', 'Footlight MT Light', 'Forte',
570        'FrankRuehl', 'Fransiscan', 'Freefrm721 Blk BT', 'FreesiaUPC', 'Freestyle Script', 'French Script MT', 'FrnkGothITC Bk BT', 'Fruitger', 'FRUTIGER',
571        'Futura', 'Futura Bk BT', 'Futura Lt BT', 'Futura Md BT', 'Futura ZBlk BT', 'FuturaBlack BT', 'Gabriola', 'Galliard BT', 'Gautami', 'Geeza Pro', 'Geometr231 BT', 'Geometr231 Hv BT', 'Geometr231 Lt BT', 'GeoSlab 703 Lt BT',
572        'GeoSlab 703 XBd BT', 'Gigi', 'Gill Sans', 'Gill Sans MT', 'Gill Sans MT Condensed', 'Gill Sans MT Ext Condensed Bold', 'Gill Sans Ultra Bold', 'Gill Sans Ultra Bold Condensed', 'Gisha', 'Gloucester MT Extra Condensed', 'GOTHAM', 'GOTHAM BOLD',
573        'Goudy Old Style', 'Goudy Stout', 'GoudyHandtooled BT', 'GoudyOLSt BT', 'Gujarati Sangam MN', 'Gulim', 'GulimChe', 'Gungsuh', 'GungsuhChe', 'Gurmukhi MN', 'Haettenschweiler', 'Harlow Solid Italic', 'Harrington', 'Heather', 'Heiti SC', 'Heiti TC', 'HELV',
574        'Herald', 'High Tower Text', 'Hiragino Kaku Gothic ProN', 'Hiragino Mincho ProN', 'Hoefler Text', 'Humanst 521 Cn BT', 'Humanst521 BT', 'Humanst521 Lt BT', 'Imprint MT Shadow', 'Incised901 Bd BT', 'Incised901 BT',
575        'Incised901 Lt BT', 'INCONSOLATA', 'Informal Roman', 'Informal011 BT', 'INTERSTATE', 'IrisUPC', 'Iskoola Pota', 'JasmineUPC', 'Jazz LET', 'Jenson', 'Jester', 'Jokerman', 'Juice ITC', 'Kabel Bk BT', 'Kabel Ult BT', 'Kailasa', 'KaiTi', 'Kalinga', 'Kannada Sangam MN',
576        'Kartika', 'Kaufmann Bd BT', 'Kaufmann BT', 'Khmer UI', 'KodchiangUPC', 'Kokila', 'Korinna BT', 'Kristen ITC', 'Krungthep', 'Kunstler Script', 'Lao UI', 'Latha', 'Leelawadee', 'Letter Gothic', 'Levenim MT', 'LilyUPC', 'Lithograph', 'Lithograph Light', 'Long Island',
577        'Lydian BT', 'Magneto', 'Maiandra GD', 'Malayalam Sangam MN', 'Malgun Gothic',
578        'Mangal', 'Marigold', 'Marion', 'Marker Felt', 'Market', 'Marlett', 'Matisse ITC', 'Matura MT Script Capitals', 'Meiryo', 'Meiryo UI', 'Microsoft Himalaya', 'Microsoft JhengHei', 'Microsoft New Tai Lue', 'Microsoft PhagsPa', 'Microsoft Tai Le',
579        'Microsoft Uighur', 'Microsoft YaHei', 'Microsoft Yi Baiti', 'MingLiU', 'MingLiU_HKSCS', 'MingLiU_HKSCS-ExtB', 'MingLiU-ExtB', 'Minion', 'Minion Pro', 'Miriam', 'Miriam Fixed', 'Mistral', 'Modern', 'Modern No. 20', 'Mona Lisa Solid ITC TT', 'Mongolian Baiti',
580        'MONO', 'MoolBoran', 'Mrs Eaves', 'MS LineDraw', 'MS Mincho', 'MS PMincho', 'MS Reference Specialty', 'MS UI Gothic', 'MT Extra', 'MUSEO', 'MV Boli',
581        'Nadeem', 'Narkisim', 'NEVIS', 'News Gothic', 'News GothicMT', 'NewsGoth BT', 'Niagara Engraved', 'Niagara Solid', 'Noteworthy', 'NSimSun', 'Nyala', 'OCR A Extended', 'Old Century', 'Old English Text MT', 'Onyx', 'Onyx BT', 'OPTIMA', 'Oriya Sangam MN',
582        'OSAKA', 'OzHandicraft BT', 'Palace Script MT', 'Papyrus', 'Parchment', 'Party LET', 'Pegasus', 'Perpetua', 'Perpetua Titling MT', 'PetitaBold', 'Pickwick', 'Plantagenet Cherokee', 'Playbill', 'PMingLiU', 'PMingLiU-ExtB',
583        'Poor Richard', 'Poster', 'PosterBodoni BT', 'PRINCETOWN LET', 'Pristina', 'PTBarnum BT', 'Pythagoras', 'Raavi', 'Rage Italic', 'Ravie', 'Ribbon131 Bd BT', 'Rockwell', 'Rockwell Condensed', 'Rockwell Extra Bold', 'Rod', 'Roman', 'Sakkal Majalla',
584        'Santa Fe LET', 'Savoye LET', 'Sceptre', 'Script', 'Script MT Bold', 'SCRIPTINA', 'Serifa', 'Serifa BT', 'Serifa Th BT', 'ShelleyVolante BT', 'Sherwood',
585        'Shonar Bangla', 'Showcard Gothic', 'Shruti', 'Signboard', 'SILKSCREEN', 'SimHei', 'Simplified Arabic', 'Simplified Arabic Fixed', 'SimSun', 'SimSun-ExtB', 'Sinhala Sangam MN', 'Sketch Rockwell', 'Skia', 'Small Fonts', 'Snap ITC', 'Snell Roundhand', 'Socket',
586        'Souvenir Lt BT', 'Staccato222 BT', 'Steamer', 'Stencil', 'Storybook', 'Styllo', 'Subway', 'Swis721 BlkEx BT', 'Swiss911 XCm BT', 'Sylfaen', 'Synchro LET', 'System', 'Tamil Sangam MN', 'Technical', 'Teletype', 'Telugu Sangam MN', 'Tempus Sans ITC',
587        'Terminal', 'Thonburi', 'Traditional Arabic', 'Trajan', 'TRAJAN PRO', 'Tristan', 'Tubular', 'Tunga', 'Tw Cen MT', 'Tw Cen MT Condensed', 'Tw Cen MT Condensed Extra Bold',
588        'TypoUpright BT', 'Unicorn', 'Univers', 'Univers CE 55 Medium', 'Univers Condensed', 'Utsaah', 'Vagabond', 'Vani', 'Vijaya', 'Viner Hand ITC', 'VisualUI', 'Vivaldi', 'Vladimir Script', 'Vrinda', 'Westminster', 'WHITNEY', 'Wide Latin',
589        'ZapfEllipt BT', 'ZapfHumnst BT', 'ZapfHumnst Dm BT', 'Zapfino', 'Zurich BlkEx BT', 'Zurich Ex BT', 'ZWAdobeF']
590      fontList = fontList.concat(extendedFontList)
591    }
592
593    fontList = fontList.concat(options.fonts.userDefinedFonts)
594
595    // remove duplicate fonts
596    fontList = fontList.filter(function (font, position) {
597      return fontList.indexOf(font) === position
598    })
599
600    // we use m or w because these two characters take up the maximum width.
601    // And we use a LLi so that the same matching fonts can get separated
602    var testString = 'mmmmmmmmmmlli'
603
604    // we test using 72px font size, we may use any size. I guess larger the better.
605    var testSize = '72px'
606
607    var h = document.getElementsByTagName('body')[0]
608
609    // div to load spans for the base fonts
610    var baseFontsDiv = document.createElement('div')
611
612    // div to load spans for the fonts to detect
613    var fontsDiv = document.createElement('div')
614
615    var defaultWidth = {}
616    var defaultHeight = {}
617
618    // creates a span where the fonts will be loaded
619    var createSpan = function () {
620      var s = document.createElement('span')
621      /*
622       * We need this css as in some weird browser this
623       * span elements shows up for a microSec which creates a
624       * bad user experience
625       */
626      s.style.position = 'absolute'
627      s.style.left = '-9999px'
628      s.style.fontSize = testSize
629
630      // css font reset to reset external styles
631      s.style.fontStyle = 'normal'
632      s.style.fontWeight = 'normal'
633      s.style.letterSpacing = 'normal'
634      s.style.lineBreak = 'auto'
635      s.style.lineHeight = 'normal'
636      s.style.textTransform = 'none'
637      s.style.textAlign = 'left'
638      s.style.textDecoration = 'none'
639      s.style.textShadow = 'none'
640      s.style.whiteSpace = 'normal'
641      s.style.wordBreak = 'normal'
642      s.style.wordSpacing = 'normal'
643
644      s.innerHTML = testString
645      return s
646    }
647
648    // creates a span and load the font to detect and a base font for fallback
649    var createSpanWithFonts = function (fontToDetect, baseFont) {
650      var s = createSpan()
651      s.style.fontFamily = "'" + fontToDetect + "'," + baseFont
652      return s
653    }
654
655    // creates spans for the base fonts and adds them to baseFontsDiv
656    var initializeBaseFontsSpans = function () {
657      var spans = []
658      for (var index = 0, length = baseFonts.length; index < length; index++) {
659        var s = createSpan()
660        s.style.fontFamily = baseFonts[index]
661        baseFontsDiv.appendChild(s)
662        spans.push(s)
663      }
664      return spans
665    }
666
667    // creates spans for the fonts to detect and adds them to fontsDiv
668    var initializeFontsSpans = function () {
669      var spans = {}
670      for (var i = 0, l = fontList.length; i < l; i++) {
671        var fontSpans = []
672        for (var j = 0, numDefaultFonts = baseFonts.length; j < numDefaultFonts; j++) {
673          var s = createSpanWithFonts(fontList[i], baseFonts[j])
674          fontsDiv.appendChild(s)
675          fontSpans.push(s)
676        }
677        spans[fontList[i]] = fontSpans // Stores {fontName : [spans for that font]}
678      }
679      return spans
680    }
681
682    // checks if a font is available
683    var isFontAvailable = function (fontSpans) {
684      var detected = false
685      for (var i = 0; i < baseFonts.length; i++) {
686        detected = (fontSpans[i].offsetWidth !== defaultWidth[baseFonts[i]] || fontSpans[i].offsetHeight !== defaultHeight[baseFonts[i]])
687        if (detected) {
688          return detected
689        }
690      }
691      return detected
692    }
693
694    // create spans for base fonts
695    var baseFontsSpans = initializeBaseFontsSpans()
696
697    // add the spans to the DOM
698    h.appendChild(baseFontsDiv)
699
700    // get the default width for the three base fonts
701    for (var index = 0, length = baseFonts.length; index < length; index++) {
702      defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth // width for the default font
703      defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight // height for the default font
704    }
705
706    // create spans for fonts to detect
707    var fontsSpans = initializeFontsSpans()
708
709    // add all the spans to the DOM
710    h.appendChild(fontsDiv)
711
712    // check available fonts
713    var available = []
714    for (var i = 0, l = fontList.length; i < l; i++) {
715      if (isFontAvailable(fontsSpans[fontList[i]])) {
716        available.push(fontList[i])
717      }
718    }
719
720    // remove spans from DOM
721    h.removeChild(fontsDiv)
722    h.removeChild(baseFontsDiv)
723    done(available)
724  }
725  var pluginsComponent = function (done, options) {
726    if (isIE()) {
727      if (!options.plugins.excludeIE) {
728        done(getIEPlugins(options))
729      } else {
730        done(options.EXCLUDED)
731      }
732    } else {
733      done(getRegularPlugins(options))
734    }
735  }
736  var getRegularPlugins = function (options) {
737    if (navigator.plugins == null) {
738      return options.NOT_AVAILABLE
739    }
740
741    var plugins = []
742    // plugins isn't defined in Node envs.
743    for (var i = 0, l = navigator.plugins.length; i < l; i++) {
744      if (navigator.plugins[i]) { plugins.push(navigator.plugins[i]) }
745    }
746
747    // sorting plugins only for those user agents, that we know randomize the plugins
748    // every time we try to enumerate them
749    if (pluginsShouldBeSorted(options)) {
750      plugins = plugins.sort(function (a, b) {
751        if (a.name > b.name) { return 1 }
752        if (a.name < b.name) { return -1 }
753        return 0
754      })
755    }
756    return map(plugins, function (p) {
757      var mimeTypes = map(p, function (mt) {
758        return [mt.type, mt.suffixes]
759      })
760      return [p.name, p.description, mimeTypes]
761    })
762  }
763  var getIEPlugins = function (options) {
764    var result = []
765    if ((Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, 'ActiveXObject')) || ('ActiveXObject' in window)) {
766      var names = [
767        'AcroPDF.PDF', // Adobe PDF reader 7+
768        'Adodb.Stream',
769        'AgControl.AgControl', // Silverlight
770        'DevalVRXCtrl.DevalVRXCtrl.1',
771        'MacromediaFlashPaper.MacromediaFlashPaper',
772        'Msxml2.DOMDocument',
773        'Msxml2.XMLHTTP',
774        'PDF.PdfCtrl', // Adobe PDF reader 6 and earlier, brrr
775        'QuickTime.QuickTime', // QuickTime
776        'QuickTimeCheckObject.QuickTimeCheck.1',
777        'RealPlayer',
778        'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)',
779        'RealVideo.RealVideo(tm) ActiveX Control (32-bit)',
780        'Scripting.Dictionary',
781        'SWCtl.SWCtl', // ShockWave player
782        'Shell.UIHelper',
783        'ShockwaveFlash.ShockwaveFlash', // flash plugin
784        'Skype.Detection',
785        'TDCCtl.TDCCtl',
786        'WMPlayer.OCX', // Windows media player
787        'rmocx.RealPlayer G2 Control',
788        'rmocx.RealPlayer G2 Control.1'
789      ]
790      // starting to detect plugins in IE
791      result = map(names, function (name) {
792        try {
793          // eslint-disable-next-line no-new
794          new window.ActiveXObject(name)
795          return name
796        } catch (e) {
797          return options.ERROR
798        }
799      })
800    } else {
801      result.push(options.NOT_AVAILABLE)
802    }
803    if (navigator.plugins) {
804      result = result.concat(getRegularPlugins(options))
805    }
806    return result
807  }
808  var pluginsShouldBeSorted = function (options) {
809    var should = false
810    for (var i = 0, l = options.plugins.sortPluginsFor.length; i < l; i++) {
811      var re = options.plugins.sortPluginsFor[i]
812      if (navigator.userAgent.match(re)) {
813        should = true
814        break
815      }
816    }
817    return should
818  }
819  var touchSupportKey = function (done) {
820    done(getTouchSupport())
821  }
822  var hardwareConcurrencyKey = function (done, options) {
823    done(getHardwareConcurrency(options))
824  }
825  var hasSessionStorage = function (options) {
826    try {
827      return !!window.sessionStorage
828    } catch (e) {
829      return options.ERROR // SecurityError when referencing it means it exists
830    }
831  }
832
833  // https://bugzilla.mozilla.org/show_bug.cgi?id=781447
834  var hasLocalStorage = function (options) {
835    try {
836      return !!window.localStorage
837    } catch (e) {
838      return options.ERROR // SecurityError when referencing it means it exists
839    }
840  }
841  var hasIndexedDB = function (options) {
842    
vendor: 2,586 bytes, lines 842-915
842try {
843      return !!window.indexedDB
844    } catch (e) {
845      return options.ERROR // SecurityError when referencing it means it exists
846    }
847  }
848  var getHardwareConcurrency = function (options) {
849    if (navigator.hardwareConcurrency) {
850      return navigator.hardwareConcurrency
851    }
852    return options.NOT_AVAILABLE
853  }
854  var getNavigatorCpuClass = function (options) {
855    return navigator.cpuClass || options.NOT_AVAILABLE
856  }
857  var getNavigatorPlatform = function (options) {
858    if (navigator.platform) {
859      return navigator.platform
860    } else {
861      return options.NOT_AVAILABLE
862    }
863  }
864  var getDoNotTrack = function (options) {
865    if (navigator.doNotTrack) {
866      return navigator.doNotTrack
867    } else if (navigator.msDoNotTrack) {
868      return navigator.msDoNotTrack
869    } else if (window.doNotTrack) {
870      return window.doNotTrack
871    } else {
872      return options.NOT_AVAILABLE
873    }
874  }
875  // This is a crude and primitive touch screen detection.
876  // It's not possible to currently reliably detect the  availability of a touch screen
877  // with a JS, without actually subscribing to a touch event.
878  // http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
879  // https://github.com/Modernizr/Modernizr/issues/548
880  // method returns an array of 3 values:
881  // maxTouchPoints, the success or failure of creating a TouchEvent,
882  // and the availability of the 'ontouchstart' property
883
884  var getTouchSupport = function () {
885    var maxTouchPoints = 0
886    var touchEvent
887    if (typeof navigator.maxTouchPoints !== 'undefined') {
888      maxTouchPoints = navigator.maxTouchPoints
889    } else if (typeof navigator.msMaxTouchPoints !== 'undefined') {
890      maxTouchPoints = navigator.msMaxTouchPoints
891    }
892    try {
893      document.createEvent('TouchEvent')
894      touchEvent = true
895    } catch (_) {
896      touchEvent = false
897    }
898    var touchStart = 'ontouchstart' in window
899    return [maxTouchPoints, touchEvent, touchStart]
900  }
901  // https://www.browserleaks.com/canvas#how-does-it-work
902
903  var getCanvasFp = function (options) {
904    var result = []
905    // Very simple now, need to make it more complex (geo shapes etc)
906    var canvas = document.createElement('canvas')
907    canvas.width = 2000
908    canvas.height = 200
909    canvas.style.display = 'inline'
910    var ctx = canvas.getContext('2d')
911    // detect browser support of canvas winding
912    // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
913    // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/canvas/winding.js
914    ctx.rect(0, 0, 10, 10)
915    ctx.rect(2, 2, 6, 6)
916    result.push('canvas winding:' + ((ctx.isPointInPath(5, 5, 'evenodd') === false) ? 'yes' : 'no'))
917
918    ctx.textBaseline = 'alphabetic'
919    ctx.fillStyle = '#f60'
920    ctx.fillRect(125, 1, 62, 20)
921    ctx.fillStyle = '#069'
922    // https://github.com/Valve/fingerprintjs2
vendor: 15,945 bytes, lines 922-1276
922/issues/66
923    if (options.dontUseFakeFontInCanvas) {
924      ctx.font = '11pt Arial'
925    } else {
926      ctx.font = '11pt no-real-font-123'
927    }
928    ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 2, 15)
929    ctx.fillStyle = 'rgba(102, 204, 0, 0.2)'
930    ctx.font = '18pt Arial'
931    ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 4, 45)
932
933    // canvas blending
934    // http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
935    // http://jsfiddle.net/NDYV8/16/
936    ctx.globalCompositeOperation = 'multiply'
937    ctx.fillStyle = 'rgb(255,0,255)'
938    ctx.beginPath()
939    ctx.arc(50, 50, 50, 0, Math.PI * 2, true)
940    ctx.closePath()
941    ctx.fill()
942    ctx.fillStyle = 'rgb(0,255,255)'
943    ctx.beginPath()
944    ctx.arc(100, 50, 50, 0, Math.PI * 2, true)
945    ctx.closePath()
946    ctx.fill()
947    ctx.fillStyle = 'rgb(255,255,0)'
948    ctx.beginPath()
949    ctx.arc(75, 100, 50, 0, Math.PI * 2, true)
950    ctx.closePath()
951    ctx.fill()
952    ctx.fillStyle = 'rgb(255,0,255)'
953    // canvas winding
954    // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
955    // http://jsfiddle.net/NDYV8/19/
956    ctx.arc(75, 75, 75, 0, Math.PI * 2, true)
957    ctx.arc(75, 75, 25, 0, Math.PI * 2, true)
958    ctx.fill('evenodd')
959
960    if (canvas.toDataURL) { result.push('canvas fp:' + canvas.toDataURL()) }
961    return result
962  }
963  var getWebglFp = function () {
964    var gl
965    var fa2s = function (fa) {
966      gl.clearColor(0.0, 0.0, 0.0, 1.0)
967      gl.enable(gl.DEPTH_TEST)
968      gl.depthFunc(gl.LEQUAL)
969      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
970      return '[' + fa[0] + ', ' + fa[1] + ']'
971    }
972    var maxAnisotropy = function (gl) {
973      var ext = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic')
974      if (ext) {
975        var anisotropy = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT)
976        if (anisotropy === 0) {
977          anisotropy = 2
978        }
979        return anisotropy
980      } else {
981        return null
982      }
983    }
984
985    gl = getWebglCanvas()
986    if (!gl) { return null }
987    // WebGL fingerprinting is a combination of techniques, found in MaxMind antifraud script & Augur fingerprinting.
988    // First it draws a gradient object with shaders and convers the image to the Base64 string.
989    // 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
990    // 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.
991    var result = []
992    var vShaderTemplate = 'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}'
993    var fShaderTemplate = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}'
994    var vertexPosBuffer = gl.createBuffer()
995    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer)
996    var vertices = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0])
997    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
998    vertexPosBuffer.itemSize = 3
999    vertexPosBuffer.numItems = 3
1000    var program = gl.createProgram()
1001    var vshader = gl.createShader(gl.VERTEX_SHADER)
1002    gl.shaderSource(vshader, vShaderTemplate)
1003    gl.compileShader(vshader)
1004    var fshader = gl.createShader(gl.FRAGMENT_SHADER)
1005    gl.shaderSource(fshader, fShaderTemplate)
1006    gl.compileShader(fshader)
1007    gl.attachShader(program, vshader)
1008    gl.attachShader(program, fshader)
1009    gl.linkProgram(program)
1010    gl.useProgram(program)
1011    program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex')
1012    program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset')
1013    gl.enableVertexAttribArray(program.vertexPosArray)
1014    gl.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, gl.FLOAT, !1, 0, 0)
1015    gl.uniform2f(program.offsetUniform, 1, 1)
1016    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems)
1017    try {
1018      result.push(gl.canvas.toDataURL())
1019    } catch (e) {
1020      /* .toDataURL may be absent or broken (blocked by extension) */
1021    }
1022    result.push('extensions:' + (gl.getSupportedExtensions() || []).join(';'))
1023    result.push('webgl aliased line width range:' + fa2s(gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE)))
1024    result.push('webgl aliased point size range:' + fa2s(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)))
1025    result.push('webgl alpha bits:' + gl.getParameter(gl.ALPHA_BITS))
1026    result.push('webgl antialiasing:' + (gl.getContextAttributes().antialias ? 'yes' : 'no'))
1027    result.push('webgl blue bits:' + gl.getParameter(gl.BLUE_BITS))
1028    result.push('webgl depth bits:' + gl.getParameter(gl.DEPTH_BITS))
1029    result.push('webgl green bits:' + gl.getParameter(gl.GREEN_BITS))
1030    result.push('webgl max anisotropy:' + maxAnisotropy(gl))
1031    result.push('webgl max combined texture image units:' + gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS))
1032    result.push('webgl max cube map texture size:' + gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE))
1033    result.push('webgl max fragment uniform vectors:' + gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS))
1034    result.push('webgl max render buffer size:' + gl.getParameter(gl.MAX_RENDERBUFFER_SIZE))
1035    result.push('webgl max texture image units:' + gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS))
1036    result.push('webgl max texture size:' + gl.getParameter(gl.MAX_TEXTURE_SIZE))
1037    result.push('webgl max varying vectors:' + gl.getParameter(gl.MAX_VARYING_VECTORS))
1038    result.push('webgl max vertex attribs:' + gl.getParameter(gl.MAX_VERTEX_ATTRIBS))
1039    result.push('webgl max vertex texture image units:' + gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS))
1040    result.push('webgl max vertex uniform vectors:' + gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS))
1041    result.push('webgl max viewport dims:' + fa2s(gl.getParameter(gl.MAX_VIEWPORT_DIMS)))
1042    result.push('webgl red bits:' + gl.getParameter(gl.RED_BITS))
1043    result.push('webgl renderer:' + gl.getParameter(gl.RENDERER))
1044    result.push('webgl shading language version:' + gl.getParameter(gl.SHADING_LANGUAGE_VERSION))
1045    result.push('webgl stencil bits:' + gl.getParameter(gl.STENCIL_BITS))
1046    result.push('webgl vendor:' + gl.getParameter(gl.VENDOR))
1047    result.push('webgl version:' + gl.getParameter(gl.VERSION))
1048
1049    try {
1050      // Add the unmasked vendor and unmasked renderer if the debug_renderer_info extension is available
1051      var extensionDebugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info')
1052      if (extensionDebugRendererInfo) {
1053        result.push('webgl unmasked vendor:' + gl.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL))
1054        result.push('webgl unmasked renderer:' + gl.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL))
1055      }
1056    } catch (e) { /* squelch */ }
1057
1058    if (!gl.getShaderPrecisionFormat) {
1059      loseWebglContext(gl)
1060      return result
1061    }
1062
1063    each(['FLOAT', 'INT'], function (numType) {
1064      each(['VERTEX', 'FRAGMENT'], function (shader) {
1065        each(['HIGH', 'MEDIUM', 'LOW'], function (numSize) {
1066          each(['precision', 'rangeMin', 'rangeMax'], function (key) {
1067            var format = gl.getShaderPrecisionFormat(gl[shader + '_SHADER'], gl[numSize + '_' + numType])[key]
1068            if (key !== 'precision') {
1069              key = 'precision ' + key
1070            }
1071            var line = ['webgl ', shader.toLowerCase(), ' shader ', numSize.toLowerCase(), ' ', numType.toLowerCase(), ' ', key, ':', format].join('')
1072            result.push(line)
1073          })
1074        })
1075      })
1076    })
1077    loseWebglContext(gl)
1078    return result
1079  }
1080  var getWebglVendorAndRenderer = function () {
1081    /* This a subset of the WebGL fingerprint with a lot of entropy, while being reasonably browser-independent */
1082    try {
1083      var glContext = getWebglCanvas()
1084      var extensionDebugRendererInfo = glContext.getExtension('WEBGL_debug_renderer_info')
1085      var params = glContext.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL) + '~' + glContext.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL)
1086      loseWebglContext(glContext)
1087      return params
1088    } catch (e) {
1089      return null
1090    }
1091  }
1092  var getAdBlock = function () {
1093    var ads = document.createElement('div')
1094    ads.innerHTML = '&nbsp;'
1095    ads.className = 'adsbox'
1096    var result = false
1097    try {
1098      // body may not exist, that's why we need try/catch
1099      document.body.appendChild(ads)
1100      result = document.getElementsByClassName('adsbox')[0].offsetHeight === 0
1101      document.body.removeChild(ads)
1102    } catch (e) {
1103      result = false
1104    }
1105    return result
1106  }
1107  var getHasLiedLanguages = function () {
1108    // We check if navigator.language is equal to the first language of navigator.languages
1109    // navigator.languages is undefined on IE11 (and potentially older IEs)
1110    if (typeof navigator.languages !== 'undefined') {
1111      try {
1112        var firstLanguages = navigator.languages[0].substr(0, 2)
1113        if (firstLanguages !== navigator.language.substr(0, 2)) {
1114          return true
1115        }
1116      } catch (err) {
1117        return true
1118      }
1119    }
1120    return false
1121  }
1122  var getHasLiedResolution = function () {
1123    return window.screen.width < window.screen.availWidth || window.screen.height < window.screen.availHeight
1124  }
1125  var getHasLiedOs = function () {
1126    var userAgent = navigator.userAgent.toLowerCase()
1127    var oscpu = navigator.oscpu
1128    var platform = navigator.platform.toLowerCase()
1129    var os
1130    // We extract the OS from the user agent (respect the order of the if else if statement)
1131    if (userAgent.indexOf('windows phone') >= 0) {
1132      os = 'Windows Phone'
1133    } else if (userAgent.indexOf('windows') >= 0 || userAgent.indexOf('win16') >= 0 || userAgent.indexOf('win32') >= 0 || userAgent.indexOf('win64') >= 0 || userAgent.indexOf('win95') >= 0 || userAgent.indexOf('win98') >= 0 || userAgent.indexOf('winnt') >= 0 || userAgent.indexOf('wow64') >= 0) {
1134      os = 'Windows'
1135    } else if (userAgent.indexOf('android') >= 0) {
1136      os = 'Android'
1137    } else if (userAgent.indexOf('linux') >= 0 || userAgent.indexOf('cros') >= 0 || userAgent.indexOf('x11') >= 0) {
1138      os = 'Linux'
1139    } else if (userAgent.indexOf('iphone') >= 0 || userAgent.indexOf('ipad') >= 0 || userAgent.indexOf('ipod') >= 0 || userAgent.indexOf('crios') >= 0 || userAgent.indexOf('fxios') >= 0) {
1140      os = 'iOS'
1141    } else if (userAgent.indexOf('macintosh') >= 0 || userAgent.indexOf('mac_powerpc)') >= 0) {
1142      os = 'Mac'
1143    } else {
1144      os = 'Other'
1145    }
1146    // We detect if the person uses a touch device
1147    var mobileDevice = (('ontouchstart' in window) ||
1148      (navigator.maxTouchPoints > 0) ||
1149      (navigator.msMaxTouchPoints > 0))
1150
1151    if (mobileDevice && os !== 'Windows' && os !== 'Windows Phone' && os !== 'Android' && os !== 'iOS' && os !== 'Other' && userAgent.indexOf('cros') === -1) {
1152      return true
1153    }
1154
1155    // We compare oscpu with the OS extracted from the UA
1156    if (typeof oscpu !== 'undefined') {
1157      oscpu = oscpu.toLowerCase()
1158      if (oscpu.indexOf('win') >= 0 && os !== 'Windows' && os !== 'Windows Phone') {
1159        return true
1160      } else if (oscpu.indexOf('linux') >= 0 && os !== 'Linux' && os !== 'Android') {
1161        return true
1162      } else if (oscpu.indexOf('mac') >= 0 && os !== 'Mac' && os !== 'iOS') {
1163        return true
1164      } else if ((oscpu.indexOf('win') === -1 && oscpu.indexOf('linux') === -1 && oscpu.indexOf('mac') === -1) !== (os === 'Other')) {
1165        return true
1166      }
1167    }
1168
1169    // We compare platform with the OS extracted from the UA
1170    if (platform.indexOf('win') >= 0 && os !== 'Windows' && os !== 'Windows Phone') {
1171      return true
1172    } else if ((platform.indexOf('linux') >= 0 || platform.indexOf('android') >= 0 || platform.indexOf('pike') >= 0) && os !== 'Linux' && os !== 'Android') {
1173      return true
1174    } else if ((platform.indexOf('mac') >= 0 || platform.indexOf('ipad') >= 0 || platform.indexOf('ipod') >= 0 || platform.indexOf('iphone') >= 0) && os !== 'Mac' && os !== 'iOS') {
1175      return true
1176    } else if (platform.indexOf('arm') >= 0 && os === 'Windows Phone') {
1177      return false
1178    } else if (platform.indexOf('pike') >= 0 && userAgent.indexOf('opera mini') >= 0) {
1179      return false
1180    } else {
1181      var platformIsOther = platform.indexOf('win') < 0 &&
1182        platform.indexOf('linux') < 0 &&
1183        platform.indexOf('mac') < 0 &&
1184        platform.indexOf('iphone') < 0 &&
1185        platform.indexOf('ipad') < 0 &&
1186        platform.indexOf('ipod') < 0
1187      if (platformIsOther !== (os === 'Other')) {
1188        return true
1189      }
1190    }
1191
1192    return typeof navigator.plugins === 'undefined' && os !== 'Windows' && os !== 'Windows Phone'
1193  }
1194  var getHasLiedBrowser = function () {
1195    var userAgent = navigator.userAgent.toLowerCase()
1196    var productSub = navigator.productSub
1197
1198    // we extract the browser from the user agent (respect the order of the tests)
1199    var browser
1200    if (userAgent.indexOf('edge/') >= 0 || userAgent.indexOf('iemobile/') >= 0) {
1201      // Unreliable, different versions use EdgeHTML, Webkit, Blink, etc.
1202      return false
1203    } else if (userAgent.indexOf('opera mini') >= 0) {
1204      // Unreliable, different modes use Presto, WebView, Webkit, etc.
1205      return false
1206    } else if (userAgent.indexOf('firefox/') >= 0) {
1207      browser = 'Firefox'
1208    } else if (userAgent.indexOf('opera/') >= 0 || userAgent.indexOf(' opr/') >= 0) {
1209      browser = 'Opera'
1210    } else if (userAgent.indexOf('chrome/') >= 0) {
1211      browser = 'Chrome'
1212    } else if (userAgent.indexOf('safari/') >= 0) {
1213      if (userAgent.indexOf('android 1.') >= 0 || userAgent.indexOf('android 2.') >= 0 || userAgent.indexOf('android 3.') >= 0 || userAgent.indexOf('android 4.') >= 0) {
1214        browser = 'AOSP'
1215      } else {
1216        browser = 'Safari'
1217      }
1218    } else if (userAgent.indexOf('trident/') >= 0) {
1219      browser = 'Internet Explorer'
1220    } else {
1221      browser = 'Other'
1222    }
1223
1224    if ((browser === 'Chrome' || browser === 'Safari' || browser === 'Opera') && productSub !== '20030107') {
1225      return true
1226    }
1227
1228    // eslint-disable-next-line no-eval
1229    var tempRes = eval.toString().length
1230    if (tempRes === 37 && browser !== 'Safari' && browser !== 'Firefox' && browser !== 'Other') {
1231      return true
1232    } else if (tempRes === 39 && browser !== 'Internet Explorer' && browser !== 'Other') {
1233      return true
1234    } else if (tempRes === 33 && browser !== 'Chrome' && browser !== 'AOSP' && browser !== 'Opera' && browser !== 'Other') {
1235      return true
1236    }
1237
1238    // We create an error to see how it is handled
1239    var errFirefox
1240    try {
1241      // eslint-disable-next-line no-throw-literal
1242      throw 'a'
1243    } catch (err) {
1244      try {
1245        err.toSource()
1246        errFirefox = true
1247      } catch (errOfErr) {
1248        errFirefox = false
1249      }
1250    }
1251    return errFirefox && browser !== 'Firefox' && browser !== 'Other'
1252  }
1253  var isCanvasSupported = function () {
1254    var elem = document.createElement('canvas')
1255    return !!(elem.getContext && elem.getContext('2d'))
1256  }
1257  var isWebGlSupported = function () {
1258    // code taken from Modernizr
1259    if (!isCanvasSupported()) {
1260      return false
1261    }
1262
1263    var glContext = getWebglCanvas()
1264    var isSupported = !!window.WebGLRenderingContext && !!glContext
1265    loseWebglContext(glContext)
1266    return isSupported
1267  }
1268  var isIE = function () {
1269    if (navigator.appName === 'Microsoft Internet Explorer') {
1270      return true
1271    } else if (navigator.appName === 'Netscape' && /Trident/.test(navigator.userAgent)) { // IE 11
1272      return true
1273    }
1274    return false
1275  }
1276  var 
vendor: 7,026 bytes, lines 1276-1466
1276hasSwfObjectLoaded = function () {
1277    return typeof window.swfobject !== 'undefined'
1278  }
1279  var hasMinFlashInstalled = function () {
1280    return window.swfobject.hasFlashPlayerVersion('9.0.0')
1281  }
1282  var addFlashDivNode = function (options) {
1283    var node = document.createElement('div')
1284    node.setAttribute('id', options.fonts.swfContainerId)
1285    document.body.appendChild(node)
1286  }
1287  var loadSwfAndDetectFonts = function (done, options) {
1288    var hiddenCallback = '___fp_swf_loaded'
1289    window[hiddenCallback] = function (fonts) {
1290      done(fonts)
1291    }
1292    var id = options.fonts.swfContainerId
1293    addFlashDivNode()
1294    var flashvars = { onReady: hiddenCallback }
1295    var flashparams = { allowScriptAccess: 'always', menu: 'false' }
1296    window.swfobject.embedSWF(options.fonts.swfPath, id, '1', '1', '9.0.0', false, flashvars, flashparams, {})
1297  }
1298  var getWebglCanvas = function () {
1299    var canvas = document.createElement('canvas')
1300    var gl = null
1301    try {
1302      gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
1303    } catch (e) { /* squelch */ }
1304    if (!gl) { gl = null }
1305    return gl
1306  }
1307  var loseWebglContext = function (context) {
1308    var loseContextExtension = context.getExtension('WEBGL_lose_context')
1309    if (loseContextExtension != null) {
1310      loseContextExtension.loseContext()
1311    }
1312  }
1313
1314  var components = [
1315    { key: 'userAgent', getData: UserAgent },
1316    { key: 'webdriver', getData: webdriver },
1317    { key: 'language', getData: languageKey },
1318    { key: 'colorDepth', getData: colorDepthKey },
1319    { key: 'deviceMemory', getData: deviceMemoryKey },
1320    { key: 'pixelRatio', getData: pixelRatioKey },
1321    { key: 'hardwareConcurrency', getData: hardwareConcurrencyKey },
1322    { key: 'screenResolution', getData: screenResolutionKey },
1323    { key: 'availableScreenResolution', getData: availableScreenResolutionKey },
1324    { key: 'timezoneOffset', getData: timezoneOffset },
1325    { key: 'timezone', getData: timezone },
1326    { key: 'sessionStorage', getData: sessionStorageKey },
1327    { key: 'localStorage', getData: localStorageKey },
1328    { key: 'indexedDb', getData: indexedDbKey },
1329    { key: 'addBehavior', getData: addBehaviorKey },
1330    { key: 'openDatabase', getData: openDatabaseKey },
1331    { key: 'cpuClass', getData: cpuClassKey },
1332    { key: 'platform', getData: platformKey },
1333    { key: 'doNotTrack', getData: doNotTrackKey },
1334    { key: 'plugins', getData: pluginsComponent },
1335    { key: 'canvas', getData: canvasKey },
1336    { key: 'webgl', getData: webglKey },
1337    { key: 'webglVendorAndRenderer', getData: webglVendorAndRendererKey },
1338    { key: 'adBlock', getData: adBlockKey },
1339    { key: 'hasLiedLanguages', getData: hasLiedLanguagesKey },
1340    { key: 'hasLiedResolution', getData: hasLiedResolutionKey },
1341    { key: 'hasLiedOs', getData: hasLiedOsKey },
1342    { key: 'hasLiedBrowser', getData: hasLiedBrowserKey },
1343    { key: 'touchSupport', getData: touchSupportKey },
1344    { key: 'fonts', getData: jsFontsKey, pauseBefore: true },
1345    { key: 'fontsFlash', getData: flashFontsKey, pauseBefore: true },
1346    { key: 'audio', getData: audioKey },
1347    { key: 'enumerateDevices', getData: enumerateDevicesKey }
1348  ]
1349
1350  var Fingerprint2 = function (options) {
1351    throw new Error("'new Fingerprint()' is deprecated, see https://github.com/Valve/fingerprintjs2#upgrade-guide-from-182-to-200")
1352  }
1353
1354  Fingerprint2.get = function (options, callback) {
1355    if (!callback) {
1356      callback = options
1357      options = {}
1358    } else if (!options) {
1359      options = {}
1360    }
1361    extendSoft(options, defaultOptions)
1362    options.components = options.extraComponents.concat(components)
1363
1364    var keys = {
1365      data: [],
1366      addPreprocessedComponent: function (key, value) {
1367        if (typeof options.preprocessor === 'function') {
1368          value = options.preprocessor(key, value)
1369        }
1370        keys.data.push({ key: key, value: value })
1371      }
1372    }
1373
1374    var i = -1
1375    var chainComponents = function (alreadyWaited) {
1376      i += 1
1377      if (i >= options.components.length) { // on finish
1378        callback(keys.data)
1379        return
1380      }
1381      var component = options.components[i]
1382
1383      if (options.excludes[component.key]) {
1384        chainComponents(false) // skip
1385        return
1386      }
1387
1388      if (!alreadyWaited && component.pauseBefore) {
1389        i -= 1
1390        setTimeout(function () {
1391          chainComponents(true)
1392        }, 1)
1393        return
1394      }
1395
1396      try {
1397        component.getData(function (value) {
1398          keys.addPreprocessedComponent(component.key, value)
1399          chainComponents(false)
1400        }, options)
1401      } catch (error) {
1402        // main body error
1403        keys.addPreprocessedComponent(component.key, String(error))
1404        chainComponents(false)
1405      }
1406    }
1407
1408    chainComponents(false)
1409  }
1410
1411  Fingerprint2.getPromise = function (options) {
1412    return new Promise(function (resolve, reject) {
1413      Fingerprint2.get(options, resolve)
1414    })
1415  }
1416
1417  Fingerprint2.getV18 = function (options, callback) {
1418    if (callback == null) {
1419      callback = options
1420      options = {}
1421    }
1422    return Fingerprint2.get(options, function (components) {
1423      var newComponents = []
1424      for (var i = 0; i < components.length; i++) {
1425        var component = components[i]
1426        if (component.value === (options.NOT_AVAILABLE || 'not available')) {
1427          newComponents.push({ key: component.key, value: 'unknown' })
1428        } else if (component.key === 'plugins') {
1429          newComponents.push({
1430            key: 'plugins',
1431            value: map(component.value, function (p) {
1432              var mimeTypes = map(p[2], function (mt) {
1433                if (mt.join) { return mt.join('~') }
1434                return mt
1435              }).join(',')
1436              return [p[0], p[1], mimeTypes].join('::')
1437            })
1438          })
1439        } else if (['canvas', 'webgl'].indexOf(component.key) !== -1 && Array.isArray(component.value)) {
1440          // sometimes WebGL returns error in headless browsers (during CI testing for example)
1441          // so we need to join only if the values are array
1442          newComponents.push({ key: component.key, value: component.value.join('~') })
1443        } else if (['sessionStorage', 'localStorage', 'indexedDb', 'addBehavior', 'openDatabase'].indexOf(component.key) !== -1) {
1444          if (component.value) {
1445            newComponents.push({ key: component.key, value: 1 })
1446          } else {
1447            // skip
1448            continue
1449          }
1450        } else {
1451          if (component.value) {
1452            newComponents.push(component.value.join ? { key: component.key, value: component.value.join(';') } : component)
1453          } else {
1454            newComponents.push({ key: component.key, value: component.value })
1455          }
1456        }
1457      }
1458      var murmur = x64hash128(map(newComponents, function (component) { return component.value }).join('~~~'), 31)
1459      callback(murmur, newComponents)
1460    })
1461  }
1462
1463  Fingerprint2.x64hash128 = x64hash128
1464  Fingerprint2.VERSION = '2.1.0'
1465  return Fingerprint2
1466})

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.