PageSourceSearch

https://lajkto.eu/themes/default/js/Fingerprintjs2/fingerprint2.js?version=3.1.1

js lajkto.eu collected 2026-10-04 10:41:31 UTC 60,693 bytes, 1,411 lines download raw bytes

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

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.